Skip to content

Commit

Permalink
feat: add Flex shim and flex item mixins (#26997)
Browse files Browse the repository at this point in the history
* cfeat: add Flex shim and flex item mixins

* chore: add api

* chore: remove lodash

* chore: optional chaining
  • Loading branch information
chpalac authored Feb 28, 2023
1 parent d8c2f34 commit 569887e
Show file tree
Hide file tree
Showing 10 changed files with 572 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,22 @@ import { ObjectOf } from '@fluentui/react-northstar';
import { ObjectShorthandValue } from '@fluentui/react-northstar';
import * as React_2 from 'react';

// @public (undocumented)
export const Flex: React_2.ForwardRefExoticComponent<React_2.HTMLAttributes<HTMLElement> & FlexProps & React_2.RefAttributes<HTMLDivElement>>;

// @public (undocumented)
export const flexClassName = "fui-Flex";

// @public (undocumented)
export const flexItem: {
align: (value: 'auto' | 'start' | 'end' | 'center' | 'baseline' | 'stretch') => GriffelStyle;
size: (value: 'half' | 'quarter' | 'small' | 'medium' | 'large') => GriffelStyle;
grow: (flexGrow: boolean | number) => GriffelStyle | undefined;
shrink: (flexShrink: boolean | number) => GriffelStyle | undefined;
pushRow: () => GriffelStyle;
pushColumn: () => GriffelStyle;
};

// @public (undocumented)
export const FormFieldShim: React_2.ForwardRefExoticComponent<{
errorMessage?: WithContent | undefined;
Expand Down Expand Up @@ -65,6 +81,9 @@ export const spinner: {
v0SpinnerLabelStyle: () => GriffelStyle;
};

// @public (undocumented)
export const useFlexStyles: () => Record<"fill" | "wrap" | "inline" | "flex" | "column" | "alignItemsFlexStart" | "alignItemsCenter" | "alignItemsFlexEnd" | "alignItemsStretch" | "justifyContentFlexStart" | "justifyContentCenter" | "justifyContentFlexEnd" | "justifyContentStretch" | "justifyContentSpaceAround" | "justifyContentSpaceBetween" | "justifyContentSpaceEvenly" | "gapForColumnFlexSmall" | "gapForColumnFlexSmaller" | "gapForColumnFlexMedium" | "gapForColumnFlexLarge" | "gapForRowFlexSmall" | "gapForRowFlexSmaller" | "gapForRowFlexMedium" | "gapForRowFlexLarge" | "paddingMedium", string>;

// @public (undocumented)
export const useGridStyles: () => Record<"grid" | "onlyRows" | "rows1" | "rows2" | "rows3" | "columns1" | "columns2" | "columns3" | "columnsDefault", string>;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { makeStyles, shorthands } from '@fluentui/react-components';

const gapValues = {
smaller: '8px',
small: '10px',
medium: '15px',
large: '30px',
};

const paddingValues = {
medium: '10px',
};

export const useFlexStyles = makeStyles({
flex: {
display: 'flex',
},
inline: {
display: 'inline-flex',
},
column: {
flexDirection: 'column',
},
alignItemsFlexStart: {
alignItems: 'flex-start',
},
alignItemsCenter: {
alignItems: 'center',
},
alignItemsFlexEnd: {
alignItems: 'flex-end',
},
alignItemsStretch: {
alignItems: 'stretch',
},
justifyContentFlexStart: {
justifyContent: 'flex-start',
},
justifyContentCenter: {
justifyContent: 'center',
},
justifyContentFlexEnd: {
justifyContent: 'flex-end',
},
justifyContentStretch: {
justifyContent: 'stretch',
},
justifyContentSpaceAround: {
justifyContent: 'space-around',
},
justifyContentSpaceBetween: {
justifyContent: 'space-between',
},
justifyContentSpaceEvenly: {
justifyContent: 'space-evenly',
},
wrap: {
flexWrap: 'wrap',
},
fill: {
width: '100%',
height: '100%',
},
gapForColumnFlexSmall: {
rowGap: gapValues.small,
},
gapForColumnFlexSmaller: {
rowGap: gapValues.smaller,
},
gapForColumnFlexMedium: {
rowGap: gapValues.medium,
},
gapForColumnFlexLarge: {
rowGap: gapValues.large,
},
gapForRowFlexSmall: {
columnGap: gapValues.small,
},
gapForRowFlexSmaller: {
columnGap: gapValues.smaller,
},
gapForRowFlexMedium: {
columnGap: gapValues.medium,
},
gapForRowFlexLarge: {
columnGap: gapValues.large,
},
paddingMedium: {
...shorthands.padding(paddingValues.medium),
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import '@testing-library/jest-dom';

import { isConformant } from '@fluentui/react-conformance';
import { render } from '@testing-library/react';
import * as React from 'react';

import { Flex } from './Flex';

describe('Flex', () => {
isConformant({
Component: Flex,
componentPath: module!.filename.replace('.test', ''),
displayName: 'Flex',
disabledTests: ['has-docblock', 'has-top-level-file', 'component-has-static-classnames-object'],
});

it('renders a default state', () => {
const { getByText } = render(<Flex>Test</Flex>);
const textElement = getByText('Test');
expect(textElement.nodeName).toBe('DIV');
expect(textElement).toHaveStyle(`
display: flex
`);
});

it('applies the column style', () => {
const { getByText } = render(<Flex column={true}>Test</Flex>);

const textElement = getByText('Test');
expect(textElement).toHaveStyle(`
flex-direction: column
`);
});

it('applies the fill style', () => {
const { getByText } = render(<Flex fill={true}>Test</Flex>);

const textElement = getByText('Test');
expect(textElement).toHaveStyle(`
width: 100%
`);
expect(textElement).toHaveStyle(`
height: 100%
`);
});

it('applies the gap style', () => {
const { getByText } = render(<Flex gap="gap.small">Test</Flex>);

const textElement = getByText('Test');

expect(textElement).toHaveStyle(`
columnGap: 10px
`);
});

it('applies the hAlign style for row', () => {
const { getByText } = render(<Flex hAlign="center">Test</Flex>);

const textElement = getByText('Test');
expect(textElement).toHaveStyle(`
justify-content: center
`);
});

it('applies the hAlign style for column', () => {
const { getByText } = render(
<Flex hAlign="center" column={true}>
Test
</Flex>,
);

const textElement = getByText('Test');
expect(textElement).toHaveStyle(`
align-items: center
`);
});

it('applies the inline style', () => {
const { getByText } = render(<Flex inline={true}>Test</Flex>);

const textElement = getByText('Test');
expect(textElement).toHaveStyle(`
display: inline-flex
`);
});

it('applies the padding style', () => {
const { getByText } = render(<Flex padding="padding.medium">Test</Flex>);

const textElement = getByText('Test');
expect(textElement).toHaveStyle(`
padding-left: 10px
`);
});

it('applies the space style', () => {
const { getByText } = render(<Flex space="around">Test</Flex>);

const textElement = getByText('Test');
expect(textElement).toHaveStyle(`
justify-content: space-around
`);
});

it('applies the vAlign style for row', () => {
const { getByText } = render(<Flex vAlign="center">Test</Flex>);

const textElement = getByText('Test');
expect(textElement).toHaveStyle(`
align-items: center
`);
});

it('applies the vAlign style for column', () => {
const { getByText } = render(
<Flex vAlign="center" column={true}>
Test
</Flex>,
);

const textElement = getByText('Test');
expect(textElement).toHaveStyle(`
justify-content: center
`);
});

it('applies the wrap style', () => {
const { getByText } = render(<Flex wrap={true}>Test</Flex>);

const textElement = getByText('Test');
expect(textElement).toHaveStyle(`
flex-wrap: wrap
`);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { mergeClasses } from '@fluentui/react-components';
import * as React from 'react';

import { useFlexStyles } from './Flex.styles';

export interface FlexProps {
/** Defines if container should be inline element. */
inline?: boolean;

/** Sets vertical flow direction. */
column?: boolean;

/** Allows overflow items to wrap on the next container's line. */
wrap?: boolean;

/** Controls items alignment in horizontal direction. */
hAlign?: 'start' | 'center' | 'end' | 'stretch';

/** Controls items alignment in vertical direction. */
vAlign?: 'start' | 'center' | 'end' | 'stretch';

/** Defines strategy for distributing remaining space between items. */
space?: 'around' | 'between' | 'evenly';

/** Defines gap between each two adjacent child items. */
gap?: 'gap.smaller' | 'gap.small' | 'gap.medium' | 'gap.large';

/** Defines container's padding. */
padding?: 'padding.medium';

/** Orders container to fill all parent's space available. */
fill?: boolean;
}

export const flexClassName = 'fui-Flex';

export const Flex = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLElement> & FlexProps>((props, ref) => {
const { children, column, fill, gap, hAlign, inline, padding, space, vAlign, wrap, className, ...rest } = props;
const classes = useFlexStyles();

const classMaps = React.useMemo(
() => ({
alignItems: {
start: classes.alignItemsFlexStart,
center: classes.alignItemsCenter,
end: classes.alignItemsFlexEnd,
stretch: classes.alignItemsCenter,
},
justifyContent: {
start: classes.justifyContentFlexStart,
center: classes.justifyContentCenter,
end: classes.justifyContentFlexEnd,
stretch: classes.justifyContentStretch,
},
justifyContentSpace: {
around: classes.justifyContentSpaceAround,
between: classes.justifyContentSpaceBetween,
evenly: classes.justifyContentSpaceEvenly,
},
gapForColumnFlex: {
'gap.smaller': classes.gapForColumnFlexSmaller,
'gap.small': classes.gapForColumnFlexSmall,
'gap.medium': classes.gapForColumnFlexMedium,
'gap.large': classes.gapForColumnFlexLarge,
},
gapRow: {
'gap.smaller': classes.gapForRowFlexSmaller,
'gap.small': classes.gapForRowFlexSmall,
'gap.medium': classes.gapForRowFlexMedium,
'gap.large': classes.gapForRowFlexLarge,
},
paddings: {
'padding.medium': classes.paddingMedium,
},
}),
[classes],
);

const flexClasses = mergeClasses(
flexClassName,
classes.flex,
inline && classes.inline,
column && classes.column,
hAlign && (column ? classMaps.alignItems[hAlign] : classMaps.justifyContent[hAlign]),
vAlign && (column ? classMaps.justifyContent[vAlign] : classMaps.alignItems[vAlign]),
space && classMaps.justifyContentSpace[space],
wrap && classes.wrap,
fill && classes.fill,
gap && (column ? classMaps.gapForColumnFlex[gap] : classMaps.gapRow[gap]),
padding && classMaps.paddings[padding],
className,
);

const content = React.Children.map(children, child => {
// @ts-expect-error __isFlexItem is added to the React type property by N*
const isFlexItemElement: boolean = child?.type?.__isFlexItem;

return isFlexItemElement
? React.cloneElement(child as React.ReactElement, {
flexDirection: column ? 'column' : 'row',
})
: child;
});

return (
<div ref={ref} className={flexClasses} {...rest}>
{content}
</div>
);
});

Flex.displayName = 'Flex';
Loading

0 comments on commit 569887e

Please sign in to comment.