@hanzo/design 0.4.0 → 0.4.2
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.
- package/components/core/Avatar.jsx +1 -1
- package/components/core/Badge.jsx +10 -7
- package/components/core/Button.jsx +35 -10
- package/components/core/Card.jsx +20 -5
- package/components/core/Progress.jsx +1 -1
- package/components/core/Table.jsx +2 -2
- package/components/forms/Checkbox.jsx +1 -1
- package/components/forms/Input.jsx +15 -5
- package/components/forms/Select.jsx +3 -3
- package/components/forms/Slider.jsx +1 -1
- package/components/forms/Textarea.jsx +3 -3
- package/components/navigation/Accordion.jsx +2 -2
- package/components/navigation/Tabs.jsx +18 -7
- package/components/navigation/navigation.card.html +1 -1
- package/components/overlays/Dialog.jsx +2 -2
- package/components/overlays/DropdownMenu.jsx +10 -6
- package/components/overlays/Sheet.jsx +3 -3
- package/components/overlays/Toaster.jsx +1 -1
- package/dist/tokens.gen.d.ts +126 -76
- package/dist/tokens.gen.d.ts.map +1 -1
- package/dist/tokens.gen.js +126 -76
- package/package.json +1 -1
- package/scripts/check-tokens.mjs +71 -4
- package/src/tokens.gen.ts +126 -76
- package/styles.css +486 -84
- package/tailwind.css +486 -84
- package/tokens/base.css +146 -5
- package/tokens/colors.css +181 -66
- package/tokens/elevation.css +76 -5
- package/tokens/motion.css +15 -0
- package/tokens/spacing.css +46 -7
- package/tokens/typography.css +21 -0
package/dist/tokens.gen.js
CHANGED
|
@@ -19,54 +19,68 @@ export const colors = {
|
|
|
19
19
|
'hanzo-black': '#0A0A0B',
|
|
20
20
|
'hanzo-white': '#FFFFFF',
|
|
21
21
|
'white-05': 'rgb(255 255 255 / .05)',
|
|
22
|
+
'white-06': 'rgb(255 255 255 / .06)',
|
|
23
|
+
'white-08': 'rgb(255 255 255 / .08)',
|
|
22
24
|
'white-10': 'rgb(255 255 255 / .10)',
|
|
23
25
|
'white-15': 'rgb(255 255 255 / .15)',
|
|
26
|
+
'white-16': 'rgb(255 255 255 / .16)',
|
|
24
27
|
'white-20': 'rgb(255 255 255 / .20)',
|
|
28
|
+
'white-22': 'rgb(255 255 255 / .22)',
|
|
25
29
|
'white-30': 'rgb(255 255 255 / .30)',
|
|
26
30
|
'white-40': 'rgb(255 255 255 / .40)',
|
|
31
|
+
'white-55': 'rgb(255 255 255 / .55)',
|
|
27
32
|
'white-60': 'rgb(255 255 255 / .60)',
|
|
33
|
+
'white-78': 'rgb(255 255 255 / .78)',
|
|
28
34
|
'white-80': 'rgb(255 255 255 / .80)',
|
|
29
|
-
'background': '#
|
|
30
|
-
'foreground': '#
|
|
31
|
-
'card': '#
|
|
32
|
-
'card-foreground': '#
|
|
33
|
-
'popover': '#
|
|
34
|
-
'popover-foreground': '#
|
|
35
|
-
'primary': '#
|
|
36
|
-
'primary-
|
|
37
|
-
'
|
|
38
|
-
'secondary
|
|
39
|
-
'
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'
|
|
35
|
+
'background': '#0a0a0a',
|
|
36
|
+
'foreground': '#fafafa',
|
|
37
|
+
'card': '#0f0f0f',
|
|
38
|
+
'card-foreground': '#fafafa',
|
|
39
|
+
'popover': '#0f0f0f',
|
|
40
|
+
'popover-foreground': '#fafafa',
|
|
41
|
+
'primary': '#fafafa',
|
|
42
|
+
'primary-hover': '#e5e5e5',
|
|
43
|
+
'primary-foreground': '#0a0a0a',
|
|
44
|
+
'secondary': '#262626',
|
|
45
|
+
'secondary-hover': '#333333',
|
|
46
|
+
'secondary-foreground': '#fafafa',
|
|
47
|
+
'muted': '#171717',
|
|
48
|
+
'muted-foreground': '#a3a3a3',
|
|
49
|
+
'accent': '#262626',
|
|
50
|
+
'accent-foreground': '#fafafa',
|
|
51
|
+
'destructive': 'var(--state-error)',
|
|
52
|
+
'destructive-hover': '#dc2626',
|
|
53
|
+
'destructive-foreground': '#fafafa',
|
|
54
|
+
'border': 'var(--white-10)',
|
|
55
|
+
'border-strong': 'var(--white-16)',
|
|
56
|
+
'border-control': 'var(--white-15)',
|
|
57
|
+
'border-focus': 'var(--white-22)',
|
|
58
|
+
'border-selected': 'var(--white-30)',
|
|
59
|
+
'input': 'var(--border-control)',
|
|
60
|
+
'ring': 'var(--white-40)',
|
|
61
|
+
'ring-halo': 'var(--white-10)',
|
|
48
62
|
'brand': '#e4e4e7',
|
|
49
63
|
'brand-foreground': '#09090b',
|
|
50
64
|
'brand-muted': '#a3a3a3',
|
|
51
65
|
'black': '#000000',
|
|
52
|
-
'white': '#
|
|
66
|
+
'white': '#fafafa',
|
|
67
|
+
'selection': 'var(--white-20)',
|
|
68
|
+
'glass': 'var(--white-05)',
|
|
69
|
+
'glass-strong': 'var(--white-08)',
|
|
53
70
|
'surface-page': 'var(--background)',
|
|
54
|
-
'surface-card': 'rgb(
|
|
55
|
-
'surface-card-emphasis': 'rgb(
|
|
56
|
-
'surface-card-quiet': 'rgb(
|
|
57
|
-
'surface-overlay': 'rgb(
|
|
71
|
+
'surface-card': 'rgb(38 38 38 / .5)',
|
|
72
|
+
'surface-card-emphasis': 'rgb(38 38 38 / .75)',
|
|
73
|
+
'surface-card-quiet': 'rgb(38 38 38 / .35)',
|
|
74
|
+
'surface-overlay': 'rgb(23 23 23 / .92)',
|
|
58
75
|
'surface-header': 'rgb(0 0 0 / .7)',
|
|
59
76
|
'surface-scrim': 'rgb(0 0 0 / .8)',
|
|
60
|
-
'border-hairline': 'var(--neutral-800)',
|
|
61
|
-
'border-card': 'var(--white-10)',
|
|
62
|
-
'border-strong': 'var(--neutral-500)',
|
|
63
77
|
'surface-0': 'var(--background)',
|
|
64
78
|
'surface-1': 'var(--card)',
|
|
65
79
|
'surface-2': 'var(--muted)',
|
|
66
80
|
'surface-3': 'var(--secondary)',
|
|
67
|
-
'text-primary': '
|
|
68
|
-
'text-secondary': 'var(--white-
|
|
69
|
-
'text-tertiary': 'var(--white-
|
|
81
|
+
'text-primary': '#fafafa',
|
|
82
|
+
'text-secondary': 'var(--white-78)',
|
|
83
|
+
'text-tertiary': 'var(--white-55)',
|
|
70
84
|
'text-helper': 'var(--muted-foreground)',
|
|
71
85
|
'text-disabled': 'var(--white-30)',
|
|
72
86
|
'state-error': '#ef4444',
|
|
@@ -119,6 +133,7 @@ export const typography = {
|
|
|
119
133
|
'font-size-7xl': 'var(--text-7xl)',
|
|
120
134
|
'font-size-8xl': 'var(--text-8xl)',
|
|
121
135
|
'font-size-9xl': 'var(--text-9xl)',
|
|
136
|
+
'text-control': 'var(--text-sm)',
|
|
122
137
|
'weight-normal': '400',
|
|
123
138
|
'weight-medium': '500',
|
|
124
139
|
'weight-semibold': '600',
|
|
@@ -172,14 +187,19 @@ export const spacing = {
|
|
|
172
187
|
'container-max': '80rem',
|
|
173
188
|
'container-prose': '48rem',
|
|
174
189
|
'container-wide': '72rem',
|
|
175
|
-
'gutter': '1rem',
|
|
190
|
+
'gutter': 'max(1rem,var(--safe-left),var(--safe-right))',
|
|
176
191
|
'gutter-sm': '1.5rem',
|
|
177
192
|
'gutter-lg': '2rem',
|
|
178
|
-
'section-y': '
|
|
179
|
-
'section-y-lg': '
|
|
180
|
-
'hero-y': '
|
|
181
|
-
'hero-y-lg': '
|
|
182
|
-
'header-height': '
|
|
193
|
+
'section-y': '2.5rem',
|
|
194
|
+
'section-y-lg': '3.5rem',
|
|
195
|
+
'hero-y': '3rem',
|
|
196
|
+
'hero-y-lg': '4.5rem',
|
|
197
|
+
'header-height': '3.5rem',
|
|
198
|
+
'tap-target': '44px',
|
|
199
|
+
'safe-top': 'env(safe-area-inset-top,0px)',
|
|
200
|
+
'safe-right': 'env(safe-area-inset-right,0px)',
|
|
201
|
+
'safe-bottom': 'env(safe-area-inset-bottom,0px)',
|
|
202
|
+
'safe-left': 'env(safe-area-inset-left,0px)',
|
|
183
203
|
};
|
|
184
204
|
/** grid tokens (from tokens/grid.css). Values are raw CSS. */
|
|
185
205
|
export const grid = {
|
|
@@ -209,9 +229,11 @@ export const radius = {
|
|
|
209
229
|
/** elevation tokens (from tokens/elevation.css). Values are raw CSS. */
|
|
210
230
|
export const elevation = {
|
|
211
231
|
'shadow-none': 'none',
|
|
212
|
-
'shadow-floating': '0
|
|
232
|
+
'shadow-floating': '0 24px 60px -16px rgb(0 0 0 / .75)',
|
|
213
233
|
'shadow-inset-hairline': 'inset 0 0 0 1px var(--white-10)',
|
|
214
|
-
'
|
|
234
|
+
'edge-highlight': 'inset 0 1px 0 0 rgb(255 255 255 / .10)',
|
|
235
|
+
'bloom': '0 0 24px -2px rgb(255 255 255 / .28)',
|
|
236
|
+
'ring-focus': '0 0 0 3px var(--ring-halo)',
|
|
215
237
|
'shadow-sm': '0 1px 2px 0 rgb(0 0 0 / .40)',
|
|
216
238
|
'shadow': '0 1px 3px 0 rgb(0 0 0 / .45), 0 1px 2px -1px rgb(0 0 0 / .45)',
|
|
217
239
|
'shadow-md': '0 4px 6px -1px rgb(0 0 0 / .50), 0 2px 4px -2px rgb(0 0 0 / .50)',
|
|
@@ -221,6 +243,7 @@ export const elevation = {
|
|
|
221
243
|
'glow-hero': 'radial-gradient(circle,rgb(255 255 255 / .12) 0%,transparent 68%)',
|
|
222
244
|
'glow-hero-blur': '120px',
|
|
223
245
|
'sheen-card': 'radial-gradient(120% 120% at 80% 0%,rgb(255 255 255 / .08) 0%,transparent 55%)',
|
|
246
|
+
'sheen-edge': 'linear-gradient(90deg,transparent,rgb(255 255 255 / .14) 50%,transparent)',
|
|
224
247
|
'gradient-chrome': 'linear-gradient(to right,#ffffff,var(--white-80),var(--white-60))',
|
|
225
248
|
'gradient-chrome-2': 'linear-gradient(to right,#ffffff,var(--neutral-500))',
|
|
226
249
|
'gradient-protect': 'linear-gradient(to bottom,var(--white-10),transparent)',
|
|
@@ -232,8 +255,10 @@ export const motion = {
|
|
|
232
255
|
'duration-slow': '400ms',
|
|
233
256
|
'duration-slower': '500ms',
|
|
234
257
|
'duration-glow': '9s',
|
|
258
|
+
'duration-press': '90ms',
|
|
235
259
|
'ease-out': 'cubic-bezier(0,0,0.2,1)',
|
|
236
260
|
'ease-in-out': 'cubic-bezier(0.4,0,0.2,1)',
|
|
261
|
+
'ease-emphasis': 'cubic-bezier(0.16,1,0.3,1)',
|
|
237
262
|
'stagger': '60ms',
|
|
238
263
|
'entry-rise': '16px',
|
|
239
264
|
'entry-rise-lg': '24px',
|
|
@@ -280,54 +305,68 @@ export const cssVars = {
|
|
|
280
305
|
'--hanzo-black': '#0A0A0B',
|
|
281
306
|
'--hanzo-white': '#FFFFFF',
|
|
282
307
|
'--white-05': 'rgb(255 255 255 / .05)',
|
|
308
|
+
'--white-06': 'rgb(255 255 255 / .06)',
|
|
309
|
+
'--white-08': 'rgb(255 255 255 / .08)',
|
|
283
310
|
'--white-10': 'rgb(255 255 255 / .10)',
|
|
284
311
|
'--white-15': 'rgb(255 255 255 / .15)',
|
|
312
|
+
'--white-16': 'rgb(255 255 255 / .16)',
|
|
285
313
|
'--white-20': 'rgb(255 255 255 / .20)',
|
|
314
|
+
'--white-22': 'rgb(255 255 255 / .22)',
|
|
286
315
|
'--white-30': 'rgb(255 255 255 / .30)',
|
|
287
316
|
'--white-40': 'rgb(255 255 255 / .40)',
|
|
317
|
+
'--white-55': 'rgb(255 255 255 / .55)',
|
|
288
318
|
'--white-60': 'rgb(255 255 255 / .60)',
|
|
319
|
+
'--white-78': 'rgb(255 255 255 / .78)',
|
|
289
320
|
'--white-80': 'rgb(255 255 255 / .80)',
|
|
290
|
-
'--background': '#
|
|
291
|
-
'--foreground': '#
|
|
292
|
-
'--card': '#
|
|
293
|
-
'--card-foreground': '#
|
|
294
|
-
'--popover': '#
|
|
295
|
-
'--popover-foreground': '#
|
|
296
|
-
'--primary': '#
|
|
297
|
-
'--primary-
|
|
298
|
-
'--
|
|
299
|
-
'--secondary
|
|
300
|
-
'--
|
|
301
|
-
'--
|
|
302
|
-
'--
|
|
303
|
-
'--
|
|
304
|
-
'--
|
|
305
|
-
'--
|
|
306
|
-
'--
|
|
307
|
-
'--
|
|
308
|
-
'--
|
|
321
|
+
'--background': '#0a0a0a',
|
|
322
|
+
'--foreground': '#fafafa',
|
|
323
|
+
'--card': '#0f0f0f',
|
|
324
|
+
'--card-foreground': '#fafafa',
|
|
325
|
+
'--popover': '#0f0f0f',
|
|
326
|
+
'--popover-foreground': '#fafafa',
|
|
327
|
+
'--primary': '#fafafa',
|
|
328
|
+
'--primary-hover': '#e5e5e5',
|
|
329
|
+
'--primary-foreground': '#0a0a0a',
|
|
330
|
+
'--secondary': '#262626',
|
|
331
|
+
'--secondary-hover': '#333333',
|
|
332
|
+
'--secondary-foreground': '#fafafa',
|
|
333
|
+
'--muted': '#171717',
|
|
334
|
+
'--muted-foreground': '#a3a3a3',
|
|
335
|
+
'--accent': '#262626',
|
|
336
|
+
'--accent-foreground': '#fafafa',
|
|
337
|
+
'--destructive': 'var(--state-error)',
|
|
338
|
+
'--destructive-hover': '#dc2626',
|
|
339
|
+
'--destructive-foreground': '#fafafa',
|
|
340
|
+
'--border': 'var(--white-10)',
|
|
341
|
+
'--border-strong': 'var(--white-16)',
|
|
342
|
+
'--border-control': 'var(--white-15)',
|
|
343
|
+
'--border-focus': 'var(--white-22)',
|
|
344
|
+
'--border-selected': 'var(--white-30)',
|
|
345
|
+
'--input': 'var(--border-control)',
|
|
346
|
+
'--ring': 'var(--white-40)',
|
|
347
|
+
'--ring-halo': 'var(--white-10)',
|
|
309
348
|
'--brand': '#e4e4e7',
|
|
310
349
|
'--brand-foreground': '#09090b',
|
|
311
350
|
'--brand-muted': '#a3a3a3',
|
|
312
351
|
'--black': '#000000',
|
|
313
|
-
'--white': '#
|
|
352
|
+
'--white': '#fafafa',
|
|
353
|
+
'--selection': 'var(--white-20)',
|
|
354
|
+
'--glass': 'var(--white-05)',
|
|
355
|
+
'--glass-strong': 'var(--white-08)',
|
|
314
356
|
'--surface-page': 'var(--background)',
|
|
315
|
-
'--surface-card': 'rgb(
|
|
316
|
-
'--surface-card-emphasis': 'rgb(
|
|
317
|
-
'--surface-card-quiet': 'rgb(
|
|
318
|
-
'--surface-overlay': 'rgb(
|
|
357
|
+
'--surface-card': 'rgb(38 38 38 / .5)',
|
|
358
|
+
'--surface-card-emphasis': 'rgb(38 38 38 / .75)',
|
|
359
|
+
'--surface-card-quiet': 'rgb(38 38 38 / .35)',
|
|
360
|
+
'--surface-overlay': 'rgb(23 23 23 / .92)',
|
|
319
361
|
'--surface-header': 'rgb(0 0 0 / .7)',
|
|
320
362
|
'--surface-scrim': 'rgb(0 0 0 / .8)',
|
|
321
|
-
'--border-hairline': 'var(--neutral-800)',
|
|
322
|
-
'--border-card': 'var(--white-10)',
|
|
323
|
-
'--border-strong': 'var(--neutral-500)',
|
|
324
363
|
'--surface-0': 'var(--background)',
|
|
325
364
|
'--surface-1': 'var(--card)',
|
|
326
365
|
'--surface-2': 'var(--muted)',
|
|
327
366
|
'--surface-3': 'var(--secondary)',
|
|
328
|
-
'--text-primary': '
|
|
329
|
-
'--text-secondary': 'var(--white-
|
|
330
|
-
'--text-tertiary': 'var(--white-
|
|
367
|
+
'--text-primary': '#fafafa',
|
|
368
|
+
'--text-secondary': 'var(--white-78)',
|
|
369
|
+
'--text-tertiary': 'var(--white-55)',
|
|
331
370
|
'--text-helper': 'var(--muted-foreground)',
|
|
332
371
|
'--text-disabled': 'var(--white-30)',
|
|
333
372
|
'--state-error': '#ef4444',
|
|
@@ -377,6 +416,7 @@ export const cssVars = {
|
|
|
377
416
|
'--font-size-7xl': 'var(--text-7xl)',
|
|
378
417
|
'--font-size-8xl': 'var(--text-8xl)',
|
|
379
418
|
'--font-size-9xl': 'var(--text-9xl)',
|
|
419
|
+
'--text-control': 'var(--text-sm)',
|
|
380
420
|
'--weight-normal': '400',
|
|
381
421
|
'--weight-medium': '500',
|
|
382
422
|
'--weight-semibold': '600',
|
|
@@ -427,14 +467,19 @@ export const cssVars = {
|
|
|
427
467
|
'--container-max': '80rem',
|
|
428
468
|
'--container-prose': '48rem',
|
|
429
469
|
'--container-wide': '72rem',
|
|
430
|
-
'--gutter': '1rem',
|
|
470
|
+
'--gutter': 'max(1rem,var(--safe-left),var(--safe-right))',
|
|
431
471
|
'--gutter-sm': '1.5rem',
|
|
432
472
|
'--gutter-lg': '2rem',
|
|
433
|
-
'--section-y': '
|
|
434
|
-
'--section-y-lg': '
|
|
435
|
-
'--hero-y': '
|
|
436
|
-
'--hero-y-lg': '
|
|
437
|
-
'--header-height': '
|
|
473
|
+
'--section-y': '2.5rem',
|
|
474
|
+
'--section-y-lg': '3.5rem',
|
|
475
|
+
'--hero-y': '3rem',
|
|
476
|
+
'--hero-y-lg': '4.5rem',
|
|
477
|
+
'--header-height': '3.5rem',
|
|
478
|
+
'--tap-target': '44px',
|
|
479
|
+
'--safe-top': 'env(safe-area-inset-top,0px)',
|
|
480
|
+
'--safe-right': 'env(safe-area-inset-right,0px)',
|
|
481
|
+
'--safe-bottom': 'env(safe-area-inset-bottom,0px)',
|
|
482
|
+
'--safe-left': 'env(safe-area-inset-left,0px)',
|
|
438
483
|
'--grid-columns': '12',
|
|
439
484
|
'--grid-gap': 'var(--space-6)',
|
|
440
485
|
'--grid-gap-tight': 'var(--space-4)',
|
|
@@ -455,9 +500,11 @@ export const cssVars = {
|
|
|
455
500
|
'--radius-composer': '28px',
|
|
456
501
|
'--radius-full': '9999px',
|
|
457
502
|
'--shadow-none': 'none',
|
|
458
|
-
'--shadow-floating': '0
|
|
503
|
+
'--shadow-floating': '0 24px 60px -16px rgb(0 0 0 / .75)',
|
|
459
504
|
'--shadow-inset-hairline': 'inset 0 0 0 1px var(--white-10)',
|
|
460
|
-
'--
|
|
505
|
+
'--edge-highlight': 'inset 0 1px 0 0 rgb(255 255 255 / .10)',
|
|
506
|
+
'--bloom': '0 0 24px -2px rgb(255 255 255 / .28)',
|
|
507
|
+
'--ring-focus': '0 0 0 3px var(--ring-halo)',
|
|
461
508
|
'--shadow-sm': '0 1px 2px 0 rgb(0 0 0 / .40)',
|
|
462
509
|
'--shadow': '0 1px 3px 0 rgb(0 0 0 / .45), 0 1px 2px -1px rgb(0 0 0 / .45)',
|
|
463
510
|
'--shadow-md': '0 4px 6px -1px rgb(0 0 0 / .50), 0 2px 4px -2px rgb(0 0 0 / .50)',
|
|
@@ -467,6 +514,7 @@ export const cssVars = {
|
|
|
467
514
|
'--glow-hero': 'radial-gradient(circle,rgb(255 255 255 / .12) 0%,transparent 68%)',
|
|
468
515
|
'--glow-hero-blur': '120px',
|
|
469
516
|
'--sheen-card': 'radial-gradient(120% 120% at 80% 0%,rgb(255 255 255 / .08) 0%,transparent 55%)',
|
|
517
|
+
'--sheen-edge': 'linear-gradient(90deg,transparent,rgb(255 255 255 / .14) 50%,transparent)',
|
|
470
518
|
'--gradient-chrome': 'linear-gradient(to right,#ffffff,var(--white-80),var(--white-60))',
|
|
471
519
|
'--gradient-chrome-2': 'linear-gradient(to right,#ffffff,var(--neutral-500))',
|
|
472
520
|
'--gradient-protect': 'linear-gradient(to bottom,var(--white-10),transparent)',
|
|
@@ -475,8 +523,10 @@ export const cssVars = {
|
|
|
475
523
|
'--duration-slow': '400ms',
|
|
476
524
|
'--duration-slower': '500ms',
|
|
477
525
|
'--duration-glow': '9s',
|
|
526
|
+
'--duration-press': '90ms',
|
|
478
527
|
'--ease-out': 'cubic-bezier(0,0,0.2,1)',
|
|
479
528
|
'--ease-in-out': 'cubic-bezier(0.4,0,0.2,1)',
|
|
529
|
+
'--ease-emphasis': 'cubic-bezier(0.16,1,0.3,1)',
|
|
480
530
|
'--stagger': '60ms',
|
|
481
531
|
'--entry-rise': '16px',
|
|
482
532
|
'--entry-rise-lg': '24px',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hanzo/design",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"packageManager": "pnpm@11.17.0",
|
|
5
5
|
"description": "Hanzo Design System \u2014 monochrome, dark-default tokens + components + brand assets, the single source of truth for every Hanzo surface. CSS + typed programmatic tokens.",
|
|
6
6
|
"license": "BSD-3-Clause",
|
package/scripts/check-tokens.mjs
CHANGED
|
@@ -114,9 +114,17 @@ const pass = (msg) => console.log(` ok ${msg}`)
|
|
|
114
114
|
: pass(`all ${used.size} internal var() references resolve`)
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
// ── 3. the
|
|
118
|
-
//
|
|
119
|
-
//
|
|
117
|
+
// ── 3. the focus-indicator contrast gate (WCAG 2.4.11) ───────────────────
|
|
118
|
+
// The focus indicator must clear 3:1 against EVERY surface it can land on, in
|
|
119
|
+
// BOTH themes. --ring was #333333 = 1.66:1 on --background.
|
|
120
|
+
//
|
|
121
|
+
// --border-control was gated here too and no longer is. That was not a
|
|
122
|
+
// regression, it was the point: pinning a control's RESTING edge at 3:1 forces
|
|
123
|
+
// it to a mid-grey on a near-black ground, four times heavier than the hairline
|
|
124
|
+
// beside it, and a form drawn that way reads as a wireframe. The edge is now
|
|
125
|
+
// rgb(255 255 255 / .15) — the reference's own value — and the contrast budget
|
|
126
|
+
// is spent entirely on --ring, which is what a keyboard user actually navigates
|
|
127
|
+
// by. A resting edge is an affordance; a focus ring is a position.
|
|
120
128
|
{
|
|
121
129
|
const css = strip(read(join(tokensDir, 'colors.css')))
|
|
122
130
|
const block = (re) => { const m = css.match(re); const o = {}; if (m) for (const [, n, v] of m[1].matchAll(/--([A-Za-z0-9-]+)\s*:\s*([^;]+);/g)) o[n] = v.trim(); return o }
|
|
@@ -144,7 +152,12 @@ const pass = (msg) => console.log(` ok ${msg}`)
|
|
|
144
152
|
}
|
|
145
153
|
|
|
146
154
|
const CANVASES = ['background', 'card', 'popover', 'muted', 'secondary', 'surface-card', 'surface-overlay']
|
|
147
|
-
|
|
155
|
+
// The gate follows the DUTY, not the name — and exactly ONE duty owes a
|
|
156
|
+
// ratio. --ring is the focus indicator: transient, keyboard-only, and the
|
|
157
|
+
// whole of how someone knows where they are. Every boundary that is merely an
|
|
158
|
+
// affordance — --border, --border-strong, --border-control, --border-focus,
|
|
159
|
+
// --border-selected — is free to be as quiet as it looks.
|
|
160
|
+
const GATED = { ring: 3 }
|
|
148
161
|
for (const [theme, scope] of Object.entries(themes)) {
|
|
149
162
|
for (const [tok, min] of Object.entries(GATED)) {
|
|
150
163
|
let worst = Infinity, where = ''
|
|
@@ -158,6 +171,60 @@ const pass = (msg) => console.log(` ok ${msg}`)
|
|
|
158
171
|
: pass(`--${tok} (${theme}) ${worst.toFixed(2)}:1 worst case (${where})`)
|
|
159
172
|
}
|
|
160
173
|
}
|
|
174
|
+
|
|
175
|
+
// ── 4. every white-alpha token must be restated for the light theme ──────
|
|
176
|
+
// The opacity ladder does NOT invert. A semantic token built from
|
|
177
|
+
// rgb(255 255 255 / a) is white-on-white in `.light` — the border does not
|
|
178
|
+
// change colour, it STOPS EXISTING — and nothing renders an error. The raw
|
|
179
|
+
// ladder itself (--white-*) is exempt: it is a palette, not a semantic token,
|
|
180
|
+
// and light-theme tokens are expected to restate rather than invert it.
|
|
181
|
+
//
|
|
182
|
+
// Two things widen this from where it started. It scans tokens/elevation.css
|
|
183
|
+
// as well as tokens/colors.css, because that is where the light lives now —
|
|
184
|
+
// an inset white highlight, a white hover bloom and a white panel sheen all
|
|
185
|
+
// fail in `.light` exactly the way a white border does, and more invisibly,
|
|
186
|
+
// since a missing lift looks like a design choice. And it matches white-alpha
|
|
187
|
+
// ANYWHERE INSIDE a value rather than requiring the whole value to be one
|
|
188
|
+
// colour, because every one of those tokens is a shadow or a gradient with
|
|
189
|
+
// the colour buried in it — `inset 0 1px 0 0 rgb(255 255 255 / .07)` would
|
|
190
|
+
// have sailed through a whole-value parse.
|
|
191
|
+
{
|
|
192
|
+
const PALETTE = /^(white|neutral|pure|hanzo)-/
|
|
193
|
+
const FILES = ['colors', 'elevation']
|
|
194
|
+
const blockOf = (src, re) => { const m = strip(src).match(re); const o = {}; if (m) for (const [, n, v] of m[1].matchAll(/--([A-Za-z0-9-]+)\s*:\s*([^;]+);/g)) o[n] = v.trim(); return o }
|
|
195
|
+
const ROOT = /:root\s*\{([\s\S]*?)\n\}/
|
|
196
|
+
const LIGHT = /\.light\s*\{([\s\S]*?)\n\}/
|
|
197
|
+
|
|
198
|
+
const sources = Object.fromEntries(FILES.map((f) => [f, read(join(tokensDir, `${f}.css`))]))
|
|
199
|
+
// One scope to resolve against: elevation references --white-* rungs that
|
|
200
|
+
// colors declares, so a per-file scope could not expand them.
|
|
201
|
+
const scope = {}
|
|
202
|
+
for (const f of FILES) Object.assign(scope, blockOf(sources[f], ROOT))
|
|
203
|
+
// Restating in ANY file's .light block counts — the cascade does not care
|
|
204
|
+
// which file a declaration came from.
|
|
205
|
+
const lightKeys = new Set()
|
|
206
|
+
for (const f of FILES) for (const k of Object.keys(blockOf(sources[f], LIGHT))) lightKeys.add(k)
|
|
207
|
+
|
|
208
|
+
// Expansion STOPS at any token that is itself restated in `.light`.
|
|
209
|
+
// `--ring-focus: 0 0 0 3px var(--ring-halo)` is theme-safe precisely
|
|
210
|
+
// because --ring-halo inverts underneath it — var() resolves per theme at
|
|
211
|
+
// use time, so deferring to a token that flips IS the fix, and a checker
|
|
212
|
+
// that expanded through it would demand every wrapper be restated too.
|
|
213
|
+
const expand = (v, d = 0) =>
|
|
214
|
+
d > 10 ? v : String(v).replace(/var\(\s*--([A-Za-z0-9-]+)\s*\)/g,
|
|
215
|
+
(m, n) => (n in scope && !lightKeys.has(n) ? expand(scope[n], d + 1) : m))
|
|
216
|
+
const WHITE_ALPHA = /rgba?\(\s*255[\s,]+255[\s,]+255\s*[/,]\s*(?:0?\.\d+|0)\s*\)/i
|
|
217
|
+
|
|
218
|
+
const leaked = []
|
|
219
|
+
for (const f of FILES)
|
|
220
|
+
for (const k of Object.keys(blockOf(sources[f], ROOT))) {
|
|
221
|
+
if (PALETTE.test(k) || lightKeys.has(k)) continue
|
|
222
|
+
if (WHITE_ALPHA.test(expand(scope[k]))) leaked.push(`${k} (tokens/${f}.css)`)
|
|
223
|
+
}
|
|
224
|
+
leaked.length
|
|
225
|
+
? leaked.forEach((k) => fail(`--${k} is built from white-alpha and has no .light value — it vanishes in the light theme`))
|
|
226
|
+
: pass(`every white-alpha token in ${FILES.join('/')}.css is restated in .light`)
|
|
227
|
+
}
|
|
161
228
|
}
|
|
162
229
|
|
|
163
230
|
console.log(failures ? `\n${failures} check(s) failed` : '\nall token checks passed')
|