@discourser/design-system 0.26.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/{chunk-AZ6QU2L2.cjs → chunk-5H764SHY.cjs} +251 -4
  2. package/dist/chunk-5H764SHY.cjs.map +1 -0
  3. package/dist/{chunk-MAVUSE4F.js → chunk-7N32LXVA.js} +18 -17
  4. package/dist/chunk-7N32LXVA.js.map +1 -0
  5. package/dist/{chunk-EBDNCZF6.cjs → chunk-BTI7U4DO.cjs} +18 -17
  6. package/dist/chunk-BTI7U4DO.cjs.map +1 -0
  7. package/dist/{chunk-4XOWPACJ.js → chunk-WMHVCIDI.js} +251 -4
  8. package/dist/chunk-WMHVCIDI.js.map +1 -0
  9. package/dist/components/Breadcrumb.d.ts.map +1 -1
  10. package/dist/components/StudioControls/StudioControls.d.ts +1 -1
  11. package/dist/components/StudioControls/StudioControls.d.ts.map +1 -1
  12. package/dist/components/StudioControls/types.d.ts +3 -0
  13. package/dist/components/StudioControls/types.d.ts.map +1 -1
  14. package/dist/components/index.cjs +76 -76
  15. package/dist/components/index.js +1 -1
  16. package/dist/figma-codex.json +9 -3
  17. package/dist/index.cjs +80 -80
  18. package/dist/index.js +2 -2
  19. package/dist/panda.buildinfo.json +856 -0
  20. package/dist/preset/index.cjs +2 -2
  21. package/dist/preset/index.d.ts.map +1 -1
  22. package/dist/preset/index.js +1 -1
  23. package/dist/preset/recipes/studio-controls.d.ts.map +1 -1
  24. package/docs/component-catalog.md +469 -0
  25. package/docs/superpowers/plans/2026-04-03-component-catalog-pipeline.md +667 -0
  26. package/package.json +4 -2
  27. package/src/components/Breadcrumb.figma.tsx +68 -17
  28. package/src/components/Breadcrumb.tsx +4 -3
  29. package/src/components/StudioControls/StudioControls.tsx +13 -12
  30. package/src/components/StudioControls/types.ts +4 -0
  31. package/src/components/__tests__/AbsoluteCenter.test.tsx +31 -0
  32. package/src/components/__tests__/Divider.test.tsx +38 -0
  33. package/src/components/__tests__/Group.test.tsx +34 -0
  34. package/src/components/__tests__/Icon.test.tsx +31 -0
  35. package/src/components/__tests__/SettingsPopover.test.tsx +39 -0
  36. package/src/components/__tests__/StudioControls.test.tsx +59 -0
  37. package/src/components/__tests__/Toaster.test.tsx +24 -0
  38. package/src/preset/index.ts +2 -0
  39. package/src/preset/recipes/breadcrumb.ts +1 -1
  40. package/src/preset/recipes/studio-controls.ts +8 -11
  41. package/dist/chunk-4XOWPACJ.js.map +0 -1
  42. package/dist/chunk-AZ6QU2L2.cjs.map +0 -1
  43. package/dist/chunk-EBDNCZF6.cjs.map +0 -1
  44. package/dist/chunk-MAVUSE4F.js.map +0 -1
  45. package/docs/context-share/ELEVATION_FIX_PLAN.md +0 -903
  46. package/docs/context-share/fix-checkbox-radio-tokens.md +0 -145
  47. package/docs/context-share/icon-component-prompt.md +0 -154
@@ -1,18 +1,69 @@
1
- import figma from '@figma/code-connect'
2
- import * as Breadcrumb from './Breadcrumb'
1
+ import figma from '@figma/code-connect';
2
+ import * as Breadcrumb from './Breadcrumb';
3
3
 
4
- figma.connect(Breadcrumb.Root, 'https://www.figma.com/design/GaHmFfmvO4loUzuZS4TgEz/Discourser.AI--V1?node-id=38-7978', {
5
- example: () => (
6
- <Breadcrumb.Root>
7
- <Breadcrumb.List>
8
- <Breadcrumb.Item>
9
- <Breadcrumb.Link href="#">Home</Breadcrumb.Link>
10
- </Breadcrumb.Item>
11
- <Breadcrumb.Separator />
12
- <Breadcrumb.Item>
13
- <Breadcrumb.CurrentLink>Current</Breadcrumb.CurrentLink>
14
- </Breadcrumb.Item>
15
- </Breadcrumb.List>
16
- </Breadcrumb.Root>
17
- ),
18
- })
4
+ // Basic breadcrumb — standard navigation trail
5
+ figma.connect(
6
+ Breadcrumb.Root,
7
+ 'https://www.figma.com/design/GaHmFfmvO4loUzuZS4TgEz/Discourser.AI--V1?node-id=38-7978',
8
+ {
9
+ example: () => (
10
+ <Breadcrumb.Root>
11
+ <Breadcrumb.List>
12
+ <Breadcrumb.Item>
13
+ <Breadcrumb.Link href="/home">Home</Breadcrumb.Link>
14
+ </Breadcrumb.Item>
15
+ <Breadcrumb.Separator />
16
+ <Breadcrumb.Item>
17
+ <Breadcrumb.CurrentLink>Current Page</Breadcrumb.CurrentLink>
18
+ </Breadcrumb.Item>
19
+ </Breadcrumb.List>
20
+ </Breadcrumb.Root>
21
+ ),
22
+ },
23
+ );
24
+
25
+ // Two-row wizard breadcrumb — used for multi-step flows (e.g. Conversation Studio)
26
+ // Pattern: TwoRowProgressiveDisclosureStep2
27
+ // - ParentRow shows the static parent context (hidden on first step)
28
+ // - Inner Breadcrumb.Root shows the current wizard progress
29
+ // - Protected steps (conversation, results) render as disabled spans, not links
30
+ figma.connect(
31
+ Breadcrumb.TwoRowRoot,
32
+ 'https://www.figma.com/design/GaHmFfmvO4loUzuZS4TgEz/Discourser.AI--V1?node-id=38-7978',
33
+ {
34
+ example: () => (
35
+ <Breadcrumb.TwoRowRoot aria-label="Conversation Studio steps">
36
+ <Breadcrumb.ParentRow show={true}>
37
+ <Breadcrumb.ParentItem href="/scenarios">
38
+ Scenarios
39
+ </Breadcrumb.ParentItem>
40
+ <Breadcrumb.ParentSeparator />
41
+ <Breadcrumb.ParentItem>Conversation Studio</Breadcrumb.ParentItem>
42
+ </Breadcrumb.ParentRow>
43
+ <Breadcrumb.Root>
44
+ <Breadcrumb.List>
45
+ <Breadcrumb.Item>
46
+ <Breadcrumb.Link href="/scenarios/conversation-studio/settings">
47
+ Level Setting
48
+ </Breadcrumb.Link>
49
+ </Breadcrumb.Item>
50
+ <Breadcrumb.Separator />
51
+ <Breadcrumb.Item>
52
+ <Breadcrumb.CurrentLink>Lobby</Breadcrumb.CurrentLink>
53
+ </Breadcrumb.Item>
54
+ <Breadcrumb.Separator />
55
+ <Breadcrumb.Item>
56
+ {/* Protected step — renders as disabled span, not a link */}
57
+ <Breadcrumb.Link disabled>In Conversation</Breadcrumb.Link>
58
+ </Breadcrumb.Item>
59
+ <Breadcrumb.Separator />
60
+ <Breadcrumb.Item>
61
+ {/* Protected step — renders as disabled span, not a link */}
62
+ <Breadcrumb.Link disabled>Results</Breadcrumb.Link>
63
+ </Breadcrumb.Item>
64
+ </Breadcrumb.List>
65
+ </Breadcrumb.Root>
66
+ </Breadcrumb.TwoRowRoot>
67
+ ),
68
+ },
69
+ );
@@ -122,8 +122,8 @@ export const ParentItem = ({
122
122
  }) => {
123
123
  const styles = css({
124
124
  color: 'fg.subtle',
125
- textStyle: 'sm',
126
- fontWeight: 'normal',
125
+ textStyle: 'bodyMedium',
126
+ fontWeight: 'light',
127
127
  textDecoration: 'none',
128
128
  _hover: href
129
129
  ? { color: 'fg.default', textDecoration: 'underline' }
@@ -145,7 +145,8 @@ export const ParentSeparator = () => (
145
145
  aria-hidden="true"
146
146
  className={css({
147
147
  color: 'fg.subtle',
148
- textStyle: 'sm',
148
+ textStyle: 'bodyMedium',
149
+ fontWeight: 'light',
149
150
  mx: '0.5',
150
151
  })}
151
152
  >
@@ -47,6 +47,11 @@ const ALL_SECTION_IDS = [
47
47
 
48
48
  // ── Component ─────────────────────────────────────────────────────────────────
49
49
 
50
+ const DEFAULT_TRIGGER_CSS = {
51
+ fontSize: 'sm',
52
+ fontWeight: 'semibold',
53
+ } as const;
54
+
50
55
  export function StudioControls({
51
56
  scenarioName,
52
57
  scenarioFocus,
@@ -56,12 +61,14 @@ export function StudioControls({
56
61
  defaultRecordingMode = 'no-recording',
57
62
  defaultShowTimer = true,
58
63
  defaultHideInterviewers = false,
64
+ triggerCss,
59
65
  onAudioLevelChange,
60
66
  onMicLevelChange,
61
67
  onRecordingModeChange,
62
68
  onTimerChange,
63
69
  onInterviewersChange,
64
70
  }: StudioControlsProps) {
71
+ const resolvedTriggerCss = { ...DEFAULT_TRIGGER_CSS, ...triggerCss };
65
72
  const styles = studioControls();
66
73
 
67
74
  const [audioLevel, setAudioLevel] = useState(defaultAudioLevel);
@@ -78,8 +85,7 @@ export function StudioControls({
78
85
  <Accordion.ItemTrigger
79
86
  className={styles.sectionTrigger}
80
87
  css={{
81
- fontSize: 'lg',
82
- fontWeight: 'medium',
88
+ ...resolvedTriggerCss,
83
89
  borderRadius: '0',
84
90
  py: '4',
85
91
  bg: 'neutral.1',
@@ -132,8 +138,7 @@ export function StudioControls({
132
138
  <Accordion.ItemTrigger
133
139
  className={styles.sectionTrigger}
134
140
  css={{
135
- fontSize: 'lg',
136
- fontWeight: 'medium',
141
+ ...resolvedTriggerCss,
137
142
  borderRadius: '0',
138
143
  py: '4',
139
144
  bg: 'neutral.1',
@@ -183,8 +188,7 @@ export function StudioControls({
183
188
  <Accordion.ItemTrigger
184
189
  className={styles.sectionTrigger}
185
190
  css={{
186
- fontSize: 'lg',
187
- fontWeight: 'medium',
191
+ ...resolvedTriggerCss,
188
192
  borderRadius: '0',
189
193
  py: '4',
190
194
  bg: 'neutral.1',
@@ -234,8 +238,7 @@ export function StudioControls({
234
238
  <Accordion.ItemTrigger
235
239
  className={styles.sectionTrigger}
236
240
  css={{
237
- fontSize: 'lg',
238
- fontWeight: 'medium',
241
+ ...resolvedTriggerCss,
239
242
  borderRadius: '0',
240
243
  py: '4',
241
244
  bg: 'neutral.1',
@@ -296,8 +299,7 @@ export function StudioControls({
296
299
  <Accordion.ItemTrigger
297
300
  className={styles.sectionTrigger}
298
301
  css={{
299
- fontSize: 'lg',
300
- fontWeight: 'medium',
302
+ ...resolvedTriggerCss,
301
303
  borderRadius: '0',
302
304
  py: '4',
303
305
  bg: 'neutral.1',
@@ -338,8 +340,7 @@ export function StudioControls({
338
340
  <Accordion.ItemTrigger
339
341
  className={styles.sectionTrigger}
340
342
  css={{
341
- fontSize: 'lg',
342
- fontWeight: 'medium',
343
+ ...resolvedTriggerCss,
343
344
  borderRadius: '0',
344
345
  py: '4',
345
346
  bg: 'neutral.1',
@@ -1,6 +1,10 @@
1
+ import type { SystemStyleObject } from 'styled-system/types';
2
+
1
3
  export type RecordingMode = 'no-recording' | 'audio-only' | 'video-audio';
2
4
 
3
5
  export interface StudioControlsProps {
6
+ /** Override CSS for every accordion trigger label — customize font, size, weight from the consuming app */
7
+ triggerCss?: SystemStyleObject;
4
8
  scenarioName: string;
5
9
  scenarioFocus: string;
6
10
  scenarioLevel: 'beginner' | 'intermediate' | 'advanced';
@@ -0,0 +1,31 @@
1
+ /* global describe, it, expect */
2
+ import React from 'react';
3
+ import { render, screen } from '@testing-library/react';
4
+ import { AbsoluteCenter } from '../AbsoluteCenter';
5
+
6
+ describe('AbsoluteCenter', () => {
7
+ it('renders children', () => {
8
+ render(<AbsoluteCenter>Centered content</AbsoluteCenter>);
9
+ expect(screen.getByText('Centered content')).toBeDefined();
10
+ });
11
+
12
+ it('renders as a div element', () => {
13
+ const { container } = render(<AbsoluteCenter>Content</AbsoluteCenter>);
14
+ expect(container.querySelector('div')).toBeDefined();
15
+ });
16
+
17
+ it('accepts and passes through className', () => {
18
+ const { container } = render(
19
+ <AbsoluteCenter className="custom-class">Content</AbsoluteCenter>,
20
+ );
21
+ const el = container.querySelector('.custom-class');
22
+ expect(el).toBeDefined();
23
+ });
24
+
25
+ it('accepts and forwards arbitrary HTML attributes', () => {
26
+ render(
27
+ <AbsoluteCenter data-testid="absolute-center">Content</AbsoluteCenter>,
28
+ );
29
+ expect(screen.getByTestId('absolute-center')).toBeDefined();
30
+ });
31
+ });
@@ -0,0 +1,38 @@
1
+ /* global describe, it, expect */
2
+ import React from 'react';
3
+ import { render, screen } from '@testing-library/react';
4
+ import { Divider } from '../divider';
5
+
6
+ describe('Divider', () => {
7
+ it('renders a div with role="separator" by default', () => {
8
+ render(<Divider />);
9
+ expect(screen.getByRole('separator')).toBeDefined();
10
+ });
11
+
12
+ it('has aria-orientation="horizontal" by default', () => {
13
+ render(<Divider />);
14
+ const el = screen.getByRole('separator');
15
+ expect(el.getAttribute('aria-orientation')).toBe('horizontal');
16
+ });
17
+
18
+ it('renders label text when label prop is provided', () => {
19
+ render(<Divider label="OR" />);
20
+ expect(screen.getByText('OR')).toBeDefined();
21
+ });
22
+
23
+ it('renders aria-orientation="vertical" when orientation="vertical"', () => {
24
+ render(<Divider orientation="vertical" />);
25
+ const el = screen.getByRole('separator');
26
+ expect(el.getAttribute('aria-orientation')).toBe('vertical');
27
+ });
28
+
29
+ it('renders without label when no label prop provided', () => {
30
+ const { container } = render(<Divider />);
31
+ expect(container.querySelector('span')).toBeNull();
32
+ });
33
+
34
+ it('accepts className passthrough', () => {
35
+ const { container } = render(<Divider className="my-divider" />);
36
+ expect(container.querySelector('.my-divider')).toBeDefined();
37
+ });
38
+ });
@@ -0,0 +1,34 @@
1
+ /* global describe, it, expect */
2
+ import React from 'react';
3
+ import { render, screen } from '@testing-library/react';
4
+ import { Group } from '../Group';
5
+
6
+ describe('Group', () => {
7
+ it('renders children', () => {
8
+ render(<Group>Child content</Group>);
9
+ expect(screen.getByText('Child content')).toBeDefined();
10
+ });
11
+
12
+ it('renders as a div', () => {
13
+ const { container } = render(<Group>Content</Group>);
14
+ expect(container.querySelector('div')).toBeDefined();
15
+ });
16
+
17
+ it('accepts data-testid', () => {
18
+ render(<Group data-testid="my-group">Content</Group>);
19
+ expect(screen.getByTestId('my-group')).toBeDefined();
20
+ });
21
+
22
+ it('renders all children', () => {
23
+ render(
24
+ <Group>
25
+ <span>First</span>
26
+ <span>Second</span>
27
+ <span>Third</span>
28
+ </Group>,
29
+ );
30
+ expect(screen.getByText('First')).toBeDefined();
31
+ expect(screen.getByText('Second')).toBeDefined();
32
+ expect(screen.getByText('Third')).toBeDefined();
33
+ });
34
+ });
@@ -0,0 +1,31 @@
1
+ /* global describe, it, expect */
2
+ import React from 'react';
3
+ import { render } from '@testing-library/react';
4
+ import { Icon } from '../Icon';
5
+
6
+ describe('Icon', () => {
7
+ it('renders as an svg element', () => {
8
+ const { container } = render(<Icon />);
9
+ expect(container.querySelector('svg')).toBeDefined();
10
+ });
11
+
12
+ it('renders children (inner SVG content)', () => {
13
+ const { container } = render(
14
+ <Icon>
15
+ <circle cx="12" cy="12" r="10" />
16
+ </Icon>,
17
+ );
18
+ expect(container.querySelector('circle')).toBeDefined();
19
+ });
20
+
21
+ it('accepts className passthrough', () => {
22
+ const { container } = render(<Icon className="custom-icon" />);
23
+ expect(container.querySelector('.custom-icon')).toBeDefined();
24
+ });
25
+
26
+ it('accepts aria-label for accessibility', () => {
27
+ const { container } = render(<Icon aria-label="circle icon" />);
28
+ const svg = container.querySelector('svg');
29
+ expect(svg?.getAttribute('aria-label')).toBe('circle icon');
30
+ });
31
+ });
@@ -0,0 +1,39 @@
1
+ /* global describe, it, expect */
2
+ import React from 'react';
3
+ import { render, screen } from '@testing-library/react';
4
+ import { SettingsPopover } from '../SettingsPopover';
5
+
6
+ const defaultProps = {
7
+ userName: 'Jane Doe',
8
+ userTier: 'Pro',
9
+ userEmail: 'jane@example.com',
10
+ actions: [{ key: 'logout', label: 'Logout', onClick: () => {} }],
11
+ };
12
+
13
+ describe('SettingsPopover', () => {
14
+ it('renders the trigger button with userName', () => {
15
+ render(<SettingsPopover {...defaultProps} />);
16
+ expect(screen.getByText('Jane Doe')).toBeDefined();
17
+ });
18
+
19
+ it('renders userTier in the trigger', () => {
20
+ render(<SettingsPopover {...defaultProps} />);
21
+ expect(screen.getByText('Pro')).toBeDefined();
22
+ });
23
+
24
+ it('trigger button has correct aria-label (default: "User settings")', () => {
25
+ render(<SettingsPopover {...defaultProps} />);
26
+ expect(screen.getByRole('button', { name: 'User settings' })).toBeDefined();
27
+ });
28
+
29
+ it('accepts custom ariaLabel prop', () => {
30
+ render(<SettingsPopover {...defaultProps} ariaLabel="Account menu" />);
31
+ expect(screen.getByRole('button', { name: 'Account menu' })).toBeDefined();
32
+ });
33
+
34
+ it('renders the trigger as a button element', () => {
35
+ render(<SettingsPopover {...defaultProps} />);
36
+ const btn = screen.getByRole('button', { name: 'User settings' });
37
+ expect(btn.tagName.toLowerCase()).toBe('button');
38
+ });
39
+ });
@@ -0,0 +1,59 @@
1
+ /* global describe, it, expect */
2
+ import React from 'react';
3
+ import { render, screen } from '@testing-library/react';
4
+ import { StudioControls } from '../StudioControls';
5
+
6
+ const defaultProps = {
7
+ scenarioName: 'UX Interview Practice',
8
+ scenarioFocus: 'Technical Communication',
9
+ scenarioLevel: 'beginner' as const,
10
+ };
11
+
12
+ describe('StudioControls', () => {
13
+ it('renders the component without crashing', () => {
14
+ const { container } = render(<StudioControls {...defaultProps} />);
15
+ expect(container.firstChild).toBeDefined();
16
+ });
17
+
18
+ it('renders scenarioName text in the document', () => {
19
+ render(<StudioControls {...defaultProps} />);
20
+ expect(screen.getByText('UX Interview Practice')).toBeDefined();
21
+ });
22
+
23
+ it('renders scenarioFocus text in the document', () => {
24
+ render(<StudioControls {...defaultProps} />);
25
+ expect(screen.getByText(/Technical Communication/)).toBeDefined();
26
+ });
27
+
28
+ it('renders "Scenario Settings" accordion trigger text', () => {
29
+ render(<StudioControls {...defaultProps} />);
30
+ // "Scenario Settings" appears in both the accordion trigger and the settings card heading
31
+ const matches = screen.getAllByText('Scenario Settings');
32
+ expect(matches.length).toBeGreaterThan(0);
33
+ });
34
+
35
+ it('renders "Audio Output" accordion trigger text', () => {
36
+ render(<StudioControls {...defaultProps} />);
37
+ expect(screen.getByText('Audio Output')).toBeDefined();
38
+ });
39
+
40
+ it('renders "Microphone Input" accordion trigger text', () => {
41
+ render(<StudioControls {...defaultProps} />);
42
+ expect(screen.getByText('Microphone Input')).toBeDefined();
43
+ });
44
+
45
+ it('renders "A/V Recording" accordion trigger text', () => {
46
+ render(<StudioControls {...defaultProps} />);
47
+ expect(screen.getByText('A/V Recording')).toBeDefined();
48
+ });
49
+
50
+ it('renders "Display Timer" accordion trigger text', () => {
51
+ render(<StudioControls {...defaultProps} />);
52
+ expect(screen.getByText('Display Timer')).toBeDefined();
53
+ });
54
+
55
+ it('renders "Hide Interviewers" accordion trigger text', () => {
56
+ render(<StudioControls {...defaultProps} />);
57
+ expect(screen.getByText('Hide Interviewers')).toBeDefined();
58
+ });
59
+ });
@@ -0,0 +1,24 @@
1
+ /* global describe, it, expect */
2
+ import React from 'react';
3
+ import { render } from '@testing-library/react';
4
+ import { Toaster, toaster } from '../Toast';
5
+
6
+ describe('Toaster', () => {
7
+ it('toaster imperative API exists and is not null', () => {
8
+ expect(toaster).toBeDefined();
9
+ expect(toaster).not.toBeNull();
10
+ });
11
+
12
+ it('toaster has a create method', () => {
13
+ expect(typeof toaster.create).toBe('function');
14
+ });
15
+
16
+ it('Toaster is a function (renderable component)', () => {
17
+ expect(typeof Toaster).toBe('function');
18
+ });
19
+
20
+ it('renders without crashing', () => {
21
+ const { container } = render(<Toaster />);
22
+ expect(container).toBeDefined();
23
+ });
24
+ });
@@ -52,6 +52,7 @@ import { contentCard } from './recipes/content-card';
52
52
  import { scenarioCard } from './recipes/scenario-card';
53
53
  import { scenarioQueue } from './recipes/scenario-queue';
54
54
  import { scenarioSettings } from './recipes/scenario-settings';
55
+ import { studioControls } from './recipes/studio-controls';
55
56
 
56
57
  // Park UI theme extensions
57
58
  import { layerStyles } from './layer-styles';
@@ -195,6 +196,7 @@ export const discourserPandaPreset = definePreset({
195
196
  scenarioCard,
196
197
  scenarioQueue,
197
198
  scenarioSettings,
199
+ studioControls,
198
200
  },
199
201
  },
200
202
  },
@@ -83,8 +83,8 @@ export const breadcrumb = defineSlotRecipe({
83
83
  },
84
84
  link: {
85
85
  color: 'fg.subtle',
86
+ textStyle: 'bodyMedium',
86
87
  fontWeight: 'medium',
87
- fontSize: 'lg',
88
88
  _hover: { color: 'fg.default' },
89
89
  _currentPage: { color: 'fg.default', fontWeight: 'semibold' },
90
90
  },
@@ -105,16 +105,14 @@ export const studioControls = defineSlotRecipe({
105
105
  gap: '1',
106
106
  },
107
107
  scenarioName: {
108
- fontSize: 'lg',
108
+ textStyle: 'bodyMedium',
109
109
  fontWeight: 'semibold',
110
110
  color: 'onSurface',
111
- lineHeight: '1.2',
112
111
  },
113
112
  scenarioFocus: {
114
- fontSize: 'sm',
113
+ textStyle: 'bodyMedium',
115
114
  fontWeight: 'medium',
116
115
  color: 'onSurface',
117
- lineHeight: '1.4',
118
116
  },
119
117
  // Stone-bg inner card — Figma: #F5F1EB ≈ surface.container
120
118
  settingsCard: {
@@ -127,7 +125,7 @@ export const studioControls = defineSlotRecipe({
127
125
  py: '2.5',
128
126
  },
129
127
  settingsCardHeading: {
130
- fontSize: 'md',
128
+ textStyle: 'bodyMedium',
131
129
  fontWeight: 'medium',
132
130
  color: 'onSurface',
133
131
  },
@@ -143,7 +141,7 @@ export const studioControls = defineSlotRecipe({
143
141
  width: 'full',
144
142
  },
145
143
  settingsRowLabel: {
146
- fontSize: 'sm',
144
+ textStyle: 'bodyMedium',
147
145
  fontWeight: 'medium',
148
146
  color: 'onSurface',
149
147
  flex: 1,
@@ -198,8 +196,8 @@ export const studioControls = defineSlotRecipe({
198
196
  width: 'full',
199
197
  },
200
198
  sliderLabelText: {
201
- fontSize: 'md',
202
- fontWeight: 'semibold',
199
+ textStyle: 'bodyMedium',
200
+ fontWeight: 'medium',
203
201
  color: 'onSurface',
204
202
  },
205
203
  levelBadge: {
@@ -242,10 +240,9 @@ export const studioControls = defineSlotRecipe({
242
240
  width: 'full',
243
241
  },
244
242
  toggleDescription: {
245
- fontSize: 'sm',
246
- fontWeight: 'normal',
243
+ textStyle: 'bodyMedium',
244
+ fontWeight: 'medium',
247
245
  color: 'onSurface',
248
- lineHeight: '1.4',
249
246
  flex: 1,
250
247
  },
251
248
  },