@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,247 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import type { CSSProperties } from 'react';
5
+ import Color from 'colorjs.io';
6
+
7
+ /**
8
+ * WordPress dependencies
9
+ */
10
+ import { useMemo, useContext } from '@wordpress/element';
11
+
12
+ /**
13
+ * Internal dependencies
14
+ */
15
+ import { ThemeContext } from './context';
16
+ import semanticVariables from './prebuilt/ts/design-tokens';
17
+ import {
18
+ buildBgRamp,
19
+ buildAccentRamp,
20
+ DEFAULT_SEED_COLORS,
21
+ type RampResult,
22
+ } from './color-ramps';
23
+ import type { ThemeProviderProps } from './types';
24
+
25
+ type Entry = [ string, string ];
26
+
27
+ const legacyWpComponentsOverridesCSS: Entry[] = [
28
+ [ '--wp-components-color-accent', 'var(--wp-admin-theme-color)' ],
29
+ [
30
+ '--wp-components-color-accent-darker-10',
31
+ 'var(--wp-admin-theme-color-darker-10)',
32
+ ],
33
+ [
34
+ '--wp-components-color-accent-darker-20',
35
+ 'var(--wp-admin-theme-color-darker-20)',
36
+ ],
37
+ [
38
+ '--wp-components-color-accent-inverted',
39
+ 'var(--wpds-color-fg-interactive-brand-strong)',
40
+ ],
41
+ [
42
+ '--wp-components-color-background',
43
+ 'var(--wpds-color-bg-surface-neutral-strong)',
44
+ ],
45
+ [
46
+ '--wp-components-color-foreground',
47
+ 'var(--wpds-color-fg-content-neutral)',
48
+ ],
49
+ [
50
+ '--wp-components-color-foreground-inverted',
51
+ 'var(--wpds-color-bg-surface-neutral)',
52
+ ],
53
+ [
54
+ '--wp-components-color-gray-100',
55
+ 'var(--wpds-color-bg-surface-neutral)',
56
+ ],
57
+ [
58
+ '--wp-components-color-gray-200',
59
+ 'var(--wpds-color-stroke-surface-neutral)',
60
+ ],
61
+ [
62
+ '--wp-components-color-gray-300',
63
+ 'var(--wpds-color-stroke-surface-neutral)',
64
+ ],
65
+ [
66
+ '--wp-components-color-gray-400',
67
+ 'var(--wpds-color-stroke-interactive-neutral)',
68
+ ],
69
+ [
70
+ '--wp-components-color-gray-600',
71
+ 'var(--wpds-color-stroke-interactive-neutral)',
72
+ ],
73
+ [
74
+ '--wp-components-color-gray-700',
75
+ 'var(--wpds-color-fg-content-neutral-weak)',
76
+ ],
77
+ [
78
+ '--wp-components-color-gray-800',
79
+ 'var(--wpds-color-fg-content-neutral)',
80
+ ],
81
+ ];
82
+
83
+ function customRgbFormat( color: Color ) {
84
+ const rgb = color.to( 'srgb' );
85
+ return [ rgb.r, rgb.g, rgb.b ]
86
+ .map( ( n ) => Math.round( n * 255 ) )
87
+ .join( ', ' );
88
+ }
89
+
90
+ function legacyWpAdminThemeOverridesCSS( accent: string ): Entry[] {
91
+ const parsedAccent = new Color( accent ).to( 'hsl' );
92
+
93
+ const hsl = parsedAccent.coords;
94
+ const darker10 = new Color( 'hsl', [
95
+ hsl[ 0 ], // h
96
+ hsl[ 1 ], // s
97
+ Math.max( 0, Math.min( 100, hsl[ 2 ] - 5 ) ), // l (reduced by 5%)
98
+ ] ).to( 'srgb' );
99
+ const darker20 = new Color( 'hsl', [
100
+ hsl[ 0 ], // h
101
+ hsl[ 1 ], // s
102
+ Math.max( 0, Math.min( 100, hsl[ 2 ] - 10 ) ), // l (reduced by 10%)
103
+ ] ).to( 'srgb' );
104
+
105
+ return [
106
+ [
107
+ '--wp-admin-theme-color',
108
+ parsedAccent.to( 'srgb' ).toString( { format: 'hex' } ),
109
+ ],
110
+ [ '--wp-admin-theme-color--rgb', customRgbFormat( parsedAccent ) ],
111
+ [
112
+ '--wp-admin-theme-color-darker-10',
113
+ darker10.toString( { format: 'hex' } ),
114
+ ],
115
+ [
116
+ '--wp-admin-theme-color-darker-10--rgb',
117
+ customRgbFormat( darker10 ),
118
+ ],
119
+ [
120
+ '--wp-admin-theme-color-darker-20',
121
+ darker20.toString( { format: 'hex' } ),
122
+ ],
123
+ [
124
+ '--wp-admin-theme-color-darker-20--rgb',
125
+ customRgbFormat( darker20 ),
126
+ ],
127
+ ];
128
+ }
129
+
130
+ function semanticTokensCSS(
131
+ filterFn: ( entry: [ string, Record< string, string > ] ) => boolean = () =>
132
+ true
133
+ ): Entry[] {
134
+ return Object.entries( semanticVariables )
135
+ .filter( filterFn )
136
+ .map( ( [ variableName, modesAndValues ] ) => [
137
+ variableName,
138
+ modesAndValues[ '.' ],
139
+ ] );
140
+ }
141
+
142
+ const toKebabCase = ( str: string ) =>
143
+ str.replace(
144
+ /[A-Z]+(?![a-z])|[A-Z]/g,
145
+ ( $, ofs ) => ( ofs ? '-' : '' ) + $.toLowerCase()
146
+ );
147
+
148
+ function colorRampCSS( ramp: RampResult, prefix: string ): Entry[] {
149
+ return [ ...Object.entries( ramp.ramp ) ].map(
150
+ ( [ tokenName, tokenValue ] ) => [
151
+ `${ prefix }${ toKebabCase( tokenName ) }`,
152
+ tokenValue.color,
153
+ ]
154
+ );
155
+ }
156
+
157
+ function generateStyles( {
158
+ primary,
159
+ computedColorRamps,
160
+ }: {
161
+ primary: string;
162
+ computedColorRamps: Map< string, RampResult >;
163
+ } ): CSSProperties {
164
+ return Object.fromEntries(
165
+ [
166
+ // Primitive tokens
167
+ Array.from( computedColorRamps )
168
+ .map( ( [ rampName, computedColorRamp ] ) => [
169
+ colorRampCSS(
170
+ computedColorRamp,
171
+ `--wpds-color-private-${ rampName }-`
172
+ ),
173
+ ] )
174
+ .flat( 2 ),
175
+ // Semantic color tokens (other semantic tokens for now are static)
176
+ semanticTokensCSS( ( [ key ] ) => /color/.test( key ) ),
177
+ // Legacy overrides
178
+ legacyWpAdminThemeOverridesCSS( primary ),
179
+ legacyWpComponentsOverridesCSS,
180
+ ].flat()
181
+ );
182
+ }
183
+
184
+ export function useThemeProviderStyles( {
185
+ color = {},
186
+ }: {
187
+ color?: ThemeProviderProps[ 'color' ];
188
+ } = {} ) {
189
+ const { resolvedSettings: inheritedSettings } = useContext( ThemeContext );
190
+
191
+ // Compute settings:
192
+ // - used provided prop value;
193
+ // - otherwise, use inherited value from parent instance;
194
+ // - otherwise, use fallback value (where applicable).
195
+ const primary =
196
+ color.primary ??
197
+ inheritedSettings.color?.primary ??
198
+ DEFAULT_SEED_COLORS.primary;
199
+ const bg =
200
+ color.bg ?? inheritedSettings.color?.bg ?? DEFAULT_SEED_COLORS.bg;
201
+
202
+ const resolvedSettings = useMemo(
203
+ () => ( {
204
+ color: {
205
+ primary,
206
+ bg,
207
+ },
208
+ } ),
209
+ [ primary, bg ]
210
+ );
211
+
212
+ const themeProviderStyles = useMemo( () => {
213
+ // Determine which seeds are needed for generating ramps.
214
+ const seeds = {
215
+ ...DEFAULT_SEED_COLORS,
216
+ bg,
217
+ primary,
218
+ };
219
+
220
+ // Generate ramps.
221
+ const computedColorRamps = new Map< string, RampResult >();
222
+ const bgRamp = buildBgRamp( { seed: seeds.bg } );
223
+ Object.entries( seeds ).forEach( ( [ rampName, seed ] ) => {
224
+ if ( rampName === 'bg' ) {
225
+ computedColorRamps.set( rampName, bgRamp );
226
+ } else {
227
+ computedColorRamps.set(
228
+ rampName,
229
+ buildAccentRamp( {
230
+ seed,
231
+ bgRamp,
232
+ } )
233
+ );
234
+ }
235
+ } );
236
+
237
+ return generateStyles( {
238
+ primary: seeds.primary,
239
+ computedColorRamps,
240
+ } );
241
+ }, [ primary, bg ] );
242
+
243
+ return {
244
+ resolvedSettings,
245
+ themeProviderStyles,
246
+ };
247
+ }
@@ -0,0 +1,102 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { defineConfig } from '@terrazzo/cli';
5
+ import pluginCSS from '@terrazzo/plugin-css';
6
+ import { makeCSSVar } from '@terrazzo/token-tools/css';
7
+ /**
8
+ * Internal dependencies
9
+ */
10
+ import pluginFigmaDsTokenManager from './bin/terrazzo-plugin-figma-ds-token-manager/index';
11
+ import pluginKnownWpdsCssVariables from './bin/terrazzo-plugin-known-wpds-css-variables/index';
12
+ import pluginDsTokenDocs from './bin/terrazzo-plugin-ds-tokens-docs/index';
13
+
14
+ export default defineConfig( {
15
+ tokens: [
16
+ './tokens/border.json',
17
+ './tokens/color.json',
18
+ './tokens/elevation.json',
19
+ './tokens/spacing.json',
20
+ './tokens/typography.json',
21
+ ],
22
+ outDir: './src/prebuilt',
23
+
24
+ plugins: [
25
+ pluginCSS( {
26
+ filename: 'css/design-tokens.css',
27
+ variableName: ( token ) =>
28
+ makeCSSVar(
29
+ `wpds.${ token.id
30
+ .replace( /normal/g, '' )
31
+ .replace( /resting/g, '' )
32
+ .replace( /primitive/g, 'private' )
33
+ .replace( /semantic/g, '' ) }`
34
+ ),
35
+ baseSelector: ':root',
36
+ modeSelectors: [
37
+ {
38
+ mode: 'high-dpi',
39
+ selectors: [
40
+ '@media ( -webkit-min-device-pixel-ratio: 2 ), ( min-resolution: 192dpi )',
41
+ ],
42
+ },
43
+ ],
44
+ legacyHex: true,
45
+ } ),
46
+ pluginFigmaDsTokenManager( {
47
+ filename: 'json/figma.json',
48
+ } ),
49
+ pluginKnownWpdsCssVariables( {
50
+ exports: [
51
+ { filename: 'js/design-tokens.js', modes: false },
52
+ { filename: 'ts/design-tokens.ts', modes: true },
53
+ ],
54
+ } ),
55
+ pluginDsTokenDocs( {
56
+ filename: '../../docs/ds-tokens.md',
57
+ } ),
58
+ ],
59
+
60
+ // Linter rules current error when multiple entry files are used
61
+ // See https://github.com/terrazzoapp/terrazzo/issues/505
62
+ // lint: {
63
+ // rules: {
64
+ // 'a11y/min-contrast': [
65
+ // 'error',
66
+ // {
67
+ // level: 'AA',
68
+ // pairs: [
69
+ // // Standard BG / FG pairs
70
+ // ...[
71
+ // 'color.primitive.neutral.1',
72
+ // 'color.primitive.neutral.2',
73
+ // 'color.primitive.neutral.3',
74
+ // 'color.primitive.primary.1',
75
+ // 'color.primitive.primary.2',
76
+ // 'color.primitive.primary.3',
77
+ // ].flatMap( ( bgToken ) =>
78
+ // [
79
+ // 'color.primitive.neutral.11',
80
+ // 'color.primitive.neutral.12',
81
+ // 'color.primitive.primary.11',
82
+ // 'color.primitive.primary.12',
83
+ // ].map( ( fgToken ) => ( {
84
+ // foreground: fgToken,
85
+ // background: bgToken,
86
+ // } ) )
87
+ // ),
88
+ // // Action pairs (ie. using step 9 as background)
89
+ // {
90
+ // foreground: 'color.primitive.primary.contrast',
91
+ // background: 'color.primitive.primary.9',
92
+ // },
93
+ // {
94
+ // foreground: 'color.primitive.primary.1',
95
+ // background: 'color.primitive.primary.9',
96
+ // },
97
+ // ],
98
+ // },
99
+ // ],
100
+ // },
101
+ // },
102
+ } );
@@ -0,0 +1,34 @@
1
+ {
2
+ "border": {
3
+ "$type": "dimension",
4
+ "radius": {
5
+ "x-small": {
6
+ "$value": { "value": 1, "unit": "px" },
7
+ "$description": "Extra small radius"
8
+ },
9
+ "small": {
10
+ "$value": { "value": 2, "unit": "px" },
11
+ "$description": "Small radius"
12
+ },
13
+ "medium": {
14
+ "$value": { "value": 4, "unit": "px" },
15
+ "$description": "Medium radius"
16
+ },
17
+ "large": {
18
+ "$value": { "value": 8, "unit": "px" },
19
+ "$description": "Large radius"
20
+ }
21
+ },
22
+ "width": {
23
+ "focus": {
24
+ "$value": { "value": 2, "unit": "px" },
25
+ "$description": "Border width for focus ring",
26
+ "$extensions": {
27
+ "mode": {
28
+ "high-dpi": { "value": 1.5, "unit": "px" }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }