@discourser/design-system 0.22.4 → 0.24.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 (71) hide show
  1. package/dist/{chunk-GLPWI7OF.js → chunk-HN2IHIMR.js} +13 -7
  2. package/dist/chunk-HN2IHIMR.js.map +1 -0
  3. package/dist/{chunk-NN4YW27E.cjs → chunk-KIJKNZ73.cjs} +13 -7
  4. package/dist/chunk-KIJKNZ73.cjs.map +1 -0
  5. package/dist/{chunk-NU6GI57K.js → chunk-VN2QX6S7.js} +437 -3
  6. package/dist/chunk-VN2QX6S7.js.map +1 -0
  7. package/dist/{chunk-WSJLKVXZ.cjs → chunk-VOH2QELR.cjs} +441 -2
  8. package/dist/chunk-VOH2QELR.cjs.map +1 -0
  9. package/dist/components/Icons/AppleLoginIcon.d.ts +6 -0
  10. package/dist/components/Icons/AppleLoginIcon.d.ts.map +1 -0
  11. package/dist/components/Icons/ChevronUpDownIcon.d.ts +6 -0
  12. package/dist/components/Icons/ChevronUpDownIcon.d.ts.map +1 -0
  13. package/dist/components/Icons/GoogleLoginIcon.d.ts +6 -0
  14. package/dist/components/Icons/GoogleLoginIcon.d.ts.map +1 -0
  15. package/dist/components/Icons/LoginIcon.d.ts +6 -0
  16. package/dist/components/Icons/LoginIcon.d.ts.map +1 -0
  17. package/dist/components/Icons/LogoutIcon.d.ts +6 -0
  18. package/dist/components/Icons/LogoutIcon.d.ts.map +1 -0
  19. package/dist/components/Icons/index.d.ts +5 -0
  20. package/dist/components/Icons/index.d.ts.map +1 -1
  21. package/dist/components/SettingsPopover/SettingsPopover.d.ts +3 -0
  22. package/dist/components/SettingsPopover/SettingsPopover.d.ts.map +1 -0
  23. package/dist/components/SettingsPopover/index.d.ts +3 -0
  24. package/dist/components/SettingsPopover/index.d.ts.map +1 -0
  25. package/dist/components/SettingsPopover/types.d.ts +30 -0
  26. package/dist/components/SettingsPopover/types.d.ts.map +1 -0
  27. package/dist/components/divider/divider.d.ts +9 -0
  28. package/dist/components/divider/divider.d.ts.map +1 -0
  29. package/dist/components/divider/index.d.ts +2 -0
  30. package/dist/components/divider/index.d.ts.map +1 -0
  31. package/dist/components/index.cjs +89 -69
  32. package/dist/components/index.d.ts +5 -0
  33. package/dist/components/index.d.ts.map +1 -1
  34. package/dist/components/index.js +1 -1
  35. package/dist/contracts/design-language.contract.d.ts +2 -0
  36. package/dist/contracts/design-language.contract.d.ts.map +1 -1
  37. package/dist/figma-codex.json +2 -2
  38. package/dist/index.cjs +93 -73
  39. package/dist/index.js +2 -2
  40. package/dist/languages/material3.language.d.ts.map +1 -1
  41. package/dist/languages/transform.d.ts +1 -0
  42. package/dist/languages/transform.d.ts.map +1 -1
  43. package/dist/preset/index.cjs +2 -2
  44. package/dist/preset/index.js +1 -1
  45. package/package.json +5 -1
  46. package/src/components/Icons/AppleLoginIcon.tsx +21 -0
  47. package/src/components/Icons/ChevronUpDownIcon.tsx +23 -0
  48. package/src/components/Icons/GoogleLoginIcon.tsx +36 -0
  49. package/src/components/Icons/LoginIcon.tsx +71 -0
  50. package/src/components/Icons/LogoutIcon.tsx +43 -0
  51. package/src/components/Icons/index.ts +8 -0
  52. package/src/components/SettingsPopover/SettingsPopover.test.tsx +256 -0
  53. package/src/components/SettingsPopover/SettingsPopover.tsx +159 -0
  54. package/src/components/SettingsPopover/index.ts +2 -0
  55. package/src/components/SettingsPopover/types.ts +37 -0
  56. package/src/components/divider/divider.tsx +125 -0
  57. package/src/components/divider/index.ts +1 -0
  58. package/src/components/index.ts +14 -0
  59. package/src/contracts/design-language.contract.ts +3 -1
  60. package/src/languages/material3.language.ts +55 -45
  61. package/src/languages/transform.ts +3 -0
  62. package/src/stories/foundations/Typography.mdx +5 -2
  63. package/dist/chunk-GLPWI7OF.js.map +0 -1
  64. package/dist/chunk-NN4YW27E.cjs.map +0 -1
  65. package/dist/chunk-NU6GI57K.js.map +0 -1
  66. package/dist/chunk-WSJLKVXZ.cjs.map +0 -1
  67. package/docs/context-share/STORY-001-VALIDATION-PASSED.md +0 -192
  68. package/docs/context-share/STORY-002-IMPLEMENTATION-COMPLETE.md +0 -161
  69. package/docs/context-share/STORYBOOK_MCP_STRATEGY.md +0 -867
  70. package/docs/context-share/m3-token-pipeline-audit.md +0 -125
  71. package/docs/context-share/storybook-mcp-kai-agent-revised-summary.md +0 -211
@@ -0,0 +1,43 @@
1
+ import { ark } from '@ark-ui/react/factory';
2
+ import type { ComponentProps } from 'react';
3
+ import { styled } from 'styled-system/jsx';
4
+
5
+ const StyledSvg = styled(ark.svg);
6
+
7
+ export type LogoutIconProps = ComponentProps<typeof StyledSvg>;
8
+
9
+ export const LogoutIcon = (props: LogoutIconProps) => (
10
+ <StyledSvg
11
+ viewBox="0 0 40 40"
12
+ fill="none"
13
+ xmlns="http://www.w3.org/2000/svg"
14
+ width="1em"
15
+ height="1em"
16
+ {...props}
17
+ >
18
+ <path
19
+ d="M29.0664 24.3673L33.3331 20.1007L29.0664 15.834"
20
+ stroke="currentColor"
21
+ strokeWidth="2.5"
22
+ strokeMiterlimit="10"
23
+ strokeLinecap="round"
24
+ strokeLinejoin="round"
25
+ />
26
+ <path
27
+ d="M16.2666 20.1006H33.2166"
28
+ stroke="currentColor"
29
+ strokeWidth="2.5"
30
+ strokeMiterlimit="10"
31
+ strokeLinecap="round"
32
+ strokeLinejoin="round"
33
+ />
34
+ <path
35
+ d="M19.5999 33.3337C12.2333 33.3337 6.2666 28.3337 6.2666 20.0003C6.2666 11.667 12.2333 6.66699 19.5999 6.66699"
36
+ stroke="currentColor"
37
+ strokeWidth="2.5"
38
+ strokeMiterlimit="10"
39
+ strokeLinecap="round"
40
+ strokeLinejoin="round"
41
+ />
42
+ </StyledSvg>
43
+ );
@@ -38,3 +38,11 @@ export { UserProfileIcon, type UserProfileIconProps } from './UserProfileIcon';
38
38
  export { PlayIcon, type PlayIconProps } from './PlayIcon';
39
39
  export { SpeechIcon, type SpeechIconProps } from './SpeechIcon';
40
40
  export { TrashIcon, type TrashIconProps } from './TrashIcon';
41
+ export { AppleLoginIcon, type AppleLoginIconProps } from './AppleLoginIcon';
42
+ export { GoogleLoginIcon, type GoogleLoginIconProps } from './GoogleLoginIcon';
43
+ export {
44
+ ChevronUpDownIcon,
45
+ type ChevronUpDownIconProps,
46
+ } from './ChevronUpDownIcon';
47
+ export { LogoutIcon, type LogoutIconProps } from './LogoutIcon';
48
+ export { LoginIcon, type LoginIconProps } from './LoginIcon';
@@ -0,0 +1,256 @@
1
+ /* global describe, it, expect, vi */
2
+ import React from 'react';
3
+ import { render, screen, waitFor } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { axe } from 'jest-axe';
6
+ import { SettingsPopover } from './SettingsPopover';
7
+ import type { SettingsPopoverAction } from './types';
8
+
9
+ // ── Mock icon ─────────────────────────────────────────────────────────────────
10
+ const MockIcon = () =>
11
+ React.createElement('svg', { 'data-testid': 'action-icon' });
12
+
13
+ // ── Fixtures ──────────────────────────────────────────────────────────────────
14
+ const MOCK_ACTIONS: SettingsPopoverAction[] = [
15
+ {
16
+ key: 'logout',
17
+ label: 'Logout',
18
+ icon: React.createElement(MockIcon),
19
+ onClick: vi.fn(),
20
+ },
21
+ ];
22
+
23
+ const defaultProps = {
24
+ userName: 'Will Streeter',
25
+ userTier: 'Free Trial',
26
+ userEmail: 'w.streeter+002@tastymakers.io',
27
+ actions: MOCK_ACTIONS,
28
+ };
29
+
30
+ // ── Tests ─────────────────────────────────────────────────────────────────────
31
+
32
+ describe('SettingsPopover', () => {
33
+ // ── Trigger Rendering ────────────────────────────────────────────────────
34
+ describe('Trigger', () => {
35
+ it('renders a trigger button with the provided aria-label', () => {
36
+ render(
37
+ <SettingsPopover {...defaultProps} ariaLabel="Account settings" />,
38
+ );
39
+ expect(
40
+ screen.getByRole('button', { name: 'Account settings' }),
41
+ ).toBeInTheDocument();
42
+ });
43
+
44
+ it('defaults aria-label to "User settings"', () => {
45
+ render(<SettingsPopover {...defaultProps} />);
46
+ expect(
47
+ screen.getByRole('button', { name: 'User settings' }),
48
+ ).toBeInTheDocument();
49
+ });
50
+
51
+ it('displays the user name on the trigger', () => {
52
+ render(<SettingsPopover {...defaultProps} />);
53
+ expect(screen.getByText('Will Streeter')).toBeInTheDocument();
54
+ });
55
+
56
+ it('displays the user tier on the trigger', () => {
57
+ render(<SettingsPopover {...defaultProps} />);
58
+ expect(screen.getByText('Free Trial')).toBeInTheDocument();
59
+ });
60
+ });
61
+
62
+ // ── Popover Open / Close ─────────────────────────────────────────────────
63
+ describe('Popover Toggle', () => {
64
+ it('popover content is not visible initially', () => {
65
+ render(<SettingsPopover {...defaultProps} />);
66
+ expect(
67
+ screen.queryByText(defaultProps.userEmail),
68
+ ).not.toBeInTheDocument();
69
+ });
70
+
71
+ it('clicking the trigger opens the popover and shows the email', async () => {
72
+ const user = userEvent.setup();
73
+ render(<SettingsPopover {...defaultProps} />);
74
+
75
+ await user.click(screen.getByRole('button', { name: 'User settings' }));
76
+
77
+ await waitFor(() => {
78
+ expect(screen.getByText(defaultProps.userEmail)).toBeInTheDocument();
79
+ });
80
+ });
81
+
82
+ it('clicking the trigger again closes the popover', async () => {
83
+ const user = userEvent.setup();
84
+ render(<SettingsPopover {...defaultProps} />);
85
+
86
+ const trigger = screen.getByRole('button', { name: 'User settings' });
87
+ await user.click(trigger);
88
+
89
+ await waitFor(() => {
90
+ expect(screen.getByText(defaultProps.userEmail)).toBeInTheDocument();
91
+ });
92
+
93
+ await user.click(trigger);
94
+
95
+ await waitFor(() => {
96
+ expect(
97
+ screen.queryByText(defaultProps.userEmail),
98
+ ).not.toBeInTheDocument();
99
+ });
100
+ });
101
+ });
102
+
103
+ // ── Popover Content ──────────────────────────────────────────────────────
104
+ describe('Popover Content', () => {
105
+ it('displays the user email in the popover card', async () => {
106
+ const user = userEvent.setup();
107
+ render(<SettingsPopover {...defaultProps} />);
108
+
109
+ await user.click(screen.getByRole('button', { name: 'User settings' }));
110
+
111
+ await waitFor(() => {
112
+ expect(screen.getByText(defaultProps.userEmail)).toBeInTheDocument();
113
+ });
114
+ });
115
+
116
+ it('renders all action items', async () => {
117
+ const user = userEvent.setup();
118
+ render(<SettingsPopover {...defaultProps} />);
119
+
120
+ await user.click(screen.getByRole('button', { name: 'User settings' }));
121
+
122
+ await waitFor(() => {
123
+ expect(screen.getByText('Logout')).toBeInTheDocument();
124
+ });
125
+ });
126
+
127
+ it('renders action icons when provided', async () => {
128
+ const user = userEvent.setup();
129
+ render(<SettingsPopover {...defaultProps} />);
130
+
131
+ await user.click(screen.getByRole('button', { name: 'User settings' }));
132
+
133
+ await waitFor(() => {
134
+ expect(screen.getByTestId('action-icon')).toBeInTheDocument();
135
+ });
136
+ });
137
+
138
+ it('renders actions without icons when icon is not provided', async () => {
139
+ const user = userEvent.setup();
140
+ const actionsNoIcon: SettingsPopoverAction[] = [
141
+ { key: 'logout', label: 'Logout', onClick: vi.fn() },
142
+ ];
143
+ render(<SettingsPopover {...defaultProps} actions={actionsNoIcon} />);
144
+
145
+ await user.click(screen.getByRole('button', { name: 'User settings' }));
146
+
147
+ await waitFor(() => {
148
+ expect(screen.getByText('Logout')).toBeInTheDocument();
149
+ });
150
+ });
151
+ });
152
+
153
+ // ── Actions ──────────────────────────────────────────────────────────────
154
+ describe('Actions', () => {
155
+ it('clicking an action calls its onClick handler', async () => {
156
+ const user = userEvent.setup();
157
+ const onLogout = vi.fn();
158
+ const actions: SettingsPopoverAction[] = [
159
+ { key: 'logout', label: 'Logout', onClick: onLogout },
160
+ ];
161
+ render(<SettingsPopover {...defaultProps} actions={actions} />);
162
+
163
+ await user.click(screen.getByRole('button', { name: 'User settings' }));
164
+
165
+ await waitFor(() => {
166
+ expect(screen.getByText('Logout')).toBeInTheDocument();
167
+ });
168
+
169
+ await user.click(screen.getByText('Logout'));
170
+ expect(onLogout).toHaveBeenCalledTimes(1);
171
+ });
172
+
173
+ it('renders multiple actions', async () => {
174
+ const user = userEvent.setup();
175
+ const actions: SettingsPopoverAction[] = [
176
+ { key: 'logout', label: 'Logout', onClick: vi.fn() },
177
+ { key: 'settings', label: 'Settings', onClick: vi.fn() },
178
+ ];
179
+ render(<SettingsPopover {...defaultProps} actions={actions} />);
180
+
181
+ await user.click(screen.getByRole('button', { name: 'User settings' }));
182
+
183
+ await waitFor(() => {
184
+ expect(screen.getByText('Logout')).toBeInTheDocument();
185
+ expect(screen.getByText('Settings')).toBeInTheDocument();
186
+ });
187
+ });
188
+ });
189
+
190
+ // ── Avatar ───────────────────────────────────────────────────────────────
191
+ describe('Avatar', () => {
192
+ it('renders an avatar element in the trigger', () => {
193
+ const { container } = render(<SettingsPopover {...defaultProps} />);
194
+ // Avatar.Root renders a span with data-scope="avatar"
195
+ expect(
196
+ container.querySelector('[data-scope="avatar"]'),
197
+ ).toBeInTheDocument();
198
+ });
199
+
200
+ it('uses custom avatarFallback when provided', () => {
201
+ render(
202
+ <SettingsPopover
203
+ {...defaultProps}
204
+ avatarFallback={React.createElement(
205
+ 'span',
206
+ { 'data-testid': 'custom-fallback' },
207
+ 'WS',
208
+ )}
209
+ />,
210
+ );
211
+ expect(screen.getByTestId('custom-fallback')).toBeInTheDocument();
212
+ });
213
+ });
214
+
215
+ // ── Accessibility ────────────────────────────────────────────────────────
216
+ describe('Accessibility', () => {
217
+ it('passes axe audit in closed state', async () => {
218
+ const { container } = render(<SettingsPopover {...defaultProps} />);
219
+ const results = await axe(container);
220
+ expect(results).toHaveNoViolations();
221
+ });
222
+
223
+ it('passes axe audit in open state', async () => {
224
+ const user = userEvent.setup();
225
+ const { container } = render(<SettingsPopover {...defaultProps} />);
226
+
227
+ await user.click(screen.getByRole('button', { name: 'User settings' }));
228
+
229
+ await waitFor(() => {
230
+ expect(screen.getByText(defaultProps.userEmail)).toBeInTheDocument();
231
+ });
232
+
233
+ const results = await axe(container);
234
+ expect(results).toHaveNoViolations();
235
+ });
236
+
237
+ it('trigger is keyboard-focusable', () => {
238
+ render(<SettingsPopover {...defaultProps} />);
239
+ const trigger = screen.getByRole('button', { name: 'User settings' });
240
+ expect(trigger.tagName).toBe('BUTTON');
241
+ });
242
+
243
+ it('action buttons inside the popover are keyboard-focusable', async () => {
244
+ const user = userEvent.setup();
245
+ render(<SettingsPopover {...defaultProps} />);
246
+
247
+ await user.click(screen.getByRole('button', { name: 'User settings' }));
248
+
249
+ await waitFor(() => {
250
+ const logoutButton = screen.getByText('Logout').closest('button');
251
+ expect(logoutButton).toBeInTheDocument();
252
+ expect(logoutButton?.tagName).toBe('BUTTON');
253
+ });
254
+ });
255
+ });
256
+ });
@@ -0,0 +1,159 @@
1
+ 'use client';
2
+
3
+ import { css } from 'styled-system/css';
4
+ import { HStack, VStack } from 'styled-system/jsx';
5
+ import * as Popover from '../Popover';
6
+ import * as Avatar from '../Avatar';
7
+ import { ChevronUpDownIcon } from '../Icons/ChevronUpDownIcon';
8
+ import { LoginIcon } from '../Icons/LoginIcon';
9
+ import type { SettingsPopoverProps } from './types';
10
+
11
+ export function SettingsPopover({
12
+ userName,
13
+ userTier,
14
+ userEmail,
15
+ avatarFallback,
16
+ avatarSrc,
17
+ actions,
18
+ placement = 'top',
19
+ ariaLabel = 'User settings',
20
+ }: SettingsPopoverProps) {
21
+ return (
22
+ <Popover.Root positioning={{ placement, overflowPadding: 0 }}>
23
+ <Popover.Trigger asChild>
24
+ <button
25
+ className={css({
26
+ display: 'flex',
27
+ alignItems: 'center',
28
+ justifyContent: 'space-between',
29
+ w: 'full',
30
+ px: '3',
31
+ py: '3',
32
+ bg: 'surface.container',
33
+ border: 'none',
34
+ borderTopWidth: '1px',
35
+ borderTopStyle: 'solid',
36
+ borderTopColor: 'neutral.3',
37
+ borderRadius: '0',
38
+ boxShadow:
39
+ '0px -1px 2px rgba(0, 0, 0, 0.3), 0px -2px 6px 2px rgba(0, 0, 0, 0.15)',
40
+ cursor: 'pointer',
41
+ transition: 'background 0.2s',
42
+ _hover: {
43
+ bg: 'surface.containerHighest',
44
+ },
45
+ })}
46
+ aria-label={ariaLabel}
47
+ >
48
+ <HStack gap="3">
49
+ <Avatar.Root size="sm">
50
+ {avatarSrc ? (
51
+ <Avatar.Image src={avatarSrc} alt={userName} />
52
+ ) : null}
53
+ <Avatar.Fallback>
54
+ {avatarFallback ?? <LoginIcon width="100%" height="100%" />}
55
+ </Avatar.Fallback>
56
+ </Avatar.Root>
57
+ <VStack gap="0" alignItems="flex-start">
58
+ <span
59
+ className={css({
60
+ fontSize: 'sm',
61
+ fontWeight: 'medium',
62
+ color: 'onSurface',
63
+ lineHeight: 'tight',
64
+ })}
65
+ >
66
+ {userName}
67
+ </span>
68
+ <span
69
+ className={css({
70
+ fontSize: 'xs',
71
+ color: 'fg.muted',
72
+ lineHeight: 'tight',
73
+ })}
74
+ >
75
+ {userTier}
76
+ </span>
77
+ </VStack>
78
+ </HStack>
79
+ <ChevronUpDownIcon w="5" h="5" color="primary.50" flexShrink={0} />
80
+ </button>
81
+ </Popover.Trigger>
82
+
83
+ <Popover.Positioner>
84
+ <Popover.Content
85
+ aria-label={ariaLabel}
86
+ className={css({
87
+ p: '0',
88
+ w: '275px',
89
+ borderRadius: 'l3',
90
+ boxShadow: 'level2',
91
+ overflow: 'hidden',
92
+ })}
93
+ >
94
+ {/* Email display */}
95
+ <div
96
+ className={css({
97
+ px: '4',
98
+ pt: '3',
99
+ pb: '2',
100
+ fontSize: 'bodyMedium',
101
+ color: 'neutral.7',
102
+ overflow: 'hidden',
103
+ textOverflow: 'ellipsis',
104
+ whiteSpace: 'nowrap',
105
+ borderBottom: '1px solid',
106
+ borderColor: 'border',
107
+ })}
108
+ >
109
+ {userEmail}
110
+ </div>
111
+
112
+ {/* Action items */}
113
+ <div
114
+ className={css({
115
+ py: '1',
116
+ })}
117
+ >
118
+ {actions.map((action) => (
119
+ <button
120
+ key={action.key}
121
+ onClick={action.onClick}
122
+ className={css({
123
+ display: 'flex',
124
+ alignItems: 'center',
125
+ gap: '3',
126
+ w: 'full',
127
+ px: '4',
128
+ py: '2.5',
129
+ bg: 'transparent',
130
+ border: 'none',
131
+ cursor: 'pointer',
132
+ fontSize: 'sm',
133
+ color: 'onSurface',
134
+ transition: 'background 0.15s',
135
+ _hover: {
136
+ bg: 'surface.container',
137
+ },
138
+ })}
139
+ >
140
+ {action.icon && (
141
+ <span
142
+ className={css({
143
+ display: 'flex',
144
+ alignItems: 'center',
145
+ color: 'primary.50',
146
+ })}
147
+ >
148
+ {action.icon}
149
+ </span>
150
+ )}
151
+ <span>{action.label}</span>
152
+ </button>
153
+ ))}
154
+ </div>
155
+ </Popover.Content>
156
+ </Popover.Positioner>
157
+ </Popover.Root>
158
+ );
159
+ }
@@ -0,0 +1,2 @@
1
+ export { SettingsPopover } from './SettingsPopover';
2
+ export type { SettingsPopoverProps, SettingsPopoverAction } from './types';
@@ -0,0 +1,37 @@
1
+ import type React from 'react';
2
+
3
+ export interface SettingsPopoverAction {
4
+ /** Unique key for the action */
5
+ key: string;
6
+ /** Display label for the action */
7
+ label: string;
8
+ /** Icon component to render before the label */
9
+ icon?: React.ReactNode;
10
+ /** Click handler */
11
+ onClick: () => void;
12
+ }
13
+
14
+ export interface SettingsPopoverProps {
15
+ /** User's display name (shown on the trigger) */
16
+ userName: string;
17
+ /** User's subscription tier label (shown below the name, e.g. "Free Trial") */
18
+ userTier: string;
19
+ /** User's email address (shown inside the popover card) */
20
+ userEmail: string;
21
+ /** Avatar fallback content — defaults to LoginIcon if not provided */
22
+ avatarFallback?: React.ReactNode;
23
+ /** Avatar image src — if provided, shows photo instead of fallback */
24
+ avatarSrc?: string;
25
+ /** List of action items to display in the popover card */
26
+ actions: SettingsPopoverAction[];
27
+ /** Popover placement relative to trigger (default: 'top') */
28
+ placement?:
29
+ | 'top'
30
+ | 'top-start'
31
+ | 'top-end'
32
+ | 'bottom'
33
+ | 'bottom-start'
34
+ | 'bottom-end';
35
+ /** Optional aria-label for the trigger button (default: 'User settings') */
36
+ ariaLabel?: string;
37
+ }
@@ -0,0 +1,125 @@
1
+ import { forwardRef, type HTMLAttributes } from 'react';
2
+ import { css, cx } from 'styled-system/css';
3
+
4
+ export interface DividerProps extends HTMLAttributes<HTMLDivElement> {
5
+ /** Optional center label text (e.g. "OR") */
6
+ label?: string;
7
+ /** Orientation of the divider */
8
+ orientation?: 'horizontal' | 'vertical';
9
+ }
10
+
11
+ export const Divider = forwardRef<HTMLDivElement, DividerProps>(
12
+ function Divider(
13
+ { label, orientation = 'horizontal', className, ...props },
14
+ ref,
15
+ ) {
16
+ if (orientation === 'vertical') {
17
+ return (
18
+ <div
19
+ ref={ref}
20
+ role="separator"
21
+ aria-orientation="vertical"
22
+ className={cx(
23
+ css({
24
+ display: 'inline-flex',
25
+ flexDirection: 'column',
26
+ alignItems: 'center',
27
+ gap: 'sm',
28
+ alignSelf: 'stretch',
29
+ }),
30
+ className,
31
+ )}
32
+ {...props}
33
+ >
34
+ <div
35
+ className={css({
36
+ flex: 1,
37
+ width: '1px',
38
+ bg: 'outlineVariant',
39
+ })}
40
+ />
41
+ {label && (
42
+ <span
43
+ className={css({
44
+ textStyle: 'labelMedium',
45
+ color: 'onSurfaceVariant',
46
+ userSelect: 'none',
47
+ })}
48
+ >
49
+ {label}
50
+ </span>
51
+ )}
52
+ {label && (
53
+ <div
54
+ className={css({
55
+ flex: 1,
56
+ width: '1px',
57
+ bg: 'outlineVariant',
58
+ })}
59
+ />
60
+ )}
61
+ </div>
62
+ );
63
+ }
64
+
65
+ if (label) {
66
+ return (
67
+ <div
68
+ ref={ref}
69
+ role="separator"
70
+ className={cx(
71
+ css({
72
+ display: 'flex',
73
+ alignItems: 'center',
74
+ gap: 'sm',
75
+ width: 'full',
76
+ }),
77
+ className,
78
+ )}
79
+ {...props}
80
+ >
81
+ <div
82
+ className={css({
83
+ flex: 1,
84
+ height: '1px',
85
+ bg: 'outlineVariant',
86
+ })}
87
+ />
88
+ <span
89
+ className={css({
90
+ textStyle: 'labelMedium',
91
+ color: 'onSurfaceVariant',
92
+ userSelect: 'none',
93
+ })}
94
+ >
95
+ {label}
96
+ </span>
97
+ <div
98
+ className={css({
99
+ flex: 1,
100
+ height: '1px',
101
+ bg: 'outlineVariant',
102
+ })}
103
+ />
104
+ </div>
105
+ );
106
+ }
107
+
108
+ return (
109
+ <div
110
+ ref={ref}
111
+ role="separator"
112
+ aria-orientation="horizontal"
113
+ className={cx(
114
+ css({
115
+ width: 'full',
116
+ height: '1px',
117
+ bg: 'outlineVariant',
118
+ }),
119
+ className,
120
+ )}
121
+ {...props}
122
+ />
123
+ );
124
+ },
125
+ );
@@ -0,0 +1 @@
1
+ export { Divider, type DividerProps } from './divider';
@@ -19,6 +19,9 @@ export { Textarea, type TextareaProps } from './Textarea';
19
19
  // Typography Components (simple)
20
20
  export { Header, type HeadingProps } from './Header';
21
21
 
22
+ // Layout & Utility Components
23
+ export { Divider, type DividerProps } from './divider';
24
+
22
25
  // Feedback & Status Components (simple)
23
26
  export { Badge, type BadgeProps } from './Badge';
24
27
  export { Spinner, type SpinnerProps } from './Spinner';
@@ -108,6 +111,12 @@ export {
108
111
  } from './Icons/UserProfileIcon';
109
112
  export { PlayIcon, type PlayIconProps } from './Icons/PlayIcon';
110
113
  export { SpeechIcon, type SpeechIconProps } from './Icons/SpeechIcon';
114
+ export {
115
+ ChevronUpDownIcon,
116
+ type ChevronUpDownIconProps,
117
+ } from './Icons/ChevronUpDownIcon';
118
+ export { LogoutIcon, type LogoutIconProps } from './Icons/LogoutIcon';
119
+ export { LoginIcon, type LoginIconProps } from './Icons/LoginIcon';
111
120
 
112
121
  // Navigation & Progress Components
113
122
  export * as Breadcrumb from './Breadcrumb';
@@ -121,6 +130,11 @@ export {
121
130
  type NavSection,
122
131
  type NavItem,
123
132
  } from './NavigationMenu';
133
+ export {
134
+ SettingsPopover,
135
+ type SettingsPopoverProps,
136
+ type SettingsPopoverAction,
137
+ } from './SettingsPopover';
124
138
 
125
139
  // Settings Components
126
140
  export {