@discourser/design-system 0.25.3 → 0.27.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 (61) hide show
  1. package/README.md +76 -73
  2. package/dist/{chunk-ZPECW4N2.js → chunk-4XOWPACJ.js} +257 -105
  3. package/dist/chunk-4XOWPACJ.js.map +1 -0
  4. package/dist/{chunk-QNCZYFUJ.cjs → chunk-AZ6QU2L2.cjs} +257 -105
  5. package/dist/chunk-AZ6QU2L2.cjs.map +1 -0
  6. package/dist/{chunk-TBLDQATQ.cjs → chunk-EBDNCZF6.cjs} +94 -54
  7. package/dist/chunk-EBDNCZF6.cjs.map +1 -0
  8. package/dist/{chunk-UHSL4N44.js → chunk-MAVUSE4F.js} +94 -55
  9. package/dist/chunk-MAVUSE4F.js.map +1 -0
  10. package/dist/components/Checkbox.d.ts +1 -1
  11. package/dist/components/Icons/LeftArrowIcon.d.ts +6 -0
  12. package/dist/components/Icons/LeftArrowIcon.d.ts.map +1 -0
  13. package/dist/components/Icons/RightArrowIcon.d.ts.map +1 -1
  14. package/dist/components/Icons/index.d.ts +1 -0
  15. package/dist/components/Icons/index.d.ts.map +1 -1
  16. package/dist/components/index.cjs +79 -75
  17. package/dist/components/index.d.ts +1 -0
  18. package/dist/components/index.d.ts.map +1 -1
  19. package/dist/components/index.js +1 -1
  20. package/dist/contracts/design-language.contract.d.ts +52 -18
  21. package/dist/contracts/design-language.contract.d.ts.map +1 -1
  22. package/dist/figma-codex.json +2 -2
  23. package/dist/index.cjs +83 -79
  24. package/dist/index.js +2 -2
  25. package/dist/languages/material3.language.d.ts.map +1 -1
  26. package/dist/languages/transform.d.ts +5 -5
  27. package/dist/languages/transform.d.ts.map +1 -1
  28. package/dist/preset/index.cjs +2 -2
  29. package/dist/preset/index.js +1 -1
  30. package/docs/component-catalog.md +469 -0
  31. package/docs/superpowers/plans/2026-04-03-component-catalog-pipeline.md +667 -0
  32. package/docs/token-name-mapping.json +614 -42
  33. package/docs/token-name-mapping.md +117 -29
  34. package/package.json +3 -2
  35. package/src/components/Icons/LeftArrowIcon.tsx +28 -0
  36. package/src/components/Icons/RightArrowIcon.tsx +7 -2
  37. package/src/components/Icons/index.ts +1 -0
  38. package/src/components/__tests__/AbsoluteCenter.test.tsx +31 -0
  39. package/src/components/__tests__/Divider.test.tsx +38 -0
  40. package/src/components/__tests__/Group.test.tsx +34 -0
  41. package/src/components/__tests__/Icon.test.tsx +31 -0
  42. package/src/components/__tests__/SettingsPopover.test.tsx +39 -0
  43. package/src/components/__tests__/StudioControls.test.tsx +59 -0
  44. package/src/components/__tests__/Toaster.test.tsx +24 -0
  45. package/src/components/index.ts +1 -0
  46. package/src/contracts/design-language.contract.ts +69 -20
  47. package/src/languages/material3.language.ts +249 -80
  48. package/src/languages/transform.ts +45 -48
  49. package/src/preset/__tests__/translation-token-accuracy.test.ts +13 -0
  50. package/src/stories/foundations/Colors.mdx +9 -1
  51. package/src/stories/foundations/Elevation.mdx +23 -17
  52. package/src/stories/foundations/TokenReference.stories.tsx +970 -0
  53. package/src/stories/foundations/TonalPaletteDerivation.stories.tsx +782 -0
  54. package/src/stories/foundations/Typography.mdx +125 -25
  55. package/dist/chunk-QNCZYFUJ.cjs.map +0 -1
  56. package/dist/chunk-TBLDQATQ.cjs.map +0 -1
  57. package/dist/chunk-UHSL4N44.js.map +0 -1
  58. package/dist/chunk-ZPECW4N2.js.map +0 -1
  59. package/docs/context-share/ELEVATION_FIX_PLAN.md +0 -903
  60. package/docs/context-share/fix-checkbox-radio-tokens.md +0 -145
  61. package/docs/context-share/icon-component-prompt.md +0 -154
@@ -0,0 +1,970 @@
1
+ /**
2
+ * Token Reference — auto-generated from docs/token-name-mapping.json
3
+ *
4
+ * This file imports the JSON directly. It is always current — no manual
5
+ * editing required. To update the tables, run:
6
+ *
7
+ * pnpm figma:export
8
+ *
9
+ * then rebuild Storybook. The JSON is regenerated by
10
+ * scripts/export-figma-tokens.ts as part of that command.
11
+ */
12
+
13
+ import type { Meta, StoryObj } from '@storybook/react-vite';
14
+ import type { CSSProperties } from 'react';
15
+ import tokenMap from '../../../docs/token-name-mapping.json';
16
+
17
+ // ── Types inferred from the JSON shape ───────────────────────────────────────
18
+
19
+ interface SemanticEntry {
20
+ figmaPath: string;
21
+ pandaToken: string;
22
+ cssProperty: string;
23
+ exampleUsage: string;
24
+ note?: string;
25
+ }
26
+
27
+ interface SpacingShapeEntry {
28
+ figmaPath: string;
29
+ pandaToken: string;
30
+ pandaCategory: string;
31
+ cssProperty: string;
32
+ exampleUsage: string;
33
+ }
34
+
35
+ interface EffectStyleEntry {
36
+ figmaEffectStyle: string;
37
+ pandaToken: string;
38
+ pandaCategory: string;
39
+ cssProperty: string;
40
+ description: string;
41
+ exampleUsage: string;
42
+ }
43
+
44
+ interface PandaOnlyEntry {
45
+ pandaToken: string;
46
+ cssProperty: string;
47
+ notes?: string;
48
+ exampleUsage: string;
49
+ }
50
+
51
+ interface ShadowAliasEntry {
52
+ pandaToken: string;
53
+ cssProperty: string;
54
+ resolvesTo: string;
55
+ description?: string;
56
+ exampleUsage: string;
57
+ }
58
+
59
+ interface TextStyleEntry {
60
+ figmaTextStyle: string;
61
+ figmaDescription: string;
62
+ ddsTokenPath: string;
63
+ pandaTextStyle: string;
64
+ fontFamily: string;
65
+ fontStyle: string;
66
+ fontWeight: number;
67
+ fontSize: number;
68
+ lineHeightPx: number;
69
+ letterSpacing: number;
70
+ isDefaultWeight: boolean;
71
+ exampleUsage: string;
72
+ }
73
+
74
+ // ── Shared style helpers ─────────────────────────────────────────────────────
75
+
76
+ const T: Record<string, CSSProperties> = {
77
+ page: {
78
+ fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif',
79
+ fontSize: '14px',
80
+ color: '#1a1a1a',
81
+ maxWidth: '1100px',
82
+ },
83
+ section: {
84
+ marginBottom: '56px',
85
+ },
86
+ h2: {
87
+ fontSize: '20px',
88
+ fontWeight: '600',
89
+ margin: '0 0 4px',
90
+ color: '#111',
91
+ },
92
+ meta: {
93
+ fontSize: '12px',
94
+ color: '#888',
95
+ margin: '0 0 20px',
96
+ fontFamily: 'monospace',
97
+ },
98
+ table: {
99
+ width: '100%',
100
+ borderCollapse: 'collapse' as const,
101
+ fontSize: '13px',
102
+ },
103
+ th: {
104
+ textAlign: 'left' as const,
105
+ padding: '8px 12px',
106
+ background: '#f4f4f4',
107
+ borderBottom: '2px solid #e0e0e0',
108
+ fontWeight: '600',
109
+ fontSize: '12px',
110
+ color: '#444',
111
+ whiteSpace: 'nowrap' as const,
112
+ },
113
+ td: {
114
+ padding: '8px 12px',
115
+ borderBottom: '1px solid #f0f0f0',
116
+ verticalAlign: 'middle' as const,
117
+ },
118
+ mono: {
119
+ fontFamily: 'monospace',
120
+ fontSize: '12px',
121
+ background: '#f5f5f5',
122
+ padding: '2px 5px',
123
+ borderRadius: '3px',
124
+ color: '#333',
125
+ whiteSpace: 'nowrap' as const,
126
+ display: 'inline-block' as const,
127
+ },
128
+ pill: {
129
+ display: 'inline-block' as const,
130
+ fontSize: '10px',
131
+ fontWeight: '600',
132
+ padding: '1px 6px',
133
+ borderRadius: '10px',
134
+ background: '#e8f0e0',
135
+ color: '#3a5a18',
136
+ marginLeft: '6px',
137
+ verticalAlign: 'middle' as const,
138
+ },
139
+ warning: {
140
+ display: 'inline-block' as const,
141
+ fontSize: '10px',
142
+ fontWeight: '600',
143
+ padding: '1px 6px',
144
+ borderRadius: '10px',
145
+ background: '#fff3cd',
146
+ color: '#856404',
147
+ marginLeft: '6px',
148
+ verticalAlign: 'middle' as const,
149
+ },
150
+ };
151
+
152
+ function rowBg(i: number): CSSProperties {
153
+ return { background: i % 2 === 0 ? '#fff' : '#fafafa' };
154
+ }
155
+
156
+ // ── Color swatch using CSS custom property directly ──────────────────────────
157
+ // We use the cssProperty value from the JSON (e.g. --colors-surface-container)
158
+ // to render a live swatch via inline style. This bypasses Panda's static
159
+ // extraction requirement and works with any token at runtime.
160
+
161
+ function Swatch({ cssVar, size = 32 }: { cssVar: string; size?: number }) {
162
+ return (
163
+ <div
164
+ style={{
165
+ display: 'inline-flex',
166
+ gap: '4px',
167
+ alignItems: 'center',
168
+ }}
169
+ >
170
+ {/* Light swatch */}
171
+ <div
172
+ title={`Light: var(${cssVar})`}
173
+ style={{
174
+ width: size,
175
+ height: size,
176
+ borderRadius: '4px',
177
+ border: '1px solid rgba(0,0,0,0.12)',
178
+ background: `var(${cssVar})`,
179
+ flexShrink: 0,
180
+ }}
181
+ />
182
+ {/* Dark swatch — forced via data-theme attribute */}
183
+ <div
184
+ data-theme="dark"
185
+ title={`Dark: var(${cssVar})`}
186
+ style={{
187
+ width: size,
188
+ height: size,
189
+ borderRadius: '4px',
190
+ border: '1px solid rgba(255,255,255,0.15)',
191
+ background: `var(${cssVar})`,
192
+ flexShrink: 0,
193
+ }}
194
+ />
195
+ </div>
196
+ );
197
+ }
198
+
199
+ function SpacingSwatch({
200
+ cssVar,
201
+ category,
202
+ }: {
203
+ cssVar: string;
204
+ category: string;
205
+ }) {
206
+ if (category !== 'spacing') {
207
+ // For radii — show a box with the corner radius applied
208
+ return (
209
+ <div
210
+ title={`var(${cssVar})`}
211
+ style={{
212
+ width: 32,
213
+ height: 32,
214
+ background: '#4C662B',
215
+ borderRadius: `var(${cssVar})`,
216
+ flexShrink: 0,
217
+ border: '1px solid rgba(0,0,0,0.1)',
218
+ }}
219
+ />
220
+ );
221
+ }
222
+ // For spacing — show a bar whose width is the spacing value
223
+ return (
224
+ <div
225
+ style={{
226
+ display: 'flex',
227
+ alignItems: 'center',
228
+ gap: '4px',
229
+ minWidth: '80px',
230
+ }}
231
+ >
232
+ <div
233
+ title={`var(${cssVar})`}
234
+ style={{
235
+ height: '20px',
236
+ width: `var(${cssVar})`,
237
+ minWidth: '2px',
238
+ background: '#4C662B',
239
+ borderRadius: '2px',
240
+ flexShrink: 0,
241
+ }}
242
+ />
243
+ </div>
244
+ );
245
+ }
246
+
247
+ function ShadowSwatch({ cssVar }: { cssVar: string }) {
248
+ return (
249
+ <div
250
+ title={`var(${cssVar})`}
251
+ style={{
252
+ width: 40,
253
+ height: 32,
254
+ borderRadius: '6px',
255
+ background: '#fff',
256
+ boxShadow: `var(${cssVar})`,
257
+ border: '1px solid rgba(0,0,0,0.06)',
258
+ flexShrink: 0,
259
+ }}
260
+ />
261
+ );
262
+ }
263
+
264
+ function TypeSwatch({
265
+ fontFamily,
266
+ fontWeight,
267
+ fontSize,
268
+ }: {
269
+ fontFamily: string;
270
+ fontWeight: number;
271
+ fontSize: number;
272
+ }) {
273
+ // Clamp display size so table rows don't get too tall
274
+ const displaySize = Math.min(fontSize, 22);
275
+ return (
276
+ <span
277
+ style={{
278
+ fontFamily,
279
+ fontWeight,
280
+ fontSize: `${displaySize}px`,
281
+ lineHeight: 1,
282
+ color: '#1a1a1a',
283
+ whiteSpace: 'nowrap',
284
+ }}
285
+ >
286
+ Aa
287
+ </span>
288
+ );
289
+ }
290
+
291
+ // ── Storybook meta ────────────────────────────────────────────────────────────
292
+
293
+ const meta = {
294
+ title: 'Foundations/Token Reference',
295
+ parameters: { layout: 'padded' },
296
+ } satisfies Meta;
297
+
298
+ export default meta;
299
+ type Story = StoryObj<typeof meta>;
300
+
301
+ // ── Story 1: Semantic Colors ─────────────────────────────────────────────────
302
+
303
+ export const SemanticColors: Story = {
304
+ name: '1 · Semantic Colors',
305
+ render: () => {
306
+ const entries = tokenMap.semantic as SemanticEntry[];
307
+ const pandaOnly = (tokenMap.semanticPandaOnly ?? []) as PandaOnlyEntry[];
308
+
309
+ // Mismatches flagged in token-name-mapping.md
310
+ const mismatches = new Set([
311
+ 'surfaceVariant',
312
+ 'inverseSurface',
313
+ 'inverseOnSurface',
314
+ 'inversePrimary',
315
+ ]);
316
+
317
+ return (
318
+ <div style={T.page}>
319
+ <div style={T.section}>
320
+ <h2 style={T.h2}>Semantic Colors</h2>
321
+ <p style={T.meta}>
322
+ {entries.length} entries · source: Figma &quot;Semantic&quot;
323
+ collection · auto-generated from docs/token-name-mapping.json v
324
+ {tokenMap.version}
325
+ </p>
326
+
327
+ <table style={T.table}>
328
+ <thead>
329
+ <tr>
330
+ <th style={T.th}>Swatch (L / D)</th>
331
+ <th style={T.th}>Figma Variable Path</th>
332
+ <th style={T.th}>Panda CSS Token</th>
333
+ <th style={T.th}>CSS Custom Property</th>
334
+ <th style={T.th}>Code Usage</th>
335
+ </tr>
336
+ </thead>
337
+ <tbody>
338
+ {entries.map((e, i) => (
339
+ <tr key={e.figmaPath} style={rowBg(i)}>
340
+ <td style={T.td}>
341
+ <Swatch cssVar={e.cssProperty} />
342
+ </td>
343
+ <td style={T.td}>
344
+ <span style={T.mono}>{e.figmaPath}</span>
345
+ </td>
346
+ <td style={T.td}>
347
+ <span style={T.mono}>{e.pandaToken}</span>
348
+ {mismatches.has(e.pandaToken) && (
349
+ <span
350
+ style={T.warning}
351
+ title="Not dot-notation — see mismatch notes"
352
+ >
353
+ ⚠ flat
354
+ </span>
355
+ )}
356
+ </td>
357
+ <td style={T.td}>
358
+ <span style={{ ...T.mono, color: '#666' }}>
359
+ {e.cssProperty}
360
+ </span>
361
+ </td>
362
+ <td style={T.td}>
363
+ <span style={{ ...T.mono, background: '#eef4e8' }}>
364
+ {e.exampleUsage}
365
+ </span>
366
+ </td>
367
+ </tr>
368
+ ))}
369
+ </tbody>
370
+ </table>
371
+
372
+ {pandaOnly.length > 0 && (
373
+ <>
374
+ <h3
375
+ style={{
376
+ fontSize: '15px',
377
+ fontWeight: '600',
378
+ margin: '32px 0 12px',
379
+ color: '#333',
380
+ }}
381
+ >
382
+ Panda-only tokens (no Figma variable)
383
+ </h3>
384
+ <table style={T.table}>
385
+ <thead>
386
+ <tr>
387
+ <th style={T.th}>Swatch (L / D)</th>
388
+ <th style={T.th}>Panda CSS Token</th>
389
+ <th style={T.th}>CSS Custom Property</th>
390
+ <th style={T.th}>Code Usage</th>
391
+ <th style={T.th}>Notes</th>
392
+ </tr>
393
+ </thead>
394
+ <tbody>
395
+ {pandaOnly.map((e, i) => (
396
+ <tr key={e.pandaToken} style={rowBg(i)}>
397
+ <td style={T.td}>
398
+ <Swatch cssVar={e.cssProperty} />
399
+ </td>
400
+ <td style={T.td}>
401
+ <span style={T.mono}>{e.pandaToken}</span>
402
+ </td>
403
+ <td style={T.td}>
404
+ <span style={{ ...T.mono, color: '#666' }}>
405
+ {e.cssProperty}
406
+ </span>
407
+ </td>
408
+ <td style={T.td}>
409
+ <span style={{ ...T.mono, background: '#eef4e8' }}>
410
+ {e.exampleUsage}
411
+ </span>
412
+ </td>
413
+ <td style={{ ...T.td, color: '#666', fontSize: '12px' }}>
414
+ {e.notes ?? '—'}
415
+ </td>
416
+ </tr>
417
+ ))}
418
+ </tbody>
419
+ </table>
420
+ </>
421
+ )}
422
+
423
+ <div
424
+ style={{
425
+ marginTop: '20px',
426
+ padding: '12px 16px',
427
+ background: '#fff8e1',
428
+ borderRadius: '6px',
429
+ fontSize: '12px',
430
+ color: '#555',
431
+ lineHeight: 1.6,
432
+ }}
433
+ >
434
+ <strong>⚠ Mismatch tokens</strong> — five tokens do NOT follow
435
+ dot-notation despite slash-path Figma naming:{' '}
436
+ <code>surfaceVariant</code>, <code>inverseSurface</code>,{' '}
437
+ <code>inverseOnSurface</code>, <code>inversePrimary</code>,{' '}
438
+ <code>inverseSecondary</code>, <code>inverseTertiary</code>. Use the
439
+ Panda CSS token column exactly — never derive it from the Figma path
440
+ by replacing <code>/</code> with <code>.</code>.
441
+ </div>
442
+ </div>
443
+ </div>
444
+ );
445
+ },
446
+ };
447
+
448
+ // ── Story 2: Spacing & Shape ─────────────────────────────────────────────────
449
+
450
+ export const SpacingAndShape: Story = {
451
+ name: '2 · Spacing & Shape',
452
+ render: () => {
453
+ const entries = tokenMap.spacingAndShape as SpacingShapeEntry[];
454
+
455
+ const categories = ['spacing', 'radii', 'borderWidths', 'durations'];
456
+
457
+ return (
458
+ <div style={T.page}>
459
+ {categories.map((cat) => {
460
+ const rows = entries.filter((e) => e.pandaCategory === cat);
461
+ if (rows.length === 0) return null;
462
+
463
+ const catLabel: Record<string, string> = {
464
+ spacing: 'Spacing',
465
+ radii: 'Border Radii (M3 base tokens)',
466
+ borderWidths: 'Border Widths',
467
+ durations: 'Motion Durations',
468
+ };
469
+
470
+ return (
471
+ <div key={cat} style={T.section}>
472
+ <h2 style={T.h2}>{catLabel[cat] ?? cat}</h2>
473
+ <p style={T.meta}>
474
+ {rows.length} entries · Figma &quot;Spacing &amp; Shape&quot;
475
+ collection · v{tokenMap.version}
476
+ </p>
477
+
478
+ <table style={T.table}>
479
+ <thead>
480
+ <tr>
481
+ {cat === 'spacing' || cat === 'radii' ? (
482
+ <th style={T.th}>Visual</th>
483
+ ) : null}
484
+ <th style={T.th}>Figma Variable Path</th>
485
+ <th style={T.th}>Panda CSS Token</th>
486
+ <th style={T.th}>CSS Custom Property</th>
487
+ <th style={T.th}>Code Usage</th>
488
+ </tr>
489
+ </thead>
490
+ <tbody>
491
+ {rows.map((e, i) => (
492
+ <tr key={e.figmaPath} style={rowBg(i)}>
493
+ {cat === 'spacing' || cat === 'radii' ? (
494
+ <td style={T.td}>
495
+ <SpacingSwatch
496
+ cssVar={e.cssProperty}
497
+ category={cat}
498
+ />
499
+ </td>
500
+ ) : null}
501
+ <td style={T.td}>
502
+ <span style={T.mono}>{e.figmaPath}</span>
503
+ </td>
504
+ <td style={T.td}>
505
+ <span style={T.mono}>{e.pandaToken}</span>
506
+ {cat === 'radii' && (
507
+ <span
508
+ style={{
509
+ ...T.warning,
510
+ background: '#e8f0e0',
511
+ color: '#3a5a18',
512
+ }}
513
+ title="Use l1/l2/l3/full in component code, not this M3 base token"
514
+ >
515
+ base
516
+ </span>
517
+ )}
518
+ </td>
519
+ <td style={T.td}>
520
+ <span style={{ ...T.mono, color: '#666' }}>
521
+ {e.cssProperty}
522
+ </span>
523
+ </td>
524
+ <td style={T.td}>
525
+ <span style={{ ...T.mono, background: '#eef4e8' }}>
526
+ {e.exampleUsage}
527
+ </span>
528
+ </td>
529
+ </tr>
530
+ ))}
531
+ </tbody>
532
+ </table>
533
+
534
+ {cat === 'radii' && (
535
+ <div
536
+ style={{
537
+ marginTop: '12px',
538
+ padding: '12px 16px',
539
+ background: '#e8f0e0',
540
+ borderRadius: '6px',
541
+ fontSize: '12px',
542
+ color: '#3a5a18',
543
+ lineHeight: 1.6,
544
+ }}
545
+ >
546
+ <strong>Use DDS canonical radii in component code</strong> —
547
+ not these M3 base tokens. <code>l1</code> = 2px (checkboxes,
548
+ breadcrumbs) · <code>l2</code> = 6px (buttons, inputs, badges)
549
+ · <code>l3</code> = 8px (cards, dialogs, popovers) ·{' '}
550
+ <code>full</code> = 9999px (avatars, pills, switches)
551
+ </div>
552
+ )}
553
+ </div>
554
+ );
555
+ })}
556
+ </div>
557
+ );
558
+ },
559
+ };
560
+
561
+ // ── Story 3: Elevation & Shadows ─────────────────────────────────────────────
562
+
563
+ export const ElevationAndShadows: Story = {
564
+ name: '3 · Elevation & Shadows',
565
+ render: () => {
566
+ const baseLevels = tokenMap.effectStyles as EffectStyleEntry[];
567
+ const aliases = (tokenMap.shadowSemanticAliases ??
568
+ []) as ShadowAliasEntry[];
569
+
570
+ return (
571
+ <div style={T.page}>
572
+ <div style={T.section}>
573
+ <h2 style={T.h2}>Base Elevation Levels (M3)</h2>
574
+ <p style={T.meta}>
575
+ {baseLevels.length} levels · Figma effect styles · v
576
+ {tokenMap.version}
577
+ </p>
578
+
579
+ <table style={T.table}>
580
+ <thead>
581
+ <tr>
582
+ <th style={T.th}>Visual</th>
583
+ <th style={T.th}>Figma Effect Style</th>
584
+ <th style={T.th}>Panda Base Token</th>
585
+ <th style={T.th}>CSS Custom Property</th>
586
+ <th style={T.th}>Description</th>
587
+ <th style={T.th}>Code Usage</th>
588
+ </tr>
589
+ </thead>
590
+ <tbody>
591
+ {baseLevels.map((e, i) => (
592
+ <tr key={e.figmaEffectStyle} style={rowBg(i)}>
593
+ <td style={T.td}>
594
+ <ShadowSwatch cssVar={e.cssProperty} />
595
+ </td>
596
+ <td style={T.td}>
597
+ <span style={T.mono}>{e.figmaEffectStyle}</span>
598
+ </td>
599
+ <td style={T.td}>
600
+ <span style={T.mono}>{e.pandaToken}</span>
601
+ <span
602
+ style={T.warning}
603
+ title="Use semantic alias (xs/sm/md/lg/xl) in component code"
604
+ >
605
+ base
606
+ </span>
607
+ </td>
608
+ <td style={T.td}>
609
+ <span style={{ ...T.mono, color: '#666' }}>
610
+ {e.cssProperty}
611
+ </span>
612
+ </td>
613
+ <td style={{ ...T.td, color: '#555', fontSize: '12px' }}>
614
+ {e.description}
615
+ </td>
616
+ <td style={T.td}>
617
+ <span
618
+ style={{
619
+ ...T.mono,
620
+ background: '#fff3cd',
621
+ color: '#856404',
622
+ }}
623
+ >
624
+ {e.exampleUsage}
625
+ </span>
626
+ </td>
627
+ </tr>
628
+ ))}
629
+ </tbody>
630
+ </table>
631
+
632
+ <div
633
+ style={{
634
+ marginTop: '12px',
635
+ padding: '12px 16px',
636
+ background: '#fff3cd',
637
+ borderRadius: '6px',
638
+ fontSize: '12px',
639
+ color: '#856404',
640
+ lineHeight: 1.6,
641
+ }}
642
+ >
643
+ <strong>Do not use base level tokens in component code.</strong> Use
644
+ the semantic aliases below (xs/sm/md/lg/xl).
645
+ </div>
646
+ </div>
647
+
648
+ {aliases.length > 0 && (
649
+ <div style={T.section}>
650
+ <h2 style={T.h2}>Semantic Shadow Aliases</h2>
651
+ <p style={T.meta}>
652
+ {aliases.length} aliases · src/preset/shadows.ts · use these in
653
+ components and recipes
654
+ </p>
655
+
656
+ <table style={T.table}>
657
+ <thead>
658
+ <tr>
659
+ <th style={T.th}>Visual</th>
660
+ <th style={T.th}>Panda Token</th>
661
+ <th style={T.th}>CSS Custom Property</th>
662
+ <th style={T.th}>Resolves To</th>
663
+ <th style={T.th}>Description</th>
664
+ <th style={T.th}>Code Usage</th>
665
+ </tr>
666
+ </thead>
667
+ <tbody>
668
+ {aliases.map((e, i) => (
669
+ <tr key={e.pandaToken} style={rowBg(i)}>
670
+ <td style={T.td}>
671
+ <ShadowSwatch cssVar={e.cssProperty} />
672
+ </td>
673
+ <td style={T.td}>
674
+ <span style={{ ...T.mono, fontWeight: '600' }}>
675
+ {e.pandaToken}
676
+ </span>
677
+ </td>
678
+ <td style={T.td}>
679
+ <span style={{ ...T.mono, color: '#666' }}>
680
+ {e.cssProperty}
681
+ </span>
682
+ </td>
683
+ <td style={T.td}>
684
+ <span style={T.mono}>{e.resolvesTo}</span>
685
+ </td>
686
+ <td style={{ ...T.td, color: '#555', fontSize: '12px' }}>
687
+ {e.description ?? '—'}
688
+ </td>
689
+ <td style={T.td}>
690
+ <span style={{ ...T.mono, background: '#eef4e8' }}>
691
+ {e.exampleUsage}
692
+ </span>
693
+ </td>
694
+ </tr>
695
+ ))}
696
+ </tbody>
697
+ </table>
698
+ </div>
699
+ )}
700
+ </div>
701
+ );
702
+ },
703
+ };
704
+
705
+ // ── Story 4: Typography ───────────────────────────────────────────────────────
706
+
707
+ export const Typography: Story = {
708
+ name: '4 · Typography',
709
+ render: () => {
710
+ const entries = tokenMap.textStyles as TextStyleEntry[];
711
+
712
+ // Group by pandaTextStyle (scale step)
713
+ const groups = entries.reduce<Record<string, TextStyleEntry[]>>(
714
+ (acc, e) => {
715
+ if (!acc[e.pandaTextStyle]) acc[e.pandaTextStyle] = [];
716
+ acc[e.pandaTextStyle].push(e);
717
+ return acc;
718
+ },
719
+ {},
720
+ );
721
+
722
+ const categoryOrder = [
723
+ 'displayLarge',
724
+ 'displayMedium',
725
+ 'displaySmall',
726
+ 'headlineLarge',
727
+ 'headlineMedium',
728
+ 'headlineSmall',
729
+ 'titleLarge',
730
+ 'titleMedium',
731
+ 'titleSmall',
732
+ 'bodyLarge',
733
+ 'bodyMedium',
734
+ 'bodySmall',
735
+ 'labelLarge',
736
+ 'labelMedium',
737
+ 'labelSmall',
738
+ ];
739
+
740
+ const categoryLabel: Record<string, string> = {
741
+ displayLarge: 'Display',
742
+ headlineLarge: 'Headline',
743
+ titleLarge: 'Title',
744
+ bodyLarge: 'Body',
745
+ labelLarge: 'Label',
746
+ };
747
+
748
+ let rowIndex = 0;
749
+
750
+ return (
751
+ <div style={T.page}>
752
+ <h2 style={T.h2}>Typography — All Weight Variants</h2>
753
+ <p style={T.meta}>
754
+ {entries.length} entries ({categoryOrder.length} scale steps × weight
755
+ variants) · auto-generated from docs/token-name-mapping.json v
756
+ {tokenMap.version}
757
+ </p>
758
+
759
+ <table style={T.table}>
760
+ <thead>
761
+ <tr>
762
+ <th style={T.th}>Preview</th>
763
+ <th style={T.th}>Figma Text Style</th>
764
+ <th style={T.th}>DDS Token Path</th>
765
+ <th style={T.th}>Panda textStyle</th>
766
+ <th style={T.th}>Font / Weight</th>
767
+ <th style={T.th}>Size / LH</th>
768
+ <th style={T.th}>Code Usage</th>
769
+ </tr>
770
+ </thead>
771
+ <tbody>
772
+ {categoryOrder.flatMap((step) => {
773
+ const rows = groups[step] ?? [];
774
+ return rows.map((e) => {
775
+ const i = rowIndex++;
776
+ const isDefault = e.isDefaultWeight;
777
+ return (
778
+ <tr key={e.figmaTextStyle} style={rowBg(i)}>
779
+ <td style={T.td}>
780
+ <TypeSwatch
781
+ fontFamily={e.fontFamily}
782
+ fontWeight={e.fontWeight}
783
+ fontSize={e.fontSize}
784
+ />
785
+ </td>
786
+ <td style={T.td}>
787
+ <span style={T.mono}>{e.figmaTextStyle}</span>
788
+ {/* Section label at first row of each category */}
789
+ {categoryLabel[step] && rows.indexOf(e) === 0 && (
790
+ <span
791
+ style={{
792
+ display: 'block',
793
+ fontSize: '10px',
794
+ fontWeight: '700',
795
+ color: '#4C662B',
796
+ textTransform: 'uppercase',
797
+ letterSpacing: '0.05em',
798
+ marginTop: '2px',
799
+ }}
800
+ >
801
+ ── {categoryLabel[step]}
802
+ </span>
803
+ )}
804
+ </td>
805
+ <td style={T.td}>
806
+ <span style={{ ...T.mono, fontSize: '11px' }}>
807
+ {e.ddsTokenPath}
808
+ </span>
809
+ </td>
810
+ <td style={T.td}>
811
+ <span style={T.mono}>{e.pandaTextStyle}</span>
812
+ {isDefault && (
813
+ <span
814
+ style={T.pill}
815
+ title="Default weight for this scale step"
816
+ >
817
+ default
818
+ </span>
819
+ )}
820
+ </td>
821
+ <td style={T.td}>
822
+ <span style={{ ...T.mono, fontSize: '11px' }}>
823
+ {e.fontFamily.split(',')[0].replace(/"/g, '')}
824
+ </span>
825
+ <span
826
+ style={{
827
+ display: 'block',
828
+ fontFamily: 'monospace',
829
+ fontSize: '11px',
830
+ color: '#777',
831
+ }}
832
+ >
833
+ {e.fontStyle} ({e.fontWeight})
834
+ </span>
835
+ </td>
836
+ <td style={T.td}>
837
+ <span
838
+ style={{
839
+ fontFamily: 'monospace',
840
+ fontSize: '11px',
841
+ color: '#444',
842
+ }}
843
+ >
844
+ {e.fontSize}px / {e.lineHeightPx}px
845
+ {e.letterSpacing !== 0 && (
846
+ <span style={{ color: '#999' }}>
847
+ {' '}
848
+ ls:{e.letterSpacing}
849
+ </span>
850
+ )}
851
+ </span>
852
+ </td>
853
+ <td style={T.td}>
854
+ <span
855
+ style={{
856
+ ...T.mono,
857
+ background: '#eef4e8',
858
+ fontSize: '11px',
859
+ }}
860
+ >
861
+ {e.exampleUsage}
862
+ </span>
863
+ {!isDefault && (
864
+ <span
865
+ style={{
866
+ display: 'block',
867
+ fontFamily: 'monospace',
868
+ fontSize: '10px',
869
+ color: '#888',
870
+ marginTop: '2px',
871
+ }}
872
+ >
873
+ + fontWeight=&quot;{e.fontStyle.toLowerCase()}&quot;
874
+ </span>
875
+ )}
876
+ </td>
877
+ </tr>
878
+ );
879
+ });
880
+ })}
881
+ </tbody>
882
+ </table>
883
+
884
+ <div
885
+ style={{
886
+ marginTop: '20px',
887
+ padding: '12px 16px',
888
+ background: '#f0f4ff',
889
+ borderRadius: '6px',
890
+ fontSize: '12px',
891
+ color: '#334',
892
+ lineHeight: 1.7,
893
+ }}
894
+ >
895
+ <strong>How to read this table:</strong> The <em>Panda textStyle</em>{' '}
896
+ sets geometry (size, line-height, letter-spacing, font-family). The
897
+ default weight variant is applied automatically. To use a non-default
898
+ weight, add <code>fontWeight</code> separately.
899
+ <br />
900
+ Example:{' '}
901
+ <code>
902
+ &lt;Text textStyle=&quot;headlineSmall&quot;
903
+ fontWeight=&quot;semibold&quot;&gt;
904
+ </code>
905
+ <br />
906
+ The <em>DDS Token Path</em> is what the Figma text style description
907
+ field encodes (
908
+ <code>dds:typography.scale.headlineSmall.weights.semiBold</code>) —
909
+ the most direct resolution path from MCP output.
910
+ </div>
911
+ </div>
912
+ );
913
+ },
914
+ };
915
+
916
+ // ── Story 5: Full Reference (all sections on one page) ───────────────────────
917
+
918
+ export const FullReference: Story = {
919
+ name: '5 · Full Reference',
920
+ render: () => (
921
+ <div style={T.page}>
922
+ <div
923
+ style={{
924
+ padding: '16px 20px',
925
+ background: '#eef4e8',
926
+ borderRadius: '8px',
927
+ marginBottom: '40px',
928
+ fontSize: '13px',
929
+ color: '#3a5a18',
930
+ lineHeight: 1.7,
931
+ }}
932
+ >
933
+ <strong>Token Reference — v{tokenMap.version}</strong>
934
+ <br />
935
+ Generated:{' '}
936
+ {tokenMap.generatedAt
937
+ ? new Date(tokenMap.generatedAt).toLocaleString()
938
+ : 'unknown'}
939
+ <br />
940
+ Sources:{' '}
941
+ {(tokenMap.generatedFrom as string[]).map((f, i) => (
942
+ <span key={f}>
943
+ <code
944
+ style={{
945
+ fontSize: '11px',
946
+ background: '#d4e8c4',
947
+ padding: '1px 4px',
948
+ borderRadius: '3px',
949
+ }}
950
+ >
951
+ {f}
952
+ </code>
953
+ {i < tokenMap.generatedFrom.length - 1 ? ' · ' : ''}
954
+ </span>
955
+ ))}
956
+ <br />
957
+ <strong>To update:</strong> run <code>pnpm figma:export</code> then
958
+ rebuild Storybook.
959
+ </div>
960
+
961
+ {/* Render all four sections inline */}
962
+ {SemanticColors.render && SemanticColors.render({} as never, {} as never)}
963
+ {SpacingAndShape.render &&
964
+ SpacingAndShape.render({} as never, {} as never)}
965
+ {ElevationAndShadows.render &&
966
+ ElevationAndShadows.render({} as never, {} as never)}
967
+ {Typography.render && Typography.render({} as never, {} as never)}
968
+ </div>
969
+ ),
970
+ };