uikit-react-public 0.49.4 → 0.49.6

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 (33) hide show
  1. package/dist/components/Overlay/Overlay.d.ts +2 -2
  2. package/dist/components/Overlay/Overlay.stories.d.ts +13 -1
  3. package/dist/components/Select/index.d.ts +1 -1
  4. package/dist/components/Select/subcomponents/Panel.d.ts +1 -2
  5. package/dist/components/ToggleGroup/ToggleGroup.d.ts +15 -0
  6. package/dist/components/ToggleGroup/ToggleGroup.stories.d.ts +18 -0
  7. package/dist/components/ToggleGroup/ToggleGroupContext.d.ts +8 -0
  8. package/dist/components/ToggleGroup/__tests__/ToggleGroup.test.d.ts +1 -0
  9. package/dist/components/ToggleGroup/index.d.ts +4 -0
  10. package/dist/components/ToggleGroup/subcomponents/Item.d.ts +11 -0
  11. package/dist/components/ToggleGroup/subcomponents/Item.stories.d.ts +331 -0
  12. package/dist/components/ToggleGroup/subcomponents/__tests__/Item.test.d.ts +1 -0
  13. package/dist/components/index.d.ts +2 -0
  14. package/dist/index.js +8351 -8121
  15. package/lib/components/Icon/svgImports.ts +4 -2
  16. package/lib/components/Overlay/Overlay.tsx +9 -3
  17. package/lib/components/Select/Select.mdx +1 -0
  18. package/lib/components/Select/__tests__/Select.test.tsx +137 -4
  19. package/lib/components/Select/index.ts +5 -1
  20. package/lib/components/Select/subcomponents/CustomSelect.tsx +42 -43
  21. package/lib/components/Select/subcomponents/Panel.tsx +2 -8
  22. package/lib/components/ToggleGroup/ToggleGroup.mdx +31 -0
  23. package/lib/components/ToggleGroup/ToggleGroup.stories.tsx +120 -0
  24. package/lib/components/ToggleGroup/ToggleGroup.tsx +144 -0
  25. package/lib/components/ToggleGroup/ToggleGroupContext.tsx +25 -0
  26. package/lib/components/ToggleGroup/__tests__/ToggleGroup.test.tsx +468 -0
  27. package/lib/components/ToggleGroup/index.ts +4 -0
  28. package/lib/components/ToggleGroup/subcomponents/Item.stories.tsx +63 -0
  29. package/lib/components/ToggleGroup/subcomponents/Item.tsx +233 -0
  30. package/lib/components/ToggleGroup/subcomponents/__tests__/Item.test.tsx +195 -0
  31. package/lib/components/index.ts +3 -0
  32. package/package.json +3 -3
  33. package/dist/components/Icon/svgImports.d.ts +0 -8
@@ -0,0 +1,144 @@
1
+ import type React from 'react';
2
+ import {
3
+ Children,
4
+ isValidElement,
5
+ KeyboardEvent,
6
+ useId,
7
+ useState,
8
+ } from 'react';
9
+
10
+ import { css } from '@emotion/css';
11
+ import useTheme from '../../theme/useTheme';
12
+ import useMediaQuery from '../../hooks/useMediaQuery';
13
+ import Select, { NonClearableSelectProps } from '../Select';
14
+ import ToggleGroupContext from './ToggleGroupContext';
15
+ import Item, { ItemProps } from './subcomponents/Item';
16
+
17
+ export interface ToggleGroupProps extends React.HTMLAttributes<HTMLDivElement> {
18
+ children: React.ReactNode;
19
+ value?: string;
20
+ defaultValue?: string;
21
+ onValueChange?: (value: string) => void;
22
+ selectProps?: Omit<
23
+ NonClearableSelectProps<string>,
24
+ 'options' | 'value' | 'onValueChange'
25
+ >;
26
+ }
27
+
28
+ const MOBILE_SELECT_ITEM_THRESHOLD = 2;
29
+
30
+ const NAVIGATION_KEYS = ['ArrowRight', 'ArrowLeft', 'Home', 'End'];
31
+
32
+ const getEnabledItems = (container: HTMLElement) =>
33
+ Array.from(
34
+ container.querySelectorAll<HTMLInputElement>(
35
+ '[role="radio"]:not(:disabled)'
36
+ )
37
+ );
38
+
39
+ const getItemsFromChildren = (
40
+ children: React.ReactNode
41
+ ): React.ReactElement<ItemProps>[] =>
42
+ Children.toArray(children).flatMap((child) => {
43
+ if (!isValidElement<{ children?: React.ReactNode }>(child)) {
44
+ return [];
45
+ }
46
+ if (child.type === Item) {
47
+ return [child as React.ReactElement<ItemProps>];
48
+ }
49
+ return getItemsFromChildren(child.props.children);
50
+ });
51
+
52
+ const ToggleGroup = ({
53
+ children,
54
+ value,
55
+ defaultValue,
56
+ onValueChange,
57
+ selectProps,
58
+ }: ToggleGroupProps) => {
59
+ const [theme] = useTheme();
60
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
61
+ const isControlled = value !== undefined;
62
+ const selectedValue = value ?? uncontrolledValue;
63
+ const isMobile = !useMediaQuery(`(min-width: ${theme.breakpoints.tablet}px)`);
64
+ const items = getItemsFromChildren(children);
65
+ const name = useId();
66
+
67
+ const setValue = (nextValue: string) => {
68
+ if (!isControlled) {
69
+ setUncontrolledValue(nextValue);
70
+ }
71
+
72
+ onValueChange?.(nextValue);
73
+ };
74
+
75
+ const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
76
+ if (!NAVIGATION_KEYS.includes(event.key)) {
77
+ return;
78
+ }
79
+
80
+ const enabledItems = getEnabledItems(event.currentTarget);
81
+ const currentIndex = enabledItems.indexOf(
82
+ document.activeElement as HTMLInputElement
83
+ );
84
+
85
+ if (currentIndex === -1) {
86
+ return;
87
+ }
88
+
89
+ event.preventDefault();
90
+
91
+ const nextIndex =
92
+ {
93
+ ArrowRight: (currentIndex + 1) % enabledItems.length,
94
+ ArrowLeft:
95
+ (currentIndex - 1 + enabledItems.length) % enabledItems.length,
96
+ Home: 0,
97
+ End: enabledItems.length - 1,
98
+ }[event.key] ?? currentIndex;
99
+
100
+ const nextItem = enabledItems[nextIndex];
101
+ nextItem.focus();
102
+ setValue(nextItem.dataset.uclToggleGroupValue as string);
103
+ };
104
+
105
+ const toggleGroupStyle = css`
106
+ display: flex;
107
+ gap: -1px;
108
+ padding: ${theme.padding.p4};
109
+ background-color: ${theme.colour.fill.subtle};
110
+ border-radius: ${theme.radius.r4};
111
+ `;
112
+
113
+ if (isMobile && items.length > MOBILE_SELECT_ITEM_THRESHOLD) {
114
+ return (
115
+ <Select
116
+ {...selectProps}
117
+ options={items.map((item) => ({
118
+ label: item.props.label,
119
+ value: item.props.value,
120
+ }))}
121
+ value={selectedValue}
122
+ onValueChange={setValue}
123
+ />
124
+ );
125
+ }
126
+
127
+ return (
128
+ <ToggleGroupContext.Provider
129
+ value={{ value: selectedValue, setValue, name }}
130
+ >
131
+ <div
132
+ className={toggleGroupStyle}
133
+ role='radiogroup'
134
+ onKeyDown={handleKeyDown}
135
+ >
136
+ {children}
137
+ </div>
138
+ </ToggleGroupContext.Provider>
139
+ );
140
+ };
141
+
142
+ ToggleGroup.Item = Item;
143
+
144
+ export default ToggleGroup;
@@ -0,0 +1,25 @@
1
+ import { createContext, use } from 'react';
2
+
3
+ export interface ToggleGroupContextType {
4
+ value: string | undefined;
5
+ setValue: (value: string) => void;
6
+ name: string;
7
+ }
8
+
9
+ const ToggleGroupContext = createContext<ToggleGroupContextType | undefined>(
10
+ undefined
11
+ );
12
+
13
+ export const useToggleGroupContext = () => {
14
+ const context = use(ToggleGroupContext);
15
+
16
+ if (!context) {
17
+ throw new Error(
18
+ 'ToggleGroupItem must be used within a ToggleGroup component'
19
+ );
20
+ }
21
+
22
+ return context;
23
+ };
24
+
25
+ export default ToggleGroupContext;
@@ -0,0 +1,468 @@
1
+ import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest';
2
+ import { fireEvent, render, screen } from '@testing-library/react';
3
+ import userEvent from '@testing-library/user-event';
4
+ import ToggleGroup from '../ToggleGroup';
5
+ import ToggleGroupItem from '../subcomponents/Item';
6
+ import { ThemeContextProvider } from '../../../theme/useTheme';
7
+
8
+ const mockMatchMedia = (matchesDesktop: boolean) => {
9
+ globalThis.matchMedia = vi.fn().mockImplementation((query) => ({
10
+ matches: query.includes('min-width') ? matchesDesktop : false,
11
+ media: query,
12
+ addListener: vi.fn(),
13
+ removeListener: vi.fn(),
14
+ addEventListener: vi.fn(),
15
+ removeEventListener: vi.fn(),
16
+ dispatchEvent: vi.fn(),
17
+ }));
18
+ };
19
+
20
+ const wrap = (ui: React.ReactElement) =>
21
+ render(<ThemeContextProvider>{ui}</ThemeContextProvider>);
22
+
23
+ describe('ToggleGroup', () => {
24
+ beforeEach(() => {
25
+ mockMatchMedia(true);
26
+ });
27
+
28
+ test('renders a radiogroup with selectable radio items', () => {
29
+ const onValueChange = vi.fn();
30
+
31
+ wrap(
32
+ <ToggleGroup
33
+ defaultValue='option-1'
34
+ onValueChange={onValueChange}
35
+ >
36
+ <ToggleGroupItem
37
+ value='option-1'
38
+ label='Option 1'
39
+ />
40
+ <ToggleGroupItem
41
+ value='option-2'
42
+ label='Option 2'
43
+ />
44
+ </ToggleGroup>
45
+ );
46
+
47
+ expect(screen.getByRole('radiogroup')).toBeInTheDocument();
48
+ expect(screen.getByRole('radio', { name: 'Option 1' })).toHaveAttribute(
49
+ 'aria-checked',
50
+ 'true'
51
+ );
52
+
53
+ fireEvent.click(screen.getByRole('radio', { name: 'Option 2' }));
54
+
55
+ expect(onValueChange).toHaveBeenCalledWith('option-2');
56
+ expect(screen.getByRole('radio', { name: 'Option 2' })).toHaveAttribute(
57
+ 'aria-checked',
58
+ 'true'
59
+ );
60
+ expect(screen.getByRole('radio', { name: 'Option 1' })).toHaveAttribute(
61
+ 'aria-checked',
62
+ 'false'
63
+ );
64
+ });
65
+
66
+ test('only ever has one selected item at a time', () => {
67
+ wrap(
68
+ <ToggleGroup defaultValue='option-1'>
69
+ <ToggleGroupItem
70
+ value='option-1'
71
+ label='Option 1'
72
+ />
73
+ <ToggleGroupItem
74
+ value='option-2'
75
+ label='Option 2'
76
+ />
77
+ <ToggleGroupItem
78
+ value='option-3'
79
+ label='Option 3'
80
+ />
81
+ </ToggleGroup>
82
+ );
83
+
84
+ fireEvent.click(screen.getByRole('radio', { name: 'Option 3' }));
85
+
86
+ const checked = screen
87
+ .getAllByRole('radio')
88
+ .filter((item) => item.getAttribute('aria-checked') === 'true');
89
+
90
+ expect(checked).toHaveLength(1);
91
+ expect(checked[0]).toHaveAccessibleName('Option 3');
92
+ });
93
+
94
+ test('behaves as a controlled component when value is provided', () => {
95
+ const onValueChange = vi.fn();
96
+
97
+ const { rerender } = wrap(
98
+ <ToggleGroup
99
+ value='option-1'
100
+ onValueChange={onValueChange}
101
+ >
102
+ <ToggleGroupItem
103
+ value='option-1'
104
+ label='Option 1'
105
+ />
106
+ <ToggleGroupItem
107
+ value='option-2'
108
+ label='Option 2'
109
+ />
110
+ </ToggleGroup>
111
+ );
112
+
113
+ fireEvent.click(screen.getByRole('radio', { name: 'Option 2' }));
114
+
115
+ expect(onValueChange).toHaveBeenCalledWith('option-2');
116
+ // Selection does not change without the caller updating the value prop
117
+ expect(screen.getByRole('radio', { name: 'Option 1' })).toHaveAttribute(
118
+ 'aria-checked',
119
+ 'true'
120
+ );
121
+
122
+ rerender(
123
+ <ThemeContextProvider>
124
+ <ToggleGroup
125
+ value='option-2'
126
+ onValueChange={onValueChange}
127
+ >
128
+ <ToggleGroupItem
129
+ value='option-1'
130
+ label='Option 1'
131
+ />
132
+ <ToggleGroupItem
133
+ value='option-2'
134
+ label='Option 2'
135
+ />
136
+ </ToggleGroup>
137
+ </ThemeContextProvider>
138
+ );
139
+
140
+ expect(screen.getByRole('radio', { name: 'Option 2' })).toHaveAttribute(
141
+ 'aria-checked',
142
+ 'true'
143
+ );
144
+ });
145
+
146
+ test('moves focus and selection with arrow keys', () => {
147
+ wrap(
148
+ <ToggleGroup defaultValue='option-1'>
149
+ <ToggleGroupItem
150
+ value='option-1'
151
+ label='Option 1'
152
+ />
153
+ <ToggleGroupItem
154
+ value='option-2'
155
+ label='Option 2'
156
+ />
157
+ <ToggleGroupItem
158
+ value='option-3'
159
+ label='Option 3'
160
+ />
161
+ </ToggleGroup>
162
+ );
163
+
164
+ const option1 = screen.getByRole('radio', { name: 'Option 1' });
165
+ const option2 = screen.getByRole('radio', { name: 'Option 2' });
166
+ const option3 = screen.getByRole('radio', { name: 'Option 3' });
167
+
168
+ option1.focus();
169
+ fireEvent.keyDown(screen.getByRole('radiogroup'), { key: 'ArrowRight' });
170
+
171
+ expect(option2).toHaveFocus();
172
+ expect(option2).toHaveAttribute('aria-checked', 'true');
173
+
174
+ fireEvent.keyDown(screen.getByRole('radiogroup'), { key: 'End' });
175
+
176
+ expect(option3).toHaveFocus();
177
+ expect(option3).toHaveAttribute('aria-checked', 'true');
178
+
179
+ fireEvent.keyDown(screen.getByRole('radiogroup'), { key: 'Home' });
180
+
181
+ expect(option1).toHaveFocus();
182
+ expect(option1).toHaveAttribute('aria-checked', 'true');
183
+
184
+ fireEvent.keyDown(screen.getByRole('radiogroup'), { key: 'ArrowLeft' });
185
+
186
+ expect(option3).toHaveFocus();
187
+ expect(option3).toHaveAttribute('aria-checked', 'true');
188
+ });
189
+
190
+ test('skips disabled items when navigating with arrow keys', () => {
191
+ wrap(
192
+ <ToggleGroup defaultValue='option-1'>
193
+ <ToggleGroupItem
194
+ value='option-1'
195
+ label='Option 1'
196
+ />
197
+ <ToggleGroupItem
198
+ value='option-2'
199
+ label='Option 2'
200
+ disabled
201
+ />
202
+ <ToggleGroupItem
203
+ value='option-3'
204
+ label='Option 3'
205
+ />
206
+ </ToggleGroup>
207
+ );
208
+
209
+ const option1 = screen.getByRole('radio', { name: 'Option 1' });
210
+ const option3 = screen.getByRole('radio', { name: 'Option 3' });
211
+
212
+ option1.focus();
213
+ fireEvent.keyDown(screen.getByRole('radiogroup'), { key: 'ArrowRight' });
214
+
215
+ expect(option3).toHaveFocus();
216
+ });
217
+
218
+ test('ignores non-navigation keys', () => {
219
+ wrap(
220
+ <ToggleGroup defaultValue='option-1'>
221
+ <ToggleGroupItem
222
+ value='option-1'
223
+ label='Option 1'
224
+ />
225
+ <ToggleGroupItem
226
+ value='option-2'
227
+ label='Option 2'
228
+ />
229
+ </ToggleGroup>
230
+ );
231
+
232
+ const option1 = screen.getByRole('radio', { name: 'Option 1' });
233
+
234
+ option1.focus();
235
+ fireEvent.keyDown(screen.getByRole('radiogroup'), { key: 'a' });
236
+
237
+ expect(option1).toHaveFocus();
238
+ expect(option1).toHaveAttribute('aria-checked', 'true');
239
+ });
240
+ });
241
+
242
+ describe('ToggleGroup mobile select fallback', () => {
243
+ beforeAll(() => {
244
+ // CustomOption scrolls the selected option into view; jsdom has no layout engine.
245
+ Object.defineProperty(Element.prototype, 'scrollIntoView', {
246
+ value: vi.fn(),
247
+ writable: true,
248
+ });
249
+ });
250
+
251
+ beforeEach(() => {
252
+ mockMatchMedia(false);
253
+ });
254
+
255
+ test('renders the radiogroup on mobile when item count is at or below the threshold', () => {
256
+ wrap(
257
+ <ToggleGroup defaultValue='option-1'>
258
+ <ToggleGroupItem
259
+ value='option-1'
260
+ label='Option 1'
261
+ />
262
+ <ToggleGroupItem
263
+ value='option-2'
264
+ label='Option 2'
265
+ />
266
+ </ToggleGroup>
267
+ );
268
+
269
+ expect(screen.getByRole('radiogroup')).toBeInTheDocument();
270
+ expect(screen.queryByRole('combobox')).not.toBeInTheDocument();
271
+ });
272
+
273
+ test('renders a Select instead of the radiogroup once item count exceeds the threshold', () => {
274
+ wrap(
275
+ <ToggleGroup defaultValue='option-1'>
276
+ <ToggleGroupItem
277
+ value='option-1'
278
+ label='Option 1'
279
+ />
280
+ <ToggleGroupItem
281
+ value='option-2'
282
+ label='Option 2'
283
+ />
284
+ <ToggleGroupItem
285
+ value='option-3'
286
+ label='Option 3'
287
+ />
288
+ </ToggleGroup>
289
+ );
290
+
291
+ expect(screen.queryByRole('radiogroup')).not.toBeInTheDocument();
292
+ expect(screen.getByRole('combobox')).toBeInTheDocument();
293
+ });
294
+
295
+ test('counts items nested in fragments toward the mobile select threshold', async () => {
296
+ const user = userEvent.setup();
297
+
298
+ wrap(
299
+ <ToggleGroup defaultValue='option-1'>
300
+ <>
301
+ <ToggleGroupItem
302
+ value='option-1'
303
+ label='Option 1'
304
+ />
305
+ <ToggleGroupItem
306
+ value='option-2'
307
+ label='Option 2'
308
+ />
309
+ </>
310
+ <ToggleGroupItem
311
+ value='option-3'
312
+ label='Option 3'
313
+ />
314
+ </ToggleGroup>
315
+ );
316
+
317
+ expect(screen.getByRole('combobox')).toBeInTheDocument();
318
+
319
+ await user.click(screen.getByRole('combobox'));
320
+
321
+ expect(
322
+ screen.getByRole('option', { name: 'Option 1' })
323
+ ).toBeInTheDocument();
324
+ expect(
325
+ screen.getByRole('option', { name: 'Option 2' })
326
+ ).toBeInTheDocument();
327
+ expect(
328
+ screen.getByRole('option', { name: 'Option 3' })
329
+ ).toBeInTheDocument();
330
+ });
331
+
332
+ test('updates the selected value when an option is chosen from the mobile select', async () => {
333
+ const user = userEvent.setup();
334
+ const onValueChange = vi.fn();
335
+
336
+ wrap(
337
+ <ToggleGroup
338
+ defaultValue='option-1'
339
+ onValueChange={onValueChange}
340
+ >
341
+ <ToggleGroupItem
342
+ value='option-1'
343
+ label='Option 1'
344
+ />
345
+ <ToggleGroupItem
346
+ value='option-2'
347
+ label='Option 2'
348
+ />
349
+ <ToggleGroupItem
350
+ value='option-3'
351
+ label='Option 3'
352
+ />
353
+ </ToggleGroup>
354
+ );
355
+
356
+ await user.click(screen.getByRole('combobox'));
357
+ await user.click(screen.getByRole('option', { name: 'Option 2' }));
358
+
359
+ expect(onValueChange).toHaveBeenCalledWith('option-2');
360
+ expect(screen.getByRole('combobox')).toHaveTextContent('Option 2');
361
+ });
362
+
363
+ // The Select fallback only forwards `label`/`value` for each item, so a disabled
364
+ // ToggleGroupItem's disabled state is not carried over to the mobile Select option.
365
+ test('does not prevent selection of a disabled item in the mobile select', async () => {
366
+ const user = userEvent.setup();
367
+ const onValueChange = vi.fn();
368
+
369
+ wrap(
370
+ <ToggleGroup
371
+ defaultValue='option-1'
372
+ onValueChange={onValueChange}
373
+ >
374
+ <ToggleGroupItem
375
+ value='option-1'
376
+ label='Option 1'
377
+ />
378
+ <ToggleGroupItem
379
+ value='option-2'
380
+ label='Option 2'
381
+ disabled
382
+ />
383
+ <ToggleGroupItem
384
+ value='option-3'
385
+ label='Option 3'
386
+ />
387
+ </ToggleGroup>
388
+ );
389
+
390
+ await user.click(screen.getByRole('combobox'));
391
+ await user.click(screen.getByRole('option', { name: 'Option 2' }));
392
+
393
+ expect(onValueChange).toHaveBeenCalledWith('option-2');
394
+ });
395
+
396
+ test('renders a native <select> with matching options when selectProps requests the native variant', () => {
397
+ wrap(
398
+ <ToggleGroup
399
+ defaultValue='option-1'
400
+ selectProps={{
401
+ native: true,
402
+ nativeHtmlAttributes: { onChange: vi.fn() },
403
+ }}
404
+ >
405
+ <ToggleGroupItem
406
+ value='option-1'
407
+ label='Option 1'
408
+ />
409
+ <ToggleGroupItem
410
+ value='option-2'
411
+ label='Option 2'
412
+ />
413
+ <ToggleGroupItem
414
+ value='option-3'
415
+ label='Option 3'
416
+ />
417
+ </ToggleGroup>
418
+ );
419
+
420
+ const select = screen.getByTestId(
421
+ 'ucl-uikit-select--native'
422
+ ) as HTMLSelectElement;
423
+
424
+ expect(select.tagName).toBe('SELECT');
425
+ expect(select.value).toBe('option-1');
426
+ expect(
427
+ Array.from(select.querySelectorAll('option')).map(
428
+ (option) => option.textContent
429
+ )
430
+ ).toEqual(['Option 1', 'Option 2', 'Option 3']);
431
+ });
432
+
433
+ test('relies on the caller-supplied nativeHtmlAttributes.onChange rather than onValueChange for native selects', () => {
434
+ const onValueChange = vi.fn();
435
+ const nativeOnChange = vi.fn();
436
+
437
+ wrap(
438
+ <ToggleGroup
439
+ defaultValue='option-1'
440
+ onValueChange={onValueChange}
441
+ selectProps={{
442
+ native: true,
443
+ nativeHtmlAttributes: { onChange: nativeOnChange },
444
+ }}
445
+ >
446
+ <ToggleGroupItem
447
+ value='option-1'
448
+ label='Option 1'
449
+ />
450
+ <ToggleGroupItem
451
+ value='option-2'
452
+ label='Option 2'
453
+ />
454
+ <ToggleGroupItem
455
+ value='option-3'
456
+ label='Option 3'
457
+ />
458
+ </ToggleGroup>
459
+ );
460
+
461
+ fireEvent.change(screen.getByTestId('ucl-uikit-select--native'), {
462
+ target: { value: 'option-2' },
463
+ });
464
+
465
+ expect(nativeOnChange).toHaveBeenCalled();
466
+ expect(onValueChange).not.toHaveBeenCalled();
467
+ });
468
+ });
@@ -0,0 +1,4 @@
1
+ export { default } from './ToggleGroup';
2
+ export type { ToggleGroupProps } from './ToggleGroup';
3
+ export { default as Item } from './subcomponents/Item';
4
+ export type { ItemProps } from './subcomponents/Item';
@@ -0,0 +1,63 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+
3
+ import ToggleGroup from '../ToggleGroup';
4
+ import Item from './Item';
5
+
6
+ const meta = {
7
+ title: 'Components/ToggleGroup/Item',
8
+ component: Item,
9
+ parameters: {
10
+ layout: 'centered',
11
+ },
12
+ tags: ['autodocs'],
13
+ decorators: [
14
+ (Story) => (
15
+ <ToggleGroup defaultValue='option-1'>
16
+ <Story />
17
+ </ToggleGroup>
18
+ ),
19
+ ],
20
+ } satisfies Meta<typeof Item>;
21
+
22
+ export default meta;
23
+
24
+ type Story = StoryObj<typeof meta>;
25
+
26
+ export const Selected: Story = {
27
+ args: {
28
+ value: 'option-1',
29
+ label: 'Selected',
30
+ },
31
+ };
32
+
33
+ export const Unselected: Story = {
34
+ args: {
35
+ value: 'option-2',
36
+ label: 'Unselected',
37
+ },
38
+ };
39
+
40
+ export const WithIcon: Story = {
41
+ args: {
42
+ value: 'option-2',
43
+ label: 'With icon',
44
+ icon: 'Calendar',
45
+ },
46
+ };
47
+
48
+ export const WithSublabel: Story = {
49
+ args: {
50
+ value: 'option-2',
51
+ label: 'With sublabel',
52
+ sublabel: 'Sublabel text',
53
+ icon: 'User',
54
+ },
55
+ };
56
+
57
+ export const Disabled: Story = {
58
+ args: {
59
+ value: 'option-2',
60
+ label: 'Disabled',
61
+ disabled: true,
62
+ },
63
+ };