@discourser/design-system 0.22.2 → 0.22.4
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/dist/figma-codex.json +2 -2
- package/docs/CSS_USAGE.md +235 -0
- package/docs/FIGMA_MAKE_SETUP.md +339 -0
- package/docs/GUIDELINES_REVIEW.md +728 -0
- package/docs/MAINTAINER_CHECKLIST.md +265 -0
- package/docs/TESTING_QUICK_REFERENCE.md +159 -0
- package/docs/TESTING_TOKENS.md +340 -0
- package/docs/active-stories/README.md +29 -0
- package/docs/active-stories/STORY-006a-figma-translation-foundations.md +324 -0
- package/docs/active-stories/STORY-006b-figma-translation-components.md +201 -0
- package/docs/active-stories/STORY-006c-figma-translation-layout-extension.md +258 -0
- package/docs/active-stories/STORY-008-kai-sidecar-fragments.md +137 -0
- package/docs/active-stories/STORY-011-verify-translation-docs.md +182 -0
- package/docs/archive/ARCHITECTURE-discourser-design-system.md +448 -0
- package/docs/claude-feed-back/ARCHITECTURE_DIAGRAM.md +243 -0
- package/docs/claude-feed-back/STYLING_VERIFICATION.md +89 -0
- package/docs/claude-feed-back/TEST_RESULTS.md +182 -0
- package/docs/context-share/ELEVATION_FIX_PLAN.md +903 -0
- package/docs/context-share/STORY-001-VALIDATION-PASSED.md +192 -0
- package/docs/context-share/STORY-002-IMPLEMENTATION-COMPLETE.md +161 -0
- package/docs/context-share/STORYBOOK_MCP_STRATEGY.md +867 -0
- package/docs/context-share/TESTING_GAPS_FILLED.md +353 -0
- package/docs/context-share/TOKEN_TESTING_SUMMARY.md +388 -0
- package/docs/context-share/code-connect-prompt.md +90 -0
- package/docs/context-share/dds-autonomous-pipeline.md +765 -0
- package/docs/context-share/fix-checkbox-radio-tokens.md +145 -0
- package/docs/context-share/icon-component-prompt.md +154 -0
- package/docs/context-share/icons/Audience.svg +3 -0
- package/docs/context-share/icons/AudioSpeaker.svg +3 -0
- package/docs/context-share/icons/BookmarkPlus.svg +3 -0
- package/docs/context-share/icons/ClipBoard.svg +8 -0
- package/docs/context-share/icons/DiscourserLogo.svg +4 -0
- package/docs/context-share/icons/ExitStudio.svg +4 -0
- package/docs/context-share/icons/Microphone.svg +5 -0
- package/docs/context-share/icons/NotebookPen.svg +3 -0
- package/docs/context-share/icons/PausePlay.svg +5 -0
- package/docs/context-share/icons/Play.svg +4 -0
- package/docs/context-share/icons/Record.svg +6 -0
- package/docs/context-share/icons/RepeatQuestion.svg +3 -0
- package/docs/context-share/icons/ScrollText.svg +3 -0
- package/docs/context-share/icons/Sparkles.svg +3 -0
- package/docs/context-share/icons/Speech.svg +3 -0
- package/docs/context-share/icons/StopPlay.svg +4 -0
- package/docs/context-share/icons/Timer.svg +3 -0
- package/docs/context-share/icons/UserProfile.svg +3 -0
- package/docs/context-share/m3-token-pipeline-audit.md +125 -0
- package/docs/context-share/storybook-mcp-kai-agent-revised-summary.md +211 -0
- package/docs/discourser-design-system-prd.md +3698 -0
- package/docs/figma-captures/01-typography.png +0 -0
- package/docs/figma-captures/02-button-iconbutton.png +0 -0
- package/docs/figma-captures/03-form-inputs.png +0 -0
- package/docs/figma-captures/04-form-controls.png +0 -0
- package/docs/figma-captures/05-data-display.png +0 -0
- package/docs/figma-captures/06-feedback.png +0 -0
- package/docs/figma-captures/07-overlays.png +0 -0
- package/docs/figma-captures/08-navigation-layout.png +0 -0
- package/docs/figma-captures/09-custom-components.png +0 -0
- package/docs/figma-captures/10-scenario-queue.png +0 -0
- package/docs/figma-captures/11-icon-library.png +0 -0
- package/docs/figma-make-docs/01-understanding-templates.md +235 -0
- package/docs/figma-make-docs/02-prerequisites.md +266 -0
- package/docs/figma-make-docs/03-creating-template.md +306 -0
- package/docs/figma-make-docs/04-adding-guidelines.md +448 -0
- package/docs/figma-make-docs/05-example-starter-code.md +590 -0
- package/docs/figma-make-docs/06-publishing-template.md +417 -0
- package/docs/figma-make-docs/07-maintenance.md +536 -0
- package/docs/figma-make-docs/08-faq.md +490 -0
- package/docs/figma-make-docs/README.md +95 -0
- package/docs/material-theme.json +418 -0
- package/docs/plans/2026-03-12-figma-token-export-rewrite.md +504 -0
- package/docs/plans/2026-03-12-step7-panda-token-resolution-design.md +119 -0
- package/docs/plans/2026-03-12-step7-panda-token-resolution.md +993 -0
- package/docs/token-name-mapping.json +850 -0
- package/docs/token-name-mapping.md +251 -0
- package/package.json +3 -2
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
# Figma Token Export Rewrite Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Rewrite `scripts/export-figma-tokens.ts` to generate three DTCG-compatible output files from `material3Language` with zero hardcoded values.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Single script with pure helper functions. Each output file is built by a dedicated builder function that reads exclusively from `material3Language`. The semantic key → Figma path mapping is handled by a single `semanticKeyToFigmaPath()` pure function. All three files are written to `dist/`, and `dist/figma-variables.json` is also copied to `tokens/tokens.json`.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript (ESM), tsx runner, Node.js `fs`/`path`, no new dependencies.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
### Task 1: Add `figma:export` script to package.json
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
|
|
17
|
+
- Modify: `package.json`
|
|
18
|
+
|
|
19
|
+
**Step 1: Add the script entry**
|
|
20
|
+
|
|
21
|
+
In `package.json`, find the `"scripts"` block. Add after the last existing script entry:
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
"figma:export": "tsx scripts/export-figma-tokens.ts"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Step 2: Verify**
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cd /Users/willstreeter/WebstormProjects/vibe-coding/shifu-project/Discourser-Design-System
|
|
31
|
+
cat package.json | grep figma:export
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Expected output: `"figma:export": "tsx scripts/export-figma-tokens.ts"`
|
|
35
|
+
|
|
36
|
+
**Step 3: Commit**
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git add package.json
|
|
40
|
+
git commit -m "chore: add figma:export script to package.json"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### Task 2: Rewrite scripts/export-figma-tokens.ts
|
|
46
|
+
|
|
47
|
+
**Files:**
|
|
48
|
+
|
|
49
|
+
- Modify: `scripts/export-figma-tokens.ts` (complete rewrite)
|
|
50
|
+
|
|
51
|
+
**Step 1: Replace the entire file with this implementation**
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
/**
|
|
55
|
+
* Export Design System Tokens for Figma
|
|
56
|
+
*
|
|
57
|
+
* Generates three DTCG-compatible output files from material3Language.
|
|
58
|
+
* Zero hardcoded values — all data sourced programmatically.
|
|
59
|
+
*
|
|
60
|
+
* Usage: pnpm figma:export
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
import { material3Language } from '../src/languages/material3.language';
|
|
64
|
+
import * as fs from 'fs';
|
|
65
|
+
import * as path from 'path';
|
|
66
|
+
import { fileURLToPath } from 'url';
|
|
67
|
+
|
|
68
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
69
|
+
const __dirname = path.dirname(__filename);
|
|
70
|
+
|
|
71
|
+
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
|
72
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
73
|
+
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Helper: strip px suffix and convert to number
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
|
|
78
|
+
function px(value: string): number {
|
|
79
|
+
return parseFloat(value.replace('px', ''));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function ms(value: string): number {
|
|
83
|
+
return parseFloat(value.replace('ms', ''));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Helper: camelCase semantic key → Figma path (/ separator)
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
function semanticKeyToFigmaPath(key: string): string {
|
|
91
|
+
// Surface container variants
|
|
92
|
+
if (key === 'surfaceContainer') return 'surface/container';
|
|
93
|
+
if (key === 'surfaceContainerLow') return 'surface/container/low';
|
|
94
|
+
if (key === 'surfaceContainerLowest') return 'surface/container/lowest';
|
|
95
|
+
if (key === 'surfaceContainerHigh') return 'surface/container/high';
|
|
96
|
+
if (key === 'surfaceContainerHighest') return 'surface/container/highest';
|
|
97
|
+
|
|
98
|
+
// surfaceVariant / onSurfaceVariant
|
|
99
|
+
if (key === 'surfaceVariant') return 'surface/variant';
|
|
100
|
+
if (key === 'onSurfaceVariant') return 'onSurface/variant';
|
|
101
|
+
|
|
102
|
+
// outlineVariant
|
|
103
|
+
if (key === 'outlineVariant') return 'outline/variant';
|
|
104
|
+
|
|
105
|
+
// inverse*
|
|
106
|
+
if (key === 'inverseSurface') return 'inverse/surface';
|
|
107
|
+
if (key === 'inverseOnSurface') return 'inverse/onSurface';
|
|
108
|
+
if (key === 'inversePrimary') return 'inverse/primary';
|
|
109
|
+
|
|
110
|
+
// on*Container (e.g. onPrimaryContainer → onPrimary/container)
|
|
111
|
+
const onContainerMatch = key.match(/^(on[A-Z][a-z]+)Container$/);
|
|
112
|
+
if (onContainerMatch) {
|
|
113
|
+
return `${onContainerMatch[1]}/container`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// *Container (e.g. primaryContainer → primary/container)
|
|
117
|
+
const containerMatch = key.match(/^([a-z][a-zA-Z]+)Container$/);
|
|
118
|
+
if (containerMatch) {
|
|
119
|
+
return `${containerMatch[1]}/container`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// All others pass through as-is
|
|
123
|
+
return key;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Builder 1: Primitives collection — tonal palettes
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
type DtcgColorEntry = {
|
|
131
|
+
$type: 'color';
|
|
132
|
+
$value: Record<string, string>;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
type DtcgNumberEntry = {
|
|
136
|
+
$type: 'number';
|
|
137
|
+
$value: Record<string, number>;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
function buildPrimitives(): Record<string, DtcgColorEntry> {
|
|
141
|
+
const result: Record<string, DtcgColorEntry> = {};
|
|
142
|
+
const lang = material3Language;
|
|
143
|
+
|
|
144
|
+
for (const [paletteName, tones] of Object.entries(lang.colors)) {
|
|
145
|
+
for (const [tone, hex] of Object.entries(tones)) {
|
|
146
|
+
const key = `${paletteName}/${tone}`;
|
|
147
|
+
result[key] = {
|
|
148
|
+
$type: 'color',
|
|
149
|
+
$value: { Value: String(hex) },
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// Builder 2: Semantic collection — light + dark modes
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
function buildSemantic(): Record<string, DtcgColorEntry> {
|
|
162
|
+
const result: Record<string, DtcgColorEntry> = {};
|
|
163
|
+
const lang = material3Language;
|
|
164
|
+
|
|
165
|
+
// Iterate light keys (semantic and semanticDark must have identical key sets)
|
|
166
|
+
for (const [key, lightValue] of Object.entries(lang.semantic)) {
|
|
167
|
+
const darkValue = (lang.semanticDark as Record<string, string>)[key];
|
|
168
|
+
const figmaPath = semanticKeyToFigmaPath(key);
|
|
169
|
+
|
|
170
|
+
result[figmaPath] = {
|
|
171
|
+
$type: 'color',
|
|
172
|
+
$value: {
|
|
173
|
+
Light: lightValue,
|
|
174
|
+
Dark: darkValue,
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// Builder 3: Spacing & Shape collection — numeric tokens
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
|
|
186
|
+
function buildSpacingAndShape(): Record<string, DtcgNumberEntry> {
|
|
187
|
+
const result: Record<string, DtcgNumberEntry> = {};
|
|
188
|
+
const lang = material3Language;
|
|
189
|
+
|
|
190
|
+
// Spacing
|
|
191
|
+
for (const [key, value] of Object.entries(lang.spacing)) {
|
|
192
|
+
result[`spacing/${key}`] = {
|
|
193
|
+
$type: 'number',
|
|
194
|
+
$value: { Value: px(value) },
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Radii
|
|
199
|
+
for (const [key, value] of Object.entries(lang.shape.radii)) {
|
|
200
|
+
result[`radii/${key}`] = {
|
|
201
|
+
$type: 'number',
|
|
202
|
+
$value: { Value: px(value) },
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Border widths
|
|
207
|
+
for (const [key, value] of Object.entries(lang.border.widths)) {
|
|
208
|
+
result[`border/${key}`] = {
|
|
209
|
+
$type: 'number',
|
|
210
|
+
$value: { Value: px(value) },
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Durations
|
|
215
|
+
for (const [key, value] of Object.entries(lang.motion.durations)) {
|
|
216
|
+
result[`duration/${key}`] = {
|
|
217
|
+
$type: 'number',
|
|
218
|
+
$value: { Value: ms(value) },
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// Builder 4: Effect styles — elevation
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
|
|
229
|
+
type ElevationEntry = {
|
|
230
|
+
value: string;
|
|
231
|
+
description: string;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const elevationDescriptions: Record<string, string> = {
|
|
235
|
+
level0: 'No elevation — flat surfaces',
|
|
236
|
+
level1: 'Cards at rest, contained buttons (semantic: xs)',
|
|
237
|
+
level2: 'Cards on hover, raised buttons (semantic: sm)',
|
|
238
|
+
level3: 'Dialogs, dropdowns, popovers (semantic: md)',
|
|
239
|
+
level4: 'Navigation drawers, modal sheets (semantic: lg)',
|
|
240
|
+
level5: 'FABs, tooltips, snackbars (semantic: xl)',
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
function buildEffectStyles(): { elevation: Record<string, ElevationEntry> } {
|
|
244
|
+
const elevation: Record<string, ElevationEntry> = {};
|
|
245
|
+
const lang = material3Language;
|
|
246
|
+
|
|
247
|
+
for (const [key, value] of Object.entries(lang.elevation.levels)) {
|
|
248
|
+
elevation[key] = {
|
|
249
|
+
value,
|
|
250
|
+
description: elevationDescriptions[key] ?? key,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return { elevation };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// Builder 5: Text styles — typography
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
type TextStyleEntry = {
|
|
262
|
+
fontFamily: string;
|
|
263
|
+
fontSize: number;
|
|
264
|
+
fontWeight: number;
|
|
265
|
+
lineHeight: number;
|
|
266
|
+
letterSpacing: number;
|
|
267
|
+
figmaTextStyle: string;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
function buildTextStyles(): Record<string, TextStyleEntry> {
|
|
271
|
+
const result: Record<string, TextStyleEntry> = {};
|
|
272
|
+
const lang = material3Language;
|
|
273
|
+
|
|
274
|
+
for (const [key, style] of Object.entries(lang.typography.scale)) {
|
|
275
|
+
const fontKey = style.fontFamily as 'display' | 'body' | 'mono';
|
|
276
|
+
const fontFamilyString = lang.typography.fonts[fontKey];
|
|
277
|
+
// Extract first font name before comma, strip quotes
|
|
278
|
+
const fontFamily = fontFamilyString
|
|
279
|
+
.split(',')[0]
|
|
280
|
+
.replace(/['"]/g, '')
|
|
281
|
+
.trim();
|
|
282
|
+
|
|
283
|
+
result[key] = {
|
|
284
|
+
fontFamily,
|
|
285
|
+
fontSize: px(style.fontSize),
|
|
286
|
+
fontWeight: Number(style.fontWeight),
|
|
287
|
+
lineHeight: px(style.lineHeight),
|
|
288
|
+
letterSpacing: px(style.letterSpacing),
|
|
289
|
+
figmaTextStyle: key,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// ---------------------------------------------------------------------------
|
|
297
|
+
// Main: assemble, write files, print summary
|
|
298
|
+
// ---------------------------------------------------------------------------
|
|
299
|
+
|
|
300
|
+
function ensureDir(filePath: string): void {
|
|
301
|
+
const dir = path.dirname(filePath);
|
|
302
|
+
if (!fs.existsSync(dir)) {
|
|
303
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function writeJson(filePath: string, data: unknown): void {
|
|
308
|
+
ensureDir(filePath);
|
|
309
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const distDir = path.join(__dirname, '..', 'dist');
|
|
313
|
+
|
|
314
|
+
// --- figma-variables.json ---
|
|
315
|
+
const primitives = buildPrimitives();
|
|
316
|
+
const semantic = buildSemantic();
|
|
317
|
+
const spacingAndShape = buildSpacingAndShape();
|
|
318
|
+
|
|
319
|
+
const figmaVariables = {
|
|
320
|
+
$metadata: {
|
|
321
|
+
version: packageJson.version ?? '0.0.0',
|
|
322
|
+
generated: new Date().toISOString(),
|
|
323
|
+
},
|
|
324
|
+
Primitives: primitives,
|
|
325
|
+
Semantic: semantic,
|
|
326
|
+
'Spacing & Shape': spacingAndShape,
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const figmaVariablesPath = path.join(distDir, 'figma-variables.json');
|
|
330
|
+
writeJson(figmaVariablesPath, figmaVariables);
|
|
331
|
+
|
|
332
|
+
// --- figma-effect-styles.json ---
|
|
333
|
+
const effectStyles = buildEffectStyles();
|
|
334
|
+
const effectStylesPath = path.join(distDir, 'figma-effect-styles.json');
|
|
335
|
+
writeJson(effectStylesPath, effectStyles);
|
|
336
|
+
|
|
337
|
+
// --- figma-text-styles.json ---
|
|
338
|
+
const textStyles = buildTextStyles();
|
|
339
|
+
const textStylesPath = path.join(distDir, 'figma-text-styles.json');
|
|
340
|
+
writeJson(textStylesPath, textStyles);
|
|
341
|
+
|
|
342
|
+
// --- tokens/tokens.json (copy of figma-variables.json) ---
|
|
343
|
+
const tokensPath = path.join(__dirname, '..', 'tokens', 'tokens.json');
|
|
344
|
+
writeJson(tokensPath, figmaVariables);
|
|
345
|
+
|
|
346
|
+
// --- Summary ---
|
|
347
|
+
console.log(
|
|
348
|
+
`✅ dist/figma-variables.json — ${Object.keys(primitives).length} Primitives, ${Object.keys(semantic).length} Semantic, ${Object.keys(spacingAndShape).length} Spacing & Shape tokens`,
|
|
349
|
+
);
|
|
350
|
+
console.log(
|
|
351
|
+
`✅ dist/figma-effect-styles.json — ${Object.keys(effectStyles.elevation).length} elevation levels`,
|
|
352
|
+
);
|
|
353
|
+
console.log(
|
|
354
|
+
`✅ dist/figma-text-styles.json — ${Object.keys(textStyles).length} text styles`,
|
|
355
|
+
);
|
|
356
|
+
console.log(`✅ tokens/tokens.json updated`);
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**Step 2: Run the export script to verify it executes clean**
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
cd /Users/willstreeter/WebstormProjects/vibe-coding/shifu-project/Discourser-Design-System
|
|
363
|
+
pnpm figma:export
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Expected output (exact numbers):
|
|
367
|
+
|
|
368
|
+
```
|
|
369
|
+
✅ dist/figma-variables.json — 78 Primitives, 31 Semantic, 24 Spacing & Shape tokens
|
|
370
|
+
✅ dist/figma-effect-styles.json — 6 elevation levels
|
|
371
|
+
✅ dist/figma-text-styles.json — 15 text styles
|
|
372
|
+
✅ tokens/tokens.json updated
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**Step 3: Spot-check the output files**
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
# Verify Primitives structure
|
|
379
|
+
node -e "const f=JSON.parse(require('fs').readFileSync('dist/figma-variables.json','utf8')); console.log(Object.keys(f.Primitives).slice(0,3)); console.log(f.Primitives['primary/0'])"
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Expected:
|
|
383
|
+
|
|
384
|
+
```
|
|
385
|
+
[ 'primary/0', 'primary/10', 'primary/20' ]
|
|
386
|
+
{ '$type': 'color', '$value': { Value: '#000000' } }
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
# Verify Semantic has Light/Dark modes (no dark-* prefix keys)
|
|
391
|
+
node -e "const f=JSON.parse(require('fs').readFileSync('dist/figma-variables.json','utf8')); const s=f.Semantic; console.log(s['primary']); console.log(s['primary/container']); console.log(s['surface/container/lowest'])"
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Expected:
|
|
395
|
+
|
|
396
|
+
```
|
|
397
|
+
{ '$type': 'color', '$value': { Light: '#4C662B', Dark: '#B1D18A' } }
|
|
398
|
+
{ '$type': 'color', '$value': { Light: '#CDEDA3', Dark: '#354E16' } }
|
|
399
|
+
{ '$type': 'color', '$value': { Light: '#FFFFFF', Dark: '#0C0F09' } }
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
```bash
|
|
403
|
+
# Verify numeric tokens are numbers not strings
|
|
404
|
+
node -e "const f=JSON.parse(require('fs').readFileSync('dist/figma-variables.json','utf8')); const s=f['Spacing & Shape']; console.log(s['spacing/md']); console.log(s['radii/extraSmall']); console.log(s['border/thin']); console.log(s['duration/normal'])"
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
Expected:
|
|
408
|
+
|
|
409
|
+
```
|
|
410
|
+
{ '$type': 'number', '$value': { Value: 16 } }
|
|
411
|
+
{ '$type': 'number', '$value': { Value: 4 } }
|
|
412
|
+
{ '$type': 'number', '$value': { Value: 1 } }
|
|
413
|
+
{ '$type': 'number', '$value': { Value: 200 } }
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
# Verify text styles have numeric values and correct font family
|
|
418
|
+
node -e "const f=JSON.parse(require('fs').readFileSync('dist/figma-text-styles.json','utf8')); console.log(f['displayLarge']); console.log(f['bodyMedium'])"
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Expected:
|
|
422
|
+
|
|
423
|
+
```
|
|
424
|
+
{
|
|
425
|
+
fontFamily: 'Fraunces',
|
|
426
|
+
fontSize: 57,
|
|
427
|
+
fontWeight: 400,
|
|
428
|
+
lineHeight: 64,
|
|
429
|
+
letterSpacing: -0.25,
|
|
430
|
+
figmaTextStyle: 'displayLarge'
|
|
431
|
+
}
|
|
432
|
+
{
|
|
433
|
+
fontFamily: 'Poppins',
|
|
434
|
+
fontSize: 14,
|
|
435
|
+
fontWeight: 400,
|
|
436
|
+
lineHeight: 20,
|
|
437
|
+
letterSpacing: 0.25,
|
|
438
|
+
figmaTextStyle: 'bodyMedium'
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
# Verify effect styles
|
|
444
|
+
node -e "const f=JSON.parse(require('fs').readFileSync('dist/figma-effect-styles.json','utf8')); console.log(Object.keys(f.elevation)); console.log(f.elevation.level1)"
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Expected:
|
|
448
|
+
|
|
449
|
+
```
|
|
450
|
+
[ 'level0', 'level1', 'level2', 'level3', 'level4', 'level5' ]
|
|
451
|
+
{
|
|
452
|
+
value: '0px 1px 2px rgba(0, 0, 0, 0.3), 0px 1px 3px 1px rgba(0, 0, 0, 0.15)',
|
|
453
|
+
description: 'Cards at rest, contained buttons (semantic: xs)'
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
# Verify tokens/tokens.json was updated
|
|
459
|
+
node -e "const f=JSON.parse(require('fs').readFileSync('tokens/tokens.json','utf8')); console.log(Object.keys(f))"
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Expected:
|
|
463
|
+
|
|
464
|
+
```
|
|
465
|
+
[ '$metadata', 'Primitives', 'Semantic', 'Spacing & Shape' ]
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
**Step 4: Run typecheck**
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
pnpm tsc --noEmit
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Expected: No errors.
|
|
475
|
+
|
|
476
|
+
**Step 5: Run tests to verify no regressions**
|
|
477
|
+
|
|
478
|
+
```bash
|
|
479
|
+
pnpm test
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
Expected: All tests pass (306/306).
|
|
483
|
+
|
|
484
|
+
**Step 6: Commit**
|
|
485
|
+
|
|
486
|
+
```bash
|
|
487
|
+
git add scripts/export-figma-tokens.ts tokens/tokens.json
|
|
488
|
+
git commit -m "feat: rewrite export-figma-tokens to DTCG format with Light/Dark modes"
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## Success Criteria
|
|
494
|
+
|
|
495
|
+
- [ ] `pnpm figma:export` runs clean with no errors
|
|
496
|
+
- [ ] `dist/figma-variables.json` exists with `Primitives`, `Semantic`, `Spacing & Shape` collections
|
|
497
|
+
- [ ] Semantic collection has `Light`/`Dark` modes (no `dark-*` prefix keys anywhere)
|
|
498
|
+
- [ ] All numeric tokens (spacing, radii, border, duration) are numbers not strings
|
|
499
|
+
- [ ] `dist/figma-effect-styles.json` exists with 6 elevation levels
|
|
500
|
+
- [ ] `dist/figma-text-styles.json` exists with 15 text styles, numeric values
|
|
501
|
+
- [ ] `tokens/tokens.json` updated to match figma-variables.json content
|
|
502
|
+
- [ ] Zero hardcoded hex values in the script
|
|
503
|
+
- [ ] `pnpm test` passes: 306/306 (no regressions)
|
|
504
|
+
- [ ] `pnpm tsc --noEmit` clean
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Step 7 Design: Panda Token Resolution for Foundation Stories
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-03-12
|
|
4
|
+
**Branch context:** fix/remove-m3-labels (or new feature branch)
|
|
5
|
+
|
|
6
|
+
## Goal
|
|
7
|
+
|
|
8
|
+
Replace raw-value rendering with Panda CSS token resolution in all four foundation MDX stories. A broken token must produce a blank/invisible output rather than silently working.
|
|
9
|
+
|
|
10
|
+
## Approach: A (Update Components)
|
|
11
|
+
|
|
12
|
+
Move all Panda `css()` lookups inside the display component files in `src/stories/foundations/components/`. The `.mdx` files pass token name strings instead of raw values. This is cleaner than inlining JSX in `.mdx`.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Per-Story Design
|
|
17
|
+
|
|
18
|
+
### Colors.mdx
|
|
19
|
+
|
|
20
|
+
**TonalSwatch section** → Remove entirely. Replace with a note pointing to the Color Scale story. Rationale: tonal palette tones (0, 10, 20, …, 100) have no Panda token equivalents — they are internal pipeline values. Adding 65 documentation-only tokens would be noise. ColorScale.stories.tsx already covers the usable Radix-scale vocabulary.
|
|
21
|
+
|
|
22
|
+
**SemanticSwatch** → Update `ColorSwatch.tsx`:
|
|
23
|
+
|
|
24
|
+
- Remove `lightValue` / `darkValue` props
|
|
25
|
+
- Add `name` prop (token key string, e.g. `"primary"`, `"onPrimary"`, `"primary.container"`)
|
|
26
|
+
- Pre-declare `semanticBg: Record<string, string>` with ~31 explicit `css({ bg: '...' })` entries
|
|
27
|
+
- Show light/dark side-by-side: light box = plain div with lookup class; dark box = same class inside a `data-theme="dark"` wrapper
|
|
28
|
+
- Labels show token path (e.g. `primary`) and `primary [dark]` — no hex strings
|
|
29
|
+
|
|
30
|
+
`Colors.mdx` changes:
|
|
31
|
+
|
|
32
|
+
- Remove tonal palette grid divs + `TonalSwatch` import usage (keep import if ColorSwatch still exports it, or remove)
|
|
33
|
+
- Replace each `<SemanticSwatch lightValue=... darkValue=...>` with `<SemanticSwatch name="primary" ...>`
|
|
34
|
+
- Keep all prose, headings, and usage guidelines unchanged
|
|
35
|
+
|
|
36
|
+
### Elevation.mdx / ElevationCard.tsx
|
|
37
|
+
|
|
38
|
+
Shadow tokens `level0`–`level5` exist in Panda as `boxShadow` values.
|
|
39
|
+
|
|
40
|
+
`ElevationCard.tsx` changes:
|
|
41
|
+
|
|
42
|
+
- Pre-declare `elevationClasses: Record<string, string>` with 6 entries: `level0: css({ boxShadow: 'level0' })` … `level5: css({ boxShadow: 'level5' })`
|
|
43
|
+
- Remove `shadow` prop; use `level` prop as lookup key
|
|
44
|
+
- The displayed shadow string becomes static text (hardcoded in the mdx reference table)
|
|
45
|
+
|
|
46
|
+
`ElevationGrid.tsx`:
|
|
47
|
+
|
|
48
|
+
- Remove `elevations: Record<string, string>` prop
|
|
49
|
+
- Render a fixed array `['level0', 'level1', 'level2', 'level3', 'level4', 'level5']`
|
|
50
|
+
|
|
51
|
+
`Elevation.mdx` changes:
|
|
52
|
+
|
|
53
|
+
- `<ElevationGrid />` — no prop needed
|
|
54
|
+
- "Visual Comparison" inline divs use pre-declared elevation classes (pre-declared in the mdx or extracted to a helper)
|
|
55
|
+
|
|
56
|
+
### Spacing.mdx / SpacingBox.tsx
|
|
57
|
+
|
|
58
|
+
Spacing tokens `none`, `xxs`, `xs`, `sm`, `md`, `lg`, `xl`, `xxl`, `xxxl` exist in Panda.
|
|
59
|
+
|
|
60
|
+
`SpacingBox.tsx` changes:
|
|
61
|
+
|
|
62
|
+
- Pre-declare `spacingWidthClasses: Record<string, string>` with 9 entries: `none: css({ width: 'none' })` … `xxxl: css({ width: 'xxxl' })`
|
|
63
|
+
- Width bar uses `className={spacingWidthClasses[name]}` instead of `style={{ width: value }}`
|
|
64
|
+
- Keep `value` prop for the px label display
|
|
65
|
+
|
|
66
|
+
`Spacing.mdx` changes:
|
|
67
|
+
|
|
68
|
+
- `<SpacingBox>` calls keep `name` and `value` props (value is for px label only)
|
|
69
|
+
- "Visual Examples" section uses static px strings (e.g. `gap: '8px'`) instead of `material3Language.spacing.sm`
|
|
70
|
+
|
|
71
|
+
### Typography.mdx / TypeSpecimen.tsx
|
|
72
|
+
|
|
73
|
+
Text styles exist: `displayLarge`, `displayMedium`, `displaySmall`, `headlineLarge`, `headlineMedium`, `headlineSmall`, `titleLarge`, `titleMedium`, `titleSmall`, `bodyLarge`, `bodyMedium`, `bodySmall`, `labelLarge`, `labelMedium`, `labelSmall` (15 total).
|
|
74
|
+
|
|
75
|
+
`TypeSpecimen.tsx` changes:
|
|
76
|
+
|
|
77
|
+
- Add `styleName` prop (e.g. `'displayLarge'`)
|
|
78
|
+
- Pre-declare `textStyleClasses: Record<string, string>` with 15 explicit `css({ textStyle: '...' })` entries
|
|
79
|
+
- Specimen text uses `className={textStyleClasses[styleName]}` instead of inline `specimenStyle`
|
|
80
|
+
- Keep all raw-value props (`fontSize`, `lineHeight`, etc.) — used only for the spec table below each specimen
|
|
81
|
+
|
|
82
|
+
`Typography.mdx` changes:
|
|
83
|
+
|
|
84
|
+
- Each `<TypeSpecimen>` gets `styleName="displayLarge"` (etc.) added
|
|
85
|
+
- Font Families section: replace `fontFamily: material3Language.typography.fonts.display` with static string `'"Fraunces", Georgia, "Times New Roman", serif'` etc.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Implementation Rules
|
|
90
|
+
|
|
91
|
+
- All `css()` calls use string literals — no dynamic template strings or variables
|
|
92
|
+
- Use a pre-declared lookup object (same pattern as `ColorScale.stories.tsx` `scaleBg` / `semanticClasses`)
|
|
93
|
+
- `material3Language` must not be used to set any rendered color, shadow, width, or textStyle
|
|
94
|
+
- `material3Language` may still be referenced for static label/description text only
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Test Requirement
|
|
99
|
+
|
|
100
|
+
After first story is refactored (Colors.mdx / SemanticSwatch):
|
|
101
|
+
|
|
102
|
+
1. In `semantic-tokens.ts`, temporarily rename `primary` → `primary_BROKEN`
|
|
103
|
+
2. Storybook: confirm primary color swatches go blank/invisible
|
|
104
|
+
3. Revert rename
|
|
105
|
+
4. Confirm swatches return to normal
|
|
106
|
+
Document in commit message.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Success Criteria
|
|
111
|
+
|
|
112
|
+
- [x] All four .mdx stories render via Panda CSS token variables, not raw values
|
|
113
|
+
- [x] No css() calls use dynamic string interpolation
|
|
114
|
+
- [x] material3Language is NOT used to set any rendered color, shadow, size, or textStyle
|
|
115
|
+
- [x] Test-and-revert confirms visual failure on token breakage
|
|
116
|
+
- [x] pnpm test passes: 306/306
|
|
117
|
+
- [x] pnpm tsc --noEmit clean
|
|
118
|
+
- [x] Storybook renders all stories without console errors
|
|
119
|
+
- [x] Sequence status doc updated: Step 7 complete
|