@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
@@ -0,0 +1,426 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { Meta, StoryObj } from '@storybook/react';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import {
10
+ useEffect,
11
+ useState,
12
+ useRef,
13
+ useId,
14
+ createPortal,
15
+ } from '@wordpress/element';
16
+
17
+ /**
18
+ * Internal dependencies
19
+ */
20
+ import { ThemeProvider } from '../theme-provider';
21
+
22
+ const meta: Meta< typeof ThemeProvider > = {
23
+ title: 'Design System/Theme Provider',
24
+ component: ThemeProvider,
25
+ args: {
26
+ isRoot: true,
27
+ },
28
+ argTypes: {
29
+ children: {
30
+ control: false,
31
+ },
32
+ },
33
+ parameters: {
34
+ controls: { expanded: true },
35
+ docs: { canvas: { sourceState: 'shown' } },
36
+ },
37
+ tags: [ 'status-experimental' ],
38
+ };
39
+ export default meta;
40
+
41
+ function getCSSCustomPropsFromStylesheets() {
42
+ const primitiveProps: Record< string, string > = {};
43
+ const semanticProps: Record< string, string > = {};
44
+ const legacyProps: Record< string, string > = {};
45
+
46
+ for ( const sheet of document.styleSheets ) {
47
+ try {
48
+ for ( const rule of sheet.cssRules || [] ) {
49
+ const ruleStyle = ( rule as CSSStyleRule ).style;
50
+ if ( ruleStyle ) {
51
+ for ( const name of ruleStyle ) {
52
+ if (
53
+ name.startsWith( '--wp-admin-theme' ) ||
54
+ name.startsWith( '--wp-components-color' )
55
+ ) {
56
+ legacyProps[ name ] = ruleStyle
57
+ .getPropertyValue( name )
58
+ .trim();
59
+ }
60
+ if ( name.startsWith( '--wpds-color' ) ) {
61
+ if ( name.includes( 'private' ) ) {
62
+ primitiveProps[ name ] = ruleStyle
63
+ .getPropertyValue( name )
64
+ .trim();
65
+ } else {
66
+ semanticProps[ name ] = ruleStyle
67
+ .getPropertyValue( name )
68
+ .trim();
69
+ }
70
+ }
71
+ }
72
+ }
73
+ }
74
+ } catch ( e ) {
75
+ // Avoid security errors from cross-origin stylesheets
76
+ // eslint-disable-next-line no-console
77
+ console.error( e );
78
+ continue;
79
+ }
80
+ }
81
+
82
+ return { primitiveProps, semanticProps, legacyProps };
83
+ }
84
+
85
+ const ColorTokenTable = ( {
86
+ tokens,
87
+ }: {
88
+ tokens: Record< string, string >;
89
+ } ) => {
90
+ return (
91
+ <ul
92
+ style={ {
93
+ listStyle: 'none',
94
+ display: 'flex',
95
+ flexDirection: 'column',
96
+ gap: '0.5rem',
97
+ } }
98
+ >
99
+ { Object.entries( tokens ).map( ( [ name ] ) => (
100
+ <li
101
+ key={ name }
102
+ style={ {
103
+ display: 'grid',
104
+ gridTemplateColumns: '80px 1fr',
105
+ alignItems: 'center',
106
+ gap: '0.5rem',
107
+ } }
108
+ >
109
+ <span
110
+ style={ {
111
+ backgroundColor: `var(${ name })`,
112
+ border: '1px solid var(--wpds-color-stroke-surface-neutral)',
113
+ width: '100%',
114
+ aspectRatio: '2/1',
115
+ display: 'block',
116
+ } }
117
+ aria-label={ name }
118
+ ></span>
119
+ <code>{ name }</code>
120
+ </li>
121
+ ) ) }
122
+ </ul>
123
+ );
124
+ };
125
+
126
+ const DSTokensList = () => {
127
+ const [ props, setProps ] = useState< {
128
+ semanticProps: Record< string, string >;
129
+ primitiveProps: Record< string, string >;
130
+ legacyProps: Record< string, string >;
131
+ } >( {
132
+ semanticProps: {},
133
+ primitiveProps: {},
134
+ legacyProps: {},
135
+ } );
136
+
137
+ useEffect( () => {
138
+ setProps( getCSSCustomPropsFromStylesheets() );
139
+ }, [] );
140
+
141
+ return (
142
+ <div style={ { color: 'var( --wpds-color-fg-content-neutral )' } }>
143
+ <h1>DS Color tokens</h1>
144
+ <h2>Semantic tokens (can be consumed directly)</h2>
145
+ <ColorTokenTable tokens={ props.semanticProps } />
146
+ <h2>Primitive tokens (should not be consumed directly)</h2>
147
+ <details>
148
+ <summary>Click to expand</summary>
149
+ <ColorTokenTable tokens={ props.primitiveProps } />
150
+ </details>
151
+ <h2>Legacy tokens (should not be consumed directly)</h2>
152
+ <details>
153
+ <summary>Click to expand</summary>
154
+ <ColorTokenTable tokens={ props.legacyProps } />
155
+ </details>
156
+ </div>
157
+ );
158
+ };
159
+
160
+ export const Default: StoryObj< typeof ThemeProvider > = {
161
+ args: {
162
+ children: <DSTokensList />,
163
+ },
164
+ };
165
+
166
+ export const WithPicker: StoryObj< typeof ThemeProvider > = {
167
+ render: ( args ) => {
168
+ const id = useId();
169
+ const [ primary, setPrimary ] = useState< undefined | string >();
170
+
171
+ return (
172
+ <ThemeProvider
173
+ { ...args }
174
+ color={ {
175
+ primary,
176
+ } }
177
+ >
178
+ <div style={ { position: 'relative' } }>
179
+ <div
180
+ style={ {
181
+ position: 'sticky',
182
+ top: 0,
183
+ right: 0,
184
+ backgroundColor:
185
+ 'var(--wpds-color-bg-surface-neutral)',
186
+ color: 'var( --wpds-color-fg-content-neutral )',
187
+ padding: '0.5rem',
188
+ borderRadius: '0.5rem',
189
+ boxShadow: '0 0 0.5rem 0 rgba(0, 0, 0, 0.1)',
190
+ } }
191
+ >
192
+ <div>
193
+ <input
194
+ type="color"
195
+ id={ id }
196
+ name="primary"
197
+ value={ primary }
198
+ onChange={ ( e ) =>
199
+ setPrimary( e.target.value )
200
+ }
201
+ />
202
+ <label htmlFor={ id }>Pick the primary color</label>
203
+ </div>
204
+ </div>
205
+ { args.children }
206
+ </div>
207
+ </ThemeProvider>
208
+ );
209
+ },
210
+ args: {
211
+ children: <DSTokensList />,
212
+ },
213
+ };
214
+
215
+ const NestingDebug = ( { bg = '', primary = '' } ) => (
216
+ <div
217
+ style={ {
218
+ padding: '0.25rem',
219
+ color: 'var(--wpds-color-fg-content-neutral)',
220
+ backgroundColor: 'var(--wpds-color-bg-surface-neutral)',
221
+ display: 'flex',
222
+ alignItems: 'center',
223
+ flexWrap: 'wrap',
224
+ gap: '1rem',
225
+ } }
226
+ >
227
+ <pre>
228
+ bg: { bg } | primary: { primary }
229
+ </pre>
230
+ <span
231
+ style={ {
232
+ display: 'inline-block',
233
+ padding: '0.25rem',
234
+ borderRadius: '0.25rem',
235
+ backgroundColor:
236
+ 'var(--wpds-color-bg-interactive-brand-strong)',
237
+ color: 'var(--wpds-color-fg-interactive-brand-strong)',
238
+ } }
239
+ >
240
+ primary
241
+ </span>
242
+ <span
243
+ style={ {
244
+ display: 'inline-block',
245
+ marginInlineStart: '0.25rem',
246
+ padding: '0.25rem',
247
+ borderRadius: '0.25rem',
248
+ backgroundColor:
249
+ 'var(--wpds-color-bg-interactive-brand-weak-disabled)',
250
+ color: 'var(--wpds-color-fg-content-neutral)',
251
+ } }
252
+ >
253
+ Neutral
254
+ </span>
255
+ </div>
256
+ );
257
+
258
+ export const NestingAndInheriting: StoryObj< typeof ThemeProvider > = {
259
+ render: () => {
260
+ return (
261
+ <ThemeProvider>
262
+ <NestingDebug bg="inherit (root)" primary="inherit (root)" />
263
+ <div style={ { paddingInlineStart: '1rem' } }>
264
+ <ThemeProvider
265
+ color={ {
266
+ bg: '#1e1e1e',
267
+ } }
268
+ >
269
+ <NestingDebug bg="#1e1e1e" primary="inherit (root)" />
270
+ <div style={ { paddingInlineStart: '1rem' } }>
271
+ <ThemeProvider>
272
+ <NestingDebug
273
+ bg="inherit (#1e1e1e)"
274
+ primary="inherit (root)"
275
+ />
276
+ <div style={ { paddingInlineStart: '1rem' } }>
277
+ <ThemeProvider
278
+ color={ { primary: 'hotpink' } }
279
+ >
280
+ <NestingDebug
281
+ bg="inherit (#1e1e1e)"
282
+ primary="hotpink"
283
+ />
284
+ <div
285
+ style={ {
286
+ paddingInlineStart: '1rem',
287
+ } }
288
+ >
289
+ <ThemeProvider
290
+ color={ { bg: '#f8f8f8' } }
291
+ >
292
+ <NestingDebug
293
+ bg="#f8f8f8"
294
+ primary="inherit (hotpink)"
295
+ />
296
+ </ThemeProvider>
297
+ </div>
298
+ </ThemeProvider>
299
+ </div>
300
+ </ThemeProvider>
301
+ </div>
302
+ </ThemeProvider>
303
+ </div>
304
+ </ThemeProvider>
305
+ );
306
+ },
307
+ };
308
+
309
+ function IframeWithClonedTokenStyles( {
310
+ children,
311
+ }: {
312
+ children: React.ReactNode;
313
+ } ) {
314
+ const iframeRef = useRef< HTMLIFrameElement >( null );
315
+ const [ iframeLoaded, setIframeLoaded ] = useState( false );
316
+
317
+ // Copy the stylesheet where the DS tokens are defined to the iframe.
318
+ // While this technique is a bit hacky, it works well enough for the purpose
319
+ // of this demo.
320
+ // Consumers of the DS could instead reference the stylesheet directly.
321
+ useEffect( () => {
322
+ const iframe = iframeRef.current;
323
+ if ( ! iframe || ! iframe.contentDocument ) {
324
+ return;
325
+ }
326
+
327
+ const head = iframe.contentDocument.head;
328
+
329
+ // Filter styles associated with a theme provider
330
+ const allStyles = Array.from(
331
+ document.head.querySelectorAll( 'style, link[rel="stylesheet"]' )
332
+ );
333
+
334
+ allStyles.forEach( ( node ) => {
335
+ if ( node.tagName === 'STYLE' ) {
336
+ const text = node.textContent || '';
337
+ if ( text.includes( 'data-wpds-theme-provider-id' ) ) {
338
+ head.appendChild( node.cloneNode( true ) );
339
+ }
340
+ } else if ( node.tagName === 'LINK' ) {
341
+ // Fetch and inspect the stylesheet content
342
+ const href = ( node as HTMLLinkElement ).href;
343
+ fetch( href )
344
+ .then( ( res ) => res.text() )
345
+ .then( ( css ) => {
346
+ if ( css.includes( 'data-wpds-theme-provider-id' ) ) {
347
+ const linkClone = node.cloneNode( true );
348
+ head.appendChild( linkClone );
349
+ }
350
+ } )
351
+ .catch( ( err ) => {
352
+ // eslint-disable-next-line no-console
353
+ console.warn( 'Failed to load stylesheet:', href, err );
354
+ } );
355
+ }
356
+ } );
357
+
358
+ setIframeLoaded( true );
359
+ }, [] );
360
+
361
+ return (
362
+ <iframe
363
+ ref={ iframeRef }
364
+ style={ {
365
+ width: '100%',
366
+ height: '400px',
367
+ border: '1px solid #ccc',
368
+ } }
369
+ title="demo"
370
+ >
371
+ { iframeLoaded &&
372
+ iframeRef.current?.contentDocument?.body &&
373
+ createPortal(
374
+ children,
375
+ iframeRef.current.contentDocument.body
376
+ ) }
377
+ </iframe>
378
+ );
379
+ }
380
+
381
+ export const AcrossIframes: StoryObj< typeof ThemeProvider > = {
382
+ render: ( args ) => {
383
+ return (
384
+ <ThemeProvider { ...args }>
385
+ { args.children }
386
+
387
+ <IframeWithClonedTokenStyles>
388
+ <div
389
+ style={ {
390
+ color: 'var(--wpds-color-fg-content-neutral)',
391
+ } }
392
+ >
393
+ In the iframe, but outside of `ThemeProvider`
394
+ </div>
395
+ <ThemeProvider
396
+ { ...args }
397
+ // Note: the isRoot prop is necessary to apply the DS tokens to any
398
+ // UI rendered outside of the ThemeProvider (including overlays, etc)
399
+ isRoot
400
+ >
401
+ { args.children }
402
+ </ThemeProvider>
403
+ </IframeWithClonedTokenStyles>
404
+ </ThemeProvider>
405
+ );
406
+ },
407
+ args: {
408
+ children: (
409
+ <div style={ { color: 'var(--wpds-color-fg-content-neutral)' } }>
410
+ Code is poetry.{ ' ' }
411
+ <span
412
+ style={ {
413
+ display: 'inline-block',
414
+ padding: '0.25rem',
415
+ borderRadius: '0.25rem',
416
+ backgroundColor:
417
+ 'var(--wpds-color-bg-interactive-brand-strong)',
418
+ color: 'var(--wpds-color-fg-interactive-brand-strong)',
419
+ } }
420
+ >
421
+ primary
422
+ </span>
423
+ </div>
424
+ ),
425
+ },
426
+ };
@@ -0,0 +1,3 @@
1
+ .root {
2
+ display: contents;
3
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { CSSProperties } from 'react';
5
+
6
+ /**
7
+ * WordPress dependencies
8
+ */
9
+ import { useMemo, useId } from '@wordpress/element';
10
+
11
+ /**
12
+ * Internal dependencies
13
+ */
14
+ import { ThemeContext } from './context';
15
+ import { useThemeProviderStyles } from './use-theme-provider-styles';
16
+ import { type ThemeProviderProps } from './types';
17
+ import styles from './style.module.css';
18
+
19
+ function cssObjectToText( values: CSSProperties ) {
20
+ return Object.entries( values )
21
+ .map( ( [ key, value ] ) => `${ key }: ${ value };` )
22
+ .join( '' );
23
+ }
24
+
25
+ function generateCSSSelector( {
26
+ instanceId,
27
+ isRoot,
28
+ }: {
29
+ instanceId: string;
30
+ isRoot: boolean;
31
+ } ) {
32
+ const rootSel = `[data-wpds-root-provider="true"]`;
33
+ const instanceIdSel = `[data-wpds-theme-provider-id="${ instanceId }"]`;
34
+
35
+ const selectors = [];
36
+
37
+ if ( isRoot ) {
38
+ selectors.push(
39
+ `:root:has(.${ styles.root }${ rootSel }${ instanceIdSel })`
40
+ );
41
+ }
42
+
43
+ selectors.push( `.${ styles.root }.${ styles.root }${ instanceIdSel }` );
44
+
45
+ return selectors.join( ',' );
46
+ }
47
+
48
+ export const ThemeProvider = ( {
49
+ children,
50
+ color = {},
51
+ isRoot = false,
52
+ }: ThemeProviderProps ) => {
53
+ const instanceId = useId();
54
+
55
+ const { themeProviderStyles, resolvedSettings } = useThemeProviderStyles( {
56
+ color,
57
+ } );
58
+
59
+ const contextValue = useMemo(
60
+ () => ( {
61
+ resolvedSettings,
62
+ } ),
63
+ [ resolvedSettings ]
64
+ );
65
+
66
+ return (
67
+ <>
68
+ { themeProviderStyles ? (
69
+ <style>
70
+ { `${ generateCSSSelector( {
71
+ instanceId,
72
+ isRoot,
73
+ } ) } {${ cssObjectToText( themeProviderStyles ) }}` }
74
+ </style>
75
+ ) : null }
76
+ <div
77
+ data-wpds-theme-provider-id={ instanceId }
78
+ data-wpds-root-provider={ isRoot }
79
+ className={ styles.root }
80
+ >
81
+ <ThemeContext.Provider value={ contextValue }>
82
+ { children }
83
+ </ThemeContext.Provider>
84
+ </div>
85
+ </>
86
+ );
87
+ };
@@ -0,0 +1,4 @@
1
+ declare module '*.module.css' {
2
+ const classes: { [ key: string ]: string };
3
+ export default classes;
4
+ }
package/src/types.ts ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { type ReactNode } from 'react';
5
+
6
+ export interface ThemeProviderSettings {
7
+ /**
8
+ * The set of color options to apply to the theme.
9
+ */
10
+ color?: {
11
+ /**
12
+ * The primary seed color to use for the theme.
13
+ *
14
+ * By default, it inherits from parent `ThemeProvider`,
15
+ * and fallbacks to statically built CSS.
16
+ */
17
+ primary?: string;
18
+ /**
19
+ * The background seed color to use for the theme.
20
+ *
21
+ * By default, it inherits from parent `ThemeProvider`,
22
+ * and fallbacks to statically built CSS.
23
+ */
24
+ bg?: string;
25
+ };
26
+ }
27
+
28
+ export interface ThemeProviderProps extends ThemeProviderSettings {
29
+ /**
30
+ * The children to render.
31
+ */
32
+ children?: ReactNode;
33
+
34
+ /**
35
+ * When a ThemeProvider is the root provider, it will apply its theming
36
+ * settings also to the root document element (e.g. the html element).
37
+ * This is useful, for example, to make sure that the `html` element can
38
+ * consume the right background color, or that overlays rendered inside a
39
+ * portal can inherit the correct color scheme.
40
+ *
41
+ * @default false
42
+ */
43
+ isRoot?: boolean;
44
+ }