diff --git a/packages/canon/src/components/Box/Box.props.ts b/packages/canon/src/components/Box/Box.props.ts new file mode 100644 index 0000000000..af444adbf8 --- /dev/null +++ b/packages/canon/src/components/Box/Box.props.ts @@ -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; + +export { boxPropDefs }; +export type { BoxOwnProps }; diff --git a/packages/canon/src/components/Box/Box.stories.tsx b/packages/canon/src/components/Box/Box.stories.tsx index 5958e2a739..aaa19105cc 100644 --- a/packages/canon/src/components/Box/Box.stories.tsx +++ b/packages/canon/src/components/Box/Box.stories.tsx @@ -67,9 +67,6 @@ export const Display: Story = { Block - - Flex - Inline @@ -77,124 +74,13 @@ export const Display: Story = { None - + Responsive ), }; -export const FlexDirection: Story = { - args: { - style: { - ...Default.args?.style, - width: 'auto', - height: 'auto', - padding: '8px', - }, - display: 'flex', - gap: 'xs', - }, - render: args => ( - - - Row - Row - - - Column - Column - - - Responsive - Flex Direction - - - ), -}; - -export const JustifyContent: Story = { - args: { - style: { - ...Default.args?.style, - width: '200px', - height: 'auto', - padding: '8px', - }, - display: 'flex', - gap: 'xs', - }, - render: args => ( - - - Flex Start - - - Center - - - Flex End - - - Space - Around - - - Space - Between - - - Responsive - Spacing - - - ), -}; - -export const AlignItems: Story = { - args: { - style: { - ...Default.args?.style, - width: '200px', - height: '100px', - padding: '8px', - }, - display: 'flex', - gap: 'xs', - }, - render: args => ( - - - Flex Start - - - Center - - - Flex End - - - Responsive - Spacing - - - ), -}; - const styleInsideBox = { background: 'rgb(196, 202, 251)', color: 'white', @@ -294,119 +180,3 @@ export const Margin: Story = { ), }; - -export const FlexWrap: Story = { - args: { - style: { - ...Default.args?.style, - width: '200px', - height: 'auto', - padding: '8px', - }, - display: 'flex', - gap: 'xs', - }, - render: args => ( - - - One - Two - Three - Four - Five - Six - - - One - Two - Three - Four - Five - Six - - - One - Two - Three - Four - Five - Six - - - ), -}; - -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 => ( - - - 2xs - - - xs - - - sm - - - md - - - xl - - - 2xl - - - ), -}; - -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 => ( - - - Base - - - Error - - - Warning - - - Selected - - - None - - - ), -}; diff --git a/packages/canon/src/components/Box/Box.tsx b/packages/canon/src/components/Box/Box.tsx index 04dc4648ce..9a92d26de1 100644 --- a/packages/canon/src/components/Box/Box.tsx +++ b/packages/canon/src/components/Box/Box.tsx @@ -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((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, { diff --git a/packages/canon/src/components/Box/types.ts b/packages/canon/src/components/Box/types.ts index a55c856caf..dcab30cd22 100644 --- a/packages/canon/src/components/Box/types.ts +++ b/packages/canon/src/components/Box/types.ts @@ -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; diff --git a/packages/canon/src/props/height.props.ts b/packages/canon/src/props/height.props.ts new file mode 100644 index 0000000000..337a91001e --- /dev/null +++ b/packages/canon/src/props/height.props.ts @@ -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; + minHeight: PropDef; + maxHeight: PropDef; +}; + +type HeightProps = GetPropDefTypes; + +export { heightPropDefs }; +export type { HeightProps }; diff --git a/packages/canon/src/props/margin.ts b/packages/canon/src/props/margin.props.ts similarity index 100% rename from packages/canon/src/props/margin.ts rename to packages/canon/src/props/margin.props.ts diff --git a/packages/canon/src/props/padding.ts b/packages/canon/src/props/padding.props.ts similarity index 100% rename from packages/canon/src/props/padding.ts rename to packages/canon/src/props/padding.props.ts diff --git a/packages/canon/src/props/position.props.ts b/packages/canon/src/props/position.props.ts new file mode 100644 index 0000000000..248141221e --- /dev/null +++ b/packages/canon/src/props/position.props.ts @@ -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; + +export { positionPropDefs }; +export type { PositionProps }; diff --git a/packages/canon/src/props/spacing.ts b/packages/canon/src/props/spacing.props.ts similarity index 89% rename from packages/canon/src/props/spacing.ts rename to packages/canon/src/props/spacing.props.ts index 58b3a36eff..d33bab2aa5 100644 --- a/packages/canon/src/props/spacing.ts +++ b/packages/canon/src/props/spacing.props.ts @@ -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', diff --git a/packages/canon/src/props/width.props.ts b/packages/canon/src/props/width.props.ts new file mode 100644 index 0000000000..bb1d92249b --- /dev/null +++ b/packages/canon/src/props/width.props.ts @@ -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; + minWidth: PropDef; + maxWidth: PropDef; +}; + +type WidthProps = GetPropDefTypes; + +export { widthPropDefs }; +export type { WidthProps }; diff --git a/packages/canon/src/types.ts b/packages/canon/src/types.ts index 3a6b55f13b..ee5d954576 100644 --- a/packages/canon/src/types.ts +++ b/packages/canon/src/types.ts @@ -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 | Partial>; + +/** @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>; - marginBottom?: Space | Partial>; - marginLeft?: Space | Partial>; - marginRight?: Space | Partial>; - marginTop?: Space | Partial>; - marginX?: Space | Partial>; - marginY?: Space | Partial>; - padding?: Space | Partial>; - paddingBottom?: Space | Partial>; - paddingLeft?: Space | Partial>; - paddingRight?: Space | Partial>; - paddingTop?: Space | Partial>; - paddingX?: Space | Partial>; - paddingY?: Space | Partial>; + m?: Responsive; + mb?: Responsive; + ml?: Responsive; + mr?: Responsive; + mt?: Responsive; + mx?: Responsive; + my?: Responsive; + p?: Responsive; + pb?: Responsive; + pl?: Responsive; + pr?: Responsive; + pt?: Responsive; + px?: Responsive; + py?: Responsive; } /** @public */ export interface UtilityProps extends SpaceProps { - alignItems?: AlignItems | Partial>; - border?: Border | Partial>; - borderRadius?: BorderRadius | Partial>; - colEnd?: Columns | 'auto' | Partial>; - colSpan?: Columns | 'full' | Partial>; - colStart?: Columns | 'auto' | Partial>; - columns?: Columns | Partial>; - display?: Display | Partial>; - flexDirection?: FlexDirection | Partial>; - flexWrap?: FlexWrap | Partial>; - gap?: Space | Partial>; - justifyContent?: JustifyContent | Partial>; - rowSpan?: Columns | 'full' | Partial>; + alignItems?: Responsive; + border?: Responsive; + borderRadius?: Responsive; + colEnd?: Responsive; + colSpan?: Responsive; + colStart?: Responsive; + columns?: Responsive; + display?: Responsive; + flexDirection?: Responsive; + flexWrap?: Responsive; + gap?: Responsive; + justifyContent?: Responsive; + rowSpan?: Responsive; }