Clean Flex
Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/** @public */
|
||||
const boxPropDefs = {
|
||||
as: { type: 'enum', values: as, default: 'div' },
|
||||
} satisfies {
|
||||
as: PropDef<(typeof as)[number]>;
|
||||
};
|
||||
|
||||
// Use all of the imported prop defs to ensure that JSDoc works
|
||||
/** @public */
|
||||
type BoxOwnProps = GetPropDefTypes<typeof boxPropDefs>;
|
||||
|
||||
export { boxPropDefs };
|
||||
export type { BoxOwnProps };
|
||||
@@ -15,5 +15,3 @@
|
||||
*/
|
||||
export { Box } from './Box';
|
||||
export type * from './types';
|
||||
export type { BoxOwnProps } from './Box.props';
|
||||
export { boxPropDefs } from './Box.props';
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
const directionValues = [
|
||||
'row',
|
||||
'column',
|
||||
'row-reverse',
|
||||
'column-reverse',
|
||||
] as const;
|
||||
const justifyValues = ['start', 'center', 'end', 'between'] as const;
|
||||
|
||||
/** @public */
|
||||
const flexPropDefs = {
|
||||
align: {
|
||||
type: 'enum',
|
||||
className: 'bui-align',
|
||||
values: alignValues,
|
||||
responsive: true,
|
||||
},
|
||||
direction: {
|
||||
type: 'enum',
|
||||
className: 'bui-fd',
|
||||
values: directionValues,
|
||||
responsive: true,
|
||||
},
|
||||
justify: {
|
||||
type: 'enum',
|
||||
className: 'bui-jc',
|
||||
values: justifyValues,
|
||||
responsive: true,
|
||||
},
|
||||
} satisfies {
|
||||
align: PropDef<(typeof alignValues)[number]>;
|
||||
direction: PropDef<(typeof directionValues)[number]>;
|
||||
justify: PropDef<(typeof justifyValues)[number]>;
|
||||
};
|
||||
|
||||
/** @public */
|
||||
type FlexOwnProps = GetPropDefTypes<typeof flexPropDefs>;
|
||||
|
||||
export { flexPropDefs };
|
||||
export type { FlexOwnProps };
|
||||
@@ -17,6 +17,7 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||
import { Flex } from './Flex';
|
||||
import { Text } from '../Text';
|
||||
import { Box } from '../Box';
|
||||
|
||||
const meta = {
|
||||
title: 'Backstage UI/Flex',
|
||||
@@ -43,17 +44,38 @@ const meta = {
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof meta>;
|
||||
|
||||
const DecorativeBox = () => {
|
||||
const DecorativeBox = ({
|
||||
width = '48px',
|
||||
height = '48px',
|
||||
}: {
|
||||
width?: string;
|
||||
height?: string;
|
||||
}) => {
|
||||
const diagonalStripePattern = (() => {
|
||||
const svg = `
|
||||
<svg width="6" height="6" viewBox="0 0 6 6" xmlns="http://www.w3.org/2000/svg">
|
||||
<g fill="#2563eb" fill-opacity="0.6" fill-rule="evenodd">
|
||||
<path d="M5 0h1L0 6V5zM6 5v1H5z"/>
|
||||
</g>
|
||||
</svg>
|
||||
`.trim();
|
||||
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
||||
})();
|
||||
|
||||
return (
|
||||
<div
|
||||
<Box
|
||||
width={width}
|
||||
height={height}
|
||||
style={{
|
||||
background: '#eaf2fd',
|
||||
borderRadius: '4px',
|
||||
boxShadow: '0 0 0 1px #2563eb',
|
||||
height: '32px',
|
||||
minWidth: '100px',
|
||||
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")',
|
||||
border: '1px solid #2563eb',
|
||||
backgroundImage: `url("${diagonalStripePattern}")`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 'bold',
|
||||
color: '#2563eb',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -85,12 +107,12 @@ export const RowDirection: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const AlignLeft: Story = {
|
||||
export const AlignStart: Story = {
|
||||
render: () => (
|
||||
<Flex align="start">
|
||||
<DecorativeBox />
|
||||
<DecorativeBox />
|
||||
<DecorativeBox />
|
||||
<DecorativeBox height="32px" />
|
||||
<DecorativeBox height="24px" />
|
||||
<DecorativeBox height="48px" />
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
@@ -98,19 +120,19 @@ export const AlignLeft: Story = {
|
||||
export const AlignCenter: Story = {
|
||||
render: () => (
|
||||
<Flex align="center">
|
||||
<DecorativeBox />
|
||||
<DecorativeBox />
|
||||
<DecorativeBox />
|
||||
<DecorativeBox height="32px" />
|
||||
<DecorativeBox height="24px" />
|
||||
<DecorativeBox height="48px" />
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
|
||||
export const AlignRight: Story = {
|
||||
export const AlignEnd: Story = {
|
||||
render: () => (
|
||||
<Flex align="end">
|
||||
<DecorativeBox />
|
||||
<DecorativeBox />
|
||||
<DecorativeBox />
|
||||
<DecorativeBox height="32px" />
|
||||
<DecorativeBox height="24px" />
|
||||
<DecorativeBox height="48px" />
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
@@ -118,9 +140,9 @@ export const AlignRight: Story = {
|
||||
export const ResponsiveAlign: Story = {
|
||||
render: () => (
|
||||
<Flex align={{ xs: 'start', md: 'center', lg: 'end' }}>
|
||||
<DecorativeBox />
|
||||
<DecorativeBox />
|
||||
<DecorativeBox />
|
||||
<DecorativeBox height="32px" />
|
||||
<DecorativeBox height="24px" />
|
||||
<DecorativeBox height="48px" />
|
||||
</Flex>
|
||||
),
|
||||
};
|
||||
|
||||
@@ -14,31 +14,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createElement, forwardRef } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { FlexProps } from './types';
|
||||
import clsx from 'clsx';
|
||||
import { flexPropDefs } from './Flex.props';
|
||||
import { extractProps } from '../../utils/extractProps';
|
||||
import { gapPropDefs } from '../../props/gap-props';
|
||||
import { spacingPropDefs } from '../../props/spacing.props';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
|
||||
/** @public */
|
||||
export const Flex = forwardRef<HTMLDivElement, FlexProps>((props, ref) => {
|
||||
const propDefs = {
|
||||
...gapPropDefs,
|
||||
...flexPropDefs,
|
||||
...spacingPropDefs,
|
||||
};
|
||||
const { classNames, utilityClasses, style, cleanedProps } = useStyles(
|
||||
'Flex',
|
||||
{ gap: '4', ...props },
|
||||
);
|
||||
|
||||
const { classNames } = useStyles('Flex');
|
||||
const { className, style, dataProps } = extractProps(props, propDefs);
|
||||
const { children, ...rest } = cleanedProps;
|
||||
|
||||
return createElement('div', {
|
||||
ref,
|
||||
className: clsx(classNames.root, className),
|
||||
...dataProps,
|
||||
style,
|
||||
children: props.children,
|
||||
});
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(classNames.root, utilityClasses)}
|
||||
style={style}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -14,6 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { Flex } from './Flex';
|
||||
export { flexPropDefs } from './Flex.props';
|
||||
export type { FlexProps } from './types';
|
||||
export type { FlexOwnProps } from './Flex.props';
|
||||
|
||||
@@ -16,139 +16,7 @@
|
||||
import { useBreakpoint, breakpoints } from './useBreakpoint';
|
||||
import { componentDefinitions } from '../utils/componentDefinitions';
|
||||
import type { ComponentDefinitionName, ComponentClassNames } from '../types';
|
||||
|
||||
// Valid spacing values that have predefined utility classes
|
||||
const VALID_SPACING_VALUES = [
|
||||
'0.5',
|
||||
'1',
|
||||
'1.5',
|
||||
'2',
|
||||
'3',
|
||||
'4',
|
||||
'5',
|
||||
'6',
|
||||
'7',
|
||||
'8',
|
||||
'9',
|
||||
'10',
|
||||
'11',
|
||||
'12',
|
||||
'13',
|
||||
'14',
|
||||
] as const;
|
||||
|
||||
const utilityClassMap = {
|
||||
m: {
|
||||
class: 'bui-m',
|
||||
cssVar: '--m',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
mb: {
|
||||
class: 'bui-mb',
|
||||
cssVar: '--mb',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
ml: {
|
||||
class: 'bui-ml',
|
||||
cssVar: '--ml',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
mr: {
|
||||
class: 'bui-mr',
|
||||
cssVar: '--mr',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
mt: {
|
||||
class: 'bui-mt',
|
||||
cssVar: '--mt',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
mx: {
|
||||
class: 'bui-mx',
|
||||
cssVar: '--mx',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
my: {
|
||||
class: 'bui-my',
|
||||
cssVar: '--my',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
p: {
|
||||
class: 'bui-p',
|
||||
cssVar: '--p',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
pb: {
|
||||
class: 'bui-pb',
|
||||
cssVar: '--pb',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
pl: {
|
||||
class: 'bui-pl',
|
||||
cssVar: '--pl',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
pr: {
|
||||
class: 'bui-pr',
|
||||
cssVar: '--pr',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
pt: {
|
||||
class: 'bui-pt',
|
||||
cssVar: '--pt',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
px: {
|
||||
class: 'bui-px',
|
||||
cssVar: '--px',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
py: {
|
||||
class: 'bui-py',
|
||||
cssVar: '--py',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
width: {
|
||||
class: 'bui-w',
|
||||
cssVar: '--width',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
minWidth: {
|
||||
class: 'bui-min-w',
|
||||
cssVar: '--min-width',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
maxWidth: {
|
||||
class: 'bui-max-w',
|
||||
cssVar: '--max-width',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
height: {
|
||||
class: 'bui-h',
|
||||
cssVar: '--height',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
minHeight: {
|
||||
class: 'bui-min-h',
|
||||
cssVar: '--min-height',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
maxHeight: {
|
||||
class: 'bui-max-h',
|
||||
cssVar: '--max-height',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
position: {
|
||||
class: 'bui-position',
|
||||
cssVar: '--position',
|
||||
values: ['static', 'relative', 'absolute', 'fixed', 'sticky'],
|
||||
},
|
||||
display: {
|
||||
class: 'bui-display',
|
||||
cssVar: '--display',
|
||||
values: ['none', 'flex', 'block', 'inline'],
|
||||
},
|
||||
};
|
||||
import { utilityClassMap } from '../utils/utilityClassMap';
|
||||
|
||||
/**
|
||||
* Resolve a responsive value based on the current breakpoint
|
||||
@@ -248,19 +116,23 @@ export function useStyles<T extends ComponentDefinitionName>(
|
||||
}
|
||||
|
||||
// Check if value is in the list of valid values for this utility
|
||||
if (utilityConfig.values.includes(val as any)) {
|
||||
if (
|
||||
utilityConfig.values.length > 0 &&
|
||||
utilityConfig.values.includes(val as string | number)
|
||||
) {
|
||||
// Generate utility class with value suffix and optional breakpoint prefix
|
||||
const className = prefix
|
||||
? `${prefix}${utilityConfig.class}-${val}`
|
||||
: `${utilityConfig.class}-${val}`;
|
||||
utilityClassList.push(className);
|
||||
} else {
|
||||
} else if (utilityConfig.cssVar) {
|
||||
// Custom value - add CSS custom property AND utility class name
|
||||
// Only if cssVar is defined (properties with fixed values don't have cssVar)
|
||||
const cssVarKey = prefix
|
||||
? `${utilityConfig.cssVar}-${prefix.slice(0, -1)}`
|
||||
: utilityConfig.cssVar;
|
||||
// CSS custom properties need to be set as any since they're not part of CSSProperties
|
||||
(generatedStyle as any)[cssVarKey] = val;
|
||||
// CSS custom properties need to be set on the style object as strings
|
||||
(generatedStyle as Record<string, unknown>)[cssVarKey] = val;
|
||||
|
||||
// Add utility class name (without value suffix) with optional breakpoint prefix
|
||||
const className = prefix
|
||||
@@ -268,6 +140,7 @@ export function useStyles<T extends ComponentDefinitionName>(
|
||||
: utilityConfig.class;
|
||||
utilityClassList.push(className);
|
||||
}
|
||||
// If no cssVar and value is not in valid values, skip (invalid value for fixed-value property)
|
||||
};
|
||||
|
||||
for (const key of utilityPropNames) {
|
||||
|
||||
@@ -121,6 +121,26 @@ export const componentDefinitions = {
|
||||
classNames: {
|
||||
root: 'bui-Flex',
|
||||
},
|
||||
utilityProps: [
|
||||
'm',
|
||||
'mb',
|
||||
'ml',
|
||||
'mr',
|
||||
'mt',
|
||||
'mx',
|
||||
'my',
|
||||
'p',
|
||||
'pb',
|
||||
'pl',
|
||||
'pr',
|
||||
'pt',
|
||||
'px',
|
||||
'py',
|
||||
'gap',
|
||||
'align',
|
||||
'justify',
|
||||
'direction',
|
||||
],
|
||||
},
|
||||
Grid: {
|
||||
classNames: {
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Valid spacing values that have predefined utility classes
|
||||
const VALID_SPACING_VALUES = [
|
||||
'0.5',
|
||||
'1',
|
||||
'1.5',
|
||||
'2',
|
||||
'3',
|
||||
'4',
|
||||
'5',
|
||||
'6',
|
||||
'7',
|
||||
'8',
|
||||
'9',
|
||||
'10',
|
||||
'11',
|
||||
'12',
|
||||
'13',
|
||||
'14',
|
||||
] as const;
|
||||
|
||||
export const utilityClassMap: Record<
|
||||
string,
|
||||
{ class: string; cssVar?: string; values: readonly (string | number)[] }
|
||||
> = {
|
||||
m: {
|
||||
class: 'bui-m',
|
||||
cssVar: '--m',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
mb: {
|
||||
class: 'bui-mb',
|
||||
cssVar: '--mb',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
ml: {
|
||||
class: 'bui-ml',
|
||||
cssVar: '--ml',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
mr: {
|
||||
class: 'bui-mr',
|
||||
cssVar: '--mr',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
mt: {
|
||||
class: 'bui-mt',
|
||||
cssVar: '--mt',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
mx: {
|
||||
class: 'bui-mx',
|
||||
cssVar: '--mx',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
my: {
|
||||
class: 'bui-my',
|
||||
cssVar: '--my',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
p: {
|
||||
class: 'bui-p',
|
||||
cssVar: '--p',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
pb: {
|
||||
class: 'bui-pb',
|
||||
cssVar: '--pb',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
pl: {
|
||||
class: 'bui-pl',
|
||||
cssVar: '--pl',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
pr: {
|
||||
class: 'bui-pr',
|
||||
cssVar: '--pr',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
pt: {
|
||||
class: 'bui-pt',
|
||||
cssVar: '--pt',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
px: {
|
||||
class: 'bui-px',
|
||||
cssVar: '--px',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
py: {
|
||||
class: 'bui-py',
|
||||
cssVar: '--py',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
width: {
|
||||
class: 'bui-w',
|
||||
cssVar: '--width',
|
||||
values: [], // Always use custom value
|
||||
},
|
||||
minWidth: {
|
||||
class: 'bui-min-w',
|
||||
cssVar: '--min-width',
|
||||
values: [], // Always use custom value
|
||||
},
|
||||
maxWidth: {
|
||||
class: 'bui-max-w',
|
||||
cssVar: '--max-width',
|
||||
values: [], // Always use custom value
|
||||
},
|
||||
height: {
|
||||
class: 'bui-h',
|
||||
cssVar: '--height',
|
||||
values: [], // Always use custom value
|
||||
},
|
||||
minHeight: {
|
||||
class: 'bui-min-h',
|
||||
cssVar: '--min-height',
|
||||
values: [], // Always use custom value
|
||||
},
|
||||
maxHeight: {
|
||||
class: 'bui-max-h',
|
||||
cssVar: '--max-height',
|
||||
values: [], // Always use custom value
|
||||
},
|
||||
gap: {
|
||||
class: 'bui-gap',
|
||||
cssVar: '--gap',
|
||||
values: VALID_SPACING_VALUES,
|
||||
},
|
||||
position: {
|
||||
class: 'bui-position',
|
||||
values: ['static', 'relative', 'absolute', 'fixed', 'sticky'],
|
||||
},
|
||||
display: {
|
||||
class: 'bui-display',
|
||||
values: ['none', 'flex', 'block', 'inline'],
|
||||
},
|
||||
align: {
|
||||
class: 'bui-align',
|
||||
values: ['start', 'center', 'end', 'baseline', 'stretch'],
|
||||
},
|
||||
justify: {
|
||||
class: 'bui-justify',
|
||||
values: ['start', 'center', 'end', 'between'],
|
||||
},
|
||||
direction: {
|
||||
class: 'bui-fd',
|
||||
values: ['row', 'column', 'row-reverse', 'column-reverse'],
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user