diff --git a/packages/canon/src/components/Box/Box.tsx b/packages/canon/src/components/Box/Box.tsx index 27f8a3046f..00e15a2b72 100644 --- a/packages/canon/src/components/Box/Box.tsx +++ b/packages/canon/src/components/Box/Box.tsx @@ -29,7 +29,6 @@ import { displayPropDefs } from '../../props/display.props'; export const Box = forwardRef((props, ref) => { const { as = 'div', children } = props; - // Extract utility class names and styles const propDefs = { ...spacingPropDefs, ...widthPropDefs, diff --git a/packages/canon/src/components/Container/Container.tsx b/packages/canon/src/components/Container/Container.tsx index a925a5c38e..46a5356d56 100644 --- a/packages/canon/src/components/Container/Container.tsx +++ b/packages/canon/src/components/Container/Container.tsx @@ -25,7 +25,6 @@ export const Container = forwardRef( (props, ref) => { const { children } = props; - // Extract utility class names and styles const propDefs = { ...displayPropDefs, }; diff --git a/packages/canon/src/components/Stack/Stack.props.ts b/packages/canon/src/components/Stack/Stack.props.ts new file mode 100644 index 0000000000..5cc7300c5d --- /dev/null +++ b/packages/canon/src/components/Stack/Stack.props.ts @@ -0,0 +1,38 @@ +/* + * 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 alignValues = ['start', 'center', 'end', 'baseline', 'stretch'] as const; + +/** @public */ +const stackPropDefs = { + align: { + type: 'enum', + className: 'cu-align', + values: alignValues, + responsive: true, + default: 'stretch', + }, +} satisfies { + align: PropDef<(typeof alignValues)[number]>; +}; + +/** @public */ +type StackOwnProps = GetPropDefTypes; + +export { stackPropDefs }; +export type { StackOwnProps }; diff --git a/packages/canon/src/components/Stack/Stack.stories.tsx b/packages/canon/src/components/Stack/Stack.stories.tsx index 4c48cf80cc..0b02849a91 100644 --- a/packages/canon/src/components/Stack/Stack.stories.tsx +++ b/packages/canon/src/components/Stack/Stack.stories.tsx @@ -29,16 +29,13 @@ const meta = { children: { control: false, }, - as: { - control: false, - }, className: { control: 'text', }, }, args: { - align: 'left', - gap: 'xs', + align: 'stretch', + gap: '4', children: 'hello world', }, } satisfies Meta; @@ -63,20 +60,14 @@ const DecorativeBox = () => { }; export const Default: Story = { - render: () => ( -
- - - - - -
- ), + args: { + children: [, , ], + }, }; export const AlignLeft: Story = { render: () => ( - + @@ -96,7 +87,7 @@ export const AlignCenter: Story = { export const AlignRight: Story = { render: () => ( - + @@ -106,7 +97,7 @@ export const AlignRight: Story = { export const ResponsiveAlign: Story = { render: () => ( - + @@ -116,7 +107,7 @@ export const ResponsiveAlign: Story = { export const ResponsiveGap: Story = { render: () => ( - + @@ -126,7 +117,7 @@ export const ResponsiveGap: Story = { export const LargeGap: Story = { render: () => ( - + diff --git a/packages/canon/src/components/Stack/Stack.tsx b/packages/canon/src/components/Stack/Stack.tsx index 4b6a4596bb..9080a28e11 100644 --- a/packages/canon/src/components/Stack/Stack.tsx +++ b/packages/canon/src/components/Stack/Stack.tsx @@ -16,51 +16,24 @@ import { createElement, forwardRef } from 'react'; import { StackProps } from './types'; -import { getClassNames } from '../../utils/getClassNames'; -import type { AlignItems, Breakpoint } from '../../types'; import clsx from 'clsx'; - -// Function to map align values -const mapAlignValue = (value?: StackProps['align']) => { - if (typeof value === 'string') { - let returnedValue: AlignItems = 'stretch'; - if (value === 'left') returnedValue = 'stretch'; - if (value === 'center') returnedValue = 'center'; - if (value === 'right') returnedValue = 'end'; - return returnedValue; - } else if (typeof value === 'object') { - const returnedValue: Partial> = {}; - for (const [key, val] of Object.entries(value)) { - returnedValue[key as Breakpoint] = mapAlignValue(val) as AlignItems; - } - return returnedValue; - } - return 'stretch'; -}; +import { stackPropDefs } from './Stack.props'; +import { extractProps } from '../../utils/extractProps'; +import { gapPropDefs } from '../../props/gap-props'; /** @public */ export const Stack = forwardRef((props, ref) => { - const { - as = 'div', - children, - align = 'left', - gap = 'xs', - className, - style, - ...restProps - } = props; + const propDefs = { + ...gapPropDefs, + ...stackPropDefs, + }; - // Generate utility class names - const utilityClassNames = getClassNames({ - gap, - alignItems: mapAlignValue(align), - ...restProps, - }); + const { className, style } = extractProps(props, propDefs); - return createElement(as, { + return createElement('div', { ref, - className: clsx('canon-Stack', utilityClassNames, className), + className: clsx('canon-Stack', className), style, - children, + children: props.children, }); }); diff --git a/packages/canon/src/components/Stack/types.ts b/packages/canon/src/components/Stack/types.ts index bccdc59a57..267845c48a 100644 --- a/packages/canon/src/components/Stack/types.ts +++ b/packages/canon/src/components/Stack/types.ts @@ -14,23 +14,15 @@ * limitations under the License. */ -import type { - SpaceProps, - UtilityProps, - AsProps, - Breakpoint, -} from '../../types'; +import type { SpaceProps } from '../../types'; +import { StackOwnProps } from './Stack.props'; +import type { GapProps } from '../../props/gap-props'; /** @public */ export interface StackProps extends SpaceProps { children: React.ReactNode; - as?: AsProps; - gap?: UtilityProps['gap']; - align?: - | 'left' - | 'center' - | 'right' - | Partial>; + gap?: GapProps['gap']; + align?: StackOwnProps['align']; className?: string; style?: React.CSSProperties; } diff --git a/packages/canon/src/css/utilities.css b/packages/canon/src/css/utilities.css index 60f5d886d9..aa7136fb94 100644 --- a/packages/canon/src/css/utilities.css +++ b/packages/canon/src/css/utilities.css @@ -67,3 +67,6 @@ /* Gap */ @import './utilities/gap.css'; + +/* Flex */ +@import './utilities/flex.css'; diff --git a/packages/canon/src/css/utilities/flex.css b/packages/canon/src/css/utilities/flex.css new file mode 100644 index 0000000000..209334c23d --- /dev/null +++ b/packages/canon/src/css/utilities/flex.css @@ -0,0 +1,86 @@ +.cu-align-start { + align-items: start; +} + +.cu-align-center { + align-items: center; +} + +.cu-align-end { + align-items: end; +} + +/* Breakpoint xs */ +@media (min-width: 640px) { + .xs\:cu-align-start { + align-items: start; + } + + .xs\:cu-align-center { + align-items: center; + } + + .xs\:cu-align-end { + align-items: end; + } +} + +/* Breakpoint sm */ +@media (min-width: 768px) { + .sm\:cu-align-start { + align-items: start; + } + + .sm\:cu-align-center { + align-items: center; + } + + .sm\:cu-align-end { + align-items: end; + } +} + +/* Breakpoint md */ +@media (min-width: 1024px) { + .md\:cu-align-start { + align-items: start; + } + + .md\:cu-align-center { + align-items: center; + } + + .md\:cu-align-end { + align-items: end; + } +} + +/* Breakpoint lg */ +@media (min-width: 1280px) { + .lg\:cu-align-start { + align-items: start; + } + + .lg\:cu-align-center { + align-items: center; + } + + .lg\:cu-align-end { + align-items: end; + } +} + +/* Breakpoint xl */ +@media (min-width: 1536px) { + .xl\:cu-align-start { + align-items: start; + } + + .xl\:cu-align-center { + align-items: center; + } + + .xl\:cu-align-end { + align-items: end; + } +}