diff --git a/.changeset/red-cycles-allow.md b/.changeset/red-cycles-allow.md new file mode 100644 index 00000000000..76960cccefc --- /dev/null +++ b/.changeset/red-cycles-allow.md @@ -0,0 +1,5 @@ +--- +'@shopify/polaris': minor +--- + +Added `Box` component diff --git a/polaris-react/package.json b/polaris-react/package.json index 38987d2539a..70706141b32 100644 --- a/polaris-react/package.json +++ b/polaris-react/package.json @@ -62,6 +62,7 @@ "preversion": "node ./scripts/readme-update-version" }, "dependencies": { + "@radix-ui/react-polymorphic": "^0.0.14", "@shopify/polaris-icons": "^5.4.0", "@shopify/polaris-tokens": "^6.0.0", "@types/react": "*", diff --git a/polaris-react/src/components/Box/Box.scss b/polaris-react/src/components/Box/Box.scss new file mode 100644 index 00000000000..e9f41654a2b --- /dev/null +++ b/polaris-react/src/components/Box/Box.scss @@ -0,0 +1,23 @@ +.Box { + display: block; + background-color: var(--pc-box-background, initial); + /* stylelint-disable declaration-block-no-redundant-longhand-properties */ + border-bottom-left-radius: var(--pc-box-border-radius-bottom-left, initial); + border-bottom-right-radius: var(--pc-box-border-radius-bottom-right, initial); + border-top-left-radius: var(--pc-box-border-radius-top-left, initial); + border-top-right-radius: var(--pc-box-border-radius-top-right, initial); + border-bottom: var(--pc-box-border-bottom, initial); + border-left: var(--pc-box-border-left, initial); + border-right: var(--pc-box-border-right, initial); + border-top: var(--pc-box-border-top, initial); + margin-bottom: var(--pc-box-margin-bottom, initial); + margin-left: var(--pc-box-margin-left, initial); + margin-right: var(--pc-box-margin-right, initial); + margin-top: var(--pc-box-margin-top, initial); + padding-bottom: var(--pc-box-padding-bottom, initial); + padding-left: var(--pc-box-padding-left, initial); + padding-right: var(--pc-box-padding-right, initial); + padding-top: var(--pc-box-padding-top, initial); + /* stylelint-enable declaration-block-no-redundant-longhand-properties */ + box-shadow: var(--pc-box-shadow, initial); +} diff --git a/polaris-react/src/components/Box/Box.tsx b/polaris-react/src/components/Box/Box.tsx new file mode 100644 index 00000000000..12b2684f0f2 --- /dev/null +++ b/polaris-react/src/components/Box/Box.tsx @@ -0,0 +1,253 @@ +import React, {ReactNode, forwardRef} from 'react'; +import type * as Polymorphic from '@radix-ui/react-polymorphic'; +import type {colors, depth, shape, spacing} from '@shopify/polaris-tokens'; + +import {classNames} from '../../utilities/css'; + +import styles from './Box.scss'; + +type ColorsTokenGroup = typeof colors; +type ColorsTokenName = keyof ColorsTokenGroup; +type BackgroundColorTokenScale = Extract< + ColorsTokenName, + | 'background' + | `background-${string}` + | 'surface' + | `surface-${string}` + | 'backdrop' + | 'overlay' +>; + +type DepthTokenGroup = typeof depth; +type DepthTokenName = keyof DepthTokenGroup; +type ShadowsTokenName = Exclude; + +type DepthTokenScale = ShadowsTokenName extends `shadow-${infer Scale}` + ? Scale + : never; + +type ShapeTokenGroup = typeof shape; +type ShapeTokenName = keyof ShapeTokenGroup; + +type BorderShapeTokenScale = ShapeTokenName extends `border-${infer Scale}` + ? Scale + : never; + +type BorderTokenScale = Exclude< + BorderShapeTokenScale, + `radius-${string}` | `width-${string}` +>; + +interface Border { + bottom: BorderTokenScale; + left: BorderTokenScale; + right: BorderTokenScale; + top: BorderTokenScale; +} + +type BorderRadiusTokenScale = Extract< + BorderShapeTokenScale, + `radius-${string}` +> extends `radius-${infer Scale}` + ? Scale + : never; + +interface BorderRadius { + bottomLeft: BorderRadiusTokenScale | ''; + bottomRight: BorderRadiusTokenScale | ''; + topLeft: BorderRadiusTokenScale | ''; + topRight: BorderRadiusTokenScale | ''; +} + +type SpacingTokenGroup = typeof spacing; +type SpacingTokenName = keyof SpacingTokenGroup; + +// TODO: Bring this logic into tokens +type SpacingTokenScale = SpacingTokenName extends `space-${infer Scale}` + ? Scale + : never; + +interface Spacing { + bottom: SpacingTokenScale | ''; + left: SpacingTokenScale | ''; + right: SpacingTokenScale | ''; + top: SpacingTokenScale | ''; +} + +interface BoxBaseProps { + /** Background color of the Box */ + background?: BackgroundColorTokenScale; + /** Border styling of the Box */ + border?: BorderTokenScale; + /** Bottom border styling of the Box */ + borderBottom?: BorderTokenScale; + /** Left border styling of the Box */ + borderLeft?: BorderTokenScale; + /** Right border styling of the Box */ + borderRight?: BorderTokenScale; + /** Top border styling of the Box */ + borderTop?: BorderTokenScale; + /** Border radius of the Box */ + borderRadius?: BorderRadiusTokenScale; + /** Bottom left border radius of the Box */ + borderRadiusBottomLeft?: BorderRadiusTokenScale; + /** Bottom right border radius of the Box */ + borderRadiusBottomRight?: BorderRadiusTokenScale; + /** Top left border radius of the Box */ + borderRadiusTopLeft?: BorderRadiusTokenScale; + /** Top right border radius of the Box */ + borderRadiusTopRight?: BorderRadiusTokenScale; + /** Inner content of the Box */ + children: ReactNode; + /** Spacing outside of the Box */ + margin?: SpacingTokenScale; + /** Bottom spacing outside of the Box */ + marginBottom?: SpacingTokenScale; + /** Left side spacing outside of the Box */ + marginLeft?: SpacingTokenScale; + /** Right side spacing outside of the Box */ + marginRight?: SpacingTokenScale; + /** Top spacing outside of the Box */ + marginTop?: SpacingTokenScale; + /** Spacing inside of the Box */ + padding?: SpacingTokenScale; + /** Bottom spacing inside of the Box */ + paddingBottom?: SpacingTokenScale; + /** Left side spacing inside of the Box */ + paddingLeft?: SpacingTokenScale; + /** Right side spacing inside of the Box */ + paddingRight?: SpacingTokenScale; + /** Top spacing inside of the Box */ + paddingTop?: SpacingTokenScale; + /** Shadow on the Box */ + shadow?: DepthTokenScale; +} + +type PolymorphicBox = Polymorphic.ForwardRefComponent<'div', BoxBaseProps>; + +export type BoxProps = Polymorphic.OwnProps; + +export const Box = forwardRef( + ( + { + as: Component = 'div', + background, + border = '', + borderBottom = '', + borderLeft = '', + borderRight = '', + borderTop = '', + borderRadius = '', + borderRadiusBottomLeft = '', + borderRadiusBottomRight = '', + borderRadiusTopLeft = '', + borderRadiusTopRight = '', + children, + margin = '', + marginBottom = '', + marginLeft = '', + marginRight = '', + marginTop = '', + padding = '', + paddingBottom = '', + paddingLeft = '', + paddingRight = '', + paddingTop = '', + shadow, + }, + ref, + ) => { + const borders = { + bottom: borderBottom ? borderBottom : border, + left: borderLeft ? borderLeft : border, + right: borderRight ? borderRight : border, + top: borderTop ? borderTop : border, + } as Border; + + const borderRadiuses = { + bottomLeft: borderRadiusBottomLeft + ? borderRadiusBottomLeft + : borderRadius, + bottomRight: borderRadiusBottomRight + ? borderRadiusBottomRight + : borderRadius, + topLeft: borderRadiusTopLeft ? borderRadiusTopLeft : borderRadius, + topRight: borderRadiusTopRight ? borderRadiusTopRight : borderRadius, + } as BorderRadius; + + const margins = { + bottom: marginBottom ? marginBottom : margin, + left: marginLeft ? marginLeft : margin, + right: marginRight ? marginRight : margin, + top: marginTop ? marginTop : margin, + } as Spacing; + + const paddings = { + bottom: paddingBottom ? paddingBottom : padding, + left: paddingLeft ? paddingLeft : padding, + right: paddingRight ? paddingRight : padding, + top: paddingTop ? paddingTop : padding, + } as Spacing; + + const style = { + '--pc-box-background': background ? `var(--p-${background})` : '', + '--pc-box-margin-bottom': margins.bottom + ? `var(--p-space-${margins.bottom})` + : '', + '--pc-box-margin-left': margins.left + ? `var(--p-space-${margins.left})` + : '', + '--pc-box-margin-right': margins.right + ? `var(--p-space-${margins.right})` + : '', + '--pc-box-margin-top': margins.top ? `var(--p-space-${margins.top})` : '', + '--pc-box-padding-bottom': paddings.bottom + ? `var(--p-space-${paddings.bottom})` + : '', + '--pc-box-padding-left': paddings.left + ? `var(--p-space-${paddings.left})` + : '', + '--pc-box-padding-right': paddings.right + ? `var(--p-space-${paddings.right})` + : '', + '--pc-box-padding-top': paddings.top + ? `var(--p-space-${paddings.top})` + : '', + '--pc-box-border-bottom': borders.bottom + ? `var(--p-border-${borders.bottom})` + : '', + '--pc-box-border-left': borders.left + ? `var(--p-border-${borders.left})` + : '', + '--pc-box-border-right': borders.right + ? `var(--p-border-${borders.right})` + : '', + '--pc-box-border-top': borders.top + ? `var(--p-border-${borders.top})` + : '', + '--pc-box-border-radius-bottom-left': borderRadiuses.bottomLeft + ? `var(--p-border-radius-${borderRadiuses.bottomLeft})` + : '', + '--pc-box-border-radius-bottom-right': borderRadiuses.bottomRight + ? `var(--p-border-radius-${borderRadiuses.bottomRight})` + : '', + '--pc-box-border-radius-top-left': borderRadiuses.topLeft + ? `var(--p-border-radius-${borderRadiuses.topLeft})` + : '', + '--pc-box-border-radius-top-right': borderRadiuses.topRight + ? `var(--p-border-radius-${borderRadiuses.topRight})` + : '', + '--pc-box-shadow': shadow ? `var(--p-shadow-${shadow})` : '', + } as React.CSSProperties; + + const className = classNames(styles.root); + + return ( + + {children} + + ); + }, +) as PolymorphicBox; + +Box.displayName = 'Box'; diff --git a/polaris-react/src/components/Box/index.ts b/polaris-react/src/components/Box/index.ts new file mode 100644 index 00000000000..305f81d78bc --- /dev/null +++ b/polaris-react/src/components/Box/index.ts @@ -0,0 +1 @@ +export * from './Box'; diff --git a/polaris-react/src/index.ts b/polaris-react/src/index.ts index ff69562764e..116ecfd8ddc 100644 --- a/polaris-react/src/index.ts +++ b/polaris-react/src/index.ts @@ -69,6 +69,9 @@ export type { BannerHandles, } from './components/Banner'; +export {Box} from './components/Box'; +export type {BoxProps} from './components/Box'; + export {Breadcrumbs} from './components/Breadcrumbs'; export type {BreadcrumbsProps} from './components/Breadcrumbs'; diff --git a/yarn.lock b/yarn.lock index 8d0c31da8a1..c186903a9ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2479,6 +2479,11 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== +"@radix-ui/react-polymorphic@^0.0.14": + version "0.0.14" + resolved "https://registry.yarnpkg.com/@radix-ui/react-polymorphic/-/react-polymorphic-0.0.14.tgz#fc6cefee6686db8c5a7ff14c8c1b9b5abdee325b" + integrity sha512-9nsMZEDU3LeIUeHJrpkkhZVxu/9Fc7P2g2I3WR+uA9mTbNC3hGaabi0dV6wg0CfHb+m4nSs1pejbE/5no3MJTA== + "@rollup/plugin-babel@^5.3.1": version "5.3.1" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"