@discourser/design-system 0.24.0 → 0.25.1

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 (105) hide show
  1. package/dist/{chunk-KIJKNZ73.cjs → chunk-QNCZYFUJ.cjs} +46 -11
  2. package/dist/chunk-QNCZYFUJ.cjs.map +1 -0
  3. package/dist/{chunk-VOH2QELR.cjs → chunk-TBLDQATQ.cjs} +377 -12
  4. package/dist/chunk-TBLDQATQ.cjs.map +1 -0
  5. package/dist/{chunk-VN2QX6S7.js → chunk-UHSL4N44.js} +378 -14
  6. package/dist/chunk-UHSL4N44.js.map +1 -0
  7. package/dist/{chunk-HN2IHIMR.js → chunk-ZPECW4N2.js} +46 -11
  8. package/dist/chunk-ZPECW4N2.js.map +1 -0
  9. package/dist/components/Badge.figma.d.ts +2 -0
  10. package/dist/components/Badge.figma.d.ts.map +1 -0
  11. package/dist/components/Button.figma.d.ts +2 -0
  12. package/dist/components/Button.figma.d.ts.map +1 -0
  13. package/dist/components/Card.figma.d.ts +2 -0
  14. package/dist/components/Card.figma.d.ts.map +1 -0
  15. package/dist/components/Icons/AudienceIcon.d.ts.map +1 -1
  16. package/dist/components/Input.figma.d.ts +2 -0
  17. package/dist/components/Input.figma.d.ts.map +1 -0
  18. package/dist/components/RadioGroup.figma.d.ts +2 -0
  19. package/dist/components/RadioGroup.figma.d.ts.map +1 -0
  20. package/dist/components/SettingsPopover/SettingsPopover.figma.d.ts +2 -0
  21. package/dist/components/SettingsPopover/SettingsPopover.figma.d.ts.map +1 -0
  22. package/dist/components/Slider.figma.d.ts +2 -0
  23. package/dist/components/Slider.figma.d.ts.map +1 -0
  24. package/dist/components/Stepper/Stepper.figma.d.ts +2 -0
  25. package/dist/components/Stepper/Stepper.figma.d.ts.map +1 -0
  26. package/dist/components/StudioControls/StudioControls.d.ts +3 -0
  27. package/dist/components/StudioControls/StudioControls.d.ts.map +1 -0
  28. package/dist/components/StudioControls/StudioControls.figma.d.ts +2 -0
  29. package/dist/components/StudioControls/StudioControls.figma.d.ts.map +1 -0
  30. package/dist/components/StudioControls/index.d.ts +3 -0
  31. package/dist/components/StudioControls/index.d.ts.map +1 -0
  32. package/dist/components/StudioControls/types.d.ts +17 -0
  33. package/dist/components/StudioControls/types.d.ts.map +1 -0
  34. package/dist/components/Switch.figma.d.ts +2 -0
  35. package/dist/components/Switch.figma.d.ts.map +1 -0
  36. package/dist/components/index.cjs +78 -74
  37. package/dist/components/index.d.ts +1 -0
  38. package/dist/components/index.d.ts.map +1 -1
  39. package/dist/components/index.js +1 -1
  40. package/dist/figma-codex/parser.d.ts +2 -0
  41. package/dist/figma-codex/parser.d.ts.map +1 -1
  42. package/dist/figma-codex/resolver.d.ts.map +1 -1
  43. package/dist/figma-codex/schema.d.ts +7 -0
  44. package/dist/figma-codex/schema.d.ts.map +1 -1
  45. package/dist/figma-codex.json +420 -2
  46. package/dist/index.cjs +82 -78
  47. package/dist/index.js +2 -2
  48. package/dist/preset/index.cjs +2 -2
  49. package/dist/preset/index.js +1 -1
  50. package/dist/preset/recipes/badge.d.ts.map +1 -1
  51. package/dist/preset/recipes/index.d.ts +1 -0
  52. package/dist/preset/recipes/index.d.ts.map +1 -1
  53. package/dist/preset/recipes/studio-controls.d.ts +2 -0
  54. package/dist/preset/recipes/studio-controls.d.ts.map +1 -0
  55. package/dist/preset/recipes/switch.d.ts.map +1 -1
  56. package/docs/figma-mcp-return/card-audit.json +31 -0
  57. package/docs/figma-mcp-return/conversation-prelaunch-audit.json +51 -0
  58. package/docs/figma-mcp-return/conversation_lobby-design-context.md +359 -0
  59. package/docs/figma-mcp-return/conversation_lobby-metadata.xml +1 -0
  60. package/docs/figma-mcp-return/discourser-accordion-audit.json +264 -0
  61. package/docs/figma-mcp-return/discourser-accordion-design-context-v2.tsx +350 -0
  62. package/docs/figma-mcp-return/discourser-accordion-design-context.tsx +130 -0
  63. package/docs/figma-mcp-return/discourser-accordion-metadata-v2.xml +1 -0
  64. package/docs/figma-mcp-return/discourser-accordion-metadata.xml +1 -0
  65. package/docs/figma-mcp-return/kai-resolution-simulation.md +181 -0
  66. package/docs/figma-mcp-return/prelaunch-comparison-analysis.md +126 -0
  67. package/docs/figma-mcp-return/prelaunch-get-design-context.md +982 -0
  68. package/docs/figma-mcp-return/prelaunch-get-metadata.md +7 -0
  69. package/docs/figma-mcp-return/prelaunch-get-metadata.xml +3 -0
  70. package/docs/figma-mcp-return/prelaunch-post-component-update.md +791 -0
  71. package/docs/figma-mcp-return/prelaunch-post-rebind-design-context.md +969 -0
  72. package/docs/figma-mcp-return/radio-group-audit.json +23 -0
  73. package/docs/figma-mcp-return/switch-audit.json +38 -0
  74. package/docs/session-summary-2026-03-29.md +98 -0
  75. package/package.json +2 -1
  76. package/src/components/Badge.figma.tsx +39 -0
  77. package/src/components/Button.figma.tsx +18 -0
  78. package/src/components/Card.figma.tsx +33 -0
  79. package/src/components/Icons/AudienceIcon.tsx +3 -1
  80. package/src/components/Icons/AudioSpeakerIcon.tsx +1 -1
  81. package/src/components/Icons/MicrophoneIcon.tsx +3 -3
  82. package/src/components/Icons/RecordIcon.tsx +4 -4
  83. package/src/components/Icons/TimerIcon.tsx +1 -1
  84. package/src/components/Input.figma.tsx +17 -0
  85. package/src/components/RadioGroup.figma.tsx +61 -0
  86. package/src/components/SettingsPopover/SettingsPopover.figma.tsx +17 -0
  87. package/src/components/Slider.figma.tsx +66 -0
  88. package/src/components/Stepper/Stepper.figma.tsx +19 -0
  89. package/src/components/StudioControls/StudioControls.figma.tsx +25 -0
  90. package/src/components/StudioControls/StudioControls.tsx +381 -0
  91. package/src/components/StudioControls/index.ts +2 -0
  92. package/src/components/StudioControls/types.ts +17 -0
  93. package/src/components/Switch.figma.tsx +49 -0
  94. package/src/components/index.ts +5 -0
  95. package/src/figma-codex/parser.ts +55 -0
  96. package/src/figma-codex/resolver.ts +1 -0
  97. package/src/figma-codex/schema.ts +9 -0
  98. package/src/preset/recipes/badge.ts +41 -7
  99. package/src/preset/recipes/index.ts +1 -0
  100. package/src/preset/recipes/studio-controls.ts +252 -0
  101. package/src/preset/recipes/switch.ts +5 -4
  102. package/dist/chunk-HN2IHIMR.js.map +0 -1
  103. package/dist/chunk-KIJKNZ73.cjs.map +0 -1
  104. package/dist/chunk-VN2QX6S7.js.map +0 -1
  105. package/dist/chunk-VOH2QELR.cjs.map +0 -1
@@ -0,0 +1,381 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import * as Accordion from '../Accordion';
5
+ import * as RadioGroup from '../RadioGroup';
6
+ import * as Switch from '../Switch';
7
+ import * as Slider from '../Slider';
8
+ import { ScenarioIcon } from '../Icons/ScenarioIcon';
9
+ import { AudioSpeakerIcon } from '../Icons/AudioSpeakerIcon';
10
+ import { MicrophoneIcon } from '../Icons/MicrophoneIcon';
11
+ import { RecordIcon } from '../Icons/RecordIcon';
12
+ import { TimerIcon } from '../Icons/TimerIcon';
13
+ import { AudienceIcon } from '../Icons/AudienceIcon';
14
+ import { studioControls } from 'styled-system/recipes';
15
+ import { Badge } from '../Badge';
16
+ import type { StudioControlsProps, RecordingMode } from './types';
17
+
18
+ // ── Static section data ────────────────────────────────────────────────────────
19
+
20
+ const SETTING_BADGES = [
21
+ { id: 'conversation-flow', label: 'Conversation Flow' },
22
+ { id: 'question-complexity', label: 'Question Complexity' },
23
+ { id: 'response-pacing', label: 'Response Pacing' },
24
+ { id: 'interview-tone', label: 'Interview Tone' },
25
+ ] as const;
26
+
27
+ const RECORDING_OPTIONS = [
28
+ { value: 'no-recording', label: 'No Recording' },
29
+ { value: 'audio-only', label: 'Record just audio' },
30
+ { value: 'video-audio', label: 'Record video & audio' },
31
+ ] as const;
32
+
33
+ const LEVEL_COLOR_PALETTE = {
34
+ beginner: 'primary',
35
+ intermediate: 'secondary',
36
+ advanced: 'tertiary',
37
+ } as const;
38
+
39
+ const ALL_SECTION_IDS = [
40
+ 'scenario-settings',
41
+ 'audio-output',
42
+ 'mic-input',
43
+ 'av-recording',
44
+ 'display-timer',
45
+ 'hide-interviewers',
46
+ ];
47
+
48
+ // ── Component ─────────────────────────────────────────────────────────────────
49
+
50
+ export function StudioControls({
51
+ scenarioName,
52
+ scenarioFocus,
53
+ scenarioLevel,
54
+ defaultAudioLevel = 75,
55
+ defaultMicLevel = 75,
56
+ defaultRecordingMode = 'no-recording',
57
+ defaultShowTimer = true,
58
+ defaultHideInterviewers = false,
59
+ onAudioLevelChange,
60
+ onMicLevelChange,
61
+ onRecordingModeChange,
62
+ onTimerChange,
63
+ onInterviewersChange,
64
+ }: StudioControlsProps) {
65
+ const styles = studioControls();
66
+
67
+ const [audioLevel, setAudioLevel] = useState(defaultAudioLevel);
68
+ const [micLevel, setMicLevel] = useState(defaultMicLevel);
69
+
70
+ const levelLabel =
71
+ scenarioLevel.charAt(0).toUpperCase() + scenarioLevel.slice(1);
72
+
73
+ return (
74
+ <div className={styles.root}>
75
+ <Accordion.Root defaultValue={ALL_SECTION_IDS} multiple variant="plain">
76
+ {/* ── Section 1: Scenario Settings ─────────────────────────────────── */}
77
+ <Accordion.Item value="scenario-settings" className={styles.section}>
78
+ <Accordion.ItemTrigger
79
+ className={styles.sectionTrigger}
80
+ css={{
81
+ fontSize: 'lg',
82
+ fontWeight: 'medium',
83
+ borderRadius: '0',
84
+ py: '4',
85
+ bg: 'neutral.1',
86
+ color: 'onSurface',
87
+ }}
88
+ >
89
+ <span className={styles.triggerIcon}>
90
+ <ScenarioIcon />
91
+ </span>
92
+ <span className={styles.triggerLabel}>Scenario Settings</span>
93
+ <Accordion.ItemIndicator
94
+ className={styles.sectionIndicator}
95
+ css={{ color: 'primary.7' }}
96
+ />
97
+ </Accordion.ItemTrigger>
98
+
99
+ <Accordion.ItemContent className={styles.sectionContent}>
100
+ <div className={styles.infoPanel}>
101
+ <div className={styles.scenarioMeta}>
102
+ <p className={styles.scenarioName}>{scenarioName}</p>
103
+ <p className={styles.scenarioFocus}>
104
+ <strong>Focus</strong>: {scenarioFocus}
105
+ </p>
106
+ </div>
107
+ <div className={styles.settingsCard}>
108
+ <p className={styles.settingsCardHeading}>Scenario Settings</p>
109
+ <div className={styles.settingsList}>
110
+ {SETTING_BADGES.map((badge) => (
111
+ <div key={badge.id} className={styles.settingsRow}>
112
+ <span className={styles.settingsRowLabel}>
113
+ {badge.label}:
114
+ </span>
115
+ <Badge
116
+ variant="rating"
117
+ colorPalette={LEVEL_COLOR_PALETTE[scenarioLevel]}
118
+ size="md"
119
+ >
120
+ {levelLabel}
121
+ </Badge>
122
+ </div>
123
+ ))}
124
+ </div>
125
+ </div>
126
+ </div>
127
+ </Accordion.ItemContent>
128
+ </Accordion.Item>
129
+
130
+ {/* ── Section 2: Audio Output ──────────────────────────────────────── */}
131
+ <Accordion.Item value="audio-output" className={styles.section}>
132
+ <Accordion.ItemTrigger
133
+ className={styles.sectionTrigger}
134
+ css={{
135
+ fontSize: 'lg',
136
+ fontWeight: 'medium',
137
+ borderRadius: '0',
138
+ py: '4',
139
+ bg: 'neutral.1',
140
+ color: 'onSurface',
141
+ }}
142
+ >
143
+ <span className={styles.triggerIcon}>
144
+ <AudioSpeakerIcon />
145
+ </span>
146
+ <span className={styles.triggerLabel}>Audio Output</span>
147
+ <Accordion.ItemIndicator
148
+ className={styles.sectionIndicator}
149
+ css={{ color: 'primary.7' }}
150
+ />
151
+ </Accordion.ItemTrigger>
152
+
153
+ <Accordion.ItemContent className={styles.sectionContent}>
154
+ <div className={styles.sliderPanel}>
155
+ <div className={styles.sliderLabel}>
156
+ <span className={styles.sliderLabelText}>Volume</span>
157
+ <span className={styles.levelBadge}>{audioLevel}%</span>
158
+ </div>
159
+ <Slider.Root
160
+ min={0}
161
+ max={100}
162
+ value={[audioLevel]}
163
+ colorPalette="tertiary"
164
+ onValueChange={({ value }) => {
165
+ const next = value[0] ?? audioLevel;
166
+ setAudioLevel(next);
167
+ onAudioLevelChange?.(next);
168
+ }}
169
+ >
170
+ <Slider.Control className={styles.sliderTrack}>
171
+ <Slider.Track>
172
+ <Slider.Range />
173
+ </Slider.Track>
174
+ <Slider.Thumbs />
175
+ </Slider.Control>
176
+ </Slider.Root>
177
+ </div>
178
+ </Accordion.ItemContent>
179
+ </Accordion.Item>
180
+
181
+ {/* ── Section 3: Microphone Output ─────────────────────────────────── */}
182
+ <Accordion.Item value="mic-input" className={styles.section}>
183
+ <Accordion.ItemTrigger
184
+ className={styles.sectionTrigger}
185
+ css={{
186
+ fontSize: 'lg',
187
+ fontWeight: 'medium',
188
+ borderRadius: '0',
189
+ py: '4',
190
+ bg: 'neutral.1',
191
+ color: 'onSurface',
192
+ }}
193
+ >
194
+ <span className={styles.triggerIcon}>
195
+ <MicrophoneIcon />
196
+ </span>
197
+ <span className={styles.triggerLabel}>Microphone Input</span>
198
+ <Accordion.ItemIndicator
199
+ className={styles.sectionIndicator}
200
+ css={{ color: 'primary.7' }}
201
+ />
202
+ </Accordion.ItemTrigger>
203
+
204
+ <Accordion.ItemContent className={styles.sectionContent}>
205
+ <div className={styles.sliderPanel}>
206
+ <div className={styles.sliderLabel}>
207
+ <span className={styles.sliderLabelText}>Mic Gain</span>
208
+ <span className={styles.levelBadge}>{micLevel}%</span>
209
+ </div>
210
+ <Slider.Root
211
+ min={0}
212
+ max={100}
213
+ value={[micLevel]}
214
+ colorPalette="tertiary"
215
+ onValueChange={({ value }) => {
216
+ const next = value[0] ?? micLevel;
217
+ setMicLevel(next);
218
+ onMicLevelChange?.(next);
219
+ }}
220
+ >
221
+ <Slider.Control className={styles.sliderTrack}>
222
+ <Slider.Track>
223
+ <Slider.Range />
224
+ </Slider.Track>
225
+ <Slider.Thumbs />
226
+ </Slider.Control>
227
+ </Slider.Root>
228
+ </div>
229
+ </Accordion.ItemContent>
230
+ </Accordion.Item>
231
+
232
+ {/* ── Section 4: A/V Recording ─────────────────────────────────────── */}
233
+ <Accordion.Item value="av-recording" className={styles.section}>
234
+ <Accordion.ItemTrigger
235
+ className={styles.sectionTrigger}
236
+ css={{
237
+ fontSize: 'lg',
238
+ fontWeight: 'medium',
239
+ borderRadius: '0',
240
+ py: '4',
241
+ bg: 'neutral.1',
242
+ color: 'onSurface',
243
+ }}
244
+ >
245
+ <span className={styles.triggerIcon}>
246
+ <RecordIcon />
247
+ </span>
248
+ <span className={styles.triggerLabel}>A/V Recording</span>
249
+ <Accordion.ItemIndicator
250
+ className={styles.sectionIndicator}
251
+ css={{ color: 'primary.7' }}
252
+ />
253
+ </Accordion.ItemTrigger>
254
+
255
+ <Accordion.ItemContent className={styles.sectionContent}>
256
+ <div className={styles.radioPanel}>
257
+ <RadioGroup.Root
258
+ defaultValue={defaultRecordingMode}
259
+ colorPalette="primary"
260
+ onValueChange={({ value }) => {
261
+ if (!value) return;
262
+ onRecordingModeChange?.(value as RecordingMode);
263
+ }}
264
+ css={{ gap: '3' }}
265
+ >
266
+ {RECORDING_OPTIONS.map((option) => (
267
+ <RadioGroup.Item key={option.value} value={option.value}>
268
+ <RadioGroup.ItemControl
269
+ css={{
270
+ boxShadow: 'none',
271
+ borderWidth: '1px',
272
+ borderStyle: 'solid',
273
+ borderColor: 'primary.7',
274
+ _checked: {
275
+ bg: 'm3Primary.container',
276
+ borderColor: 'primary.7',
277
+ _after: { bg: 'primary.7' },
278
+ },
279
+ }}
280
+ />
281
+ <RadioGroup.ItemText
282
+ css={{ fontSize: 'md', fontWeight: 'medium' }}
283
+ >
284
+ {option.label}
285
+ </RadioGroup.ItemText>
286
+ <RadioGroup.ItemHiddenInput />
287
+ </RadioGroup.Item>
288
+ ))}
289
+ </RadioGroup.Root>
290
+ </div>
291
+ </Accordion.ItemContent>
292
+ </Accordion.Item>
293
+
294
+ {/* ── Section 5: Display Timer ──────────────────────────────────────── */}
295
+ <Accordion.Item value="display-timer" className={styles.section}>
296
+ <Accordion.ItemTrigger
297
+ className={styles.sectionTrigger}
298
+ css={{
299
+ fontSize: 'lg',
300
+ fontWeight: 'medium',
301
+ borderRadius: '0',
302
+ py: '4',
303
+ bg: 'neutral.1',
304
+ color: 'onSurface',
305
+ }}
306
+ >
307
+ <span className={styles.triggerIcon}>
308
+ <TimerIcon />
309
+ </span>
310
+ <span className={styles.triggerLabel}>Display Timer</span>
311
+ <Accordion.ItemIndicator
312
+ className={styles.sectionIndicator}
313
+ css={{ color: 'primary.7' }}
314
+ />
315
+ </Accordion.ItemTrigger>
316
+
317
+ <Accordion.ItemContent className={styles.sectionContent}>
318
+ <div className={styles.togglePanel}>
319
+ <p className={styles.toggleDescription}>
320
+ Hide timer to create more realistic interview
321
+ </p>
322
+ <Switch.Root
323
+ colorPalette="primary"
324
+ defaultChecked={defaultShowTimer}
325
+ onCheckedChange={({ checked }) => {
326
+ onTimerChange?.(checked);
327
+ }}
328
+ >
329
+ <Switch.Control />
330
+ <Switch.HiddenInput />
331
+ </Switch.Root>
332
+ </div>
333
+ </Accordion.ItemContent>
334
+ </Accordion.Item>
335
+
336
+ {/* ── Section 6: Hide Interviewers ─────────────────────────────────── */}
337
+ <Accordion.Item value="hide-interviewers" className={styles.section}>
338
+ <Accordion.ItemTrigger
339
+ className={styles.sectionTrigger}
340
+ css={{
341
+ fontSize: 'lg',
342
+ fontWeight: 'medium',
343
+ borderRadius: '0',
344
+ py: '4',
345
+ bg: 'neutral.1',
346
+ color: 'onSurface',
347
+ }}
348
+ >
349
+ <span className={styles.triggerIcon}>
350
+ <AudienceIcon />
351
+ </span>
352
+ <span className={styles.triggerLabel}>Hide Interviewers</span>
353
+ <Accordion.ItemIndicator
354
+ className={styles.sectionIndicator}
355
+ css={{ color: 'primary.7' }}
356
+ />
357
+ </Accordion.ItemTrigger>
358
+
359
+ <Accordion.ItemContent className={styles.sectionContent}>
360
+ <div className={styles.togglePanel}>
361
+ <p className={styles.toggleDescription}>
362
+ Switch off to hide video of interviewers if it is too
363
+ distracting
364
+ </p>
365
+ <Switch.Root
366
+ colorPalette="primary"
367
+ defaultChecked={defaultHideInterviewers}
368
+ onCheckedChange={({ checked }) => {
369
+ onInterviewersChange?.(checked);
370
+ }}
371
+ >
372
+ <Switch.Control />
373
+ <Switch.HiddenInput />
374
+ </Switch.Root>
375
+ </div>
376
+ </Accordion.ItemContent>
377
+ </Accordion.Item>
378
+ </Accordion.Root>
379
+ </div>
380
+ );
381
+ }
@@ -0,0 +1,2 @@
1
+ export { StudioControls } from './StudioControls';
2
+ export type { StudioControlsProps, RecordingMode } from './types';
@@ -0,0 +1,17 @@
1
+ export type RecordingMode = 'no-recording' | 'audio-only' | 'video-audio';
2
+
3
+ export interface StudioControlsProps {
4
+ scenarioName: string;
5
+ scenarioFocus: string;
6
+ scenarioLevel: 'beginner' | 'intermediate' | 'advanced';
7
+ defaultAudioLevel?: number;
8
+ defaultMicLevel?: number;
9
+ defaultRecordingMode?: RecordingMode;
10
+ defaultShowTimer?: boolean;
11
+ defaultHideInterviewers?: boolean;
12
+ onAudioLevelChange?: (value: number) => void;
13
+ onMicLevelChange?: (value: number) => void;
14
+ onRecordingModeChange?: (mode: RecordingMode) => void;
15
+ onTimerChange?: (show: boolean) => void;
16
+ onInterviewersChange?: (hide: boolean) => void;
17
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @dds-tokens
3
+ * recipe: switchComponent
4
+ * variantProps: variant, size
5
+ * figmaPropToRecipeProp:
6
+ * Toggled: checked
7
+ * State: state
8
+ */
9
+ import figma from '@figma/code-connect';
10
+ import * as Switch from './Switch';
11
+
12
+ // Original designer-built component — Discourser AI Switch Toggle (38:8121)
13
+ // Variants: Toggled(False/True) × State(Default/Focus/Disabled)
14
+ // This is the component used in production Discourser.AI page designs.
15
+ //
16
+ // Import as namespace: import * as Switch from '@discourser/design-system/Switch'
17
+ //
18
+ // Anatomy:
19
+ // <Switch.Root defaultChecked={bool}>
20
+ // <Switch.Control /> ← renders the track + thumb internally
21
+ // <Switch.Label>Label text</Switch.Label>
22
+ // </Switch.Root>
23
+ //
24
+ // Key props on Root:
25
+ // defaultChecked — uncontrolled initial state (boolean)
26
+ // checked — controlled state (boolean)
27
+ // onCheckedChange — callback ({ checked }: { checked: boolean }) => void
28
+ // disabled — boolean
29
+ //
30
+ // Note: Switch.Control includes Switch.Thumb by default via defaultProps.
31
+ // Do NOT add <Switch.Thumb> manually inside <Switch.Control>.
32
+ figma.connect(
33
+ Switch.Root,
34
+ 'https://www.figma.com/design/GaHmFfmvO4loUzuZS4TgEz/Discourser.AI--V1?node-id=38-8121',
35
+ {
36
+ props: {
37
+ checked: figma.enum('Toggled', {
38
+ False: false,
39
+ True: true,
40
+ }),
41
+ },
42
+ example: ({ checked }) => (
43
+ <Switch.Root defaultChecked={checked}>
44
+ <Switch.Control />
45
+ <Switch.Label>Toggle</Switch.Label>
46
+ </Switch.Root>
47
+ ),
48
+ },
49
+ );
@@ -146,6 +146,11 @@ export {
146
146
  type DurationValue,
147
147
  type QuestionCountValue,
148
148
  } from './ScenarioSettings';
149
+ export {
150
+ StudioControls,
151
+ type StudioControlsProps,
152
+ type RecordingMode,
153
+ } from './StudioControls';
149
154
 
150
155
  // Queue Components
151
156
  export {
@@ -1,3 +1,5 @@
1
+ import type { ComponentTokens } from './schema';
2
+
1
3
  export class ParseError extends Error {
2
4
  constructor(
3
5
  message: string,
@@ -19,6 +21,7 @@ export interface ParsedFigmaFile {
19
21
  figmaNodeId: string;
20
22
  example: string;
21
23
  propsMapping: Record<string, string>;
24
+ tokens?: ComponentTokens;
22
25
  }
23
26
 
24
27
  function parseNodeId(url: string): string {
@@ -77,6 +80,54 @@ function extractPropsMapping(content: string): Record<string, string> {
77
80
  return result;
78
81
  }
79
82
 
83
+ /**
84
+ * Parse a @dds-tokens JSDoc block that appears before figma.connect().
85
+ * Format:
86
+ * @dds-tokens
87
+ * recipe: badge
88
+ * variantProps: variant, size, colorPalette
89
+ * figmaPropToRecipeProp:
90
+ * Variant: variant
91
+ * Size: size
92
+ * Color: colorPalette
93
+ */
94
+ function parseDdsTokens(content: string): ComponentTokens | undefined {
95
+ const blockMatch = content.match(/\/\*\*[\s\S]*?@dds-tokens([\s\S]*?)\*\//);
96
+ if (!blockMatch) return undefined;
97
+
98
+ const block = blockMatch[1]
99
+ .replace(/^\s*\*\s?/gm, '') // strip leading * from each line
100
+ .trim();
101
+
102
+ const tokens: ComponentTokens = {};
103
+
104
+ const recipeMatch = block.match(/^recipe:\s*(.+)$/m);
105
+ if (recipeMatch) tokens.recipe = recipeMatch[1].trim();
106
+
107
+ const variantPropsMatch = block.match(/^variantProps:\s*(.+)$/m);
108
+ if (variantPropsMatch) {
109
+ tokens.variantProps = variantPropsMatch[1]
110
+ .split(',')
111
+ .map((s) => s.trim())
112
+ .filter(Boolean);
113
+ }
114
+
115
+ const mapHeaderIdx = block.indexOf('figmaPropToRecipeProp:');
116
+ if (mapHeaderIdx !== -1) {
117
+ const mapBlock = block.slice(
118
+ mapHeaderIdx + 'figmaPropToRecipeProp:'.length,
119
+ );
120
+ const mapping: Record<string, string> = {};
121
+ for (const line of mapBlock.split('\n')) {
122
+ const m = line.match(/^\s+(\w+):\s*(\w+)/);
123
+ if (m) mapping[m[1]] = m[2];
124
+ }
125
+ if (Object.keys(mapping).length > 0) tokens.figmaPropToRecipeProp = mapping;
126
+ }
127
+
128
+ return Object.keys(tokens).length > 0 ? tokens : undefined;
129
+ }
130
+
80
131
  export function parseFigmaFile(
81
132
  content: string,
82
133
  filePath: string,
@@ -108,6 +159,8 @@ export function parseFigmaFile(
108
159
  const figmaUrl = connectMatch[3];
109
160
  const connectSubComponent = connectMatch[2];
110
161
 
162
+ const tokens = parseDdsTokens(content);
163
+
111
164
  if (nsMatch) {
112
165
  return {
113
166
  filePath,
@@ -120,6 +173,7 @@ export function parseFigmaFile(
120
173
  figmaNodeId: parseNodeId(figmaUrl),
121
174
  example: extractExample(content),
122
175
  propsMapping: extractPropsMapping(content),
176
+ tokens,
123
177
  };
124
178
  }
125
179
 
@@ -134,5 +188,6 @@ export function parseFigmaFile(
134
188
  figmaNodeId: parseNodeId(figmaUrl),
135
189
  example: extractExample(content),
136
190
  propsMapping: extractPropsMapping(content),
191
+ tokens,
137
192
  };
138
193
  }
@@ -276,5 +276,6 @@ export function resolveComponent(
276
276
  subComponents,
277
277
  example: parsed.example,
278
278
  sourcePath,
279
+ tokens: parsed.tokens,
279
280
  };
280
281
  }
@@ -26,6 +26,12 @@ export interface FigmaCodex {
26
26
  components: Record<string, ComponentEntry>;
27
27
  }
28
28
 
29
+ export interface ComponentTokens {
30
+ recipe?: string;
31
+ variantProps?: string[];
32
+ figmaPropToRecipeProp?: Record<string, string>;
33
+ }
34
+
29
35
  export interface ComponentEntry {
30
36
  /** Human-readable component name */
31
37
  name: string;
@@ -62,6 +68,9 @@ export interface ComponentEntry {
62
68
 
63
69
  /** Source file path relative to project root */
64
70
  sourcePath: string;
71
+
72
+ /** Token mappings parsed from @dds-tokens JSDoc block */
73
+ tokens?: ComponentTokens;
65
74
  }
66
75
 
67
76
  export interface PropDefinition {
@@ -1,4 +1,4 @@
1
- import { defineRecipe } from '@pandacss/dev'
1
+ import { defineRecipe } from '@pandacss/dev';
2
2
 
3
3
  export const badge = defineRecipe({
4
4
  className: 'badge',
@@ -32,6 +32,10 @@ export const badge = defineRecipe({
32
32
  bg: 'colorPalette.subtle.bg',
33
33
  color: 'colorPalette.subtle.fg',
34
34
  },
35
+ rating: {
36
+ bg: 'color-mix(in srgb, var(--colors-color-palette-solid-bg) 35%, transparent)',
37
+ color: 'colorPalette.subtle.fg',
38
+ },
35
39
  outline: {
36
40
  borderWidth: '1px',
37
41
  borderColor: 'colorPalette.outline.border',
@@ -39,11 +43,41 @@ export const badge = defineRecipe({
39
43
  },
40
44
  },
41
45
  size: {
42
- sm: { fontSize: 'xs', px: '1.5', h: '4.5', gap: '0.5', _icon: { boxSize: '2.5' } },
43
- md: { fontSize: 'xs', px: '2', h: '5', gap: '1', _icon: { boxSize: '3' } },
44
- lg: { fontSize: 'xs', px: '2.5', h: '5.5', gap: '1', _icon: { boxSize: '3.5' } },
45
- xl: { fontSize: 'sm', px: '2.5', h: '6', gap: '1.5', _icon: { boxSize: '4' } },
46
- '2xl': { fontSize: 'md', px: '3', h: '7', gap: '1.5', _icon: { boxSize: '4.5' } },
46
+ sm: {
47
+ fontSize: 'xs',
48
+ px: '1.5',
49
+ h: '4.5',
50
+ gap: '0.5',
51
+ _icon: { boxSize: '2.5' },
52
+ },
53
+ md: {
54
+ fontSize: 'xs',
55
+ px: '2',
56
+ h: '5',
57
+ gap: '1',
58
+ _icon: { boxSize: '3' },
59
+ },
60
+ lg: {
61
+ fontSize: 'xs',
62
+ px: '2.5',
63
+ h: '5.5',
64
+ gap: '1',
65
+ _icon: { boxSize: '3.5' },
66
+ },
67
+ xl: {
68
+ fontSize: 'sm',
69
+ px: '2.5',
70
+ h: '6',
71
+ gap: '1.5',
72
+ _icon: { boxSize: '4' },
73
+ },
74
+ '2xl': {
75
+ fontSize: 'md',
76
+ px: '3',
77
+ h: '7',
78
+ gap: '1.5',
79
+ _icon: { boxSize: '4.5' },
80
+ },
47
81
  },
48
82
  },
49
- })
83
+ });
@@ -49,3 +49,4 @@ export * from './content-card';
49
49
  export * from './scenario-card';
50
50
  export * from './scenario-queue';
51
51
  export * from './scenario-settings';
52
+ export * from './studio-controls';