diff --git a/docs/manifest.json b/docs/manifest.json index 8454aa294683bf..7691b8dcf9c319 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1067,6 +1067,12 @@ "markdown_source": "../packages/components/src/snackbar/README.md", "parent": "components" }, + { + "title": "Spacer", + "slug": "spacer", + "markdown_source": "../packages/components/src/spacer/README.md", + "parent": "components" + }, { "title": "Spinner", "slug": "spinner", diff --git a/packages/components/src/index.js b/packages/components/src/index.js index afbb7e37db8ae2..3539a38860cddc 100644 --- a/packages/components/src/index.js +++ b/packages/components/src/index.js @@ -102,6 +102,7 @@ export { default as SandBox } from './sandbox'; export { default as SelectControl } from './select-control'; export { default as Snackbar } from './snackbar'; export { default as SnackbarList } from './snackbar/list'; +export { Spacer as __experimentalSpacer } from './spacer'; export { default as Spinner } from './spinner'; export { default as TabPanel } from './tab-panel'; export { Text as __experimentalText } from './text'; diff --git a/packages/components/src/spacer/README.md b/packages/components/src/spacer/README.md new file mode 100644 index 00000000000000..1aad9670c082f5 --- /dev/null +++ b/packages/components/src/spacer/README.md @@ -0,0 +1,118 @@ +# Spacer + +> **Experimental!** + +`Spacer` is a primitive layout component that providers inner (`padding`) or outer (`margin`) space in-between components. It can also be used to adaptively provide space within an `HStack` or `VStack`. + +## Table of contents + +## Usage + +`Spacer` comes with a bunch of shorthand props to adjust `margin` and `padding`. The values of these props work as a multiplier to the library's grid system (base of `4px`). + +```jsx +import { + __experimentalSpacer as Spacer, + __experimentalHeading as Heading, + __experimentalView as View +} from '@wordpress/components'; + +function Example() { + return ( + + + WordPress.org + + + Code is Poetry + + + ); +} +``` + +## Props + +##### margin + +**Type**: `number` + +Adjusts all margins. + +##### marginBottom + +**Type**: `number` + +Adjusts bottom margins. + +##### marginLeft + +**Type**: `number` + +Adjusts left margins. + +##### marginRight + +**Type**: `number` + +Adjusts right margins. + +##### marginTop + +**Type**: `number` + +Adjusts top margins. + +##### marginX + +**Type**: `number` + +Adjusts left and right margins. + +##### marginY + +**Type**: `number` + +Adjusts top and bottom margins. + +##### padding + +**Type**: `number` + +Adjusts all padding. + +##### paddingBottom + +**Type**: `number` + +Adjusts bottom padding. + +##### paddingLeft + +**Type**: `number` + +Adjusts left padding. + +##### paddingRight + +**Type**: `number` + +Adjusts right padding. + +##### paddingTop + +**Type**: `number` + +Adjusts top padding. + +##### paddingX + +**Type**: `number` + +Adjusts left and right padding. + +##### paddingY + +**Type**: `number` + +Adjusts top and bottom padding. diff --git a/packages/components/src/spacer/component.ts b/packages/components/src/spacer/component.ts new file mode 100644 index 00000000000000..162029e1cb9c66 --- /dev/null +++ b/packages/components/src/spacer/component.ts @@ -0,0 +1,36 @@ +/** + * Internal dependencies + */ +import { createComponent } from '../ui/utils'; +import { useSpacer } from './hook'; + +/** + * `Spacer` is a primitive layout component that providers inner (`padding`) or outer (`margin`) space in-between components. It can also be used to adaptively provide space within an `HStack` or `VStack`. + * + * `Spacer` comes with a bunch of shorthand props to adjust `margin` and `padding`. The values of these props work as a multiplier to the library's grid system (base of `4px`). + * + * @example + * ```jsx + * import { Spacer } from `@wordpress/components` + * + * function Example() { + * return ( + * + * + * WordPress.org + * + * + * Code is Poetry + * + * + * ); + * } + * ``` + */ +const Spacer = createComponent( { + as: 'div', + useHook: useSpacer, + name: 'Spacer', +} ); + +export default Spacer; diff --git a/packages/components/src/spacer/hook.ts b/packages/components/src/spacer/hook.ts new file mode 100644 index 00000000000000..cd2465627d31c8 --- /dev/null +++ b/packages/components/src/spacer/hook.ts @@ -0,0 +1,163 @@ +/** + * External dependencies + */ +import { css, cx } from 'emotion'; + +/** + * Internal dependencies + */ +import { useContextSystem } from '../ui/context'; +// eslint-disable-next-line no-duplicate-imports +import type { ViewOwnProps } from '../ui/context'; +import { space } from '../ui/utils/space'; + +const isDefined = < T >( o: T ): o is Exclude< T, null | undefined > => + typeof o !== 'undefined' && o !== null; + +export interface SpacerProps { + /** + * Adjusts all margins. + */ + margin?: number; + /** + * Adjusts top and bottom margins. + */ + marginY?: number; + /** + * Adjusts left and right margins. + */ + marginX?: number; + /** + * Adjusts top margins. + */ + marginTop?: number; + /** + * Adjusts bottom margins. + * + * @default 2 + */ + marginBottom?: number; + /** + * Adjusts left margins. + */ + marginLeft?: number; + /** + * Adjusts right margins. + */ + marginRight?: number; + /** + * Adjusts all padding. + */ + padding?: number; + /** + * Adjusts top and bottom padding. + */ + paddingY?: number; + /** + * Adjusts left and right padding. + */ + paddingX?: number; + /** + * Adjusts top padding. + */ + paddingTop?: number; + /** + * Adjusts bottom padding. + */ + paddingBottom?: number; + /** + * Adjusts left padding. + */ + paddingLeft?: number; + /** + * Adjusts right padding. + */ + paddingRight?: number; +} + +export function useSpacer( props: ViewOwnProps< SpacerProps, 'div' > ) { + const { + className, + margin, + marginBottom = 2, + marginLeft, + marginRight, + marginTop, + marginX, + marginY, + padding, + paddingBottom, + paddingLeft, + paddingRight, + paddingTop, + paddingX, + paddingY, + ...otherProps + } = useContextSystem( props, 'Spacer' ); + + const classes = cx( + isDefined( marginTop ) && + css` + margin-top: ${ space( marginTop ) }; + `, + isDefined( marginBottom ) && + css` + margin-bottom: ${ space( marginBottom ) }; + `, + isDefined( marginLeft ) && + css` + margin-left: ${ space( marginLeft ) }; + `, + isDefined( marginRight ) && + css` + margin-right: ${ space( marginRight ) }; + `, + isDefined( marginX ) && + css` + margin-left: ${ space( marginX ) }; + margin-right: ${ space( marginX ) }; + `, + isDefined( marginY ) && + css` + margin-bottom: ${ space( marginY ) }; + margin-top: ${ space( marginY ) }; + `, + isDefined( margin ) && + css` + margin: ${ space( margin ) }; + `, + isDefined( paddingTop ) && + css` + padding-top: ${ space( paddingTop ) }; + `, + isDefined( paddingBottom ) && + css` + padding-bottom: ${ space( paddingBottom ) }; + `, + isDefined( paddingLeft ) && + css` + padding-left: ${ space( paddingLeft ) }; + `, + isDefined( paddingRight ) && + css` + padding-right: ${ space( paddingRight ) }; + `, + isDefined( paddingX ) && + css` + padding-left: ${ space( paddingX ) }; + padding-right: ${ space( paddingX ) }; + `, + isDefined( paddingY ) && + css` + padding-bottom: ${ space( paddingY ) }; + padding-top: ${ space( paddingY ) }; + `, + isDefined( padding ) && + css` + padding: ${ space( padding ) }; + `, + className + ); + + return { ...otherProps, className: classes }; +} diff --git a/packages/components/src/spacer/index.ts b/packages/components/src/spacer/index.ts new file mode 100644 index 00000000000000..765121f409b066 --- /dev/null +++ b/packages/components/src/spacer/index.ts @@ -0,0 +1,3 @@ +export { default as Spacer } from './component'; +export { useSpacer } from './hook'; +export type { SpacerProps } from './hook'; diff --git a/packages/components/src/spacer/test/__snapshots__/index.js.snap b/packages/components/src/spacer/test/__snapshots__/index.js.snap new file mode 100644 index 00000000000000..ecf40a7e47be27 --- /dev/null +++ b/packages/components/src/spacer/test/__snapshots__/index.js.snap @@ -0,0 +1,199 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`props should render correctly 1`] = ` +.emotion-0 { + margin-bottom: calc(4px * 2); +} + +
+`; + +exports[`props should render margin 1`] = ` +Snapshot Diff: +- Received styles ++ Base styles + + Array [ + Object { +- "margin": "calc(4px * 5)", + "margin-bottom": "calc(4px * 2)", + }, + ] +`; + +exports[`props should render marginBottom 1`] = ` +Snapshot Diff: +- Received styles ++ Base styles + + Array [ + Object { +- "margin-bottom": "calc(4px * 5)", ++ "margin-bottom": "calc(4px * 2)", + }, + ] +`; + +exports[`props should render marginLeft 1`] = ` +Snapshot Diff: +- Received styles ++ Base styles + + Array [ + Object { + "margin-bottom": "calc(4px * 2)", +- "margin-left": "calc(4px * 5)", + }, + ] +`; + +exports[`props should render marginRight 1`] = ` +Snapshot Diff: +- Received styles ++ Base styles + + Array [ + Object { + "margin-bottom": "calc(4px * 2)", +- "margin-right": "calc(4px * 5)", + }, + ] +`; + +exports[`props should render marginTop 1`] = ` +Snapshot Diff: +- Received styles ++ Base styles + + Array [ + Object { + "margin-bottom": "calc(4px * 2)", +- "margin-top": "calc(4px * 5)", + }, + ] +`; + +exports[`props should render marginX 1`] = ` +Snapshot Diff: +- Received styles ++ Base styles + + Array [ + Object { + "margin-bottom": "calc(4px * 2)", +- "margin-left": "calc(4px * 5)", +- "margin-right": "calc(4px * 5)", + }, + ] +`; + +exports[`props should render marginY 1`] = ` +Snapshot Diff: +- Received styles ++ Base styles + + Array [ + Object { +- "margin-bottom": "calc(4px * 5)", +- "margin-top": "calc(4px * 5)", ++ "margin-bottom": "calc(4px * 2)", + }, + ] +`; + +exports[`props should render padding 1`] = ` +Snapshot Diff: +- Received styles ++ Base styles + + Array [ + Object { + "margin-bottom": "calc(4px * 2)", +- "padding": "calc(4px * 5)", + }, + ] +`; + +exports[`props should render paddingBottom 1`] = ` +Snapshot Diff: +- Received styles ++ Base styles + + Array [ + Object { + "margin-bottom": "calc(4px * 2)", +- "padding-bottom": "calc(4px * 5)", + }, + ] +`; + +exports[`props should render paddingLeft 1`] = ` +Snapshot Diff: +- Received styles ++ Base styles + + Array [ + Object { + "margin-bottom": "calc(4px * 2)", +- "padding-left": "calc(4px * 5)", + }, + ] +`; + +exports[`props should render paddingRight 1`] = ` +Snapshot Diff: +- Received styles ++ Base styles + + Array [ + Object { + "margin-bottom": "calc(4px * 2)", +- "padding-right": "calc(4px * 5)", + }, + ] +`; + +exports[`props should render paddingTop 1`] = ` +Snapshot Diff: +- Received styles ++ Base styles + + Array [ + Object { + "margin-bottom": "calc(4px * 2)", +- "padding-top": "calc(4px * 5)", + }, + ] +`; + +exports[`props should render paddingX 1`] = ` +Snapshot Diff: +- Received styles ++ Base styles + + Array [ + Object { + "margin-bottom": "calc(4px * 2)", +- "padding-left": "calc(4px * 5)", +- "padding-right": "calc(4px * 5)", + }, + ] +`; + +exports[`props should render paddingY 1`] = ` +Snapshot Diff: +- Received styles ++ Base styles + + Array [ + Object { + "margin-bottom": "calc(4px * 2)", +- "padding-bottom": "calc(4px * 5)", +- "padding-top": "calc(4px * 5)", + }, + ] +`; diff --git a/packages/components/src/spacer/test/index.js b/packages/components/src/spacer/test/index.js new file mode 100644 index 00000000000000..47d9ab1833596c --- /dev/null +++ b/packages/components/src/spacer/test/index.js @@ -0,0 +1,118 @@ +/** + * External dependencies + */ +import { render } from '@testing-library/react'; + +/** + * Internal dependencies + */ +import { Spacer } from '../index'; + +describe( 'props', () => { + let base; + beforeEach( () => { + base = render( ).container; + } ); + + test( 'should render correctly', () => { + expect( base.firstChild ).toMatchSnapshot(); + } ); + + test( 'should render margin', () => { + const { container } = render( ); + expect( container.firstChild ).toMatchStyleDiffSnapshot( + base.firstChild + ); + } ); + + test( 'should render marginX', () => { + const { container } = render( ); + expect( container.firstChild ).toMatchStyleDiffSnapshot( + base.firstChild + ); + } ); + + test( 'should render marginY', () => { + const { container } = render( ); + expect( container.firstChild ).toMatchStyleDiffSnapshot( + base.firstChild + ); + } ); + + test( 'should render marginTop', () => { + const { container } = render( ); + expect( container.firstChild ).toMatchStyleDiffSnapshot( + base.firstChild + ); + } ); + + test( 'should render marginBottom', () => { + const { container } = render( ); + expect( container.firstChild ).toMatchStyleDiffSnapshot( + base.firstChild + ); + } ); + + test( 'should render marginLeft', () => { + const { container } = render( ); + expect( container.firstChild ).toMatchStyleDiffSnapshot( + base.firstChild + ); + } ); + + test( 'should render marginRight', () => { + const { container } = render( ); + expect( container.firstChild ).toMatchStyleDiffSnapshot( + base.firstChild + ); + } ); + + test( 'should render padding', () => { + const { container } = render( ); + expect( container.firstChild ).toMatchStyleDiffSnapshot( + base.firstChild + ); + } ); + + test( 'should render paddingX', () => { + const { container } = render( ); + expect( container.firstChild ).toMatchStyleDiffSnapshot( + base.firstChild + ); + } ); + + test( 'should render paddingY', () => { + const { container } = render( ); + expect( container.firstChild ).toMatchStyleDiffSnapshot( + base.firstChild + ); + } ); + + test( 'should render paddingTop', () => { + const { container } = render( ); + expect( container.firstChild ).toMatchStyleDiffSnapshot( + base.firstChild + ); + } ); + + test( 'should render paddingBottom', () => { + const { container } = render( ); + expect( container.firstChild ).toMatchStyleDiffSnapshot( + base.firstChild + ); + } ); + + test( 'should render paddingLeft', () => { + const { container } = render( ); + expect( container.firstChild ).toMatchStyleDiffSnapshot( + base.firstChild + ); + } ); + + test( 'should render paddingRight', () => { + const { container } = render( ); + expect( container.firstChild ).toMatchStyleDiffSnapshot( + base.firstChild + ); + } ); +} ); diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index f4c7d9364e0b6f..c93cc82efe1bb6 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -25,6 +25,7 @@ "src/scroll-lock/**/*", "src/shortcut/**/*", "src/spinner/**/*", + "src/spacer/**/*", "src/tip/**/*", "src/truncate/**/*", "src/ui/**/*",