Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 43 additions & 14 deletions packages/theme/bin/terrazzo-plugin-inline-alias-values/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { type Plugin } from '@terrazzo/parser';
import { type Plugin, type TokenNormalized } from '@terrazzo/parser';

interface InlineAliasValuesOptions {
/**
Expand Down Expand Up @@ -40,25 +40,54 @@ export default function inlineAliasValues( {
return {
name: '@wordpress/terrazzo-plugin-inline-alias-values',
async transform( { tokens } ) {
Object.keys( tokens )
.filter( ( id ) => pattern.test( id ) )
.forEach( ( id ) => {
const token = tokens[ id ];
// Map of primitive token ID -> array of references to inline
const inlineMap: Record< string, TokenNormalized[] > = {};

// Single pass: identify primitives and collect references
for ( const [ id, token ] of Object.entries( tokens ) ) {
const shouldInline = pattern.test( id );

if ( shouldInline ) {
// Track this token for inlining
inlineMap[ id ] = [];

// Track aliased tokens for output file
if ( token.aliasedBy ) {
aliasedBy[ tokenId( id ) ] =
token.aliasedBy.map( tokenId );
}
}

// Check if this token's main value references a primitive
if ( token.aliasOf && pattern.test( token.aliasOf ) ) {
inlineMap[ token.aliasOf ] ??= [];
inlineMap[ token.aliasOf ].push( token );
}

for ( const aliasedId of token.aliasedBy ) {
Object.assign( tokens[ aliasedId ], {
$value: token.$value,
mode: token.mode,
originalValue: token.originalValue,
} );
}
// Check if any mode values reference a primitive
for ( const modeValue of Object.values( token.mode ) ) {
const { aliasOf } = modeValue;
if ( aliasOf && pattern.test( aliasOf ) ) {
const primitiveId = aliasOf;
inlineMap[ primitiveId ] ??= [];
inlineMap[ primitiveId ].push( modeValue );
}
}
}

// Inline values and delete primitives
for ( const [ id, references ] of Object.entries( inlineMap ) ) {
const token = tokens[ id ];

delete tokens[ id ];
} );
for ( const target of references ) {
target.$value = token.$value;
target.originalValue = token.originalValue;
delete target.aliasOf;
delete target.aliasChain;
}

delete tokens[ id ];
}
},
async build( { outputFile } ) {
if ( ! filename ) {
Expand Down
15 changes: 8 additions & 7 deletions packages/theme/docs/ds-tokens.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,14 @@ Do not edit directly.

### Dimension

| Variable name | Description |
| ------------------------------------------ | -------------------------------- |
| `--wpds-dimension-base` | Base dimension unit |
| `--wpds-dimension-padding-surface-x-small` | Extra small spacing for surfaces |
| `--wpds-dimension-padding-surface-small` | Small spacing for surfaces |
| `--wpds-dimension-padding-surface-medium` | Medium spacing for surfaces |
| `--wpds-dimension-padding-surface-large` | Large spacing for surfaces |
| Variable name | Description |
| -------------------------------------- | ----------------------------------- |
| `--wpds-dimension-base` | Base dimension unit |
| `--wpds-dimension-padding-surface-2xs` | 2x extra small spacing for surfaces |
| `--wpds-dimension-padding-surface-xs` | Extra small spacing for surfaces |
| `--wpds-dimension-padding-surface-sm` | Small spacing for surfaces |
| `--wpds-dimension-padding-surface-md` | Medium spacing for surfaces |
| `--wpds-dimension-padding-surface-lg` | Large spacing for surfaces |

### Elevation

Expand Down
34 changes: 30 additions & 4 deletions packages/theme/src/prebuilt/css/design-tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,11 @@
--wpds-color-stroke-surface-warning: #d2b581; /* Decorative stroke color used to define warning-toned surface boundaries with normal emphasis. */
--wpds-color-stroke-surface-warning-strong: #936400; /* Decorative stroke color used to define warning-toned surface boundaries with strong emphasis. */
--wpds-dimension-base: 4px; /* Base dimension unit */
--wpds-dimension-padding-surface-large: 24px; /* Large spacing for surfaces */
--wpds-dimension-padding-surface-medium: 16px; /* Medium spacing for surfaces */
--wpds-dimension-padding-surface-small: 12px; /* Small spacing for surfaces */
--wpds-dimension-padding-surface-x-small: 8px; /* Extra small spacing for surfaces */
--wpds-dimension-padding-surface-2xs: 4px; /* 2x extra small spacing for surfaces */
--wpds-dimension-padding-surface-lg: 32px; /* Large spacing for surfaces */
--wpds-dimension-padding-surface-md: 24px; /* Medium spacing for surfaces */
--wpds-dimension-padding-surface-sm: 16px; /* Small spacing for surfaces */
--wpds-dimension-padding-surface-xs: 8px; /* Extra small spacing for surfaces */
--wpds-elevation-large: 0 5px 15px 0 #00000014, 0 15px 27px 0 #00000012,
0 30px 36px 0 #0000000a, 0 50px 43px 0 #00000005; /* For components that confirm decisions or handle necessary interruptions. Example: Modals. */
--wpds-elevation-medium: 0 2px 3px 0 #0000000d, 0 4px 5px 0 #0000000a,
Expand Down Expand Up @@ -127,6 +128,31 @@
--wpds-font-size-x-small: 11px; /* Extra small font size */
}

[data-wpds-theme-provider-id][data-wpds-density='default'] {
--wpds-dimension-base: 4px; /* Base dimension unit */
--wpds-dimension-padding-surface-2xs: 4px; /* 2x extra small spacing for surfaces */
--wpds-dimension-padding-surface-lg: 32px; /* Large spacing for surfaces */
--wpds-dimension-padding-surface-md: 24px; /* Medium spacing for surfaces */
--wpds-dimension-padding-surface-sm: 16px; /* Small spacing for surfaces */
--wpds-dimension-padding-surface-xs: 8px; /* Extra small spacing for surfaces */
}

[data-wpds-theme-provider-id][data-wpds-density='compact'] {
--wpds-dimension-padding-surface-2xs: 4px; /* 2x extra small spacing for surfaces */
--wpds-dimension-padding-surface-lg: 24px; /* Large spacing for surfaces */
--wpds-dimension-padding-surface-md: 20px; /* Medium spacing for surfaces */
--wpds-dimension-padding-surface-sm: 12px; /* Small spacing for surfaces */
--wpds-dimension-padding-surface-xs: 4px; /* Extra small spacing for surfaces */
}

[data-wpds-theme-provider-id][data-wpds-density='comfortable'] {
--wpds-dimension-padding-surface-2xs: 8px; /* 2x extra small spacing for surfaces */
--wpds-dimension-padding-surface-lg: 40px; /* Large spacing for surfaces */
--wpds-dimension-padding-surface-md: 32px; /* Medium spacing for surfaces */
--wpds-dimension-padding-surface-sm: 20px; /* Small spacing for surfaces */
--wpds-dimension-padding-surface-xs: 12px; /* Extra small spacing for surfaces */
}

@media ( -webkit-min-device-pixel-ratio: 2 ), ( min-resolution: 192dpi ) {
:root {
--wpds-border-width-focus: 1.5px; /* Border width for focus ring */
Expand Down
9 changes: 5 additions & 4 deletions packages/theme/src/prebuilt/js/design-tokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,11 @@ export default [
'--wpds-color-stroke-interactive-error-strong',
'--wpds-color-stroke-focus-brand',
'--wpds-dimension-base',
'--wpds-dimension-padding-surface-x-small',
'--wpds-dimension-padding-surface-small',
'--wpds-dimension-padding-surface-medium',
'--wpds-dimension-padding-surface-large',
'--wpds-dimension-padding-surface-2xs',
'--wpds-dimension-padding-surface-xs',
'--wpds-dimension-padding-surface-sm',
'--wpds-dimension-padding-surface-md',
'--wpds-dimension-padding-surface-lg',
'--wpds-elevation-x-small',
'--wpds-elevation-small',
'--wpds-elevation-medium',
Expand Down
32 changes: 24 additions & 8 deletions packages/theme/src/prebuilt/json/figma.json
Original file line number Diff line number Diff line change
Expand Up @@ -558,27 +558,43 @@
},
"description": "Base dimension unit"
},
"Dimension/Semantic/padding-surface-x-small": {
"Dimension/Semantic/padding-surface-2xs": {
"value": {
".": "8px"
".": "4px",
"compact": "4px",
"comfortable": "8px"
},
"description": "2x extra small spacing for surfaces"
},
"Dimension/Semantic/padding-surface-xs": {
"value": {
".": "8px",
"compact": "4px",
"comfortable": "12px"
},
"description": "Extra small spacing for surfaces"
},
"Dimension/Semantic/padding-surface-small": {
"Dimension/Semantic/padding-surface-sm": {
"value": {
".": "12px"
".": "16px",
"compact": "12px",
"comfortable": "20px"
},
"description": "Small spacing for surfaces"
},
"Dimension/Semantic/padding-surface-medium": {
"Dimension/Semantic/padding-surface-md": {
"value": {
".": "16px"
".": "24px",
"compact": "20px",
"comfortable": "32px"
},
"description": "Medium spacing for surfaces"
},
"Dimension/Semantic/padding-surface-large": {
"Dimension/Semantic/padding-surface-lg": {
"value": {
".": "24px"
".": "32px",
"compact": "24px",
"comfortable": "40px"
},
"description": "Large spacing for surfaces"
},
Copy link
Contributor

@jameskoster jameskoster Nov 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we'll probably need tokens for 32px and 48px padding. 32px is currently used in Dialogs and 48px in DataViews.

I guess that also means we'll need a new primitive so that 48px can jump to 56px in comfortable density.

Expand Down
32 changes: 23 additions & 9 deletions packages/theme/src/stories/index.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
* Internal dependencies
*/
import { ThemeProvider } from '../theme-provider';
import '../prebuilt/css/design-tokens.css';

const meta: Meta< typeof ThemeProvider > = {
title: 'Design System/Theme Provider',
Expand Down Expand Up @@ -198,10 +199,10 @@ export const WithPicker: StoryObj< typeof ThemeProvider > = {
},
};

const NestingDebug = ( { bg = '', primary = '' } ) => (
const NestingDebug = ( { bg = '', primary = '', density = '' } ) => (
<div
style={ {
padding: '0.25rem',
padding: 'var(--wpds-dimension-padding-surface-sm)',
color: 'var(--wpds-color-fg-content-neutral)',
backgroundColor: 'var(--wpds-color-bg-surface-neutral)',
display: 'flex',
Expand All @@ -210,13 +211,13 @@ const NestingDebug = ( { bg = '', primary = '' } ) => (
gap: '1rem',
} }
>
<pre>
bg: { bg } | primary: { primary }
<pre style={ { margin: 0 } }>
bg: { bg } | primary: { primary } | density: { density }
</pre>
<span
style={ {
display: 'inline-block',
padding: '0.25rem',
padding: 'var(--wpds-dimension-padding-surface-xs)',
borderRadius: '0.25rem',
backgroundColor:
'var(--wpds-color-bg-interactive-brand-strong)',
Expand All @@ -229,7 +230,7 @@ const NestingDebug = ( { bg = '', primary = '' } ) => (
style={ {
display: 'inline-block',
marginInlineStart: '0.25rem',
padding: '0.25rem',
padding: 'var(--wpds-dimension-padding-surface-xs)',
borderRadius: '0.25rem',
backgroundColor:
'var(--wpds-color-bg-interactive-brand-weak-disabled)',
Expand All @@ -245,27 +246,39 @@ export const NestingAndInheriting: StoryObj< typeof ThemeProvider > = {
render: () => {
return (
<ThemeProvider>
<NestingDebug bg="inherit (root)" primary="inherit (root)" />
<NestingDebug
bg="inherit (root)"
primary="inherit (root)"
density="inherit (root)"
/>
<div style={ { paddingInlineStart: '1rem' } }>
<ThemeProvider
color={ {
bg: '#1e1e1e',
} }
density="compact"
>
<NestingDebug bg="#1e1e1e" primary="inherit (root)" />
<NestingDebug
bg="#1e1e1e"
primary="inherit (root)"
density="compact"
/>
<div style={ { paddingInlineStart: '1rem' } }>
<ThemeProvider>
<NestingDebug
bg="inherit (#1e1e1e)"
primary="inherit (root)"
density="inherit (compact)"
/>
<div style={ { paddingInlineStart: '1rem' } }>
<ThemeProvider
color={ { primary: 'hotpink' } }
density="default"
>
<NestingDebug
bg="inherit (#1e1e1e)"
primary="hotpink"
density="default"
/>
<div
style={ {
Expand All @@ -278,6 +291,7 @@ export const NestingAndInheriting: StoryObj< typeof ThemeProvider > = {
<NestingDebug
bg="#f8f8f8"
primary="inherit (hotpink)"
density="inherit (default)"
/>
</ThemeProvider>
</div>
Expand Down Expand Up @@ -397,7 +411,7 @@ export const AcrossIframes: StoryObj< typeof ThemeProvider > = {
<span
style={ {
display: 'inline-block',
padding: '0.25rem',
padding: 'var(--wpds-dimension-padding-surface-xs)',
borderRadius: '0.25rem',
backgroundColor:
'var(--wpds-color-bg-interactive-brand-strong)',
Expand Down
2 changes: 2 additions & 0 deletions packages/theme/src/theme-provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const ThemeProvider = ( {
children,
color = {},
isRoot = false,
density,
}: ThemeProviderProps ) => {
const instanceId = useId();

Expand Down Expand Up @@ -76,6 +77,7 @@ export const ThemeProvider = ( {
<div
data-wpds-theme-provider-id={ instanceId }
data-wpds-root-provider={ isRoot }
data-wpds-density={ density }
className={ styles.root }
>
<ThemeContext.Provider value={ contextValue }>
Expand Down
9 changes: 9 additions & 0 deletions packages/theme/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ export interface ThemeProviderSettings {
*/
bg?: string;
};

/**
* The density of the theme. If left unspecified, the theme inherits from
* the density of the closest `ThemeProvider`, or uses the default density
* if there is no inherited density.
*
* @default undefined
*/
density?: undefined | 'default' | 'compact' | 'comfortable';
Comment on lines +27 to +34
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought, no strong opinion, but would something like this be more intuitive? Personally I think it would be easier to understand when skimming the docs, since it's closer to the CSS mental model.

Suggested change
/**
* The density of the theme. If left unspecified, the theme inherits from
* the density of the closest `ThemeProvider`, or uses the default density
* if there is no inherited density.
*
* @default undefined
*/
density?: undefined | 'default' | 'compact' | 'comfortable';
/**
* The density of the theme. If left unspecified, the theme inherits from
* the density of the closest `ThemeProvider`, or uses the default density
* if there is no inherited density.
*
* @default 'inherit'
*/
density?: 'inherit' | 'default' | 'compact' | 'comfortable';

Copy link
Member Author

@aduth aduth Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I think there's some advantages to that, particularly around explicitness. However, on the whole, I don't know that it's a change I want to make now:

  • The idea of using undefined (absence of value) as interpreted to inherit from the parent is consistent with how we're treating color customization above (source)
  • While not really a blocker in itself, doing this incurs some internal technical complexity because we need to normalize the 'inherit' value back to undefined in order to effect the inherit behavior by ensuring that data-wpds-density isn't assigned (source) because, unlike [data-wpds-density="compact"], etc., we don't have an explicit selector for the inherited behavior (there is no [data-wpds-density="inherit"]).
  • The suggestion feels like a logical step in part because of how I've gone out of my way to be explicit here, though in retrospect I'm not fully convinced it was necessary to be so explicit (i.e. including @default undefined and including undefined in the type union for density).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair. Maybe it looks weird to me because all our "physical" components do @default 'default', and this is the first time we're doing props with inheritance.

}

export interface ThemeProviderProps extends ThemeProviderSettings {
Expand Down
21 changes: 21 additions & 0 deletions packages/theme/terrazzo.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@ export default defineConfig( {
makeCSSVar( `wpds.${ publicTokenId( token.id ) }` ),
baseSelector: ':root',
modeSelectors: [
{
tokens: [ 'dimension.*' ],
mode: '.',
selectors: [
"[data-wpds-theme-provider-id][data-wpds-density='default']",
],
},
{
tokens: [ 'dimension.*' ],
mode: 'compact',
selectors: [
"[data-wpds-theme-provider-id][data-wpds-density='compact']",
],
},
{
tokens: [ 'dimension.*' ],
mode: 'comfortable',
selectors: [
"[data-wpds-theme-provider-id][data-wpds-density='comfortable']",
],
},
{
mode: 'high-dpi',
selectors: [
Expand Down
Loading
Loading