@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.
- package/README.md +76 -73
- package/dist/{chunk-ZPECW4N2.js → chunk-4XOWPACJ.js} +257 -105
- package/dist/chunk-4XOWPACJ.js.map +1 -0
- package/dist/{chunk-QNCZYFUJ.cjs → chunk-AZ6QU2L2.cjs} +257 -105
- package/dist/chunk-AZ6QU2L2.cjs.map +1 -0
- package/dist/{chunk-TBLDQATQ.cjs → chunk-EBDNCZF6.cjs} +94 -54
- package/dist/chunk-EBDNCZF6.cjs.map +1 -0
- package/dist/{chunk-UHSL4N44.js → chunk-MAVUSE4F.js} +94 -55
- package/dist/chunk-MAVUSE4F.js.map +1 -0
- package/dist/components/Checkbox.d.ts +1 -1
- package/dist/components/Icons/LeftArrowIcon.d.ts +6 -0
- package/dist/components/Icons/LeftArrowIcon.d.ts.map +1 -0
- package/dist/components/Icons/RightArrowIcon.d.ts.map +1 -1
- package/dist/components/Icons/index.d.ts +1 -0
- package/dist/components/Icons/index.d.ts.map +1 -1
- package/dist/components/index.cjs +79 -75
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/contracts/design-language.contract.d.ts +52 -18
- package/dist/contracts/design-language.contract.d.ts.map +1 -1
- package/dist/figma-codex.json +2 -2
- package/dist/index.cjs +83 -79
- package/dist/index.js +2 -2
- package/dist/languages/material3.language.d.ts.map +1 -1
- package/dist/languages/transform.d.ts +5 -5
- package/dist/languages/transform.d.ts.map +1 -1
- package/dist/preset/index.cjs +2 -2
- package/dist/preset/index.js +1 -1
- package/docs/component-catalog.md +469 -0
- package/docs/superpowers/plans/2026-04-03-component-catalog-pipeline.md +667 -0
- package/docs/token-name-mapping.json +614 -42
- package/docs/token-name-mapping.md +117 -29
- package/package.json +3 -2
- package/src/components/Icons/LeftArrowIcon.tsx +28 -0
- package/src/components/Icons/RightArrowIcon.tsx +7 -2
- package/src/components/Icons/index.ts +1 -0
- package/src/components/__tests__/AbsoluteCenter.test.tsx +31 -0
- package/src/components/__tests__/Divider.test.tsx +38 -0
- package/src/components/__tests__/Group.test.tsx +34 -0
- package/src/components/__tests__/Icon.test.tsx +31 -0
- package/src/components/__tests__/SettingsPopover.test.tsx +39 -0
- package/src/components/__tests__/StudioControls.test.tsx +59 -0
- package/src/components/__tests__/Toaster.test.tsx +24 -0
- package/src/components/index.ts +1 -0
- package/src/contracts/design-language.contract.ts +69 -20
- package/src/languages/material3.language.ts +249 -80
- package/src/languages/transform.ts +45 -48
- package/src/preset/__tests__/translation-token-accuracy.test.ts +13 -0
- package/src/stories/foundations/Colors.mdx +9 -1
- package/src/stories/foundations/Elevation.mdx +23 -17
- package/src/stories/foundations/TokenReference.stories.tsx +970 -0
- package/src/stories/foundations/TonalPaletteDerivation.stories.tsx +782 -0
- package/src/stories/foundations/Typography.mdx +125 -25
- package/dist/chunk-QNCZYFUJ.cjs.map +0 -1
- package/dist/chunk-TBLDQATQ.cjs.map +0 -1
- package/dist/chunk-UHSL4N44.js.map +0 -1
- package/dist/chunk-ZPECW4N2.js.map +0 -1
- package/docs/context-share/ELEVATION_FIX_PLAN.md +0 -903
- package/docs/context-share/fix-checkbox-radio-tokens.md +0 -145
- package/docs/context-share/icon-component-prompt.md +0 -154
|
@@ -0,0 +1,782 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tonal Palette Derivation Map
|
|
3
|
+
*
|
|
4
|
+
* Shows every M3 tonal primitive — its Figma name, hex value,
|
|
5
|
+
* the CSS variable it would generate IF exposed as a Panda token,
|
|
6
|
+
* and which semantic role(s) it derives into.
|
|
7
|
+
*
|
|
8
|
+
* Key insight: tonal steps have NO direct Panda CSS tokens.
|
|
9
|
+
* They exist only as Figma Primitives variables and as the raw
|
|
10
|
+
* values behind semantic tokens. Use semantic tokens in code.
|
|
11
|
+
*
|
|
12
|
+
* Data sourced from src/languages/material3.language.ts
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
16
|
+
import type { CSSProperties } from 'react';
|
|
17
|
+
|
|
18
|
+
// ── Raw data sourced directly from material3.language.ts ─────────────────────
|
|
19
|
+
|
|
20
|
+
const PALETTES = {
|
|
21
|
+
primary: {
|
|
22
|
+
label: 'Primary',
|
|
23
|
+
description: 'TastyMakers green — brand identity, CTAs, active states',
|
|
24
|
+
steps: {
|
|
25
|
+
0: '#000000',
|
|
26
|
+
10: '#102000',
|
|
27
|
+
20: '#1F3700',
|
|
28
|
+
30: '#2F4F00',
|
|
29
|
+
40: '#3F6900',
|
|
30
|
+
50: '#518500',
|
|
31
|
+
60: '#64A104',
|
|
32
|
+
70: '#7DBD2A',
|
|
33
|
+
80: '#97D945',
|
|
34
|
+
90: '#B2F65F',
|
|
35
|
+
95: '#D2FF9B',
|
|
36
|
+
99: '#F9FFE9',
|
|
37
|
+
100: '#FFFFFF',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
secondary: {
|
|
41
|
+
label: 'Secondary',
|
|
42
|
+
description:
|
|
43
|
+
'Muted olive green — supporting actions, less prominent elements',
|
|
44
|
+
steps: {
|
|
45
|
+
0: '#000000',
|
|
46
|
+
10: '#121F04',
|
|
47
|
+
20: '#263515',
|
|
48
|
+
30: '#3C4C2A',
|
|
49
|
+
40: '#54643F',
|
|
50
|
+
50: '#6C7D56',
|
|
51
|
+
60: '#85976E',
|
|
52
|
+
70: '#A0B187',
|
|
53
|
+
80: '#BBCDA1',
|
|
54
|
+
90: '#D7E9BB',
|
|
55
|
+
95: '#E5F7C9',
|
|
56
|
+
99: '#F9FFE9',
|
|
57
|
+
100: '#FFFFFF',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
tertiary: {
|
|
61
|
+
label: 'Tertiary',
|
|
62
|
+
description:
|
|
63
|
+
'Teal / cyan — accent color, visual contrast, complementary interest',
|
|
64
|
+
steps: {
|
|
65
|
+
0: '#000000',
|
|
66
|
+
10: '#00201E',
|
|
67
|
+
20: '#003735',
|
|
68
|
+
30: '#00504C',
|
|
69
|
+
40: '#046A66',
|
|
70
|
+
50: '#30837F',
|
|
71
|
+
60: '#4D9D98',
|
|
72
|
+
70: '#69B8B3',
|
|
73
|
+
80: '#85D4CF',
|
|
74
|
+
90: '#A1F1EB',
|
|
75
|
+
95: '#B0FFF9',
|
|
76
|
+
99: '#F2FFFD',
|
|
77
|
+
100: '#FFFFFF',
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
neutral: {
|
|
81
|
+
label: 'Neutral',
|
|
82
|
+
description: 'Warm gray — all surface, background, and primary text tokens',
|
|
83
|
+
steps: {
|
|
84
|
+
0: '#000000',
|
|
85
|
+
10: '#1B1C18',
|
|
86
|
+
20: '#30312C',
|
|
87
|
+
30: '#464742',
|
|
88
|
+
40: '#5E5F59',
|
|
89
|
+
50: '#777771',
|
|
90
|
+
60: '#91918B',
|
|
91
|
+
70: '#ABACA5',
|
|
92
|
+
80: '#C7C7C0',
|
|
93
|
+
90: '#E3E3DB',
|
|
94
|
+
95: '#F2F1E9',
|
|
95
|
+
99: '#FDFCF5',
|
|
96
|
+
100: '#FFFFFF',
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
neutralVariant: {
|
|
100
|
+
label: 'Neutral Variant',
|
|
101
|
+
description:
|
|
102
|
+
'Slightly warmer gray — chip backgrounds, secondary borders, muted text',
|
|
103
|
+
steps: {
|
|
104
|
+
0: '#000000',
|
|
105
|
+
10: '#191D14',
|
|
106
|
+
20: '#2E3228',
|
|
107
|
+
30: '#44483D',
|
|
108
|
+
40: '#5C6054',
|
|
109
|
+
50: '#75796C',
|
|
110
|
+
60: '#8F9285',
|
|
111
|
+
70: '#A9AD9F',
|
|
112
|
+
80: '#C5C8BA',
|
|
113
|
+
90: '#E1E4D5',
|
|
114
|
+
95: '#EFF2E3',
|
|
115
|
+
99: '#FBFEEE',
|
|
116
|
+
100: '#FFFFFF',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
error: {
|
|
120
|
+
label: 'Error',
|
|
121
|
+
description:
|
|
122
|
+
'Red — destructive actions, validation failures, danger states',
|
|
123
|
+
steps: {
|
|
124
|
+
0: '#000000',
|
|
125
|
+
10: '#410E0B',
|
|
126
|
+
20: '#601410',
|
|
127
|
+
30: '#8C1D18',
|
|
128
|
+
40: '#B3261E',
|
|
129
|
+
50: '#DC362E',
|
|
130
|
+
60: '#E46962',
|
|
131
|
+
70: '#EC928E',
|
|
132
|
+
80: '#F2B8B5',
|
|
133
|
+
90: '#F9DEDC',
|
|
134
|
+
95: '#FCEEEE',
|
|
135
|
+
99: '#FFFBF9',
|
|
136
|
+
100: '#FFFFFF',
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
} as const;
|
|
140
|
+
|
|
141
|
+
// ── Derivation map: (palette, step) → semantic Panda CSS token(s) ─────────────
|
|
142
|
+
// Sourced from src/preset/semantic-tokens.ts + M3 spec derivation
|
|
143
|
+
|
|
144
|
+
const DERIVATION: Record<string, string[]> = {
|
|
145
|
+
// Primary
|
|
146
|
+
'primary/10': ['onPrimary.container'],
|
|
147
|
+
'primary/40': ['primary'],
|
|
148
|
+
'primary/80': ['inversePrimary'],
|
|
149
|
+
'primary/90': ['primary.container'],
|
|
150
|
+
'primary/100': ['onPrimary'],
|
|
151
|
+
// Secondary
|
|
152
|
+
'secondary/10': ['onSecondary.container'],
|
|
153
|
+
'secondary/40': ['secondary'],
|
|
154
|
+
'secondary/90': ['secondary.container'],
|
|
155
|
+
'secondary/100': ['onSecondary'],
|
|
156
|
+
// Tertiary
|
|
157
|
+
'tertiary/10': ['onTertiary.container'],
|
|
158
|
+
'tertiary/40': ['tertiary'],
|
|
159
|
+
'tertiary/90': ['tertiary.container'],
|
|
160
|
+
'tertiary/100': ['onTertiary'],
|
|
161
|
+
// Error
|
|
162
|
+
'error/10': ['onError.container'],
|
|
163
|
+
'error/40': ['error'],
|
|
164
|
+
'error/90': ['error.container'],
|
|
165
|
+
'error/100': ['onError'],
|
|
166
|
+
// Neutral → surface system
|
|
167
|
+
'neutral/0': ['scrim', 'shadow'],
|
|
168
|
+
'neutral/10': ['onSurface', 'onBackground'],
|
|
169
|
+
'neutral/20': ['inverseSurface'],
|
|
170
|
+
'neutral/90': ['surface.container'],
|
|
171
|
+
'neutral/95': ['surface.container.low', 'inverseOnSurface'],
|
|
172
|
+
'neutral/99': ['surface', 'background'],
|
|
173
|
+
'neutral/100': ['surface.container.lowest'],
|
|
174
|
+
// NeutralVariant → surface variant + outline
|
|
175
|
+
'neutralVariant/30': ['onSurface.variant'],
|
|
176
|
+
'neutralVariant/50': ['outline'],
|
|
177
|
+
'neutralVariant/80': ['outline.variant'],
|
|
178
|
+
'neutralVariant/90': ['surfaceVariant'],
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// ── Helper: luminance for readable text color over swatch ───────────────────
|
|
182
|
+
|
|
183
|
+
function isLight(hex: string): boolean {
|
|
184
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
185
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
186
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
187
|
+
return r * 0.299 + g * 0.587 + b * 0.114 > 128;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── Shared styles ─────────────────────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
const S: Record<string, CSSProperties> = {
|
|
193
|
+
page: {
|
|
194
|
+
fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, sans-serif',
|
|
195
|
+
fontSize: '13px',
|
|
196
|
+
color: '#1a1a1a',
|
|
197
|
+
maxWidth: '1200px',
|
|
198
|
+
},
|
|
199
|
+
header: {
|
|
200
|
+
marginBottom: '40px',
|
|
201
|
+
},
|
|
202
|
+
h1: {
|
|
203
|
+
fontSize: '22px',
|
|
204
|
+
fontWeight: '700',
|
|
205
|
+
margin: '0 0 8px',
|
|
206
|
+
color: '#111',
|
|
207
|
+
},
|
|
208
|
+
subtitle: {
|
|
209
|
+
fontSize: '13px',
|
|
210
|
+
color: '#666',
|
|
211
|
+
margin: '0 0 24px',
|
|
212
|
+
lineHeight: 1.6,
|
|
213
|
+
maxWidth: '700px',
|
|
214
|
+
},
|
|
215
|
+
sectionBlock: {
|
|
216
|
+
marginBottom: '48px',
|
|
217
|
+
},
|
|
218
|
+
sectionHeader: {
|
|
219
|
+
display: 'flex',
|
|
220
|
+
alignItems: 'baseline',
|
|
221
|
+
gap: '12px',
|
|
222
|
+
marginBottom: '4px',
|
|
223
|
+
},
|
|
224
|
+
h2: {
|
|
225
|
+
fontSize: '17px',
|
|
226
|
+
fontWeight: '700',
|
|
227
|
+
margin: '0',
|
|
228
|
+
color: '#111',
|
|
229
|
+
},
|
|
230
|
+
description: {
|
|
231
|
+
fontSize: '12px',
|
|
232
|
+
color: '#777',
|
|
233
|
+
margin: '0 0 12px',
|
|
234
|
+
lineHeight: 1.5,
|
|
235
|
+
},
|
|
236
|
+
table: {
|
|
237
|
+
width: '100%',
|
|
238
|
+
borderCollapse: 'collapse' as const,
|
|
239
|
+
fontSize: '12px',
|
|
240
|
+
tableLayout: 'fixed' as const,
|
|
241
|
+
},
|
|
242
|
+
th: {
|
|
243
|
+
textAlign: 'left' as const,
|
|
244
|
+
padding: '7px 10px',
|
|
245
|
+
background: '#f3f3f3',
|
|
246
|
+
borderBottom: '2px solid #ddd',
|
|
247
|
+
fontWeight: '600',
|
|
248
|
+
fontSize: '11px',
|
|
249
|
+
color: '#444',
|
|
250
|
+
whiteSpace: 'nowrap' as const,
|
|
251
|
+
},
|
|
252
|
+
td: {
|
|
253
|
+
padding: '0',
|
|
254
|
+
borderBottom: '1px solid #eee',
|
|
255
|
+
verticalAlign: 'middle' as const,
|
|
256
|
+
},
|
|
257
|
+
mono: {
|
|
258
|
+
fontFamily: 'monospace',
|
|
259
|
+
fontSize: '11px',
|
|
260
|
+
},
|
|
261
|
+
pill: {
|
|
262
|
+
display: 'inline-flex' as const,
|
|
263
|
+
alignItems: 'center',
|
|
264
|
+
fontSize: '10px',
|
|
265
|
+
fontWeight: '600',
|
|
266
|
+
padding: '2px 7px',
|
|
267
|
+
borderRadius: '10px',
|
|
268
|
+
whiteSpace: 'nowrap' as const,
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
function cellPad(extra?: CSSProperties): CSSProperties {
|
|
273
|
+
return { padding: '6px 10px', ...extra };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Semantic token pill — green if it has a semantic role
|
|
277
|
+
function SemanticPill({ token }: { token: string }) {
|
|
278
|
+
const isContainer = token.includes('container');
|
|
279
|
+
const isInverse = token.includes('inverse') || token.includes('Inverse');
|
|
280
|
+
const isOutline = token.includes('outline') || token.includes('Outline');
|
|
281
|
+
const isSurface = token.includes('surface') || token.includes('Surface');
|
|
282
|
+
|
|
283
|
+
let bg = '#e8f4e8';
|
|
284
|
+
let color = '#2a6a2a';
|
|
285
|
+
|
|
286
|
+
if (isInverse) {
|
|
287
|
+
bg = '#f3e8ff';
|
|
288
|
+
color = '#6a2a8a';
|
|
289
|
+
} else if (isContainer) {
|
|
290
|
+
bg = '#e8f0ff';
|
|
291
|
+
color = '#2a4a8a';
|
|
292
|
+
} else if (isOutline) {
|
|
293
|
+
bg = '#fff3e0';
|
|
294
|
+
color = '#8a5a00';
|
|
295
|
+
} else if (isSurface) {
|
|
296
|
+
bg = '#f0f0f0';
|
|
297
|
+
color = '#444444';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return (
|
|
301
|
+
<span
|
|
302
|
+
style={{
|
|
303
|
+
...S.pill,
|
|
304
|
+
background: bg,
|
|
305
|
+
color,
|
|
306
|
+
marginRight: '3px',
|
|
307
|
+
marginBottom: '2px',
|
|
308
|
+
}}
|
|
309
|
+
>
|
|
310
|
+
{token}
|
|
311
|
+
</span>
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Color swatch cell
|
|
316
|
+
function SwatchCell({ hex, step }: { hex: string; step: number }) {
|
|
317
|
+
const light = isLight(hex);
|
|
318
|
+
return (
|
|
319
|
+
<td style={{ ...S.td, width: '72px' }}>
|
|
320
|
+
<div
|
|
321
|
+
style={{
|
|
322
|
+
background: hex,
|
|
323
|
+
width: '100%',
|
|
324
|
+
height: '40px',
|
|
325
|
+
display: 'flex',
|
|
326
|
+
alignItems: 'center',
|
|
327
|
+
justifyContent: 'center',
|
|
328
|
+
}}
|
|
329
|
+
>
|
|
330
|
+
<span
|
|
331
|
+
style={{
|
|
332
|
+
...S.mono,
|
|
333
|
+
fontSize: '10px',
|
|
334
|
+
color: light ? 'rgba(0,0,0,0.55)' : 'rgba(255,255,255,0.7)',
|
|
335
|
+
fontWeight: '600',
|
|
336
|
+
}}
|
|
337
|
+
>
|
|
338
|
+
{step}
|
|
339
|
+
</span>
|
|
340
|
+
</div>
|
|
341
|
+
</td>
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ── Main story component ──────────────────────────────────────────────────────
|
|
346
|
+
|
|
347
|
+
function DerivationTable({
|
|
348
|
+
paletteKey,
|
|
349
|
+
}: {
|
|
350
|
+
paletteKey: keyof typeof PALETTES;
|
|
351
|
+
}) {
|
|
352
|
+
const palette = PALETTES[paletteKey];
|
|
353
|
+
const steps = Object.entries(palette.steps) as [string, string][];
|
|
354
|
+
|
|
355
|
+
return (
|
|
356
|
+
<div style={S.sectionBlock}>
|
|
357
|
+
<div style={S.sectionHeader}>
|
|
358
|
+
<h2 style={S.h2}>{palette.label}</h2>
|
|
359
|
+
<span style={{ ...S.mono, fontSize: '11px', color: '#999' }}>
|
|
360
|
+
{paletteKey}
|
|
361
|
+
</span>
|
|
362
|
+
</div>
|
|
363
|
+
<p style={S.description}>{palette.description}</p>
|
|
364
|
+
|
|
365
|
+
<table style={S.table}>
|
|
366
|
+
<colgroup>
|
|
367
|
+
<col style={{ width: '72px' }} /> {/* swatch */}
|
|
368
|
+
<col style={{ width: '160px' }} /> {/* figma */}
|
|
369
|
+
<col style={{ width: '90px' }} /> {/* step */}
|
|
370
|
+
<col style={{ width: '90px' }} /> {/* hex */}
|
|
371
|
+
<col style={{ width: '240px' }} /> {/* css var */}
|
|
372
|
+
<col /> {/* semantic */}
|
|
373
|
+
</colgroup>
|
|
374
|
+
<thead>
|
|
375
|
+
<tr>
|
|
376
|
+
<th style={{ ...S.th, width: '72px' }}></th>
|
|
377
|
+
<th style={S.th}>Figma Variable</th>
|
|
378
|
+
<th style={S.th}>Step</th>
|
|
379
|
+
<th style={S.th}>Hex</th>
|
|
380
|
+
<th style={S.th}>Panda CSS Note</th>
|
|
381
|
+
<th style={S.th}>Semantic Role(s)</th>
|
|
382
|
+
</tr>
|
|
383
|
+
</thead>
|
|
384
|
+
<tbody>
|
|
385
|
+
{steps.map(([stepStr, hex]) => {
|
|
386
|
+
const step = parseInt(stepStr);
|
|
387
|
+
const figmaName = `${paletteKey}/${step}`;
|
|
388
|
+
const semanticRoles = DERIVATION[figmaName] ?? [];
|
|
389
|
+
const hasRole = semanticRoles.length > 0;
|
|
390
|
+
|
|
391
|
+
// Highlight rows that have semantic roles
|
|
392
|
+
const rowBg = hasRole ? 'rgba(76,102,43,0.04)' : undefined;
|
|
393
|
+
|
|
394
|
+
return (
|
|
395
|
+
<tr key={step} style={{ background: rowBg }}>
|
|
396
|
+
<SwatchCell hex={hex} step={step} />
|
|
397
|
+
|
|
398
|
+
{/* Figma variable name */}
|
|
399
|
+
<td style={S.td}>
|
|
400
|
+
<span style={{ ...cellPad(), ...S.mono, display: 'block' }}>
|
|
401
|
+
{figmaName}
|
|
402
|
+
</span>
|
|
403
|
+
</td>
|
|
404
|
+
|
|
405
|
+
{/* Step number */}
|
|
406
|
+
<td style={S.td}>
|
|
407
|
+
<span style={{ ...cellPad(), ...S.mono, display: 'block' }}>
|
|
408
|
+
{step}
|
|
409
|
+
</span>
|
|
410
|
+
</td>
|
|
411
|
+
|
|
412
|
+
{/* Hex */}
|
|
413
|
+
<td style={S.td}>
|
|
414
|
+
<div
|
|
415
|
+
style={{
|
|
416
|
+
...cellPad(),
|
|
417
|
+
display: 'flex',
|
|
418
|
+
alignItems: 'center',
|
|
419
|
+
gap: '6px',
|
|
420
|
+
}}
|
|
421
|
+
>
|
|
422
|
+
<div
|
|
423
|
+
style={{
|
|
424
|
+
width: '12px',
|
|
425
|
+
height: '12px',
|
|
426
|
+
borderRadius: '2px',
|
|
427
|
+
background: hex,
|
|
428
|
+
border: '1px solid rgba(0,0,0,0.15)',
|
|
429
|
+
flexShrink: 0,
|
|
430
|
+
}}
|
|
431
|
+
/>
|
|
432
|
+
<span style={S.mono}>{hex}</span>
|
|
433
|
+
</div>
|
|
434
|
+
</td>
|
|
435
|
+
|
|
436
|
+
{/* Panda CSS note */}
|
|
437
|
+
<td style={S.td}>
|
|
438
|
+
<span
|
|
439
|
+
style={{
|
|
440
|
+
...cellPad(),
|
|
441
|
+
display: 'block',
|
|
442
|
+
fontSize: '11px',
|
|
443
|
+
color: '#999',
|
|
444
|
+
fontStyle: 'italic',
|
|
445
|
+
}}
|
|
446
|
+
>
|
|
447
|
+
No direct Panda token —{' '}
|
|
448
|
+
<span style={{ ...S.mono, fontStyle: 'normal' }}>
|
|
449
|
+
--colors-{paletteKey}-{step}
|
|
450
|
+
</span>{' '}
|
|
451
|
+
(Figma Primitives only)
|
|
452
|
+
</span>
|
|
453
|
+
</td>
|
|
454
|
+
|
|
455
|
+
{/* Semantic roles */}
|
|
456
|
+
<td style={S.td}>
|
|
457
|
+
<div
|
|
458
|
+
style={{
|
|
459
|
+
...cellPad(),
|
|
460
|
+
display: 'flex',
|
|
461
|
+
flexWrap: 'wrap',
|
|
462
|
+
gap: '2px',
|
|
463
|
+
}}
|
|
464
|
+
>
|
|
465
|
+
{hasRole ? (
|
|
466
|
+
semanticRoles.map((role) => (
|
|
467
|
+
<SemanticPill key={role} token={role} />
|
|
468
|
+
))
|
|
469
|
+
) : (
|
|
470
|
+
<span style={{ color: '#ccc', fontSize: '11px' }}>
|
|
471
|
+
— no semantic mapping
|
|
472
|
+
</span>
|
|
473
|
+
)}
|
|
474
|
+
</div>
|
|
475
|
+
</td>
|
|
476
|
+
</tr>
|
|
477
|
+
);
|
|
478
|
+
})}
|
|
479
|
+
</tbody>
|
|
480
|
+
</table>
|
|
481
|
+
</div>
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// ── Storybook meta ────────────────────────────────────────────────────────────
|
|
486
|
+
|
|
487
|
+
const meta = {
|
|
488
|
+
title: 'Foundations/Tonal Palette Derivation',
|
|
489
|
+
parameters: { layout: 'padded' },
|
|
490
|
+
} satisfies Meta;
|
|
491
|
+
|
|
492
|
+
export default meta;
|
|
493
|
+
type Story = StoryObj<typeof meta>;
|
|
494
|
+
|
|
495
|
+
// ── Story 1: Full derivation map ─────────────────────────────────────────────
|
|
496
|
+
|
|
497
|
+
export const FullDerivationMap: Story = {
|
|
498
|
+
name: '1 · Full Derivation Map',
|
|
499
|
+
render: () => (
|
|
500
|
+
<div style={S.page}>
|
|
501
|
+
<div style={S.header}>
|
|
502
|
+
<h1 style={S.h1}>Tonal Palette → Semantic Token Derivation Map</h1>
|
|
503
|
+
<p style={S.subtitle}>
|
|
504
|
+
Every M3 tonal primitive across all six palettes, showing which
|
|
505
|
+
semantic Panda CSS token each step derives into.{' '}
|
|
506
|
+
<strong>
|
|
507
|
+
Highlighted rows have a semantic equivalent — use that token
|
|
508
|
+
instead.
|
|
509
|
+
</strong>
|
|
510
|
+
<br />
|
|
511
|
+
Tonal steps have no direct Panda CSS tokens — they exist only as Figma
|
|
512
|
+
Primitives variables. The CSS variable column shows what they{' '}
|
|
513
|
+
<em>would</em> be named if exposed, for reference when auditing
|
|
514
|
+
existing code that uses them.
|
|
515
|
+
</p>
|
|
516
|
+
|
|
517
|
+
{/* Legend */}
|
|
518
|
+
<div
|
|
519
|
+
style={{
|
|
520
|
+
display: 'flex',
|
|
521
|
+
gap: '24px',
|
|
522
|
+
flexWrap: 'wrap',
|
|
523
|
+
padding: '12px 16px',
|
|
524
|
+
background: '#f8f8f8',
|
|
525
|
+
borderRadius: '6px',
|
|
526
|
+
fontSize: '12px',
|
|
527
|
+
marginBottom: '8px',
|
|
528
|
+
}}
|
|
529
|
+
>
|
|
530
|
+
<span style={{ fontWeight: '600', color: '#444' }}>
|
|
531
|
+
Semantic role colours:
|
|
532
|
+
</span>
|
|
533
|
+
<span>
|
|
534
|
+
<span
|
|
535
|
+
style={{ ...S.pill, background: '#e8f4e8', color: '#2a6a2a' }}
|
|
536
|
+
>
|
|
537
|
+
primary role
|
|
538
|
+
</span>
|
|
539
|
+
</span>
|
|
540
|
+
<span>
|
|
541
|
+
<span
|
|
542
|
+
style={{ ...S.pill, background: '#e8f0ff', color: '#2a4a8a' }}
|
|
543
|
+
>
|
|
544
|
+
container role
|
|
545
|
+
</span>
|
|
546
|
+
</span>
|
|
547
|
+
<span>
|
|
548
|
+
<span
|
|
549
|
+
style={{ ...S.pill, background: '#f3e8ff', color: '#6a2a8a' }}
|
|
550
|
+
>
|
|
551
|
+
inverse role
|
|
552
|
+
</span>
|
|
553
|
+
</span>
|
|
554
|
+
<span>
|
|
555
|
+
<span
|
|
556
|
+
style={{ ...S.pill, background: '#fff3e0', color: '#8a5a00' }}
|
|
557
|
+
>
|
|
558
|
+
outline role
|
|
559
|
+
</span>
|
|
560
|
+
</span>
|
|
561
|
+
<span>
|
|
562
|
+
<span
|
|
563
|
+
style={{ ...S.pill, background: '#f0f0f0', color: '#444444' }}
|
|
564
|
+
>
|
|
565
|
+
surface role
|
|
566
|
+
</span>
|
|
567
|
+
</span>
|
|
568
|
+
<span style={{ color: '#ccc' }}>
|
|
569
|
+
— no semantic mapping (raw primitive only)
|
|
570
|
+
</span>
|
|
571
|
+
</div>
|
|
572
|
+
</div>
|
|
573
|
+
|
|
574
|
+
{(Object.keys(PALETTES) as (keyof typeof PALETTES)[]).map((key) => (
|
|
575
|
+
<DerivationTable key={key} paletteKey={key} />
|
|
576
|
+
))}
|
|
577
|
+
</div>
|
|
578
|
+
),
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
// ── Story 2: Migration guide — which primitives have semantic equivalents ─────
|
|
582
|
+
|
|
583
|
+
export const MigrationGuide: Story = {
|
|
584
|
+
name: '2 · Migration Guide',
|
|
585
|
+
render: () => {
|
|
586
|
+
// Flatten all steps that have semantic roles
|
|
587
|
+
const mapped: Array<{
|
|
588
|
+
figmaName: string;
|
|
589
|
+
hex: string;
|
|
590
|
+
semanticTokens: string[];
|
|
591
|
+
migrate: string;
|
|
592
|
+
note?: string;
|
|
593
|
+
}> = [];
|
|
594
|
+
|
|
595
|
+
for (const [paletteKey, palette] of Object.entries(PALETTES)) {
|
|
596
|
+
for (const [stepStr, hex] of Object.entries(palette.steps)) {
|
|
597
|
+
const step = parseInt(stepStr);
|
|
598
|
+
const figmaName = `${paletteKey}/${step}`;
|
|
599
|
+
const semanticRoles = DERIVATION[figmaName];
|
|
600
|
+
if (semanticRoles && semanticRoles.length > 0) {
|
|
601
|
+
// Pick the most useful token for migration guidance
|
|
602
|
+
const primary = semanticRoles[0];
|
|
603
|
+
mapped.push({
|
|
604
|
+
figmaName,
|
|
605
|
+
hex,
|
|
606
|
+
semanticTokens: semanticRoles,
|
|
607
|
+
migrate: primary,
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Special case: neutral/20 gets a note
|
|
614
|
+
const neutral20 = mapped.find((r) => r.figmaName === 'neutral/20');
|
|
615
|
+
if (neutral20) {
|
|
616
|
+
neutral20.note =
|
|
617
|
+
'inverseSurface is its semantic role, but its intent is tooltip/snackbar bg. ' +
|
|
618
|
+
'For dark text use: consider adding fg.strong (onSurface is darker at neutral/10).';
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return (
|
|
622
|
+
<div style={S.page}>
|
|
623
|
+
<div style={S.header}>
|
|
624
|
+
<h1 style={S.h1}>
|
|
625
|
+
Migration Guide — Primitives with Semantic Equivalents
|
|
626
|
+
</h1>
|
|
627
|
+
<p style={S.subtitle}>
|
|
628
|
+
If you are using any of these tonal step values directly in Figma or
|
|
629
|
+
in code, switch to the semantic token in the third column. Semantic
|
|
630
|
+
tokens are theme-aware and switch automatically in dark mode.
|
|
631
|
+
<br />
|
|
632
|
+
Steps with <em>no semantic mapping</em> are not listed — those have
|
|
633
|
+
no current equivalent and require a custom token or can remain
|
|
634
|
+
as-is.
|
|
635
|
+
</p>
|
|
636
|
+
</div>
|
|
637
|
+
|
|
638
|
+
<table style={{ ...S.table, tableLayout: 'auto' as const }}>
|
|
639
|
+
<thead>
|
|
640
|
+
<tr>
|
|
641
|
+
<th style={S.th}>Swatch</th>
|
|
642
|
+
<th style={S.th}>Figma Primitive</th>
|
|
643
|
+
<th style={S.th}>Hex</th>
|
|
644
|
+
<th style={S.th}>→ Use This Semantic Token</th>
|
|
645
|
+
<th style={S.th}>In Code</th>
|
|
646
|
+
<th style={S.th}>Notes</th>
|
|
647
|
+
</tr>
|
|
648
|
+
</thead>
|
|
649
|
+
<tbody>
|
|
650
|
+
{mapped.map((row, i) => (
|
|
651
|
+
<tr
|
|
652
|
+
key={row.figmaName}
|
|
653
|
+
style={{ background: i % 2 === 0 ? '#fff' : '#fafafa' }}
|
|
654
|
+
>
|
|
655
|
+
{/* Swatch */}
|
|
656
|
+
<td style={{ ...S.td, width: '40px' }}>
|
|
657
|
+
<div
|
|
658
|
+
style={{
|
|
659
|
+
width: '36px',
|
|
660
|
+
height: '36px',
|
|
661
|
+
background: row.hex,
|
|
662
|
+
margin: '4px 6px',
|
|
663
|
+
borderRadius: '4px',
|
|
664
|
+
border: '1px solid rgba(0,0,0,0.1)',
|
|
665
|
+
}}
|
|
666
|
+
/>
|
|
667
|
+
</td>
|
|
668
|
+
|
|
669
|
+
{/* Figma name */}
|
|
670
|
+
<td style={S.td}>
|
|
671
|
+
<span style={{ ...cellPad(), ...S.mono, display: 'block' }}>
|
|
672
|
+
{row.figmaName}
|
|
673
|
+
</span>
|
|
674
|
+
</td>
|
|
675
|
+
|
|
676
|
+
{/* Hex */}
|
|
677
|
+
<td style={S.td}>
|
|
678
|
+
<span style={{ ...cellPad(), ...S.mono, display: 'block' }}>
|
|
679
|
+
{row.hex}
|
|
680
|
+
</span>
|
|
681
|
+
</td>
|
|
682
|
+
|
|
683
|
+
{/* Semantic token(s) */}
|
|
684
|
+
<td style={S.td}>
|
|
685
|
+
<div
|
|
686
|
+
style={{
|
|
687
|
+
...cellPad(),
|
|
688
|
+
display: 'flex',
|
|
689
|
+
flexWrap: 'wrap',
|
|
690
|
+
gap: '3px',
|
|
691
|
+
}}
|
|
692
|
+
>
|
|
693
|
+
{row.semanticTokens.map((t) => (
|
|
694
|
+
<SemanticPill key={t} token={t} />
|
|
695
|
+
))}
|
|
696
|
+
</div>
|
|
697
|
+
</td>
|
|
698
|
+
|
|
699
|
+
{/* Code example */}
|
|
700
|
+
<td style={S.td}>
|
|
701
|
+
<span
|
|
702
|
+
style={{
|
|
703
|
+
...cellPad(),
|
|
704
|
+
...S.mono,
|
|
705
|
+
display: 'block',
|
|
706
|
+
background: '#eef4e8',
|
|
707
|
+
borderRadius: '3px',
|
|
708
|
+
fontSize: '11px',
|
|
709
|
+
color: '#2a6a2a',
|
|
710
|
+
}}
|
|
711
|
+
>
|
|
712
|
+
{/* Guess the right prop based on token name */}
|
|
713
|
+
{row.migrate.startsWith('on') ||
|
|
714
|
+
row.migrate.includes('fg') ||
|
|
715
|
+
row.migrate.includes('text')
|
|
716
|
+
? `color="${row.migrate}"`
|
|
717
|
+
: row.migrate.includes('border') ||
|
|
718
|
+
row.migrate.includes('outline')
|
|
719
|
+
? `borderColor="${row.migrate}"`
|
|
720
|
+
: `bg="${row.migrate}"`}
|
|
721
|
+
</span>
|
|
722
|
+
</td>
|
|
723
|
+
|
|
724
|
+
{/* Notes */}
|
|
725
|
+
<td
|
|
726
|
+
style={{
|
|
727
|
+
...S.td,
|
|
728
|
+
fontSize: '11px',
|
|
729
|
+
color: '#888',
|
|
730
|
+
lineHeight: 1.5,
|
|
731
|
+
}}
|
|
732
|
+
>
|
|
733
|
+
<span style={cellPad()}>
|
|
734
|
+
{row.note ? (
|
|
735
|
+
<span style={{ color: '#c07000' }}>⚠ {row.note}</span>
|
|
736
|
+
) : row.semanticTokens.length > 1 ? (
|
|
737
|
+
`Also used for: ${row.semanticTokens.slice(1).join(', ')}`
|
|
738
|
+
) : (
|
|
739
|
+
''
|
|
740
|
+
)}
|
|
741
|
+
</span>
|
|
742
|
+
</td>
|
|
743
|
+
</tr>
|
|
744
|
+
))}
|
|
745
|
+
</tbody>
|
|
746
|
+
</table>
|
|
747
|
+
|
|
748
|
+
{/* Unmapped steps summary */}
|
|
749
|
+
<div
|
|
750
|
+
style={{
|
|
751
|
+
marginTop: '40px',
|
|
752
|
+
padding: '16px 20px',
|
|
753
|
+
background: '#f8f8f8',
|
|
754
|
+
borderRadius: '8px',
|
|
755
|
+
fontSize: '12px',
|
|
756
|
+
color: '#555',
|
|
757
|
+
lineHeight: 1.7,
|
|
758
|
+
}}
|
|
759
|
+
>
|
|
760
|
+
<strong>Steps with no semantic mapping</strong> — these have no
|
|
761
|
+
current DDS semantic equivalent. If you are using them, either keep
|
|
762
|
+
them as tonal step values (accepting no dark mode support) or add a
|
|
763
|
+
custom semantic token to <code>src/preset/semantic-tokens.ts</code>.
|
|
764
|
+
<br />
|
|
765
|
+
<br />
|
|
766
|
+
<strong>neutral/20</strong> (<code>#30312C</code>) — your most-used
|
|
767
|
+
unmapped step. Its M3 semantic role is <code>inverseSurface</code>{' '}
|
|
768
|
+
(tooltip bg) which is not the right intent for text. Closest available
|
|
769
|
+
alternatives:
|
|
770
|
+
<br />
|
|
771
|
+
• <code>onSurface</code> (<code>#1A1C16</code>,
|
|
772
|
+
neutral/10) — slightly darker, primary text intent ✓<br />
|
|
773
|
+
• <code>onSurface.variant</code> (<code>#44483D</code>,
|
|
774
|
+
neutralVariant/30) — slightly lighter, secondary text intent ✓<br />
|
|
775
|
+
• Add <code>fg.strong</code> to semantic-tokens.ts mapping
|
|
776
|
+
neutral/20 → neutral/80 for dark mode — recommended if you need this
|
|
777
|
+
exact shade with dark mode support
|
|
778
|
+
</div>
|
|
779
|
+
</div>
|
|
780
|
+
);
|
|
781
|
+
},
|
|
782
|
+
};
|