From da1d5bbe8dcca2e88a9d88e9ff06ed69be188c4e Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Thu, 28 Nov 2024 18:37:26 +0000 Subject: [PATCH] Improve Box + Stack components Signed-off-by: Charles de Dreuille --- .../list-values.ts => docs/utils/argTypes.ts} | 32 ++++- .../canon/src/components/Box/Box.stories.tsx | 88 +++--------- packages/canon/src/components/Box/Box.tsx | 29 ++-- packages/canon/src/components/Box/Docs.mdx | 6 - .../src/components/Box/docs/props-table.tsx | 8 +- .../src/components/Box/docs/spacing-table.tsx | 8 +- packages/canon/src/components/Box/index.tsx | 2 +- .../canon/src/components/Box/sprinkles.css.ts | 62 +-------- packages/canon/src/components/Box/types.ts | 117 ++-------------- .../canon/src/components/Inline/Inline.tsx | 3 +- .../src/components/Stack/Stack.stories.tsx | 66 +++++++++ packages/canon/src/components/Stack/Stack.tsx | 79 +++++------ .../src/components/Stack/sprinkles.css.ts | 73 ++-------- .../canon/src/components/Stack/styles.css | 4 + packages/canon/src/components/Stack/types.ts | 32 +++++ .../{components/Box => layout}/properties.ts | 0 packages/canon/src/layout/sprinkles.css.ts | 63 +++++++++ packages/canon/src/layout/types.ts | 131 ++++++++++++++++++ packages/canon/src/theme/styles.css | 1 + packages/canon/src/utils/align.ts | 49 ------- 20 files changed, 426 insertions(+), 427 deletions(-) rename packages/canon/{src/utils/list-values.ts => docs/utils/argTypes.ts} (55%) create mode 100644 packages/canon/src/components/Stack/styles.css create mode 100644 packages/canon/src/components/Stack/types.ts rename packages/canon/src/{components/Box => layout}/properties.ts (100%) create mode 100644 packages/canon/src/layout/sprinkles.css.ts create mode 100644 packages/canon/src/layout/types.ts delete mode 100644 packages/canon/src/utils/align.ts diff --git a/packages/canon/src/utils/list-values.ts b/packages/canon/docs/utils/argTypes.ts similarity index 55% rename from packages/canon/src/utils/list-values.ts rename to packages/canon/docs/utils/argTypes.ts index 9e3464a4f5..ff5a159677 100644 --- a/packages/canon/src/utils/list-values.ts +++ b/packages/canon/docs/utils/argTypes.ts @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import { boxProperties } from '../../src/components/Box/sprinkles.css'; import { - responsiveProperties, colorProperties, -} from '../components/Box/sprinkles.css'; + spacingProperties, +} from '../../src/layout/sprinkles.css'; export const listResponsiveValues = ( - value: keyof typeof responsiveProperties.styles, + value: keyof typeof boxProperties.styles, ) => { - const values = responsiveProperties.styles[value]; + const values = boxProperties.styles[value]; if ('values' in values) { return Object.keys(values.values); @@ -40,3 +40,25 @@ export const listColorValues = (value: keyof typeof colorProperties.styles) => { return []; }; + +export const argTypesSpacing = Object.keys(spacingProperties.styles).reduce< + Record +>((acc, n) => { + acc[n] = { + control: 'inline-radio', + options: ['xs', 'sm', 'md', 'lg', 'xl', 'xxl'], + }; + return acc; +}, {}); + +export const argTypesColor = Object.keys(colorProperties.styles).reduce< + Record +>((acc, n) => { + acc[n as keyof typeof colorProperties.styles] = { + control: 'select', + options: Object.keys( + colorProperties.styles[n as keyof typeof colorProperties.styles].values, + ), + }; + return acc; +}, {}); diff --git a/packages/canon/src/components/Box/Box.stories.tsx b/packages/canon/src/components/Box/Box.stories.tsx index 8b8a2d7c56..6f15d984e1 100644 --- a/packages/canon/src/components/Box/Box.stories.tsx +++ b/packages/canon/src/components/Box/Box.stories.tsx @@ -17,87 +17,41 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react'; import { Box } from './Box'; -import { listResponsiveValues } from '../../utils/list-values'; -import { responsiveProperties, colorProperties } from './sprinkles.css'; +import { + listResponsiveValues, + argTypesSpacing, + argTypesColor, +} from '../../../docs/utils/argTypes'; +import { boxProperties } from './sprinkles.css'; -const argTypesResponsive = Object.keys(responsiveProperties.styles).reduce< +const argTypesBox = Object.keys(boxProperties.styles).reduce< Record >((acc, n) => { - if ( - [ - 'margin', - 'marginBottom', - 'marginLeft', - 'marginRight', - 'marginTop', - 'marginX', - 'marginY', - 'padding', - 'paddingBottom', - 'paddingLeft', - 'paddingRight', - 'paddingTop', - 'paddingX', - 'paddingY', - ].includes(n) - ) { - acc[n] = { - control: 'select', - options: ['xs', 'sm', 'md', 'lg', 'xl', '2xl'], - }; - } else { - acc[n] = { - control: 'select', - options: listResponsiveValues( - n as keyof typeof responsiveProperties.styles, - ), - }; - } - return acc; -}, {}); - -const argTypesColor = Object.keys(colorProperties.styles).reduce< - Record ->((acc, n) => { - acc[n as keyof typeof colorProperties.styles] = { + acc[n] = { control: 'select', - options: Object.keys( - colorProperties.styles[n as keyof typeof colorProperties.styles].values, - ), + options: listResponsiveValues(n as keyof typeof boxProperties.styles), }; return acc; }, {}); -const argTypes = { - ...argTypesResponsive, - ...argTypesColor, -}; - -// Sort the resulting object keys alphabetically -const sortedArgTypes = Object.keys(argTypes) - .sort() - .reduce((acc, key) => { - acc[key] = argTypes[key]; - return acc; - }, {} as Record); - -// Add 'as' and 'children' to the sortedArgTypes -sortedArgTypes['as'] = { - control: 'select', - options: ['div', 'span', 'article', 'section'], -}; - -sortedArgTypes['children'] = { - control: 'text', -}; - const meta = { title: 'Components/Box', component: Box, parameters: { layout: 'centered', }, - argTypes: sortedArgTypes, + argTypes: { + ...argTypesSpacing, + ...argTypesColor, + ...argTypesBox, + as: { + control: { type: 'select' }, + options: ['div', 'span', 'article', 'section'], + }, + children: { + control: false, + }, + }, args: { as: 'div', background: 'elevation1', diff --git a/packages/canon/src/components/Box/Box.tsx b/packages/canon/src/components/Box/Box.tsx index 960220a949..d134abff35 100644 --- a/packages/canon/src/components/Box/Box.tsx +++ b/packages/canon/src/components/Box/Box.tsx @@ -21,30 +21,19 @@ import { BoxProps } from './types'; /** @public */ export const Box = (props: BoxProps) => { - const { as = 'div', className, style, ...rest } = props; - const boxSprinklesProps: Record = {}; - const nativeProps: Record = {}; + const { as = 'div', className, style, children, ...restProps } = props; - // Split props between sprinkles and native HTML props - Object.entries(rest).forEach(([key, value]) => { - if (value === undefined) return; + // Generate the list of class names + const sprinklesClassName = boxSprinkles(restProps); - if ( - boxSprinkles.properties.has( - key as keyof Parameters[0], - ) - ) { - boxSprinklesProps[key] = value; - } else { - nativeProps[key] = value; - } - }); - - const sprinklesClassName = boxSprinkles(boxSprinklesProps); + // Combine the base class name, the sprinkles class name, and any additional class names + const classNames = [base, sprinklesClassName, className] + .filter(Boolean) + .join(' '); return createElement(as, { - className: [base, sprinklesClassName, className].filter(Boolean).join(' '), + className: classNames, style, - ...nativeProps, + children, }); }; diff --git a/packages/canon/src/components/Box/Docs.mdx b/packages/canon/src/components/Box/Docs.mdx index a3ce48b06d..dac10952cf 100644 --- a/packages/canon/src/components/Box/Docs.mdx +++ b/packages/canon/src/components/Box/Docs.mdx @@ -1,13 +1,7 @@ import { Canvas, Meta, Unstyled } from '@storybook/blocks'; import * as BoxStories from './Box.stories'; -import { Chip } from '../../../docs/components/Chip'; -import * as Table from '../../../docs/components/Table'; -import { Box } from './Box'; -import { Stack } from '../Stack'; import { Title, Text } from '../../../docs/components'; import { Padding } from './docs/padding'; -import { listResponsiveValues, listColorValues } from '../../utils/list-values'; -import { responsiveProperties } from './sprinkles.css'; import { PropsTable } from './docs/props-table'; import { SpacingTable } from './docs/spacing-table'; diff --git a/packages/canon/src/components/Box/docs/props-table.tsx b/packages/canon/src/components/Box/docs/props-table.tsx index bcdda93f2a..9979701f2e 100644 --- a/packages/canon/src/components/Box/docs/props-table.tsx +++ b/packages/canon/src/components/Box/docs/props-table.tsx @@ -17,8 +17,8 @@ import React from 'react'; import * as Table from '../../../../docs/components'; import { Chip } from '../../../../docs/components'; -import { listResponsiveValues } from '../../../utils/list-values'; -import { responsiveProperties } from '../sprinkles.css'; +import { listResponsiveValues } from '../../../../docs/utils/argTypes'; +import { boxProperties } from '../sprinkles.css'; export const PropsTable = () => { return ( @@ -30,7 +30,7 @@ export const PropsTable = () => { - {Object.keys(responsiveProperties.styles) + {Object.keys(boxProperties.styles) .filter( n => ![ @@ -57,7 +57,7 @@ export const PropsTable = () => { {listResponsiveValues( - n as keyof typeof responsiveProperties.styles, + n as keyof typeof boxProperties.styles, ).map(value => ( {value} ))} diff --git a/packages/canon/src/components/Box/docs/spacing-table.tsx b/packages/canon/src/components/Box/docs/spacing-table.tsx index 32b1eac1b9..8ce9d357b1 100644 --- a/packages/canon/src/components/Box/docs/spacing-table.tsx +++ b/packages/canon/src/components/Box/docs/spacing-table.tsx @@ -17,8 +17,8 @@ import React from 'react'; import * as Table from '../../../../docs/components'; import { Chip } from '../../../../docs/components'; -import { listResponsiveValues } from '../../../utils/list-values'; -import { responsiveProperties } from '../sprinkles.css'; +import { listResponsiveValues } from '../../../../docs/utils/argTypes'; +import { boxProperties } from '../sprinkles.css'; export const SpacingTable = () => { return ( @@ -30,7 +30,7 @@ export const SpacingTable = () => { - {Object.keys(responsiveProperties.styles) + {Object.keys(boxProperties.styles) .filter(n => [ 'padding', @@ -56,7 +56,7 @@ export const SpacingTable = () => { {listResponsiveValues( - 'paddingTop' as keyof typeof responsiveProperties.styles, + 'paddingTop' as keyof typeof boxProperties.styles, ).map(value => ( {value} ))} diff --git a/packages/canon/src/components/Box/index.tsx b/packages/canon/src/components/Box/index.tsx index f275aa352c..e4f1867edc 100644 --- a/packages/canon/src/components/Box/index.tsx +++ b/packages/canon/src/components/Box/index.tsx @@ -15,4 +15,4 @@ */ export { Box } from './Box'; export type * from './types'; -export { breakpoints, space, themes } from './properties'; +export { breakpoints, space, themes } from '../../layout/properties'; diff --git a/packages/canon/src/components/Box/sprinkles.css.ts b/packages/canon/src/components/Box/sprinkles.css.ts index 36904cb2ab..df5664029b 100644 --- a/packages/canon/src/components/Box/sprinkles.css.ts +++ b/packages/canon/src/components/Box/sprinkles.css.ts @@ -14,16 +14,11 @@ * limitations under the License. */ -import { - defineProperties, - createSprinkles, - RequiredConditionalValue, - ConditionalValue, - createMapValueFn, -} from '@vanilla-extract/sprinkles'; -import { breakpoints, space } from './properties'; +import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles'; +import { breakpoints } from '../../layout/properties'; +import { colorProperties, spacingProperties } from '../../layout/sprinkles.css'; -export const responsiveProperties = defineProperties({ +export const boxProperties = defineProperties({ conditions: breakpoints, defaultCondition: 'xs', responsiveArray: ['xs', 'sm', 'md', 'lg', 'xl', '2xl'], @@ -55,57 +50,12 @@ export const responsiveProperties = defineProperties({ 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: { - padding: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'], - paddingX: ['paddingLeft', 'paddingRight'], - paddingY: ['paddingTop', 'paddingBottom'], - margin: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'], - marginX: ['marginLeft', 'marginRight'], - marginY: ['marginTop', 'marginBottom'], - }, -}); - -export const colorProperties = defineProperties({ - conditions: { - light: { selector: '[data-theme="light"] &' }, - dark: { selector: '[data-theme="dark"] &' }, - }, - defaultCondition: ['light', 'dark'], - properties: { - 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', - }, - }, }); export const boxSprinkles = createSprinkles( - responsiveProperties, + spacingProperties, + boxProperties, colorProperties, ); - -export type OptionalResponsiveValue = - ConditionalValue; - -export type RequiredResponsiveValue = - RequiredConditionalValue; - -export const mapResponsiveValue = createMapValueFn(responsiveProperties); diff --git a/packages/canon/src/components/Box/types.ts b/packages/canon/src/components/Box/types.ts index 3f307bf2eb..2ba5929fdd 100644 --- a/packages/canon/src/components/Box/types.ts +++ b/packages/canon/src/components/Box/types.ts @@ -13,19 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { breakpoints, space, themes } from './properties'; + +import { Breakpoint, SpaceProps, ColorProps } from '../../layout/types'; /** @public */ -export type Breakpoint = keyof typeof breakpoints; - -/** @public */ -export type Space = keyof typeof space; - -/** @public */ -export type Theme = keyof typeof themes; - -/** @public */ -export type Display = +export type DisplayProps = | 'flex' | 'none' | 'inline' @@ -33,19 +25,19 @@ export type Display = | Partial>; /** @public */ -export type FlexDirection = +export type FlexDirectionProps = | 'row' | 'column' | Partial>; /** @public */ -export type FlexWrap = +export type FlexWrapProps = | 'wrap' | 'nowrap' | Partial>; /** @public */ -export type JustifyContent = +export type JustifyContentProps = | 'stretch' | 'flex-start' | 'center' @@ -65,7 +57,7 @@ export type JustifyContent = >; /** @public */ -export type AlignItems = +export type AlignItemsProps = | 'stretch' | 'flex-start' | 'center' @@ -75,7 +67,7 @@ export type AlignItems = >; /** @public */ -export type BorderRadius = +export type BorderRadiusProps = | 'none' | 'small' | 'medium' @@ -83,94 +75,15 @@ export type BorderRadius = | Partial>; /** @public */ -export type Gap = Space | Partial>; - -/** @public */ -export type PaddingLeft = Space | Partial>; - -/** @public */ -export type PaddingRight = Space | Partial>; - -/** @public */ -export type PaddingTop = Space | Partial>; - -/** @public */ -export type PaddingBottom = Space | Partial>; - -/** @public */ -export type Padding = Space | Partial>; - -/** @public */ -export type PaddingX = Space | Partial>; - -/** @public */ -export type PaddingY = Space | Partial>; - -/** @public */ -export type MarginLeft = Space | Partial>; - -/** @public */ -export type MarginRight = Space | Partial>; - -/** @public */ -export type MarginTop = Space | Partial>; - -/** @public */ -export type MarginBottom = Space | Partial>; - -/** @public */ -export type Margin = Space | Partial>; - -/** @public */ -export type MarginX = Space | Partial>; - -/** @public */ -export type MarginY = Space | Partial>; - -/** @public */ -export type Background = - | 'background' - | 'elevation1' - | 'elevation2' - | 'transparent' - | Partial< - Record - >; - -/** @public */ -export type Color = - | 'primary' - | 'secondary' - | 'error' - | Partial>; - -/** @public */ -export interface BoxProps { +export interface BoxProps extends SpaceProps, ColorProps { as?: keyof JSX.IntrinsicElements; - background?: Background; children?: React.ReactNode; - color?: Color; - display?: Display; - flexDirection?: FlexDirection; - flexWrap?: FlexWrap; - justifyContent?: JustifyContent; - alignItems?: AlignItems; - borderRadius?: BorderRadius; - gap?: Gap; - padding?: Padding; - paddingLeft?: PaddingLeft; - paddingRight?: PaddingRight; - paddingTop?: PaddingTop; - paddingBottom?: PaddingBottom; - paddingX?: PaddingX; - paddingY?: PaddingY; - margin?: Margin; - marginLeft?: MarginLeft; - marginRight?: MarginRight; - marginTop?: MarginTop; - marginBottom?: MarginBottom; - marginX?: MarginX; - marginY?: MarginY; + display?: DisplayProps; + flexDirection?: FlexDirectionProps; + flexWrap?: FlexWrapProps; + justifyContent?: JustifyContentProps; + alignItems?: AlignItemsProps; + borderRadius?: BorderRadiusProps; className?: string; style?: React.CSSProperties; } diff --git a/packages/canon/src/components/Inline/Inline.tsx b/packages/canon/src/components/Inline/Inline.tsx index db9f4bd9c3..9d4541ef12 100644 --- a/packages/canon/src/components/Inline/Inline.tsx +++ b/packages/canon/src/components/Inline/Inline.tsx @@ -16,7 +16,6 @@ import React from 'react'; import { Box } from '../Box/Box'; -import { alignToFlexAlign } from '../../utils/align'; import type { BoxProps } from '../Box/types'; export const validInlineComponents = [ @@ -47,7 +46,7 @@ export const Inline = ({ ; export default meta; @@ -50,6 +73,49 @@ export const Default: Story = { }, }; +export const AlignLeft: Story = { + args: { + ...Default.args, + align: 'left', + }, +}; + +export const AlignCenter: Story = { + args: { + ...Default.args, + align: 'center', + }, +}; + +export const AlignRight: Story = { + args: { + ...Default.args, + align: 'right', + }, +}; + +export const ResponsiveAlign: Story = { + args: { + ...Default.args, + align: { + xs: 'left', + md: 'center', + lg: 'right', + }, + }, +}; + +export const ResponsiveGap: Story = { + args: { + ...Default.args, + gap: { + xs: 'xs', + md: 'md', + lg: 'xxl', + }, + }, +}; + export const LargeGap: Story = { args: { ...Default.args, diff --git a/packages/canon/src/components/Stack/Stack.tsx b/packages/canon/src/components/Stack/Stack.tsx index 121ab1b176..f2316805b5 100644 --- a/packages/canon/src/components/Stack/Stack.tsx +++ b/packages/canon/src/components/Stack/Stack.tsx @@ -14,61 +14,44 @@ * limitations under the License. */ -import React from 'react'; -import { Box } from '../Box/Box'; -import { alignToFlexAlign } from '../../utils/align'; -import type { BoxProps } from '../Box/types'; -const validStackComponents = [ - 'div', - 'span', - 'p', - 'article', - 'section', - 'main', - 'nav', - 'aside', - 'ul', - 'ol', - 'li', - 'details', - 'summary', - 'dd', - 'dl', - 'dt', -] as const; +import { createElement } from 'react'; +import { StackProps } from './types'; +import { stackSprinkles } from './sprinkles.css'; -export interface StackProps extends Omit { - children: React.ReactNode; - as?: (typeof validStackComponents)[number]; - align?: 'left' | 'center' | 'right'; - gap?: BoxProps['gap']; -} +const alignToFlexAlign = (align: StackProps['align']) => { + if (align === 'left') return 'stretch'; + if (align === 'center') return 'center'; + if (align === 'right') return 'flex-end'; + return undefined; +}; export const Stack = ({ as = 'div', children, - align: alignProp, + align = 'left', gap = 'xs', + className, + style, ...restProps }: StackProps) => { - /** - * 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'; + // Transform the align prop + const flexAlign = alignToFlexAlign(align); - return ( - - {children} - - ); + // Generate the list of class names + const sprinklesClassName = stackSprinkles({ + ...restProps, + gap, + alignItems: flexAlign, + }); + + // Combine the base class name, the sprinkles class name, and any additional class names + const classNames = ['stack', sprinklesClassName, className] + .filter(Boolean) + .join(' '); + + return createElement(as, { + className: classNames, + style, + children, + }); }; diff --git a/packages/canon/src/components/Stack/sprinkles.css.ts b/packages/canon/src/components/Stack/sprinkles.css.ts index 461f38fcde..85c8f83a71 100644 --- a/packages/canon/src/components/Stack/sprinkles.css.ts +++ b/packages/canon/src/components/Stack/sprinkles.css.ts @@ -13,74 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles'; -import { breakpoints, space } from '../Box/properties'; -import { colorProperties } from '../Box/sprinkles.css'; -const responsiveProperties = defineProperties({ +import { defineProperties, createSprinkles } from '@vanilla-extract/sprinkles'; +import { breakpoints } from '../../layout/properties'; +import { colorProperties, spacingProperties } from '../../layout/sprinkles.css'; + +const stackProperties = defineProperties({ conditions: breakpoints, defaultCondition: 'xs', properties: { - 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: { - p: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'], - pt: ['paddingTop'], - pr: ['paddingRight'], - pb: ['paddingBottom'], - pl: ['paddingLeft'], - px: ['paddingLeft', 'paddingRight'], - py: ['paddingTop', 'paddingBottom'], - m: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'], - mt: ['marginTop'], - mr: ['marginRight'], - mb: ['marginBottom'], - ml: ['marginLeft'], - mx: ['marginLeft', 'marginRight'], - my: ['marginTop', 'marginBottom'], - direction: ['flexDirection'], - items: ['alignItems'], - justify: ['justifyContent'], }, }); -export const sprinkles = createSprinkles(responsiveProperties, colorProperties); - -// It's a good idea to export the Sprinkles type too -export type Sprinkles = Parameters[0]; +export const stackSprinkles = createSprinkles( + spacingProperties, + colorProperties, + stackProperties, +); diff --git a/packages/canon/src/components/Stack/styles.css b/packages/canon/src/components/Stack/styles.css new file mode 100644 index 0000000000..784fd3b1ab --- /dev/null +++ b/packages/canon/src/components/Stack/styles.css @@ -0,0 +1,4 @@ +.stack { + display: flex; + flex-direction: column; +} diff --git a/packages/canon/src/components/Stack/types.ts b/packages/canon/src/components/Stack/types.ts new file mode 100644 index 0000000000..7390e13666 --- /dev/null +++ b/packages/canon/src/components/Stack/types.ts @@ -0,0 +1,32 @@ +/* + * 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 { AsProps, Breakpoint, ColorProps } from '../../layout/types'; +import { SpaceProps } from '../../layout/types'; + +/** @public */ +export type AlignProps = + | 'left' + | 'center' + | 'right' + | Partial>; + +export interface StackProps extends SpaceProps, ColorProps { + children: React.ReactNode; + as?: AsProps; + align?: AlignProps; + className?: string; + style?: React.CSSProperties; +} diff --git a/packages/canon/src/components/Box/properties.ts b/packages/canon/src/layout/properties.ts similarity index 100% rename from packages/canon/src/components/Box/properties.ts rename to packages/canon/src/layout/properties.ts diff --git a/packages/canon/src/layout/sprinkles.css.ts b/packages/canon/src/layout/sprinkles.css.ts new file mode 100644 index 0000000000..7e85487fe3 --- /dev/null +++ b/packages/canon/src/layout/sprinkles.css.ts @@ -0,0 +1,63 @@ +/* + * 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 } from '@vanilla-extract/sprinkles'; +import { breakpoints, space } from './properties'; + +export const spacingProperties = defineProperties({ + conditions: breakpoints, + defaultCondition: 'xs', + responsiveArray: ['xs', 'sm', 'md', 'lg', 'xl', '2xl'], + properties: { + paddingTop: space, + paddingBottom: space, + paddingLeft: space, + paddingRight: space, + marginTop: space, + marginBottom: space, + marginLeft: space, + marginRight: space, + gap: space, + }, + shorthands: { + padding: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'], + paddingX: ['paddingLeft', 'paddingRight'], + paddingY: ['paddingTop', 'paddingBottom'], + margin: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'], + marginX: ['marginLeft', 'marginRight'], + marginY: ['marginTop', 'marginBottom'], + }, +}); + +export const colorProperties = defineProperties({ + conditions: { + light: { selector: '[data-theme="light"] &' }, + dark: { selector: '[data-theme="dark"] &' }, + }, + defaultCondition: ['light', 'dark'], + properties: { + 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', + }, + }, +}); diff --git a/packages/canon/src/layout/types.ts b/packages/canon/src/layout/types.ts new file mode 100644 index 0000000000..c051dacb44 --- /dev/null +++ b/packages/canon/src/layout/types.ts @@ -0,0 +1,131 @@ +/* + * 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 { breakpoints, space, themes } from './properties'; + +/** @public */ +export type Breakpoint = keyof typeof breakpoints; + +/** @public */ +export type Space = keyof typeof space; + +/** @public */ +export type Theme = keyof typeof themes; + +/** @public */ +export type Gap = Space | Partial>; + +/** @public */ +export type PaddingLeft = Space | Partial>; + +/** @public */ +export type PaddingRight = Space | Partial>; + +/** @public */ +export type PaddingTop = Space | Partial>; + +/** @public */ +export type PaddingBottom = Space | Partial>; + +/** @public */ +export type Padding = Space | Partial>; + +/** @public */ +export type PaddingX = Space | Partial>; + +/** @public */ +export type PaddingY = Space | Partial>; + +/** @public */ +export type MarginLeft = Space | Partial>; + +/** @public */ +export type MarginRight = Space | Partial>; + +/** @public */ +export type MarginTop = Space | Partial>; + +/** @public */ +export type MarginBottom = Space | Partial>; + +/** @public */ +export type Margin = Space | Partial>; + +/** @public */ +export type MarginX = Space | Partial>; + +/** @public */ +export type MarginY = Space | Partial>; + +/** @public */ +export interface SpaceProps { + gap?: Gap; + padding?: Padding; + paddingLeft?: PaddingLeft; + paddingRight?: PaddingRight; + paddingTop?: PaddingTop; + paddingBottom?: PaddingBottom; + paddingX?: PaddingX; + paddingY?: PaddingY; + margin?: Margin; + marginLeft?: MarginLeft; + marginRight?: MarginRight; + marginTop?: MarginTop; + marginBottom?: MarginBottom; + marginX?: MarginX; + marginY?: MarginY; +} + +/** @public */ +export type Background = + | 'background' + | 'elevation1' + | 'elevation2' + | 'transparent' + | Partial< + Record + >; + +/** @public */ +export type Color = + | 'primary' + | 'secondary' + | 'error' + | Partial>; + +/** @public */ +export type AsProps = + | 'div' + | 'span' + | 'p' + | 'article' + | 'section' + | 'main' + | 'nav' + | 'aside' + | 'ul' + | 'ol' + | 'li' + | 'details' + | 'summary' + | 'dd' + | 'dl' + | 'dt'; + +/** @public */ +export interface ColorProps { + color?: Color; + background?: Background; +} diff --git a/packages/canon/src/theme/styles.css b/packages/canon/src/theme/styles.css index fa5ed2ba4d..001b8abf10 100644 --- a/packages/canon/src/theme/styles.css +++ b/packages/canon/src/theme/styles.css @@ -23,3 +23,4 @@ /* Components */ @import '../components/Button/styles.css'; +@import '../components/Stack/styles.css'; diff --git a/packages/canon/src/utils/align.ts b/packages/canon/src/utils/align.ts deleted file mode 100644 index f4d7b5932e..0000000000 --- a/packages/canon/src/utils/align.ts +++ /dev/null @@ -1,49 +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 { - 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 | 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 | undefined, -) => - alignY - ? mapResponsiveValue(alignY, value => alignYToFlexAlignLookup[value]) - : undefined;