Merge pull request #32711 from backstage/bui-surfaces
BUI - Replace Surface system with Bg (neutral background) system
This commit is contained in:
+148
-79
@@ -144,7 +144,6 @@ export const AlertDefinition: {
|
||||
readonly spinner: 'bui-AlertSpinner';
|
||||
readonly actions: 'bui-AlertActions';
|
||||
};
|
||||
readonly surface: 'container';
|
||||
readonly propDefs: {
|
||||
readonly status: {
|
||||
readonly dataAttribute: true;
|
||||
@@ -157,7 +156,6 @@ export const AlertDefinition: {
|
||||
readonly customActions: {};
|
||||
readonly title: {};
|
||||
readonly description: {};
|
||||
readonly surface: {};
|
||||
readonly className: {};
|
||||
readonly style: {};
|
||||
};
|
||||
@@ -165,7 +163,7 @@ export const AlertDefinition: {
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type AlertOwnProps = ContainerSurfaceProps & {
|
||||
export type AlertOwnProps = {
|
||||
status?: Responsive<'info' | 'success' | 'warning' | 'danger'>;
|
||||
icon?: boolean | ReactElement;
|
||||
loading?: boolean;
|
||||
@@ -207,6 +205,23 @@ export interface AvatarProps extends React.ComponentPropsWithoutRef<'div'> {
|
||||
src: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface BgContextValue {
|
||||
// (undocumented)
|
||||
bg: ContainerBg | undefined;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const BgProvider: ({ bg, children }: BgProviderProps) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface BgProviderProps {
|
||||
// (undocumented)
|
||||
bg: ContainerBg;
|
||||
// (undocumented)
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type Border = 'none' | 'base' | 'error' | 'warning' | 'selected';
|
||||
|
||||
@@ -234,12 +249,12 @@ export const BoxDefinition: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-Box';
|
||||
};
|
||||
readonly surface: 'container';
|
||||
readonly bg: 'provider';
|
||||
readonly propDefs: {
|
||||
readonly as: {
|
||||
readonly default: 'div';
|
||||
};
|
||||
readonly surface: {
|
||||
readonly bg: {
|
||||
readonly dataAttribute: true;
|
||||
};
|
||||
readonly children: {};
|
||||
@@ -275,7 +290,7 @@ export const BoxDefinition: {
|
||||
// @public (undocumented)
|
||||
export type BoxOwnProps = {
|
||||
as?: keyof JSX.IntrinsicElements;
|
||||
surface?: Responsive<Surface>;
|
||||
bg?: Responsive<ProviderBg>;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
@@ -320,7 +335,7 @@ export const ButtonDefinition: {
|
||||
readonly content: 'bui-ButtonContent';
|
||||
readonly spinner: 'bui-ButtonSpinner';
|
||||
};
|
||||
readonly surface: 'leaf';
|
||||
readonly bg: 'consumer';
|
||||
readonly propDefs: {
|
||||
readonly size: {
|
||||
readonly dataAttribute: true;
|
||||
@@ -338,7 +353,6 @@ export const ButtonDefinition: {
|
||||
};
|
||||
readonly iconStart: {};
|
||||
readonly iconEnd: {};
|
||||
readonly onSurface: {};
|
||||
readonly children: {};
|
||||
readonly className: {};
|
||||
readonly style: {};
|
||||
@@ -360,7 +374,7 @@ export const ButtonIconDefinition: {
|
||||
readonly content: 'bui-ButtonIconContent';
|
||||
readonly spinner: 'bui-ButtonIconSpinner';
|
||||
};
|
||||
readonly surface: 'leaf';
|
||||
readonly bg: 'consumer';
|
||||
readonly propDefs: {
|
||||
readonly size: {
|
||||
readonly dataAttribute: true;
|
||||
@@ -374,14 +388,13 @@ export const ButtonIconDefinition: {
|
||||
readonly dataAttribute: true;
|
||||
};
|
||||
readonly icon: {};
|
||||
readonly onSurface: {};
|
||||
readonly className: {};
|
||||
readonly style: {};
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type ButtonIconOwnProps = LeafSurfaceProps & {
|
||||
export type ButtonIconOwnProps = {
|
||||
size?: Responsive<'small' | 'medium'>;
|
||||
variant?: Responsive<'primary' | 'secondary' | 'tertiary'>;
|
||||
icon?: ReactElement;
|
||||
@@ -409,7 +422,7 @@ export const ButtonLinkDefinition: {
|
||||
readonly root: 'bui-ButtonLink';
|
||||
readonly content: 'bui-ButtonLinkContent';
|
||||
};
|
||||
readonly surface: 'leaf';
|
||||
readonly bg: 'consumer';
|
||||
readonly propDefs: {
|
||||
readonly size: {
|
||||
readonly dataAttribute: true;
|
||||
@@ -421,7 +434,6 @@ export const ButtonLinkDefinition: {
|
||||
};
|
||||
readonly iconStart: {};
|
||||
readonly iconEnd: {};
|
||||
readonly onSurface: {};
|
||||
readonly children: {};
|
||||
readonly className: {};
|
||||
readonly style: {};
|
||||
@@ -429,7 +441,7 @@ export const ButtonLinkDefinition: {
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type ButtonLinkOwnProps = LeafSurfaceProps & {
|
||||
export type ButtonLinkOwnProps = {
|
||||
size?: Responsive<'small' | 'medium'>;
|
||||
variant?: Responsive<'primary' | 'secondary' | 'tertiary'>;
|
||||
iconStart?: ReactElement;
|
||||
@@ -445,7 +457,7 @@ export interface ButtonLinkProps
|
||||
ButtonLinkOwnProps {}
|
||||
|
||||
// @public (undocumented)
|
||||
export type ButtonOwnProps = LeafSurfaceProps & {
|
||||
export type ButtonOwnProps = {
|
||||
size?: Responsive<'small' | 'medium'>;
|
||||
variant?: Responsive<'primary' | 'secondary' | 'tertiary'>;
|
||||
destructive?: boolean;
|
||||
@@ -473,18 +485,41 @@ export const CardBody: ForwardRefExoticComponent<
|
||||
>;
|
||||
|
||||
// @public
|
||||
export interface CardBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
// (undocumented)
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export const CardBodyDefinition: {
|
||||
readonly styles: {
|
||||
readonly [key: string]: string;
|
||||
};
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-CardBody';
|
||||
};
|
||||
readonly propDefs: {
|
||||
readonly children: {};
|
||||
readonly className: {};
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type CardBodyOwnProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface CardBodyProps
|
||||
extends CardBodyOwnProps,
|
||||
React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
// @public
|
||||
export const CardDefinition: {
|
||||
readonly styles: {
|
||||
readonly [key: string]: string;
|
||||
};
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-Card';
|
||||
readonly header: 'bui-CardHeader';
|
||||
readonly body: 'bui-CardBody';
|
||||
readonly footer: 'bui-CardFooter';
|
||||
};
|
||||
readonly propDefs: {
|
||||
readonly children: {};
|
||||
readonly className: {};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -494,10 +529,29 @@ export const CardFooter: ForwardRefExoticComponent<
|
||||
>;
|
||||
|
||||
// @public
|
||||
export interface CardFooterProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
// (undocumented)
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export const CardFooterDefinition: {
|
||||
readonly styles: {
|
||||
readonly [key: string]: string;
|
||||
};
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-CardFooter';
|
||||
};
|
||||
readonly propDefs: {
|
||||
readonly children: {};
|
||||
readonly className: {};
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type CardFooterOwnProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface CardFooterProps
|
||||
extends CardFooterOwnProps,
|
||||
React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
// @public
|
||||
export const CardHeader: ForwardRefExoticComponent<
|
||||
@@ -505,16 +559,40 @@ export const CardHeader: ForwardRefExoticComponent<
|
||||
>;
|
||||
|
||||
// @public
|
||||
export interface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
// (undocumented)
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export const CardHeaderDefinition: {
|
||||
readonly styles: {
|
||||
readonly [key: string]: string;
|
||||
};
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-CardHeader';
|
||||
};
|
||||
readonly propDefs: {
|
||||
readonly children: {};
|
||||
readonly className: {};
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type CardHeaderOwnProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
// (undocumented)
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export interface CardHeaderProps
|
||||
extends CardHeaderOwnProps,
|
||||
React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
// @public (undocumented)
|
||||
export type CardOwnProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface CardProps
|
||||
extends CardOwnProps,
|
||||
React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
// @public (undocumented)
|
||||
export const Cell: {
|
||||
@@ -654,6 +732,15 @@ export const Container: ForwardRefExoticComponent<
|
||||
ContainerProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
|
||||
// @public
|
||||
export type ContainerBg =
|
||||
| 'neutral-1'
|
||||
| 'neutral-2'
|
||||
| 'neutral-3'
|
||||
| 'danger'
|
||||
| 'warning'
|
||||
| 'success';
|
||||
|
||||
// @public
|
||||
export const ContainerDefinition: {
|
||||
readonly classNames: {
|
||||
@@ -684,12 +771,6 @@ export interface ContainerProps {
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ContainerSurfaceProps {
|
||||
// (undocumented)
|
||||
surface?: Responsive<Surface>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CursorParams<TFilter> {
|
||||
// (undocumented)
|
||||
@@ -861,11 +942,10 @@ export const FlexDefinition: {
|
||||
'direction',
|
||||
];
|
||||
readonly dataAttributes: {
|
||||
readonly surface: readonly [
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
readonly bg: readonly [
|
||||
'neutral-1',
|
||||
'neutral-2',
|
||||
'neutral-3',
|
||||
'danger',
|
||||
'warning',
|
||||
'success',
|
||||
@@ -881,6 +961,8 @@ export interface FlexProps extends SpaceProps {
|
||||
// (undocumented)
|
||||
align?: Responsive<'start' | 'center' | 'end' | 'baseline' | 'stretch'>;
|
||||
// (undocumented)
|
||||
bg?: Responsive<ProviderBg>;
|
||||
// (undocumented)
|
||||
children?: React.ReactNode;
|
||||
// (undocumented)
|
||||
className?: string;
|
||||
@@ -892,8 +974,6 @@ export interface FlexProps extends SpaceProps {
|
||||
justify?: Responsive<'start' | 'center' | 'end' | 'between'>;
|
||||
// (undocumented)
|
||||
style?: React.CSSProperties;
|
||||
// (undocumented)
|
||||
surface?: Responsive<Surface>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
@@ -931,11 +1011,10 @@ export const GridDefinition: {
|
||||
'py',
|
||||
];
|
||||
readonly dataAttributes: {
|
||||
readonly surface: readonly [
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
readonly bg: readonly [
|
||||
'neutral-1',
|
||||
'neutral-2',
|
||||
'neutral-3',
|
||||
'danger',
|
||||
'warning',
|
||||
'success',
|
||||
@@ -950,11 +1029,10 @@ export const GridItemDefinition: {
|
||||
};
|
||||
readonly utilityProps: ['colSpan', 'colEnd', 'colStart', 'rowSpan'];
|
||||
readonly dataAttributes: {
|
||||
readonly surface: readonly [
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
readonly bg: readonly [
|
||||
'neutral-1',
|
||||
'neutral-2',
|
||||
'neutral-3',
|
||||
'danger',
|
||||
'warning',
|
||||
'success',
|
||||
@@ -964,6 +1042,8 @@ export const GridItemDefinition: {
|
||||
|
||||
// @public (undocumented)
|
||||
export interface GridItemProps {
|
||||
// (undocumented)
|
||||
bg?: Responsive<ProviderBg>;
|
||||
// (undocumented)
|
||||
children?: React.ReactNode;
|
||||
// (undocumented)
|
||||
@@ -978,12 +1058,12 @@ export interface GridItemProps {
|
||||
rowSpan?: Responsive<Columns>;
|
||||
// (undocumented)
|
||||
style?: React.CSSProperties;
|
||||
// (undocumented)
|
||||
surface?: Responsive<Surface>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface GridProps extends SpaceProps {
|
||||
// (undocumented)
|
||||
bg?: Responsive<ProviderBg>;
|
||||
// (undocumented)
|
||||
children?: React.ReactNode;
|
||||
// (undocumented)
|
||||
@@ -994,8 +1074,6 @@ export interface GridProps extends SpaceProps {
|
||||
gap?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
style?: React.CSSProperties;
|
||||
// (undocumented)
|
||||
surface?: Responsive<Surface>;
|
||||
}
|
||||
|
||||
// @public
|
||||
@@ -1088,12 +1166,6 @@ export type JustifyContent =
|
||||
| 'around'
|
||||
| 'between';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface LeafSurfaceProps {
|
||||
// (undocumented)
|
||||
onSurface?: Responsive<Surface>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const Link: ForwardRefExoticComponent<
|
||||
LinkProps & RefAttributes<HTMLAnchorElement>
|
||||
@@ -1444,6 +1516,9 @@ export interface PopoverProps extends Omit<PopoverProps_2, 'children'> {
|
||||
hideArrow?: boolean;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type ProviderBg = ContainerBg | 'neutral-auto';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface QueryOptions<TFilter> {
|
||||
// (undocumented)
|
||||
@@ -1655,17 +1730,6 @@ export const SubmenuTrigger: (props: SubmenuTriggerProps) => JSX_2.Element;
|
||||
// @public (undocumented)
|
||||
export interface SubmenuTriggerProps extends SubmenuTriggerProps_2 {}
|
||||
|
||||
// @public
|
||||
export type Surface =
|
||||
| '0'
|
||||
| '1'
|
||||
| '2'
|
||||
| '3'
|
||||
| 'danger'
|
||||
| 'warning'
|
||||
| 'success'
|
||||
| 'auto';
|
||||
|
||||
// @public (undocumented)
|
||||
export const Switch: ForwardRefExoticComponent<
|
||||
SwitchProps & RefAttributes<HTMLLabelElement>
|
||||
@@ -2081,7 +2145,6 @@ export interface ToggleButtonProps extends ToggleButtonProps_2 {
|
||||
iconEnd?: ReactElement;
|
||||
// (undocumented)
|
||||
iconStart?: ReactElement;
|
||||
onSurface?: Responsive<Surface>;
|
||||
// (undocumented)
|
||||
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
|
||||
}
|
||||
@@ -2110,6 +2173,12 @@ export const TooltipTrigger: (
|
||||
props: TooltipTriggerComponentProps,
|
||||
) => JSX_2.Element;
|
||||
|
||||
// @public
|
||||
export function useBgConsumer(): BgContextValue;
|
||||
|
||||
// @public
|
||||
export function useBgProvider(bg?: Responsive<ProviderBg>): BgContextValue;
|
||||
|
||||
// @public (undocumented)
|
||||
export const useBreakpoint: () => {
|
||||
breakpoint: Breakpoint;
|
||||
|
||||
@@ -284,50 +284,38 @@ export const LongContent = meta.story({
|
||||
),
|
||||
});
|
||||
|
||||
export const OnDifferentSurfaces = meta.story({
|
||||
export const OnDifferentBackgrounds = meta.story({
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>Default Surface</Text>
|
||||
<Text>Default</Text>
|
||||
<Flex direction="column" gap="2" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on default surface" />
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
title="Alert on default surface"
|
||||
/>
|
||||
<Alert status="info" icon={true} title="Alert on default bg" />
|
||||
<Alert status="success" icon={true} title="Alert on default bg" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 0</Text>
|
||||
<Flex direction="column" gap="2" surface="0" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on surface 0" />
|
||||
<Alert status="success" icon={true} title="Alert on surface 0" />
|
||||
<Text>On Neutral 1</Text>
|
||||
<Flex direction="column" gap="2" bg="neutral-1" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on neutral-1" />
|
||||
<Alert status="success" icon={true} title="Alert on neutral-1" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 1</Text>
|
||||
<Flex direction="column" gap="2" surface="1" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on surface 1" />
|
||||
<Alert status="success" icon={true} title="Alert on surface 1" />
|
||||
<Text>On Neutral 2</Text>
|
||||
<Flex direction="column" gap="2" bg="neutral-2" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on neutral-2" />
|
||||
<Alert status="success" icon={true} title="Alert on neutral-2" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 2</Text>
|
||||
<Flex direction="column" gap="2" surface="2" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on surface 2" />
|
||||
<Alert status="success" icon={true} title="Alert on surface 2" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 3</Text>
|
||||
<Flex direction="column" gap="2" surface="3" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on surface 3" />
|
||||
<Alert status="success" icon={true} title="Alert on surface 3" />
|
||||
<Text>On Neutral 3</Text>
|
||||
<Flex direction="column" gap="2" bg="neutral-3" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on neutral-3" />
|
||||
<Alert status="success" icon={true} title="Alert on neutral-3" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
@@ -349,7 +337,7 @@ export const Responsive = meta.story({
|
||||
|
||||
export const WithUtilityProps = meta.story({
|
||||
render: () => (
|
||||
<Box surface="1" py="4">
|
||||
<Box bg="neutral-1" py="4">
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
|
||||
@@ -34,7 +34,6 @@ export const AlertDefinition = defineComponent<AlertOwnProps>()({
|
||||
spinner: 'bui-AlertSpinner',
|
||||
actions: 'bui-AlertActions',
|
||||
},
|
||||
surface: 'container',
|
||||
propDefs: {
|
||||
status: { dataAttribute: true, default: 'info' },
|
||||
loading: { dataAttribute: true },
|
||||
@@ -42,7 +41,6 @@ export const AlertDefinition = defineComponent<AlertOwnProps>()({
|
||||
customActions: {},
|
||||
title: {},
|
||||
description: {},
|
||||
surface: {},
|
||||
className: {},
|
||||
style: {},
|
||||
},
|
||||
|
||||
@@ -15,14 +15,10 @@
|
||||
*/
|
||||
|
||||
import type { ReactElement, ReactNode, CSSProperties } from 'react';
|
||||
import type {
|
||||
ContainerSurfaceProps,
|
||||
Responsive,
|
||||
MarginProps,
|
||||
} from '../../types';
|
||||
import type { Responsive, MarginProps } from '../../types';
|
||||
|
||||
/** @public */
|
||||
export type AlertOwnProps = ContainerSurfaceProps & {
|
||||
export type AlertOwnProps = {
|
||||
status?: Responsive<'info' | 'success' | 'warning' | 'danger'>;
|
||||
icon?: boolean | ReactElement;
|
||||
loading?: boolean;
|
||||
|
||||
@@ -23,31 +23,27 @@
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
.bui-Box[data-surface='0'] {
|
||||
background-color: var(--bui-bg-neutral-0);
|
||||
}
|
||||
|
||||
.bui-Box[data-surface='1'] {
|
||||
.bui-Box[data-bg='neutral-1'] {
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
}
|
||||
|
||||
.bui-Box[data-surface='2'] {
|
||||
.bui-Box[data-bg='neutral-2'] {
|
||||
background-color: var(--bui-bg-neutral-2);
|
||||
}
|
||||
|
||||
.bui-Box[data-surface='3'] {
|
||||
.bui-Box[data-bg='neutral-3'] {
|
||||
background-color: var(--bui-bg-neutral-3);
|
||||
}
|
||||
|
||||
.bui-Box[data-surface='danger'] {
|
||||
.bui-Box[data-bg='danger'] {
|
||||
background-color: var(--bui-bg-danger);
|
||||
}
|
||||
|
||||
.bui-Box[data-surface='warning'] {
|
||||
.bui-Box[data-bg='warning'] {
|
||||
background-color: var(--bui-bg-warning);
|
||||
}
|
||||
|
||||
.bui-Box[data-surface='success'] {
|
||||
.bui-Box[data-bg='success'] {
|
||||
background-color: var(--bui-bg-success);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,90 +346,47 @@ export const Display = meta.story({
|
||||
),
|
||||
});
|
||||
|
||||
export const Surfaces = meta.story({
|
||||
export const BackgroundColors = meta.story({
|
||||
args: { px: '6', py: '4' },
|
||||
render: args => (
|
||||
<Flex align="center" style={{ flexWrap: 'wrap' }}>
|
||||
<Box {...args}>Default</Box>
|
||||
<Box surface="0" {...args}>
|
||||
Surface 0
|
||||
<Box bg="neutral-1" {...args}>
|
||||
Neutral 1
|
||||
</Box>
|
||||
<Box surface="1" {...args}>
|
||||
Surface 1
|
||||
<Box bg="neutral-2" {...args}>
|
||||
Neutral 2
|
||||
</Box>
|
||||
<Box surface="2" {...args}>
|
||||
Surface 2
|
||||
<Box bg="neutral-3" {...args}>
|
||||
Neutral 3
|
||||
</Box>
|
||||
<Box surface="3" {...args}>
|
||||
Surface 3
|
||||
<Box bg={{ initial: 'neutral-1', sm: 'neutral-2' }} {...args}>
|
||||
Responsive Neutral
|
||||
</Box>
|
||||
<Box surface={{ initial: '0', sm: '1' }} {...args}>
|
||||
Responsive Surface
|
||||
<Box bg="danger" {...args}>
|
||||
Danger
|
||||
</Box>
|
||||
<Box surface="danger" {...args}>
|
||||
Surface Danger
|
||||
<Box bg="warning" {...args}>
|
||||
Warning
|
||||
</Box>
|
||||
<Box surface="warning" {...args}>
|
||||
Surface Warning
|
||||
</Box>
|
||||
<Box surface="success" {...args}>
|
||||
Surface Success
|
||||
<Box bg="success" {...args}>
|
||||
Success
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const SurfacesNested = meta.story({
|
||||
export const NestedNeutralColors = meta.story({
|
||||
args: { px: '6', py: '4' },
|
||||
render: args => (
|
||||
<Flex direction="column">
|
||||
<Box style={{ maxWidth: '600px' }} mb="4">
|
||||
In this test, we are nesting boxes and buttons on different surfaces to
|
||||
ensure that the correct surface is applied to each element. If a Button
|
||||
is placed on a surface that doesn't have the surface prop set, it will
|
||||
inherit the surface from the parent.
|
||||
</Box>
|
||||
<Box {...args} surface="1">
|
||||
<Button variant="secondary">Button</Button>
|
||||
<Box {...args} surface="2" mt="4">
|
||||
<Button variant="secondary">Button</Button>
|
||||
<Box {...args} mt="4">
|
||||
<Button variant="secondary">Button</Button>
|
||||
</Box>
|
||||
<Box {...args} bg="neutral-1">
|
||||
<Button variant="secondary">Button (on neutral-1)</Button>
|
||||
<Box {...args} bg="neutral-2" mt="4">
|
||||
<Button variant="secondary">Button (on neutral-2)</Button>
|
||||
<Box {...args} bg="neutral-3" mt="4">
|
||||
<Button variant="secondary">Button (on neutral-3)</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const SurfacesAutoIncrement = meta.story({
|
||||
args: { px: '6', py: '4' },
|
||||
render: args => (
|
||||
<Flex direction="column">
|
||||
<Box style={{ maxWidth: '600px' }} mb="4">
|
||||
Using surface="auto" automatically increments from the parent surface
|
||||
level. This makes components more reusable as they don't need to know
|
||||
their absolute surface level. Notice how each nested Box with
|
||||
surface="auto" automatically increments: 0 → 1 → 2 → 3 (capped at 3).
|
||||
</Box>
|
||||
<Box {...args} surface="0">
|
||||
Surface 0 (explicit)
|
||||
<Box {...args} surface="auto" mt="4">
|
||||
<Box mb="3">Surface auto (becomes 1)</Box>
|
||||
<Button variant="secondary" onSurface="auto">
|
||||
Button auto
|
||||
</Button>
|
||||
<Box {...args} surface="auto" mt="4">
|
||||
Surface auto (becomes 2)
|
||||
<Box {...args} surface="auto" mt="4">
|
||||
Surface auto (becomes 3)
|
||||
<Box {...args} surface="auto" mt="4">
|
||||
Surface auto (stays 3 - capped)
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ export const Box = forwardRef<HTMLDivElement, BoxProps>((props, ref) => {
|
||||
BoxDefinition,
|
||||
props,
|
||||
);
|
||||
const { classes, as, surfaceChildren } = ownProps;
|
||||
const { classes, as, childrenWithBgProvider } = ownProps;
|
||||
|
||||
return createElement(
|
||||
as,
|
||||
@@ -36,7 +36,7 @@ export const Box = forwardRef<HTMLDivElement, BoxProps>((props, ref) => {
|
||||
...dataAttributes,
|
||||
...restProps,
|
||||
},
|
||||
surfaceChildren,
|
||||
childrenWithBgProvider,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -27,10 +27,10 @@ export const BoxDefinition = defineComponent<BoxOwnProps>()({
|
||||
classNames: {
|
||||
root: 'bui-Box',
|
||||
},
|
||||
surface: 'container',
|
||||
bg: 'provider',
|
||||
propDefs: {
|
||||
as: { default: 'div' },
|
||||
surface: { dataAttribute: true },
|
||||
bg: { dataAttribute: true },
|
||||
children: {},
|
||||
className: {},
|
||||
style: {},
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
*/
|
||||
|
||||
import type { ReactNode, CSSProperties } from 'react';
|
||||
import type { Responsive, Surface, SpaceProps } from '../../types';
|
||||
import type { Responsive, ProviderBg, SpaceProps } from '../../types';
|
||||
|
||||
/** @public */
|
||||
export type BoxOwnProps = {
|
||||
as?: keyof JSX.IntrinsicElements;
|
||||
surface?: Responsive<Surface>;
|
||||
bg?: Responsive<ProviderBg>;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
|
||||
@@ -118,19 +118,19 @@
|
||||
--bg-active: var(--bui-bg-neutral-1-pressed);
|
||||
--fg: var(--bui-fg-primary);
|
||||
|
||||
&[data-on-surface='1'] {
|
||||
&[data-on-bg='neutral-1'] {
|
||||
--bg: var(--bui-bg-neutral-2);
|
||||
--bg-hover: var(--bui-bg-neutral-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-2-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='2'] {
|
||||
&[data-on-bg='neutral-2'] {
|
||||
--bg: var(--bui-bg-neutral-3);
|
||||
--bg-hover: var(--bui-bg-neutral-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-3-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='3'] {
|
||||
&[data-on-bg='neutral-3'] {
|
||||
--bg: var(--bui-bg-neutral-4);
|
||||
--bg-hover: var(--bui-bg-neutral-4-hover);
|
||||
--bg-active: var(--bui-bg-neutral-4-pressed);
|
||||
@@ -182,17 +182,17 @@
|
||||
--bg-active: var(--bui-bg-neutral-1-pressed);
|
||||
--fg: var(--bui-fg-primary);
|
||||
|
||||
&[data-on-surface='1'] {
|
||||
&[data-on-bg='neutral-1'] {
|
||||
--bg-hover: var(--bui-bg-neutral-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-2-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='2'] {
|
||||
&[data-on-bg='neutral-2'] {
|
||||
--bg-hover: var(--bui-bg-neutral-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-3-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='3'] {
|
||||
&[data-on-bg='neutral-3'] {
|
||||
--bg-hover: var(--bui-bg-neutral-4-hover);
|
||||
--bg-active: var(--bui-bg-neutral-4-pressed);
|
||||
}
|
||||
|
||||
@@ -81,32 +81,9 @@ export const Variants = meta.story({
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>Neutral 0</Text>
|
||||
<Flex align="center" surface="0" p="4">
|
||||
<Button iconStart={<RiCloudLine />} variant="primary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="secondary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="primary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="secondary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>Neutral 1</Text>
|
||||
<Flex align="center" surface="1" p="4">
|
||||
<Flex align="center" bg="neutral-1" p="4">
|
||||
<Button iconStart={<RiCloudLine />} variant="primary">
|
||||
Button
|
||||
</Button>
|
||||
@@ -129,63 +106,49 @@ export const Variants = meta.story({
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>Neutral 2</Text>
|
||||
<Box surface="1">
|
||||
<Flex align="center" surface="2" p="4">
|
||||
<Button iconStart={<RiCloudLine />} variant="primary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="secondary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="primary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="secondary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Flex align="center" bg="neutral-2" p="4">
|
||||
<Button iconStart={<RiCloudLine />} variant="primary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="secondary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="primary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="secondary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>Neutral 3</Text>
|
||||
<Box surface="1">
|
||||
<Box surface="2">
|
||||
<Flex align="center" surface="3" p="4">
|
||||
<Button iconStart={<RiCloudLine />} variant="primary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="secondary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="primary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button
|
||||
iconStart={<RiCloudLine />}
|
||||
variant="secondary"
|
||||
destructive
|
||||
>
|
||||
Button
|
||||
</Button>
|
||||
<Button
|
||||
iconStart={<RiCloudLine />}
|
||||
variant="tertiary"
|
||||
destructive
|
||||
>
|
||||
Button
|
||||
</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Box>
|
||||
<Flex align="center" bg="neutral-3" p="4">
|
||||
<Button iconStart={<RiCloudLine />} variant="primary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="secondary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="primary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="secondary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
),
|
||||
@@ -246,8 +209,8 @@ export const Destructive = meta.story({
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 1</Text>
|
||||
<Flex align="center" surface="1" p="4" gap="4">
|
||||
<Text>On Neutral 1</Text>
|
||||
<Flex align="center" bg="neutral-1" p="4" gap="4">
|
||||
<Button variant="primary" destructive>
|
||||
Primary
|
||||
</Button>
|
||||
@@ -462,49 +425,33 @@ export const LoadingVariants = meta.story({
|
||||
),
|
||||
});
|
||||
|
||||
export const OnSurfaceAuto = meta.story({
|
||||
export const AutoBg = meta.story({
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<div style={{ maxWidth: '600px' }}>
|
||||
Using onSurface="auto" on buttons inherits their container's surface
|
||||
level, making them reusable. This is equivalent to not specifying
|
||||
onSurface. To override, use explicit surface values like onSurface="0"
|
||||
or onSurface="2".
|
||||
Buttons automatically detect their parent bg context and increment the
|
||||
neutral level by 1. No prop is needed on the button -- it's fully
|
||||
automatic.
|
||||
</div>
|
||||
<Box surface="0" p="4">
|
||||
<Text>Surface 0 container</Text>
|
||||
<Box bg="neutral-1" p="4">
|
||||
<Text>Neutral 1 container</Text>
|
||||
<Flex gap="2" mt="2">
|
||||
<Button variant="secondary">Default (inherits 0)</Button>
|
||||
<Button variant="secondary" onSurface="auto">
|
||||
Auto (inherits 0)
|
||||
</Button>
|
||||
<Button variant="secondary" onSurface="1">
|
||||
Explicit 1
|
||||
</Button>
|
||||
<Button variant="secondary">Auto (neutral-2)</Button>
|
||||
<Button variant="tertiary">Auto (neutral-2)</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box surface="1" p="4">
|
||||
<Text>Surface 1 container</Text>
|
||||
<Box bg="neutral-2" p="4">
|
||||
<Text>Neutral 2 container</Text>
|
||||
<Flex gap="2" mt="2">
|
||||
<Button variant="secondary">Default (inherits 1)</Button>
|
||||
<Button variant="secondary" onSurface="auto">
|
||||
Auto (inherits 1)
|
||||
</Button>
|
||||
<Button variant="secondary" onSurface="2">
|
||||
Explicit 2
|
||||
</Button>
|
||||
<Button variant="secondary">Auto (neutral-3)</Button>
|
||||
<Button variant="tertiary">Auto (neutral-3)</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box surface="2" p="4">
|
||||
<Text>Surface 2 container</Text>
|
||||
<Box bg="neutral-3" p="4">
|
||||
<Text>Neutral 3 container</Text>
|
||||
<Flex gap="2" mt="2">
|
||||
<Button variant="secondary">Default (inherits 2)</Button>
|
||||
<Button variant="secondary" onSurface="auto">
|
||||
Auto (inherits 2)
|
||||
</Button>
|
||||
<Button variant="secondary" onSurface="3">
|
||||
Explicit 3
|
||||
</Button>
|
||||
<Button variant="secondary">Auto (neutral-4)</Button>
|
||||
<Button variant="tertiary">Auto (neutral-4)</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
@@ -29,7 +29,7 @@ export const ButtonDefinition = defineComponent<ButtonOwnProps>()({
|
||||
content: 'bui-ButtonContent',
|
||||
spinner: 'bui-ButtonSpinner',
|
||||
},
|
||||
surface: 'leaf',
|
||||
bg: 'consumer',
|
||||
propDefs: {
|
||||
size: { dataAttribute: true, default: 'small' },
|
||||
variant: { dataAttribute: true, default: 'primary' },
|
||||
@@ -37,7 +37,6 @@ export const ButtonDefinition = defineComponent<ButtonOwnProps>()({
|
||||
loading: { dataAttribute: true },
|
||||
iconStart: {},
|
||||
iconEnd: {},
|
||||
onSurface: {},
|
||||
children: {},
|
||||
className: {},
|
||||
style: {},
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
import type { ReactElement, ReactNode, CSSProperties } from 'react';
|
||||
import type { ButtonProps as RAButtonProps } from 'react-aria-components';
|
||||
import type { LeafSurfaceProps, Responsive } from '../../types';
|
||||
import type { Responsive } from '../../types';
|
||||
|
||||
/** @public */
|
||||
export type ButtonOwnProps = LeafSurfaceProps & {
|
||||
export type ButtonOwnProps = {
|
||||
size?: Responsive<'small' | 'medium'>;
|
||||
variant?: Responsive<'primary' | 'secondary' | 'tertiary'>;
|
||||
destructive?: boolean;
|
||||
|
||||
@@ -85,19 +85,19 @@
|
||||
--bg-active: var(--bui-bg-neutral-1-pressed);
|
||||
--fg: var(--bui-fg-primary);
|
||||
|
||||
&[data-on-surface='1'] {
|
||||
&[data-on-bg='neutral-1'] {
|
||||
--bg: var(--bui-bg-neutral-2);
|
||||
--bg-hover: var(--bui-bg-neutral-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-2-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='2'] {
|
||||
&[data-on-bg='neutral-2'] {
|
||||
--bg: var(--bui-bg-neutral-3);
|
||||
--bg-hover: var(--bui-bg-neutral-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-3-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='3'] {
|
||||
&[data-on-bg='neutral-3'] {
|
||||
--bg: var(--bui-bg-neutral-4);
|
||||
--bg-hover: var(--bui-bg-neutral-4-hover);
|
||||
--bg-active: var(--bui-bg-neutral-4-pressed);
|
||||
@@ -122,17 +122,17 @@
|
||||
--bg-active: var(--bui-bg-neutral-1-pressed);
|
||||
--fg: var(--bui-fg-primary);
|
||||
|
||||
&[data-on-surface='1'] {
|
||||
&[data-on-bg='neutral-1'] {
|
||||
--bg-hover: var(--bui-bg-neutral-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-2-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='2'] {
|
||||
&[data-on-bg='neutral-2'] {
|
||||
--bg-hover: var(--bui-bg-neutral-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-3-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='3'] {
|
||||
&[data-on-bg='neutral-3'] {
|
||||
--bg-hover: var(--bui-bg-neutral-4-hover);
|
||||
--bg-active: var(--bui-bg-neutral-4-pressed);
|
||||
}
|
||||
|
||||
@@ -29,13 +29,12 @@ export const ButtonIconDefinition = defineComponent<ButtonIconOwnProps>()({
|
||||
content: 'bui-ButtonIconContent',
|
||||
spinner: 'bui-ButtonIconSpinner',
|
||||
},
|
||||
surface: 'leaf',
|
||||
bg: 'consumer',
|
||||
propDefs: {
|
||||
size: { dataAttribute: true, default: 'small' },
|
||||
variant: { dataAttribute: true, default: 'primary' },
|
||||
loading: { dataAttribute: true },
|
||||
icon: {},
|
||||
onSurface: {},
|
||||
className: {},
|
||||
style: {},
|
||||
},
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
import type { ReactElement, CSSProperties } from 'react';
|
||||
import type { ButtonProps as RAButtonProps } from 'react-aria-components';
|
||||
import type { LeafSurfaceProps, Responsive } from '../../types';
|
||||
import type { Responsive } from '../../types';
|
||||
|
||||
/** @public */
|
||||
export type ButtonIconOwnProps = LeafSurfaceProps & {
|
||||
export type ButtonIconOwnProps = {
|
||||
size?: Responsive<'small' | 'medium'>;
|
||||
variant?: Responsive<'primary' | 'secondary' | 'tertiary'>;
|
||||
icon?: ReactElement;
|
||||
|
||||
@@ -79,19 +79,19 @@
|
||||
--bg-active: var(--bui-bg-neutral-1-pressed);
|
||||
--fg: var(--bui-fg-primary);
|
||||
|
||||
&[data-on-surface='1'] {
|
||||
&[data-on-bg='neutral-1'] {
|
||||
--bg: var(--bui-bg-neutral-2);
|
||||
--bg-hover: var(--bui-bg-neutral-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-2-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='2'] {
|
||||
&[data-on-bg='neutral-2'] {
|
||||
--bg: var(--bui-bg-neutral-3);
|
||||
--bg-hover: var(--bui-bg-neutral-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-3-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='3'] {
|
||||
&[data-on-bg='neutral-3'] {
|
||||
--bg: var(--bui-bg-neutral-4);
|
||||
--bg-hover: var(--bui-bg-neutral-4-hover);
|
||||
--bg-active: var(--bui-bg-neutral-4-pressed);
|
||||
@@ -115,17 +115,17 @@
|
||||
--bg-active: var(--bui-bg-neutral-1-pressed);
|
||||
--fg: var(--bui-fg-primary);
|
||||
|
||||
&[data-on-surface='1'] {
|
||||
&[data-on-bg='neutral-1'] {
|
||||
--bg-hover: var(--bui-bg-neutral-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-2-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='2'] {
|
||||
&[data-on-bg='neutral-2'] {
|
||||
--bg-hover: var(--bui-bg-neutral-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-3-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='3'] {
|
||||
&[data-on-bg='neutral-3'] {
|
||||
--bg-hover: var(--bui-bg-neutral-4-hover);
|
||||
--bg-active: var(--bui-bg-neutral-4-pressed);
|
||||
}
|
||||
|
||||
@@ -28,13 +28,12 @@ export const ButtonLinkDefinition = defineComponent<ButtonLinkOwnProps>()({
|
||||
root: 'bui-ButtonLink',
|
||||
content: 'bui-ButtonLinkContent',
|
||||
},
|
||||
surface: 'leaf',
|
||||
bg: 'consumer',
|
||||
propDefs: {
|
||||
size: { dataAttribute: true, default: 'small' },
|
||||
variant: { dataAttribute: true, default: 'primary' },
|
||||
iconStart: {},
|
||||
iconEnd: {},
|
||||
onSurface: {},
|
||||
children: {},
|
||||
className: {},
|
||||
style: {},
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
import type { ReactElement, ReactNode, CSSProperties } from 'react';
|
||||
import type { LinkProps as RALinkProps } from 'react-aria-components';
|
||||
import type { LeafSurfaceProps, Responsive } from '../../types';
|
||||
import type { Responsive } from '../../types';
|
||||
|
||||
/** @public */
|
||||
export type ButtonLinkOwnProps = LeafSurfaceProps & {
|
||||
export type ButtonLinkOwnProps = {
|
||||
size?: Responsive<'small' | 'medium'>;
|
||||
variant?: Responsive<'primary' | 'secondary' | 'tertiary'>;
|
||||
iconStart?: ReactElement;
|
||||
|
||||
@@ -21,11 +21,9 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--bui-space-3);
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
border-radius: var(--bui-radius-3);
|
||||
padding-block: var(--bui-space-3);
|
||||
color: var(--bui-fg-primary);
|
||||
border: 1px solid var(--bui-border);
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
import preview from '../../../../../.storybook/preview';
|
||||
import { Card, CardHeader, CardBody, CardFooter } from './Card';
|
||||
import { Text } from '../..';
|
||||
import { Flex } from '../Flex';
|
||||
import { Box } from '../Box';
|
||||
import { Button } from '../Button';
|
||||
|
||||
const meta = preview.meta({
|
||||
title: 'Backstage UI/Card',
|
||||
@@ -124,3 +127,119 @@ export const WithListRow = meta.story({
|
||||
</Card>
|
||||
),
|
||||
});
|
||||
|
||||
export const Backgrounds = meta.story({
|
||||
render: args => (
|
||||
<Flex align="start" style={{ flexWrap: 'wrap' }} gap="4">
|
||||
<Card {...args} style={{ width: '200px' }}>
|
||||
<CardHeader>No parent</CardHeader>
|
||||
<CardBody>Defaults to neutral-1</CardBody>
|
||||
</Card>
|
||||
<Box bg="neutral-1" p="4" style={{ borderRadius: '8px' }}>
|
||||
<Card {...args} style={{ width: '200px' }}>
|
||||
<CardHeader>On neutral-1</CardHeader>
|
||||
<CardBody>Auto-increments to neutral-2</CardBody>
|
||||
</Card>
|
||||
</Box>
|
||||
<Box bg="neutral-2" p="4" style={{ borderRadius: '8px' }}>
|
||||
<Card {...args} style={{ width: '200px' }}>
|
||||
<CardHeader>On neutral-2</CardHeader>
|
||||
<CardBody>Auto-increments to neutral-3</CardBody>
|
||||
</Card>
|
||||
</Box>
|
||||
<Box bg="neutral-3" p="4" style={{ borderRadius: '8px' }}>
|
||||
<Card {...args} style={{ width: '200px' }}>
|
||||
<CardHeader>On neutral-3</CardHeader>
|
||||
<CardBody>Steps up to neutral-4</CardBody>
|
||||
</Card>
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const BgNested = meta.story({
|
||||
render: args => (
|
||||
<Flex direction="column">
|
||||
<Box style={{ maxWidth: '600px' }} mb="4">
|
||||
Nested cards auto-increment their neutral level. Buttons inherit the
|
||||
parent card's bg via data-on-bg.
|
||||
</Box>
|
||||
<Card {...args} style={{ width: '500px' }}>
|
||||
<CardHeader>Card (visual: neutral-1, provides: neutral-1)</CardHeader>
|
||||
<CardBody>
|
||||
<Button variant="secondary">Button (on neutral-1)</Button>
|
||||
<Card {...args} style={{ marginTop: '16px' }}>
|
||||
<CardHeader>
|
||||
Card (visual: neutral-2, provides: neutral-2)
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Button variant="secondary">Button (on neutral-2)</Button>
|
||||
<Card {...args} style={{ marginTop: '16px' }}>
|
||||
<CardHeader>
|
||||
Card (visual: neutral-4, provides: neutral-3)
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Button variant="secondary">Button (on neutral-3)</Button>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const BgOnProviders = meta.story({
|
||||
render: args => (
|
||||
<Flex align="start" style={{ flexWrap: 'wrap' }} gap="4">
|
||||
<Card {...args} style={{ width: '200px' }}>
|
||||
<CardHeader>No provider</CardHeader>
|
||||
<CardBody>Card defaults to neutral-1</CardBody>
|
||||
</Card>
|
||||
<Box bg="neutral-1" p="4" style={{ borderRadius: '8px' }}>
|
||||
<Card {...args} style={{ width: '200px' }}>
|
||||
<CardHeader>On neutral-1</CardHeader>
|
||||
<CardBody>Card auto-increments to neutral-2</CardBody>
|
||||
</Card>
|
||||
</Box>
|
||||
<Box bg="neutral-2" p="4" style={{ borderRadius: '8px' }}>
|
||||
<Card {...args} style={{ width: '200px' }}>
|
||||
<CardHeader>On neutral-2</CardHeader>
|
||||
<CardBody>Card auto-increments to neutral-3</CardBody>
|
||||
</Card>
|
||||
</Box>
|
||||
<Box bg="neutral-3" p="4" style={{ borderRadius: '8px' }}>
|
||||
<Card {...args} style={{ width: '200px' }}>
|
||||
<CardHeader>On neutral-3</CardHeader>
|
||||
<CardBody>Card visually at neutral-4</CardBody>
|
||||
</Card>
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const CustomCardWithBox = meta.story({
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<Box style={{ maxWidth: '600px' }}>
|
||||
A custom card built with Box. Use Box with an explicit bg prop to create
|
||||
a card-like container that participates in the bg system as a provider.
|
||||
</Box>
|
||||
<Box
|
||||
bg="neutral-auto"
|
||||
p="4"
|
||||
style={{ borderRadius: '8px', width: '300px' }}
|
||||
>
|
||||
<Button variant="secondary" style={{ marginTop: '8px' }}>
|
||||
Button (on neutral-1)
|
||||
</Button>
|
||||
</Box>
|
||||
<Card style={{ width: '300px' }}>
|
||||
<CardHeader>Header</CardHeader>
|
||||
<CardBody>Body</CardBody>
|
||||
<CardFooter>Footer</CardFooter>
|
||||
</Card>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -15,16 +15,20 @@
|
||||
*/
|
||||
|
||||
import { forwardRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { CardDefinition } from './definition';
|
||||
import { useDefinition } from '../../hooks/useDefinition';
|
||||
import {
|
||||
CardDefinition,
|
||||
CardHeaderDefinition,
|
||||
CardBodyDefinition,
|
||||
CardFooterDefinition,
|
||||
} from './definition';
|
||||
import type {
|
||||
CardProps,
|
||||
CardHeaderProps,
|
||||
CardBodyProps,
|
||||
CardFooterProps,
|
||||
} from './types';
|
||||
import styles from './Card.module.css';
|
||||
import { Box } from '../Box/Box';
|
||||
|
||||
/**
|
||||
* Card component.
|
||||
@@ -32,18 +36,27 @@ import styles from './Card.module.css';
|
||||
* @public
|
||||
*/
|
||||
export const Card = forwardRef<HTMLDivElement, CardProps>((props, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles(CardDefinition, props);
|
||||
const { className, ...rest } = cleanedProps;
|
||||
const { ownProps, restProps, dataAttributes } = useDefinition(
|
||||
CardDefinition,
|
||||
props,
|
||||
);
|
||||
const { classes, children } = ownProps;
|
||||
|
||||
return (
|
||||
<div
|
||||
<Box
|
||||
bg="neutral-auto"
|
||||
ref={ref}
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
{...rest}
|
||||
/>
|
||||
className={classes.root}
|
||||
{...dataAttributes}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
Card.displayName = 'Card';
|
||||
|
||||
/**
|
||||
* CardHeader component.
|
||||
*
|
||||
@@ -51,23 +64,19 @@ export const Card = forwardRef<HTMLDivElement, CardProps>((props, ref) => {
|
||||
*/
|
||||
export const CardHeader = forwardRef<HTMLDivElement, CardHeaderProps>(
|
||||
(props, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles(CardDefinition, props);
|
||||
const { className, ...rest } = cleanedProps;
|
||||
const { ownProps, restProps } = useDefinition(CardHeaderDefinition, props);
|
||||
const { classes, children } = ownProps;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
classNames.header,
|
||||
styles[classNames.header],
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
<div ref={ref} className={classes.root} {...restProps}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
CardHeader.displayName = 'CardHeader';
|
||||
|
||||
/**
|
||||
* CardBody component.
|
||||
*
|
||||
@@ -75,19 +84,19 @@ export const CardHeader = forwardRef<HTMLDivElement, CardHeaderProps>(
|
||||
*/
|
||||
export const CardBody = forwardRef<HTMLDivElement, CardBodyProps>(
|
||||
(props, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles(CardDefinition, props);
|
||||
const { className, ...rest } = cleanedProps;
|
||||
const { ownProps, restProps } = useDefinition(CardBodyDefinition, props);
|
||||
const { classes, children } = ownProps;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(classNames.body, styles[classNames.body], className)}
|
||||
{...rest}
|
||||
/>
|
||||
<div ref={ref} className={classes.root} {...restProps}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
CardBody.displayName = 'CardBody';
|
||||
|
||||
/**
|
||||
* CardFooter component.
|
||||
*
|
||||
@@ -95,19 +104,15 @@ export const CardBody = forwardRef<HTMLDivElement, CardBodyProps>(
|
||||
*/
|
||||
export const CardFooter = forwardRef<HTMLDivElement, CardFooterProps>(
|
||||
(props, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles(CardDefinition, props);
|
||||
const { className, ...rest } = cleanedProps;
|
||||
const { ownProps, restProps } = useDefinition(CardFooterDefinition, props);
|
||||
const { classes, children } = ownProps;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
classNames.footer,
|
||||
styles[classNames.footer],
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
<div ref={ref} className={classes.root} {...restProps}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
CardFooter.displayName = 'CardFooter';
|
||||
|
||||
@@ -14,17 +14,71 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ComponentDefinition } from '../../types';
|
||||
import { defineComponent } from '../../hooks/useDefinition';
|
||||
import type {
|
||||
CardOwnProps,
|
||||
CardHeaderOwnProps,
|
||||
CardBodyOwnProps,
|
||||
CardFooterOwnProps,
|
||||
} from './types';
|
||||
import styles from './Card.module.css';
|
||||
|
||||
/**
|
||||
* Component definition for Card
|
||||
* @public
|
||||
*/
|
||||
export const CardDefinition = {
|
||||
export const CardDefinition = defineComponent<CardOwnProps>()({
|
||||
styles,
|
||||
classNames: {
|
||||
root: 'bui-Card',
|
||||
header: 'bui-CardHeader',
|
||||
body: 'bui-CardBody',
|
||||
footer: 'bui-CardFooter',
|
||||
},
|
||||
} as const satisfies ComponentDefinition;
|
||||
propDefs: {
|
||||
children: {},
|
||||
className: {},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Component definition for CardHeader
|
||||
* @public
|
||||
*/
|
||||
export const CardHeaderDefinition = defineComponent<CardHeaderOwnProps>()({
|
||||
styles,
|
||||
classNames: {
|
||||
root: 'bui-CardHeader',
|
||||
},
|
||||
propDefs: {
|
||||
children: {},
|
||||
className: {},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Component definition for CardBody
|
||||
* @public
|
||||
*/
|
||||
export const CardBodyDefinition = defineComponent<CardBodyOwnProps>()({
|
||||
styles,
|
||||
classNames: {
|
||||
root: 'bui-CardBody',
|
||||
},
|
||||
propDefs: {
|
||||
children: {},
|
||||
className: {},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Component definition for CardFooter
|
||||
* @public
|
||||
*/
|
||||
export const CardFooterDefinition = defineComponent<CardFooterOwnProps>()({
|
||||
styles,
|
||||
classNames: {
|
||||
root: 'bui-CardFooter',
|
||||
},
|
||||
propDefs: {
|
||||
children: {},
|
||||
className: {},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,11 +15,10 @@
|
||||
*/
|
||||
|
||||
export { Card, CardHeader, CardBody, CardFooter } from './Card';
|
||||
export { CardDefinition } from './definition';
|
||||
|
||||
export type {
|
||||
CardProps,
|
||||
CardHeaderProps,
|
||||
CardBodyProps,
|
||||
CardFooterProps,
|
||||
} from './types';
|
||||
export {
|
||||
CardDefinition,
|
||||
CardHeaderDefinition,
|
||||
CardBodyDefinition,
|
||||
CardFooterDefinition,
|
||||
} from './definition';
|
||||
export type * from './types';
|
||||
|
||||
@@ -14,38 +14,64 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
/** @public */
|
||||
export type CardOwnProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for the Card component.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export interface CardProps
|
||||
extends CardOwnProps,
|
||||
React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
/** @public */
|
||||
export type CardHeaderOwnProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for the CardHeader component.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export interface CardHeaderProps
|
||||
extends CardHeaderOwnProps,
|
||||
React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
/** @public */
|
||||
export type CardBodyOwnProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for the CardBody component.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface CardBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export interface CardBodyProps
|
||||
extends CardBodyOwnProps,
|
||||
React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
/** @public */
|
||||
export type CardFooterOwnProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for the CardFooter component.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface CardFooterProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export interface CardFooterProps
|
||||
extends CardFooterOwnProps,
|
||||
React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
@@ -24,31 +24,27 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bui-Flex[data-surface='0'] {
|
||||
background-color: var(--bui-bg-neutral-0);
|
||||
}
|
||||
|
||||
.bui-Flex[data-surface='1'] {
|
||||
.bui-Flex[data-bg='neutral-1'] {
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
}
|
||||
|
||||
.bui-Flex[data-surface='2'] {
|
||||
.bui-Flex[data-bg='neutral-2'] {
|
||||
background-color: var(--bui-bg-neutral-2);
|
||||
}
|
||||
|
||||
.bui-Flex[data-surface='3'] {
|
||||
.bui-Flex[data-bg='neutral-3'] {
|
||||
background-color: var(--bui-bg-neutral-3);
|
||||
}
|
||||
|
||||
.bui-Flex[data-surface='danger'] {
|
||||
.bui-Flex[data-bg='danger'] {
|
||||
background-color: var(--bui-bg-danger);
|
||||
}
|
||||
|
||||
.bui-Flex[data-surface='warning'] {
|
||||
.bui-Flex[data-bg='warning'] {
|
||||
background-color: var(--bui-bg-warning);
|
||||
}
|
||||
|
||||
.bui-Flex[data-surface='success'] {
|
||||
.bui-Flex[data-bg='success'] {
|
||||
background-color: var(--bui-bg-success);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ export const WithTextTruncate = meta.story({
|
||||
),
|
||||
});
|
||||
|
||||
export const Surfaces = meta.story({
|
||||
export const Backgrounds = meta.story({
|
||||
args: {
|
||||
px: '6',
|
||||
py: '4',
|
||||
@@ -252,54 +252,46 @@ export const Surfaces = meta.story({
|
||||
render: args => (
|
||||
<Flex align="center" style={{ flexWrap: 'wrap' }}>
|
||||
<Flex {...args}>Default</Flex>
|
||||
<Flex surface="0" {...args}>
|
||||
Surface 0
|
||||
<Flex bg="neutral-1" {...args}>
|
||||
Neutral 1
|
||||
</Flex>
|
||||
<Flex surface="1" {...args}>
|
||||
Surface 1
|
||||
<Flex bg="neutral-2" {...args}>
|
||||
Neutral 2
|
||||
</Flex>
|
||||
<Flex surface="2" {...args}>
|
||||
Surface 2
|
||||
<Flex bg="neutral-3" {...args}>
|
||||
Neutral 3
|
||||
</Flex>
|
||||
<Flex surface="3" {...args}>
|
||||
Surface 3
|
||||
<Flex bg={{ initial: 'neutral-1', sm: 'neutral-2' }} {...args}>
|
||||
Responsive Bg
|
||||
</Flex>
|
||||
<Flex surface={{ initial: '0', sm: '1' }} {...args}>
|
||||
Responsive Surface
|
||||
<Flex bg="danger" {...args}>
|
||||
Danger
|
||||
</Flex>
|
||||
<Flex surface="danger" {...args}>
|
||||
Surface Danger
|
||||
<Flex bg="warning" {...args}>
|
||||
Warning
|
||||
</Flex>
|
||||
<Flex surface="warning" {...args}>
|
||||
Surface Warning
|
||||
</Flex>
|
||||
<Flex surface="success" {...args}>
|
||||
Surface Success
|
||||
<Flex bg="success" {...args}>
|
||||
Success
|
||||
</Flex>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const SurfacesAutoIncrement = meta.story({
|
||||
export const BgNeutralAuto = meta.story({
|
||||
args: { px: '6', py: '4', gap: '4' },
|
||||
render: args => (
|
||||
<Flex direction="column">
|
||||
<div style={{ maxWidth: '600px', marginBottom: '16px' }}>
|
||||
Using surface="auto" automatically increments from the parent surface.
|
||||
This allows components to be reusable without hardcoding surface levels.
|
||||
Using bg="neutral-auto" on Flex auto-increments from the parent context.
|
||||
The first Flex defaults to neutral-1 (no parent), then each nested Flex
|
||||
increments by one, capping at neutral-3.
|
||||
</div>
|
||||
<Flex {...args} surface="0" direction="column">
|
||||
<div>Surface 0 (explicit)</div>
|
||||
<Flex {...args} surface="auto" direction="column">
|
||||
<div>Surface auto (becomes 1)</div>
|
||||
<Flex {...args} surface="auto" direction="column">
|
||||
<div>Surface auto (becomes 2)</div>
|
||||
<Flex {...args} surface="auto" direction="column">
|
||||
<div>Surface auto (becomes 3)</div>
|
||||
<Flex {...args} surface="auto" direction="column">
|
||||
<div>Surface auto (stays 3 - capped)</div>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex {...args} bg="neutral-auto" direction="column">
|
||||
<div>Neutral 1 (auto, no parent)</div>
|
||||
<Flex {...args} bg="neutral-auto" direction="column">
|
||||
<div>Neutral 2 (auto-incremented)</div>
|
||||
<Flex {...args} bg="neutral-auto" direction="column">
|
||||
<div>Neutral 3 (auto-incremented, capped)</div>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -20,24 +20,20 @@ import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { FlexDefinition } from './definition';
|
||||
import styles from './Flex.module.css';
|
||||
import { SurfaceProvider, useSurface } from '../../hooks/useSurface';
|
||||
import { BgProvider, useBgProvider } from '../../hooks/useBg';
|
||||
|
||||
/** @public */
|
||||
export const Flex = forwardRef<HTMLDivElement, FlexProps>((props, ref) => {
|
||||
// Resolve the surface this Flex creates for its children
|
||||
// Using 'surface' parameter = container behavior (auto increments)
|
||||
const { surface: resolvedSurface } = useSurface({
|
||||
surface: props.surface,
|
||||
});
|
||||
const { bg: resolvedBg } = useBgProvider(props.bg);
|
||||
|
||||
const { classNames, dataAttributes, utilityClasses, style, cleanedProps } =
|
||||
useStyles(FlexDefinition, {
|
||||
gap: '4',
|
||||
...props,
|
||||
surface: resolvedSurface, // Use resolved surface for data attribute
|
||||
bg: resolvedBg, // Use resolved bg for data attribute
|
||||
});
|
||||
|
||||
const { className, surface, ...rest } = cleanedProps;
|
||||
const { className, bg, ...rest } = cleanedProps;
|
||||
|
||||
const content = (
|
||||
<div
|
||||
@@ -54,8 +50,8 @@ export const Flex = forwardRef<HTMLDivElement, FlexProps>((props, ref) => {
|
||||
/>
|
||||
);
|
||||
|
||||
return resolvedSurface ? (
|
||||
<SurfaceProvider surface={resolvedSurface}>{content}</SurfaceProvider>
|
||||
return resolvedBg ? (
|
||||
<BgProvider bg={resolvedBg}>{content}</BgProvider>
|
||||
) : (
|
||||
content
|
||||
);
|
||||
|
||||
@@ -45,6 +45,13 @@ export const FlexDefinition = {
|
||||
'direction',
|
||||
],
|
||||
dataAttributes: {
|
||||
surface: ['0', '1', '2', '3', 'danger', 'warning', 'success'] as const,
|
||||
bg: [
|
||||
'neutral-1',
|
||||
'neutral-2',
|
||||
'neutral-3',
|
||||
'danger',
|
||||
'warning',
|
||||
'success',
|
||||
] as const,
|
||||
},
|
||||
} as const satisfies ComponentDefinition;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Responsive, Space, SpaceProps, Surface } from '../../types';
|
||||
import type { Responsive, Space, SpaceProps, ProviderBg } from '../../types';
|
||||
|
||||
/** @public */
|
||||
export interface FlexProps extends SpaceProps {
|
||||
@@ -25,5 +25,5 @@ export interface FlexProps extends SpaceProps {
|
||||
direction?: Responsive<'row' | 'column' | 'row-reverse' | 'column-reverse'>;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
surface?: Responsive<Surface>;
|
||||
bg?: Responsive<ProviderBg>;
|
||||
}
|
||||
|
||||
@@ -21,38 +21,33 @@
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.bui-Grid[data-surface='0'],
|
||||
.bui-GridItem[data-surface='0'] {
|
||||
background-color: var(--bui-bg-neutral-0);
|
||||
}
|
||||
|
||||
.bui-Grid[data-surface='1'],
|
||||
.bui-GridItem[data-surface='1'] {
|
||||
.bui-Grid[data-bg='neutral-1'],
|
||||
.bui-GridItem[data-bg='neutral-1'] {
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
}
|
||||
|
||||
.bui-Grid[data-surface='2'],
|
||||
.bui-GridItem[data-surface='2'] {
|
||||
.bui-Grid[data-bg='neutral-2'],
|
||||
.bui-GridItem[data-bg='neutral-2'] {
|
||||
background-color: var(--bui-bg-neutral-2);
|
||||
}
|
||||
|
||||
.bui-Grid[data-surface='3'],
|
||||
.bui-GridItem[data-surface='3'] {
|
||||
.bui-Grid[data-bg='neutral-3'],
|
||||
.bui-GridItem[data-bg='neutral-3'] {
|
||||
background-color: var(--bui-bg-neutral-3);
|
||||
}
|
||||
|
||||
.bui-Grid[data-surface='danger'],
|
||||
.bui-GridItem[data-surface='danger'] {
|
||||
.bui-Grid[data-bg='danger'],
|
||||
.bui-GridItem[data-bg='danger'] {
|
||||
background-color: var(--bui-bg-danger);
|
||||
}
|
||||
|
||||
.bui-Grid[data-surface='warning'],
|
||||
.bui-GridItem[data-surface='warning'] {
|
||||
.bui-Grid[data-bg='warning'],
|
||||
.bui-GridItem[data-bg='warning'] {
|
||||
background-color: var(--bui-bg-warning);
|
||||
}
|
||||
|
||||
.bui-Grid[data-surface='success'],
|
||||
.bui-GridItem[data-surface='success'] {
|
||||
.bui-Grid[data-bg='success'],
|
||||
.bui-GridItem[data-bg='success'] {
|
||||
background-color: var(--bui-bg-success);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,78 +106,70 @@ export const RowAndColumns = meta.story({
|
||||
),
|
||||
});
|
||||
|
||||
export const Surfaces = meta.story({
|
||||
export const Backgrounds = meta.story({
|
||||
args: { px: '6', py: '4' },
|
||||
render: args => (
|
||||
<Flex direction="column">
|
||||
<Flex style={{ flexWrap: 'wrap' }}>
|
||||
<Grid.Root {...args} surface="0">
|
||||
Surface 0
|
||||
<Grid.Root {...args} bg="neutral-1">
|
||||
Neutral 1
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args} surface="1">
|
||||
Surface 1
|
||||
<Grid.Root {...args} bg="neutral-2">
|
||||
Neutral 2
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args} surface="2">
|
||||
Surface 2
|
||||
<Grid.Root {...args} bg="neutral-3">
|
||||
Neutral 3
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args} surface="3">
|
||||
Surface 3
|
||||
<Grid.Root {...args} bg={{ initial: 'neutral-1', sm: 'neutral-2' }}>
|
||||
Responsive Bg
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args} surface={{ initial: '0', sm: '1' }}>
|
||||
Responsive Surface
|
||||
<Grid.Root {...args} bg="danger">
|
||||
Danger
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args} surface="danger">
|
||||
Surface Danger
|
||||
<Grid.Root {...args} bg="warning">
|
||||
Warning
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args} surface="warning">
|
||||
Surface Warning
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args} surface="success">
|
||||
Surface Success
|
||||
<Grid.Root {...args} bg="success">
|
||||
Success
|
||||
</Grid.Root>
|
||||
</Flex>
|
||||
<Flex style={{ flexWrap: 'wrap' }}>
|
||||
<Grid.Root {...args}>
|
||||
<Grid.Item surface="0" style={{ padding: '4px' }}>
|
||||
Surface 0
|
||||
<Grid.Item bg="neutral-1" style={{ padding: '4px' }}>
|
||||
Neutral 1
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args}>
|
||||
<Grid.Item surface="1" style={{ padding: '4px' }}>
|
||||
Surface 1
|
||||
<Grid.Item bg="neutral-2" style={{ padding: '4px' }}>
|
||||
Neutral 2
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args}>
|
||||
<Grid.Item surface="2" style={{ padding: '4px' }}>
|
||||
Surface 2
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args}>
|
||||
<Grid.Item surface="3" style={{ padding: '4px' }}>
|
||||
Surface 3
|
||||
<Grid.Item bg="neutral-3" style={{ padding: '4px' }}>
|
||||
Neutral 3
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args}>
|
||||
<Grid.Item
|
||||
surface={{ initial: '0', sm: '1' }}
|
||||
bg={{ initial: 'neutral-1', sm: 'neutral-2' }}
|
||||
style={{ padding: '4px' }}
|
||||
>
|
||||
Responsive Surface
|
||||
Responsive Bg
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args}>
|
||||
<Grid.Item surface="danger" style={{ padding: '4px' }}>
|
||||
Surface Danger
|
||||
<Grid.Item bg="danger" style={{ padding: '4px' }}>
|
||||
Danger
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args}>
|
||||
<Grid.Item surface="warning" style={{ padding: '4px' }}>
|
||||
Surface Warning
|
||||
<Grid.Item bg="warning" style={{ padding: '4px' }}>
|
||||
Warning
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args}>
|
||||
<Grid.Item surface="success" style={{ padding: '4px' }}>
|
||||
Surface Success
|
||||
<Grid.Item bg="success" style={{ padding: '4px' }}>
|
||||
Success
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
</Flex>
|
||||
@@ -185,23 +177,21 @@ export const Surfaces = meta.story({
|
||||
),
|
||||
});
|
||||
|
||||
export const SurfacesAutoIncrement = meta.story({
|
||||
export const BgNeutralAuto = meta.story({
|
||||
args: { px: '6', py: '4', columns: '2', gap: '4' },
|
||||
render: args => (
|
||||
<Flex direction="column">
|
||||
<div style={{ maxWidth: '600px', marginBottom: '16px' }}>
|
||||
Using surface="auto" automatically increments from the parent surface.
|
||||
Each Grid.Item with auto will be one level above its Grid.Root parent.
|
||||
Grid is a layout primitive and is transparent to the bg system by
|
||||
default. Only an explicit bg prop establishes a new bg level. Nested
|
||||
grids without a bg prop inherit the parent context unchanged.
|
||||
</div>
|
||||
<Grid.Root {...args} surface="0">
|
||||
<Grid.Item>Surface 0 (Grid.Root)</Grid.Item>
|
||||
<Grid.Item surface="auto">Surface auto (becomes 1)</Grid.Item>
|
||||
<Grid.Root {...args} bg="neutral-1">
|
||||
<Grid.Item>Neutral 1 (Grid.Root)</Grid.Item>
|
||||
<Grid.Item>
|
||||
<Grid.Root {...args} surface="auto">
|
||||
<Grid.Item>Nested: Surface auto (becomes 1)</Grid.Item>
|
||||
<Grid.Item surface="auto">
|
||||
Nested: Surface auto (becomes 2)
|
||||
</Grid.Item>
|
||||
<Grid.Root {...args} bg="neutral-2">
|
||||
<Grid.Item>Nested: neutral-2 (explicit)</Grid.Item>
|
||||
<Grid.Item>Nested: neutral-2 (explicit)</Grid.Item>
|
||||
</Grid.Root>
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
|
||||
@@ -20,24 +20,20 @@ import type { GridItemProps, GridProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { GridDefinition, GridItemDefinition } from './definition';
|
||||
import styles from './Grid.module.css';
|
||||
import { SurfaceProvider, useSurface } from '../../hooks/useSurface';
|
||||
import { BgProvider, useBgProvider } from '../../hooks/useBg';
|
||||
|
||||
const GridRoot = forwardRef<HTMLDivElement, GridProps>((props, ref) => {
|
||||
// Resolve the surface this Grid creates for its children
|
||||
// Using 'surface' parameter = container behavior (auto increments)
|
||||
const { surface: resolvedSurface } = useSurface({
|
||||
surface: props.surface,
|
||||
});
|
||||
const { bg: resolvedBg } = useBgProvider(props.bg);
|
||||
|
||||
const { classNames, dataAttributes, utilityClasses, style, cleanedProps } =
|
||||
useStyles(GridDefinition, {
|
||||
columns: 'auto',
|
||||
gap: '4',
|
||||
...props,
|
||||
surface: resolvedSurface, // Use resolved surface for data attribute
|
||||
bg: resolvedBg, // Use resolved bg for data attribute
|
||||
});
|
||||
|
||||
const { className, surface, ...rest } = cleanedProps;
|
||||
const { className, bg, ...rest } = cleanedProps;
|
||||
|
||||
const content = (
|
||||
<div
|
||||
@@ -54,27 +50,23 @@ const GridRoot = forwardRef<HTMLDivElement, GridProps>((props, ref) => {
|
||||
/>
|
||||
);
|
||||
|
||||
return resolvedSurface ? (
|
||||
<SurfaceProvider surface={resolvedSurface}>{content}</SurfaceProvider>
|
||||
return resolvedBg ? (
|
||||
<BgProvider bg={resolvedBg}>{content}</BgProvider>
|
||||
) : (
|
||||
content
|
||||
);
|
||||
});
|
||||
|
||||
const GridItem = forwardRef<HTMLDivElement, GridItemProps>((props, ref) => {
|
||||
// Resolve the surface this GridItem creates for its children
|
||||
// Using 'surface' parameter = container behavior (auto increments)
|
||||
const { surface: resolvedSurface } = useSurface({
|
||||
surface: props.surface,
|
||||
});
|
||||
const { bg: resolvedBg } = useBgProvider(props.bg);
|
||||
|
||||
const { classNames, dataAttributes, utilityClasses, style, cleanedProps } =
|
||||
useStyles(GridItemDefinition, {
|
||||
...props,
|
||||
surface: resolvedSurface, // Use resolved surface for data attribute
|
||||
bg: resolvedBg, // Use resolved bg for data attribute
|
||||
});
|
||||
|
||||
const { className, surface, ...rest } = cleanedProps;
|
||||
const { className, bg, ...rest } = cleanedProps;
|
||||
|
||||
const content = (
|
||||
<div
|
||||
@@ -91,8 +83,8 @@ const GridItem = forwardRef<HTMLDivElement, GridItemProps>((props, ref) => {
|
||||
/>
|
||||
);
|
||||
|
||||
return resolvedSurface ? (
|
||||
<SurfaceProvider surface={resolvedSurface}>{content}</SurfaceProvider>
|
||||
return resolvedBg ? (
|
||||
<BgProvider bg={resolvedBg}>{content}</BgProvider>
|
||||
) : (
|
||||
content
|
||||
);
|
||||
|
||||
@@ -43,7 +43,14 @@ export const GridDefinition = {
|
||||
'py',
|
||||
],
|
||||
dataAttributes: {
|
||||
surface: ['0', '1', '2', '3', 'danger', 'warning', 'success'] as const,
|
||||
bg: [
|
||||
'neutral-1',
|
||||
'neutral-2',
|
||||
'neutral-3',
|
||||
'danger',
|
||||
'warning',
|
||||
'success',
|
||||
] as const,
|
||||
},
|
||||
} as const satisfies ComponentDefinition;
|
||||
|
||||
@@ -57,6 +64,13 @@ export const GridItemDefinition = {
|
||||
},
|
||||
utilityProps: ['colSpan', 'colEnd', 'colStart', 'rowSpan'],
|
||||
dataAttributes: {
|
||||
surface: ['0', '1', '2', '3', 'danger', 'warning', 'success'] as const,
|
||||
bg: [
|
||||
'neutral-1',
|
||||
'neutral-2',
|
||||
'neutral-3',
|
||||
'danger',
|
||||
'warning',
|
||||
'success',
|
||||
] as const,
|
||||
},
|
||||
} as const satisfies ComponentDefinition;
|
||||
|
||||
@@ -19,7 +19,7 @@ import type {
|
||||
SpaceProps,
|
||||
Responsive,
|
||||
Columns,
|
||||
Surface,
|
||||
ProviderBg,
|
||||
} from '../../types';
|
||||
|
||||
/** @public */
|
||||
@@ -29,7 +29,7 @@ export interface GridProps extends SpaceProps {
|
||||
columns?: Responsive<Columns>;
|
||||
gap?: Responsive<Space>;
|
||||
style?: React.CSSProperties;
|
||||
surface?: Responsive<Surface>;
|
||||
bg?: Responsive<ProviderBg>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
@@ -41,5 +41,5 @@ export interface GridItemProps {
|
||||
colStart?: Responsive<Columns>;
|
||||
rowSpan?: Responsive<Columns>;
|
||||
style?: React.CSSProperties;
|
||||
surface?: Responsive<Surface>;
|
||||
bg?: Responsive<ProviderBg>;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ export const Default = meta.story({
|
||||
},
|
||||
});
|
||||
|
||||
export const Surfaces = meta.story({
|
||||
export const Backgrounds = meta.story({
|
||||
args: {
|
||||
children: 'Toggle',
|
||||
},
|
||||
@@ -64,26 +64,20 @@ export const Surfaces = meta.story({
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 0</Text>
|
||||
<Flex align="center" surface="0" p="4">
|
||||
<Text>On Neutral 1</Text>
|
||||
<Flex align="center" bg="neutral-1" p="4">
|
||||
<ToggleButton>Toggle</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 1</Text>
|
||||
<Flex align="center" surface="1" p="4">
|
||||
<Text>On Neutral 2</Text>
|
||||
<Flex align="center" bg="neutral-2" p="4">
|
||||
<ToggleButton>Toggle</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 2</Text>
|
||||
<Flex align="center" surface="2" p="4">
|
||||
<ToggleButton>Toggle</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 3</Text>
|
||||
<Flex align="center" surface="3" p="4">
|
||||
<Text>On Neutral 3</Text>
|
||||
<Flex align="center" bg="neutral-3" p="4">
|
||||
<ToggleButton>Toggle</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -21,7 +21,6 @@ import type { ToggleButtonProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { ToggleButtonDefinition } from './definition';
|
||||
import styles from './ToggleButton.module.css';
|
||||
import { useSurface } from '../../hooks/useSurface';
|
||||
|
||||
/** @public */
|
||||
export const ToggleButton = forwardRef(
|
||||
@@ -34,17 +33,13 @@ export const ToggleButton = forwardRef(
|
||||
},
|
||||
);
|
||||
|
||||
const { children, className, iconStart, iconEnd, onSurface, ...rest } =
|
||||
cleanedProps;
|
||||
|
||||
const { surface } = useSurface({ onSurface });
|
||||
const { children, className, iconStart, iconEnd, ...rest } = cleanedProps;
|
||||
|
||||
return (
|
||||
<AriaToggleButton
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
ref={ref}
|
||||
{...dataAttributes}
|
||||
{...(typeof surface === 'string' ? { 'data-on-surface': surface } : {})}
|
||||
{...rest}
|
||||
>
|
||||
{renderProps => {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
import type { Breakpoint } from '../..';
|
||||
import type { ReactElement } from 'react';
|
||||
import type { ToggleButtonProps as AriaToggleButtonProps } from 'react-aria-components';
|
||||
import type { Responsive, Surface } from '../../types';
|
||||
|
||||
/**
|
||||
* Properties for {@link ToggleButton}
|
||||
@@ -28,6 +27,4 @@ export interface ToggleButtonProps extends AriaToggleButtonProps {
|
||||
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
|
||||
iconStart?: ReactElement;
|
||||
iconEnd?: ReactElement;
|
||||
/** Surface the toggle button is placed on. Defaults to context surface if available */
|
||||
onSurface?: Responsive<Surface>;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ export const MultipleSelection = meta.story({
|
||||
),
|
||||
});
|
||||
|
||||
export const Surfaces = meta.story({
|
||||
export const Backgrounds = meta.story({
|
||||
args: {
|
||||
selectionMode: 'single',
|
||||
defaultSelectedKeys: ['option1'],
|
||||
@@ -99,8 +99,8 @@ export const Surfaces = meta.story({
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 0</Text>
|
||||
<Flex align="center" surface="0" p="4" gap="4">
|
||||
<Text>On Neutral 1</Text>
|
||||
<Flex align="center" bg="neutral-1" p="4" gap="4">
|
||||
<ToggleButtonGroup
|
||||
selectionMode="single"
|
||||
defaultSelectedKeys={['option1']}
|
||||
@@ -112,8 +112,8 @@ export const Surfaces = meta.story({
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 1</Text>
|
||||
<Flex align="center" surface="1" p="4" gap="4">
|
||||
<Text>On Neutral 2</Text>
|
||||
<Flex align="center" bg="neutral-2" p="4" gap="4">
|
||||
<ToggleButtonGroup
|
||||
selectionMode="single"
|
||||
defaultSelectedKeys={['option1']}
|
||||
@@ -125,21 +125,8 @@ export const Surfaces = meta.story({
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 2</Text>
|
||||
<Flex align="center" surface="2" p="4" gap="4">
|
||||
<ToggleButtonGroup
|
||||
selectionMode="single"
|
||||
defaultSelectedKeys={['option1']}
|
||||
>
|
||||
<ToggleButton id="option1">Option 1</ToggleButton>
|
||||
<ToggleButton id="option2">Option 2</ToggleButton>
|
||||
<ToggleButton id="option3">Option 3</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 3</Text>
|
||||
<Flex align="center" surface="3" p="4" gap="4">
|
||||
<Text>On Neutral 3</Text>
|
||||
<Flex align="center" bg="neutral-3" p="4" gap="4">
|
||||
<ToggleButtonGroup
|
||||
selectionMode="single"
|
||||
defaultSelectedKeys={['option1']}
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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 { useContext, ReactNode } from 'react';
|
||||
import {
|
||||
createVersionedContext,
|
||||
createVersionedValueMap,
|
||||
} from '@backstage/version-bridge';
|
||||
import { ContainerBg, ProviderBg, Responsive } from '../types';
|
||||
import { useBreakpoint } from './useBreakpoint';
|
||||
import { resolveResponsiveValue } from './useDefinition/helpers';
|
||||
|
||||
/** @public */
|
||||
export interface BgContextValue {
|
||||
bg: ContainerBg | undefined;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface BgProviderProps {
|
||||
bg: ContainerBg;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const BgContext = createVersionedContext<{
|
||||
1: BgContextValue;
|
||||
}>('bg-context');
|
||||
|
||||
/**
|
||||
* Increments a neutral bg level by one, capping at 'neutral-3'.
|
||||
* Intent backgrounds (danger, warning, success) pass through unchanged.
|
||||
*
|
||||
* The 'neutral-4' level is reserved for consumer component CSS and is
|
||||
* never set on providers.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function incrementNeutralBg(bg: ContainerBg | undefined): ContainerBg {
|
||||
if (!bg) return 'neutral-1';
|
||||
if (bg === 'neutral-1') return 'neutral-2';
|
||||
if (bg === 'neutral-2') return 'neutral-3';
|
||||
if (bg === 'neutral-3') return 'neutral-3'; // capped at neutral-3
|
||||
// Intent values pass through unchanged
|
||||
return bg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider component that establishes the bg context for child components.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const BgProvider = ({ bg, children }: BgProviderProps) => {
|
||||
return (
|
||||
<BgContext.Provider value={createVersionedValueMap({ 1: { bg } })}>
|
||||
{children}
|
||||
</BgContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for consumer components (e.g. Button) to read the parent bg context.
|
||||
*
|
||||
* Returns the parent container's bg unchanged. The consumer component's CSS
|
||||
* handles the visual step-up (e.g. on a neutral-1 surface, the consumer
|
||||
* uses neutral-2 tokens via `data-on-bg`).
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function useBgConsumer(): BgContextValue {
|
||||
const value = useContext(BgContext)?.atVersion(1);
|
||||
return value ?? { bg: undefined };
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for provider components (e.g. Box, Card) to resolve and provide bg context.
|
||||
*
|
||||
* **Resolution rules:**
|
||||
*
|
||||
* - `bg` is `undefined` -- transparent, no context change, returns `{ bg: undefined }`.
|
||||
* This is the default for Box, Flex, and Grid (they do **not** auto-increment).
|
||||
* - `bg` is a `ContainerBg` value -- uses that value directly (e.g. `'neutral-1'`).
|
||||
* - `bg` is `'neutral-auto'` -- increments the neutral level from the parent context,
|
||||
* capping at `neutral-3`. Only components that explicitly pass `'neutral-auto'`
|
||||
* (e.g. Card) will auto-increment; it is never implicit.
|
||||
*
|
||||
* **Capping:**
|
||||
*
|
||||
* Provider components cap at `neutral-3`. The `neutral-4` level is **not** a valid
|
||||
* prop value -- it exists only in consumer component CSS (e.g. a Button on a
|
||||
* `neutral-3` surface renders with `neutral-4` tokens via `data-on-bg`).
|
||||
*
|
||||
* The caller is responsible for wrapping children with `BgProvider` when the
|
||||
* resolved bg is defined.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function useBgProvider(bg?: Responsive<ProviderBg>): BgContextValue {
|
||||
const { breakpoint } = useBreakpoint();
|
||||
const context = useBgConsumer();
|
||||
|
||||
if (bg === undefined) {
|
||||
return { bg: undefined };
|
||||
}
|
||||
|
||||
const resolved = resolveResponsiveValue(bg, breakpoint);
|
||||
|
||||
if (resolved === 'neutral-auto') {
|
||||
return { bg: incrementNeutralBg(context.bg) };
|
||||
}
|
||||
|
||||
return { bg: resolved };
|
||||
}
|
||||
@@ -14,13 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ComponentConfig, SurfacePropsConstraint } from './types';
|
||||
import type { ComponentConfig, BgPropsConstraint } from './types';
|
||||
|
||||
export function defineComponent<P extends Record<string, any>>() {
|
||||
return <
|
||||
const S extends Record<string, string>,
|
||||
const C extends ComponentConfig<P, S>,
|
||||
>(
|
||||
config: C & SurfacePropsConstraint<P, C['surface']>,
|
||||
config: C & BgPropsConstraint<P, C['bg']>,
|
||||
): C => config;
|
||||
}
|
||||
|
||||
@@ -36,25 +36,25 @@ export interface ComponentConfig<
|
||||
propDefs: { [K in keyof P]: PropDefConfig<P[K]> };
|
||||
// readonly for compatibility with const inference from factory
|
||||
utilityProps?: readonly UtilityPropKey[];
|
||||
surface?: 'container' | 'leaf';
|
||||
/**
|
||||
* How this component participates in the bg system.
|
||||
*
|
||||
* - `'provider'` — calls `useBgProvider`, sets `data-bg`, wraps children in `BgProvider`
|
||||
* - `'consumer'` — calls `useBgConsumer`, sets `data-on-bg`
|
||||
*/
|
||||
bg?: 'provider' | 'consumer';
|
||||
}
|
||||
|
||||
/**
|
||||
* Type constraint that validates surface props are present in the props type.
|
||||
* - If surface is 'leaf', P must include 'onSurface'
|
||||
* - If surface is 'container', P must include 'surface'
|
||||
* Type constraint that validates bg props are present in the props type.
|
||||
* - Provider components must include 'bg' in their props
|
||||
* - Consumer components don't need a bg prop
|
||||
*/
|
||||
export type SurfacePropsConstraint<P, Surface> = Surface extends 'leaf'
|
||||
? 'onSurface' extends keyof P
|
||||
export type BgPropsConstraint<P, Bg> = Bg extends 'provider'
|
||||
? 'bg' extends keyof P
|
||||
? {}
|
||||
: {
|
||||
__error: 'Leaf components must include onSurface in props type. Extend LeafProps.';
|
||||
}
|
||||
: Surface extends 'container'
|
||||
? 'surface' extends keyof P
|
||||
? {}
|
||||
: {
|
||||
__error: 'Container components must include surface in props type. Extend ContainerProps.';
|
||||
__error: 'Bg provider components must include bg in props type.';
|
||||
}
|
||||
: {};
|
||||
|
||||
@@ -81,12 +81,10 @@ type ResolvedOwnProps<
|
||||
[K in keyof PropDefs & keyof P]: ResolvePropType<P[K], PropDefs[K]>;
|
||||
};
|
||||
|
||||
type ChildrenProps<Surface extends 'container' | 'leaf' | undefined> =
|
||||
Surface extends 'container'
|
||||
? { surfaceChildren: ReactNode; children?: never }
|
||||
: Surface extends 'leaf'
|
||||
? { children: ReactNode; surfaceChildren?: never }
|
||||
: { children: ReactNode };
|
||||
type ChildrenProps<Bg extends 'provider' | 'consumer' | undefined> =
|
||||
Bg extends 'provider'
|
||||
? { childrenWithBgProvider: ReactNode; children?: never }
|
||||
: { children: ReactNode; childrenWithBgProvider?: never };
|
||||
|
||||
type DataAttributeKeys<PropDefs> = {
|
||||
[K in keyof PropDefs]: PropDefs[K] extends { dataAttribute: true }
|
||||
@@ -98,7 +96,7 @@ type DataAttributes<PropDefs> = {
|
||||
[K in DataAttributeKeys<PropDefs> as `data-${Lowercase<
|
||||
string & K
|
||||
>}`]?: string;
|
||||
} & { 'data-on-surface'?: string };
|
||||
} & { 'data-bg'?: string; 'data-on-bg'?: string };
|
||||
|
||||
export type UtilityKeys<D extends ComponentConfig<any, any>> =
|
||||
D['utilityProps'] extends ReadonlyArray<infer K extends string> ? K : never;
|
||||
@@ -127,7 +125,7 @@ export interface UseDefinitionResult<
|
||||
ownProps: {
|
||||
classes: Record<keyof D['classNames'], string>;
|
||||
} & ResolvedOwnProps<P, D['propDefs']> &
|
||||
ChildrenProps<D['surface']>;
|
||||
ChildrenProps<D['bg']>;
|
||||
|
||||
// Rest props excludes both propDefs keys AND utility prop keys
|
||||
restProps: keyof Omit<P, keyof D['propDefs'] | UtilityKeys<D>> extends never
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { ReactNode } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { useBreakpoint } from '../useBreakpoint';
|
||||
import { useSurface, SurfaceProvider, UseSurfaceOptions } from '../useSurface';
|
||||
import { useBgProvider, useBgConsumer, BgProvider } from '../useBg';
|
||||
import { resolveResponsiveValue, processUtilityProps } from './helpers';
|
||||
import type {
|
||||
ComponentConfig,
|
||||
@@ -36,14 +36,13 @@ export function useDefinition<
|
||||
): UseDefinitionResult<D, P> {
|
||||
const { breakpoint } = useBreakpoint();
|
||||
|
||||
const surfaceOptions: UseSurfaceOptions | undefined =
|
||||
definition.surface === 'container'
|
||||
? { surface: props.surface }
|
||||
: definition.surface === 'leaf'
|
||||
? { onSurface: props.onSurface }
|
||||
: undefined;
|
||||
// Provider: resolve bg and provide context for children
|
||||
const providerBg = useBgProvider(
|
||||
definition.bg === 'provider' ? props.bg : undefined,
|
||||
);
|
||||
|
||||
const { surface: resolvedSurface } = useSurface(surfaceOptions);
|
||||
// Consumer: read parent context bg
|
||||
const consumerBg = useBgConsumer();
|
||||
|
||||
const ownPropKeys = new Set(Object.keys(definition.propDefs));
|
||||
const utilityPropKeys = new Set(definition.utilityProps ?? []);
|
||||
@@ -70,6 +69,9 @@ export function useDefinition<
|
||||
if (finalValue !== undefined) {
|
||||
ownPropsResolved[key] = finalValue;
|
||||
|
||||
// Skip data-bg for bg prop when the provider path handles it
|
||||
if (key === 'bg' && definition.bg === 'provider') continue;
|
||||
|
||||
if ((config as any).dataAttribute) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
dataAttributes[`data-${key.toLowerCase()}`] = String(finalValue);
|
||||
@@ -77,16 +79,14 @@ export function useDefinition<
|
||||
}
|
||||
}
|
||||
|
||||
// Add data-on-surface for leaf components
|
||||
if (definition.surface === 'leaf' && resolvedSurface !== undefined) {
|
||||
// Handle responsive surface values - for data attributes, use the resolved string
|
||||
const surfaceValue =
|
||||
typeof resolvedSurface === 'object'
|
||||
? resolveResponsiveValue(resolvedSurface as any, breakpoint)
|
||||
: resolvedSurface;
|
||||
if (surfaceValue !== undefined) {
|
||||
dataAttributes['data-on-surface'] = String(surfaceValue);
|
||||
}
|
||||
// Provider: set data-bg from the resolved provider bg
|
||||
if (definition.bg === 'provider' && providerBg.bg !== undefined) {
|
||||
dataAttributes['data-bg'] = String(providerBg.bg);
|
||||
}
|
||||
|
||||
// Consumer: set data-on-bg from the parent context
|
||||
if (definition.bg === 'consumer' && consumerBg.bg !== undefined) {
|
||||
dataAttributes['data-on-bg'] = String(consumerBg.bg);
|
||||
}
|
||||
|
||||
const { utilityClasses, utilityStyle } = processUtilityProps<UtilityKeys<D>>(
|
||||
@@ -109,13 +109,11 @@ export function useDefinition<
|
||||
}
|
||||
|
||||
let children: ReactNode | undefined;
|
||||
let surfaceChildren: ReactNode | undefined;
|
||||
let childrenWithBgProvider: ReactNode | undefined;
|
||||
|
||||
if (definition.surface === 'container') {
|
||||
surfaceChildren = resolvedSurface ? (
|
||||
<SurfaceProvider surface={resolvedSurface}>
|
||||
{props.children}
|
||||
</SurfaceProvider>
|
||||
if (definition.bg === 'provider') {
|
||||
childrenWithBgProvider = providerBg.bg ? (
|
||||
<BgProvider bg={providerBg.bg}>{props.children}</BgProvider>
|
||||
) : (
|
||||
props.children
|
||||
);
|
||||
@@ -127,8 +125,8 @@ export function useDefinition<
|
||||
ownProps: {
|
||||
classes,
|
||||
...ownPropsResolved,
|
||||
...(definition.surface === 'container'
|
||||
? { surfaceChildren }
|
||||
...(definition.bg === 'provider'
|
||||
? { childrenWithBgProvider }
|
||||
: { children }),
|
||||
},
|
||||
restProps,
|
||||
|
||||
@@ -1,204 +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 { useContext, ReactNode } from 'react';
|
||||
import {
|
||||
createVersionedContext,
|
||||
createVersionedValueMap,
|
||||
} from '@backstage/version-bridge';
|
||||
import { Surface, Responsive } from '../types';
|
||||
|
||||
/** @public */
|
||||
export interface SurfaceContextValue {
|
||||
surface: Responsive<Surface> | undefined;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface SurfaceProviderProps {
|
||||
surface: Responsive<Surface>;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface UseSurfaceOptions {
|
||||
/**
|
||||
* The surface value this component CREATES for its children (container behavior).
|
||||
* When 'auto', increments from parent surface.
|
||||
*
|
||||
* Use this for components like Box, Flex, Grid that establish surface context.
|
||||
*/
|
||||
surface?: Responsive<Surface>;
|
||||
/**
|
||||
* The surface value this component is ON for styling (leaf behavior).
|
||||
* When 'auto', inherits from current surface.
|
||||
*
|
||||
* Use this for leaf components like Button that consume surface for styling.
|
||||
*/
|
||||
onSurface?: Responsive<Surface>;
|
||||
}
|
||||
|
||||
const SurfaceContext = createVersionedContext<{
|
||||
1: SurfaceContextValue;
|
||||
}>('surface-context');
|
||||
|
||||
/**
|
||||
* Increments a surface level by one, capping at '3'.
|
||||
* Intent surfaces (danger, warning, success) remain unchanged.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function incrementSurface(surface: Surface | undefined): Surface {
|
||||
if (!surface) return '0'; // no context = root level
|
||||
if (surface === '0') return '1';
|
||||
if (surface === '1') return '2';
|
||||
if (surface === '2' || surface === '3') return '3'; // cap at max
|
||||
// Intent surfaces remain unchanged
|
||||
if (surface === 'danger') return 'danger';
|
||||
if (surface === 'warning') return 'warning';
|
||||
if (surface === 'success') return 'success';
|
||||
// 'auto' should not appear here, but handle it defensively
|
||||
if (surface === 'auto') return '1';
|
||||
return surface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a surface value for containers (SurfaceProvider).
|
||||
* When 'auto' is used, increments from the parent surface.
|
||||
* For responsive surfaces (objects), returns them as-is without resolution.
|
||||
*
|
||||
* @param contextSurface - The surface from context
|
||||
* @param requestedSurface - The requested surface value (may be 'auto')
|
||||
* @returns The resolved surface value
|
||||
* @internal
|
||||
*/
|
||||
export function resolveSurfaceForProvider(
|
||||
contextSurface: Responsive<Surface> | undefined,
|
||||
requestedSurface: Responsive<Surface> | undefined,
|
||||
): Responsive<Surface> | undefined {
|
||||
if (!requestedSurface) {
|
||||
return contextSurface;
|
||||
}
|
||||
|
||||
// If requestedSurface is a responsive object (breakpoint-based), return as-is
|
||||
if (typeof requestedSurface === 'object') {
|
||||
return requestedSurface;
|
||||
}
|
||||
|
||||
// If contextSurface is a responsive object, we can't auto-increment from it
|
||||
// Return the requested surface as-is or default to '0' for auto
|
||||
if (typeof contextSurface === 'object') {
|
||||
if (requestedSurface === 'auto') {
|
||||
return '0'; // fallback to root when context is responsive
|
||||
}
|
||||
return requestedSurface;
|
||||
}
|
||||
|
||||
// For containers, 'auto' means increment to create a new elevated context
|
||||
if (requestedSurface === 'auto') {
|
||||
return incrementSurface(contextSurface);
|
||||
}
|
||||
|
||||
return requestedSurface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a surface value for leaf components (useSurface hook).
|
||||
* When 'auto' is used, inherits the current surface (doesn't increment).
|
||||
* For responsive surfaces (objects), returns them as-is without resolution.
|
||||
*
|
||||
* @param contextSurface - The surface from context
|
||||
* @param requestedSurface - The requested surface value (may be 'auto')
|
||||
* @returns The resolved surface value
|
||||
* @internal
|
||||
*/
|
||||
function resolveSurfaceForConsumer(
|
||||
contextSurface: Responsive<Surface> | undefined,
|
||||
requestedSurface: Responsive<Surface> | undefined,
|
||||
): Responsive<Surface> | undefined {
|
||||
if (!requestedSurface) {
|
||||
return contextSurface;
|
||||
}
|
||||
|
||||
// If requestedSurface is a responsive object (breakpoint-based), return as-is
|
||||
if (typeof requestedSurface === 'object') {
|
||||
return requestedSurface;
|
||||
}
|
||||
|
||||
// For leaf components, 'auto' means inherit the current surface
|
||||
if (requestedSurface === 'auto') {
|
||||
// If context is responsive, fallback to '0'
|
||||
if (typeof contextSurface === 'object') {
|
||||
return '0';
|
||||
}
|
||||
return contextSurface;
|
||||
}
|
||||
|
||||
return requestedSurface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider component that establishes the surface context for child components.
|
||||
* This allows components to adapt their styling based on their background surface.
|
||||
*
|
||||
* Note: The surface value should already be resolved before passing to this provider.
|
||||
* Container components should use useSurface with the surface parameter.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export const SurfaceProvider = ({
|
||||
surface,
|
||||
children,
|
||||
}: SurfaceProviderProps) => {
|
||||
return (
|
||||
<SurfaceContext.Provider
|
||||
value={createVersionedValueMap({ 1: { surface } })}
|
||||
>
|
||||
{children}
|
||||
</SurfaceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to access the current surface context.
|
||||
* Returns the current surface level, or undefined if no provider is present.
|
||||
*
|
||||
* The parameter name determines the behavior:
|
||||
* - `surface`: Container behavior - 'auto' increments from parent
|
||||
* - `onSurface`: Leaf behavior - 'auto' inherits from parent
|
||||
*
|
||||
* @param options - Optional configuration for surface resolution
|
||||
* @internal
|
||||
*/
|
||||
export const useSurface = (
|
||||
options?: UseSurfaceOptions,
|
||||
): SurfaceContextValue => {
|
||||
const value = useContext(SurfaceContext)?.atVersion(1);
|
||||
const context = value ?? { surface: undefined };
|
||||
|
||||
// Infer behavior from which parameter is provided
|
||||
// 'surface' = provider behavior (increment)
|
||||
// 'onSurface' = consumer behavior (inherit)
|
||||
const isProvider = options?.surface !== undefined;
|
||||
const requestedSurface = options?.surface ?? options?.onSurface;
|
||||
|
||||
const resolvedSurface = isProvider
|
||||
? resolveSurfaceForProvider(context.surface, requestedSurface)
|
||||
: resolveSurfaceForConsumer(context.surface, requestedSurface);
|
||||
|
||||
return {
|
||||
surface: resolvedSurface,
|
||||
};
|
||||
};
|
||||
@@ -64,3 +64,5 @@ export * from './types';
|
||||
|
||||
// Hooks
|
||||
export { useBreakpoint } from './hooks/useBreakpoint';
|
||||
export { useBgProvider, useBgConsumer, BgProvider } from './hooks/useBg';
|
||||
export type { BgContextValue, BgProviderProps } from './hooks/useBg';
|
||||
|
||||
+20
-19
@@ -182,29 +182,30 @@ export interface ComponentDefinition {
|
||||
}
|
||||
|
||||
/**
|
||||
* Surface type
|
||||
* Background type for the neutral bg system.
|
||||
*
|
||||
* Supports absolute levels ('0'-'3'), intent surfaces ('danger', 'warning', 'success'),
|
||||
* and 'auto' which increments from the parent surface context.
|
||||
* Supports neutral levels ('neutral-1' through 'neutral-3') and
|
||||
* intent backgrounds ('danger', 'warning', 'success').
|
||||
*
|
||||
* The 'neutral-4' level is not exposed as a prop value -- it is reserved
|
||||
* for leaf component CSS (e.g. Button on a 'neutral-3' surface).
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type Surface =
|
||||
| '0'
|
||||
| '1'
|
||||
| '2'
|
||||
| '3'
|
||||
export type ContainerBg =
|
||||
| 'neutral-1'
|
||||
| 'neutral-2'
|
||||
| 'neutral-3'
|
||||
| 'danger'
|
||||
| 'warning'
|
||||
| 'success'
|
||||
| 'auto';
|
||||
| 'success';
|
||||
|
||||
/** @public */
|
||||
export interface LeafSurfaceProps {
|
||||
onSurface?: Responsive<Surface>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface ContainerSurfaceProps {
|
||||
surface?: Responsive<Surface>;
|
||||
}
|
||||
/**
|
||||
* Background values accepted by provider components.
|
||||
*
|
||||
* Includes all `ContainerBg` values plus `'neutral-auto'` which
|
||||
* automatically increments the neutral level from the parent context.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ProviderBg = ContainerBg | 'neutral-auto';
|
||||
|
||||
Reference in New Issue
Block a user