@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.
- package/dist/{chunk-AZ6QU2L2.cjs → chunk-5H764SHY.cjs} +251 -4
- package/dist/chunk-5H764SHY.cjs.map +1 -0
- package/dist/{chunk-MAVUSE4F.js → chunk-7N32LXVA.js} +18 -17
- package/dist/chunk-7N32LXVA.js.map +1 -0
- package/dist/{chunk-EBDNCZF6.cjs → chunk-BTI7U4DO.cjs} +18 -17
- package/dist/chunk-BTI7U4DO.cjs.map +1 -0
- package/dist/{chunk-4XOWPACJ.js → chunk-WMHVCIDI.js} +251 -4
- package/dist/chunk-WMHVCIDI.js.map +1 -0
- package/dist/components/Breadcrumb.d.ts.map +1 -1
- package/dist/components/StudioControls/StudioControls.d.ts +1 -1
- package/dist/components/StudioControls/StudioControls.d.ts.map +1 -1
- package/dist/components/StudioControls/types.d.ts +3 -0
- package/dist/components/StudioControls/types.d.ts.map +1 -1
- package/dist/components/index.cjs +76 -76
- package/dist/components/index.js +1 -1
- package/dist/figma-codex.json +9 -3
- package/dist/index.cjs +80 -80
- package/dist/index.js +2 -2
- package/dist/panda.buildinfo.json +856 -0
- package/dist/preset/index.cjs +2 -2
- package/dist/preset/index.d.ts.map +1 -1
- package/dist/preset/index.js +1 -1
- package/dist/preset/recipes/studio-controls.d.ts.map +1 -1
- package/docs/component-catalog.md +469 -0
- package/docs/superpowers/plans/2026-04-03-component-catalog-pipeline.md +667 -0
- package/package.json +4 -2
- package/src/components/Breadcrumb.figma.tsx +68 -17
- package/src/components/Breadcrumb.tsx +4 -3
- package/src/components/StudioControls/StudioControls.tsx +13 -12
- package/src/components/StudioControls/types.ts +4 -0
- package/src/components/__tests__/AbsoluteCenter.test.tsx +31 -0
- package/src/components/__tests__/Divider.test.tsx +38 -0
- package/src/components/__tests__/Group.test.tsx +34 -0
- package/src/components/__tests__/Icon.test.tsx +31 -0
- package/src/components/__tests__/SettingsPopover.test.tsx +39 -0
- package/src/components/__tests__/StudioControls.test.tsx +59 -0
- package/src/components/__tests__/Toaster.test.tsx +24 -0
- package/src/preset/index.ts +2 -0
- package/src/preset/recipes/breadcrumb.ts +1 -1
- package/src/preset/recipes/studio-controls.ts +8 -11
- package/dist/chunk-4XOWPACJ.js.map +0 -1
- package/dist/chunk-AZ6QU2L2.cjs.map +0 -1
- package/dist/chunk-EBDNCZF6.cjs.map +0 -1
- package/dist/chunk-MAVUSE4F.js.map +0 -1
- package/docs/context-share/ELEVATION_FIX_PLAN.md +0 -903
- package/docs/context-share/fix-checkbox-radio-tokens.md +0 -145
- 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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<Breadcrumb.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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: '
|
|
126
|
-
fontWeight: '
|
|
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: '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
});
|
package/src/preset/index.ts
CHANGED
|
@@ -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
|
-
|
|
108
|
+
textStyle: 'bodyMedium',
|
|
109
109
|
fontWeight: 'semibold',
|
|
110
110
|
color: 'onSurface',
|
|
111
|
-
lineHeight: '1.2',
|
|
112
111
|
},
|
|
113
112
|
scenarioFocus: {
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
202
|
-
fontWeight: '
|
|
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
|
-
|
|
246
|
-
fontWeight: '
|
|
243
|
+
textStyle: 'bodyMedium',
|
|
244
|
+
fontWeight: 'medium',
|
|
247
245
|
color: 'onSurface',
|
|
248
|
-
lineHeight: '1.4',
|
|
249
246
|
flex: 1,
|
|
250
247
|
},
|
|
251
248
|
},
|