@wordpress/theme 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/LICENSE.md +788 -0
  2. package/README.md +67 -0
  3. package/bin/build-tokens.js +83 -0
  4. package/bin/generate-primitive-tokens/index.ts +115 -0
  5. package/bin/terrazzo-plugin-ds-tokens-docs/index.ts +103 -0
  6. package/bin/terrazzo-plugin-figma-ds-token-manager/index.ts +210 -0
  7. package/bin/terrazzo-plugin-figma-ds-token-manager/lib.ts +1 -0
  8. package/bin/terrazzo-plugin-known-wpds-css-variables/index.ts +72 -0
  9. package/build/color-ramps/index.js +132 -0
  10. package/build/color-ramps/index.js.map +7 -0
  11. package/build/color-ramps/lib/cache-utils.js +57 -0
  12. package/build/color-ramps/lib/cache-utils.js.map +7 -0
  13. package/build/color-ramps/lib/constants.js +105 -0
  14. package/build/color-ramps/lib/constants.js.map +7 -0
  15. package/build/color-ramps/lib/find-color-with-constraints.js +141 -0
  16. package/build/color-ramps/lib/find-color-with-constraints.js.map +7 -0
  17. package/build/color-ramps/lib/index.js +264 -0
  18. package/build/color-ramps/lib/index.js.map +7 -0
  19. package/build/color-ramps/lib/ramp-configs.js +315 -0
  20. package/build/color-ramps/lib/ramp-configs.js.map +7 -0
  21. package/build/color-ramps/lib/taper-chroma.js +159 -0
  22. package/build/color-ramps/lib/taper-chroma.js.map +7 -0
  23. package/build/color-ramps/lib/types.js +17 -0
  24. package/build/color-ramps/lib/types.js.map +7 -0
  25. package/build/color-ramps/lib/utils.js +106 -0
  26. package/build/color-ramps/lib/utils.js.map +7 -0
  27. package/build/context.js +34 -0
  28. package/build/context.js.map +7 -0
  29. package/build/index.js +29 -0
  30. package/build/index.js.map +7 -0
  31. package/build/lock-unlock.js +35 -0
  32. package/build/lock-unlock.js.map +7 -0
  33. package/build/prebuilt/js/design-tokens.js +135 -0
  34. package/build/prebuilt/js/design-tokens.js.map +7 -0
  35. package/build/prebuilt/json/figma.json +1317 -0
  36. package/build/prebuilt/ts/design-tokens.js +354 -0
  37. package/build/prebuilt/ts/design-tokens.js.map +7 -0
  38. package/build/private-apis.js +36 -0
  39. package/build/private-apis.js.map +7 -0
  40. package/build/style.module.css.js +2 -0
  41. package/build/theme-provider.js +92 -0
  42. package/build/theme-provider.js.map +7 -0
  43. package/build/types/css-modules.d.js +2 -0
  44. package/build/types/css-modules.d.js.map +7 -0
  45. package/build/types.js +17 -0
  46. package/build/types.js.map +7 -0
  47. package/build/use-theme-provider-styles.js +230 -0
  48. package/build/use-theme-provider-styles.js.map +7 -0
  49. package/build-module/color-ramps/index.js +95 -0
  50. package/build-module/color-ramps/index.js.map +7 -0
  51. package/build-module/color-ramps/lib/cache-utils.js +31 -0
  52. package/build-module/color-ramps/lib/cache-utils.js.map +7 -0
  53. package/build-module/color-ramps/lib/constants.js +63 -0
  54. package/build-module/color-ramps/lib/constants.js.map +7 -0
  55. package/build-module/color-ramps/lib/find-color-with-constraints.js +112 -0
  56. package/build-module/color-ramps/lib/find-color-with-constraints.js.map +7 -0
  57. package/build-module/color-ramps/lib/index.js +235 -0
  58. package/build-module/color-ramps/lib/index.js.map +7 -0
  59. package/build-module/color-ramps/lib/ramp-configs.js +290 -0
  60. package/build-module/color-ramps/lib/ramp-configs.js.map +7 -0
  61. package/build-module/color-ramps/lib/taper-chroma.js +125 -0
  62. package/build-module/color-ramps/lib/taper-chroma.js.map +7 -0
  63. package/build-module/color-ramps/lib/types.js +1 -0
  64. package/build-module/color-ramps/lib/types.js.map +7 -0
  65. package/build-module/color-ramps/lib/utils.js +84 -0
  66. package/build-module/color-ramps/lib/utils.js.map +7 -0
  67. package/build-module/context.js +10 -0
  68. package/build-module/context.js.map +7 -0
  69. package/build-module/index.js +5 -0
  70. package/build-module/index.js.map +7 -0
  71. package/build-module/lock-unlock.js +10 -0
  72. package/build-module/lock-unlock.js.map +7 -0
  73. package/build-module/prebuilt/js/design-tokens.js +115 -0
  74. package/build-module/prebuilt/js/design-tokens.js.map +7 -0
  75. package/build-module/prebuilt/json/figma.json +1317 -0
  76. package/build-module/prebuilt/ts/design-tokens.js +334 -0
  77. package/build-module/prebuilt/ts/design-tokens.js.map +7 -0
  78. package/build-module/private-apis.js +12 -0
  79. package/build-module/private-apis.js.map +7 -0
  80. package/build-module/style.module.css.js +1 -0
  81. package/build-module/theme-provider.js +58 -0
  82. package/build-module/theme-provider.js.map +7 -0
  83. package/build-module/types/css-modules.d.js +1 -0
  84. package/build-module/types/css-modules.d.js.map +7 -0
  85. package/build-module/types.js +1 -0
  86. package/build-module/types.js.map +7 -0
  87. package/build-module/use-theme-provider-styles.js +200 -0
  88. package/build-module/use-theme-provider-styles.js.map +7 -0
  89. package/build-style/style.css +3 -0
  90. package/build-types/color-ramps/index.d.ts +44 -0
  91. package/build-types/color-ramps/index.d.ts.map +1 -0
  92. package/build-types/color-ramps/lib/cache-utils.d.ts +22 -0
  93. package/build-types/color-ramps/lib/cache-utils.d.ts.map +1 -0
  94. package/build-types/color-ramps/lib/constants.d.ts +38 -0
  95. package/build-types/color-ramps/lib/constants.d.ts.map +1 -0
  96. package/build-types/color-ramps/lib/find-color-with-constraints.d.ts +37 -0
  97. package/build-types/color-ramps/lib/find-color-with-constraints.d.ts.map +1 -0
  98. package/build-types/color-ramps/lib/index.d.ts +11 -0
  99. package/build-types/color-ramps/lib/index.d.ts.map +1 -0
  100. package/build-types/color-ramps/lib/ramp-configs.d.ts +7 -0
  101. package/build-types/color-ramps/lib/ramp-configs.d.ts.map +1 -0
  102. package/build-types/color-ramps/lib/taper-chroma.d.ts +32 -0
  103. package/build-types/color-ramps/lib/taper-chroma.d.ts.map +1 -0
  104. package/build-types/color-ramps/lib/types.d.ts +78 -0
  105. package/build-types/color-ramps/lib/types.d.ts.map +1 -0
  106. package/build-types/color-ramps/lib/utils.d.ts +38 -0
  107. package/build-types/color-ramps/lib/utils.d.ts.map +1 -0
  108. package/build-types/color-ramps/stories/index.story.d.ts +14 -0
  109. package/build-types/color-ramps/stories/index.story.d.ts.map +1 -0
  110. package/build-types/color-ramps/stories/ramp-table.d.ts +19 -0
  111. package/build-types/color-ramps/stories/ramp-table.d.ts.map +1 -0
  112. package/build-types/context.d.ts +10 -0
  113. package/build-types/context.d.ts.map +1 -0
  114. package/build-types/index.d.ts +2 -0
  115. package/build-types/index.d.ts.map +1 -0
  116. package/build-types/lock-unlock.d.ts +2 -0
  117. package/build-types/lock-unlock.d.ts.map +1 -0
  118. package/build-types/prebuilt/js/design-tokens.d.ts +3 -0
  119. package/build-types/prebuilt/js/design-tokens.d.ts.map +1 -0
  120. package/build-types/prebuilt/ts/design-tokens.d.ts +7 -0
  121. package/build-types/prebuilt/ts/design-tokens.d.ts.map +1 -0
  122. package/build-types/private-apis.d.ts +2 -0
  123. package/build-types/private-apis.d.ts.map +1 -0
  124. package/build-types/stories/index.story.d.ts +15 -0
  125. package/build-types/stories/index.story.d.ts.map +1 -0
  126. package/build-types/theme-provider.d.ts +3 -0
  127. package/build-types/theme-provider.d.ts.map +1 -0
  128. package/build-types/types.d.ts +42 -0
  129. package/build-types/types.d.ts.map +1 -0
  130. package/build-types/use-theme-provider-styles.d.ts +17 -0
  131. package/build-types/use-theme-provider-styles.d.ts.map +1 -0
  132. package/docs/ds-tokens.md +283 -0
  133. package/package.json +58 -0
  134. package/src/color-ramps/index.ts +155 -0
  135. package/src/color-ramps/lib/cache-utils.ts +56 -0
  136. package/src/color-ramps/lib/constants.ts +85 -0
  137. package/src/color-ramps/lib/find-color-with-constraints.ts +190 -0
  138. package/src/color-ramps/lib/index.ts +369 -0
  139. package/src/color-ramps/lib/ramp-configs.ts +309 -0
  140. package/src/color-ramps/lib/taper-chroma.ts +226 -0
  141. package/src/color-ramps/lib/types.ts +90 -0
  142. package/src/color-ramps/lib/utils.ts +161 -0
  143. package/src/color-ramps/stories/index.story.tsx +264 -0
  144. package/src/color-ramps/stories/ramp-table.tsx +212 -0
  145. package/src/color-ramps/test/__snapshots__/index.test.ts.snap +1280 -0
  146. package/src/color-ramps/test/index.test.ts +94 -0
  147. package/src/context.ts +19 -0
  148. package/src/index.ts +2 -0
  149. package/src/lock-unlock.ts +10 -0
  150. package/src/prebuilt/css/design-tokens.css +401 -0
  151. package/src/prebuilt/js/design-tokens.js +116 -0
  152. package/src/prebuilt/json/figma.json +1317 -0
  153. package/src/prebuilt/ts/design-tokens.ts +335 -0
  154. package/src/private-apis.ts +12 -0
  155. package/src/stories/index.story.tsx +426 -0
  156. package/src/style.module.css +3 -0
  157. package/src/theme-provider.tsx +87 -0
  158. package/src/types/css-modules.d.ts +4 -0
  159. package/src/types.ts +44 -0
  160. package/src/use-theme-provider-styles.ts +247 -0
  161. package/terrazzo.config.ts +102 -0
  162. package/tokens/border.json +34 -0
  163. package/tokens/color.json +877 -0
  164. package/tokens/elevation.json +201 -0
  165. package/tokens/spacing.json +45 -0
  166. package/tokens/typography.json +93 -0
  167. package/tsconfig.json +9 -0
  168. package/tsconfig.tsbuildinfo +1 -0
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # (Experimental) Theme
2
+
3
+ A theming package that's part of the WordPress Design System. It has two parts:
4
+
5
+ - **Design Tokens**: A comprehensive system of design tokens for colors, spacing, typography, and more
6
+ - **Theme System**: A flexible theming provider for consistent theming across applications
7
+
8
+ ## Design Tokens
9
+
10
+ In the **[Design Tokens Reference](docs/ds-tokens.md)** document there is a complete reference of all available design tokens including colors, spacing, typography, and more.
11
+
12
+ ## Theme Provider
13
+
14
+ The `ThemeProvider` is a React component that should wrap your application to provide design tokens and theme context to the child UI components.
15
+
16
+ ```tsx
17
+ import { ThemeProvider } from '@wordpress/theme';
18
+
19
+ function App() {
20
+ return (
21
+ <ThemeProvider color={ { scheme: 'light', accent: 'blue' } }>
22
+ { /* Your app content */ }
23
+ </ThemeProvider>
24
+ );
25
+ }
26
+ ```
27
+
28
+ The provider can be used recursively to override or modify the theme for a specific subtree.
29
+
30
+ ```tsx
31
+ <ThemeProvider>
32
+ { /* system-themed UI components */ }
33
+ <ThemeProvider color={ { scheme: 'dark' } }>
34
+ { /* dark-themed UI components */ }
35
+ <ThemeProvider color={ { scheme: 'light' } }>
36
+ { /* light-themed UI components */ }
37
+ </ThemeProvider>
38
+ { /* dark-themed UI components */ }
39
+ </ThemeProvider>
40
+ { /* system-themed UI components */ }
41
+ </ThemeProvider>
42
+ ```
43
+
44
+ The `ThemeProvider` redefines some of the design system tokens. Components consuming semantic design system tokens will automatically follow the chosen theme. Note that the tokens are defined and inherited using the CSS cascade, and therefore the DOM tree, not the React tree. This is very important when using React portals.
45
+
46
+ ### Building
47
+
48
+ This package is built in two steps. When `npm run build` is run at the root of the repo, it will first run the "prebuild" step of this package, which is defined in the `build` script of this package's package.json.
49
+
50
+ This step will:
51
+
52
+ 1. Generate primitive tokens.
53
+ 2. Build CSS and JavaScript token files.
54
+ 3. Update the design tokens documentation.
55
+ 4. Format all generated files.
56
+
57
+ The files generated in this step will all be committed to the repo.
58
+
59
+ After the prebuild step, the package will be built into its final form via the repo's standard package build script.
60
+
61
+ ## Contributing to this package
62
+
63
+ This is an individual package that's part of the Gutenberg project. The project is organized as a monorepo. It's made up of multiple self-contained software packages, each with a specific purpose. The packages in this monorepo are published to [npm](https://www.npmjs.com/) and used by [WordPress](https://make.wordpress.org/core/) as well as other software projects.
64
+
65
+ To find out more about contributing to this package or Gutenberg as a whole, please read the project's main [contributor guide](https://github.com/WordPress/gutenberg/tree/HEAD/CONTRIBUTING.md).
66
+
67
+ <br /><br /><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>
@@ -0,0 +1,83 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ const { execSync } = require( 'child_process' );
5
+ const fs = require( 'fs' );
6
+ const path = require( 'path' );
7
+
8
+ /**
9
+ * Build script for design tokens.
10
+ *
11
+ * This script:
12
+ * 1. Compiles and runs the primitive token generator
13
+ * 2. Compiles the terrazzo config and runs the Terrazzo build
14
+ * 3. Cleans up temporary files
15
+ */
16
+
17
+ const TEMP_FILES = [
18
+ 'bin/generate-primitive-tokens/index.mjs',
19
+ 'terrazzo.config.mjs',
20
+ ];
21
+
22
+ function cleanup() {
23
+ TEMP_FILES.forEach( ( file ) => {
24
+ const filePath = path.join( process.cwd(), file );
25
+ if ( fs.existsSync( filePath ) ) {
26
+ fs.unlinkSync( filePath );
27
+ }
28
+ } );
29
+ }
30
+
31
+ // Ensure cleanup happens even if the process exits unexpectedly
32
+ process.on( 'exit', cleanup );
33
+ process.on( 'SIGINT', () => {
34
+ cleanup();
35
+ process.exit( 130 );
36
+ } );
37
+ process.on( 'SIGTERM', () => {
38
+ cleanup();
39
+ process.exit( 143 );
40
+ } );
41
+
42
+ try {
43
+ // Step 1: Compile the primitive token generator
44
+ console.log( '🔨 Compiling primitive token generator...' );
45
+ execSync(
46
+ 'npx esbuild bin/generate-primitive-tokens/index.ts --bundle --platform=node --format=esm --packages=external --outfile=bin/generate-primitive-tokens/index.mjs',
47
+ {
48
+ stdio: 'inherit',
49
+ cwd: process.cwd(),
50
+ }
51
+ );
52
+
53
+ // Step 2: Run the primitive token generator
54
+ console.log( '🎨 Generating primitive tokens...' );
55
+ execSync( 'node bin/generate-primitive-tokens/index.mjs', {
56
+ stdio: 'inherit',
57
+ cwd: process.cwd(),
58
+ } );
59
+
60
+ // Step 3: Compile the terrazzo config
61
+ console.log( '🔨 Compiling terrazzo config...' );
62
+ execSync(
63
+ 'npx esbuild terrazzo.config.ts --bundle --platform=node --format=esm --packages=external --outfile=terrazzo.config.mjs',
64
+ {
65
+ stdio: 'inherit',
66
+ cwd: process.cwd(),
67
+ }
68
+ );
69
+
70
+ // Step 4: Run terrazzo build
71
+ console.log( '🏗️ Running terrazzo build...' );
72
+ execSync( 'npx tz build --config terrazzo.config.mjs', {
73
+ stdio: 'inherit',
74
+ cwd: process.cwd(),
75
+ } );
76
+
77
+ console.log( '✅ Token build complete!' );
78
+ } catch ( error ) {
79
+ console.error( '❌ Build failed:', error.message );
80
+ process.exit( 1 );
81
+ } finally {
82
+ cleanup();
83
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import Color from 'colorjs.io';
8
+
9
+ /**
10
+ * Internal dependencies
11
+ */
12
+ import {
13
+ DEFAULT_SEED_COLORS,
14
+ buildBgRamp,
15
+ buildAccentRamp,
16
+ } from '../../src/color-ramps/index';
17
+
18
+ const __filename = fileURLToPath( import.meta.url );
19
+ const __dirname = path.dirname( __filename );
20
+
21
+ // Path to the color.json file
22
+ const colorJsonPath = path.join( __dirname, '../../tokens/color.json' );
23
+
24
+ const transformColorStringToDTCGValue = ( color: string ) => {
25
+ if ( /oklch|p3/.test( color ) ) {
26
+ let parsed: Color;
27
+ try {
28
+ parsed = new Color( color ).to( 'oklch' );
29
+ } catch {
30
+ return color;
31
+ }
32
+
33
+ const coords = parsed.coords;
34
+ return {
35
+ colorSpace: 'oklch',
36
+ components: [
37
+ Math.floor( 10000 * coords[ 0 ] ) / 10000, // l
38
+ coords[ 1 ], // c
39
+ isNaN( coords[ 2 ] ) ? 0 : coords[ 2 ], // h
40
+ ],
41
+ ...( parsed.alpha < 1 ? { alpha: parsed.alpha } : undefined ),
42
+ hex: parsed.to( 'srgb' ).toString( { format: 'hex' } ),
43
+ };
44
+ }
45
+
46
+ return color;
47
+ };
48
+
49
+ // Main function
50
+ function generatePrimitiveColorTokens() {
51
+ const startTime = performance.now();
52
+ console.log( '🎨 Starting primitive color tokens generation...' );
53
+
54
+ try {
55
+ // Read the color.json file
56
+ const colorJson = JSON.parse(
57
+ fs.readFileSync( colorJsonPath, 'utf8' )
58
+ );
59
+
60
+ // Build the ramps
61
+ const bgRamp = buildBgRamp( { seed: DEFAULT_SEED_COLORS.bg } );
62
+ const accentRamps = [ ...Object.entries( DEFAULT_SEED_COLORS ) ]
63
+ .filter( ( [ scaleName ] ) => scaleName !== 'bg' )
64
+ .map( ( [ scaleName, seed ] ) => ( {
65
+ scaleName,
66
+ ramp: buildAccentRamp( {
67
+ seed,
68
+ bgRamp,
69
+ } ),
70
+ } ) );
71
+
72
+ // Convert the ramp values in a DTCG compatible format
73
+ [
74
+ {
75
+ scaleName: 'bg',
76
+ ramp: bgRamp,
77
+ },
78
+ ...accentRamps,
79
+ ].forEach( ( { scaleName, ramp } ) => {
80
+ colorJson.color.primitive[ scaleName ] = {};
81
+ for ( const [ tokenName, tokenValue ] of Object.entries(
82
+ ramp.ramp
83
+ ) ) {
84
+ colorJson.color.primitive[ scaleName ][ tokenName ] = {
85
+ $value: transformColorStringToDTCGValue( tokenValue.color ),
86
+ };
87
+ }
88
+ } );
89
+
90
+ // Write the updated JSON back to the file with proper formatting
91
+ fs.writeFileSync(
92
+ colorJsonPath,
93
+ JSON.stringify( colorJson, null, '\t' )
94
+ );
95
+
96
+ const endTime = performance.now();
97
+ const duration = endTime - startTime;
98
+ console.log(
99
+ `✅ Successfully updated color.json (${ duration.toFixed( 2 ) }ms)`
100
+ );
101
+ } catch ( error ) {
102
+ const endTime = performance.now();
103
+ const duration = endTime - startTime;
104
+ console.error(
105
+ `❌ Error updating color tokens after ${ duration.toFixed(
106
+ 2
107
+ ) }ms:`,
108
+ error
109
+ );
110
+ process.exit( 1 );
111
+ }
112
+ }
113
+
114
+ // Run the script
115
+ generatePrimitiveColorTokens();
@@ -0,0 +1,103 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { FORMAT_ID } from '@terrazzo/plugin-css';
5
+ import type { Plugin } from '@terrazzo/parser';
6
+
7
+ function isPrivateToken( token: string ) {
8
+ return /-private-/i.test( token );
9
+ }
10
+
11
+ function titleCase( str: string ) {
12
+ return str[ 0 ].toUpperCase() + str.slice( 1 );
13
+ }
14
+
15
+ type TokensMap = Record< string, Record< string, string > >;
16
+
17
+ export default function pluginDsTokenDocs( {
18
+ filename = 'design-tokens.md',
19
+ } = {} ): Plugin {
20
+ return {
21
+ name: '@terrazzo/terrazzo-plugin-ds-tokens-docs',
22
+ async build( { getTransforms, outputFile } ) {
23
+ if ( ! filename ) {
24
+ return;
25
+ }
26
+
27
+ const primitiveTokens: TokensMap = {};
28
+ const semanticTokens: TokensMap = {};
29
+ // Re-use transformed tokens from the CSS plugin
30
+ for ( const token of getTransforms( {
31
+ format: FORMAT_ID,
32
+ id: '*',
33
+ mode: '.',
34
+ } ) ) {
35
+ if ( token.localID === undefined ) {
36
+ console.warn(
37
+ 'Unexpected — Missing local ID when building token list for eslint plugin'
38
+ );
39
+ continue;
40
+ }
41
+
42
+ // Use the tokens filename (without .json) as the group name
43
+ const group =
44
+ token.token.source.loc
45
+ ?.split( '/' )
46
+ .at( -1 )
47
+ ?.split( '.json' )[ 0 ] ?? 'Miscellaneous';
48
+
49
+ // Organize tokens in semantic/private, and group by category.
50
+ const tokensObject = isPrivateToken( token.localID )
51
+ ? primitiveTokens
52
+ : semanticTokens;
53
+ tokensObject[ group ] ??= {};
54
+ tokensObject[ group ][ token.localID ] =
55
+ token.token.$description ?? 'N/A';
56
+ }
57
+
58
+ function tokensToMdTable(
59
+ tokens: TokensMap,
60
+ isPrivate: boolean = false
61
+ ) {
62
+ return Object.entries( tokens )
63
+ .map( ( [ group, tokensInGroup ] ) => [
64
+ `### ${ titleCase( group ) }${
65
+ isPrivate ? ' (private)' : ''
66
+ }`,
67
+ '',
68
+ '| Variable name | Description |',
69
+ '|---|---|',
70
+ ...Object.entries( tokensInGroup ).map(
71
+ ( [ name, description ] ) =>
72
+ `| \`${ name }\` | ${ description } |`
73
+ ),
74
+ '',
75
+ ] )
76
+ .flat( 2 );
77
+ }
78
+
79
+ outputFile(
80
+ filename,
81
+ [
82
+ '<!--',
83
+ 'This file is generated by @terrazzo/terrazzo-plugin-ds-tokens-docs.',
84
+ 'Do not edit directly.',
85
+ '-->',
86
+ '',
87
+ '# DS Tokens reference',
88
+ '',
89
+ '## Semantic tokens',
90
+ '',
91
+ ...tokensToMdTable( semanticTokens ),
92
+ '',
93
+ '## Primitive tokens',
94
+ '',
95
+ '**🚨 Note: These tokens are only private implementation details of the Theme, and should never be referenced / consumed directly in the code.**',
96
+ '',
97
+ ...tokensToMdTable( primitiveTokens, true ),
98
+ '', // final empty line
99
+ ].join( '\n' )
100
+ );
101
+ },
102
+ };
103
+ }
@@ -0,0 +1,210 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { Plugin, TokenNormalized } from '@terrazzo/parser';
5
+ import { transformCSSValue } from '@terrazzo/token-tools/css';
6
+ import Color from 'colorjs.io';
7
+
8
+ /**
9
+ * Internal dependencies
10
+ */
11
+ import { FORMAT_JSON_ID } from './lib';
12
+
13
+ function titleCase( str: string ) {
14
+ return str[ 0 ].toUpperCase() + str.slice( 1 );
15
+ }
16
+
17
+ function kebabToCamel( str: string ) {
18
+ return str.replace( /-([a-z])/g, ( _, letter ) => letter.toUpperCase() );
19
+ }
20
+
21
+ function transformTokenName( { id }: { id: string } ) {
22
+ return (
23
+ id
24
+ // Capitalize first segment
25
+ .replace( /^(\w+)\./g, ( _, g1 ) => `${ titleCase( g1 ) }/` )
26
+ // Capitalize
27
+ .replace( /primitive\./g, '_Primitives/' )
28
+ .replace( /semantic\./g, 'Semantic/' )
29
+ .replace(
30
+ /(color\/_Primitives)\/(\w+)\.(.*)/gi,
31
+ ( _, prefix, tone, rampStep ) => {
32
+ return `${ prefix }/${ titleCase( tone ) }/${ rampStep }`;
33
+ }
34
+ )
35
+ // Color-specific transformation for semantic tokens:
36
+ // - add extra folder (Background, Foreground, Stroke)
37
+ // - swap "tone" folder order, capitalize
38
+ // - limit bg-* to 6 characters
39
+ // - keep last part of the token name with dots (eg no folders)
40
+ .replace(
41
+ /(color\/Semantic)\/([\w,\-]+)\.(\w+)\.(.*)/gi,
42
+ ( _, prefix, element, tone, emphasisAndState ) => {
43
+ let extraFolder = '';
44
+ let elementName = element;
45
+ if ( /bg/.test( element ) ) {
46
+ extraFolder = 'Background/';
47
+ elementName = element.slice( 0, 6 );
48
+ } else if ( /fg/.test( element ) ) {
49
+ extraFolder = 'Foreground/';
50
+ elementName = element.slice( 0, 6 );
51
+ } else if ( /stroke/.test( element ) ) {
52
+ extraFolder = 'Stroke/';
53
+ elementName = element.slice( 0, 10 );
54
+ }
55
+ return `${ prefix }/${ extraFolder }${ titleCase(
56
+ tone
57
+ ) }/${ kebabToCamel(
58
+ elementName
59
+ ) }.${ tone }.${ emphasisAndState }`;
60
+ }
61
+ )
62
+ // Remove default emphasis and state variants from variable name
63
+ .replace( /normal\./g, '' )
64
+ .replace( /resting/g, '' )
65
+ // Remove double dots
66
+ .replace( /\.{2,}/g, '.' )
67
+ // Remove trailing dot
68
+ .replace( /\.$/g, '' )
69
+ // Replace remaining dots with dashes
70
+ .replace( /\./g, '-' )
71
+ );
72
+ }
73
+
74
+ function transformColorToken(
75
+ token: TokenNormalized,
76
+ mode: string,
77
+ tokens: Record< string, TokenNormalized >
78
+ ) {
79
+ if (
80
+ token.mode[ mode ]?.aliasChain &&
81
+ token.mode[ mode ].aliasChain.length > 0
82
+ ) {
83
+ // Keep aliases
84
+ return `{${ transformTokenName( {
85
+ id: token.mode[ mode ].aliasChain[ 0 ],
86
+ } ) }}`;
87
+ }
88
+ // Start by letting terrazzo do the heavy lifting.
89
+ const baselineCSSValue = transformCSSValue( token, {
90
+ mode,
91
+ tokensSet: tokens,
92
+ transformAlias: transformTokenName,
93
+ } );
94
+
95
+ if ( baselineCSSValue === undefined ) {
96
+ console.warn( 'Unexpected: could not tranform color token value' );
97
+ return;
98
+ }
99
+
100
+ let cssColorValue: string;
101
+
102
+ if ( typeof baselineCSSValue === 'object' ) {
103
+ if ( 'srgb' in baselineCSSValue ) {
104
+ // Pick SRGB gamut (safer compared to p3 or rec2020)
105
+ cssColorValue = baselineCSSValue.srgb;
106
+ } else {
107
+ console.log( 'UNSUPPORTED USE CASE' );
108
+ return;
109
+ }
110
+ } else {
111
+ cssColorValue = baselineCSSValue;
112
+ }
113
+
114
+ // Always convert to hex
115
+ // (easier to convert to Figma RGB, and includes clamping)
116
+ let convertedColor: Color;
117
+ try {
118
+ convertedColor = new Color( cssColorValue );
119
+ } catch {
120
+ console.warn( 'Unexpected: could not convert token value to Color' );
121
+ return;
122
+ }
123
+
124
+ return convertedColor.to( 'srgb' ).toString( { format: 'hex' } );
125
+ }
126
+
127
+ export default function pluginFigmaDsTokenManager( {
128
+ filename = 'figma-ds-tokens.json',
129
+ } = {} ): Plugin {
130
+ return {
131
+ name: '@terrazzo/plugin-figma-ds-token-manager',
132
+ async transform( { tokens, getTransforms, setTransform } ) {
133
+ // skip work if another .json plugin has already run
134
+ const jsonTokens = getTransforms( {
135
+ format: FORMAT_JSON_ID,
136
+ id: '*',
137
+ mode: '.',
138
+ } );
139
+ if ( jsonTokens.length ) {
140
+ return;
141
+ }
142
+
143
+ for ( const [ id, token ] of Object.entries( tokens ) ) {
144
+ for ( const mode of Object.keys( token.mode ) ) {
145
+ const localID = transformTokenName( token );
146
+
147
+ let transformedValue;
148
+
149
+ if ( token.$type === 'color' ) {
150
+ transformedValue = transformColorToken(
151
+ token,
152
+ mode,
153
+ tokens
154
+ );
155
+ } else if (
156
+ token.mode[ mode ]?.aliasChain &&
157
+ token.mode[ mode ].aliasChain.length > 0
158
+ ) {
159
+ // Keep aliases
160
+ transformedValue = `{${ transformTokenName( {
161
+ id: token.mode[ mode ].aliasChain[ 0 ],
162
+ } ) }}`;
163
+ } else {
164
+ // Fallback to terrazzo
165
+ transformedValue = transformCSSValue( token, {
166
+ mode,
167
+ tokensSet: tokens,
168
+ transformAlias: transformTokenName,
169
+ } );
170
+ }
171
+
172
+ if ( transformedValue !== undefined ) {
173
+ setTransform( id, {
174
+ format: FORMAT_JSON_ID,
175
+ localID,
176
+ value: transformedValue,
177
+ mode,
178
+ } );
179
+ }
180
+ }
181
+ }
182
+ },
183
+ async build( { getTransforms, outputFile } ) {
184
+ const tokenVals: Record<
185
+ string,
186
+ {
187
+ value: Record< string, string | Record< string, string > >;
188
+ description?: string;
189
+ }
190
+ > = {};
191
+
192
+ for ( const token of getTransforms( {
193
+ format: FORMAT_JSON_ID,
194
+ id: '*',
195
+ } ) ) {
196
+ if ( ! token.localID ) {
197
+ continue;
198
+ }
199
+
200
+ tokenVals[ token.localID ] ??= { value: {}, description: '' };
201
+
202
+ tokenVals[ token.localID ].value[ token.mode ] = token.value;
203
+ tokenVals[ token.localID ].description =
204
+ token.token.$description;
205
+ }
206
+
207
+ outputFile( filename, JSON.stringify( tokenVals, null, 2 ) );
208
+ },
209
+ };
210
+ }
@@ -0,0 +1 @@
1
+ export const FORMAT_JSON_ID = 'json';
@@ -0,0 +1,72 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { FORMAT_ID } from '@terrazzo/plugin-css';
5
+ import type { Plugin } from '@terrazzo/parser';
6
+
7
+ function isPrivateToken( token: string ) {
8
+ return /-private-/i.test( token );
9
+ }
10
+
11
+ export default function pluginKnownWpdsCssVariables( {
12
+ exports = [ { filename: 'design-tokens.js', modes: false } ],
13
+ } = {} ): Plugin {
14
+ return {
15
+ name: '@terrazzo/plugin-known-wpds-css-variables',
16
+ async build( { getTransforms, outputFile } ) {
17
+ // Either a string (modes=false) or an object (modes=true)
18
+ const tokensToExport: Record<
19
+ string,
20
+ Record< string, string | Record< string, string > >
21
+ > = {};
22
+
23
+ for ( const token of getTransforms( {
24
+ format: FORMAT_ID,
25
+ id: '*',
26
+ } ) ) {
27
+ if ( ! token.localID ) {
28
+ console.warn(
29
+ 'Unexpected — Missing local ID when building token list for eslint plugin'
30
+ );
31
+ continue;
32
+ }
33
+
34
+ if ( ! isPrivateToken( token.localID ) ) {
35
+ tokensToExport[ token.localID ] ??= {};
36
+
37
+ tokensToExport[ token.localID ][ token.mode ] = token.value;
38
+ }
39
+ }
40
+
41
+ const exportsAsArray = ! Array.isArray( exports )
42
+ ? [ exports ]
43
+ : exports;
44
+ for ( const { filename, modes } of exportsAsArray ) {
45
+ outputFile(
46
+ filename,
47
+ [
48
+ '/**',
49
+ ' * This file is generated by the @terrazzo/plugin-known-wpds-css-variables plugin.',
50
+ ' * Do not edit this file directly.',
51
+ ' */',
52
+ '',
53
+ `export default ${ JSON.stringify(
54
+ modes === false
55
+ ? Array.from(
56
+ new Set( Object.keys( tokensToExport ) )
57
+ )
58
+ : tokensToExport,
59
+ null,
60
+ 2
61
+ ) }${
62
+ filename.endsWith( '.ts' ) && modes
63
+ ? ' as Record< string, Record< string, string > >'
64
+ : ''
65
+ }`,
66
+ '', // final empty line
67
+ ].join( '\n' )
68
+ );
69
+ }
70
+ },
71
+ };
72
+ }