Skip to content

Commit 4e17711

Browse files
laurkimaaronccasanovaavelinealex-pagekyledurand
committed
[Layout foundations] Add alpha Box component (#7000)
Resolves #6892. <!-- Context about the problem that’s being addressed. --> Adds a new alpha `Box` component. <details> <summary>Using `Box` to recreate the `Card` component</summary> <img src="https://user-images.githubusercontent.com/26749317/187263883-0e8194f5-fbd8-4227-bdf5-66e868ffa573.png" alt="Using `Box` to recreate the `Card` component"> </details> 🖥 [Local development instructions](https://github.com/Shopify/polaris/blob/main/README.md#local-development) 🗒 [General tophatting guidelines](https://github.com/Shopify/polaris/blob/main/documentation/Tophatting.md) 📄 [Changelog guidelines](https://github.com/Shopify/polaris/blob/main/.github/CONTRIBUTING.md#changelog) <!-- Give as much information as needed to experiment with the component in the playground. --> <details> <summary>Copy-paste this code in <code>playground/Playground.tsx</code>:</summary> ```jsx import React from 'react'; import {Page, Card, Text, Box} from '../src'; export function Playground() { return ( <Page title="Playground"> {/* Add the code you want to test in here */} <Box background="surface" borderRadius="radius-2" padding="5" shadow="card" > <Text as="h2" variant="headingMd"> Online store dashboard </Text> <Text as="p" variant="bodyMd"> This was made using the 📦 Box 📦 component. </Text> </Box> <div style={{ borderTop: 'var(--p-border-divider)', marginTop: 'var(--p-space-8)', marginBottom: 'var(--p-space-8)', }} ></div> <Card title="Online store dashboard" sectioned> <Text as="p" variant="bodyMd"> This was made using the 🃏 Card 🃏 component. </Text> </Card> </Page> ); } ``` </details> - [ ] Tested on [mobile](https://github.com/Shopify/polaris/blob/main/documentation/Tophatting.md#cross-browser-testing) - [ ] Tested on [multiple browsers](https://help.shopify.com/en/manual/shopify-admin/supported-browsers) - [ ] Tested for [accessibility](https://github.com/Shopify/polaris/blob/main/documentation/Accessibility%20testing.md) - [ ] Updated the component's `README.md` with documentation changes - [ ] [Tophatted documentation](https://github.com/Shopify/polaris/blob/main/documentation/Tophatting%20documentation.md) changes in the style guide Co-authored-by: Aaron Casanova <[email protected]> Co-authored-by: Aveline Thelen <[email protected]> Co-authored-by: Alex Page <[email protected]> Co-authored-by: Kyle Durand <[email protected]>
1 parent 2349db3 commit 4e17711

File tree

6 files changed

+290
-0
lines changed

6 files changed

+290
-0
lines changed

.changeset/red-cycles-allow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@shopify/polaris': minor
3+
---
4+
5+
Added `Box` component
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
.Box {
2+
display: block;
3+
background-color: var(--pc-box-background, initial);
4+
/* stylelint-disable declaration-block-no-redundant-longhand-properties */
5+
border-bottom-left-radius: var(--pc-box-border-radius-bottom-left, initial);
6+
border-bottom-right-radius: var(--pc-box-border-radius-bottom-right, initial);
7+
border-top-left-radius: var(--pc-box-border-radius-top-left, initial);
8+
border-top-right-radius: var(--pc-box-border-radius-top-right, initial);
9+
border-bottom: var(--pc-box-border-bottom, initial);
10+
border-left: var(--pc-box-border-left, initial);
11+
border-right: var(--pc-box-border-right, initial);
12+
border-top: var(--pc-box-border-top, initial);
13+
margin-bottom: var(--pc-box-margin-bottom, initial);
14+
margin-left: var(--pc-box-margin-left, initial);
15+
margin-right: var(--pc-box-margin-right, initial);
16+
margin-top: var(--pc-box-margin-top, initial);
17+
padding-bottom: var(--pc-box-padding-bottom, initial);
18+
padding-left: var(--pc-box-padding-left, initial);
19+
padding-right: var(--pc-box-padding-right, initial);
20+
padding-top: var(--pc-box-padding-top, initial);
21+
/* stylelint-enable declaration-block-no-redundant-longhand-properties */
22+
box-shadow: var(--pc-box-shadow, initial);
23+
}
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
import React, {ReactNode, forwardRef} from 'react';
2+
import type * as Polymorphic from '@radix-ui/react-polymorphic';
3+
import type {colors, depth, shape, spacing} from '@shopify/polaris-tokens';
4+
5+
import {classNames} from '../../utilities/css';
6+
7+
import styles from './Box.scss';
8+
9+
type ColorsTokenGroup = typeof colors;
10+
type ColorsTokenName = keyof ColorsTokenGroup;
11+
type BackgroundColorTokenScale = Extract<
12+
ColorsTokenName,
13+
| 'background'
14+
| `background-${string}`
15+
| 'surface'
16+
| `surface-${string}`
17+
| 'backdrop'
18+
| 'overlay'
19+
>;
20+
21+
type DepthTokenGroup = typeof depth;
22+
type DepthTokenName = keyof DepthTokenGroup;
23+
type ShadowsTokenName = Exclude<DepthTokenName, `shadows-${string}`>;
24+
25+
type DepthTokenScale = ShadowsTokenName extends `shadow-${infer Scale}`
26+
? Scale
27+
: never;
28+
29+
type ShapeTokenGroup = typeof shape;
30+
type ShapeTokenName = keyof ShapeTokenGroup;
31+
32+
type BorderShapeTokenScale = ShapeTokenName extends `border-${infer Scale}`
33+
? Scale
34+
: never;
35+
36+
type BorderTokenScale = Exclude<
37+
BorderShapeTokenScale,
38+
`radius-${string}` | `width-${string}`
39+
>;
40+
41+
interface Border {
42+
bottom: BorderTokenScale;
43+
left: BorderTokenScale;
44+
right: BorderTokenScale;
45+
top: BorderTokenScale;
46+
}
47+
48+
type BorderRadiusTokenScale = Extract<
49+
BorderShapeTokenScale,
50+
`radius-${string}`
51+
> extends `radius-${infer Scale}`
52+
? Scale
53+
: never;
54+
55+
interface BorderRadius {
56+
bottomLeft: BorderRadiusTokenScale | '';
57+
bottomRight: BorderRadiusTokenScale | '';
58+
topLeft: BorderRadiusTokenScale | '';
59+
topRight: BorderRadiusTokenScale | '';
60+
}
61+
62+
type SpacingTokenGroup = typeof spacing;
63+
type SpacingTokenName = keyof SpacingTokenGroup;
64+
65+
// TODO: Bring this logic into tokens
66+
type SpacingTokenScale = SpacingTokenName extends `space-${infer Scale}`
67+
? Scale
68+
: never;
69+
70+
interface Spacing {
71+
bottom: SpacingTokenScale | '';
72+
left: SpacingTokenScale | '';
73+
right: SpacingTokenScale | '';
74+
top: SpacingTokenScale | '';
75+
}
76+
77+
interface BoxBaseProps {
78+
/** Background color of the Box */
79+
background?: BackgroundColorTokenScale;
80+
/** Border styling of the Box */
81+
border?: BorderTokenScale;
82+
/** Bottom border styling of the Box */
83+
borderBottom?: BorderTokenScale;
84+
/** Left border styling of the Box */
85+
borderLeft?: BorderTokenScale;
86+
/** Right border styling of the Box */
87+
borderRight?: BorderTokenScale;
88+
/** Top border styling of the Box */
89+
borderTop?: BorderTokenScale;
90+
/** Border radius of the Box */
91+
borderRadius?: BorderRadiusTokenScale;
92+
/** Bottom left border radius of the Box */
93+
borderRadiusBottomLeft?: BorderRadiusTokenScale;
94+
/** Bottom right border radius of the Box */
95+
borderRadiusBottomRight?: BorderRadiusTokenScale;
96+
/** Top left border radius of the Box */
97+
borderRadiusTopLeft?: BorderRadiusTokenScale;
98+
/** Top right border radius of the Box */
99+
borderRadiusTopRight?: BorderRadiusTokenScale;
100+
/** Inner content of the Box */
101+
children: ReactNode;
102+
/** Spacing outside of the Box */
103+
margin?: SpacingTokenScale;
104+
/** Bottom spacing outside of the Box */
105+
marginBottom?: SpacingTokenScale;
106+
/** Left side spacing outside of the Box */
107+
marginLeft?: SpacingTokenScale;
108+
/** Right side spacing outside of the Box */
109+
marginRight?: SpacingTokenScale;
110+
/** Top spacing outside of the Box */
111+
marginTop?: SpacingTokenScale;
112+
/** Spacing inside of the Box */
113+
padding?: SpacingTokenScale;
114+
/** Bottom spacing inside of the Box */
115+
paddingBottom?: SpacingTokenScale;
116+
/** Left side spacing inside of the Box */
117+
paddingLeft?: SpacingTokenScale;
118+
/** Right side spacing inside of the Box */
119+
paddingRight?: SpacingTokenScale;
120+
/** Top spacing inside of the Box */
121+
paddingTop?: SpacingTokenScale;
122+
/** Shadow on the Box */
123+
shadow?: DepthTokenScale;
124+
}
125+
126+
type PolymorphicBox = Polymorphic.ForwardRefComponent<'div', BoxBaseProps>;
127+
128+
export type BoxProps = Polymorphic.OwnProps<PolymorphicBox>;
129+
130+
export const Box = forwardRef(
131+
(
132+
{
133+
as: Component = 'div',
134+
background,
135+
border = '',
136+
borderBottom = '',
137+
borderLeft = '',
138+
borderRight = '',
139+
borderTop = '',
140+
borderRadius = '',
141+
borderRadiusBottomLeft = '',
142+
borderRadiusBottomRight = '',
143+
borderRadiusTopLeft = '',
144+
borderRadiusTopRight = '',
145+
children,
146+
margin = '',
147+
marginBottom = '',
148+
marginLeft = '',
149+
marginRight = '',
150+
marginTop = '',
151+
padding = '',
152+
paddingBottom = '',
153+
paddingLeft = '',
154+
paddingRight = '',
155+
paddingTop = '',
156+
shadow,
157+
},
158+
ref,
159+
) => {
160+
const borders = {
161+
bottom: borderBottom ? borderBottom : border,
162+
left: borderLeft ? borderLeft : border,
163+
right: borderRight ? borderRight : border,
164+
top: borderTop ? borderTop : border,
165+
} as Border;
166+
167+
const borderRadiuses = {
168+
bottomLeft: borderRadiusBottomLeft
169+
? borderRadiusBottomLeft
170+
: borderRadius,
171+
bottomRight: borderRadiusBottomRight
172+
? borderRadiusBottomRight
173+
: borderRadius,
174+
topLeft: borderRadiusTopLeft ? borderRadiusTopLeft : borderRadius,
175+
topRight: borderRadiusTopRight ? borderRadiusTopRight : borderRadius,
176+
} as BorderRadius;
177+
178+
const margins = {
179+
bottom: marginBottom ? marginBottom : margin,
180+
left: marginLeft ? marginLeft : margin,
181+
right: marginRight ? marginRight : margin,
182+
top: marginTop ? marginTop : margin,
183+
} as Spacing;
184+
185+
const paddings = {
186+
bottom: paddingBottom ? paddingBottom : padding,
187+
left: paddingLeft ? paddingLeft : padding,
188+
right: paddingRight ? paddingRight : padding,
189+
top: paddingTop ? paddingTop : padding,
190+
} as Spacing;
191+
192+
const style = {
193+
'--pc-box-background': background ? `var(--p-${background})` : '',
194+
'--pc-box-margin-bottom': margins.bottom
195+
? `var(--p-space-${margins.bottom})`
196+
: '',
197+
'--pc-box-margin-left': margins.left
198+
? `var(--p-space-${margins.left})`
199+
: '',
200+
'--pc-box-margin-right': margins.right
201+
? `var(--p-space-${margins.right})`
202+
: '',
203+
'--pc-box-margin-top': margins.top ? `var(--p-space-${margins.top})` : '',
204+
'--pc-box-padding-bottom': paddings.bottom
205+
? `var(--p-space-${paddings.bottom})`
206+
: '',
207+
'--pc-box-padding-left': paddings.left
208+
? `var(--p-space-${paddings.left})`
209+
: '',
210+
'--pc-box-padding-right': paddings.right
211+
? `var(--p-space-${paddings.right})`
212+
: '',
213+
'--pc-box-padding-top': paddings.top
214+
? `var(--p-space-${paddings.top})`
215+
: '',
216+
'--pc-box-border-bottom': borders.bottom
217+
? `var(--p-border-${borders.bottom})`
218+
: '',
219+
'--pc-box-border-left': borders.left
220+
? `var(--p-border-${borders.left})`
221+
: '',
222+
'--pc-box-border-right': borders.right
223+
? `var(--p-border-${borders.right})`
224+
: '',
225+
'--pc-box-border-top': borders.top
226+
? `var(--p-border-${borders.top})`
227+
: '',
228+
'--pc-box-border-radius-bottom-left': borderRadiuses.bottomLeft
229+
? `var(--p-border-radius-${borderRadiuses.bottomLeft})`
230+
: '',
231+
'--pc-box-border-radius-bottom-right': borderRadiuses.bottomRight
232+
? `var(--p-border-radius-${borderRadiuses.bottomRight})`
233+
: '',
234+
'--pc-box-border-radius-top-left': borderRadiuses.topLeft
235+
? `var(--p-border-radius-${borderRadiuses.topLeft})`
236+
: '',
237+
'--pc-box-border-radius-top-right': borderRadiuses.topRight
238+
? `var(--p-border-radius-${borderRadiuses.topRight})`
239+
: '',
240+
'--pc-box-shadow': shadow ? `var(--p-shadow-${shadow})` : '',
241+
} as React.CSSProperties;
242+
243+
const className = classNames(styles.root);
244+
245+
return (
246+
<Component ref={ref} className={className} style={style}>
247+
{children}
248+
</Component>
249+
);
250+
},
251+
) as PolymorphicBox;
252+
253+
Box.displayName = 'Box';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './Box';

polaris-react/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ export type {
6969
BannerHandles,
7070
} from './components/Banner';
7171

72+
export {Box} from './components/Box';
73+
export type {BoxProps} from './components/Box';
74+
7275
export {Breadcrumbs} from './components/Breadcrumbs';
7376
export type {BreadcrumbsProps} from './components/Breadcrumbs';
7477

yarn.lock

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2402,6 +2402,11 @@
24022402
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1"
24032403
integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==
24042404

2405+
"@radix-ui/react-polymorphic@^0.0.14":
2406+
version "0.0.14"
2407+
resolved "https://registry.yarnpkg.com/@radix-ui/react-polymorphic/-/react-polymorphic-0.0.14.tgz#fc6cefee6686db8c5a7ff14c8c1b9b5abdee325b"
2408+
integrity sha512-9nsMZEDU3LeIUeHJrpkkhZVxu/9Fc7P2g2I3WR+uA9mTbNC3hGaabi0dV6wg0CfHb+m4nSs1pejbE/5no3MJTA==
2409+
24052410
"@rollup/plugin-babel@^5.3.1":
24062411
version "5.3.1"
24072412
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"

0 commit comments

Comments
 (0)