Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Components: Update Flex #28609

Merged
merged 8 commits into from
Feb 3, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions packages/components/src/flex/block.js
Original file line number Diff line number Diff line change
@@ -2,20 +2,29 @@
* External dependencies
*/
import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { forwardRef } from '@wordpress/element';

/**
* Internal dependencies
*/
import { Block } from './styles/flex-styles';
import { withNextFlexBlock } from './next';

/**
* WordPress dependencies
* @typedef {import('react').HTMLProps<HTMLDivElement> & import('react').RefAttributes<HTMLDivElement>} Props
*/
import { forwardRef } from '@wordpress/element';

/**
* @param {Props} props
* @param {import('react').Ref<HTMLDivElement>} ref
*/
function FlexBlock( { className, ...props }, ref ) {
const classes = classnames( 'components-flex__block', className );

return <Block { ...props } className={ classes } ref={ ref } />;
}

export default forwardRef( FlexBlock );
export default withNextFlexBlock( forwardRef( FlexBlock ) );
19 changes: 18 additions & 1 deletion packages/components/src/flex/index.js
Original file line number Diff line number Diff line change
@@ -12,10 +12,27 @@ import { forwardRef } from '@wordpress/element';
* Internal dependencies
*/
import { Flex as BaseFlex } from './styles/flex-styles';
import { withNextFlex } from './next';

export { default as FlexBlock } from './block';
export { default as FlexItem } from './item';

/* eslint-disable jsdoc/valid-types */
/**
* @typedef OwnProps
* @property {import('react').CSSProperties['alignItems'] | 'top' | 'bottom'} [align='center'] Sets align-items. Top and bottom are shorthand for flex-start and flex-end respectively.
* @property {number} [gap=2] Determines the spacing in between children components. The `gap` value is a multiplier to the base value of `4`.
* @property {import('react').CSSProperties['justifyContent'] | 'left' | 'right'} [justify='space-between'] * Sets jusifty-content. Left and right are shorthand for flex-start and flex-end respectively, not the actual CSS value.
* @property {boolean} [isReversed=false] Reversed the flex direction.
*/
/* eslint-enable jsdoc/valid-types */

/** @typedef {OwnProps & import('react').HTMLProps<HTMLDivElement> & import('react').RefAttributes<HTMLDivElement>} Props */

/**
* @param {Props} props
* @param {import('react').Ref<HTMLDivElement>} ref
*/
function FlexComponent(
{
align = 'center',
@@ -42,6 +59,6 @@ function FlexComponent(
);
}

export const Flex = forwardRef( FlexComponent );
export const Flex = withNextFlex( forwardRef( FlexComponent ) );

export default Flex;
15 changes: 12 additions & 3 deletions packages/components/src/flex/item.js
Original file line number Diff line number Diff line change
@@ -3,20 +3,29 @@
*/
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { forwardRef } from '@wordpress/element';

/**
* Internal dependencies
*/
import { Item } from './styles/flex-styles';
import { withNextFlexItem } from './next';

/**
* WordPress dependencies
* @typedef {import('react').RefAttributes<HTMLDivElement> & import('react').HTMLProps<HTMLDivElement>} Props
*/
import { forwardRef } from '@wordpress/element';

/**
* @param {Props} props
* @param {import('react').Ref<HTMLDivElement>} ref
*/
function FlexItem( { className, ...props }, ref ) {
const classes = classnames( 'components-flex__item', className );

return <Item { ...props } className={ classes } ref={ ref } />;
}

export default forwardRef( FlexItem );
export default withNextFlexItem( forwardRef( FlexItem ) );
75 changes: 75 additions & 0 deletions packages/components/src/flex/next.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* Internal dependencies
*/
import { withNext } from '../ui/context';
import {
Flex as NextFlex,
FlexItem as NextFlexItem,
FlexBlock as NextFlexBlock,
} from '../ui/flex';

const Flex = process.env.COMPONENT_SYSTEM_PHASE === 1 ? NextFlex : undefined;
const FlexBlock =
process.env.COMPONENT_SYSTEM_PHASE === 1 ? NextFlexBlock : undefined;
const FlexItem =
process.env.COMPONENT_SYSTEM_PHASE === 1 ? NextFlexItem : undefined;

/**
* @param {import('./index').Props} props Current props.
* @return {import('../ui/flex/types').FlexProps} Next props.
*/
const flexAdapter = ( { isReversed, ...restProps } ) => ( {
// There's no equivalent for `direction` on the original component so we can just translate `isReversed` to it
direction: isReversed ? 'row-reverse' : 'row',
...restProps,
// There's an HTML attribute named `wrap` that will exist in `restProps` so we need to set it to undefined so the default behavior of the next component takes over
wrap: undefined,
} );

/**
* @param {import('./item').Props} props Current props.
* @return {import('../ui/flex/types').FlexItemProps} Next props.
*/
const flexItemAdapter = ( props ) => ( {
...props,
// ensure these are undefined so the default takes over
isBlock: undefined,
display: undefined,
} );

/**
* @param {import('./block').Props} props Current props.
* @return {import('../ui/flex/types').FlexBlockProps} Next props.
*/
const flexBlockAdapter = ( props ) => ( {
...props,
// ensure this is undefined so the default takes over
display: undefined,
} );

/* eslint-disable jsdoc/valid-types */
/**
* @param {import('react').ForwardRefExoticComponent<import('./index').Props>} Current
*/
/* eslint-enable jsdoc/valid-types */
export function withNextFlex( Current ) {
return withNext( Current, Flex, 'WPComponentsFlex', flexAdapter );
}

/* eslint-disable jsdoc/valid-types */
/**
* @param {import('react').ForwardRefExoticComponent<import('./item').Props>} Current
*/
/* eslint-enable jsdoc/valid-types */
export function withNextFlexItem( Current ) {
return withNext( Current, FlexItem, 'WPComponentsFlex', flexItemAdapter );
ItsJonQ marked this conversation as resolved.
Show resolved Hide resolved
}

/* eslint-disable jsdoc/valid-types */
/**
* @param {import('react').ForwardRefExoticComponent<import('./block').Props>} Current
*/
/* eslint-enable jsdoc/valid-types */
export function withNextFlexBlock( Current ) {
return withNext( Current, FlexBlock, 'WPComponentsFlex', flexBlockAdapter );
}
27 changes: 23 additions & 4 deletions packages/components/src/flex/styles/flex-styles.js
Original file line number Diff line number Diff line change
@@ -4,26 +4,39 @@
import { css } from '@emotion/core';
import styled from '@emotion/styled';

/**
* @param {import('..').OwnProps} props
*/
const alignStyle = ( { align } ) => {
if ( align === undefined ) return '';

const aligns = {
top: 'flex-start',
bottom: 'flex-end',
};
const value = aligns[ align ] || align;

const value = aligns[ /** @type {'top' | 'bottom'} */ ( align ) ] || align;

return css( {
alignItems: value,
} );
};

/**
* @param {import('..').OwnProps} props
*/
const justifyStyle = ( { justify, isReversed } ) => {
const justifies = {
left: 'flex-start',
right: 'flex-end',
};
let value = justifies[ justify ] || justify;
let value =
justifies[ /** @type {'left' | 'right'} */ ( justify ) ] || justify;

if ( isReversed && justifies[ justify ] ) {
if (
isReversed &&
justifies[ /** @type {'left' | 'right'} */ ( justify ) ]
) {
value = justify === 'left' ? justifies.right : justifies.left;
}

@@ -32,6 +45,9 @@ const justifyStyle = ( { justify, isReversed } ) => {
} );
};

/**
* @param {import('..').OwnProps} Props
*/
const gapStyle = ( { gap, isReversed } ) => {
const base = 4;
const value = typeof gap === 'number' ? base * gap : base;
@@ -49,6 +65,9 @@ const gapStyle = ( { gap, isReversed } ) => {
`;
};

/**
* @param {import('..').OwnProps} props
*/
const reversedStyles = ( { isReversed } ) => {
if ( ! isReversed ) return '';

@@ -64,8 +83,8 @@ export const Flex = styled.div`
${ alignStyle }
${ justifyStyle }
${ gapStyle }
${ reversedStyles }
${ gapStyle }
`;

export const Item = styled.div`
4 changes: 2 additions & 2 deletions packages/components/src/font-size-picker/index.js
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ import { isNumber, isString } from 'lodash';
import { __ } from '@wordpress/i18n';
import { useInstanceId } from '@wordpress/compose';
import { textColor } from '@wordpress/icons';
import { useMemo } from '@wordpress/element';
import { useMemo, forwardRef } from '@wordpress/element';

/**
* Internal dependencies
@@ -177,4 +177,4 @@ function FontSizePicker(
);
}

export default withNextComponent( FontSizePicker );
export default withNextComponent( forwardRef( FontSizePicker ) );
3 changes: 3 additions & 0 deletions packages/components/src/input-control/input-base.js
Original file line number Diff line number Diff line change
@@ -43,6 +43,7 @@ export function InputBase(
ref
) {
const id = useUniqueId( idProp );
const hideLabel = hideLabelFromVision || ! label;

return (
<Root
@@ -51,6 +52,7 @@ export function InputBase(
isFocused={ isFocused }
labelPosition={ labelPosition }
ref={ ref }
__unstableVersion="next"
>
<LabelWrapper>
<Label
@@ -67,6 +69,7 @@ export function InputBase(
__unstableInputWidth={ __unstableInputWidth }
className="components-input-control__container"
disabled={ disabled }
hideLabel={ hideLabel }
isFocused={ isFocused }
labelPosition={ labelPosition }
>
Original file line number Diff line number Diff line change
@@ -59,6 +59,11 @@ const containerDisabledStyles = ( { disabled } ) => {
return css( { backgroundColor } );
};

// Normalizes the margins from the <Flex /> (components/ui/flex/) container.
const containerMarginStyles = ( { hideLabel } ) => {
return hideLabel ? css( { margin: '0 !important' } ) : null;
};

const containerWidthStyles = ( { __unstableInputWidth, labelPosition } ) => {
if ( ! __unstableInputWidth ) return css( { width: '100%' } );

@@ -82,6 +87,7 @@ export const Container = styled.div`
position: relative;
${ containerDisabledStyles }
${ containerMarginStyles }
${ containerWidthStyles }
`;

1 change: 1 addition & 0 deletions packages/components/src/text/index.js
Original file line number Diff line number Diff line change
@@ -17,4 +17,5 @@ const Text = styled.p(
text
);

// @ts-ignore Text _is_ forwarded ref but the styled component definition doesn't include $$typeof so we'll just ignore it here
export default withNextComponent( Text );
2 changes: 1 addition & 1 deletion packages/components/src/text/next.js
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ export const adapter = ( { as, variant, ...restProps } ) => ( {

/* eslint-disable jsdoc/valid-types */
/**
* @param {import('react').ComponentType<AdaptedTextProps>} Current
* @param {import('react').ForwardRefExoticComponent<AdaptedTextProps>} Current
*/
/* eslint-enable jsdoc/valid-types */
export function withNextComponent( Current ) {
4 changes: 2 additions & 2 deletions packages/components/src/ui/context/with-next.js
Original file line number Diff line number Diff line change
@@ -6,13 +6,13 @@ import { contextConnect, useContextSystem } from '@wp-g2/context';
/**
* @template {{}} TCurrentProps
* @template {{}} TNextProps
* @param {import('react').ComponentType<TCurrentProps>} CurrentComponent
* @param {import('react').ForwardRefExoticComponent<TCurrentProps>} CurrentComponent
* @param {import('react').ComponentType<TNextProps>} NextComponent
* @param {string} namespace
* @param {(props: TCurrentProps) => TNextProps} adapter
*/
export function withNext(
CurrentComponent = () => null,
CurrentComponent,
NextComponent = () => null,
namespace = 'Component',
adapter = ( p ) => /** @type {any} */ ( p )
156 changes: 156 additions & 0 deletions packages/components/src/ui/flex/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# Flex

`Flex` is a primitive layout component that adaptively aligns child content horizontally or vertically. `Flex` powers components like `HStack` and `VStack`.

## Usage

`Flex` is used with any of it's two sub-components, `FlexItem` and `FlexBlock`.

```jsx live
import { Flex, FlexItem, FlexBlock, Text, View } from '@wordpress/components/ui';

function Example() {
return (
<Flex>
<FlexItem>
<View css={[ui.background.blue]}>
<Text>Ana</Text>
</View>
</FlexItem>
<FlexBlock>
<View css={[ui.background.blue]}>
<Text>Elsa</Text>
</View>
</FlexBlock>
</Flex>
);
}
```

## Props

##### align

**Type**: `CSS['alignItems']`

Aligns children using CSS Flexbox `align-items`. Vertically aligns content if the `direction` is `row`, or horizontally aligns content if the `direction` is `column`.

In the example below, `flex-start` will align the children content to the top.

##### direction

**Type**: `FlexDirection`

The direction flow of the children content can be adjusted with `direction`. `column` will align children vertically and `row` will align children horizontally.

```jsx live
import { Flex, Text, View } from '@wordpress/components/ui';
import { ui } from '@wp-g2/styles';

function Example() {
return (
<Flex direction="column">
<View css={[ui.background.blue]}>
<Text>Ana</Text>
</View>
<View css={[ui.background.blue]}>
<Text>Elsa</Text>
</View>
</Flex>
);
}
```

##### expanded

**Type**: `boolean`

Expands to the maximum available width (if horizontal) or height (if vertical).

```jsx live
import { Flex, Text, View } from '@wordpress/components/ui';
import { ui } from '@wp-g2/styles';

function Example() {
return (
<Flex direction="row" expanded>
<View css={[ui.background.blue]}>
<Text>Ana</Text>
</View>
<View css={[ui.background.blue]}>
<Text>Elsa</Text>
</View>
</Flex>
);
}
```

##### gap

**Type**: `number`

Spacing in between each child can be adjusted by using `gap`. The value of `gap` works as a multiplier to the library's grid system (base of `4px`).

```jsx live
import { Flex, Text, View } from '@wordpress/components/ui';
import { ui } from '@wp-g2/styles';

function Example() {
return (
<Flex justify="flex-start" gap={8}>
<View css={[ui.background.blue]}>
<Text>Ana</Text>
</View>
<View css={[ui.background.blue]}>
<Text>Elsa</Text>
</View>
</Flex>
);
}
```

##### justify

**Type**: `CSS['justifyContent']`

Horizontally aligns content if the `direction` is `row`, or vertically aligns content if the `direction` is `column`.
In the example below, `flex-start` will align the children content to the left.

##### wrap

**Type**: `boolean`

Determines if children should wrap.

```jsx live
import { Flex, Text, View } from '@wordpress/components/ui';
import { ui } from '@wp-g2/styles';

function Example() {
return (
<Flex justify="flex-start" wrap>
<View css={[ui.background.blue, { width: 200 }]}>
<Text>Ana</Text>
</View>
<View css={[ui.background.blue, { width: 200 }]}>
<Text>Elsa</Text>
</View>
<View css={[ui.background.blue, { width: 200 }]}>
<Text>Olaf</Text>
</View>
<View css={[ui.background.blue, { width: 200 }]}>
<Text>Kristoff</Text>
</View>
<View css={[ui.background.blue, { width: 200 }]}>
<Text>Queen Iduna</Text>
</View>
<View css={[ui.background.blue, { width: 200 }]}>
<Text>King Agnarr</Text>
</View>
<View css={[ui.background.blue, { width: 200 }]}>
<Text>Yelena</Text>
</View>
</Flex>
);
}
```
23 changes: 23 additions & 0 deletions packages/components/src/ui/flex/flex-block.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Internal dependencies
*/
import { createComponent } from '../utils';
import { useFlexBlock } from './use-flex-block';

/**
* `FlexBlock` is a primitive layout component that adaptively resizes content within layout containers like `Flex`.
*
* @example
* ```jsx
* <Flex>
* <FlexBlock>...</FlexBlock>
* </Flex>
* ```
*/
const FlexBlock = createComponent( {
as: 'div',
useHook: useFlexBlock,
name: 'FlexBlock',
} );

export default FlexBlock;
23 changes: 23 additions & 0 deletions packages/components/src/ui/flex/flex-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Internal dependencies
*/
import { createComponent } from '../utils';
import { useFlexItem } from './use-flex-item';

/**
* `FlexItem` is a primitive layout component that aligns content within layout containers like `Flex`.
*
* @example
* ```jsx
* <Flex>
* <FlexItem>...</FlexItem>
* </Flex>
* ```
*/
const FlexItem = createComponent( {
as: 'div',
useHook: useFlexItem,
name: 'FlexItem',
} );

export default FlexItem;
44 changes: 44 additions & 0 deletions packages/components/src/ui/flex/flex-styles.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* External dependencies
*/
import { css } from '@wp-g2/styles';

export const Flex = css`
display: flex;
`;

export const Item = css`
display: block;
max-height: 100%;
max-width: 100%;
min-height: 0;
min-width: 0;
`;

export const block = css`
flex: 1;
`;

/**
* Workaround to optimize DOM rendering.
* We'll enhance alignment with naive parent flex assumptions.
*
* Trade-off:
* Far less DOM less. However, UI rendering is not as reliable.
*/

/**
* Improves stability of width/height rendering.
* https://github.com/ItsJonQ/g2/pull/149
*/
export const ItemsColumn = css`
> * {
min-height: 0;
}
`;

export const ItemsRow = css`
> * {
min-width: 0;
}
`;
41 changes: 41 additions & 0 deletions packages/components/src/ui/flex/flex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Internal dependencies
*/
import { createComponent } from '../utils';
import { useFlex } from './use-flex';

/**
* `Flex` is a primitive layout component that adaptively aligns child content horizontally or vertically. `Flex` powers components like `HStack` and `VStack`.
*
* `Flex` is used with any of it's two sub-components, `FlexItem` and `FlexBlock`.
*
*
* @example
* ```jsx
* import { Flex, FlexItem, FlexBlock, Text, View } from `@wordpress/components`
Copy link

@ItsJonQ ItsJonQ Feb 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor update change the import path to components/ui.
It's a bummer that we have to manually duplicate this stuff in multiple places :(.

For now, I wonder if it may be easier remove the inline examples until we can figure out some tooling to help automate things

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively we could just remove the import statements from the inline examples. They're probably the least important part of the examples.

*
* function Example() {
* return (
* <Flex>
* <FlexItem>
* <View css={[ui.background.blue]}>
* <Text>Ana</Text>
* </View>
* </FlexItem>
* <FlexBlock>
* <View css={[ui.background.blue]}>
* <Text>Elsa</Text>
* </View>
* </FlexBlock>
* </Flex>
* );
* }
* ```
*/
const Flex = createComponent( {
as: 'div',
useHook: useFlex,
name: 'Flex',
} );

export default Flex;
7 changes: 7 additions & 0 deletions packages/components/src/ui/flex/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export { default as Flex } from './flex';
export { default as FlexItem } from './flex-item';
export { default as FlexBlock } from './flex-block';

export * from './use-flex';
export * from './use-flex-item';
export * from './use-flex-block';
30 changes: 30 additions & 0 deletions packages/components/src/ui/flex/stories/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Internal dependencies
*/
import Flex from '../flex';
import FlexItem from '../flex-item';
import { View } from '../../view';

export default {
component: Flex,
title: 'G2 Components (Experimental)/Flex',
};

export const _default = () => {
return (
<>
<Flex gap={ 3 }>
<View>Item</View>
<View>Item</View>
</Flex>
<Flex direction={ [ 'column', 'row' ] } gap={ 3 }>
<View css={ { width: '180px' } }>Item</View>
<FlexItem isBlock>
<View>Item</View>
</FlexItem>
<View>Item</View>
<View>Item</View>
</Flex>
</>
);
};
687 changes: 687 additions & 0 deletions packages/components/src/ui/flex/test/__snapshots__/index.js.snap

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions packages/components/src/ui/flex/test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* External dependencies
*/
import { render } from '@testing-library/react';

/**
* Internal dependencies
*/
import { View } from '../../view';
import Flex from '../flex';
import FlexItem from '../flex-item';
import FlexBlock from '../flex-block';

describe( 'props', () => {
test( 'should render correctly', () => {
const { container } = render(
<Flex>
<FlexItem />
<FlexBlock />
</Flex>
);
expect( container.firstChild ).toMatchSnapshot();
} );

test( 'should render + wrap non Flex children', () => {
const { container } = render(
<Flex>
<FlexItem />
<View />
<div />
<FlexBlock />
</Flex>
);
expect( container.firstChild ).toMatchSnapshot();
} );

test( 'should render align', () => {
const { container } = render(
<Flex align="flex-start">
<FlexItem />
<FlexBlock />
</Flex>
);
expect( container.firstChild ).toMatchSnapshot();
} );

test( 'should render justify', () => {
const { container } = render(
<Flex justify="flex-start">
<FlexItem />
<FlexBlock />
</Flex>
);
expect( container.firstChild ).toMatchSnapshot();
} );

test( 'should render spacing', () => {
const { container } = render(
<Flex spacing={ 5 }>
<View />
<View />
</Flex>
);
expect( container.firstChild ).toMatchSnapshot();
} );
} );
198 changes: 198 additions & 0 deletions packages/components/src/ui/flex/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { CSSProperties } from 'react';
import { ResponsiveCSSValue } from '../utils/types';

export type FlexDirection = ResponsiveCSSValue< CSSProperties[ 'flexDirection' ] >;

export type FlexProps = {
/**
* Aligns children using CSS Flexbox `align-items`. Vertically aligns content if the `direction` is `row`, or horizontally aligns content if the `direction` is `column`.
*
* In the example below, `flex-start` will align the children content to the top.
*
* @default 'center'
*
* @example
* ```jsx
* import { Flex, Text, View } from `@wordpress/components/ui`
* import { ui } from `@wp-g2/styles`
*
* function Example() {
* return (
* <Flex align="flex-start">
* <View css={[ui.background.blue, { height: 100 }]}>
* <Text>Ana</Text>
* </View>
* <View css={[ui.background.blue]}>
* <Text>Elsa</Text>
* </View>
* </Flex>
* )
* }
* ```
*/
align?: CSSProperties[ 'alignItems' ];
/**
* @default false
*/
alignItems?: boolean;
/**
* The direction flow of the children content can be adjusted with `direction`. `column` will align children vertically and `row` will align children horizontally.
*
* @default 'row'
*
* @example
* ```jsx
* import { Flex, Text, View } from `@wordpress/components/ui`
* import { ui } from `@wp-g2/styles`
*
* function Example() {
* return (
* <Flex direction="column">
* <View css={[ui.background.blue]}>
* <Text>Ana</Text>
* </View>
* <View css={[ui.background.blue]}>
* <Text>Elsa</Text>
* </View>
* </Flex>
* )
* }
* ```
*/
direction?: FlexDirection;
/**
* Expands to the maximum available width (if horizontal) or height (if vertical).
*
* @default true
*
* @example
* ```jsx
* import { Flex, Text, View } from `@wordpress/components/ui`
* import { ui } from `@wp-g2/styles`
*
* function Example() {
* return (
* <Flex direction="row" expanded>
* <View css={[ui.background.blue]}>
* <Text>Ana</Text>
* </View>
* <View css={[ui.background.blue]}>
* <Text>Elsa</Text>
* </View>
* </Flex>
* )
* }
* ```
*/
expanded?: boolean;
/**
* Spacing in between each child can be adjusted by using `gap`. The value of `gap` works as a multiplier to the library's grid system (base of `4px`).
*
* @default 2
*
* @example
* ```jsx
* import { Flex, Text, View } from `@wordpress/components/ui`
* import { ui } from `@wp-g2/styles`
*
* function Example() {
* return (
* <Flex justify="flex-start" gap={8}>
* <View css={[ui.background.blue]}>
* <Text>Ana</Text>
* </View>
* <View css={[ui.background.blue]}>
* <Text>Elsa</Text>
* </View>
* </Flex>
* )
* }
* ```
*/
gap?: number;
/**
* Horizontally aligns content if the `direction` is `row`, or vertically aligns content if the `direction` is `column`.
* In the example below, `flex-start` will align the children content to the left.
*
* @default 'space-between'
*
* @example
* ```jsx
* import { Flex, Text, View } from `@wordpress/components/ui`
* import { ui } from `@wp-g2/styles`
*
* function Example() {
* return (
* <Flex justify="flex-start">
* <View css={[ui.background.blue, { height: 100 }]}>
* <Text>Ana</Text>
* </View>
* <View css={[ui.background.blue]}>
* <Text>Elsa</Text>
* </View>
* </Flex>
* )
* }
* ```
*/
justify?: CSSProperties[ 'justifyContent' ];
/**
* @default false
*/
justifyContent?: boolean;
/**
* Determines if children should wrap.
*
* @default false
*
* @example
* ```jsx
* import { Flex, Text, View } from `@wordpress/components/ui`
* import { ui } from `@wp-g2/styles`
*
* function Example() {
* return (
* <Flex justify="flex-start" wrap>
* <View css={[ui.background.blue, { width: 200 }]}>
* <Text>Ana</Text>
* </View>
* <View css={[ui.background.blue, { width: 200 }]}>
* <Text>Elsa</Text>
* </View>
* <View css={[ui.background.blue, { width: 200 }]}>
* <Text>Olaf</Text>
* </View>
* <View css={[ui.background.blue, { width: 200 }]}>
* <Text>Kristoff</Text>
* </View>
* <View css={[ui.background.blue, { width: 200 }]}>
* <Text>Queen Iduna</Text>
* </View>
* <View css={[ui.background.blue, { width: 200 }]}>
* <Text>King Agnarr</Text>
* </View>
* <View css={[ui.background.blue, { width: 200 }]}>
* <Text>Yelena</Text>
* </View>
* </Flex>
* )
* }
* ```
*/
wrap?: boolean;
};

export type FlexItemProps = {
/**
* The (CSS) display of the `FlexItem`.
*/
display?: CSSProperties[ 'display' ];
/**
* Determines if `FlexItem` should render as an adaptive full-width block.
*
* @default true
*/
isBlock?: boolean;
};

export type FlexBlockProps = Omit< FlexItemProps, 'isBlock' >;
19 changes: 19 additions & 0 deletions packages/components/src/ui/flex/use-flex-block.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* External dependencies
*/
import { useContextSystem } from '@wp-g2/context';

/**
* Internal dependencies
*/
import { useFlexItem } from './use-flex-item';

/**
* @param {import('@wp-g2/create-styles').ViewOwnProps<import('./types').FlexBlockProps, 'div'>} props
*/
export function useFlexBlock( props ) {
const otherProps = useContextSystem( props, 'FlexBlock' );
const flexItemProps = useFlexItem( { isBlock: true, ...otherProps } );

return flexItemProps;
}
39 changes: 39 additions & 0 deletions packages/components/src/ui/flex/use-flex-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* External dependencies
*/
import { useContextSystem } from '@wp-g2/context';
import { css, cx, ui } from '@wp-g2/styles';

/**
* Internal dependencies
*/
import * as styles from './flex-styles';

/**
* @param {import('@wp-g2/create-styles').ViewOwnProps<import('./types').FlexItemProps, 'div'>} props
*/
export function useFlexItem( props ) {
const {
className,
display: displayProp,
isBlock = false,
...otherProps
} = useContextSystem( props, 'FlexItem' );
const sx = {};

sx.Base = css( {
display: displayProp || ui.get( 'FlexItemDisplay' ),
} );

const classes = cx(
styles.Item,
sx.Base,
isBlock && styles.block,
className
);

return {
...otherProps,
className: classes,
};
}
117 changes: 117 additions & 0 deletions packages/components/src/ui/flex/use-flex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* External dependencies
*/
import { useContextSystem } from '@wp-g2/context';
import { css, cx, ui, useResponsiveValue } from '@wp-g2/styles';

/**
* WordPress dependencies
*/
import { useMemo } from '@wordpress/element';

/**
* Internal dependencies
*/
import * as styles from './flex-styles';

/**
* @param {import('@wp-g2/create-styles').ViewOwnProps<import('./types').FlexProps, 'div'>} props
*/
export function useFlex( props ) {
const {
align = 'center',
className,
direction: directionProp = 'row',
expanded = true,
gap = 2,
justify = 'space-between',
wrap = false,
...otherProps
} = useContextSystem( props, 'Flex' );

const directionAsArray = Array.isArray( directionProp )
? directionProp
: [ directionProp ];
const direction = useResponsiveValue( directionAsArray );

const isColumn =
typeof direction === 'string' && !! direction.includes( 'column' );
const isReverse =
typeof direction === 'string' && direction.includes( 'reverse' );

const classes = useMemo( () => {
const sx = {};

sx.Base = css( {
ItsJonQ marked this conversation as resolved.
Show resolved Hide resolved
[ ui.createToken( 'FlexGap' ) ]: ui.space( gap ),
[ ui.createToken( 'FlexItemDisplay' ) ]: isColumn
? 'block'
: undefined,
[ ui.createToken( 'FlexItemMarginBottom' ) ]: isColumn
? ui.get( 'FlexGap' )
: 0,
[ ui.createToken( 'FlexItemMarginRight' ) ]:
! isColumn && ! isReverse ? ui.get( 'FlexGap' ) : 0,
[ ui.createToken( 'FlexItemMarginLeft' ) ]:
! isColumn && isReverse ? ui.get( 'FlexGap' ) : 0,
alignItems: isColumn ? 'normal' : align,
flexDirection: direction,
flexWrap: wrap ? 'wrap' : undefined,
justifyContent: justify,
height: isColumn && expanded ? '100%' : undefined,
width: ! isColumn && expanded ? '100%' : undefined,
marginBottom: wrap ? `calc(${ ui.space( gap ) } * -1)` : undefined,
} );

sx.Items = css( {
/**
* Workaround to optimize DOM rendering.
* We'll enhance alignment with naive parent flex assumptions.
*
* Trade-off:
* Far less DOM less. However, UI rendering is not as reliable.
*/
'> * + *:not(marquee)': {
marginTop: isColumn ? ui.space( gap ) : undefined,
marginRight:
! isColumn && isReverse ? ui.space( gap ) : undefined,
marginLeft:
! isColumn && ! isReverse ? ui.space( gap ) : undefined,
},
} );

sx.WrapItems = css( {
'> *:not(marquee)': {
marginBottom: ui.space( gap ),
marginLeft:
! isColumn && isReverse ? ui.space( gap ) : undefined,
marginRight:
! isColumn && ! isReverse ? ui.space( gap ) : undefined,
},
'> *:last-child:not(marquee)': {
marginLeft: ! isColumn && isReverse ? 0 : undefined,
marginRight: ! isColumn && ! isReverse ? 0 : undefined,
},
} );

return cx(
styles.Flex,
sx.Base,
wrap ? sx.WrapItems : sx.Items,
isColumn ? styles.ItemsColumn : styles.ItemsRow,
className
);
}, [
align,
className,
direction,
expanded,
gap,
isColumn,
isReverse,
justify,
wrap,
] );

return { ...otherProps, className: classes };
}
1 change: 1 addition & 0 deletions packages/components/src/ui/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './flex';
export * from './grid';
export * from './text';
export * from './truncate';
2 changes: 2 additions & 0 deletions packages/components/src/ui/utils/types.ts
Original file line number Diff line number Diff line change
@@ -7,3 +7,5 @@ export type Options<T extends As, P extends ViewOwnProps<{}, T>> = {
useHook: (props: P) => any;
memo?: boolean;
};

export type ResponsiveCSSValue<T> = Array<T | undefined> | T;
1 change: 1 addition & 0 deletions packages/components/tsconfig.json
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@
"src/animate/**/*",
"src/base-control/**/*",
"src/dashicon/**/*",
"src/flex/**/*",
"src/tip/**/*",
"src/ui/**/*",
"src/utils/**/*",