Improve layout components
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -5,13 +5,10 @@ import * as Table from '../../storybook/table';
|
||||
import { Box } from './box';
|
||||
import { Stack } from '../stack/stack';
|
||||
import { Title, Text } from '../../storybook';
|
||||
import {
|
||||
alignItems,
|
||||
backgroundColors,
|
||||
boxShadows,
|
||||
borderRadius,
|
||||
display,
|
||||
} from './properties';
|
||||
import { Padding } from './docs/padding';
|
||||
import { listResponsiveValues, listColorValues } from '../../utils/list-values';
|
||||
import { responsiveProperties } from './sprinkles.css';
|
||||
import { PropsTable } from './docs/props-table';
|
||||
|
||||
<Meta of={BoxStories} />
|
||||
|
||||
@@ -20,137 +17,19 @@ import {
|
||||
<Title type="h1">Box</Title>
|
||||
<Text>
|
||||
Box is the lowest-level component in Canon. We use it internally to build all
|
||||
of our components.
|
||||
of our components. It is a wrapper around the HTML `<div>` element and
|
||||
provides a consistent API for styling and layout. You'll find below a list of all the available properties.
|
||||
</Text>
|
||||
|
||||
<Stack
|
||||
direction="column"
|
||||
items="center"
|
||||
justify="center"
|
||||
borderRadius="small"
|
||||
mb="xxl"
|
||||
gap="xl"
|
||||
py="xl"
|
||||
style={{ border: '1px solid #e7e7e7' }}
|
||||
>
|
||||
<Stack items="center" gap="xl">
|
||||
<Stack
|
||||
items="center"
|
||||
justify="center"
|
||||
borderRadius="small"
|
||||
p="md"
|
||||
style={{ background: '#c4cafb', color: 'white' }}
|
||||
>
|
||||
<Stack
|
||||
px="md"
|
||||
py="sm"
|
||||
borderRadius="small"
|
||||
style={{ background: '#1f47ff', color: 'white' }}
|
||||
>
|
||||
<span>padding</span>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
items="center"
|
||||
justify="center"
|
||||
borderRadius="small"
|
||||
px="md"
|
||||
style={{ background: '#c4cafb', color: 'white' }}
|
||||
>
|
||||
<Stack
|
||||
px="md"
|
||||
py="sm"
|
||||
borderRadius="small"
|
||||
style={{ background: '#1f47ff', color: 'white' }}
|
||||
>
|
||||
<span>paddingX</span>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
items="center"
|
||||
justify="center"
|
||||
borderRadius="small"
|
||||
py="md"
|
||||
style={{ background: '#c4cafb', color: 'white' }}
|
||||
>
|
||||
<Stack
|
||||
px="md"
|
||||
py="sm"
|
||||
borderRadius="small"
|
||||
style={{ background: '#1f47ff', color: 'white' }}
|
||||
>
|
||||
<span>paddingY</span>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack items="center" gap="xl">
|
||||
<Stack
|
||||
items="center"
|
||||
justify="center"
|
||||
borderRadius="small"
|
||||
pt="md"
|
||||
style={{ background: '#c4cafb', color: 'white' }}
|
||||
>
|
||||
<Stack
|
||||
px="md"
|
||||
py="sm"
|
||||
borderRadius="small"
|
||||
style={{ background: '#1f47ff', color: 'white' }}
|
||||
>
|
||||
<span>paddingTop</span>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
items="center"
|
||||
justify="center"
|
||||
borderRadius="small"
|
||||
pb="md"
|
||||
style={{ background: '#c4cafb', color: 'white' }}
|
||||
>
|
||||
<Stack
|
||||
px="md"
|
||||
py="sm"
|
||||
borderRadius="small"
|
||||
style={{ background: '#1f47ff', color: 'white' }}
|
||||
>
|
||||
<span>paddingBottom</span>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
items="center"
|
||||
justify="center"
|
||||
borderRadius="small"
|
||||
pl="md"
|
||||
style={{ background: '#c4cafb', color: 'white' }}
|
||||
>
|
||||
<Stack
|
||||
px="md"
|
||||
py="sm"
|
||||
borderRadius="small"
|
||||
style={{ background: '#1f47ff', color: 'white' }}
|
||||
>
|
||||
<span>paddingLeft</span>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
items="center"
|
||||
justify="center"
|
||||
borderRadius="small"
|
||||
pr="md"
|
||||
style={{ background: '#c4cafb', color: 'white' }}
|
||||
>
|
||||
<Stack
|
||||
px="md"
|
||||
py="sm"
|
||||
borderRadius="small"
|
||||
style={{ background: '#1f47ff', color: 'white' }}
|
||||
>
|
||||
<span>paddingRight</span>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<PropsTable />
|
||||
|
||||
<Title type="h2">Padding & Margin</Title>
|
||||
<Text>
|
||||
Padding and margin are used to create space around your component using our
|
||||
predefined spacing tokens. We would recommend to use padding over margin to
|
||||
avoid collapsing margins but both are available.
|
||||
</Text>
|
||||
<Padding />
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
@@ -160,68 +39,6 @@ import {
|
||||
</Table.HeaderRow>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>alignItems</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{alignItems.map(n => (
|
||||
<Chip>{n}</Chip>
|
||||
))}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Chip>stretch</Chip>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>background</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{Object.keys(backgroundColors).map(n => (
|
||||
<Chip>{n}</Chip>
|
||||
))}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Chip>transparent</Chip>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>borderRadius</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{Object.keys(borderRadius).map(n => (
|
||||
<Chip>{n}</Chip>
|
||||
))}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Chip>none</Chip>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>boxShadow</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{Object.keys(boxShadows).map(n => (
|
||||
<Chip>{n}</Chip>
|
||||
))}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>display</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{display.map(n => (
|
||||
<Chip>{n}</Chip>
|
||||
))}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Chip>block</Chip>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Chip head>padding</Chip>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Box } from './box';
|
||||
@@ -32,10 +33,10 @@ type Story = StoryObj<typeof meta>;
|
||||
export const Basic: Story = {
|
||||
render: () => (
|
||||
<Box
|
||||
padding="medium"
|
||||
padding="xs"
|
||||
background="background"
|
||||
paddingX="large"
|
||||
paddingY="medium"
|
||||
paddingX="lg"
|
||||
paddingY="md"
|
||||
borderRadius="small"
|
||||
>
|
||||
Basic Box
|
||||
@@ -47,7 +48,7 @@ export const Responsive: Story = {
|
||||
render: () => (
|
||||
<Box
|
||||
display={{ xs: 'block', sm: 'flex' }}
|
||||
padding={{ xs: 'small', sm: 'medium', lg: 'large' }}
|
||||
padding={{ xs: 'xs', sm: 'md', lg: 'lg' }}
|
||||
background="background"
|
||||
>
|
||||
Responsive Box
|
||||
@@ -60,16 +61,16 @@ export const FlexContainer: Story = {
|
||||
<Box
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
padding="large"
|
||||
padding="lg"
|
||||
background="background"
|
||||
>
|
||||
<Box padding="medium" background="background">
|
||||
<Box padding="md" background="background" color="primary">
|
||||
Item 1
|
||||
</Box>
|
||||
<Box padding="medium" background="background">
|
||||
<Box padding="md" background="background" color="primary">
|
||||
Item 2
|
||||
</Box>
|
||||
<Box padding="medium" background="background">
|
||||
<Box padding="md" background="background" color="primary">
|
||||
Item 3
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -78,24 +79,24 @@ export const FlexContainer: Story = {
|
||||
|
||||
export const Nested: Story = {
|
||||
render: () => (
|
||||
<Box padding="large" background="background">
|
||||
<Box padding="medium" background="background">
|
||||
<Box padding="lg" background="background">
|
||||
<Box padding="md" background="background">
|
||||
Header
|
||||
</Box>
|
||||
<Box
|
||||
display="flex"
|
||||
padding="medium"
|
||||
padding="md"
|
||||
background="background"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Box padding="small" background="background">
|
||||
<Box padding="xs" background="background">
|
||||
Sidebar
|
||||
</Box>
|
||||
<Box padding="small" background="background">
|
||||
<Box padding="xs" background="background">
|
||||
Main Content
|
||||
</Box>
|
||||
</Box>
|
||||
<Box padding="medium" background="background">
|
||||
<Box padding="md" background="background">
|
||||
Footer
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -106,13 +107,13 @@ export const Alignment: Story = {
|
||||
render: () => (
|
||||
<Box
|
||||
display="flex"
|
||||
padding="large"
|
||||
padding="lg"
|
||||
background="background"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
style={{ height: '200px' }}
|
||||
>
|
||||
<Box padding="medium" background="background">
|
||||
<Box padding="md" background="background">
|
||||
Centered Content
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -122,17 +123,17 @@ export const Alignment: Story = {
|
||||
// Example showing different spacing combinations
|
||||
export const Spacing: Story = {
|
||||
render: () => (
|
||||
<Box display="flex" flexDirection="column" gap="medium">
|
||||
<Box padding="small" background="background">
|
||||
<Box display="flex" flexDirection="column" gap="md">
|
||||
<Box padding="xs" background="background">
|
||||
Small Padding
|
||||
</Box>
|
||||
<Box padding="medium" background="background">
|
||||
<Box padding="md" background="background">
|
||||
Medium Padding
|
||||
</Box>
|
||||
<Box padding="large" background="background">
|
||||
<Box padding="lg" background="background">
|
||||
Large Padding
|
||||
</Box>
|
||||
<Box paddingX="large" paddingY="small" background="background">
|
||||
<Box paddingX="lg" paddingY="xs" background="background">
|
||||
Mixed Padding
|
||||
</Box>
|
||||
</Box>
|
||||
@@ -142,14 +143,14 @@ export const Spacing: Story = {
|
||||
// Example showing different display values
|
||||
export const DisplayVariants: Story = {
|
||||
render: () => (
|
||||
<Box display="flex" flexDirection="column" gap="medium">
|
||||
<Box padding="medium" background="background" display="block">
|
||||
<Box display="flex" flexDirection="column" gap="md">
|
||||
<Box padding="md" background="background" display="block">
|
||||
Display Block
|
||||
</Box>
|
||||
<Box padding="medium" background="background" display="flex">
|
||||
<Box padding="md" background="background" display="flex">
|
||||
Display Flex
|
||||
</Box>
|
||||
<Box padding="medium" background="background" display="inline">
|
||||
<Box padding="md" background="background" display="inline">
|
||||
Display Inline
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import React from 'react';
|
||||
import { Stack } from '../../stack/stack';
|
||||
import { Inline } from '../../inline/inline';
|
||||
import { Box } from '../box';
|
||||
|
||||
const FakeBox = ({ children }: { children: string }) => (
|
||||
<Box
|
||||
px="md"
|
||||
py="sm"
|
||||
borderRadius="small"
|
||||
style={{ background: '#1f47ff', color: 'white' }}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const Padding = () => {
|
||||
return (
|
||||
<Stack
|
||||
align="center"
|
||||
borderRadius="small"
|
||||
mb="md"
|
||||
gap="xl"
|
||||
py="xl"
|
||||
style={{ border: '1px solid #e7e7e7' }}
|
||||
>
|
||||
<Inline align="center" gap="xl">
|
||||
<Stack
|
||||
align="center"
|
||||
borderRadius="small"
|
||||
p="md"
|
||||
style={{ background: '#c4cafb', color: 'white' }}
|
||||
>
|
||||
<FakeBox>padding</FakeBox>
|
||||
</Stack>
|
||||
<Stack
|
||||
align="center"
|
||||
borderRadius="small"
|
||||
px="md"
|
||||
style={{ background: '#c4cafb', color: 'white' }}
|
||||
>
|
||||
<FakeBox>paddingX</FakeBox>
|
||||
</Stack>
|
||||
<Stack
|
||||
align="center"
|
||||
borderRadius="small"
|
||||
py="md"
|
||||
style={{ background: '#c4cafb', color: 'white' }}
|
||||
>
|
||||
<FakeBox>paddingY</FakeBox>
|
||||
</Stack>
|
||||
</Inline>
|
||||
<Inline align="center" gap="xl">
|
||||
<Stack
|
||||
align="center"
|
||||
borderRadius="small"
|
||||
pt="md"
|
||||
style={{ background: '#c4cafb', color: 'white' }}
|
||||
>
|
||||
<FakeBox>paddingTop</FakeBox>
|
||||
</Stack>
|
||||
<Stack
|
||||
align="center"
|
||||
borderRadius="small"
|
||||
pb="md"
|
||||
style={{ background: '#c4cafb', color: 'white' }}
|
||||
>
|
||||
<FakeBox>paddingBottom</FakeBox>
|
||||
</Stack>
|
||||
<Stack
|
||||
align="center"
|
||||
borderRadius="small"
|
||||
pl="md"
|
||||
style={{ background: '#c4cafb', color: 'white' }}
|
||||
>
|
||||
<FakeBox>paddingLeft</FakeBox>
|
||||
</Stack>
|
||||
<Stack
|
||||
align="center"
|
||||
borderRadius="small"
|
||||
pr="md"
|
||||
style={{ background: '#c4cafb', color: 'white' }}
|
||||
>
|
||||
<FakeBox>paddingRight</FakeBox>
|
||||
</Stack>
|
||||
</Inline>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import * as Table from '../../../storybook';
|
||||
import { Chip } from '../../../storybook';
|
||||
import { listResponsiveValues } from '../../../utils/list-values';
|
||||
import { responsiveProperties } from '../sprinkles.css';
|
||||
|
||||
export const PropsTable = () => {
|
||||
return (
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.HeaderRow>
|
||||
<Table.HeaderCell>Prop</Table.HeaderCell>
|
||||
<Table.HeaderCell>Type</Table.HeaderCell>
|
||||
</Table.HeaderRow>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{Object.keys(responsiveProperties.styles)
|
||||
.filter(
|
||||
n =>
|
||||
![
|
||||
'padding',
|
||||
'paddingX',
|
||||
'paddingY',
|
||||
'paddingLeft',
|
||||
'paddingRight',
|
||||
'paddingTop',
|
||||
'paddingBottom',
|
||||
'p',
|
||||
'px',
|
||||
'py',
|
||||
'pl',
|
||||
'pr',
|
||||
'pt',
|
||||
'pb',
|
||||
'margin',
|
||||
'marginX',
|
||||
'marginY',
|
||||
'marginLeft',
|
||||
'marginRight',
|
||||
'marginTop',
|
||||
'marginBottom',
|
||||
'm',
|
||||
'mx',
|
||||
'my',
|
||||
'ml',
|
||||
'mr',
|
||||
'mt',
|
||||
'mb',
|
||||
].includes(n),
|
||||
)
|
||||
.map(n => (
|
||||
<Table.Row key={n}>
|
||||
<Table.Cell>
|
||||
<Chip head>{n}</Chip>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{listResponsiveValues(
|
||||
n as keyof typeof responsiveProperties.styles,
|
||||
).map(value => (
|
||||
<Chip key={value}>{value}</Chip>
|
||||
))}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,3 @@
|
||||
export const alignItems = ['stretch', 'flex-start', 'center', 'flex-end'];
|
||||
|
||||
export const breakpoints = {
|
||||
xs: {},
|
||||
sm: { '@media': 'screen and (min-width: 640px)' },
|
||||
@@ -9,49 +7,6 @@ export const breakpoints = {
|
||||
'2xl': { '@media': 'screen and (min-width: 1536px)' },
|
||||
};
|
||||
|
||||
export const borderRadius = {
|
||||
none: 0,
|
||||
small: '4px',
|
||||
medium: '8px',
|
||||
full: '9999px',
|
||||
};
|
||||
|
||||
export const backgroundColors = {
|
||||
background: 'var(--canon-color-background)',
|
||||
elevation1: 'var(--canon-color-elevation1)',
|
||||
elevation2: 'var(--canon-color-elevation2)',
|
||||
transparent: 'transparent',
|
||||
};
|
||||
|
||||
export const border = {
|
||||
none: 'none',
|
||||
thin: '1px solid var(--canon-outline)',
|
||||
error: '1px solid var(--canon-error)',
|
||||
};
|
||||
|
||||
export const boxShadows = {
|
||||
small: 'var(--canon-box-shadow-small)',
|
||||
medium: 'var(--canon-box-shadow-medium)',
|
||||
large: 'var(--canon-box-shadow-large)',
|
||||
};
|
||||
|
||||
export const colors = {
|
||||
background: '#eff6ff',
|
||||
};
|
||||
|
||||
export const display = ['none', 'flex', 'block', 'inline'];
|
||||
|
||||
export const flexDirection = ['row', 'column'] as const;
|
||||
|
||||
export const justifyContent = [
|
||||
'stretch',
|
||||
'flex-start',
|
||||
'center',
|
||||
'flex-end',
|
||||
'space-around',
|
||||
'space-between',
|
||||
];
|
||||
|
||||
export const space = {
|
||||
none: 0,
|
||||
xxs: 'var(--space-xxs)',
|
||||
|
||||
@@ -13,84 +13,101 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles';
|
||||
|
||||
import {
|
||||
alignItems,
|
||||
borderRadius,
|
||||
breakpoints,
|
||||
colors,
|
||||
backgroundColors,
|
||||
display,
|
||||
flexDirection,
|
||||
justifyContent,
|
||||
space,
|
||||
boxShadows,
|
||||
border,
|
||||
} from './properties';
|
||||
defineProperties,
|
||||
createSprinkles,
|
||||
RequiredConditionalValue,
|
||||
ConditionalValue,
|
||||
createMapValueFn,
|
||||
} from '@vanilla-extract/sprinkles';
|
||||
import { breakpoints, space } from './properties';
|
||||
|
||||
export const commonProperties = {
|
||||
flexDirection,
|
||||
justifyContent,
|
||||
alignItems,
|
||||
borderRadius,
|
||||
boxShadow: boxShadows,
|
||||
border,
|
||||
paddingTop: space,
|
||||
paddingBottom: space,
|
||||
paddingLeft: space,
|
||||
paddingRight: space,
|
||||
marginTop: space,
|
||||
marginBottom: space,
|
||||
marginLeft: space,
|
||||
marginRight: space,
|
||||
gap: space,
|
||||
};
|
||||
|
||||
export const boxShorthands: Record<string, string[]> = {
|
||||
padding: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'],
|
||||
paddingX: ['paddingLeft', 'paddingRight'],
|
||||
paddingY: ['paddingTop', 'paddingBottom'],
|
||||
p: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'],
|
||||
pt: ['paddingTop'],
|
||||
pr: ['paddingRight'],
|
||||
pb: ['paddingBottom'],
|
||||
pl: ['paddingLeft'],
|
||||
px: ['paddingLeft', 'paddingRight'],
|
||||
py: ['paddingTop', 'paddingBottom'],
|
||||
margin: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'],
|
||||
marginX: ['marginLeft', 'marginRight'],
|
||||
marginY: ['marginTop', 'marginBottom'],
|
||||
m: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'],
|
||||
mt: ['marginTop'],
|
||||
mr: ['marginRight'],
|
||||
mb: ['marginBottom'],
|
||||
ml: ['marginLeft'],
|
||||
mx: ['marginLeft', 'marginRight'],
|
||||
my: ['marginTop', 'marginBottom'],
|
||||
};
|
||||
|
||||
const responsiveProperties = defineProperties({
|
||||
export const responsiveProperties = defineProperties({
|
||||
conditions: breakpoints,
|
||||
defaultCondition: 'xs',
|
||||
responsiveArray: ['xs', 'sm', 'md', 'lg', 'xl', '2xl'],
|
||||
properties: {
|
||||
...commonProperties,
|
||||
display,
|
||||
flexDirection: ['row', 'column'],
|
||||
justifyContent: [
|
||||
'stretch',
|
||||
'flex-start',
|
||||
'center',
|
||||
'flex-end',
|
||||
'space-around',
|
||||
'space-between',
|
||||
],
|
||||
alignItems: ['stretch', 'flex-start', 'center', 'flex-end'],
|
||||
borderRadius: {
|
||||
none: 0,
|
||||
small: '4px',
|
||||
medium: '8px',
|
||||
full: '9999px',
|
||||
},
|
||||
boxShadow: {
|
||||
small: 'var(--canon-box-shadow-small)',
|
||||
medium: 'var(--canon-box-shadow-medium)',
|
||||
large: 'var(--canon-box-shadow-large)',
|
||||
},
|
||||
border: {
|
||||
none: 'none',
|
||||
thin: '1px solid var(--canon-outline)',
|
||||
error: '1px solid var(--canon-error)',
|
||||
},
|
||||
display: ['none', 'flex', 'block', 'inline'],
|
||||
paddingTop: space,
|
||||
paddingBottom: space,
|
||||
paddingLeft: space,
|
||||
paddingRight: space,
|
||||
marginTop: space,
|
||||
marginBottom: space,
|
||||
marginLeft: space,
|
||||
marginRight: space,
|
||||
gap: space,
|
||||
flexWrap: ['wrap', 'nowrap'],
|
||||
},
|
||||
shorthands: {
|
||||
...boxShorthands,
|
||||
placeItems: ['justifyContent', 'alignItems'],
|
||||
padding: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'],
|
||||
paddingX: ['paddingLeft', 'paddingRight'],
|
||||
paddingY: ['paddingTop', 'paddingBottom'],
|
||||
p: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'],
|
||||
pt: ['paddingTop'],
|
||||
pr: ['paddingRight'],
|
||||
pb: ['paddingBottom'],
|
||||
pl: ['paddingLeft'],
|
||||
px: ['paddingLeft', 'paddingRight'],
|
||||
py: ['paddingTop', 'paddingBottom'],
|
||||
margin: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'],
|
||||
marginX: ['marginLeft', 'marginRight'],
|
||||
marginY: ['marginTop', 'marginBottom'],
|
||||
m: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'],
|
||||
mt: ['marginTop'],
|
||||
mr: ['marginRight'],
|
||||
mb: ['marginBottom'],
|
||||
ml: ['marginLeft'],
|
||||
mx: ['marginLeft', 'marginRight'],
|
||||
my: ['marginTop', 'marginBottom'],
|
||||
},
|
||||
});
|
||||
|
||||
const colorProperties = defineProperties({
|
||||
export const colorProperties = defineProperties({
|
||||
conditions: {
|
||||
light: { selector: '[data-theme="light"] &' },
|
||||
dark: { selector: '[data-theme="dark"] &' },
|
||||
},
|
||||
defaultCondition: ['light', 'dark'],
|
||||
properties: {
|
||||
color: colors,
|
||||
background: backgroundColors,
|
||||
color: {
|
||||
primary: 'var(--canon-text-primary)',
|
||||
secondary: 'var(--canon-text-secondary)',
|
||||
error: 'var(--canon-error)',
|
||||
},
|
||||
background: {
|
||||
background: 'var(--canon-background)',
|
||||
elevation1: 'var(--canon-surface-1)',
|
||||
elevation2: 'var(--canon-surface-2)',
|
||||
transparent: 'transparent',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -98,3 +115,11 @@ export const sprinkles = createSprinkles(responsiveProperties, colorProperties);
|
||||
|
||||
// It's a good idea to export the Sprinkles type too
|
||||
export type Sprinkles = Parameters<typeof sprinkles>[0];
|
||||
|
||||
export type OptionalResponsiveValue<Value extends string | number> =
|
||||
ConditionalValue<typeof responsiveProperties, Value>;
|
||||
|
||||
export type RequiredResponsiveValue<Value extends string | number> =
|
||||
RequiredConditionalValue<typeof responsiveProperties, Value>;
|
||||
|
||||
export const mapResponsiveValue = createMapValueFn(responsiveProperties);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Inline } from './inline';
|
||||
import { Box } from '../box/box';
|
||||
|
||||
const meta = {
|
||||
title: 'Layout/Inline',
|
||||
component: Inline,
|
||||
} satisfies Meta<typeof Inline>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const FakeBox = () => (
|
||||
<Box
|
||||
px="xl"
|
||||
py="md"
|
||||
borderRadius="small"
|
||||
style={{ background: '#1f47ff', color: 'white' }}
|
||||
>
|
||||
Fake Box
|
||||
</Box>
|
||||
);
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<FakeBox />
|
||||
<FakeBox />
|
||||
<FakeBox />
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const LargeGap: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
gap: 'xl',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Box, BoxProps } from '../box/box';
|
||||
import { alignToFlexAlign } from '../../utils/align';
|
||||
|
||||
export const validInlineComponents = [
|
||||
'div',
|
||||
'span',
|
||||
'p',
|
||||
'nav',
|
||||
'ul',
|
||||
'ol',
|
||||
'li',
|
||||
] as const;
|
||||
|
||||
export interface InlineProps extends Omit<BoxProps, 'alignItems'> {
|
||||
as?: (typeof validInlineComponents)[number];
|
||||
children: React.ReactNode;
|
||||
align?: 'left' | 'center' | 'right';
|
||||
gap?: BoxProps['gap'];
|
||||
}
|
||||
|
||||
export const Inline = ({
|
||||
align,
|
||||
as = 'div',
|
||||
children,
|
||||
gap = 'xs',
|
||||
...restProps
|
||||
}: InlineProps) => {
|
||||
return (
|
||||
<Box
|
||||
as={as}
|
||||
display="flex"
|
||||
alignItems={align !== 'left' ? alignToFlexAlign(align) : undefined}
|
||||
justifyContent="flex-start"
|
||||
flexWrap="wrap"
|
||||
gap={gap}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles';
|
||||
import { breakpoints } from '../box/properties';
|
||||
import { boxShorthands, commonProperties } from '../box/sprinkles.css';
|
||||
|
||||
const responsiveProperties = defineProperties({
|
||||
conditions: breakpoints,
|
||||
defaultCondition: 'xs',
|
||||
properties: {
|
||||
...commonProperties,
|
||||
},
|
||||
shorthands: {
|
||||
...boxShorthands,
|
||||
direction: ['flexDirection'],
|
||||
items: ['alignItems'],
|
||||
justify: ['justifyContent'],
|
||||
},
|
||||
});
|
||||
|
||||
export const sprinkles = createSprinkles(responsiveProperties);
|
||||
|
||||
// It's a good idea to export the Sprinkles type too
|
||||
export type Sprinkles = Parameters<typeof sprinkles>[0];
|
||||
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const stackStyles = style({
|
||||
fontFamily: 'var(--canon-font-sans)',
|
||||
display: 'flex',
|
||||
});
|
||||
@@ -21,42 +21,37 @@ import { Box } from '../box/box';
|
||||
const meta = {
|
||||
title: 'Layout/Stack',
|
||||
component: Stack,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
// tags: ['autodocs'],
|
||||
} satisfies Meta<typeof Stack>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const FakeBox = () => (
|
||||
<Box
|
||||
background="background"
|
||||
paddingX="large"
|
||||
paddingY="medium"
|
||||
px="xl"
|
||||
py="md"
|
||||
borderRadius="small"
|
||||
style={{ background: '#1f47ff', color: 'white' }}
|
||||
>
|
||||
Fake Box
|
||||
</Box>
|
||||
);
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
export const Column: Story = {
|
||||
render: () => (
|
||||
<Stack gap="medium" direction="column">
|
||||
<FakeBox />
|
||||
<FakeBox />
|
||||
<FakeBox />
|
||||
</Stack>
|
||||
),
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: (
|
||||
<>
|
||||
<FakeBox />
|
||||
<FakeBox />
|
||||
<FakeBox />
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export const Row: Story = {
|
||||
render: () => (
|
||||
<Stack gap="medium" direction="row">
|
||||
<FakeBox />
|
||||
<FakeBox />
|
||||
<FakeBox />
|
||||
</Stack>
|
||||
),
|
||||
export const LargeGap: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
gap: 'xl',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -13,47 +13,62 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { createElement } from 'react';
|
||||
import { sprinkles, Sprinkles } from './sprinkles.css';
|
||||
import { stackStyles } from './stack.css';
|
||||
|
||||
type HTMLProperties = Omit<
|
||||
React.AllHTMLAttributes<HTMLElement>,
|
||||
keyof Sprinkles
|
||||
>;
|
||||
import React from 'react';
|
||||
import { Box, BoxProps } from '../box/box';
|
||||
import { alignToFlexAlign } from '../../utils/align';
|
||||
|
||||
export type StackProps = Sprinkles &
|
||||
HTMLProperties & {
|
||||
as?: keyof JSX.IntrinsicElements;
|
||||
};
|
||||
const validStackComponents = [
|
||||
'div',
|
||||
'span',
|
||||
'p',
|
||||
'article',
|
||||
'section',
|
||||
'main',
|
||||
'nav',
|
||||
'aside',
|
||||
'ul',
|
||||
'ol',
|
||||
'li',
|
||||
'details',
|
||||
'summary',
|
||||
'dd',
|
||||
'dl',
|
||||
'dt',
|
||||
] as const;
|
||||
|
||||
export interface StackProps extends Omit<BoxProps, 'alignItems'> {
|
||||
children: React.ReactNode;
|
||||
as?: (typeof validStackComponents)[number];
|
||||
align?: 'left' | 'center' | 'right';
|
||||
gap?: BoxProps['gap'];
|
||||
}
|
||||
|
||||
export const Stack = ({
|
||||
as = 'div',
|
||||
className,
|
||||
style,
|
||||
...props
|
||||
children,
|
||||
align: alignProp,
|
||||
gap = 'xs',
|
||||
...restProps
|
||||
}: StackProps) => {
|
||||
const sprinklesProps: Record<string, unknown> = {};
|
||||
const nativeProps: Record<string, unknown> = {};
|
||||
/**
|
||||
* Creating a seam between the provided prop and the default value
|
||||
* to enable only setting the text alignment when the `align` prop
|
||||
* is provided — not when it's defaulted.
|
||||
*/
|
||||
const align = alignProp || 'left';
|
||||
|
||||
// Split props between sprinkles and native HTML props
|
||||
Object.entries(props).forEach(([key, value]) => {
|
||||
if (value === undefined) return;
|
||||
|
||||
if (sprinkles.properties.has(key as keyof Sprinkles)) {
|
||||
sprinklesProps[key] = value;
|
||||
} else {
|
||||
nativeProps[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
const sprinklesClassName = sprinkles(sprinklesProps);
|
||||
|
||||
return createElement(as, {
|
||||
className: [stackStyles, sprinklesClassName, className]
|
||||
.filter(Boolean)
|
||||
.join(' '),
|
||||
style,
|
||||
...nativeProps,
|
||||
});
|
||||
return (
|
||||
<Box
|
||||
as={as}
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
alignItems={align !== 'left' ? alignToFlexAlign(align) : undefined}
|
||||
gap={gap}
|
||||
// textAlign={alignProp}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* Light theme */
|
||||
:root {
|
||||
--canon-accent: #1ed760;
|
||||
--canon-bg: #fff;
|
||||
--canon-background: #fff;
|
||||
--canon-surface-1: #f5f5f5;
|
||||
--canon-surface-2: #000;
|
||||
--canon-outline: #666;
|
||||
@@ -31,7 +31,7 @@
|
||||
/* Dark theme */
|
||||
[data-theme='dark'] {
|
||||
--canon-accent: #1ed760;
|
||||
--canon-bg: #fff;
|
||||
--canon-background: #fff;
|
||||
--canon-surface-1: #f5f5f5;
|
||||
--canon-surface-2: #000;
|
||||
--canon-outline: #666;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
type OptionalResponsiveValue,
|
||||
mapResponsiveValue,
|
||||
} from '../components/box/sprinkles.css';
|
||||
|
||||
export type Align = 'left' | 'center' | 'right';
|
||||
export type AlignY = 'top' | 'center' | 'bottom';
|
||||
|
||||
const alignToFlexAlignLookup = {
|
||||
left: 'flex-start',
|
||||
center: 'center',
|
||||
right: 'flex-end',
|
||||
} as const;
|
||||
|
||||
export const alignToFlexAlign = (
|
||||
align: OptionalResponsiveValue<Align> | undefined,
|
||||
) =>
|
||||
align
|
||||
? mapResponsiveValue(align, value => alignToFlexAlignLookup[value])
|
||||
: undefined;
|
||||
|
||||
const alignYToFlexAlignLookup = {
|
||||
top: 'flex-start',
|
||||
center: 'center',
|
||||
bottom: 'flex-end',
|
||||
} as const;
|
||||
|
||||
export const alignYToFlexAlign = (
|
||||
alignY: OptionalResponsiveValue<AlignY> | undefined,
|
||||
) =>
|
||||
alignY
|
||||
? mapResponsiveValue(alignY, value => alignYToFlexAlignLookup[value])
|
||||
: undefined;
|
||||
@@ -0,0 +1,26 @@
|
||||
import {
|
||||
responsiveProperties,
|
||||
colorProperties,
|
||||
} from '../components/box/sprinkles.css';
|
||||
|
||||
export const listResponsiveValues = (
|
||||
value: keyof typeof responsiveProperties.styles,
|
||||
) => {
|
||||
const values = responsiveProperties.styles[value];
|
||||
|
||||
if ('values' in values) {
|
||||
return Object.keys(values.values);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
export const listColorValues = (value: keyof typeof colorProperties.styles) => {
|
||||
const values = colorProperties.styles[value];
|
||||
|
||||
if ('values' in values) {
|
||||
return Object.keys(values.values);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
Reference in New Issue
Block a user