From a27dc7795d8c6e27edf2515c1c788a458a0dce6e Mon Sep 17 00:00:00 2001 From: Charles de Dreuille Date: Wed, 25 Feb 2026 12:53:35 +0000 Subject: [PATCH] feat(ui): simplify neutral bg prop to single auto-incrementing 'neutral' value Signed-off-by: Charles de Dreuille --- .../Accordion/Accordion.stories.tsx | 64 ++++++++-------- .../ui/src/components/Accordion/definition.ts | 2 +- .../ui/src/components/Alert/Alert.stories.tsx | 26 ++++--- .../ui/src/components/Box/Box.stories.tsx | 27 ++++--- .../src/components/Button/Button.stories.tsx | 32 ++++---- .../ui/src/components/Card/Card.stories.tsx | 62 +++++++++------- packages/ui/src/components/Card/Card.tsx | 2 +- .../ui/src/components/Flex/Flex.stories.tsx | 39 +++++----- .../ui/src/components/Grid/Grid.stories.tsx | 73 ++++++++++--------- .../ToggleButton/ToggleButton.stories.tsx | 21 ++++-- .../ToggleButtonGroup.stories.tsx | 49 +++++++------ packages/ui/src/hooks/useBg.tsx | 10 +-- packages/ui/src/types.ts | 16 ++-- 13 files changed, 237 insertions(+), 186 deletions(-) diff --git a/packages/ui/src/components/Accordion/Accordion.stories.tsx b/packages/ui/src/components/Accordion/Accordion.stories.tsx index b88b547e08..5def4f57d3 100644 --- a/packages/ui/src/components/Accordion/Accordion.stories.tsx +++ b/packages/ui/src/components/Accordion/Accordion.stories.tsx @@ -195,7 +195,7 @@ export const AutoBg = meta.story({ - + Neutral 1 container @@ -210,35 +210,41 @@ export const AutoBg = meta.story({ - - Neutral 2 container - - - - - - - - - - - - + + + Neutral 2 container + + + + + + + + + + + + + - - Neutral 3 container - - - - - - - - - - - - + + + + Neutral 3 container + + + + + + + + + + + + + + ), diff --git a/packages/ui/src/components/Accordion/definition.ts b/packages/ui/src/components/Accordion/definition.ts index 69ef9be3a7..2e76f0cd77 100644 --- a/packages/ui/src/components/Accordion/definition.ts +++ b/packages/ui/src/components/Accordion/definition.ts @@ -34,7 +34,7 @@ export const AccordionDefinition = defineComponent()({ }, bg: 'provider', propDefs: { - bg: { dataAttribute: true, default: 'neutral-auto' }, + bg: { dataAttribute: true, default: 'neutral' }, children: {}, className: {}, }, diff --git a/packages/ui/src/components/Alert/Alert.stories.tsx b/packages/ui/src/components/Alert/Alert.stories.tsx index 28239becb9..589d55b7dd 100644 --- a/packages/ui/src/components/Alert/Alert.stories.tsx +++ b/packages/ui/src/components/Alert/Alert.stories.tsx @@ -297,7 +297,7 @@ export const OnDifferentBackgrounds = meta.story({ On Neutral 1 - + @@ -305,18 +305,24 @@ export const OnDifferentBackgrounds = meta.story({ On Neutral 2 - - - - + + + + + + On Neutral 3 - - - - + + + + + + + + ), @@ -337,7 +343,7 @@ export const Responsive = meta.story({ export const WithUtilityProps = meta.story({ render: () => ( - + ( Default - - Neutral 1 + + Neutral (level 1) - - Neutral 2 + + + Neutral (level 2) + - - Neutral 3 - - - Responsive Neutral + + + + Neutral (level 3) + + Danger @@ -381,11 +384,11 @@ export const BackgroundColors = meta.story({ export const NestedNeutralColors = meta.story({ args: { px: '6', py: '4', children: null }, render: args => ( - + - + - + diff --git a/packages/ui/src/components/Button/Button.stories.tsx b/packages/ui/src/components/Button/Button.stories.tsx index 2e2f94b4e1..5781daca6a 100644 --- a/packages/ui/src/components/Button/Button.stories.tsx +++ b/packages/ui/src/components/Button/Button.stories.tsx @@ -357,26 +357,32 @@ export const AutoBg = meta.story({ neutral level by 1. No prop is needed on the button -- it's fully automatic. - + Neutral 1 container - - Neutral 2 container - - - - + + + Neutral 2 container + + + + + - - Neutral 3 container - - - - + + + + Neutral 3 container + + + + + + ), diff --git a/packages/ui/src/components/Card/Card.stories.tsx b/packages/ui/src/components/Card/Card.stories.tsx index 3957b7beff..87671c4cf6 100644 --- a/packages/ui/src/components/Card/Card.stories.tsx +++ b/packages/ui/src/components/Card/Card.stories.tsx @@ -135,23 +135,29 @@ export const Backgrounds = meta.story({ No parent Defaults to neutral-1 - + On neutral-1 Auto-increments to neutral-2 - - - On neutral-2 - Auto-increments to neutral-3 - + + + + On neutral-2 + Auto-increments to neutral-3 + + - - - On neutral-3 - Steps up to neutral-4 - + + + + + On neutral-3 + Steps up to neutral-4 + + + ), @@ -197,23 +203,29 @@ export const BgOnProviders = meta.story({ No provider Card defaults to neutral-1 - + On neutral-1 Card auto-increments to neutral-2 - - - On neutral-2 - Card auto-increments to neutral-3 - + + + + On neutral-2 + Card auto-increments to neutral-3 + + - - - On neutral-3 - Card visually at neutral-4 - + + + + + On neutral-3 + Card visually at neutral-4 + + + ), @@ -226,11 +238,7 @@ export const CustomCardWithBox = meta.story({ 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. - + diff --git a/packages/ui/src/components/Card/Card.tsx b/packages/ui/src/components/Card/Card.tsx index e3e4781eac..5eb09f2edc 100644 --- a/packages/ui/src/components/Card/Card.tsx +++ b/packages/ui/src/components/Card/Card.tsx @@ -44,7 +44,7 @@ export const Card = forwardRef((props, ref) => { return ( ( Default - - Neutral 1 - - - Neutral 2 - - - Neutral 3 - - - Responsive Bg + + Neutral (level 1) + + + Neutral (level 2) + + + + + + Neutral (level 3) + + + Danger @@ -278,20 +281,20 @@ export const Backgrounds = meta.story({ ), }); -export const BgNeutralAuto = meta.story({ +export const BgNeutral = meta.story({ args: { px: '6', py: '4', gap: '4' }, render: args => (
- 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 + Using bg="neutral" 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.
- -
Neutral 1 (auto, no parent)
- + +
Neutral 1 (no parent)
+
Neutral 2 (auto-incremented)
- +
Neutral 3 (auto-incremented, capped)
diff --git a/packages/ui/src/components/Grid/Grid.stories.tsx b/packages/ui/src/components/Grid/Grid.stories.tsx index b3b6957359..f60517bc31 100644 --- a/packages/ui/src/components/Grid/Grid.stories.tsx +++ b/packages/ui/src/components/Grid/Grid.stories.tsx @@ -113,18 +113,21 @@ export const Backgrounds = meta.story({ render: args => ( - - Neutral 1 - - - Neutral 2 - - - Neutral 3 - - - Responsive Bg + + Neutral (level 1) + + + Neutral (level 2) + + + + + + Neutral (level 3) + + + Danger @@ -137,28 +140,26 @@ export const Backgrounds = meta.story({ - - Neutral 1 - - - - - Neutral 2 - - - - - Neutral 3 - - - - - Responsive Bg + + Neutral (level 1) + + + + Neutral (level 2) + + + + + + + + Neutral (level 3) + + + + Danger @@ -179,7 +180,7 @@ export const Backgrounds = meta.story({ ), }); -export const BgNeutralAuto = meta.story({ +export const BgNeutral = meta.story({ args: { px: '6', py: '4', columns: '2', gap: '4' }, render: args => ( @@ -188,12 +189,12 @@ export const BgNeutralAuto = meta.story({ default. Only an explicit bg prop establishes a new bg level. Nested grids without a bg prop inherit the parent context unchanged. - + Neutral 1 (Grid.Root) - - Nested: neutral-2 (explicit) - Nested: neutral-2 (explicit) + + Nested: neutral-2 (auto-incremented) + Nested: neutral-2 (auto-incremented) diff --git a/packages/ui/src/components/ToggleButton/ToggleButton.stories.tsx b/packages/ui/src/components/ToggleButton/ToggleButton.stories.tsx index a89bbe3862..876ff654b0 100644 --- a/packages/ui/src/components/ToggleButton/ToggleButton.stories.tsx +++ b/packages/ui/src/components/ToggleButton/ToggleButton.stories.tsx @@ -16,6 +16,7 @@ import preview from '../../../../../.storybook/preview'; import { ToggleButton } from './ToggleButton'; +import { Box } from '../Box'; import { Flex } from '../Flex'; import { Text } from '../Text'; import { useState } from 'react'; @@ -65,21 +66,27 @@ export const Backgrounds = meta.story({ On Neutral 1 - + Toggle On Neutral 2 - - Toggle - + + + Toggle + + On Neutral 3 - - Toggle - + + + + Toggle + + + ), diff --git a/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.stories.tsx b/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.stories.tsx index 617cd0c0fa..c98a2e18f1 100644 --- a/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.stories.tsx +++ b/packages/ui/src/components/ToggleButtonGroup/ToggleButtonGroup.stories.tsx @@ -17,6 +17,7 @@ import preview from '../../../../../.storybook/preview'; import { ToggleButtonGroup } from './ToggleButtonGroup'; import { ToggleButton } from '../ToggleButton/ToggleButton'; +import { Box } from '../Box'; import { Flex } from '../Flex'; import { Text } from '../Text'; import { useState } from 'react'; @@ -100,7 +101,7 @@ export const Backgrounds = meta.story({ On Neutral 1 - + On Neutral 2 - - - Option 1 - Option 2 - Option 3 - - + + + + Option 1 + Option 2 + Option 3 + + + On Neutral 3 - - - Option 1 - Option 2 - Option 3 - - + + + + + Option 1 + Option 2 + Option 3 + + + + ), diff --git a/packages/ui/src/hooks/useBg.tsx b/packages/ui/src/hooks/useBg.tsx index 6e298fedf9..ea9376b039 100644 --- a/packages/ui/src/hooks/useBg.tsx +++ b/packages/ui/src/hooks/useBg.tsx @@ -90,10 +90,10 @@ export function useBgConsumer(): BgContextValue { * * - `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. + * - `bg` is `'neutral'` -- increments the neutral level from the parent context, + * capping at `neutral-3`. The increment is always relative to the parent; it is + * not possible to pin a container to an explicit neutral level. + * - `bg` is `'danger'` | `'warning'` | `'success'` -- used as-is. * * **Capping:** * @@ -116,7 +116,7 @@ export function useBgProvider(bg?: Responsive): BgContextValue { const resolved = resolveResponsiveValue(bg, breakpoint); - if (resolved === 'neutral-auto') { + if (resolved === 'neutral') { return { bg: incrementNeutralBg(context.bg) }; } diff --git a/packages/ui/src/types.ts b/packages/ui/src/types.ts index d3dfd996aa..05ef25d5dc 100644 --- a/packages/ui/src/types.ts +++ b/packages/ui/src/types.ts @@ -182,7 +182,7 @@ export interface ComponentDefinition { } /** - * Background type for the neutral bg system. + * Resolved background level stored in context and applied as `data-bg` on DOM elements. * * Supports neutral levels ('neutral-1' through 'neutral-3') and * intent backgrounds ('danger', 'warning', 'success'). @@ -190,7 +190,9 @@ export interface ComponentDefinition { * 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 + * This type is internal. Use `ProviderBg` for component prop types. + * + * @internal */ export type ContainerBg = | 'neutral-1' @@ -201,11 +203,13 @@ export type ContainerBg = | 'success'; /** - * Background values accepted by provider components. + * Background values accepted by provider components (Box, Flex, Grid, Card, etc.). * - * Includes all `ContainerBg` values plus `'neutral-auto'` which - * automatically increments the neutral level from the parent context. + * - `'neutral'` — automatically increments the neutral level from the parent context, + * capping at the maximum level. This is always incremental; explicit levels cannot + * be set directly. + * - `'danger'` | `'warning'` | `'success'` — intent backgrounds used as-is. * * @public */ -export type ProviderBg = ContainerBg | 'neutral-auto'; +export type ProviderBg = 'neutral' | 'danger' | 'warning' | 'success';