@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.
- package/dist/{chunk-KIJKNZ73.cjs → chunk-QNCZYFUJ.cjs} +46 -11
- package/dist/chunk-QNCZYFUJ.cjs.map +1 -0
- package/dist/{chunk-VOH2QELR.cjs → chunk-TBLDQATQ.cjs} +377 -12
- package/dist/chunk-TBLDQATQ.cjs.map +1 -0
- package/dist/{chunk-VN2QX6S7.js → chunk-UHSL4N44.js} +378 -14
- package/dist/chunk-UHSL4N44.js.map +1 -0
- package/dist/{chunk-HN2IHIMR.js → chunk-ZPECW4N2.js} +46 -11
- package/dist/chunk-ZPECW4N2.js.map +1 -0
- package/dist/components/Badge.figma.d.ts +2 -0
- package/dist/components/Badge.figma.d.ts.map +1 -0
- package/dist/components/Button.figma.d.ts +2 -0
- package/dist/components/Button.figma.d.ts.map +1 -0
- package/dist/components/Card.figma.d.ts +2 -0
- package/dist/components/Card.figma.d.ts.map +1 -0
- package/dist/components/Icons/AudienceIcon.d.ts.map +1 -1
- package/dist/components/Input.figma.d.ts +2 -0
- package/dist/components/Input.figma.d.ts.map +1 -0
- package/dist/components/RadioGroup.figma.d.ts +2 -0
- package/dist/components/RadioGroup.figma.d.ts.map +1 -0
- package/dist/components/SettingsPopover/SettingsPopover.figma.d.ts +2 -0
- package/dist/components/SettingsPopover/SettingsPopover.figma.d.ts.map +1 -0
- package/dist/components/Slider.figma.d.ts +2 -0
- package/dist/components/Slider.figma.d.ts.map +1 -0
- package/dist/components/Stepper/Stepper.figma.d.ts +2 -0
- package/dist/components/Stepper/Stepper.figma.d.ts.map +1 -0
- package/dist/components/StudioControls/StudioControls.d.ts +3 -0
- package/dist/components/StudioControls/StudioControls.d.ts.map +1 -0
- package/dist/components/StudioControls/StudioControls.figma.d.ts +2 -0
- package/dist/components/StudioControls/StudioControls.figma.d.ts.map +1 -0
- package/dist/components/StudioControls/index.d.ts +3 -0
- package/dist/components/StudioControls/index.d.ts.map +1 -0
- package/dist/components/StudioControls/types.d.ts +17 -0
- package/dist/components/StudioControls/types.d.ts.map +1 -0
- package/dist/components/Switch.figma.d.ts +2 -0
- package/dist/components/Switch.figma.d.ts.map +1 -0
- package/dist/components/index.cjs +78 -74
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +1 -1
- package/dist/figma-codex/parser.d.ts +2 -0
- package/dist/figma-codex/parser.d.ts.map +1 -1
- package/dist/figma-codex/resolver.d.ts.map +1 -1
- package/dist/figma-codex/schema.d.ts +7 -0
- package/dist/figma-codex/schema.d.ts.map +1 -1
- package/dist/figma-codex.json +420 -2
- package/dist/index.cjs +82 -78
- package/dist/index.js +2 -2
- package/dist/preset/index.cjs +2 -2
- package/dist/preset/index.js +1 -1
- package/dist/preset/recipes/badge.d.ts.map +1 -1
- package/dist/preset/recipes/index.d.ts +1 -0
- package/dist/preset/recipes/index.d.ts.map +1 -1
- package/dist/preset/recipes/studio-controls.d.ts +2 -0
- package/dist/preset/recipes/studio-controls.d.ts.map +1 -0
- package/dist/preset/recipes/switch.d.ts.map +1 -1
- package/docs/figma-mcp-return/card-audit.json +31 -0
- package/docs/figma-mcp-return/conversation-prelaunch-audit.json +51 -0
- package/docs/figma-mcp-return/conversation_lobby-design-context.md +359 -0
- package/docs/figma-mcp-return/conversation_lobby-metadata.xml +1 -0
- package/docs/figma-mcp-return/discourser-accordion-audit.json +264 -0
- package/docs/figma-mcp-return/discourser-accordion-design-context-v2.tsx +350 -0
- package/docs/figma-mcp-return/discourser-accordion-design-context.tsx +130 -0
- package/docs/figma-mcp-return/discourser-accordion-metadata-v2.xml +1 -0
- package/docs/figma-mcp-return/discourser-accordion-metadata.xml +1 -0
- package/docs/figma-mcp-return/kai-resolution-simulation.md +181 -0
- package/docs/figma-mcp-return/prelaunch-comparison-analysis.md +126 -0
- package/docs/figma-mcp-return/prelaunch-get-design-context.md +982 -0
- package/docs/figma-mcp-return/prelaunch-get-metadata.md +7 -0
- package/docs/figma-mcp-return/prelaunch-get-metadata.xml +3 -0
- package/docs/figma-mcp-return/prelaunch-post-component-update.md +791 -0
- package/docs/figma-mcp-return/prelaunch-post-rebind-design-context.md +969 -0
- package/docs/figma-mcp-return/radio-group-audit.json +23 -0
- package/docs/figma-mcp-return/switch-audit.json +38 -0
- package/docs/session-summary-2026-03-29.md +98 -0
- package/package.json +2 -1
- package/src/components/Badge.figma.tsx +39 -0
- package/src/components/Button.figma.tsx +18 -0
- package/src/components/Card.figma.tsx +33 -0
- package/src/components/Icons/AudienceIcon.tsx +3 -1
- package/src/components/Icons/AudioSpeakerIcon.tsx +1 -1
- package/src/components/Icons/MicrophoneIcon.tsx +3 -3
- package/src/components/Icons/RecordIcon.tsx +4 -4
- package/src/components/Icons/TimerIcon.tsx +1 -1
- package/src/components/Input.figma.tsx +17 -0
- package/src/components/RadioGroup.figma.tsx +61 -0
- package/src/components/SettingsPopover/SettingsPopover.figma.tsx +17 -0
- package/src/components/Slider.figma.tsx +66 -0
- package/src/components/Stepper/Stepper.figma.tsx +19 -0
- package/src/components/StudioControls/StudioControls.figma.tsx +25 -0
- package/src/components/StudioControls/StudioControls.tsx +381 -0
- package/src/components/StudioControls/index.ts +2 -0
- package/src/components/StudioControls/types.ts +17 -0
- package/src/components/Switch.figma.tsx +49 -0
- package/src/components/index.ts +5 -0
- package/src/figma-codex/parser.ts +55 -0
- package/src/figma-codex/resolver.ts +1 -0
- package/src/figma-codex/schema.ts +9 -0
- package/src/preset/recipes/badge.ts +41 -7
- package/src/preset/recipes/index.ts +1 -0
- package/src/preset/recipes/studio-controls.ts +252 -0
- package/src/preset/recipes/switch.ts +5 -4
- package/dist/chunk-HN2IHIMR.js.map +0 -1
- package/dist/chunk-KIJKNZ73.cjs.map +0 -1
- package/dist/chunk-VN2QX6S7.js.map +0 -1
- 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,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
|
+
);
|
package/src/components/index.ts
CHANGED
|
@@ -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
|
}
|
|
@@ -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: {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
+
});
|