Fix other layout components

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-10-11 21:51:38 +01:00
parent 5289a4d961
commit e918099695
12 changed files with 250 additions and 487 deletions
+106 -178
View File
@@ -102,44 +102,34 @@ export const Box: ForwardRefExoticComponent<
BoxProps & RefAttributes<HTMLDivElement>
>;
// @public (undocumented)
export type BoxOwnProps = GetPropDefTypes<typeof boxPropDefs>;
// @public (undocumented)
export const boxPropDefs: {
as: {
type: 'enum';
values: readonly ['div', 'span'];
default: 'div';
};
};
// @public (undocumented)
export interface BoxProps extends SpaceProps {
// (undocumented)
as?: BoxOwnProps['as'];
as?: keyof JSX.IntrinsicElements;
// (undocumented)
children?: React.ReactNode;
// (undocumented)
className?: string;
// (undocumented)
display?: DisplayProps['display'];
display?: Responsive<'none' | 'flex' | 'block' | 'inline'>;
// (undocumented)
height?: HeightProps['height'];
height?: Responsive<string>;
// (undocumented)
maxHeight?: HeightProps['maxHeight'];
maxHeight?: Responsive<string>;
// (undocumented)
maxWidth?: WidthProps['maxWidth'];
maxWidth?: Responsive<string>;
// (undocumented)
minHeight?: HeightProps['minHeight'];
minHeight?: Responsive<string>;
// (undocumented)
minWidth?: WidthProps['minWidth'];
minWidth?: Responsive<string>;
// (undocumented)
position?: PositionProps['position'];
position?: Responsive<
'static' | 'relative' | 'absolute' | 'fixed' | 'sticky'
>;
// (undocumented)
style?: React.CSSProperties;
// (undocumented)
width?: WidthProps['width'];
width?: Responsive<string>;
}
// @public (undocumented)
@@ -353,7 +343,20 @@ export const Column: (
) => JSX_2.Element;
// @public (undocumented)
export type Columns = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 'auto';
export type Columns =
| '1'
| '2'
| '3'
| '4'
| '5'
| '6'
| '7'
| '8'
| '9'
| '10'
| '11'
| '12'
| 'auto';
// @public
export type ComponentClassNames<T extends ComponentDefinitionName> =
@@ -365,6 +368,8 @@ export interface ComponentDefinition {
classNames: ClassNamesMap;
// (undocumented)
dataAttributes?: DataAttributesMap;
// (undocumented)
utilityProps?: string[];
}
// @public
@@ -386,6 +391,30 @@ export const componentDefinitions: {
readonly classNames: {
readonly root: 'bui-Box';
};
readonly utilityProps: [
'm',
'mb',
'ml',
'mr',
'mt',
'mx',
'my',
'p',
'pb',
'pl',
'pr',
'pt',
'px',
'py',
'position',
'display',
'width',
'minWidth',
'maxWidth',
'height',
'minHeight',
'maxHeight',
];
};
readonly Button: {
readonly classNames: {
@@ -435,6 +464,7 @@ export const componentDefinitions: {
readonly classNames: {
readonly root: 'bui-Container';
};
readonly utilityProps: ['my', 'mt', 'mb', 'py', 'pt', 'pb', 'display'];
};
readonly FieldLabel: {
readonly classNames: {
@@ -448,12 +478,55 @@ export const componentDefinitions: {
readonly classNames: {
readonly root: 'bui-Flex';
};
readonly utilityProps: [
'm',
'mb',
'ml',
'mr',
'mt',
'mx',
'my',
'p',
'pb',
'pl',
'pr',
'pt',
'px',
'py',
'gap',
'align',
'justify',
'direction',
];
};
readonly Grid: {
readonly classNames: {
readonly root: 'bui-Grid';
readonly item: 'bui-GridItem';
};
readonly utilityProps: [
'columns',
'gap',
'm',
'mb',
'ml',
'mr',
'mt',
'mx',
'my',
'p',
'pb',
'pl',
'pr',
'pt',
'px',
'py',
];
};
readonly GridItem: {
readonly classNames: {
readonly root: 'bui-GridItem';
};
readonly utilityProps: ['colSpan', 'colEnd', 'colStart', 'rowSpan'];
};
readonly Header: {
readonly classNames: {
@@ -752,45 +825,20 @@ export const Flex: ForwardRefExoticComponent<
// @public (undocumented)
export type FlexDirection = 'row' | 'column';
// @public (undocumented)
export type FlexOwnProps = GetPropDefTypes<typeof flexPropDefs>;
// @public (undocumented)
export const flexPropDefs: {
align: {
type: 'enum';
className: string;
values: readonly ['start', 'center', 'end', 'baseline', 'stretch'];
responsive: true;
};
direction: {
type: 'enum';
className: string;
values: readonly ['row', 'column', 'row-reverse', 'column-reverse'];
responsive: true;
};
justify: {
type: 'enum';
className: string;
values: readonly ['start', 'center', 'end', 'between'];
responsive: true;
};
};
// @public (undocumented)
export interface FlexProps extends SpaceProps {
// (undocumented)
align?: FlexOwnProps['align'];
align?: Responsive<'start' | 'center' | 'end' | 'baseline' | 'stretch'>;
// (undocumented)
children: React.ReactNode;
// (undocumented)
className?: string;
// (undocumented)
direction?: FlexOwnProps['direction'];
direction?: Responsive<'row' | 'column' | 'row-reverse' | 'column-reverse'>;
// (undocumented)
gap?: GapProps['gap'];
gap?: Responsive<Space>;
// (undocumented)
justify?: FlexOwnProps['justify'];
justify?: Responsive<'start' | 'center' | 'end' | 'between'>;
// (undocumented)
style?: React.CSSProperties;
}
@@ -849,97 +897,6 @@ export const Grid: {
>;
};
// @public (undocumented)
export type GridItemOwnProps = GetPropDefTypes<typeof gridItemPropDefs>;
// @public (undocumented)
export const gridItemPropDefs: {
colSpan: {
type: 'enum | string';
className: string;
customProperties: '--col-span'[];
values: readonly [
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'11',
'12',
'auto',
];
responsive: true;
};
colEnd: {
type: 'enum | string';
className: string;
customProperties: '--col-end'[];
values: readonly [
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'11',
'12',
'auto',
];
responsive: true;
};
colStart: {
type: 'enum | string';
className: string;
customProperties: '--col-start'[];
values: readonly [
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'11',
'12',
'auto',
];
responsive: true;
};
rowSpan: {
type: 'enum | string';
className: string;
customProperties: '--row-span'[];
values: readonly [
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'11',
'12',
'auto',
];
responsive: true;
};
};
// @public (undocumented)
export interface GridItemProps {
// (undocumented)
@@ -947,46 +904,17 @@ export interface GridItemProps {
// (undocumented)
className?: string;
// (undocumented)
colEnd?: GridItemOwnProps['colEnd'];
colEnd?: Responsive<Columns>;
// (undocumented)
colSpan?: GridItemOwnProps['colSpan'];
colSpan?: Responsive<Columns>;
// (undocumented)
colStart?: GridItemOwnProps['colStart'];
colStart?: Responsive<Columns>;
// (undocumented)
rowSpan?: GridItemOwnProps['rowSpan'];
rowSpan?: Responsive<Columns>;
// (undocumented)
style?: React.CSSProperties;
}
// @public (undocumented)
export type GridOwnProps = GetPropDefTypes<typeof gridPropDefs>;
// @public (undocumented)
export const gridPropDefs: {
columns: {
type: 'enum | string';
className: string;
customProperties: '--columns'[];
values: readonly [
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'11',
'12',
'auto',
];
responsive: true;
default: string;
};
};
// @public (undocumented)
export interface GridProps extends SpaceProps {
// (undocumented)
@@ -994,9 +922,9 @@ export interface GridProps extends SpaceProps {
// (undocumented)
className?: string;
// (undocumented)
columns?: GridOwnProps['columns'];
columns?: Responsive<Columns>;
// (undocumented)
gap?: GapProps['gap'];
gap?: Responsive<Space>;
// (undocumented)
style?: React.CSSProperties;
}
@@ -14,43 +14,26 @@
* limitations under the License.
*/
import { createElement, forwardRef } from 'react';
import { forwardRef } from 'react';
import { ContainerProps } from './types';
import clsx from 'clsx';
import { displayPropDefs } from '../../props/display.props';
import { extractProps } from '../../utils/extractProps';
import { spacingPropDefs } from '../../props/spacing.props';
import { useStyles } from '../../hooks/useStyles';
/** @public */
export const Container = forwardRef<HTMLDivElement, ContainerProps>(
(props, ref) => {
const { children } = props;
const { classNames, utilityClasses, style, cleanedProps } = useStyles(
'Container',
props,
);
const { classNames } = useStyles('Container');
// Create a subset of spacing props that match the interface
const containerSpacingProps = {
my: spacingPropDefs.my,
mt: spacingPropDefs.mt,
mb: spacingPropDefs.mb,
py: spacingPropDefs.py,
pt: spacingPropDefs.pt,
pb: spacingPropDefs.pb,
};
const propDefs = {
...displayPropDefs,
...containerSpacingProps,
};
const { className, style, dataProps } = extractProps(props, propDefs);
return createElement('div', {
ref,
className: clsx(classNames.root, className),
...dataProps,
style,
children,
});
return (
<div
ref={ref}
className={clsx(classNames.root, utilityClasses)}
style={style}
{...cleanedProps}
/>
);
},
);
+2 -6
View File
@@ -26,16 +26,12 @@ export const Flex = forwardRef<HTMLDivElement, FlexProps>((props, ref) => {
{ gap: '4', ...props },
);
const { children, ...rest } = cleanedProps;
return (
<div
ref={ref}
className={clsx(classNames.root, utilityClasses)}
style={style}
{...rest}
>
{children}
</div>
{...cleanedProps}
/>
);
});
+5 -7
View File
@@ -14,17 +14,15 @@
* limitations under the License.
*/
import type { SpaceProps } from '../../types';
import { FlexOwnProps } from './Flex.props';
import type { GapProps } from '../../props/gap-props';
import type { Responsive, Space, SpaceProps } from '../../types';
/** @public */
export interface FlexProps extends SpaceProps {
children: React.ReactNode;
gap?: GapProps['gap'];
align?: FlexOwnProps['align'];
justify?: FlexOwnProps['justify'];
direction?: FlexOwnProps['direction'];
gap?: Responsive<Space>;
align?: Responsive<'start' | 'center' | 'end' | 'baseline' | 'stretch'>;
justify?: Responsive<'start' | 'center' | 'end' | 'between'>;
direction?: Responsive<'row' | 'column' | 'row-reverse' | 'column-reverse'>;
className?: string;
style?: React.CSSProperties;
}
@@ -1,93 +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 columnsValues = [
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'11',
'12',
'auto',
] as const;
/** @public */
const gridPropDefs = {
columns: {
type: 'enum | string',
className: 'bui-columns',
customProperties: ['--columns'],
values: columnsValues,
responsive: true,
default: 'auto',
},
} satisfies {
columns: PropDef<(typeof columnsValues)[number]>;
};
/** @public */
const gridItemPropDefs = {
colSpan: {
type: 'enum | string',
className: 'bui-col-span',
customProperties: ['--col-span'],
values: columnsValues,
responsive: true,
},
colEnd: {
type: 'enum | string',
className: 'bui-col-end',
customProperties: ['--col-end'],
values: columnsValues,
responsive: true,
},
colStart: {
type: 'enum | string',
className: 'bui-col-start',
customProperties: ['--col-start'],
values: columnsValues,
responsive: true,
},
rowSpan: {
type: 'enum | string',
className: 'bui-row-span',
customProperties: ['--row-span'],
values: columnsValues,
responsive: true,
},
} satisfies {
colSpan: PropDef<(typeof columnsValues)[number]>;
colEnd: PropDef<(typeof columnsValues)[number]>;
colStart: PropDef<(typeof columnsValues)[number]>;
rowSpan: PropDef<(typeof columnsValues)[number]>;
};
/** @public */
type GridOwnProps = GetPropDefTypes<typeof gridPropDefs>;
/** @public */
type GridItemOwnProps = GetPropDefTypes<typeof gridItemPropDefs>;
export { gridPropDefs, gridItemPropDefs };
export type { GridOwnProps, GridItemOwnProps };
+25 -34
View File
@@ -14,50 +14,41 @@
* limitations under the License.
*/
import { createElement, forwardRef } from 'react';
import { gapPropDefs } from '../../props/gap-props';
import { extractProps } from '../../utils/extractProps';
import { gridItemPropDefs, gridPropDefs } from './Grid.props';
import { forwardRef } from 'react';
import clsx from 'clsx';
import type { GridItemProps, GridProps } from './types';
import { spacingPropDefs } from '../../props/spacing.props';
import { useStyles } from '../../hooks/useStyles';
const GridRoot = forwardRef<HTMLDivElement, GridProps>((props, ref) => {
const propDefs = {
...gapPropDefs,
...gridPropDefs,
...spacingPropDefs,
};
const { classNames, utilityClasses, style, cleanedProps } = useStyles(
'Grid',
{ columns: 'auto', gap: '4', ...props },
);
const { classNames } = useStyles('Grid');
const { className, style, dataProps } = extractProps(props, propDefs);
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}
{...cleanedProps}
/>
);
});
const GridItem = forwardRef<HTMLDivElement, GridItemProps>((props, ref) => {
const propDefs = {
...gridItemPropDefs,
};
const { classNames, utilityClasses, style, cleanedProps } = useStyles(
'GridItem',
props,
);
const { classNames } = useStyles('Grid');
const { className, style, dataProps } = extractProps(props, propDefs);
return createElement('div', {
ref,
className: clsx(classNames.item, className),
...dataProps,
style,
children: props.children,
});
return (
<div
ref={ref}
className={clsx(classNames.root, utilityClasses)}
style={style}
{...cleanedProps}
/>
);
});
/** @public */
+1 -2
View File
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { Grid } from './Grid';
export { gridPropDefs, gridItemPropDefs } from './Grid.props';
export type { GridProps, GridItemProps } from './types';
export type { GridOwnProps, GridItemOwnProps } from './Grid.props';
+7 -9
View File
@@ -14,16 +14,14 @@
* limitations under the License.
*/
import type { GapProps } from '../../props/gap-props';
import type { SpaceProps } from '../../types';
import type { GridItemOwnProps, GridOwnProps } from './Grid.props';
import type { Space, SpaceProps, Responsive, Columns } from '../../types';
/** @public */
export interface GridProps extends SpaceProps {
children?: React.ReactNode;
className?: string;
columns?: GridOwnProps['columns'];
gap?: GapProps['gap'];
columns?: Responsive<Columns>;
gap?: Responsive<Space>;
style?: React.CSSProperties;
}
@@ -31,9 +29,9 @@ export interface GridProps extends SpaceProps {
export interface GridItemProps {
children?: React.ReactNode;
className?: string;
colSpan?: GridItemOwnProps['colSpan'];
colEnd?: GridItemOwnProps['colEnd'];
colStart?: GridItemOwnProps['colStart'];
rowSpan?: GridItemOwnProps['rowSpan'];
colSpan?: Responsive<Columns>;
colEnd?: Responsive<Columns>;
colStart?: Responsive<Columns>;
rowSpan?: Responsive<Columns>;
style?: React.CSSProperties;
}
+14 -1
View File
@@ -78,7 +78,20 @@ export type BorderRadius =
export type Border = 'none' | 'base' | 'error' | 'warning' | 'selected';
/** @public */
export type Columns = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 'auto';
export type Columns =
| '1'
| '2'
| '3'
| '4'
| '5'
| '6'
| '7'
| '8'
| '9'
| '10'
| '11'
| '12'
| 'auto';
/** @public */
export interface SpaceProps {
+25 -1
View File
@@ -108,6 +108,7 @@ export const componentDefinitions = {
classNames: {
root: 'bui-Container',
},
utilityProps: ['my', 'mt', 'mb', 'py', 'pt', 'pb', 'display'],
},
FieldLabel: {
classNames: {
@@ -145,8 +146,31 @@ export const componentDefinitions = {
Grid: {
classNames: {
root: 'bui-Grid',
item: 'bui-GridItem',
},
utilityProps: [
'columns',
'gap',
'm',
'mb',
'ml',
'mr',
'mt',
'mx',
'my',
'p',
'pb',
'pl',
'pr',
'pt',
'px',
'py',
],
},
GridItem: {
classNames: {
root: 'bui-GridItem',
},
utilityProps: ['colSpan', 'colEnd', 'colStart', 'rowSpan'],
},
Header: {
classNames: {
-110
View File
@@ -1,110 +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.
*/
type BasePropDef = {
type: string;
values?: readonly unknown[];
default?: unknown;
required?: boolean;
className?: string;
responsive?: true;
customProperties?: string[];
};
export function extractProps(
props: {
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
as?: keyof JSX.IntrinsicElements;
[key: string]: any;
},
propDefs: { [name in string]: BasePropDef },
) {
let className: string[] = (props.className || '').split(' ');
let style: React.CSSProperties = { ...props.style };
for (const key in propDefs) {
const propDef = propDefs[key];
// Check if the prop is present or has a default value
if (!Object.hasOwn(props, key) && !propDef.hasOwnProperty('default')) {
continue; // Skip processing if neither is present
}
const value = Object.hasOwn(props, key)
? (props[key] as unknown)
: propDefs[key].default;
const propDefsValues = propDef.values;
const propDefsCustomProperties = propDef.customProperties;
const propDefsClassName = propDef.className;
const isResponsive = propDef.responsive;
const handleValue = (val: unknown, prefix: string = '') => {
// Skip adding class name if the key is "as"
if (key === 'as') return;
if (propDefsValues?.includes(val)) {
className.push(`${prefix}${propDefsClassName}-${val}`);
} else {
if (propDefsCustomProperties) {
for (const customProperty of propDefsCustomProperties) {
const customPropertyKey =
isResponsive && prefix
? `${customProperty}-${prefix.slice(0, -1)}`
: customProperty;
style[customPropertyKey as keyof typeof style] = val as any;
}
}
className.push(`${prefix}${propDefsClassName}`);
}
};
if (isResponsive && typeof value === 'object' && value !== null) {
const breakpointValues = value as { [key: string]: unknown };
// Handle responsive object values
for (const breakpoint in breakpointValues) {
const prefix = breakpoint === 'initial' ? '' : `${breakpoint}:`;
handleValue(breakpointValues[breakpoint], prefix);
}
} else {
handleValue(value);
}
}
// Ensure keys from props that are defined in propDefs are removed
const { cleanedProps, dataProps } = Object.keys(props).reduce(
(acc, key) => {
if (key.startsWith('data-')) {
acc.dataProps[key] = props[key];
}
if (!propDefs.hasOwnProperty(key)) {
acc.cleanedProps[key] = props[key];
}
return acc;
},
{ dataProps: {}, cleanedProps: {} } as {
dataProps: { [key: string]: any };
cleanedProps: { [key: string]: any };
},
);
const newClassNames = className
.filter(name => name && name.trim() !== '')
.join(' ');
return { ...cleanedProps, className: newClassNames, style, dataProps };
}
+52 -16
View File
@@ -15,7 +15,7 @@
*/
// Valid spacing values that have predefined utility classes
const VALID_SPACING_VALUES = [
const validSpacingValues = [
'0.5',
'1',
'1.5',
@@ -34,6 +34,22 @@ const VALID_SPACING_VALUES = [
'14',
] as const;
const columnsValues = [
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'11',
'12',
'auto',
] as const;
export const utilityClassMap: Record<
string,
{ class: string; cssVar?: string; values: readonly (string | number)[] }
@@ -41,72 +57,72 @@ export const utilityClassMap: Record<
m: {
class: 'bui-m',
cssVar: '--m',
values: VALID_SPACING_VALUES,
values: validSpacingValues,
},
mb: {
class: 'bui-mb',
cssVar: '--mb',
values: VALID_SPACING_VALUES,
values: validSpacingValues,
},
ml: {
class: 'bui-ml',
cssVar: '--ml',
values: VALID_SPACING_VALUES,
values: validSpacingValues,
},
mr: {
class: 'bui-mr',
cssVar: '--mr',
values: VALID_SPACING_VALUES,
values: validSpacingValues,
},
mt: {
class: 'bui-mt',
cssVar: '--mt',
values: VALID_SPACING_VALUES,
values: validSpacingValues,
},
mx: {
class: 'bui-mx',
cssVar: '--mx',
values: VALID_SPACING_VALUES,
values: validSpacingValues,
},
my: {
class: 'bui-my',
cssVar: '--my',
values: VALID_SPACING_VALUES,
values: validSpacingValues,
},
p: {
class: 'bui-p',
cssVar: '--p',
values: VALID_SPACING_VALUES,
values: validSpacingValues,
},
pb: {
class: 'bui-pb',
cssVar: '--pb',
values: VALID_SPACING_VALUES,
values: validSpacingValues,
},
pl: {
class: 'bui-pl',
cssVar: '--pl',
values: VALID_SPACING_VALUES,
values: validSpacingValues,
},
pr: {
class: 'bui-pr',
cssVar: '--pr',
values: VALID_SPACING_VALUES,
values: validSpacingValues,
},
pt: {
class: 'bui-pt',
cssVar: '--pt',
values: VALID_SPACING_VALUES,
values: validSpacingValues,
},
px: {
class: 'bui-px',
cssVar: '--px',
values: VALID_SPACING_VALUES,
values: validSpacingValues,
},
py: {
class: 'bui-py',
cssVar: '--py',
values: VALID_SPACING_VALUES,
values: validSpacingValues,
},
width: {
class: 'bui-w',
@@ -141,7 +157,7 @@ export const utilityClassMap: Record<
gap: {
class: 'bui-gap',
cssVar: '--gap',
values: VALID_SPACING_VALUES,
values: validSpacingValues,
},
position: {
class: 'bui-position',
@@ -163,4 +179,24 @@ export const utilityClassMap: Record<
class: 'bui-fd',
values: ['row', 'column', 'row-reverse', 'column-reverse'],
},
columns: {
class: 'bui-columns',
values: columnsValues,
},
colSpan: {
class: 'bui-col-span',
values: columnsValues,
},
colEnd: {
class: 'bui-col-end',
values: columnsValues,
},
colStart: {
class: 'bui-col-start',
values: columnsValues,
},
rowSpan: {
class: 'bui-row-span',
values: columnsValues,
},
};