Improve Stack

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-01-27 21:02:17 +00:00
parent 525f1a3a88
commit fca276ff68
8 changed files with 153 additions and 72 deletions
@@ -29,7 +29,6 @@ import { displayPropDefs } from '../../props/display.props';
export const Box = forwardRef<HTMLDivElement, BoxProps>((props, ref) => {
const { as = 'div', children } = props;
// Extract utility class names and styles
const propDefs = {
...spacingPropDefs,
...widthPropDefs,
@@ -25,7 +25,6 @@ export const Container = forwardRef<HTMLDivElement, ContainerProps>(
(props, ref) => {
const { children } = props;
// Extract utility class names and styles
const propDefs = {
...displayPropDefs,
};
@@ -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<typeof stackPropDefs>;
export { stackPropDefs };
export type { StackOwnProps };
@@ -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<typeof Stack>;
@@ -63,20 +60,14 @@ const DecorativeBox = () => {
};
export const Default: Story = {
render: () => (
<div style={{ width: '320px' }}>
<Stack>
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
</Stack>
</div>
),
args: {
children: [<DecorativeBox />, <DecorativeBox />, <DecorativeBox />],
},
};
export const AlignLeft: Story = {
render: () => (
<Stack align="left">
<Stack align="start">
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
@@ -96,7 +87,7 @@ export const AlignCenter: Story = {
export const AlignRight: Story = {
render: () => (
<Stack align="right">
<Stack align="end">
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
@@ -106,7 +97,7 @@ export const AlignRight: Story = {
export const ResponsiveAlign: Story = {
render: () => (
<Stack align={{ xs: 'left', md: 'center', lg: 'right' }}>
<Stack align={{ xs: 'start', md: 'center', lg: 'end' }}>
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
@@ -116,7 +107,7 @@ export const ResponsiveAlign: Story = {
export const ResponsiveGap: Story = {
render: () => (
<Stack gap={{ xs: 'xs', md: 'md', lg: '2xl' }}>
<Stack gap={{ xs: '4', md: '8', lg: '12' }}>
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
@@ -126,7 +117,7 @@ export const ResponsiveGap: Story = {
export const LargeGap: Story = {
render: () => (
<Stack gap="xl">
<Stack gap="8">
<DecorativeBox />
<DecorativeBox />
<DecorativeBox />
+11 -38
View File
@@ -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<Record<Breakpoint, AlignItems>> = {};
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<HTMLDivElement, StackProps>((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,
});
});
+5 -13
View File
@@ -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<Record<Breakpoint, 'left' | 'center' | 'right'>>;
gap?: GapProps['gap'];
align?: StackOwnProps['align'];
className?: string;
style?: React.CSSProperties;
}
+3
View File
@@ -67,3 +67,6 @@
/* Gap */
@import './utilities/gap.css';
/* Flex */
@import './utilities/flex.css';
+86
View File
@@ -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;
}
}