@discourser/design-system 0.22.1 → 0.22.3

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 (106) hide show
  1. package/dist/{chunk-VJN7TIGL.js → chunk-GLPWI7OF.js} +3 -3
  2. package/dist/chunk-GLPWI7OF.js.map +1 -0
  3. package/dist/{chunk-IGCGVSG4.cjs → chunk-NN4YW27E.cjs} +3 -3
  4. package/dist/chunk-NN4YW27E.cjs.map +1 -0
  5. package/dist/figma-codex.json +2 -2
  6. package/dist/index.cjs +4 -4
  7. package/dist/index.js +1 -1
  8. package/dist/languages/transform.d.ts +1 -1
  9. package/dist/preset/index.cjs +2 -2
  10. package/dist/preset/index.js +1 -1
  11. package/dist/stories/foundations/components/ColorSwatch.d.ts +1 -15
  12. package/dist/stories/foundations/components/ColorSwatch.d.ts.map +1 -1
  13. package/dist/stories/foundations/components/ElevationCard.d.ts +3 -6
  14. package/dist/stories/foundations/components/ElevationCard.d.ts.map +1 -1
  15. package/dist/stories/foundations/components/SpacingBox.d.ts.map +1 -1
  16. package/dist/stories/foundations/components/TypeSpecimen.d.ts +2 -1
  17. package/dist/stories/foundations/components/TypeSpecimen.d.ts.map +1 -1
  18. package/docs/CSS_USAGE.md +235 -0
  19. package/docs/FIGMA_MAKE_SETUP.md +339 -0
  20. package/docs/GUIDELINES_REVIEW.md +728 -0
  21. package/docs/MAINTAINER_CHECKLIST.md +265 -0
  22. package/docs/TESTING_QUICK_REFERENCE.md +159 -0
  23. package/docs/TESTING_TOKENS.md +340 -0
  24. package/docs/active-stories/README.md +29 -0
  25. package/docs/active-stories/STORY-006a-figma-translation-foundations.md +324 -0
  26. package/docs/active-stories/STORY-006b-figma-translation-components.md +201 -0
  27. package/docs/active-stories/STORY-006c-figma-translation-layout-extension.md +258 -0
  28. package/docs/active-stories/STORY-008-kai-sidecar-fragments.md +137 -0
  29. package/docs/active-stories/STORY-011-verify-translation-docs.md +182 -0
  30. package/docs/archive/ARCHITECTURE-discourser-design-system.md +448 -0
  31. package/docs/claude-feed-back/ARCHITECTURE_DIAGRAM.md +243 -0
  32. package/docs/claude-feed-back/STYLING_VERIFICATION.md +89 -0
  33. package/docs/claude-feed-back/TEST_RESULTS.md +182 -0
  34. package/docs/context-share/ELEVATION_FIX_PLAN.md +903 -0
  35. package/docs/context-share/STORY-001-VALIDATION-PASSED.md +192 -0
  36. package/docs/context-share/STORY-002-IMPLEMENTATION-COMPLETE.md +161 -0
  37. package/docs/context-share/STORYBOOK_MCP_STRATEGY.md +867 -0
  38. package/docs/context-share/TESTING_GAPS_FILLED.md +353 -0
  39. package/docs/context-share/TOKEN_TESTING_SUMMARY.md +388 -0
  40. package/docs/context-share/code-connect-prompt.md +90 -0
  41. package/docs/context-share/dds-autonomous-pipeline.md +765 -0
  42. package/docs/context-share/fix-checkbox-radio-tokens.md +145 -0
  43. package/docs/context-share/icon-component-prompt.md +154 -0
  44. package/docs/context-share/icons/Audience.svg +3 -0
  45. package/docs/context-share/icons/AudioSpeaker.svg +3 -0
  46. package/docs/context-share/icons/BookmarkPlus.svg +3 -0
  47. package/docs/context-share/icons/ClipBoard.svg +8 -0
  48. package/docs/context-share/icons/DiscourserLogo.svg +4 -0
  49. package/docs/context-share/icons/ExitStudio.svg +4 -0
  50. package/docs/context-share/icons/Microphone.svg +5 -0
  51. package/docs/context-share/icons/NotebookPen.svg +3 -0
  52. package/docs/context-share/icons/PausePlay.svg +5 -0
  53. package/docs/context-share/icons/Play.svg +4 -0
  54. package/docs/context-share/icons/Record.svg +6 -0
  55. package/docs/context-share/icons/RepeatQuestion.svg +3 -0
  56. package/docs/context-share/icons/ScrollText.svg +3 -0
  57. package/docs/context-share/icons/Sparkles.svg +3 -0
  58. package/docs/context-share/icons/Speech.svg +3 -0
  59. package/docs/context-share/icons/StopPlay.svg +4 -0
  60. package/docs/context-share/icons/Timer.svg +3 -0
  61. package/docs/context-share/icons/UserProfile.svg +3 -0
  62. package/docs/context-share/m3-token-pipeline-audit.md +125 -0
  63. package/docs/context-share/storybook-mcp-kai-agent-revised-summary.md +211 -0
  64. package/docs/discourser-design-system-prd.md +3698 -0
  65. package/docs/figma-captures/01-typography.png +0 -0
  66. package/docs/figma-captures/02-button-iconbutton.png +0 -0
  67. package/docs/figma-captures/03-form-inputs.png +0 -0
  68. package/docs/figma-captures/04-form-controls.png +0 -0
  69. package/docs/figma-captures/05-data-display.png +0 -0
  70. package/docs/figma-captures/06-feedback.png +0 -0
  71. package/docs/figma-captures/07-overlays.png +0 -0
  72. package/docs/figma-captures/08-navigation-layout.png +0 -0
  73. package/docs/figma-captures/09-custom-components.png +0 -0
  74. package/docs/figma-captures/10-scenario-queue.png +0 -0
  75. package/docs/figma-captures/11-icon-library.png +0 -0
  76. package/docs/figma-make-docs/01-understanding-templates.md +235 -0
  77. package/docs/figma-make-docs/02-prerequisites.md +266 -0
  78. package/docs/figma-make-docs/03-creating-template.md +306 -0
  79. package/docs/figma-make-docs/04-adding-guidelines.md +448 -0
  80. package/docs/figma-make-docs/05-example-starter-code.md +590 -0
  81. package/docs/figma-make-docs/06-publishing-template.md +417 -0
  82. package/docs/figma-make-docs/07-maintenance.md +536 -0
  83. package/docs/figma-make-docs/08-faq.md +490 -0
  84. package/docs/figma-make-docs/README.md +95 -0
  85. package/docs/material-theme.json +418 -0
  86. package/docs/plans/2026-03-12-figma-token-export-rewrite.md +504 -0
  87. package/docs/plans/2026-03-12-step7-panda-token-resolution-design.md +119 -0
  88. package/docs/plans/2026-03-12-step7-panda-token-resolution.md +993 -0
  89. package/docs/token-name-mapping.json +850 -0
  90. package/docs/token-name-mapping.md +251 -0
  91. package/package.json +6 -4
  92. package/src/languages/transform.ts +1 -1
  93. package/src/stories/foundations/Borders.stories.tsx +138 -0
  94. package/src/stories/foundations/ColorScale.stories.tsx +737 -0
  95. package/src/stories/foundations/Colors.mdx +2 -131
  96. package/src/stories/foundations/Elevation.mdx +26 -45
  97. package/src/stories/foundations/Motion.stories.tsx +306 -0
  98. package/src/stories/foundations/Shape.stories.tsx +159 -0
  99. package/src/stories/foundations/Spacing.mdx +24 -25
  100. package/src/stories/foundations/Typography.mdx +93 -79
  101. package/src/stories/foundations/components/ColorSwatch.tsx +72 -109
  102. package/src/stories/foundations/components/ElevationCard.tsx +19 -22
  103. package/src/stories/foundations/components/SpacingBox.tsx +15 -2
  104. package/src/stories/foundations/components/TypeSpecimen.tsx +20 -21
  105. package/dist/chunk-IGCGVSG4.cjs.map +0 -1
  106. package/dist/chunk-VJN7TIGL.js.map +0 -1
@@ -0,0 +1,3698 @@
1
+ # Design System Repository Setup PRD
2
+ ## Project Overview
3
+
4
+ Create a design system repository called `@discourser/design-system` using **Panda CSS** and **Ark UI**. This system uses an aesthetic-agnostic architecture where design languages (like Material Design 3) can be swapped by changing a single import.
5
+
6
+ ## Technical Stack
7
+
8
+ - **Panda CSS** - Zero-runtime CSS-in-JS with token-first architecture
9
+ - **Ark UI** - Headless, accessible components built on Zag.js state machines
10
+ - **TypeScript** - Strict mode, full type safety
11
+ - **tsup** - Build tool for ESM/CJS output
12
+ - **Storybook** - Component documentation and testing
13
+ - **Vitest** - Unit testing
14
+ - **@material/material-color-utilities** - M3 color palette generation
15
+ - **pnpm** - Package manager
16
+
17
+ ## Architecture: Three-Layer System
18
+
19
+ ```
20
+ Layer 1: Infrastructure (Unchanging)
21
+ ├── Token pipeline
22
+ ├── Build system (tsup, Storybook)
23
+ ├── Component logic (Ark UI)
24
+ └── Type contracts
25
+
26
+ Layer 2: Design Language (Swappable)
27
+ ├── Token values (colors, spacing, radii)
28
+ ├── Semantic mappings
29
+ └── Motion patterns
30
+
31
+ Layer 3: Component Recipes (Derived)
32
+ ├── Visual styling via Panda recipes
33
+ └── Variant definitions
34
+ ```
35
+
36
+ ## Repository Structure
37
+
38
+ Create this exact folder structure:
39
+
40
+ ```
41
+ design-system/
42
+ ├── .github/
43
+ │ └── workflows/
44
+ │ ├── ci.yml
45
+ │ └── release.yml
46
+ ├── .storybook/
47
+ │ ├── main.ts
48
+ │ ├── preview.ts
49
+ │ └── manager.ts
50
+ ├── scripts/
51
+ │ ├── generate-palette.ts
52
+ │ ├── sync-tokens.ts
53
+ │ └── build.ts
54
+ ├── src/
55
+ │ ├── contracts/
56
+ │ │ └── design-language.contract.ts
57
+ │ ├── languages/
58
+ │ │ ├── material3.language.ts
59
+ │ │ ├── transform.ts
60
+ │ │ └── index.ts
61
+ │ ├── tokens/
62
+ │ │ ├── typography.ts
63
+ │ │ ├── shadows.ts
64
+ │ │ ├── motion.ts
65
+ │ │ └── index.ts
66
+ │ ├── recipes/
67
+ │ │ ├── button.recipe.ts
68
+ │ │ ├── card.recipe.ts
69
+ │ │ ├── icon-button.recipe.ts
70
+ │ │ ├── input.recipe.ts
71
+ │ │ ├── dialog.recipe.ts
72
+ │ │ └── index.ts
73
+ │ ├── components/
74
+ │ │ ├── Button/
75
+ │ │ │ ├── Button.tsx
76
+ │ │ │ ├── Button.stories.tsx
77
+ │ │ │ └── index.ts
78
+ │ │ ├── Card/
79
+ │ │ │ ├── Card.tsx
80
+ │ │ │ ├── Card.stories.tsx
81
+ │ │ │ └── index.ts
82
+ │ │ ├── Dialog/
83
+ │ │ │ ├── Dialog.tsx
84
+ │ │ │ ├── Dialog.stories.tsx
85
+ │ │ │ └── index.ts
86
+ │ │ └── index.ts
87
+ │ ├── utils/
88
+ │ │ └── cn.ts
89
+ │ └── index.ts
90
+ ├── tokens/
91
+ │ └── figma-export.json (placeholder)
92
+ ├── styled-system/ # Generated by Panda (gitignore)
93
+ ├── dist/ # Build output (gitignore)
94
+ ├── panda.config.ts
95
+ ├── tsconfig.json
96
+ ├── tsup.config.ts
97
+ ├── vitest.config.ts
98
+ ├── package.json
99
+ ├── .gitignore
100
+ ├── .npmrc
101
+ └── README.md
102
+ ```
103
+
104
+ ## File Contents
105
+
106
+ ### package.json
107
+
108
+ ```json
109
+ {
110
+ "name": "@discourser/design-system",
111
+ "version": "0.1.0",
112
+ "description": "Aesthetic-agnostic design system with Panda CSS and Ark UI",
113
+ "type": "module",
114
+ "main": "./dist/index.cjs",
115
+ "module": "./dist/index.js",
116
+ "types": "./dist/index.d.ts",
117
+ "exports": {
118
+ ".": {
119
+ "import": "./dist/index.js",
120
+ "require": "./dist/index.cjs",
121
+ "types": "./dist/index.d.ts"
122
+ },
123
+ "./styled-system": {
124
+ "import": "./styled-system/index.mjs",
125
+ "require": "./styled-system/index.js"
126
+ },
127
+ "./styled-system/css": {
128
+ "import": "./styled-system/css/index.mjs",
129
+ "require": "./styled-system/css/index.js"
130
+ },
131
+ "./styled-system/tokens": {
132
+ "import": "./styled-system/tokens/index.mjs",
133
+ "require": "./styled-system/tokens/index.js"
134
+ },
135
+ "./styled-system/recipes": {
136
+ "import": "./styled-system/recipes/index.mjs",
137
+ "require": "./styled-system/recipes/index.js"
138
+ }
139
+ },
140
+ "files": [
141
+ "dist",
142
+ "styled-system"
143
+ ],
144
+ "scripts": {
145
+ "dev": "storybook dev -p 6006",
146
+ "build": "pnpm build:panda && pnpm build:lib",
147
+ "build:panda": "panda codegen",
148
+ "build:lib": "tsup",
149
+ "build:storybook": "storybook build",
150
+ "prepare": "panda codegen",
151
+ "lint": "eslint src --ext .ts,.tsx",
152
+ "test": "vitest",
153
+ "test:ui": "vitest --ui",
154
+ "tokens:generate": "tsx scripts/generate-palette.ts",
155
+ "tokens:sync": "tsx scripts/sync-tokens.ts",
156
+ "tokens:full": "pnpm tokens:generate && pnpm build:panda",
157
+ "typecheck": "tsc --noEmit"
158
+ },
159
+ "peerDependencies": {
160
+ "react": ">=19.0.0",
161
+ "react-dom": ">=19.0.0"
162
+ },
163
+ "dependencies": {
164
+ "@ark-ui/react": "^4.4.0",
165
+ "clsx": "^2.1.1"
166
+ },
167
+ "devDependencies": {
168
+ "@material/material-color-utilities": "^0.3.0",
169
+ "@pandacss/dev": "^0.52.0",
170
+ "@storybook/addon-a11y": "^8.5.0",
171
+ "@storybook/addon-essentials": "^8.5.0",
172
+ "@storybook/react": "^8.5.0",
173
+ "@storybook/react-vite": "^8.5.0",
174
+ "@types/node": "^22.0.0",
175
+ "@types/react": "^19.0.0",
176
+ "@types/react-dom": "^19.0.0",
177
+ "@vitejs/plugin-react": "^4.3.0",
178
+ "eslint": "^9.0.0",
179
+ "react": "^19.0.0",
180
+ "react-dom": "^19.0.0",
181
+ "storybook": "^8.5.0",
182
+ "tsup": "^8.3.0",
183
+ "tsx": "^4.19.0",
184
+ "typescript": "^5.7.0",
185
+ "vite": "^6.0.0",
186
+ "vitest": "^2.1.0"
187
+ },
188
+ "keywords": [
189
+ "design-system",
190
+ "panda-css",
191
+ "ark-ui",
192
+ "material-design-3",
193
+ "react",
194
+ "components"
195
+ ],
196
+ "license": "MIT",
197
+ "repository": {
198
+ "type": "git",
199
+ "url": "https://github.com/Tasty-Maker-Studio/Discourser-Design-System.git"
200
+ }
201
+ }
202
+ ```
203
+
204
+ ### tsconfig.json
205
+
206
+ ```json
207
+ {
208
+ "compilerOptions": {
209
+ "target": "ES2020",
210
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
211
+ "module": "ESNext",
212
+ "moduleResolution": "bundler",
213
+ "jsx": "react-jsx",
214
+ "strict": true,
215
+ "declaration": true,
216
+ "declarationMap": true,
217
+ "sourceMap": true,
218
+ "outDir": "dist",
219
+ "baseUrl": ".",
220
+ "paths": {
221
+ "@/*": ["./src/*"],
222
+ "styled-system/*": ["./styled-system/*"]
223
+ },
224
+ "skipLibCheck": true,
225
+ "esModuleInterop": true,
226
+ "allowSyntheticDefaultImports": true,
227
+ "forceConsistentCasingInFileNames": true,
228
+ "resolveJsonModule": true,
229
+ "isolatedModules": true,
230
+ "noEmit": true
231
+ },
232
+ "include": ["src/**/*", "scripts/**/*", "panda.config.ts"],
233
+ "exclude": ["node_modules", "dist", "styled-system", "storybook-static"]
234
+ }
235
+ ```
236
+
237
+ ### tsup.config.ts
238
+
239
+ ```typescript
240
+ import { defineConfig } from 'tsup';
241
+
242
+ export default defineConfig({
243
+ entry: ['src/index.ts'],
244
+ format: ['esm', 'cjs'],
245
+ dts: true,
246
+ splitting: true,
247
+ sourcemap: true,
248
+ clean: true,
249
+ external: ['react', 'react-dom'],
250
+ esbuildOptions(options) {
251
+ options.banner = {
252
+ js: '"use client"',
253
+ };
254
+ },
255
+ });
256
+ ```
257
+
258
+ ### panda.config.ts
259
+
260
+ ```typescript
261
+ import { defineConfig } from '@pandacss/dev';
262
+ import { activeLanguage, transformToPandaTheme } from './src/languages';
263
+ import { buttonRecipe, cardRecipe, iconButtonRecipe } from './src/recipes';
264
+
265
+ const theme = transformToPandaTheme(activeLanguage);
266
+
267
+ export default defineConfig({
268
+ preflight: true,
269
+
270
+ include: [
271
+ './src/**/*.{js,jsx,ts,tsx}',
272
+ './stories/**/*.{js,jsx,ts,tsx}'
273
+ ],
274
+
275
+ exclude: [],
276
+
277
+ outdir: 'styled-system',
278
+
279
+ jsxFramework: 'react',
280
+
281
+ layers: {
282
+ reset: 'reset',
283
+ base: 'base',
284
+ tokens: 'tokens',
285
+ recipes: 'recipes',
286
+ utilities: 'utilities'
287
+ },
288
+
289
+ theme: {
290
+ tokens: theme.tokens,
291
+ semanticTokens: theme.semanticTokens,
292
+ textStyles: theme.textStyles,
293
+ extend: {
294
+ recipes: {
295
+ button: buttonRecipe,
296
+ card: cardRecipe,
297
+ iconButton: iconButtonRecipe,
298
+ }
299
+ }
300
+ },
301
+
302
+ conditions: {
303
+ light: '[data-theme=light] &, .light &',
304
+ dark: '[data-theme=dark] &, .dark &'
305
+ },
306
+
307
+ globalCss: {
308
+ html: {
309
+ colorScheme: 'light dark'
310
+ },
311
+ body: {
312
+ fontFamily: 'body',
313
+ bg: 'surface',
314
+ color: 'onSurface',
315
+ textStyle: 'bodyMedium'
316
+ }
317
+ }
318
+ });
319
+ ```
320
+
321
+ ### src/contracts/design-language.contract.ts
322
+
323
+ ```typescript
324
+ /**
325
+ * Design Language Contract
326
+ *
327
+ * Any aesthetic (M3, Carbon, Fluent, custom) must implement this interface.
328
+ * This enables swapping aesthetics by changing one import.
329
+ */
330
+
331
+ export interface DesignLanguageContract {
332
+ name: string;
333
+ version: string;
334
+ colors: ColorPalettes;
335
+ semantic: SemanticColors;
336
+ semanticDark?: SemanticColors; // Optional dark theme overrides
337
+ typography: TypographyConfig;
338
+ spacing: SpacingScale;
339
+ shape: ShapeConfig;
340
+ elevation: ElevationConfig;
341
+ motion: MotionConfig;
342
+ }
343
+
344
+ // Color Types
345
+ export interface ColorPalettes {
346
+ primary: TonalPalette;
347
+ secondary: TonalPalette;
348
+ tertiary: TonalPalette;
349
+ neutral: TonalPalette;
350
+ neutralVariant: TonalPalette;
351
+ error: TonalPalette;
352
+ }
353
+
354
+ export interface TonalPalette {
355
+ 0: string;
356
+ 10: string;
357
+ 20: string;
358
+ 30: string;
359
+ 40: string;
360
+ 50: string;
361
+ 60: string;
362
+ 70: string;
363
+ 80: string;
364
+ 90: string;
365
+ 95: string;
366
+ 99: string;
367
+ 100: string;
368
+ }
369
+
370
+ export interface SemanticColors {
371
+ primary: string;
372
+ onPrimary: string;
373
+ primaryContainer: string;
374
+ onPrimaryContainer: string;
375
+ secondary: string;
376
+ onSecondary: string;
377
+ secondaryContainer: string;
378
+ onSecondaryContainer: string;
379
+ tertiary: string;
380
+ onTertiary: string;
381
+ tertiaryContainer: string;
382
+ onTertiaryContainer: string;
383
+ error: string;
384
+ onError: string;
385
+ errorContainer: string;
386
+ onErrorContainer: string;
387
+ surface: string;
388
+ onSurface: string;
389
+ surfaceVariant: string;
390
+ onSurfaceVariant: string;
391
+ surfaceContainerLowest: string;
392
+ surfaceContainerLow: string;
393
+ surfaceContainer: string;
394
+ surfaceContainerHigh: string;
395
+ surfaceContainerHighest: string;
396
+ outline: string;
397
+ outlineVariant: string;
398
+ inverseSurface: string;
399
+ inverseOnSurface: string;
400
+ inversePrimary: string;
401
+ background: string;
402
+ onBackground: string;
403
+ scrim: string;
404
+ shadow: string;
405
+ }
406
+
407
+ // Typography Types
408
+ export interface TypographyConfig {
409
+ fonts: {
410
+ display: string;
411
+ body: string;
412
+ mono: string;
413
+ };
414
+ scale: TypographyScale;
415
+ }
416
+
417
+ export interface TypographyScale {
418
+ displayLarge: TypeStyle;
419
+ displayMedium: TypeStyle;
420
+ displaySmall: TypeStyle;
421
+ headlineLarge: TypeStyle;
422
+ headlineMedium: TypeStyle;
423
+ headlineSmall: TypeStyle;
424
+ titleLarge: TypeStyle;
425
+ titleMedium: TypeStyle;
426
+ titleSmall: TypeStyle;
427
+ bodyLarge: TypeStyle;
428
+ bodyMedium: TypeStyle;
429
+ bodySmall: TypeStyle;
430
+ labelLarge: TypeStyle;
431
+ labelMedium: TypeStyle;
432
+ labelSmall: TypeStyle;
433
+ }
434
+
435
+ export interface TypeStyle {
436
+ fontSize: string;
437
+ lineHeight: string;
438
+ fontWeight: string;
439
+ letterSpacing: string;
440
+ fontFamily?: 'display' | 'body' | 'mono';
441
+ }
442
+
443
+ // Spacing Types
444
+ export interface SpacingScale {
445
+ none: string;
446
+ xxs: string;
447
+ xs: string;
448
+ sm: string;
449
+ md: string;
450
+ lg: string;
451
+ xl: string;
452
+ xxl: string;
453
+ xxxl: string;
454
+ }
455
+
456
+ // Shape Types
457
+ export interface ShapeConfig {
458
+ radii: RadiiScale;
459
+ style: 'sharp' | 'rounded' | 'soft' | 'organic';
460
+ }
461
+
462
+ export interface RadiiScale {
463
+ none: string;
464
+ extraSmall: string;
465
+ small: string;
466
+ medium: string;
467
+ large: string;
468
+ extraLarge: string;
469
+ full: string;
470
+ }
471
+
472
+ // Elevation Types
473
+ export interface ElevationConfig {
474
+ levels: ElevationScale;
475
+ style: 'shadow' | 'border' | 'blur' | 'flat';
476
+ }
477
+
478
+ export interface ElevationScale {
479
+ level0: string;
480
+ level1: string;
481
+ level2: string;
482
+ level3: string;
483
+ level4: string;
484
+ level5: string;
485
+ }
486
+
487
+ // Motion Types
488
+ export interface MotionConfig {
489
+ durations: DurationScale;
490
+ easings: EasingScale;
491
+ style: 'expressive' | 'productive' | 'minimal';
492
+ }
493
+
494
+ export interface DurationScale {
495
+ instant: string;
496
+ fast: string;
497
+ normal: string;
498
+ slow: string;
499
+ slower: string;
500
+ }
501
+
502
+ export interface EasingScale {
503
+ standard: string;
504
+ standardDecelerate: string;
505
+ standardAccelerate: string;
506
+ emphasized: string;
507
+ emphasizedDecelerate: string;
508
+ emphasizedAccelerate: string;
509
+ }
510
+ ```
511
+
512
+ ### src/languages/transform.ts
513
+
514
+ ```typescript
515
+ import type { DesignLanguageContract, TonalPalette, SemanticColors } from '../contracts/design-language.contract';
516
+
517
+ /**
518
+ * Transforms a DesignLanguageContract into Panda CSS theme configuration
519
+ */
520
+ export function transformToPandaTheme(language: DesignLanguageContract) {
521
+ return {
522
+ tokens: transformTokens(language),
523
+ semanticTokens: transformSemanticTokens(language),
524
+ textStyles: transformTextStyles(language)
525
+ };
526
+ }
527
+
528
+ function transformTokens(language: DesignLanguageContract) {
529
+ return {
530
+ colors: transformColorPalettes(language.colors),
531
+ fonts: {
532
+ display: { value: language.typography.fonts.display },
533
+ body: { value: language.typography.fonts.body },
534
+ mono: { value: language.typography.fonts.mono }
535
+ },
536
+ fontSizes: extractFontSizes(language.typography.scale),
537
+ lineHeights: extractLineHeights(language.typography.scale),
538
+ fontWeights: extractFontWeights(language.typography.scale),
539
+ letterSpacings: extractLetterSpacings(language.typography.scale),
540
+ spacing: objectToTokens(language.spacing),
541
+ radii: objectToTokens(language.shape.radii),
542
+ shadows: objectToTokens(language.elevation.levels),
543
+ durations: objectToTokens(language.motion.durations),
544
+ easings: objectToTokens(language.motion.easings)
545
+ };
546
+ }
547
+
548
+ function transformSemanticTokens(language: DesignLanguageContract) {
549
+ const light = language.semantic;
550
+ const dark = language.semanticDark || light; // Fallback to light if no dark
551
+
552
+ return {
553
+ colors: Object.fromEntries(
554
+ Object.entries(light).map(([key, lightValue]) => [
555
+ key,
556
+ {
557
+ value: {
558
+ base: lightValue,
559
+ _dark: dark[key as keyof SemanticColors] || lightValue
560
+ }
561
+ }
562
+ ])
563
+ )
564
+ };
565
+ }
566
+
567
+ function transformTextStyles(language: DesignLanguageContract) {
568
+ const scale = language.typography.scale;
569
+
570
+ return Object.fromEntries(
571
+ Object.entries(scale).map(([name, style]) => [
572
+ name,
573
+ {
574
+ value: {
575
+ fontFamily: style.fontFamily || 'body',
576
+ fontSize: style.fontSize,
577
+ lineHeight: style.lineHeight,
578
+ fontWeight: style.fontWeight,
579
+ letterSpacing: style.letterSpacing
580
+ }
581
+ }
582
+ ])
583
+ );
584
+ }
585
+
586
+ function transformColorPalettes(palettes: Record<string, TonalPalette>) {
587
+ return Object.fromEntries(
588
+ Object.entries(palettes).map(([name, palette]) => [
589
+ name,
590
+ Object.fromEntries(
591
+ Object.entries(palette).map(([tone, value]) => [
592
+ tone,
593
+ { value }
594
+ ])
595
+ )
596
+ ])
597
+ );
598
+ }
599
+
600
+ function objectToTokens<T extends Record<string, string>>(obj: T) {
601
+ return Object.fromEntries(
602
+ Object.entries(obj).map(([key, value]) => [key, { value }])
603
+ );
604
+ }
605
+
606
+ function extractFontSizes(scale: Record<string, { fontSize: string }>) {
607
+ return Object.fromEntries(
608
+ Object.entries(scale).map(([name, style]) => [
609
+ name,
610
+ { value: style.fontSize }
611
+ ])
612
+ );
613
+ }
614
+
615
+ function extractLineHeights(scale: Record<string, { lineHeight: string }>) {
616
+ return Object.fromEntries(
617
+ Object.entries(scale).map(([name, style]) => [
618
+ name,
619
+ { value: style.lineHeight }
620
+ ])
621
+ );
622
+ }
623
+
624
+ function extractFontWeights(scale: Record<string, { fontWeight: string }>) {
625
+ const weights = new Map<string, string>();
626
+ Object.values(scale).forEach(style => {
627
+ weights.set(style.fontWeight, style.fontWeight);
628
+ });
629
+ return Object.fromEntries(
630
+ Array.from(weights.entries()).map(([key, value]) => [
631
+ key,
632
+ { value }
633
+ ])
634
+ );
635
+ }
636
+
637
+ function extractLetterSpacings(scale: Record<string, { letterSpacing: string }>) {
638
+ return Object.fromEntries(
639
+ Object.entries(scale).map(([name, style]) => [
640
+ name,
641
+ { value: style.letterSpacing }
642
+ ])
643
+ );
644
+ }
645
+ ```
646
+
647
+ ### src/languages/material3.language.ts
648
+
649
+ ```typescript
650
+ import type { DesignLanguageContract } from '../contracts/design-language.contract';
651
+
652
+ /**
653
+ * Material Design 3 Language Implementation
654
+ *
655
+ * Source color: #63A002 (TastyMakers green)
656
+ * Generated via Material Theme Builder plugin 2024-12-24
657
+ */
658
+ export const material3Language: DesignLanguageContract = {
659
+ name: 'material3',
660
+ version: '1.0.0',
661
+
662
+ colors: {
663
+ // From Material Theme Builder export
664
+ primary: {
665
+ 0: '#000000',
666
+ 10: '#102000',
667
+ 20: '#1F3700',
668
+ 30: '#2F4F00',
669
+ 40: '#3F6900',
670
+ 50: '#518500',
671
+ 60: '#64A104',
672
+ 70: '#7DBD2A',
673
+ 80: '#97D945',
674
+ 90: '#B2F65F',
675
+ 95: '#D2FF9B',
676
+ 99: '#F9FFE9',
677
+ 100: '#FFFFFF'
678
+ },
679
+ secondary: {
680
+ 0: '#000000',
681
+ 10: '#121F04',
682
+ 20: '#263515',
683
+ 30: '#3C4C2A',
684
+ 40: '#54643F',
685
+ 50: '#6C7D56',
686
+ 60: '#85976E',
687
+ 70: '#A0B187',
688
+ 80: '#BBCDA1',
689
+ 90: '#D7E9BB',
690
+ 95: '#E5F7C9',
691
+ 99: '#F9FFE9',
692
+ 100: '#FFFFFF'
693
+ },
694
+ tertiary: {
695
+ 0: '#000000',
696
+ 10: '#00201E',
697
+ 20: '#003735',
698
+ 30: '#00504C',
699
+ 40: '#046A66',
700
+ 50: '#30837F',
701
+ 60: '#4D9D98',
702
+ 70: '#69B8B3',
703
+ 80: '#85D4CF',
704
+ 90: '#A1F1EB',
705
+ 95: '#B0FFF9',
706
+ 99: '#F2FFFD',
707
+ 100: '#FFFFFF'
708
+ },
709
+ neutral: {
710
+ 0: '#000000',
711
+ 10: '#1B1C18',
712
+ 20: '#30312C',
713
+ 30: '#464742',
714
+ 40: '#5E5F59',
715
+ 50: '#777771',
716
+ 60: '#91918B',
717
+ 70: '#ABACA5',
718
+ 80: '#C7C7C0',
719
+ 90: '#E3E3DB',
720
+ 95: '#F2F1E9',
721
+ 99: '#FDFCF5',
722
+ 100: '#FFFFFF'
723
+ },
724
+ neutralVariant: {
725
+ 0: '#000000',
726
+ 10: '#191D14',
727
+ 20: '#2E3228',
728
+ 30: '#44483D',
729
+ 40: '#5C6054',
730
+ 50: '#75796C',
731
+ 60: '#8F9285',
732
+ 70: '#A9AD9F',
733
+ 80: '#C5C8BA',
734
+ 90: '#E1E4D5',
735
+ 95: '#EFF2E3',
736
+ 99: '#FBFEEE',
737
+ 100: '#FFFFFF'
738
+ },
739
+ error: {
740
+ 0: '#000000',
741
+ 10: '#410E0B',
742
+ 20: '#601410',
743
+ 30: '#8C1D18',
744
+ 40: '#B3261E',
745
+ 50: '#DC362E',
746
+ 60: '#E46962',
747
+ 70: '#EC928E',
748
+ 80: '#F2B8B5',
749
+ 90: '#F9DEDC',
750
+ 95: '#FCEEEE',
751
+ 99: '#FFFBF9',
752
+ 100: '#FFFFFF'
753
+ }
754
+ },
755
+
756
+ semantic: {
757
+ // Light theme from Material Theme Builder
758
+ primary: '#4C662B',
759
+ onPrimary: '#FFFFFF',
760
+ primaryContainer: '#CDEDA3',
761
+ onPrimaryContainer: '#354E16',
762
+
763
+ secondary: '#586249',
764
+ onSecondary: '#FFFFFF',
765
+ secondaryContainer: '#DCE7C8',
766
+ onSecondaryContainer: '#404A33',
767
+
768
+ tertiary: '#386663',
769
+ onTertiary: '#FFFFFF',
770
+ tertiaryContainer: '#BCECE7',
771
+ onTertiaryContainer: '#1F4E4B',
772
+
773
+ error: '#BA1A1A',
774
+ onError: '#FFFFFF',
775
+ errorContainer: '#FFDAD6',
776
+ onErrorContainer: '#93000A',
777
+
778
+ surface: '#F9FAEF',
779
+ onSurface: '#1A1C16',
780
+ surfaceVariant: '#E1E4D5',
781
+ onSurfaceVariant: '#44483D',
782
+
783
+ surfaceContainerLowest: '#FFFFFF',
784
+ surfaceContainerLow: '#F3F4E9',
785
+ surfaceContainer: '#EEEFE3',
786
+ surfaceContainerHigh: '#E8E9DE',
787
+ surfaceContainerHighest: '#E2E3D8',
788
+
789
+ outline: '#75796C',
790
+ outlineVariant: '#C5C8BA',
791
+
792
+ inverseSurface: '#2F312A',
793
+ inverseOnSurface: '#F1F2E6',
794
+ inversePrimary: '#B1D18A',
795
+
796
+ background: '#F9FAEF',
797
+ onBackground: '#1A1C16',
798
+
799
+ scrim: '#000000',
800
+ shadow: '#000000'
801
+ },
802
+
803
+ // Dark theme semantic colors (for reference/dark mode implementation)
804
+ semanticDark: {
805
+ primary: '#B1D18A',
806
+ onPrimary: '#1F3701',
807
+ primaryContainer: '#354E16',
808
+ onPrimaryContainer: '#CDEDA3',
809
+
810
+ secondary: '#BFCBAD',
811
+ onSecondary: '#2A331E',
812
+ secondaryContainer: '#404A33',
813
+ onSecondaryContainer: '#DCE7C8',
814
+
815
+ tertiary: '#A0D0CB',
816
+ onTertiary: '#003735',
817
+ tertiaryContainer: '#1F4E4B',
818
+ onTertiaryContainer: '#BCECE7',
819
+
820
+ error: '#FFB4AB',
821
+ onError: '#690005',
822
+ errorContainer: '#93000A',
823
+ onErrorContainer: '#FFDAD6',
824
+
825
+ surface: '#12140E',
826
+ onSurface: '#E2E3D8',
827
+ surfaceVariant: '#44483D',
828
+ onSurfaceVariant: '#C5C8BA',
829
+
830
+ surfaceContainerLowest: '#0C0F09',
831
+ surfaceContainerLow: '#1A1C16',
832
+ surfaceContainer: '#1E201A',
833
+ surfaceContainerHigh: '#282B24',
834
+ surfaceContainerHighest: '#33362E',
835
+
836
+ outline: '#8F9285',
837
+ outlineVariant: '#44483D',
838
+
839
+ inverseSurface: '#E2E3D8',
840
+ inverseOnSurface: '#2F312A',
841
+ inversePrimary: '#4C662B',
842
+
843
+ background: '#12140E',
844
+ onBackground: '#E2E3D8',
845
+
846
+ scrim: '#000000',
847
+ shadow: '#000000'
848
+ },
849
+
850
+ typography: {
851
+ fonts: {
852
+ display: 'Georgia, "Times New Roman", serif',
853
+ body: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
854
+ mono: '"JetBrains Mono", "Fira Code", Consolas, monospace'
855
+ },
856
+ scale: {
857
+ displayLarge: {
858
+ fontSize: '57px',
859
+ lineHeight: '64px',
860
+ fontWeight: '400',
861
+ letterSpacing: '-0.25px',
862
+ fontFamily: 'display'
863
+ },
864
+ displayMedium: {
865
+ fontSize: '45px',
866
+ lineHeight: '52px',
867
+ fontWeight: '400',
868
+ letterSpacing: '0px',
869
+ fontFamily: 'display'
870
+ },
871
+ displaySmall: {
872
+ fontSize: '36px',
873
+ lineHeight: '44px',
874
+ fontWeight: '400',
875
+ letterSpacing: '0px',
876
+ fontFamily: 'display'
877
+ },
878
+ headlineLarge: {
879
+ fontSize: '32px',
880
+ lineHeight: '40px',
881
+ fontWeight: '400',
882
+ letterSpacing: '0px',
883
+ fontFamily: 'display'
884
+ },
885
+ headlineMedium: {
886
+ fontSize: '28px',
887
+ lineHeight: '36px',
888
+ fontWeight: '400',
889
+ letterSpacing: '0px',
890
+ fontFamily: 'display'
891
+ },
892
+ headlineSmall: {
893
+ fontSize: '24px',
894
+ lineHeight: '32px',
895
+ fontWeight: '400',
896
+ letterSpacing: '0px',
897
+ fontFamily: 'display'
898
+ },
899
+ titleLarge: {
900
+ fontSize: '22px',
901
+ lineHeight: '28px',
902
+ fontWeight: '500',
903
+ letterSpacing: '0px',
904
+ fontFamily: 'body'
905
+ },
906
+ titleMedium: {
907
+ fontSize: '16px',
908
+ lineHeight: '24px',
909
+ fontWeight: '500',
910
+ letterSpacing: '0.15px',
911
+ fontFamily: 'body'
912
+ },
913
+ titleSmall: {
914
+ fontSize: '14px',
915
+ lineHeight: '20px',
916
+ fontWeight: '500',
917
+ letterSpacing: '0.1px',
918
+ fontFamily: 'body'
919
+ },
920
+ bodyLarge: {
921
+ fontSize: '16px',
922
+ lineHeight: '24px',
923
+ fontWeight: '400',
924
+ letterSpacing: '0.5px',
925
+ fontFamily: 'body'
926
+ },
927
+ bodyMedium: {
928
+ fontSize: '14px',
929
+ lineHeight: '20px',
930
+ fontWeight: '400',
931
+ letterSpacing: '0.25px',
932
+ fontFamily: 'body'
933
+ },
934
+ bodySmall: {
935
+ fontSize: '12px',
936
+ lineHeight: '16px',
937
+ fontWeight: '400',
938
+ letterSpacing: '0.4px',
939
+ fontFamily: 'body'
940
+ },
941
+ labelLarge: {
942
+ fontSize: '14px',
943
+ lineHeight: '20px',
944
+ fontWeight: '500',
945
+ letterSpacing: '0.1px',
946
+ fontFamily: 'body'
947
+ },
948
+ labelMedium: {
949
+ fontSize: '12px',
950
+ lineHeight: '16px',
951
+ fontWeight: '500',
952
+ letterSpacing: '0.5px',
953
+ fontFamily: 'body'
954
+ },
955
+ labelSmall: {
956
+ fontSize: '11px',
957
+ lineHeight: '16px',
958
+ fontWeight: '500',
959
+ letterSpacing: '0.5px',
960
+ fontFamily: 'body'
961
+ }
962
+ }
963
+ },
964
+
965
+ spacing: {
966
+ none: '0px',
967
+ xxs: '2px',
968
+ xs: '4px',
969
+ sm: '8px',
970
+ md: '16px',
971
+ lg: '24px',
972
+ xl: '32px',
973
+ xxl: '48px',
974
+ xxxl: '64px'
975
+ },
976
+
977
+ shape: {
978
+ radii: {
979
+ none: '0px',
980
+ extraSmall: '4px',
981
+ small: '8px',
982
+ medium: '12px',
983
+ large: '16px',
984
+ extraLarge: '28px',
985
+ full: '9999px'
986
+ },
987
+ style: 'rounded'
988
+ },
989
+
990
+ elevation: {
991
+ levels: {
992
+ level0: 'none',
993
+ level1: '0px 1px 2px rgba(0, 0, 0, 0.3), 0px 1px 3px 1px rgba(0, 0, 0, 0.15)',
994
+ level2: '0px 1px 2px rgba(0, 0, 0, 0.3), 0px 2px 6px 2px rgba(0, 0, 0, 0.15)',
995
+ level3: '0px 4px 8px 3px rgba(0, 0, 0, 0.15), 0px 1px 3px rgba(0, 0, 0, 0.3)',
996
+ level4: '0px 6px 10px 4px rgba(0, 0, 0, 0.15), 0px 2px 3px rgba(0, 0, 0, 0.3)',
997
+ level5: '0px 8px 12px 6px rgba(0, 0, 0, 0.15), 0px 4px 4px rgba(0, 0, 0, 0.3)'
998
+ },
999
+ style: 'shadow'
1000
+ },
1001
+
1002
+ motion: {
1003
+ durations: {
1004
+ instant: '0ms',
1005
+ fast: '100ms',
1006
+ normal: '200ms',
1007
+ slow: '300ms',
1008
+ slower: '500ms'
1009
+ },
1010
+ easings: {
1011
+ standard: 'cubic-bezier(0.2, 0, 0, 1)',
1012
+ standardDecelerate: 'cubic-bezier(0, 0, 0, 1)',
1013
+ standardAccelerate: 'cubic-bezier(0.3, 0, 1, 1)',
1014
+ emphasized: 'cubic-bezier(0.2, 0, 0, 1)',
1015
+ emphasizedDecelerate: 'cubic-bezier(0.05, 0.7, 0.1, 1)',
1016
+ emphasizedAccelerate: 'cubic-bezier(0.3, 0, 0.8, 0.15)'
1017
+ },
1018
+ style: 'expressive'
1019
+ }
1020
+ };
1021
+ ```
1022
+
1023
+ ### src/languages/index.ts
1024
+
1025
+ ```typescript
1026
+ // Export the active language
1027
+ // Change this import to switch aesthetics
1028
+ export { material3Language as activeLanguage } from './material3.language';
1029
+
1030
+ // Re-export transformer
1031
+ export { transformToPandaTheme } from './transform';
1032
+
1033
+ // Re-export types
1034
+ export type { DesignLanguageContract } from '../contracts/design-language.contract';
1035
+ ```
1036
+
1037
+ ### src/recipes/button.recipe.ts
1038
+
1039
+ ```typescript
1040
+ import { defineRecipe } from '@pandacss/dev';
1041
+
1042
+ export const buttonRecipe = defineRecipe({
1043
+ className: 'button',
1044
+ description: 'Material Design 3 button component',
1045
+ base: {
1046
+ display: 'inline-flex',
1047
+ alignItems: 'center',
1048
+ justifyContent: 'center',
1049
+ gap: 'sm',
1050
+ fontFamily: 'body',
1051
+ fontWeight: '500',
1052
+ borderRadius: 'full',
1053
+ cursor: 'pointer',
1054
+ transition: 'all',
1055
+ transitionDuration: 'fast',
1056
+ transitionTimingFunction: 'standard',
1057
+ outline: 'none',
1058
+ border: 'none',
1059
+ textDecoration: 'none',
1060
+ _disabled: {
1061
+ opacity: 0.38,
1062
+ cursor: 'not-allowed',
1063
+ pointerEvents: 'none'
1064
+ },
1065
+ _focusVisible: {
1066
+ outline: '2px solid',
1067
+ outlineColor: 'primary',
1068
+ outlineOffset: '2px'
1069
+ }
1070
+ },
1071
+ variants: {
1072
+ variant: {
1073
+ filled: {
1074
+ bg: 'primary',
1075
+ color: 'onPrimary',
1076
+ _hover: {
1077
+ opacity: 0.92,
1078
+ shadow: 'level1'
1079
+ },
1080
+ _active: {
1081
+ opacity: 0.88
1082
+ }
1083
+ },
1084
+ outlined: {
1085
+ bg: 'transparent',
1086
+ color: 'primary',
1087
+ borderWidth: '1px',
1088
+ borderStyle: 'solid',
1089
+ borderColor: 'outline',
1090
+ _hover: {
1091
+ bg: 'primary',
1092
+ bgOpacity: 0.08
1093
+ },
1094
+ _active: {
1095
+ bg: 'primary',
1096
+ bgOpacity: 0.12
1097
+ }
1098
+ },
1099
+ text: {
1100
+ bg: 'transparent',
1101
+ color: 'primary',
1102
+ _hover: {
1103
+ bg: 'primary',
1104
+ bgOpacity: 0.08
1105
+ },
1106
+ _active: {
1107
+ bg: 'primary',
1108
+ bgOpacity: 0.12
1109
+ }
1110
+ },
1111
+ elevated: {
1112
+ bg: 'surfaceContainerLow',
1113
+ color: 'primary',
1114
+ shadow: 'level1',
1115
+ _hover: {
1116
+ shadow: 'level2',
1117
+ bg: 'surfaceContainerLow'
1118
+ },
1119
+ _active: {
1120
+ shadow: 'level1'
1121
+ }
1122
+ },
1123
+ tonal: {
1124
+ bg: 'secondaryContainer',
1125
+ color: 'onSecondaryContainer',
1126
+ _hover: {
1127
+ shadow: 'level1'
1128
+ },
1129
+ _active: {
1130
+ shadow: 'none'
1131
+ }
1132
+ }
1133
+ },
1134
+ size: {
1135
+ sm: {
1136
+ height: '32px',
1137
+ px: 'md',
1138
+ fontSize: 'labelMedium',
1139
+ lineHeight: 'labelMedium'
1140
+ },
1141
+ md: {
1142
+ height: '40px',
1143
+ px: 'lg',
1144
+ fontSize: 'labelLarge',
1145
+ lineHeight: 'labelLarge'
1146
+ },
1147
+ lg: {
1148
+ height: '48px',
1149
+ px: 'xl',
1150
+ fontSize: 'labelLarge',
1151
+ lineHeight: 'labelLarge'
1152
+ }
1153
+ }
1154
+ },
1155
+ defaultVariants: {
1156
+ variant: 'filled',
1157
+ size: 'md'
1158
+ }
1159
+ });
1160
+ ```
1161
+
1162
+ ### src/recipes/card.recipe.ts
1163
+
1164
+ ```typescript
1165
+ import { defineRecipe } from '@pandacss/dev';
1166
+
1167
+ export const cardRecipe = defineRecipe({
1168
+ className: 'card',
1169
+ description: 'Material Design 3 card component',
1170
+ base: {
1171
+ display: 'flex',
1172
+ flexDirection: 'column',
1173
+ borderRadius: 'medium',
1174
+ overflow: 'hidden',
1175
+ transition: 'all',
1176
+ transitionDuration: 'fast',
1177
+ transitionTimingFunction: 'standard'
1178
+ },
1179
+ variants: {
1180
+ variant: {
1181
+ elevated: {
1182
+ bg: 'surfaceContainerLow',
1183
+ shadow: 'level1',
1184
+ _hover: {
1185
+ shadow: 'level2'
1186
+ }
1187
+ },
1188
+ filled: {
1189
+ bg: 'surfaceContainerHighest'
1190
+ },
1191
+ outlined: {
1192
+ bg: 'surface',
1193
+ borderWidth: '1px',
1194
+ borderStyle: 'solid',
1195
+ borderColor: 'outlineVariant'
1196
+ }
1197
+ },
1198
+ interactive: {
1199
+ true: {
1200
+ cursor: 'pointer',
1201
+ _hover: {
1202
+ opacity: 0.96
1203
+ },
1204
+ _active: {
1205
+ opacity: 0.92
1206
+ }
1207
+ }
1208
+ }
1209
+ },
1210
+ defaultVariants: {
1211
+ variant: 'elevated',
1212
+ interactive: false
1213
+ }
1214
+ });
1215
+ ```
1216
+
1217
+ ### src/recipes/icon-button.recipe.ts
1218
+
1219
+ ```typescript
1220
+ import { defineRecipe } from '@pandacss/dev';
1221
+
1222
+ export const iconButtonRecipe = defineRecipe({
1223
+ className: 'icon-button',
1224
+ description: 'Material Design 3 icon button component',
1225
+ base: {
1226
+ display: 'inline-flex',
1227
+ alignItems: 'center',
1228
+ justifyContent: 'center',
1229
+ borderRadius: 'full',
1230
+ cursor: 'pointer',
1231
+ transition: 'all',
1232
+ transitionDuration: 'fast',
1233
+ transitionTimingFunction: 'standard',
1234
+ outline: 'none',
1235
+ border: 'none',
1236
+ p: '0',
1237
+ _disabled: {
1238
+ opacity: 0.38,
1239
+ cursor: 'not-allowed',
1240
+ pointerEvents: 'none'
1241
+ },
1242
+ _focusVisible: {
1243
+ outline: '2px solid',
1244
+ outlineColor: 'primary',
1245
+ outlineOffset: '2px'
1246
+ }
1247
+ },
1248
+ variants: {
1249
+ variant: {
1250
+ standard: {
1251
+ bg: 'transparent',
1252
+ color: 'onSurfaceVariant',
1253
+ _hover: {
1254
+ bg: 'onSurfaceVariant',
1255
+ bgOpacity: 0.08
1256
+ }
1257
+ },
1258
+ filled: {
1259
+ bg: 'primary',
1260
+ color: 'onPrimary',
1261
+ _hover: {
1262
+ opacity: 0.92
1263
+ }
1264
+ },
1265
+ tonal: {
1266
+ bg: 'secondaryContainer',
1267
+ color: 'onSecondaryContainer',
1268
+ _hover: {
1269
+ opacity: 0.92
1270
+ }
1271
+ },
1272
+ outlined: {
1273
+ bg: 'transparent',
1274
+ color: 'onSurfaceVariant',
1275
+ borderWidth: '1px',
1276
+ borderStyle: 'solid',
1277
+ borderColor: 'outline',
1278
+ _hover: {
1279
+ bg: 'onSurfaceVariant',
1280
+ bgOpacity: 0.08
1281
+ }
1282
+ }
1283
+ },
1284
+ size: {
1285
+ sm: {
1286
+ width: '32px',
1287
+ height: '32px',
1288
+ '& svg': {
1289
+ width: '18px',
1290
+ height: '18px'
1291
+ }
1292
+ },
1293
+ md: {
1294
+ width: '40px',
1295
+ height: '40px',
1296
+ '& svg': {
1297
+ width: '24px',
1298
+ height: '24px'
1299
+ }
1300
+ },
1301
+ lg: {
1302
+ width: '48px',
1303
+ height: '48px',
1304
+ '& svg': {
1305
+ width: '24px',
1306
+ height: '24px'
1307
+ }
1308
+ }
1309
+ }
1310
+ },
1311
+ defaultVariants: {
1312
+ variant: 'standard',
1313
+ size: 'md'
1314
+ }
1315
+ });
1316
+ ```
1317
+
1318
+ ### src/recipes/index.ts
1319
+
1320
+ ```typescript
1321
+ export { buttonRecipe } from './button.recipe';
1322
+ export { cardRecipe } from './card.recipe';
1323
+ export { iconButtonRecipe } from './icon-button.recipe';
1324
+ ```
1325
+
1326
+ ### src/utils/cn.ts
1327
+
1328
+ ```typescript
1329
+ import { clsx, type ClassValue } from 'clsx';
1330
+
1331
+ /**
1332
+ * Utility function to merge class names
1333
+ */
1334
+ export function cn(...inputs: ClassValue[]) {
1335
+ return clsx(inputs);
1336
+ }
1337
+ ```
1338
+
1339
+ ### src/components/Button/Button.tsx
1340
+
1341
+ ```typescript
1342
+ import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react';
1343
+ import { button, type ButtonVariantProps } from 'styled-system/recipes';
1344
+ import { cn } from '../../utils/cn';
1345
+
1346
+ export interface ButtonProps
1347
+ extends ButtonHTMLAttributes<HTMLButtonElement>,
1348
+ ButtonVariantProps {
1349
+ children: ReactNode;
1350
+ leftIcon?: ReactNode;
1351
+ rightIcon?: ReactNode;
1352
+ }
1353
+
1354
+ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
1355
+ (
1356
+ { children, variant, size, leftIcon, rightIcon, className, ...props },
1357
+ ref
1358
+ ) => {
1359
+ return (
1360
+ <button
1361
+ ref={ref}
1362
+ className={cn(button({ variant, size }), className)}
1363
+ {...props}
1364
+ >
1365
+ {leftIcon}
1366
+ {children}
1367
+ {rightIcon}
1368
+ </button>
1369
+ );
1370
+ }
1371
+ );
1372
+
1373
+ Button.displayName = 'Button';
1374
+ ```
1375
+
1376
+ ### src/components/Button/Button.stories.tsx
1377
+
1378
+ ```typescript
1379
+ import type { Meta, StoryObj } from '@storybook/react';
1380
+ import { Button } from './Button';
1381
+
1382
+ const meta: Meta<typeof Button> = {
1383
+ title: 'Components/Button',
1384
+ component: Button,
1385
+ parameters: {
1386
+ layout: 'centered',
1387
+ },
1388
+ tags: ['autodocs'],
1389
+ argTypes: {
1390
+ variant: {
1391
+ control: 'select',
1392
+ options: ['filled', 'outlined', 'text', 'elevated', 'tonal'],
1393
+ },
1394
+ size: {
1395
+ control: 'select',
1396
+ options: ['sm', 'md', 'lg'],
1397
+ },
1398
+ },
1399
+ };
1400
+
1401
+ export default meta;
1402
+ type Story = StoryObj<typeof meta>;
1403
+
1404
+ export const Filled: Story = {
1405
+ args: {
1406
+ children: 'Filled Button',
1407
+ variant: 'filled',
1408
+ },
1409
+ };
1410
+
1411
+ export const Outlined: Story = {
1412
+ args: {
1413
+ children: 'Outlined Button',
1414
+ variant: 'outlined',
1415
+ },
1416
+ };
1417
+
1418
+ export const Text: Story = {
1419
+ args: {
1420
+ children: 'Text Button',
1421
+ variant: 'text',
1422
+ },
1423
+ };
1424
+
1425
+ export const Elevated: Story = {
1426
+ args: {
1427
+ children: 'Elevated Button',
1428
+ variant: 'elevated',
1429
+ },
1430
+ };
1431
+
1432
+ export const Tonal: Story = {
1433
+ args: {
1434
+ children: 'Tonal Button',
1435
+ variant: 'tonal',
1436
+ },
1437
+ };
1438
+
1439
+ export const Sizes: Story = {
1440
+ render: () => (
1441
+ <div style={{ display: 'flex', gap: '16px', alignItems: 'center' }}>
1442
+ <Button size="sm">Small</Button>
1443
+ <Button size="md">Medium</Button>
1444
+ <Button size="lg">Large</Button>
1445
+ </div>
1446
+ ),
1447
+ };
1448
+
1449
+ export const Disabled: Story = {
1450
+ args: {
1451
+ children: 'Disabled Button',
1452
+ disabled: true,
1453
+ },
1454
+ };
1455
+ ```
1456
+
1457
+ ### src/components/Button/index.ts
1458
+
1459
+ ```typescript
1460
+ export { Button, type ButtonProps } from './Button';
1461
+ ```
1462
+
1463
+ ### src/components/index.ts
1464
+
1465
+ ```typescript
1466
+ export * from './Button';
1467
+ // export * from './Card';
1468
+ // export * from './Dialog';
1469
+ ```
1470
+
1471
+ ### src/index.ts
1472
+
1473
+ ```typescript
1474
+ // Components
1475
+ export * from './components';
1476
+
1477
+ // Recipes (for direct usage)
1478
+ export * from './recipes';
1479
+
1480
+ // Language system
1481
+ export * from './languages';
1482
+
1483
+ // Contracts
1484
+ export type * from './contracts/design-language.contract';
1485
+
1486
+ // Utilities
1487
+ export { cn } from './utils/cn';
1488
+ ```
1489
+
1490
+ ### scripts/generate-palette.ts
1491
+
1492
+ ```typescript
1493
+ import {
1494
+ argbFromHex,
1495
+ hexFromArgb,
1496
+ TonalPalette,
1497
+ Hct,
1498
+ themeFromSourceColor
1499
+ } from '@material/material-color-utilities';
1500
+ import fs from 'fs';
1501
+ import path from 'path';
1502
+
1503
+ // TastyMakers source color from Material Theme Builder
1504
+ const SOURCE_COLOR = '#63A002';
1505
+
1506
+ function generatePalette() {
1507
+ const theme = themeFromSourceColor(argbFromHex(SOURCE_COLOR));
1508
+
1509
+ const tones = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 95, 99, 100];
1510
+
1511
+ const palettes = {
1512
+ primary: extractTones(theme.palettes.primary, tones),
1513
+ secondary: extractTones(theme.palettes.secondary, tones),
1514
+ tertiary: extractTones(theme.palettes.tertiary, tones),
1515
+ neutral: extractTones(theme.palettes.neutral, tones),
1516
+ neutralVariant: extractTones(theme.palettes.neutralVariant, tones),
1517
+ error: extractTones(theme.palettes.error, tones),
1518
+ };
1519
+
1520
+ const schemes = {
1521
+ light: formatScheme(theme.schemes.light),
1522
+ dark: formatScheme(theme.schemes.dark),
1523
+ };
1524
+
1525
+ const output = {
1526
+ sourceColor: SOURCE_COLOR,
1527
+ palettes,
1528
+ schemes,
1529
+ };
1530
+
1531
+ const outputPath = path.join(process.cwd(), 'tokens/generated-palette.json');
1532
+ fs.writeFileSync(outputPath, JSON.stringify(output, null, 2));
1533
+
1534
+ console.log('✅ Palette generated!');
1535
+ console.log(` Source: ${SOURCE_COLOR}`);
1536
+ console.log(` Output: ${outputPath}`);
1537
+ console.log('');
1538
+ console.log('🎨 Primary tones:');
1539
+ Object.entries(palettes.primary).forEach(([tone, hex]) => {
1540
+ console.log(` ${tone}: ${hex}`);
1541
+ });
1542
+ }
1543
+
1544
+ function extractTones(palette: TonalPalette, tones: number[]) {
1545
+ return Object.fromEntries(
1546
+ tones.map(tone => [tone.toString(), hexFromArgb(palette.tone(tone))])
1547
+ );
1548
+ }
1549
+
1550
+ function formatScheme(scheme: any) {
1551
+ const result: Record<string, string> = {};
1552
+ for (const [key, value] of Object.entries(scheme)) {
1553
+ if (typeof value === 'number') {
1554
+ result[key] = hexFromArgb(value);
1555
+ }
1556
+ }
1557
+ return result;
1558
+ }
1559
+
1560
+ generatePalette();
1561
+ ```
1562
+
1563
+ ### .storybook/main.ts
1564
+
1565
+ ```typescript
1566
+ import type { StorybookConfig } from '@storybook/react-vite';
1567
+
1568
+ const config: StorybookConfig = {
1569
+ stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
1570
+ addons: [
1571
+ '@storybook/addon-essentials',
1572
+ '@storybook/addon-a11y',
1573
+ ],
1574
+ framework: {
1575
+ name: '@storybook/react-vite',
1576
+ options: {},
1577
+ },
1578
+ docs: {
1579
+ autodocs: 'tag',
1580
+ },
1581
+ };
1582
+
1583
+ export default config;
1584
+ ```
1585
+
1586
+ ### .storybook/preview.ts
1587
+
1588
+ ```typescript
1589
+ import type { Preview } from '@storybook/react';
1590
+ import '../styled-system/styles.css';
1591
+
1592
+ const preview: Preview = {
1593
+ parameters: {
1594
+ actions: { argTypesRegex: '^on[A-Z].*' },
1595
+ controls: {
1596
+ matchers: {
1597
+ color: /(background|color)$/i,
1598
+ date: /Date$/i,
1599
+ },
1600
+ },
1601
+ backgrounds: {
1602
+ default: 'surface',
1603
+ values: [
1604
+ { name: 'surface', value: '#F9FAEF' },
1605
+ { name: 'dark', value: '#12140E' },
1606
+ ],
1607
+ },
1608
+ },
1609
+ };
1610
+
1611
+ export default preview;
1612
+ ```
1613
+
1614
+ ### .npmrc
1615
+
1616
+ ```
1617
+ auto-install-peers=true
1618
+ strict-peer-dependencies=false
1619
+ ```
1620
+
1621
+ ### .gitignore
1622
+
1623
+ ```
1624
+ # Dependencies
1625
+ node_modules/
1626
+
1627
+ # Build outputs
1628
+ dist/
1629
+ styled-system/
1630
+ storybook-static/
1631
+
1632
+ # IDE
1633
+ .vscode/
1634
+ .idea/
1635
+ *.swp
1636
+ *.swo
1637
+
1638
+ # OS
1639
+ .DS_Store
1640
+ Thumbs.db
1641
+
1642
+ # Logs
1643
+ *.log
1644
+ npm-debug.log*
1645
+ pnpm-debug.log*
1646
+
1647
+ # Environment
1648
+ .env
1649
+ .env.local
1650
+ .env.*.local
1651
+
1652
+ # Testing
1653
+ coverage/
1654
+
1655
+ # Misc
1656
+ *.tgz
1657
+ ```
1658
+
1659
+ ### vitest.config.ts
1660
+
1661
+ ```typescript
1662
+ import { defineConfig } from 'vitest/config';
1663
+ import react from '@vitejs/plugin-react';
1664
+
1665
+ export default defineConfig({
1666
+ plugins: [react()],
1667
+ test: {
1668
+ environment: 'jsdom',
1669
+ globals: true,
1670
+ setupFiles: ['./src/test/setup.ts'],
1671
+ },
1672
+ resolve: {
1673
+ alias: {
1674
+ '@': './src',
1675
+ 'styled-system': './styled-system',
1676
+ },
1677
+ },
1678
+ });
1679
+ ```
1680
+
1681
+ ### README.md
1682
+
1683
+ ```markdown
1684
+ # @discourser/design-system
1685
+
1686
+ An aesthetic-agnostic design system built with Panda CSS and Ark UI.
1687
+
1688
+ ## Features
1689
+
1690
+ - 🎨 **Swappable aesthetics** - Change the entire look by swapping one import
1691
+ - 🎯 **Zero runtime CSS** - SSR-safe with Panda CSS
1692
+ - ♿ **Accessible** - WAI-ARIA compliant via Ark UI
1693
+ - 📦 **Tree-shakeable** - Only import what you need
1694
+ - 🌙 **Dark mode** - Built-in light/dark theme support
1695
+ - 🎭 **Material Design 3** - M3 Expressive as default aesthetic
1696
+
1697
+ ## Installation
1698
+
1699
+ \`\`\`bash
1700
+ pnpm add @discourser/design-system
1701
+ \`\`\`
1702
+
1703
+ ## Quick Start
1704
+
1705
+ \`\`\`tsx
1706
+ import { Button } from '@discourser/design-system';
1707
+ import '@discourser/design-system/styled-system/styles.css';
1708
+
1709
+ function App() {
1710
+ return (
1711
+ <Button variant="filled" size="md">
1712
+ Click me
1713
+ </Button>
1714
+ );
1715
+ }
1716
+ \`\`\`
1717
+
1718
+ ## Development
1719
+
1720
+ \`\`\`bash
1721
+ # Install dependencies
1722
+ pnpm install
1723
+
1724
+ # Start Storybook
1725
+ pnpm dev
1726
+
1727
+ # Build
1728
+ pnpm build
1729
+
1730
+ # Generate color palette from source color
1731
+ pnpm tokens:generate
1732
+
1733
+ # Run tests
1734
+ pnpm test
1735
+ \`\`\`
1736
+
1737
+ ## Architecture
1738
+
1739
+ This design system uses a three-layer architecture:
1740
+
1741
+ 1. **Infrastructure** - Token pipeline, build system, component logic
1742
+ 2. **Design Language** - Swappable aesthetic (colors, typography, spacing)
1743
+ 3. **Component Recipes** - Visual styling derived from the language
1744
+
1745
+ To change the aesthetic, edit `src/languages/index.ts` and point to a different language file.
1746
+
1747
+ ## License
1748
+
1749
+ MIT
1750
+ ```
1751
+
1752
+ ## Post-Setup Commands
1753
+
1754
+ After Copilot creates all files, run:
1755
+
1756
+ ```bash
1757
+ # Install dependencies
1758
+ pnpm install
1759
+
1760
+ # Generate Panda CSS output
1761
+ pnpm build:panda
1762
+
1763
+ # Generate M3 palette from source color (optional - values already in language file)
1764
+ pnpm tokens:generate
1765
+
1766
+ # Start Storybook to verify everything works
1767
+ pnpm dev
1768
+ ```
1769
+
1770
+ ## Success Criteria
1771
+
1772
+ 1. ✅ `pnpm install` completes without errors
1773
+ 2. ✅ `pnpm build:panda` generates `styled-system/` folder
1774
+ 3. ✅ `pnpm dev` launches Storybook
1775
+ 4. ✅ Button component renders with M3 styling
1776
+ 5. ✅ All 5 button variants visible in Storybook
1777
+ 6. ✅ `pnpm build` creates `dist/` with ESM/CJS outputs
1778
+
1779
+ ## Notes
1780
+
1781
+ - The color values in `material3.language.ts` are placeholders based on chartreuse (#CDDC39)
1782
+ - Run `pnpm tokens:generate` to create accurate M3 tonal palettes
1783
+ - Typography uses Georgia (display) and Inter (body) - ensure fonts are loaded in consuming apps
1784
+
1785
+ ---
1786
+
1787
+ ## Implementation Phases
1788
+
1789
+ This section outlines the phased approach to implementing the design system. Each phase builds on the previous one and includes validation gates.
1790
+
1791
+ ### Phase 0: Context Engineering Foundation
1792
+
1793
+ **Goal:** Set up Claude Code Skills and CLAUDE.md for efficient AI-assisted development.
1794
+
1795
+ **Deliverables:**
1796
+ ```
1797
+ .claude/
1798
+ ├── commands/
1799
+ │ ├── fix-foundation.md # Phase 1 automation
1800
+ │ ├── implement-architecture.md # Phase 2 automation
1801
+ │ └── new-component.md # Component scaffolding
1802
+ └── skills/
1803
+ ├── design-language/
1804
+ │ └── SKILL.md # Contract architecture knowledge
1805
+ ├── panda-recipes/
1806
+ │ └── SKILL.md # Recipe patterns with M3
1807
+ ├── m3-tokens/
1808
+ │ └── SKILL.md # M3 color values reference
1809
+ └── component-patterns/
1810
+ └── SKILL.md # forwardRef, Ark UI patterns
1811
+
1812
+ CLAUDE.md # Slim (<300 lines), pointer-based project context
1813
+ ```
1814
+
1815
+ **Validation:**
1816
+ - [ ] `/fix-foundation` command recognized in Claude Code
1817
+ - [ ] Skills auto-load when relevant tasks are requested
1818
+
1819
+ ---
1820
+
1821
+ ### Phase 1: Fix Foundation
1822
+
1823
+ **Goal:** Establish correct dependencies, configuration, and build pipeline.
1824
+
1825
+ **Tasks:**
1826
+ 1. Update `package.json` with correct versions:
1827
+ - `@ark-ui/react` ^4.4.0
1828
+ - `@pandacss/dev` ^0.52.0
1829
+ - `@material/material-color-utilities` ^0.3.0
1830
+ - React 19, Storybook 8.5, Vitest 2.x
1831
+ 2. Update `tsconfig.json` with path aliases
1832
+ 3. Update `.gitignore` (add `styled-system/`, `storybook-static/`)
1833
+ 4. Update `.npmrc` for peer dependency handling
1834
+
1835
+ **Validation:**
1836
+ ```bash
1837
+ pnpm install # ✅ No errors
1838
+ pnpm build:panda # ✅ Generates styled-system/
1839
+ pnpm dev # ✅ Storybook starts
1840
+ ```
1841
+
1842
+ ---
1843
+
1844
+ ### Phase 2: Architecture Implementation
1845
+
1846
+ **Goal:** Implement the three-layer Contract → Language → Transform architecture.
1847
+
1848
+ **Tasks:**
1849
+ 1. Create `src/contracts/design-language.contract.ts`
1850
+ - Full `DesignLanguageContract` interface
1851
+ - All supporting types (ColorPalettes, SemanticColors, Typography, etc.)
1852
+
1853
+ 2. Create `src/languages/material3.language.ts`
1854
+ - Implement `DesignLanguageContract`
1855
+ - Use values from `docs/material-theme.json`
1856
+ - Include `semantic` (light) and `semanticDark` (dark)
1857
+
1858
+ 3. Create `src/languages/transform.ts`
1859
+ - `transformToPandaTheme(language)` function
1860
+ - Returns `{ tokens, semanticTokens, textStyles }`
1861
+
1862
+ 4. Create `src/languages/index.ts`
1863
+ - Export `material3Language as activeLanguage`
1864
+ - Re-export transformer
1865
+
1866
+ 5. Update `panda.config.ts` to use the transform
1867
+
1868
+ **Validation:**
1869
+ ```bash
1870
+ pnpm build:panda # ✅ No errors
1871
+ # Check styled-system/tokens/ contains M3 semantic colors
1872
+ ```
1873
+
1874
+ ---
1875
+
1876
+ ### Phase 3: M3 Token Integration
1877
+
1878
+ **Goal:** Integrate full M3 token set from Material Theme Builder export.
1879
+
1880
+ **Tasks:**
1881
+ 1. Verify `docs/material-theme.json` contains complete export
1882
+ 2. Ensure all tonal palettes (primary, secondary, tertiary, neutral, neutralVariant, error) are in language file
1883
+ 3. Ensure all semantic tokens are mapped for both light and dark
1884
+ 4. Add typography scale (display, headline, title, body, label)
1885
+ 5. Add spacing, shape (radii), elevation (shadows), motion (durations, easings)
1886
+
1887
+ **Validation:**
1888
+ ```bash
1889
+ pnpm build:panda
1890
+ # Verify in Storybook:
1891
+ # - Colors render correctly
1892
+ # - Typography scale works
1893
+ # - Dark mode toggles properly (data-theme="dark")
1894
+ ```
1895
+
1896
+ ---
1897
+
1898
+ ### Phase 4: Complete Recipe Coverage + Testing
1899
+
1900
+ **Goal:** Ensure all components use the recipe pattern with full M3 variant support and comprehensive test coverage.
1901
+
1902
+ **Tasks:**
1903
+
1904
+ #### 4.1 Input Recipe & Component
1905
+ ```typescript
1906
+ // src/recipes/input.recipe.ts
1907
+ import { defineRecipe } from '@pandacss/dev';
1908
+
1909
+ export const inputRecipe = defineRecipe({
1910
+ className: 'input',
1911
+ description: 'Material Design 3 text field component',
1912
+ base: {
1913
+ display: 'flex',
1914
+ flexDirection: 'column',
1915
+ gap: 'xs',
1916
+ },
1917
+ variants: {
1918
+ variant: {
1919
+ filled: {
1920
+ // M3 filled text field styling
1921
+ },
1922
+ outlined: {
1923
+ // M3 outlined text field styling
1924
+ },
1925
+ },
1926
+ size: {
1927
+ sm: { /* ... */ },
1928
+ md: { /* ... */ },
1929
+ },
1930
+ state: {
1931
+ error: { /* ... */ },
1932
+ disabled: { /* ... */ },
1933
+ },
1934
+ },
1935
+ defaultVariants: {
1936
+ variant: 'outlined',
1937
+ size: 'md',
1938
+ },
1939
+ });
1940
+ ```
1941
+
1942
+ ```typescript
1943
+ // src/components/Input/Input.tsx
1944
+ import { forwardRef } from 'react';
1945
+ import { Field } from '@ark-ui/react';
1946
+ import { input, type InputVariantProps } from 'styled-system/recipes';
1947
+ import { cn } from '../../utils/cn';
1948
+
1949
+ export interface InputProps
1950
+ extends React.InputHTMLAttributes<HTMLInputElement>,
1951
+ InputVariantProps {
1952
+ label?: string;
1953
+ helperText?: string;
1954
+ errorText?: string;
1955
+ }
1956
+
1957
+ export const Input = forwardRef<HTMLInputElement, InputProps>(
1958
+ ({ label, helperText, errorText, variant, size, state, className, ...props }, ref) => {
1959
+ const hasError = !!errorText || state === 'error';
1960
+
1961
+ return (
1962
+ <Field.Root invalid={hasError}>
1963
+ {label && <Field.Label>{label}</Field.Label>}
1964
+ <Field.Input
1965
+ ref={ref}
1966
+ className={cn(input({ variant, size, state: hasError ? 'error' : state }), className)}
1967
+ {...props}
1968
+ />
1969
+ {helperText && !hasError && <Field.HelperText>{helperText}</Field.HelperText>}
1970
+ {errorText && <Field.ErrorText>{errorText}</Field.ErrorText>}
1971
+ </Field.Root>
1972
+ );
1973
+ }
1974
+ );
1975
+
1976
+ Input.displayName = 'Input';
1977
+ ```
1978
+
1979
+ #### 4.2 Dialog Recipe & Component (Compound Pattern)
1980
+ ```typescript
1981
+ // src/recipes/dialog.recipe.ts
1982
+ import { defineSlotRecipe } from '@pandacss/dev';
1983
+
1984
+ export const dialogRecipe = defineSlotRecipe({
1985
+ className: 'dialog',
1986
+ description: 'Material Design 3 dialog component',
1987
+ slots: ['backdrop', 'positioner', 'content', 'title', 'description', 'closeTrigger'],
1988
+ base: {
1989
+ backdrop: {
1990
+ position: 'fixed',
1991
+ inset: 0,
1992
+ bg: 'scrim',
1993
+ opacity: 0.32,
1994
+ zIndex: 'modal',
1995
+ },
1996
+ positioner: {
1997
+ position: 'fixed',
1998
+ inset: 0,
1999
+ display: 'flex',
2000
+ alignItems: 'center',
2001
+ justifyContent: 'center',
2002
+ zIndex: 'modal',
2003
+ },
2004
+ content: {
2005
+ bg: 'surfaceContainerHigh',
2006
+ borderRadius: 'extraLarge',
2007
+ p: 'lg',
2008
+ shadow: 'level3',
2009
+ maxWidth: '560px',
2010
+ minWidth: '280px',
2011
+ },
2012
+ title: {
2013
+ textStyle: 'headlineSmall',
2014
+ color: 'onSurface',
2015
+ mb: 'md',
2016
+ },
2017
+ description: {
2018
+ textStyle: 'bodyMedium',
2019
+ color: 'onSurfaceVariant',
2020
+ },
2021
+ closeTrigger: {
2022
+ position: 'absolute',
2023
+ top: 'md',
2024
+ right: 'md',
2025
+ },
2026
+ },
2027
+ variants: {
2028
+ size: {
2029
+ sm: {
2030
+ content: { maxWidth: '400px' },
2031
+ },
2032
+ md: {
2033
+ content: { maxWidth: '560px' },
2034
+ },
2035
+ lg: {
2036
+ content: { maxWidth: '720px' },
2037
+ },
2038
+ fullscreen: {
2039
+ content: {
2040
+ maxWidth: '100vw',
2041
+ maxHeight: '100vh',
2042
+ borderRadius: 'none',
2043
+ m: 0,
2044
+ },
2045
+ },
2046
+ },
2047
+ },
2048
+ defaultVariants: {
2049
+ size: 'md',
2050
+ },
2051
+ });
2052
+ ```
2053
+
2054
+ ```typescript
2055
+ // src/components/Dialog/Dialog.tsx
2056
+ import { forwardRef } from 'react';
2057
+ import { Dialog as ArkDialog } from '@ark-ui/react';
2058
+ import { dialog, type DialogVariantProps } from 'styled-system/recipes';
2059
+ import { cn } from '../../utils/cn';
2060
+
2061
+ const styles = dialog();
2062
+
2063
+ export interface DialogProps extends ArkDialog.RootProps, DialogVariantProps {}
2064
+
2065
+ const DialogRoot = (props: DialogProps) => <ArkDialog.Root {...props} />;
2066
+
2067
+ const DialogTrigger = forwardRef<HTMLButtonElement, ArkDialog.TriggerProps>(
2068
+ ({ className, ...props }, ref) => (
2069
+ <ArkDialog.Trigger ref={ref} className={className} {...props} />
2070
+ )
2071
+ );
2072
+ DialogTrigger.displayName = 'DialogTrigger';
2073
+
2074
+ const DialogBackdrop = forwardRef<HTMLDivElement, ArkDialog.BackdropProps>(
2075
+ ({ className, ...props }, ref) => (
2076
+ <ArkDialog.Backdrop ref={ref} className={cn(styles.backdrop, className)} {...props} />
2077
+ )
2078
+ );
2079
+ DialogBackdrop.displayName = 'DialogBackdrop';
2080
+
2081
+ const DialogPositioner = forwardRef<HTMLDivElement, ArkDialog.PositionerProps>(
2082
+ ({ className, ...props }, ref) => (
2083
+ <ArkDialog.Positioner ref={ref} className={cn(styles.positioner, className)} {...props} />
2084
+ )
2085
+ );
2086
+ DialogPositioner.displayName = 'DialogPositioner';
2087
+
2088
+ const DialogContent = forwardRef<HTMLDivElement, ArkDialog.ContentProps>(
2089
+ ({ className, ...props }, ref) => (
2090
+ <ArkDialog.Content ref={ref} className={cn(styles.content, className)} {...props} />
2091
+ )
2092
+ );
2093
+ DialogContent.displayName = 'DialogContent';
2094
+
2095
+ const DialogTitle = forwardRef<HTMLHeadingElement, ArkDialog.TitleProps>(
2096
+ ({ className, ...props }, ref) => (
2097
+ <ArkDialog.Title ref={ref} className={cn(styles.title, className)} {...props} />
2098
+ )
2099
+ );
2100
+ DialogTitle.displayName = 'DialogTitle';
2101
+
2102
+ const DialogDescription = forwardRef<HTMLParagraphElement, ArkDialog.DescriptionProps>(
2103
+ ({ className, ...props }, ref) => (
2104
+ <ArkDialog.Description ref={ref} className={cn(styles.description, className)} {...props} />
2105
+ )
2106
+ );
2107
+ DialogDescription.displayName = 'DialogDescription';
2108
+
2109
+ const DialogCloseTrigger = forwardRef<HTMLButtonElement, ArkDialog.CloseTriggerProps>(
2110
+ ({ className, ...props }, ref) => (
2111
+ <ArkDialog.CloseTrigger ref={ref} className={cn(styles.closeTrigger, className)} {...props} />
2112
+ )
2113
+ );
2114
+ DialogCloseTrigger.displayName = 'DialogCloseTrigger';
2115
+
2116
+ export const Dialog = {
2117
+ Root: DialogRoot,
2118
+ Trigger: DialogTrigger,
2119
+ Backdrop: DialogBackdrop,
2120
+ Positioner: DialogPositioner,
2121
+ Content: DialogContent,
2122
+ Title: DialogTitle,
2123
+ Description: DialogDescription,
2124
+ CloseTrigger: DialogCloseTrigger,
2125
+ };
2126
+ ```
2127
+
2128
+ #### 4.3 Register All Recipes
2129
+ ```typescript
2130
+ // panda.config.ts - update theme.extend.recipes
2131
+ recipes: {
2132
+ button: buttonRecipe,
2133
+ card: cardRecipe,
2134
+ iconButton: iconButtonRecipe,
2135
+ input: inputRecipe,
2136
+ },
2137
+ slotRecipes: {
2138
+ dialog: dialogRecipe,
2139
+ },
2140
+ ```
2141
+
2142
+ #### 4.4 Component Testing Requirements
2143
+
2144
+ Every component MUST have accompanying tests. Create test files alongside components.
2145
+
2146
+ **Install test dependencies:**
2147
+ ```bash
2148
+ pnpm add -D @testing-library/react @testing-library/jest-dom @testing-library/user-event jest-axe
2149
+ ```
2150
+
2151
+ **Test Setup:**
2152
+ ```typescript
2153
+ // src/test/setup.ts
2154
+ import '@testing-library/jest-dom';
2155
+ import { vi } from 'vitest';
2156
+
2157
+ // Mock matchMedia for components that use media queries
2158
+ Object.defineProperty(window, 'matchMedia', {
2159
+ writable: true,
2160
+ value: vi.fn().mockImplementation(query => ({
2161
+ matches: false,
2162
+ media: query,
2163
+ onchange: null,
2164
+ addListener: vi.fn(),
2165
+ removeListener: vi.fn(),
2166
+ addEventListener: vi.fn(),
2167
+ removeEventListener: vi.fn(),
2168
+ dispatchEvent: vi.fn(),
2169
+ })),
2170
+ });
2171
+ ```
2172
+
2173
+ **Input Component Tests:**
2174
+ ```typescript
2175
+ // src/components/Input/Input.test.tsx
2176
+ import { render, screen } from '@testing-library/react';
2177
+ import userEvent from '@testing-library/user-event';
2178
+ import { describe, it, expect, vi } from 'vitest';
2179
+ import { Input } from './Input';
2180
+
2181
+ describe('Input', () => {
2182
+ it('renders with label', () => {
2183
+ render(<Input label="Email" />);
2184
+ expect(screen.getByLabelText('Email')).toBeInTheDocument();
2185
+ });
2186
+
2187
+ it('shows error state', () => {
2188
+ render(<Input label="Email" errorText="Invalid email" />);
2189
+ expect(screen.getByText('Invalid email')).toBeInTheDocument();
2190
+ });
2191
+
2192
+ it('calls onChange when typing', async () => {
2193
+ const user = userEvent.setup();
2194
+ const handleChange = vi.fn();
2195
+ render(<Input label="Email" onChange={handleChange} />);
2196
+
2197
+ await user.type(screen.getByLabelText('Email'), 'test@example.com');
2198
+ expect(handleChange).toHaveBeenCalled();
2199
+ });
2200
+
2201
+ it('renders all variants', () => {
2202
+ const { rerender } = render(<Input variant="outlined" label="Test" />);
2203
+ expect(screen.getByLabelText('Test')).toBeInTheDocument();
2204
+
2205
+ rerender(<Input variant="filled" label="Test" />);
2206
+ expect(screen.getByLabelText('Test')).toBeInTheDocument();
2207
+ });
2208
+
2209
+ it('is disabled when disabled prop is true', () => {
2210
+ render(<Input label="Email" disabled />);
2211
+ expect(screen.getByLabelText('Email')).toBeDisabled();
2212
+ });
2213
+ });
2214
+ ```
2215
+
2216
+ **Dialog Component Tests:**
2217
+ ```typescript
2218
+ // src/components/Dialog/Dialog.test.tsx
2219
+ import { render, screen, waitFor } from '@testing-library/react';
2220
+ import userEvent from '@testing-library/user-event';
2221
+ import { describe, it, expect } from 'vitest';
2222
+ import { Dialog } from './Dialog';
2223
+ import { Button } from '../Button';
2224
+
2225
+ describe('Dialog', () => {
2226
+ it('opens when trigger is clicked', async () => {
2227
+ const user = userEvent.setup();
2228
+ render(
2229
+ <Dialog.Root>
2230
+ <Dialog.Trigger asChild>
2231
+ <Button>Open</Button>
2232
+ </Dialog.Trigger>
2233
+ <Dialog.Backdrop />
2234
+ <Dialog.Positioner>
2235
+ <Dialog.Content>
2236
+ <Dialog.Title>Test Dialog</Dialog.Title>
2237
+ <Dialog.Description>Test content</Dialog.Description>
2238
+ </Dialog.Content>
2239
+ </Dialog.Positioner>
2240
+ </Dialog.Root>
2241
+ );
2242
+
2243
+ await user.click(screen.getByText('Open'));
2244
+ await waitFor(() => {
2245
+ expect(screen.getByText('Test Dialog')).toBeInTheDocument();
2246
+ });
2247
+ });
2248
+
2249
+ it('closes when close trigger is clicked', async () => {
2250
+ const user = userEvent.setup();
2251
+ render(
2252
+ <Dialog.Root defaultOpen>
2253
+ <Dialog.Backdrop />
2254
+ <Dialog.Positioner>
2255
+ <Dialog.Content>
2256
+ <Dialog.Title>Test Dialog</Dialog.Title>
2257
+ <Dialog.CloseTrigger>Close</Dialog.CloseTrigger>
2258
+ </Dialog.Content>
2259
+ </Dialog.Positioner>
2260
+ </Dialog.Root>
2261
+ );
2262
+
2263
+ await user.click(screen.getByText('Close'));
2264
+ await waitFor(() => {
2265
+ expect(screen.queryByText('Test Dialog')).not.toBeInTheDocument();
2266
+ });
2267
+ });
2268
+
2269
+ it('traps focus within dialog', async () => {
2270
+ const user = userEvent.setup();
2271
+ render(
2272
+ <Dialog.Root defaultOpen>
2273
+ <Dialog.Backdrop />
2274
+ <Dialog.Positioner>
2275
+ <Dialog.Content>
2276
+ <Dialog.Title>Test Dialog</Dialog.Title>
2277
+ <Button>First</Button>
2278
+ <Button>Second</Button>
2279
+ <Dialog.CloseTrigger>Close</Dialog.CloseTrigger>
2280
+ </Dialog.Content>
2281
+ </Dialog.Positioner>
2282
+ </Dialog.Root>
2283
+ );
2284
+
2285
+ await user.tab();
2286
+ expect(screen.getByText('First')).toHaveFocus();
2287
+
2288
+ await user.tab();
2289
+ expect(screen.getByText('Second')).toHaveFocus();
2290
+ });
2291
+ });
2292
+ ```
2293
+
2294
+ **Validation:**
2295
+ ```bash
2296
+ pnpm build:panda # ✅ All recipes generate
2297
+ pnpm dev # ✅ All components render in Storybook
2298
+ pnpm test # ✅ All component tests pass
2299
+ ```
2300
+
2301
+ ---
2302
+
2303
+ ### Phase 5: Ark UI Integration + Accessibility Testing
2304
+
2305
+ **Goal:** Ensure all components properly leverage Ark UI for accessibility and state management, with comprehensive accessibility tests.
2306
+
2307
+ **Tasks:**
2308
+
2309
+ #### 5.1 Audit Current Components
2310
+ Review each component and ensure it uses Ark UI primitives where applicable:
2311
+
2312
+ | Component | Current | Target |
2313
+ |-----------|---------|--------|
2314
+ | Button | `<button>` | `<Ark.Button>` or keep native (simple) |
2315
+ | Card | `<div>` | Keep native (no interaction state) |
2316
+ | IconButton | `<button>` | `<Ark.Button>` |
2317
+ | Input | Native | `<Field.Root>`, `<Field.Input>`, etc. |
2318
+ | Dialog | N/A | Full Ark Dialog compound |
2319
+
2320
+ #### 5.2 Implement Missing Ark UI Patterns
2321
+
2322
+ **Button with Ark UI:**
2323
+ ```typescript
2324
+ // src/components/Button/Button.tsx
2325
+ import { Button as ArkButton } from '@ark-ui/react';
2326
+
2327
+ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
2328
+ ({ children, variant, size, leftIcon, rightIcon, className, ...props }, ref) => {
2329
+ return (
2330
+ <ArkButton
2331
+ ref={ref}
2332
+ className={cn(button({ variant, size }), className)}
2333
+ {...props}
2334
+ >
2335
+ {leftIcon}
2336
+ {children}
2337
+ {rightIcon}
2338
+ </ArkButton>
2339
+ );
2340
+ }
2341
+ );
2342
+ ```
2343
+
2344
+ #### 5.3 Add Additional Ark UI Components
2345
+
2346
+ Consider adding these M3 components using Ark UI:
2347
+ - **Menu** - `@ark-ui/react` Menu
2348
+ - **Tabs** - `@ark-ui/react` Tabs
2349
+ - **Tooltip** - `@ark-ui/react` Tooltip
2350
+ - **Switch** - `@ark-ui/react` Switch
2351
+ - **Checkbox** - `@ark-ui/react` Checkbox
2352
+ - **Select** - `@ark-ui/react` Select
2353
+
2354
+ #### 5.4 Accessibility Testing
2355
+
2356
+ Every component MUST include accessibility tests using jest-axe:
2357
+
2358
+ ```typescript
2359
+ // src/components/Button/Button.a11y.test.tsx
2360
+ import { axe, toHaveNoViolations } from 'jest-axe';
2361
+ import { render } from '@testing-library/react';
2362
+ import { Button } from './Button';
2363
+
2364
+ expect.extend(toHaveNoViolations);
2365
+
2366
+ describe('Button accessibility', () => {
2367
+ it('has no accessibility violations', async () => {
2368
+ const { container } = render(<Button>Click me</Button>);
2369
+ const results = await axe(container);
2370
+ expect(results).toHaveNoViolations();
2371
+ });
2372
+
2373
+ it('has no violations when disabled', async () => {
2374
+ const { container } = render(<Button disabled>Disabled</Button>);
2375
+ const results = await axe(container);
2376
+ expect(results).toHaveNoViolations();
2377
+ });
2378
+
2379
+ it('has no violations for all variants', async () => {
2380
+ const variants = ['filled', 'outlined', 'text', 'elevated', 'tonal'] as const;
2381
+
2382
+ for (const variant of variants) {
2383
+ const { container } = render(<Button variant={variant}>Test</Button>);
2384
+ const results = await axe(container);
2385
+ expect(results).toHaveNoViolations();
2386
+ }
2387
+ });
2388
+ });
2389
+ ```
2390
+
2391
+ ```typescript
2392
+ // src/components/Dialog/Dialog.a11y.test.tsx
2393
+ import { axe, toHaveNoViolations } from 'jest-axe';
2394
+ import { render, screen, waitFor } from '@testing-library/react';
2395
+ import userEvent from '@testing-library/user-event';
2396
+ import { Dialog } from './Dialog';
2397
+ import { Button } from '../Button';
2398
+
2399
+ expect.extend(toHaveNoViolations);
2400
+
2401
+ describe('Dialog accessibility', () => {
2402
+ it('has no accessibility violations when open', async () => {
2403
+ const { container } = render(
2404
+ <Dialog.Root defaultOpen>
2405
+ <Dialog.Backdrop />
2406
+ <Dialog.Positioner>
2407
+ <Dialog.Content>
2408
+ <Dialog.Title>Accessible Dialog</Dialog.Title>
2409
+ <Dialog.Description>This dialog should be accessible.</Dialog.Description>
2410
+ <Dialog.CloseTrigger>Close</Dialog.CloseTrigger>
2411
+ </Dialog.Content>
2412
+ </Dialog.Positioner>
2413
+ </Dialog.Root>
2414
+ );
2415
+
2416
+ const results = await axe(container);
2417
+ expect(results).toHaveNoViolations();
2418
+ });
2419
+
2420
+ it('has proper aria attributes', async () => {
2421
+ render(
2422
+ <Dialog.Root defaultOpen>
2423
+ <Dialog.Backdrop />
2424
+ <Dialog.Positioner>
2425
+ <Dialog.Content>
2426
+ <Dialog.Title>Test Title</Dialog.Title>
2427
+ <Dialog.Description>Test Description</Dialog.Description>
2428
+ </Dialog.Content>
2429
+ </Dialog.Positioner>
2430
+ </Dialog.Root>
2431
+ );
2432
+
2433
+ const dialog = screen.getByRole('dialog');
2434
+ expect(dialog).toHaveAttribute('aria-labelledby');
2435
+ expect(dialog).toHaveAttribute('aria-describedby');
2436
+ });
2437
+ });
2438
+ ```
2439
+
2440
+ **Validation:**
2441
+ ```bash
2442
+ pnpm dev # ✅ Components work correctly
2443
+ # Test keyboard navigation in Storybook
2444
+ # Test screen reader announcements
2445
+ pnpm test # ✅ All tests pass (unit + a11y)
2446
+ ```
2447
+
2448
+ ---
2449
+
2450
+ ### Phase 6: Build & Package (Figma Make Compatible)
2451
+
2452
+ **Goal:** Create a publishable npm package compatible with Figma Make (Vite-based) with proper exports, types, and documentation.
2453
+
2454
+ **Requirements from Figma Make (https://developers.figma.com/docs/code/bring-your-design-system-package/):**
2455
+ - ✅ React 18+ (we use React 19)
2456
+ - ✅ Compatible with Vite
2457
+ - ✅ Published as npm package (public or private)
2458
+
2459
+ **Tasks:**
2460
+
2461
+ #### 6.1 Verify Vite Compatibility
2462
+
2463
+ Figma Make uses Vite as its build system. Test package compatibility:
2464
+
2465
+ ```bash
2466
+ # Create a test Vite project
2467
+ npm create vite@latest make-test-app -- --template react-ts
2468
+ cd make-test-app
2469
+
2470
+ # Install your local package
2471
+ pnpm add ../path/to/tastymakers-design-system-0.1.0.tgz
2472
+
2473
+ # Test the import
2474
+ cat > src/App.tsx << 'EOF'
2475
+ import { Button, Card } from '@discourser/design-system';
2476
+ import '@discourser/design-system/styles.css';
2477
+
2478
+ function App() {
2479
+ return (
2480
+ <Card>
2481
+ <Button variant="filled">Test Button</Button>
2482
+ </Card>
2483
+ );
2484
+ }
2485
+ export default App;
2486
+ EOF
2487
+
2488
+ # Build - must succeed for Figma Make compatibility
2489
+ pnpm build
2490
+ ```
2491
+
2492
+ #### 6.2 Verify tsup Configuration
2493
+ ```typescript
2494
+ // tsup.config.ts
2495
+ import { defineConfig } from 'tsup';
2496
+
2497
+ export default defineConfig({
2498
+ entry: ['src/index.ts'],
2499
+ format: ['esm', 'cjs'],
2500
+ dts: true,
2501
+ splitting: true,
2502
+ sourcemap: true,
2503
+ clean: true,
2504
+ external: ['react', 'react-dom'],
2505
+ esbuildOptions(options) {
2506
+ options.banner = {
2507
+ js: '"use client"',
2508
+ };
2509
+ },
2510
+ });
2511
+ ```
2512
+
2513
+ #### 6.3 Verify Package Exports
2514
+ ```json
2515
+ // package.json exports field
2516
+ {
2517
+ "exports": {
2518
+ ".": {
2519
+ "import": "./dist/index.js",
2520
+ "require": "./dist/index.cjs",
2521
+ "types": "./dist/index.d.ts"
2522
+ },
2523
+ "./styles.css": "./styled-system/styles.css",
2524
+ "./styled-system": {
2525
+ "import": "./styled-system/index.mjs",
2526
+ "require": "./styled-system/index.js"
2527
+ },
2528
+ "./styled-system/css": {
2529
+ "import": "./styled-system/css/index.mjs",
2530
+ "require": "./styled-system/css/index.js"
2531
+ },
2532
+ "./styled-system/tokens": {
2533
+ "import": "./styled-system/tokens/index.mjs",
2534
+ "require": "./styled-system/tokens/index.js"
2535
+ },
2536
+ "./styled-system/recipes": {
2537
+ "import": "./styled-system/recipes/index.mjs",
2538
+ "require": "./styled-system/recipes/index.js"
2539
+ }
2540
+ }
2541
+ }
2542
+ ```
2543
+
2544
+ #### 6.4 Add CI/CD Workflows
2545
+
2546
+ ```yaml
2547
+ # .github/workflows/ci.yml
2548
+ name: CI
2549
+
2550
+ on:
2551
+ push:
2552
+ branches: [main]
2553
+ pull_request:
2554
+ branches: [main]
2555
+
2556
+ jobs:
2557
+ build:
2558
+ runs-on: ubuntu-latest
2559
+ steps:
2560
+ - uses: actions/checkout@v4
2561
+ - uses: pnpm/action-setup@v2
2562
+ with:
2563
+ version: 8
2564
+ - uses: actions/setup-node@v4
2565
+ with:
2566
+ node-version: 20
2567
+ cache: 'pnpm'
2568
+ - run: pnpm install
2569
+ - run: pnpm typecheck
2570
+ - run: pnpm build:panda
2571
+ - run: pnpm build
2572
+ - run: pnpm test
2573
+
2574
+ vite-compatibility:
2575
+ runs-on: ubuntu-latest
2576
+ needs: build
2577
+ steps:
2578
+ - uses: actions/checkout@v4
2579
+ - uses: pnpm/action-setup@v2
2580
+ with:
2581
+ version: 8
2582
+ - uses: actions/setup-node@v4
2583
+ with:
2584
+ node-version: 20
2585
+ cache: 'pnpm'
2586
+ - run: pnpm install
2587
+ - run: pnpm build
2588
+ - run: pnpm pack
2589
+ - name: Test Vite Compatibility
2590
+ run: |
2591
+ npm create vite@latest test-app -- --template react-ts
2592
+ cd test-app
2593
+ npm install ../tastymakers-design-system-*.tgz
2594
+ npm run build
2595
+ ```
2596
+
2597
+ ```yaml
2598
+ # .github/workflows/publish.yml
2599
+ name: Publish
2600
+
2601
+ on:
2602
+ release:
2603
+ types: [created]
2604
+
2605
+ jobs:
2606
+ publish:
2607
+ runs-on: ubuntu-latest
2608
+ steps:
2609
+ - uses: actions/checkout@v4
2610
+ - uses: pnpm/action-setup@v2
2611
+ with:
2612
+ version: 8
2613
+ - uses: actions/setup-node@v4
2614
+ with:
2615
+ node-version: 20
2616
+ registry-url: 'https://registry.npmjs.org'
2617
+ - run: pnpm install
2618
+ - run: pnpm build
2619
+ - run: pnpm publish --access public
2620
+ env:
2621
+ NODE_AUTH_TOKEN: ${{ secrets.
2622
+ }}
2623
+ ```
2624
+
2625
+ #### 6.5 Create npm Organization
2626
+
2627
+ Before publishing, create the `@discourser` npm organization:
2628
+
2629
+ 1. Go to https://www.npmjs.com/org/create
2630
+ 2. Create organization with name `discourser`
2631
+ 3. Choose "Unlimited public packages" (free tier)
2632
+ 4. Verify organization exists at https://www.npmjs.com/org/discourser
2633
+
2634
+ **Note:** npm allows multiple organizations per user account.
2635
+
2636
+ #### 6.6 Configure npm Authentication Locally
2637
+
2638
+ ```bash
2639
+ # Login to npm (if not already)
2640
+ npm login
2641
+
2642
+ # Verify you can publish to the org
2643
+ npm access ls-packages @discourser
2644
+ ```
2645
+
2646
+ #### 6.7 Install & Configure Changesets
2647
+
2648
+ Changesets provides automated versioning and changelog generation.
2649
+
2650
+ **Install dependencies:**
2651
+ ```bash
2652
+ pnpm add -D @changesets/cli @changesets/changelog-github
2653
+ pnpm changeset init
2654
+ ```
2655
+
2656
+ **Configure `.changeset/config.json`:**
2657
+ ```json
2658
+ {
2659
+ "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
2660
+ "changelog": [
2661
+ "@changesets/changelog-github",
2662
+ { "repo": "Tasty-Maker-Studio/Discourser-Design-System" }
2663
+ ],
2664
+ "commit": false,
2665
+ "fixed": [],
2666
+ "linked": [],
2667
+ "access": "public",
2668
+ "baseBranch": "main",
2669
+ "updateInternalDependencies": "patch",
2670
+ "ignore": []
2671
+ }
2672
+ ```
2673
+
2674
+ **Create `.changeset/README.md`:**
2675
+ ```markdown
2676
+ # Changesets
2677
+
2678
+ Hello and welcome! This folder has been automatically generated by `@changesets/cli`.
2679
+
2680
+ ## How to add a changeset
2681
+
2682
+ 1. Run `pnpm changeset`
2683
+ 2. Select the type of change (patch/minor/major)
2684
+ 3. Write a summary of the change
2685
+ 4. Commit the generated changeset file
2686
+
2687
+ Changesets are automatically consumed when the "Version Packages" PR is merged.
2688
+ ```
2689
+
2690
+ **Create `CHANGELOG.md` (root):**
2691
+ ```markdown
2692
+ # @discourser/design-system
2693
+
2694
+ ## Changelog
2695
+
2696
+ All notable changes to this project will be documented in this file.
2697
+
2698
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
2699
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
2700
+ ```
2701
+
2702
+ **Update `package.json`:**
2703
+ ```json
2704
+ {
2705
+ "name": "@discourser/design-system",
2706
+ "version": "0.0.0",
2707
+ "publishConfig": {
2708
+ "access": "public",
2709
+ "registry": "https://registry.npmjs.org"
2710
+ },
2711
+ "repository": {
2712
+ "type": "git",
2713
+ "url": "https://github.com/Tasty-Maker-Studio/Discourser-Design-System.git"
2714
+ },
2715
+ "scripts": {
2716
+ "changeset": "changeset",
2717
+ "version": "changeset version",
2718
+ "release": "pnpm build && changeset publish"
2719
+ }
2720
+ }
2721
+ ```
2722
+
2723
+ **Validation:**
2724
+ ```bash
2725
+ pnpm changeset # ✅ Interactive prompt works
2726
+ pnpm changeset status # ✅ Shows no changesets (or lists pending)
2727
+ ```
2728
+
2729
+ #### 6.8 Create CI Workflow
2730
+
2731
+ Create `.github/workflows/ci.yml`:
2732
+
2733
+ ```yaml
2734
+ name: CI
2735
+
2736
+ on:
2737
+ push:
2738
+ branches: [main]
2739
+ pull_request:
2740
+ branches: [main]
2741
+
2742
+ concurrency:
2743
+ group: ${{ github.workflow }}-${{ github.ref }}
2744
+ cancel-in-progress: true
2745
+
2746
+ jobs:
2747
+ build:
2748
+ name: Build & Test
2749
+ runs-on: ubuntu-latest
2750
+ steps:
2751
+ - name: Checkout
2752
+ uses: actions/checkout@v4
2753
+
2754
+ - name: Setup pnpm
2755
+ uses: pnpm/action-setup@v2
2756
+ with:
2757
+ version: 9
2758
+
2759
+ - name: Setup Node.js
2760
+ uses: actions/setup-node@v4
2761
+ with:
2762
+ node-version: 20
2763
+ cache: 'pnpm'
2764
+
2765
+ - name: Install dependencies
2766
+ run: pnpm install --frozen-lockfile
2767
+
2768
+ - name: Typecheck
2769
+ run: pnpm typecheck
2770
+
2771
+ - name: Build Panda CSS
2772
+ run: pnpm build:panda
2773
+
2774
+ - name: Build library
2775
+ run: pnpm build:lib
2776
+
2777
+ - name: Run tests
2778
+ run: pnpm test
2779
+
2780
+ - name: Upload build artifacts
2781
+ uses: actions/upload-artifact@v4
2782
+ with:
2783
+ name: build-output
2784
+ path: |
2785
+ dist/
2786
+ styled-system/
2787
+ retention-days: 1
2788
+
2789
+ vite-compatibility:
2790
+ name: Vite Compatibility
2791
+ runs-on: ubuntu-latest
2792
+ needs: build
2793
+ steps:
2794
+ - name: Checkout
2795
+ uses: actions/checkout@v4
2796
+
2797
+ - name: Setup pnpm
2798
+ uses: pnpm/action-setup@v2
2799
+ with:
2800
+ version: 9
2801
+
2802
+ - name: Setup Node.js
2803
+ uses: actions/setup-node@v4
2804
+ with:
2805
+ node-version: 20
2806
+ cache: 'pnpm'
2807
+
2808
+ - name: Install dependencies
2809
+ run: pnpm install --frozen-lockfile
2810
+
2811
+ - name: Build package
2812
+ run: pnpm build
2813
+
2814
+ - name: Create tarball
2815
+ run: pnpm pack
2816
+
2817
+ - name: Test Vite compatibility
2818
+ run: |
2819
+ # Create test Vite app
2820
+ npm create vite@latest test-app -- --template react-ts
2821
+ cd test-app
2822
+
2823
+ # Install the local package
2824
+ npm install ../discourser-design-system-*.tgz
2825
+
2826
+ # Create test file
2827
+ cat > src/App.tsx << 'EOF'
2828
+ import { Button } from '@discourser/design-system';
2829
+ import '@discourser/design-system/styles.css';
2830
+
2831
+ function App() {
2832
+ return <Button variant="filled">Test</Button>;
2833
+ }
2834
+ export default App;
2835
+ EOF
2836
+
2837
+ # Build must succeed
2838
+ npm run build
2839
+ ```
2840
+
2841
+ #### 6.9 Create Release Workflow
2842
+
2843
+ Create `.github/workflows/release.yml`:
2844
+
2845
+ ```yaml
2846
+ name: Release
2847
+
2848
+ on:
2849
+ push:
2850
+ branches: [main]
2851
+
2852
+ concurrency:
2853
+ group: ${{ github.workflow }}-${{ github.ref }}
2854
+ cancel-in-progress: false
2855
+
2856
+ permissions:
2857
+ contents: write
2858
+ pull-requests: write
2859
+
2860
+ jobs:
2861
+ release:
2862
+ name: Release
2863
+ runs-on: ubuntu-latest
2864
+ steps:
2865
+ - name: Checkout
2866
+ uses: actions/checkout@v4
2867
+ with:
2868
+ fetch-depth: 0
2869
+
2870
+ - name: Setup pnpm
2871
+ uses: pnpm/action-setup@v2
2872
+ with:
2873
+ version: 9
2874
+
2875
+ - name: Setup Node.js
2876
+ uses: actions/setup-node@v4
2877
+ with:
2878
+ node-version: 20
2879
+ cache: 'pnpm'
2880
+ registry-url: 'https://registry.npmjs.org'
2881
+
2882
+ - name: Install dependencies
2883
+ run: pnpm install --frozen-lockfile
2884
+
2885
+ - name: Build
2886
+ run: pnpm build
2887
+
2888
+ - name: Create Release Pull Request or Publish
2889
+ id: changesets
2890
+ uses: changesets/action@v1
2891
+ with:
2892
+ version: pnpm changeset version
2893
+ publish: pnpm release
2894
+ commit: "chore: version packages"
2895
+ title: "chore: version packages"
2896
+ env:
2897
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2898
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
2899
+
2900
+ - name: Create Git Tag
2901
+ if: steps.changesets.outputs.published == 'true'
2902
+ run: |
2903
+ git config user.name "github-actions[bot]"
2904
+ git config user.email "github-actions[bot]@users.noreply.github.com"
2905
+
2906
+ # Get the version from package.json
2907
+ VERSION=$(node -p "require('./package.json').version")
2908
+
2909
+ # Create and push tag
2910
+ git tag -a "v$VERSION" -m "Release v$VERSION"
2911
+ git push origin "v$VERSION"
2912
+ ```
2913
+
2914
+ #### 6.10 Configure GitHub Secrets & Permissions (MANUAL)
2915
+
2916
+ This step requires manual configuration in the GitHub repository settings.
2917
+
2918
+ **Step 1: Generate npm Token**
2919
+
2920
+ 1. Go to https://www.npmjs.com/settings/YOUR_USERNAME/tokens
2921
+ 2. Click "Generate New Token" → "Classic Token"
2922
+ 3. Select "Automation" type
2923
+ 4. Copy the generated token (starts with `npm_`)
2924
+
2925
+ **Step 2: Add Secret to GitHub**
2926
+
2927
+ 1. Go to repository: https://github.com/Tasty-Maker-Studio/Discourser-Design-System
2928
+ 2. Navigate to Settings → Secrets and variables → Actions
2929
+ 3. Click "New repository secret"
2930
+ 4. Name: `NPM_TOKEN`
2931
+ 5. Value: Paste the npm token
2932
+ 6. Click "Add secret"
2933
+
2934
+ **Step 3: Configure Workflow Permissions**
2935
+
2936
+ 1. Go to Settings → Actions → General
2937
+ 2. Scroll to "Workflow permissions"
2938
+ 3. Select "Read and write permissions"
2939
+ 4. Check "Allow GitHub Actions to create and approve pull requests"
2940
+ 5. Click "Save"
2941
+
2942
+ **Validation Checklist:**
2943
+ - [ ] NPM_TOKEN secret is configured
2944
+ - [ ] Workflow permissions allow write access
2945
+ - [ ] Workflow permissions allow PR creation
2946
+
2947
+ #### 6.11 Validate Full Release Pipeline
2948
+
2949
+ Test the complete release workflow end-to-end.
2950
+
2951
+ **Step 1: Create a Test Changeset**
2952
+ ```bash
2953
+ pnpm changeset
2954
+
2955
+ # Select: patch
2956
+ # Summary: "Initial release - Button, Card, IconButton components"
2957
+ # This creates a file in .changeset/ like `funny-dogs-dance.md`
2958
+ ```
2959
+
2960
+ **Step 2: Commit and Push**
2961
+ ```bash
2962
+ git add .
2963
+ git commit -m "chore: add changeset for initial release"
2964
+ git push origin main
2965
+ ```
2966
+
2967
+ **Step 3: Verify CI Workflow**
2968
+ 1. Go to https://github.com/Tasty-Maker-Studio/Discourser-Design-System/actions
2969
+ 2. Verify "CI" workflow passes (build, test, vite-compatibility)
2970
+
2971
+ **Step 4: Verify Release Workflow**
2972
+ 1. After CI passes, "Release" workflow should run
2973
+ 2. It should create a PR titled "chore: version packages"
2974
+
2975
+ **Step 5: Review Version Packages PR**
2976
+ The PR should contain:
2977
+ - `package.json`: version bumped from `0.0.0` to `0.0.1`
2978
+ - `CHANGELOG.md`: Updated with changeset content
2979
+ - Changeset file deleted from `.changeset/`
2980
+
2981
+ **Step 6: Merge and Publish**
2982
+ 1. Review the PR
2983
+ 2. Merge to main
2984
+ 3. Release workflow runs again and publishes to npm
2985
+
2986
+ **Step 7: Verify npm Publication**
2987
+ ```bash
2988
+ # Check package exists
2989
+ npm view @discourser/design-system
2990
+
2991
+ # Or visit
2992
+ # https://www.npmjs.com/package/@discourser/design-system
2993
+ ```
2994
+
2995
+ **Complete Release Flow Diagram:**
2996
+ ```
2997
+ Developer: pnpm changeset → Creates .changeset/*.md
2998
+
2999
+ Git push to main
3000
+
3001
+ CI Workflow: Build, Test, Vite Compatibility
3002
+
3003
+ Release Workflow: Detects changesets → Creates "Version Packages" PR
3004
+
3005
+ Human: Reviews and merges PR
3006
+
3007
+ Release Workflow: Runs again → Publishes to npm + Creates git tag
3008
+
3009
+ Package available at npmjs.com
3010
+ ```
3011
+
3012
+ **Validation:**
3013
+ ```bash
3014
+ # After full pipeline test:
3015
+ npm view @discourser/design-system # ✅ Package exists
3016
+ npm view @discourser/design-system versions # ✅ Shows 0.0.1
3017
+ ```
3018
+
3019
+ #### 6.12 Test Local Package (Manual Verification)
3020
+
3021
+ For local testing before committing:
3022
+
3023
+ ```bash
3024
+ # Build the package
3025
+ pnpm build
3026
+
3027
+ # Create a tarball
3028
+ pnpm pack
3029
+
3030
+ # In a separate test Vite project
3031
+ npm create vite@latest make-test-app -- --template react-ts
3032
+ cd make-test-app
3033
+ pnpm add ../path/to/discourser-design-system-0.0.1.tgz
3034
+ pnpm build # Must succeed for Figma Make compatibility
3035
+ ```
3036
+
3037
+ **Phase 6 Validation Summary:**
3038
+ ```bash
3039
+ pnpm build # ✅ Creates dist/ with ESM, CJS, and .d.ts
3040
+ pnpm pack # ✅ Creates tarball
3041
+ pnpm test # ✅ All tests pass
3042
+ pnpm changeset # ✅ Interactive prompt works
3043
+ # CI workflow passes # ✅ Build + Test + Vite compatibility
3044
+ # Release workflow # ✅ Creates Version Packages PR
3045
+ # npm publish # ✅ Package available on npmjs.com
3046
+ ```
3047
+
3048
+ ---
3049
+
3050
+ ### Phase 7: Figma Make Guidelines
3051
+
3052
+ **Goal:** Create guidelines that teach Figma Make how to use the design system package, following the [Figma documentation](https://developers.figma.com/docs/code/write-design-system-guidelines/).
3053
+
3054
+ **Background:** Figma Make's AI can inspect your package but works best with explicit guidelines. This is similar to documentation you'd give a new engineer.
3055
+
3056
+ **Tasks:**
3057
+
3058
+ #### 7.1 Create Guidelines Structure
3059
+
3060
+ ```
3061
+ guidelines/
3062
+ ├── Guidelines.md # Top-level entry point (ALWAYS read first)
3063
+ ├── overview-components.md # Component catalog and usage patterns
3064
+ ├── overview-icons.md # Icon usage (if applicable)
3065
+ ├── design-tokens/
3066
+ │ ├── colors.md # Semantic color tokens
3067
+ │ ├── typography.md # Typography scale
3068
+ │ ├── spacing.md # Spacing tokens
3069
+ │ └── elevation.md # Shadow/elevation tokens
3070
+ └── components/
3071
+ ├── button.md
3072
+ ├── card.md
3073
+ ├── icon-button.md
3074
+ ├── input.md
3075
+ ├── dialog.md
3076
+ └── [additional components].md
3077
+ ```
3078
+
3079
+ #### 7.2 Create Guidelines.md (Entry Point)
3080
+
3081
+ ```markdown
3082
+ <!-- guidelines/Guidelines.md -->
3083
+ # TastyMakers Design System Guidelines
3084
+
3085
+ This project uses the `@discourser/design-system` package, a Material Design 3 implementation built with Panda CSS and Ark UI.
3086
+
3087
+ ## IMPORTANT: Always Read These First
3088
+
3089
+ Before writing any code, follow these steps IN ORDER:
3090
+
3091
+ ### Step 1: Read Overview Files (REQUIRED)
3092
+ Read ALL files with a name that starts with "overview-":
3093
+ - `overview-components.md` - Available components and usage patterns
3094
+ - `overview-icons.md` - Icon usage (if applicable)
3095
+
3096
+ ### Step 2: Read Design Token Files (REQUIRED)
3097
+ Read ALL files in the `design-tokens/` folder:
3098
+ - `design-tokens/colors.md`
3099
+ - `design-tokens/typography.md`
3100
+ - `design-tokens/spacing.md`
3101
+ - `design-tokens/elevation.md`
3102
+
3103
+ ### Step 3: Plan Components Needed (REQUIRED)
3104
+ Identify which components you need to use.
3105
+
3106
+ ### Step 4: Read Component Guidelines BEFORE Using Components (REQUIRED)
3107
+ BEFORE using ANY component, you MUST read its guidelines file first:
3108
+ - Using Button? → Read `components/button.md` FIRST
3109
+ - Using Dialog? → Read `components/dialog.md` FIRST
3110
+ - Using Input? → Read `components/input.md` FIRST
3111
+ - Using Card? → Read `components/card.md` FIRST
3112
+
3113
+ DO NOT write code using a component until you have read its specific guidelines.
3114
+
3115
+ ## Core Principles
3116
+
3117
+ - **Always prefer design system components** over native HTML elements
3118
+ - **Use semantic tokens** (e.g., `primary`, `onPrimary`) not raw colors
3119
+ - **Follow M3 patterns** for variants, sizing, and state layers
3120
+ - **Do not override styles** unless absolutely necessary
3121
+
3122
+ ## Package Imports
3123
+
3124
+ \`\`\`typescript
3125
+ // Components
3126
+ import { Button, Card, Dialog, Input, IconButton } from '@discourser/design-system';
3127
+
3128
+ // Styles (REQUIRED - must be imported)
3129
+ import '@discourser/design-system/styles.css';
3130
+ \`\`\`
3131
+
3132
+ ## Quick Reference
3133
+
3134
+ | Component | Variants | Sizes | Guidelines |
3135
+ |-----------|----------|-------|------------|
3136
+ | Button | filled, outlined, text, elevated, tonal | sm, md, lg | `components/button.md` |
3137
+ | Card | elevated, filled, outlined | - | `components/card.md` |
3138
+ | IconButton | standard, filled, tonal, outlined | sm, md, lg | `components/icon-button.md` |
3139
+ | Input | filled, outlined | sm, md | `components/input.md` |
3140
+ | Dialog | - | sm, md, lg, fullscreen | `components/dialog.md` |
3141
+ ```
3142
+
3143
+ #### 7.3 Create overview-components.md
3144
+
3145
+ ```markdown
3146
+ <!-- guidelines/overview-components.md -->
3147
+ # Components Overview
3148
+
3149
+ Always prefer components from `@discourser/design-system` if available. Do not use native HTML elements when a design system component exists.
3150
+
3151
+ ## Available Components
3152
+
3153
+ | Component | Purpose | Guidelines |
3154
+ |-----------|---------|------------|
3155
+ | Button | Primary interactive element for actions | [button.md](components/button.md) |
3156
+ | Card | Container for related content | [card.md](components/card.md) |
3157
+ | IconButton | Icon-only interactive element | [icon-button.md](components/icon-button.md) |
3158
+ | Input | Text input with label and validation | [input.md](components/input.md) |
3159
+ | Dialog | Modal overlay for focused tasks | [dialog.md](components/dialog.md) |
3160
+
3161
+ ## Common Props
3162
+
3163
+ Most components accept:
3164
+ - `variant` - Visual style variant (e.g., filled, outlined)
3165
+ - `size` - Size variant (sm, md, lg)
3166
+ - `disabled` - Disable interaction
3167
+ - `className` - Additional CSS classes (use sparingly)
3168
+
3169
+ ## Styling Guidelines
3170
+
3171
+ **✅ DO:**
3172
+ \`\`\`typescript
3173
+ <Button variant="filled" size="md">Submit</Button>
3174
+ <Card variant="elevated">Content</Card>
3175
+ <Input variant="outlined" label="Email" />
3176
+ \`\`\`
3177
+
3178
+ **❌ DO NOT:**
3179
+ \`\`\`typescript
3180
+ // Don't override styles with inline styles
3181
+ <Button style={{ backgroundColor: 'blue' }}>Submit</Button>
3182
+
3183
+ // Don't use raw HTML when components exist
3184
+ <button className="...">Submit</button>
3185
+ <input type="text" />
3186
+
3187
+ // Don't use raw color values
3188
+ <div style={{ backgroundColor: '#4C662B' }}>...</div>
3189
+ \`\`\`
3190
+
3191
+ ## Controlled vs Uncontrolled
3192
+
3193
+ - **Controlled**: Component receives `value` and `onChange` props
3194
+ - **Uncontrolled**: Component manages own state, use `defaultValue`
3195
+ - Prefer controlled for form components
3196
+ ```
3197
+
3198
+ #### 7.4 Create Design Token Guidelines
3199
+
3200
+ ```markdown
3201
+ <!-- guidelines/design-tokens/colors.md -->
3202
+ # Color Tokens
3203
+
3204
+ The design system uses Material Design 3 semantic color tokens. Always use semantic tokens, never raw hex values.
3205
+
3206
+ ## Semantic Colors (Light Theme)
3207
+
3208
+ ### Primary
3209
+ | Token | Usage | Example Value |
3210
+ |-------|-------|---------------|
3211
+ | `primary` | Primary actions, buttons, links | #4C662B |
3212
+ | `onPrimary` | Text/icons on primary backgrounds | #FFFFFF |
3213
+ | `primaryContainer` | Primary container backgrounds | #CDEDA3 |
3214
+ | `onPrimaryContainer` | Text/icons on primary container | #354E16 |
3215
+
3216
+ ### Secondary
3217
+ | Token | Usage |
3218
+ |-------|-------|
3219
+ | `secondary` | Secondary actions |
3220
+ | `onSecondary` | Text/icons on secondary |
3221
+ | `secondaryContainer` | Secondary container backgrounds |
3222
+ | `onSecondaryContainer` | Text/icons on secondary container |
3223
+
3224
+ ### Surface
3225
+ | Token | Usage |
3226
+ |-------|-------|
3227
+ | `surface` | Default background |
3228
+ | `onSurface` | Default text color |
3229
+ | `surfaceVariant` | Alternate surface |
3230
+ | `onSurfaceVariant` | Text on variant surfaces |
3231
+ | `surfaceContainerLowest` | Lowest elevation |
3232
+ | `surfaceContainerLow` | Low elevation (cards) |
3233
+ | `surfaceContainer` | Default containers |
3234
+ | `surfaceContainerHigh` | Dialogs, elevated content |
3235
+ | `surfaceContainerHighest` | Highest elevation |
3236
+
3237
+ ### Error
3238
+ | Token | Usage |
3239
+ |-------|-------|
3240
+ | `error` | Error states |
3241
+ | `onError` | Text/icons on error |
3242
+ | `errorContainer` | Error container backgrounds |
3243
+ | `onErrorContainer` | Text/icons on error container |
3244
+
3245
+ ### Other
3246
+ | Token | Usage |
3247
+ |-------|-------|
3248
+ | `outline` | Borders, dividers |
3249
+ | `outlineVariant` | Subtle borders |
3250
+ | `scrim` | Modal overlays |
3251
+ | `shadow` | Shadow color |
3252
+
3253
+ ## Usage in Components
3254
+
3255
+ Colors are applied automatically through component variants:
3256
+
3257
+ \`\`\`typescript
3258
+ // ✅ Correct - uses semantic tokens internally
3259
+ <Button variant="filled">Primary Action</Button>
3260
+
3261
+ // ❌ Wrong - don't apply colors directly
3262
+ <Button style={{ backgroundColor: '#4C662B' }}>Primary</Button>
3263
+ \`\`\`
3264
+
3265
+ ## Dark Mode
3266
+
3267
+ The design system supports dark mode via `data-theme="dark"` on a parent element. Semantic tokens automatically adjust.
3268
+ ```
3269
+
3270
+ ```markdown
3271
+ <!-- guidelines/design-tokens/typography.md -->
3272
+ # Typography Tokens
3273
+
3274
+ The design system uses M3 typography scale with semantic naming.
3275
+
3276
+ ## Font Families
3277
+
3278
+ | Token | Font | Usage |
3279
+ |-------|------|-------|
3280
+ | `display` | Georgia, serif | Display text, headings |
3281
+ | `body` | Inter, sans-serif | Body text, UI elements |
3282
+ | `mono` | JetBrains Mono | Code snippets |
3283
+
3284
+ ## Type Scale
3285
+
3286
+ ### Display (for hero sections, large text)
3287
+ - `displayLarge` - 57px
3288
+ - `displayMedium` - 45px
3289
+ - `displaySmall` - 36px
3290
+
3291
+ ### Headline (for page/section headers)
3292
+ - `headlineLarge` - 32px
3293
+ - `headlineMedium` - 28px
3294
+ - `headlineSmall` - 24px
3295
+
3296
+ ### Title (for card titles, dialogs)
3297
+ - `titleLarge` - 22px
3298
+ - `titleMedium` - 16px
3299
+ - `titleSmall` - 14px
3300
+
3301
+ ### Body (for content)
3302
+ - `bodyLarge` - 16px (primary body text)
3303
+ - `bodyMedium` - 14px (default body text)
3304
+ - `bodySmall` - 12px (secondary text)
3305
+
3306
+ ### Label (for buttons, form labels)
3307
+ - `labelLarge` - 14px (button text)
3308
+ - `labelMedium` - 12px (form labels)
3309
+ - `labelSmall` - 11px (badges)
3310
+
3311
+ ## Usage
3312
+
3313
+ Typography is applied through `textStyle` in Panda CSS:
3314
+
3315
+ \`\`\`typescript
3316
+ import { css } from 'styled-system/css';
3317
+
3318
+ const heading = css({ textStyle: 'headlineMedium' });
3319
+ const body = css({ textStyle: 'bodyMedium' });
3320
+ \`\`\`
3321
+ ```
3322
+
3323
+ #### 7.5 Create Component Guidelines
3324
+
3325
+ ```markdown
3326
+ <!-- guidelines/components/button.md -->
3327
+ # Button
3328
+
3329
+ **Purpose:** Primary interactive element for user actions.
3330
+
3331
+ ## Import
3332
+
3333
+ \`\`\`typescript
3334
+ import { Button } from '@discourser/design-system';
3335
+ \`\`\`
3336
+
3337
+ ## Variants
3338
+
3339
+ | Variant | Usage | When to Use |
3340
+ |---------|-------|-------------|
3341
+ | `filled` | Primary actions | Submit, Confirm, Main CTA |
3342
+ | `outlined` | Secondary actions | Cancel, Back, Alternative options |
3343
+ | `text` | Tertiary actions | Links, Less prominent actions |
3344
+ | `elevated` | Floating actions | FAB-like buttons |
3345
+ | `tonal` | Medium emphasis | Secondary CTA, Soft highlight |
3346
+
3347
+ ## Sizes
3348
+
3349
+ | Size | Height | Usage |
3350
+ |------|--------|-------|
3351
+ | `sm` | 32px | Compact UI, dense layouts |
3352
+ | `md` | 40px | Default, most use cases |
3353
+ | `lg` | 48px | Touch targets, mobile emphasis |
3354
+
3355
+ ## Props
3356
+
3357
+ | Prop | Type | Default | Description |
3358
+ |------|------|---------|-------------|
3359
+ | `variant` | `'filled' \| 'outlined' \| 'text' \| 'elevated' \| 'tonal'` | `'filled'` | Visual style |
3360
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Button size |
3361
+ | `disabled` | `boolean` | `false` | Disable button |
3362
+ | `leftIcon` | `ReactNode` | - | Icon before text |
3363
+ | `rightIcon` | `ReactNode` | - | Icon after text |
3364
+
3365
+ ## Examples
3366
+
3367
+ \`\`\`typescript
3368
+ // Primary action
3369
+ <Button variant="filled">Submit</Button>
3370
+
3371
+ // Secondary action
3372
+ <Button variant="outlined">Cancel</Button>
3373
+
3374
+ // With icon
3375
+ <Button variant="filled" leftIcon={<PlusIcon />}>Add Item</Button>
3376
+
3377
+ // Disabled
3378
+ <Button variant="filled" disabled>Unavailable</Button>
3379
+
3380
+ // Different sizes
3381
+ <Button size="sm">Small</Button>
3382
+ <Button size="md">Medium</Button>
3383
+ <Button size="lg">Large</Button>
3384
+ \`\`\`
3385
+
3386
+ ## DO NOT
3387
+
3388
+ \`\`\`typescript
3389
+ // ❌ Don't use native button when Button component exists
3390
+ <button>Submit</button>
3391
+
3392
+ // ❌ Don't override button colors with inline styles
3393
+ <Button style={{ backgroundColor: 'red' }}>Delete</Button>
3394
+
3395
+ // ❌ Don't combine conflicting sizes
3396
+ <Button size="lg" style={{ height: '24px' }}>Small</Button>
3397
+
3398
+ // ❌ Don't add className to override core styles
3399
+ <Button className="bg-blue-500">Custom</Button>
3400
+ \`\`\`
3401
+ ```
3402
+
3403
+ ```markdown
3404
+ <!-- guidelines/components/dialog.md -->
3405
+ # Dialog
3406
+
3407
+ **Purpose:** Modal overlay for focused tasks requiring user attention.
3408
+
3409
+ ## Import
3410
+
3411
+ \`\`\`typescript
3412
+ import { Dialog } from '@discourser/design-system';
3413
+ \`\`\`
3414
+
3415
+ ## Structure
3416
+
3417
+ Dialog uses a compound component pattern. All parts are required for proper accessibility:
3418
+
3419
+ \`\`\`typescript
3420
+ <Dialog.Root>
3421
+ <Dialog.Trigger asChild>
3422
+ <Button>Open Dialog</Button>
3423
+ </Dialog.Trigger>
3424
+ <Dialog.Backdrop />
3425
+ <Dialog.Positioner>
3426
+ <Dialog.Content>
3427
+ <Dialog.Title>Dialog Title</Dialog.Title>
3428
+ <Dialog.Description>Dialog content goes here.</Dialog.Description>
3429
+ <Dialog.CloseTrigger asChild>
3430
+ <Button variant="text">Close</Button>
3431
+ </Dialog.CloseTrigger>
3432
+ </Dialog.Content>
3433
+ </Dialog.Positioner>
3434
+ </Dialog.Root>
3435
+ \`\`\`
3436
+
3437
+ ## Parts
3438
+
3439
+ | Part | Required | Description |
3440
+ |------|----------|-------------|
3441
+ | `Dialog.Root` | Yes | Container, manages open/close state |
3442
+ | `Dialog.Trigger` | No | Opens dialog when clicked |
3443
+ | `Dialog.Backdrop` | Yes | Semi-transparent overlay behind dialog |
3444
+ | `Dialog.Positioner` | Yes | Centers the content |
3445
+ | `Dialog.Content` | Yes | The dialog panel |
3446
+ | `Dialog.Title` | Yes | Accessible title (required for a11y) |
3447
+ | `Dialog.Description` | No | Supporting text |
3448
+ | `Dialog.CloseTrigger` | No | Closes dialog when clicked |
3449
+
3450
+ ## Sizes
3451
+
3452
+ | Size | Max Width | Usage |
3453
+ |------|-----------|-------|
3454
+ | `sm` | 400px | Confirmations, simple prompts |
3455
+ | `md` | 560px | Default, forms |
3456
+ | `lg` | 720px | Complex content, tables |
3457
+ | `fullscreen` | 100vw | Mobile, immersive experiences |
3458
+
3459
+ ## Accessibility
3460
+
3461
+ - Focus is automatically trapped within dialog when open
3462
+ - ESC key closes dialog
3463
+ - Title is announced to screen readers
3464
+ - Background content is marked as inert
3465
+
3466
+ ## Examples
3467
+
3468
+ \`\`\`typescript
3469
+ // Confirmation dialog
3470
+ <Dialog.Root>
3471
+ <Dialog.Trigger asChild>
3472
+ <Button variant="outlined">Delete Item</Button>
3473
+ </Dialog.Trigger>
3474
+ <Dialog.Backdrop />
3475
+ <Dialog.Positioner>
3476
+ <Dialog.Content size="sm">
3477
+ <Dialog.Title>Delete Item?</Dialog.Title>
3478
+ <Dialog.Description>
3479
+ This action cannot be undone. The item will be permanently deleted.
3480
+ </Dialog.Description>
3481
+ <div style={{ display: 'flex', gap: '8px', justifyContent: 'flex-end', marginTop: '24px' }}>
3482
+ <Dialog.CloseTrigger asChild>
3483
+ <Button variant="text">Cancel</Button>
3484
+ </Dialog.CloseTrigger>
3485
+ <Button variant="filled">Delete</Button>
3486
+ </div>
3487
+ </Dialog.Content>
3488
+ </Dialog.Positioner>
3489
+ </Dialog.Root>
3490
+
3491
+ // Controlled dialog
3492
+ function ControlledDialog() {
3493
+ const [open, setOpen] = useState(false);
3494
+
3495
+ return (
3496
+ <Dialog.Root open={open} onOpenChange={({ open }) => setOpen(open)}>
3497
+ <Dialog.Trigger asChild>
3498
+ <Button>Open</Button>
3499
+ </Dialog.Trigger>
3500
+ <Dialog.Backdrop />
3501
+ <Dialog.Positioner>
3502
+ <Dialog.Content>
3503
+ <Dialog.Title>Controlled Dialog</Dialog.Title>
3504
+ <Dialog.Description>State is managed externally.</Dialog.Description>
3505
+ </Dialog.Content>
3506
+ </Dialog.Positioner>
3507
+ </Dialog.Root>
3508
+ );
3509
+ }
3510
+ \`\`\`
3511
+
3512
+ ## DO NOT
3513
+
3514
+ \`\`\`typescript
3515
+ // ❌ Don't omit required parts
3516
+ <Dialog.Root>
3517
+ <Dialog.Content> {/* Missing Backdrop, Positioner, Title */}
3518
+ Content here
3519
+ </Dialog.Content>
3520
+ </Dialog.Root>
3521
+
3522
+ // ❌ Don't omit Title (accessibility violation)
3523
+ <Dialog.Root>
3524
+ <Dialog.Backdrop />
3525
+ <Dialog.Positioner>
3526
+ <Dialog.Content>
3527
+ <Dialog.Description>No title!</Dialog.Description>
3528
+ </Dialog.Content>
3529
+ </Dialog.Positioner>
3530
+ </Dialog.Root>
3531
+
3532
+ // ❌ Don't use native modal elements
3533
+ <dialog open>
3534
+ <p>Native dialog</p>
3535
+ </dialog>
3536
+ \`\`\`
3537
+ ```
3538
+
3539
+ ```markdown
3540
+ <!-- guidelines/components/input.md -->
3541
+ # Input
3542
+
3543
+ **Purpose:** Text input field with label, helper text, and validation.
3544
+
3545
+ ## Import
3546
+
3547
+ \`\`\`typescript
3548
+ import { Input } from '@discourser/design-system';
3549
+ \`\`\`
3550
+
3551
+ ## Variants
3552
+
3553
+ | Variant | Usage |
3554
+ |---------|-------|
3555
+ | `outlined` | Default, most use cases |
3556
+ | `filled` | Alternative style, denser layouts |
3557
+
3558
+ ## Sizes
3559
+
3560
+ | Size | Height | Usage |
3561
+ |------|--------|-------|
3562
+ | `sm` | 40px | Compact forms |
3563
+ | `md` | 56px | Default |
3564
+
3565
+ ## Props
3566
+
3567
+ | Prop | Type | Default | Description |
3568
+ |------|------|---------|-------------|
3569
+ | `variant` | `'outlined' \| 'filled'` | `'outlined'` | Visual style |
3570
+ | `size` | `'sm' \| 'md'` | `'md'` | Input size |
3571
+ | `label` | `string` | - | Field label |
3572
+ | `helperText` | `string` | - | Helper text below input |
3573
+ | `errorText` | `string` | - | Error message (shows error state) |
3574
+ | `disabled` | `boolean` | `false` | Disable input |
3575
+
3576
+ ## Examples
3577
+
3578
+ \`\`\`typescript
3579
+ // Basic input with label
3580
+ <Input label="Email" placeholder="you@example.com" />
3581
+
3582
+ // With helper text
3583
+ <Input
3584
+ label="Password"
3585
+ type="password"
3586
+ helperText="Must be at least 8 characters"
3587
+ />
3588
+
3589
+ // Error state
3590
+ <Input
3591
+ label="Email"
3592
+ errorText="Please enter a valid email address"
3593
+ />
3594
+
3595
+ // Disabled
3596
+ <Input label="Username" disabled value="johndoe" />
3597
+
3598
+ // Controlled
3599
+ const [email, setEmail] = useState('');
3600
+ <Input
3601
+ label="Email"
3602
+ value={email}
3603
+ onChange={(e) => setEmail(e.target.value)}
3604
+ />
3605
+ \`\`\`
3606
+
3607
+ ## DO NOT
3608
+
3609
+ \`\`\`typescript
3610
+ // ❌ Don't use native input without the component
3611
+ <input type="text" />
3612
+
3613
+ // ❌ Don't omit label (accessibility)
3614
+ <Input placeholder="Enter email" /> // Missing label!
3615
+
3616
+ // ❌ Don't override input styles
3617
+ <Input label="Email" style={{ border: '2px solid red' }} />
3618
+ \`\`\`
3619
+ ```
3620
+
3621
+ #### 7.6 Publish Package to npm
3622
+
3623
+ **Public Package:**
3624
+ ```bash
3625
+ # Ensure logged into npm
3626
+ npm login
3627
+
3628
+ # Publish
3629
+ pnpm publish --access public
3630
+ ```
3631
+
3632
+ **Private Package (for Figma organization):**
3633
+ 1. In Figma Make, go to ⚙️ Make settings → Figma npm registry
3634
+ 2. Click "Get started" and enter organization scope (e.g., `@discourser`)
3635
+ 3. Click "Generate key" (requires org admin)
3636
+ 4. Add to `.npmrc`:
3637
+ ```
3638
+ @discourser:registry=https://npm.figma.com/
3639
+ //npm.figma.com/:_authToken=YOUR_TOKEN
3640
+ ```
3641
+ 5. Publish: `npm publish`
3642
+
3643
+ #### 7.7 Create Figma Make Template (Optional)
3644
+
3645
+ After publishing, create a Make template for your team:
3646
+ 1. Create new Figma Make file
3647
+ 2. Install package: "Install @discourser/design-system"
3648
+ 3. Add guidelines folder with all markdown files from 7.1-7.5
3649
+ 4. Publish as template for team use
3650
+
3651
+ **Validation:**
3652
+ ```bash
3653
+ # In Figma Make file:
3654
+ # 1. Install package
3655
+ # 2. Ask: "Create a form with Button and Input components"
3656
+ # 3. Verify Make uses design system components correctly
3657
+ # 4. Verify semantic tokens are applied
3658
+ # 5. Ask: "Create a confirmation dialog"
3659
+ # 6. Verify Dialog compound pattern is used correctly
3660
+ ```
3661
+
3662
+ ---
3663
+
3664
+ ### Phase Summary
3665
+
3666
+ | Phase | Description | Status |
3667
+ |-------|-------------|--------|
3668
+ | 0 | Context Engineering (Skills, CLAUDE.md) | ✅ Complete |
3669
+ | 1 | Fix Foundation (deps, config) | ✅ Complete |
3670
+ | 2 | Architecture (Contract/Language/Transform) | ✅ Complete |
3671
+ | 3 | M3 Token Integration | ✅ Complete |
3672
+ | 4 | Complete Recipe Coverage + Testing | 🔄 In Progress |
3673
+ | 5 | Ark UI Integration + A11y Testing | ⬜ Not Started |
3674
+ | 6 | Build & Package (Figma Make compatible) | ⬜ Not Started |
3675
+ | 7 | Figma Make Guidelines | ⬜ Not Started |
3676
+
3677
+ ---
3678
+
3679
+ ### Slash Commands Reference
3680
+
3681
+ These commands are available in Claude Code when working in this repository:
3682
+
3683
+ ```bash
3684
+ /fix-foundation # Run Phase 1 tasks
3685
+ /implement-architecture # Run Phase 2 tasks
3686
+ /new-component <name> # Scaffold a new component with recipe + stories + tests
3687
+ ```
3688
+
3689
+ ### Skills Reference
3690
+
3691
+ These skills auto-load when relevant:
3692
+
3693
+ | Skill | Triggers On |
3694
+ |-------|-------------|
3695
+ | `design-language` | Contract, language, transform work |
3696
+ | `panda-recipes` | Recipe creation, variant patterns |
3697
+ | `m3-tokens` | Color values, semantic tokens |
3698
+ | `component-patterns` | Component creation, forwardRef, Ark UI |