Cleanup Box props

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-01-27 16:31:06 +00:00
parent ca343e2e17
commit 722a1a953c
11 changed files with 451 additions and 266 deletions
@@ -0,0 +1,57 @@
/*
* Copyright 2025 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 { PropDef, GetPropDefTypes } from '../../props/prop-def';
const as = ['div', 'span'] as const;
const displayValues = ['none', 'inline', 'inline-block', 'block'] as const;
const boxPropDefs = {
/**
* Controls whether to render **div** or **span**
*
* @example
* as="div"
* as="span"
*/
as: { type: 'enum', values: as, default: 'div' },
/**
* Sets the CSS **display** property.
* Supports a subset of the corresponding CSS values and responsive objects.
*
* @example
* display="inline-block"
* display={{ sm: 'none', lg: 'block' }}
*
* @link
* https://developer.mozilla.org/en-US/docs/Web/CSS/display
*/
display: {
type: 'enum',
className: 'rt-r-display',
values: displayValues,
responsive: true,
},
} satisfies {
as: PropDef<(typeof as)[number]>;
display: PropDef<(typeof displayValues)[number]>;
};
// Use all of the imported prop defs to ensure that JSDoc works
type BoxOwnProps = GetPropDefTypes<typeof boxPropDefs>;
export { boxPropDefs };
export type { BoxOwnProps };
@@ -67,9 +67,6 @@ export const Display: Story = {
<Box display="block" {...args}>
Block
</Box>
<Box display="flex" {...args}>
Flex
</Box>
<Box display="inline" {...args}>
Inline
</Box>
@@ -77,124 +74,13 @@ export const Display: Story = {
None
</Box>
</Inline>
<Box display={{ xs: 'block', sm: 'flex', md: 'inline' }} {...args}>
<Box display={{ initial: 'block', md: 'inline' }} {...args}>
Responsive
</Box>
</Stack>
),
};
export const FlexDirection: Story = {
args: {
style: {
...Default.args?.style,
width: 'auto',
height: 'auto',
padding: '8px',
},
display: 'flex',
gap: 'xs',
},
render: args => (
<Stack align="center">
<Box flexDirection="row" {...args}>
<span>Row</span>
<span>Row</span>
</Box>
<Box flexDirection="column" {...args}>
<span>Column</span>
<span>Column</span>
</Box>
<Box flexDirection={{ xs: 'column', sm: 'row' }} {...args}>
<span>Responsive</span>
<span>Flex Direction</span>
</Box>
</Stack>
),
};
export const JustifyContent: Story = {
args: {
style: {
...Default.args?.style,
width: '200px',
height: 'auto',
padding: '8px',
},
display: 'flex',
gap: 'xs',
},
render: args => (
<Stack>
<Box justifyContent="start" {...args}>
<span>Flex Start</span>
</Box>
<Box justifyContent="center" {...args}>
<span>Center</span>
</Box>
<Box justifyContent="end" {...args}>
<span>Flex End</span>
</Box>
<Box justifyContent="around" {...args}>
<span>Space</span>
<span>Around</span>
</Box>
<Box justifyContent="between" {...args}>
<span>Space</span>
<span>Between</span>
</Box>
<Box
justifyContent={{
xs: 'between',
sm: 'around',
md: 'start',
}}
{...args}
>
<span>Responsive</span>
<span>Spacing</span>
</Box>
</Stack>
),
};
export const AlignItems: Story = {
args: {
style: {
...Default.args?.style,
width: '200px',
height: '100px',
padding: '8px',
},
display: 'flex',
gap: 'xs',
},
render: args => (
<Stack>
<Box alignItems="start" {...args}>
<span>Flex Start</span>
</Box>
<Box alignItems="center" {...args}>
<span>Center</span>
</Box>
<Box alignItems="end" {...args}>
<span>Flex End</span>
</Box>
<Box
alignItems={{
xs: 'start',
sm: 'center',
md: 'end',
}}
{...args}
>
<span>Responsive</span>
<span>Spacing</span>
</Box>
</Stack>
),
};
const styleInsideBox = {
background: 'rgb(196, 202, 251)',
color: 'white',
@@ -294,119 +180,3 @@ export const Margin: Story = {
</Stack>
),
};
export const FlexWrap: Story = {
args: {
style: {
...Default.args?.style,
width: '200px',
height: 'auto',
padding: '8px',
},
display: 'flex',
gap: 'xs',
},
render: args => (
<Stack align="center">
<Box flexWrap="wrap" {...args}>
<span>One</span>
<span>Two</span>
<span>Three</span>
<span>Four</span>
<span>Five</span>
<span>Six</span>
</Box>
<Box flexWrap="nowrap" {...args}>
<span>One</span>
<span>Two</span>
<span>Three</span>
<span>Four</span>
<span>Five</span>
<span>Six</span>
</Box>
<Box flexWrap="wrap-reverse" {...args}>
<span>One</span>
<span>Two</span>
<span>Three</span>
<span>Four</span>
<span>Five</span>
<span>Six</span>
</Box>
</Stack>
),
};
export const BorderRadius: Story = {
args: {
style: {
width: '64px',
height: '64px',
background: '#eaf2fd',
border: '1px solid #2563eb',
color: '#2563eb',
backgroundImage:
'url("data:image/svg+xml,%3Csvg%20width%3D%226%22%20height%3D%226%22%20viewBox%3D%220%200%206%206%22%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%3E%3Cg%20fill%3D%22%232563eb%22%20fill-opacity%3D%220.3%22%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M5%200h1L0%206V5zM6%205v1H5z%22/%3E%3C/g%3E%3C/svg%3E")',
padding: '4px 8px',
},
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
},
render: args => (
<Inline align="center">
<Box borderRadius="2xs" {...args}>
<span>2xs</span>
</Box>
<Box borderRadius="xs" {...args}>
<span>xs</span>
</Box>
<Box borderRadius="sm" {...args}>
<span>sm</span>
</Box>
<Box borderRadius="md" {...args}>
<span>md</span>
</Box>
<Box borderRadius="xl" {...args}>
<span>xl</span>
</Box>
<Box borderRadius="2xl" {...args}>
<span>2xl</span>
</Box>
</Inline>
),
};
export const Border: Story = {
args: {
style: {
background: 'var(--canon-bg-elevated)',
color: 'var(--canon-fg-text-primary)',
padding: '4px 8px',
width: '80px',
height: '32px',
},
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 'xs',
},
render: args => (
<Inline align="center">
<Box border="base" {...args}>
Base
</Box>
<Box border="error" {...args}>
Error
</Box>
<Box border="warning" {...args}>
Warning
</Box>
<Box border="selected" {...args}>
Selected
</Box>
<Box border="none" {...args}>
None
</Box>
</Inline>
),
};
+12 -2
View File
@@ -18,14 +18,24 @@ import { createElement, forwardRef } from 'react';
import { BoxProps } from './types';
import clsx from 'clsx';
import { extractProps } from '../../utils/extractProps';
import { spacingPropDefs } from '../../props/spacing';
import { spacingPropDefs } from '../../props/spacing.props';
import { boxPropDefs } from './Box.props';
import { widthPropDefs } from '../../props/width.props';
import { heightPropDefs } from '../../props/height.props';
import { positionPropDefs } from '../../props/position.props';
/** @public */
export const Box = forwardRef<HTMLDivElement, BoxProps>((props, ref) => {
const { as = 'div', children } = props;
// Extract utility class names and styles
const propDefs = { ...spacingPropDefs };
const propDefs = {
...spacingPropDefs,
...widthPropDefs,
...heightPropDefs,
...positionPropDefs,
...boxPropDefs,
};
const { className, style } = extractProps(props, propDefs);
return createElement(as, {
+19 -3
View File
@@ -14,11 +14,27 @@
* limitations under the License.
*/
import type { UtilityProps } from '../../types';
import type { HeightProps } from '../../props/height.props';
import type { WidthProps } from '../../props/width.props';
import type { PositionProps } from '../../props/position.props';
import type { SpaceProps } from '../../types';
import type { BoxOwnProps } from './Box.props';
/** @public */
export interface BoxProps extends UtilityProps {
as?: keyof JSX.IntrinsicElements;
export interface BoxProps extends SpaceProps {
as?: BoxOwnProps['as'];
display?: BoxOwnProps['display'];
width?: WidthProps['width'];
minWidth?: WidthProps['minWidth'];
maxWidth?: WidthProps['maxWidth'];
height?: HeightProps['height'];
minHeight?: HeightProps['minHeight'];
maxHeight?: HeightProps['maxHeight'];
position?: PositionProps['position'];
top?: PositionProps['top'];
left?: PositionProps['left'];
right?: PositionProps['right'];
bottom?: PositionProps['bottom'];
children?: React.ReactNode;
className?: string;
style?: React.CSSProperties;
+79
View File
@@ -0,0 +1,79 @@
/*
* Copyright 2025 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 { PropDef, GetPropDefTypes } from './prop-def.js';
const heightPropDefs = {
/**
* Sets the CSS **height** property.
* Supports CSS strings and responsive objects.
*
* @example
* height="100px"
* height={{ md: '100vh', xl: '600px' }}
*
* @link
* https://developer.mozilla.org/en-US/docs/Web/CSS/height
*/
height: {
type: 'string',
className: 'cu-h',
customProperties: ['--height'],
responsive: true,
},
/**
* Sets the CSS **min-height** property.
* Supports CSS strings and responsive objects.
*
* @example
* minHeight="100px"
* minHeight={{ md: '100vh', xl: '600px' }}
*
* @link
* https://developer.mozilla.org/en-US/docs/Web/CSS/min-height
*/
minHeight: {
type: 'string',
className: 'cu-min-h',
customProperties: ['--min-height'],
responsive: true,
},
/**
* Sets the CSS **max-height** property.
* Supports CSS strings and responsive objects.
*
* @example
* maxHeight="100px"
* maxHeight={{ md: '100vh', xl: '600px' }}
*
* @link
* https://developer.mozilla.org/en-US/docs/Web/CSS/max-height
*/
maxHeight: {
type: 'string',
className: 'cu-max-h',
customProperties: ['--max-height'],
responsive: true,
},
} satisfies {
height: PropDef<string>;
minHeight: PropDef<string>;
maxHeight: PropDef<string>;
};
type HeightProps = GetPropDefTypes<typeof heightPropDefs>;
export { heightPropDefs };
export type { HeightProps };
+154
View File
@@ -0,0 +1,154 @@
/*
* Copyright 2025 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 { PropDef, GetPropDefTypes } from './prop-def';
const positionValues = [
'static',
'relative',
'absolute',
'fixed',
'sticky',
] as const;
const positionEdgeValues = [
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'-1',
'-2',
'-3',
'-4',
'-5',
'-6',
'-7',
'-8',
'-9',
] as const;
const positionPropDefs = {
/**
* Sets the CSS **position** property.
* Supports the corresponding CSS values and responsive objects.
*
* @example
* position="absolute"
* position={{ sm: 'absolute', lg: 'sticky' }}
*
* @link
* https://developer.mozilla.org/en-US/docs/Web/CSS/position
*/
position: {
type: 'enum',
className: 'cu-position',
values: positionValues,
responsive: true,
},
/**
* Sets the CSS **top** property.
* Supports space scale values, CSS strings, and responsive objects.
*
* @example
* top="4"
* top="100px"
* top={{ sm: '0', lg: '50%' }}
*
* @link
* https://developer.mozilla.org/en-US/docs/Web/CSS/top
*/
top: {
type: 'enum | string',
className: 'cu-top',
customProperties: ['--top'],
values: positionEdgeValues,
responsive: true,
},
/**
* Sets the CSS **right** property.
* Supports space scale values, CSS strings, and responsive objects.
*
* @example
* right="4"
* right="100px"
* right={{ sm: '0', lg: '50%' }}
*
* @link
* https://developer.mozilla.org/en-US/docs/Web/CSS/right
*/
right: {
type: 'enum | string',
className: 'cu-right',
customProperties: ['--right'],
values: positionEdgeValues,
responsive: true,
},
/**
* Sets the CSS **bottom** property.
* Supports space scale values, CSS strings, and responsive objects.
*
* @example
* bottom="4"
* bottom="100px"
* bottom={{ sm: '0', lg: '50%' }}
*
* @link
* https://developer.mozilla.org/en-US/docs/Web/CSS/bottom
*/
bottom: {
type: 'enum | string',
className: 'cu-bottom',
customProperties: ['--bottom'],
values: positionEdgeValues,
responsive: true,
},
/**
* Sets the CSS **left** property.
* Supports space scale values, CSS strings, and responsive objects.
*
* @example
* left="4"
* left="100px"
* left={{ sm: '0', lg: '50%' }}
*
* @link
* https://developer.mozilla.org/en-US/docs/Web/CSS/left
*/
left: {
type: 'enum | string',
className: 'cu-left',
customProperties: ['--left'],
values: positionEdgeValues,
responsive: true,
},
} satisfies {
position: PropDef<(typeof positionValues)[number]>;
top: PropDef<(typeof positionEdgeValues)[number]>;
right: PropDef<(typeof positionEdgeValues)[number]>;
bottom: PropDef<(typeof positionEdgeValues)[number]>;
left: PropDef<(typeof positionEdgeValues)[number]>;
};
// Use all of the imported prop defs to ensure that JSDoc works
type PositionProps = GetPropDefTypes<typeof positionPropDefs>;
export { positionPropDefs };
export type { PositionProps };
@@ -13,8 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { paddingPropDefs } from './padding';
import { marginPropDefs } from './margin';
import { paddingPropDefs } from './padding.props';
import { marginPropDefs } from './margin.props';
export const spacingValues = [
'0.5',
+79
View File
@@ -0,0 +1,79 @@
/*
* Copyright 2025 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 { GetPropDefTypes, PropDef } from './prop-def.js';
const widthPropDefs = {
/**
* Sets the CSS **width** property.
* Supports CSS strings and responsive objects.
*
* @example
* width="100px"
* width={{ md: '100vw', xl: '1400px' }}
*
* @link
* https://developer.mozilla.org/en-US/docs/Web/CSS/width
*/
width: {
type: 'string',
className: 'cu-w',
customProperties: ['--width'],
responsive: true,
},
/**
* Sets the CSS **min-width** property.
* Supports CSS strings and responsive objects.
*
* @example
* minWidth="100px"
* minWidth={{ md: '100vw', xl: '1400px' }}
*
* @link
* https://developer.mozilla.org/en-US/docs/Web/CSS/min-width
*/
minWidth: {
type: 'string',
className: 'cu-min-w',
customProperties: ['--min-width'],
responsive: true,
},
/**
* Sets the CSS **max-width** property.
* Supports CSS strings and responsive objects.
*
* @example
* maxWidth="100px"
* maxWidth={{ md: '100vw', xl: '1400px' }}
*
* @link
* https://developer.mozilla.org/en-US/docs/Web/CSS/max-width
*/
maxWidth: {
type: 'string',
className: 'cu-max-w',
customProperties: ['--max-width'],
responsive: true,
},
} satisfies {
width: PropDef<string>;
minWidth: PropDef<string>;
maxWidth: PropDef<string>;
};
type WidthProps = GetPropDefTypes<typeof widthPropDefs>;
export { widthPropDefs };
export type { WidthProps };
+48 -28
View File
@@ -37,7 +37,27 @@ export type AsProps =
export type Breakpoint = 'initial' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
/** @public */
export type Space = 'none' | '2xs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
export type Responsive<T> = T | Partial<Record<Breakpoint, T>>;
/** @public */
export type Space =
| '0.5'
| '1'
| '1.5'
| '2'
| '3'
| '4'
| '5'
| '6'
| '7'
| '8'
| '9'
| '10'
| '11'
| '12'
| '13'
| '14'
| string;
/** @public */
export type Display = 'none' | 'flex' | 'block' | 'inline';
@@ -79,35 +99,35 @@ export type Columns = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 'auto';
/** @public */
export interface SpaceProps {
margin?: Space | Partial<Record<Breakpoint, Space>>;
marginBottom?: Space | Partial<Record<Breakpoint, Space>>;
marginLeft?: Space | Partial<Record<Breakpoint, Space>>;
marginRight?: Space | Partial<Record<Breakpoint, Space>>;
marginTop?: Space | Partial<Record<Breakpoint, Space>>;
marginX?: Space | Partial<Record<Breakpoint, Space>>;
marginY?: Space | Partial<Record<Breakpoint, Space>>;
padding?: Space | Partial<Record<Breakpoint, Space>>;
paddingBottom?: Space | Partial<Record<Breakpoint, Space>>;
paddingLeft?: Space | Partial<Record<Breakpoint, Space>>;
paddingRight?: Space | Partial<Record<Breakpoint, Space>>;
paddingTop?: Space | Partial<Record<Breakpoint, Space>>;
paddingX?: Space | Partial<Record<Breakpoint, Space>>;
paddingY?: Space | Partial<Record<Breakpoint, Space>>;
m?: Responsive<Space>;
mb?: Responsive<Space>;
ml?: Responsive<Space>;
mr?: Responsive<Space>;
mt?: Responsive<Space>;
mx?: Responsive<Space>;
my?: Responsive<Space>;
p?: Responsive<Space>;
pb?: Responsive<Space>;
pl?: Responsive<Space>;
pr?: Responsive<Space>;
pt?: Responsive<Space>;
px?: Responsive<Space>;
py?: Responsive<Space>;
}
/** @public */
export interface UtilityProps extends SpaceProps {
alignItems?: AlignItems | Partial<Record<Breakpoint, AlignItems>>;
border?: Border | Partial<Record<Breakpoint, Border>>;
borderRadius?: BorderRadius | Partial<Record<Breakpoint, BorderRadius>>;
colEnd?: Columns | 'auto' | Partial<Record<Breakpoint, Columns | 'auto'>>;
colSpan?: Columns | 'full' | Partial<Record<Breakpoint, Columns | 'full'>>;
colStart?: Columns | 'auto' | Partial<Record<Breakpoint, Columns | 'auto'>>;
columns?: Columns | Partial<Record<Breakpoint, Columns>>;
display?: Display | Partial<Record<Breakpoint, Display>>;
flexDirection?: FlexDirection | Partial<Record<Breakpoint, FlexDirection>>;
flexWrap?: FlexWrap | Partial<Record<Breakpoint, FlexWrap>>;
gap?: Space | Partial<Record<Breakpoint, Space>>;
justifyContent?: JustifyContent | Partial<Record<Breakpoint, JustifyContent>>;
rowSpan?: Columns | 'full' | Partial<Record<Breakpoint, Columns | 'full'>>;
alignItems?: Responsive<AlignItems>;
border?: Responsive<Border>;
borderRadius?: Responsive<BorderRadius>;
colEnd?: Responsive<Columns | 'auto'>;
colSpan?: Responsive<Columns | 'full'>;
colStart?: Responsive<Columns | 'auto'>;
columns?: Responsive<Columns>;
display?: Responsive<Display>;
flexDirection?: Responsive<FlexDirection>;
flexWrap?: Responsive<FlexWrap>;
gap?: Responsive<Space>;
justifyContent?: Responsive<JustifyContent>;
rowSpan?: Responsive<Columns | 'full'>;
}