@agregio-solutions/design-system 1.90.1 → 1.92.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 (68) hide show
  1. package/dist/design-system.cjs +9 -5
  2. package/dist/design-system.js +14 -6
  3. package/dist/packages/components/Accordion/doc.md +342 -0
  4. package/dist/packages/components/Badge/doc.md +192 -0
  5. package/dist/packages/components/Breadcrumbs/doc.md +332 -0
  6. package/dist/packages/components/Button/doc.md +425 -0
  7. package/dist/packages/components/Calendar/doc.md +465 -0
  8. package/dist/packages/components/ChartLegend/doc.md +151 -0
  9. package/dist/packages/components/ChartTooltip/doc.md +124 -0
  10. package/dist/packages/components/Checkbox/doc.md +329 -0
  11. package/dist/packages/components/CheckboxGroup/doc.md +242 -0
  12. package/dist/packages/components/Chip/doc.md +99 -0
  13. package/dist/packages/components/Combobox/Combobox.d.ts +8 -0
  14. package/dist/packages/components/Combobox/doc.md +680 -0
  15. package/dist/packages/components/DataTable/doc.md +1124 -0
  16. package/dist/packages/components/DatePicker/doc.md +579 -0
  17. package/dist/packages/components/DateRangePicker/doc.md +638 -0
  18. package/dist/packages/components/Drawer/doc.md +338 -0
  19. package/dist/packages/components/Dropdown/Dropdown.d.ts +4 -0
  20. package/dist/packages/components/Dropdown/doc.md +205 -0
  21. package/dist/packages/components/EmptyState/doc.md +101 -0
  22. package/dist/packages/components/FileUpload/doc.md +449 -0
  23. package/dist/packages/components/Filter/doc.md +196 -0
  24. package/dist/packages/components/Header/doc.md +373 -0
  25. package/dist/packages/components/I18nProvider/doc.md +187 -0
  26. package/dist/packages/components/Icon/doc.md +63 -0
  27. package/dist/packages/components/Label/doc.md +60 -0
  28. package/dist/packages/components/LinearProgressBar/doc.md +148 -0
  29. package/dist/packages/components/Link/doc.md +206 -0
  30. package/dist/packages/components/List/doc.md +481 -0
  31. package/dist/packages/components/Loader/doc.md +53 -0
  32. package/dist/packages/components/Menu/Menu.d.ts +5 -1
  33. package/dist/packages/components/Menu/doc.md +231 -0
  34. package/dist/packages/components/Message/doc.md +166 -0
  35. package/dist/packages/components/Modal/doc.md +289 -0
  36. package/dist/packages/components/Navigation/doc.md +992 -0
  37. package/dist/packages/components/NavigationItem/doc.md +167 -0
  38. package/dist/packages/components/NotificationCard/doc.md +206 -0
  39. package/dist/packages/components/Notifications/doc.md +240 -0
  40. package/dist/packages/components/NumberField/doc.md +582 -0
  41. package/dist/packages/components/PageLayout/doc.md +651 -0
  42. package/dist/packages/components/Pagination/doc.md +227 -0
  43. package/dist/packages/components/Popover/doc.md +245 -0
  44. package/dist/packages/components/Radio/doc.md +370 -0
  45. package/dist/packages/components/RouterProvider/doc.md +64 -0
  46. package/dist/packages/components/SearchBar/doc.md +504 -0
  47. package/dist/packages/components/SegmentedControl/doc.md +398 -0
  48. package/dist/packages/components/Select/Select.d.ts +4 -0
  49. package/dist/packages/components/Select/doc.md +1133 -0
  50. package/dist/packages/components/Skeleton/doc.md +129 -0
  51. package/dist/packages/components/Slider/doc.md +362 -0
  52. package/dist/packages/components/Stepper/doc.md +104 -0
  53. package/dist/packages/components/Switch/doc.md +296 -0
  54. package/dist/packages/components/Tabs/doc.md +295 -0
  55. package/dist/packages/components/Tag/doc.md +81 -0
  56. package/dist/packages/components/TextInput/doc.md +490 -0
  57. package/dist/packages/components/TimeField/doc.md +353 -0
  58. package/dist/packages/components/Timeline/doc.md +1046 -0
  59. package/dist/packages/components/Toaster/doc.md +263 -0
  60. package/dist/packages/components/ToggleButton/doc.md +108 -0
  61. package/dist/packages/components/ToggleButtonGroup/doc.md +307 -0
  62. package/dist/packages/components/Tooltip/doc.md +206 -0
  63. package/dist/packages/components/YearMonthPicker/YearMonthPicker.d.ts +8 -0
  64. package/dist/packages/components/YearMonthPicker/doc.md +638 -0
  65. package/dist/public_docs/components.md +68 -0
  66. package/dist/public_docs/index.md +30 -0
  67. package/dist/public_docs/tokens.md +121 -0
  68. package/package.json +3 -2
@@ -0,0 +1,1133 @@
1
+ # Select
2
+
3
+ ## Props
4
+
5
+ The complete Props documentation with JS doc for this component is available at this path:
6
+
7
+ node_modules/@agregio-solutions/design-system/dist/packages/components/Select/Select.d.ts
8
+
9
+ ## Example usage
10
+
11
+ Here are the Storybook Stories. The Select component exposes two distinct modes, each with its own stories file.
12
+
13
+ ### Single mode
14
+
15
+ ```tsx
16
+ import { Meta, StoryObj } from "@storybook/react-vite";
17
+ import { expect, fn, within } from "storybook/test";
18
+
19
+ import Select from "../Select";
20
+ import SelectItem from "../../SelectItem/SelectItem";
21
+ import Badge from "@packages/components/Badge/Badge";
22
+
23
+ const meta: Meta<typeof Select> = {
24
+ component: Select,
25
+ argTypes: {
26
+ open: { control: false },
27
+ helperText: { control: { type: "text" } },
28
+ label: { control: { type: "text" } },
29
+ description: { control: { type: "text" } },
30
+ children: { control: false },
31
+ mode: { control: false },
32
+ errorHelperText: { control: { type: "text" } },
33
+ successHelperText: { control: { type: "text" } },
34
+ selectedKeys: { control: false },
35
+ },
36
+ parameters: {
37
+ layout: "centered",
38
+ },
39
+ globals: {
40
+ backgrounds: { value: "light" },
41
+ },
42
+ };
43
+ export default meta;
44
+
45
+ export const Playground: StoryObj<typeof Select> = {
46
+ args: {
47
+ id: "my-select",
48
+ onChange: fn(),
49
+ onOpenChange: fn(),
50
+ label: "[Insert label]",
51
+ mode: "single",
52
+ children: (
53
+ <>
54
+ <SelectItem text="Option 1" id="1" />
55
+ <SelectItem text="Option 2" id="2" />
56
+ <SelectItem text="Option 3" id="3" />
57
+ <SelectItem text="Option disabled" id="disabled" />
58
+ </>
59
+ ),
60
+ disabledIds: ["disabled"],
61
+ placeholder: "Please select an option",
62
+ labelIconRight: "help_outline",
63
+ labelIconRightTooltip: "Additional information",
64
+ required: true,
65
+ description: "[Insert description]",
66
+ name: "my-select",
67
+ helperText: "[Insert helper]",
68
+ helperTextIcon: "help_outline",
69
+ fullWidth: true,
70
+ wrapperProps: {
71
+ style: { width: 220 },
72
+ },
73
+ },
74
+ play: async ({ canvasElement }) => {
75
+ const canvas = within(canvasElement);
76
+ await canvas.findByTitle("Required");
77
+ await canvas.findByText("[Insert label]");
78
+ await canvas.findByText("[Insert description]");
79
+ await canvas.findByText("[Insert helper]");
80
+ },
81
+ };
82
+
83
+ export const WithLongOptions: StoryObj<typeof Select> = {
84
+ args: {
85
+ ...Playground.args,
86
+ children: (
87
+ <>
88
+ <SelectItem text="Option 1" id="1" />
89
+ <SelectItem
90
+ text={"Very long option that should be truncated"}
91
+ id="very-long"
92
+ />
93
+ </>
94
+ ),
95
+ mode: "single",
96
+ value: "very-long",
97
+ },
98
+ };
99
+
100
+ export const Disabled: StoryObj<typeof Select> = {
101
+ args: {
102
+ ...Playground.args,
103
+ disabled: true,
104
+ },
105
+ };
106
+
107
+ export const Focus: StoryObj<typeof Select> = {
108
+ args: {
109
+ ...Playground.args,
110
+ value: "2",
111
+ buttonProps: {
112
+ ...Playground.args?.buttonProps,
113
+ // @ts-ignore
114
+ "data-force-focus": true,
115
+ },
116
+ },
117
+ };
118
+
119
+ export const WithError: StoryObj<typeof Select> = {
120
+ args: {
121
+ ...Playground.args,
122
+ mode: "single",
123
+ value: "2",
124
+ helperText: undefined,
125
+ errorHelperText: "Error message",
126
+ errorHelperTextIcon: "error_outline",
127
+ },
128
+ };
129
+
130
+ export const WithSuccess: StoryObj<typeof Select> = {
131
+ args: {
132
+ ...Playground.args,
133
+ mode: "single",
134
+ value: "2",
135
+ helperText: undefined,
136
+ successHelperText: "Success message",
137
+ successHelperTextIcon: "check",
138
+ },
139
+ };
140
+
141
+ export const WithWarning: StoryObj<typeof Select> = {
142
+ args: {
143
+ ...Playground.args,
144
+ mode: "single",
145
+ value: "2",
146
+ helperText: undefined,
147
+ warningHelperText: "Warning message",
148
+ warningHelperTextIcon: "warning_amber",
149
+ },
150
+ };
151
+
152
+ export const WithHorizontalOrientation: StoryObj<typeof Select> = {
153
+ args: {
154
+ ...Playground.args,
155
+ fullWidth: false,
156
+ orientation: "horizontal",
157
+ description: undefined,
158
+ wrapperProps: undefined,
159
+ },
160
+ };
161
+
162
+ export const Open: StoryObj<typeof Select> = {
163
+ args: {
164
+ ...Playground.args,
165
+ mode: "single",
166
+ value: "2",
167
+ open: true,
168
+ },
169
+ };
170
+
171
+ export const WithTallDropdown: StoryObj<typeof Select> = {
172
+ args: {
173
+ ...Open.args,
174
+ tallDropdown: true,
175
+ children: (
176
+ <>
177
+ {Array.from({ length: 20 }, (_, i) => (
178
+ <SelectItem key={i + 1} text={`Option ${i + 1}`} id={`${i + 1}`} />
179
+ ))}
180
+ </>
181
+ ),
182
+ },
183
+ play: async () => {
184
+ const listbox = document.querySelector(
185
+ '[role="listbox"][data-tall="true"]',
186
+ );
187
+ await expect(listbox).not.toBeNull();
188
+ },
189
+ };
190
+
191
+ export const WithTriggerContentRight: StoryObj<typeof Select> = {
192
+ args: {
193
+ ...Playground.args,
194
+ wrapperProps: {
195
+ style: { width: 300 },
196
+ },
197
+ triggerContentRight: <Badge value="+3" />,
198
+ },
199
+ };
200
+
201
+ export const FullWidth: StoryObj<typeof Select> = {
202
+ parameters: {
203
+ ...meta.parameters,
204
+ layout: "padded",
205
+ },
206
+ args: {
207
+ ...Playground.args,
208
+ wrapperProps: undefined,
209
+ buttonProps: undefined,
210
+ fullWidth: true,
211
+ },
212
+ };
213
+ ```
214
+
215
+ ### Multiple mode
216
+
217
+ ```tsx
218
+ import { Meta, StoryObj } from "@storybook/react-vite";
219
+ import { expect, fn, within } from "storybook/test";
220
+ import Select from "../Select";
221
+ import SelectItem from "@components/SelectItem/SelectItem";
222
+ import { useState } from "react";
223
+ import { I18nProvider } from "react-aria";
224
+ import Badge from "@packages/components/Badge/Badge";
225
+
226
+ const meta: Meta<typeof Select> = {
227
+ component: Select,
228
+ argTypes: {
229
+ open: { control: false },
230
+ helperText: { control: { type: "text" } },
231
+ label: { control: { type: "text" } },
232
+ description: { control: { type: "text" } },
233
+ children: { control: false },
234
+ mode: { control: false },
235
+ errorHelperText: { control: { type: "text" } },
236
+ successHelperText: { control: { type: "text" } },
237
+ selectedKeys: { control: false },
238
+ },
239
+ parameters: {
240
+ layout: "centered",
241
+ },
242
+ globals: {
243
+ backgrounds: { value: "light" },
244
+ },
245
+ render: (args) => {
246
+ const Form = () => {
247
+ const [selectedKeys, setSelectedKeys] = useState<Array<string>>(() => {
248
+ if (args.mode === "multiple" && args.selectedKeys) {
249
+ return args.selectedKeys;
250
+ }
251
+ return [];
252
+ });
253
+
254
+ return (
255
+ <I18nProvider locale="en-EN">
256
+ <div style={{ marginBottom: 16 }}>
257
+ Debug selected values : {[...selectedKeys].join(", ")}
258
+ </div>
259
+ <Select
260
+ {...args}
261
+ mode="multiple"
262
+ selectedKeys={selectedKeys}
263
+ onSelectionChange={setSelectedKeys}
264
+ >
265
+ {args.children}
266
+ </Select>
267
+ </I18nProvider>
268
+ );
269
+ };
270
+
271
+ return <Form />;
272
+ },
273
+ };
274
+ export default meta;
275
+
276
+ export const Playground: StoryObj<typeof Select> = {
277
+ args: {
278
+ id: "my-select",
279
+ onOpenChange: fn(),
280
+ onSelectionChange: fn(),
281
+ label: "[Insert label]",
282
+ mode: "multiple",
283
+ children: (
284
+ <>
285
+ <SelectItem text="Option 1" id="1" />
286
+ <SelectItem text="Option 2" id="2" />
287
+ <SelectItem text="Option 3" id="3" />
288
+ <SelectItem text="Option disabled" id="disabled" />
289
+ </>
290
+ ),
291
+ disabledIds: ["disabled"],
292
+ placeholder: "Please select an option",
293
+ labelIconRight: "help_outline",
294
+ labelIconRightTooltip: "Additional information",
295
+ required: true,
296
+ description: "[Insert description]",
297
+ helperText: "[Insert helper]",
298
+ helperTextIcon: "help_outline",
299
+ fullWidth: true,
300
+ wrapperProps: {
301
+ style: { width: 220 },
302
+ },
303
+ },
304
+ play: async ({ canvasElement }) => {
305
+ const canvas = within(canvasElement);
306
+ await canvas.findByTitle("Required");
307
+ await canvas.findByText("[Insert label]");
308
+ await canvas.findByText("[Insert description]");
309
+ await canvas.findByText("[Insert helper]");
310
+ },
311
+ };
312
+
313
+ export const Disabled: StoryObj<typeof Select> = {
314
+ args: {
315
+ ...Playground.args,
316
+ disabled: true,
317
+ },
318
+ };
319
+
320
+ export const Focus: StoryObj<typeof Select> = {
321
+ args: {
322
+ ...Playground.args,
323
+ buttonProps: {
324
+ ...Playground.args?.buttonProps,
325
+ // @ts-ignore
326
+ "data-force-focus": true,
327
+ },
328
+ },
329
+ };
330
+
331
+ export const WithError: StoryObj<typeof Select> = {
332
+ args: {
333
+ ...Playground.args,
334
+ helperText: undefined,
335
+ errorHelperText: "Error message",
336
+ errorHelperTextIcon: "error_outline",
337
+ },
338
+ };
339
+
340
+ export const WithSuccess: StoryObj<typeof Select> = {
341
+ args: {
342
+ ...Playground.args,
343
+ helperText: undefined,
344
+ successHelperText: "Success message",
345
+ successHelperTextIcon: "check",
346
+ },
347
+ };
348
+
349
+ export const WithWarning: StoryObj<typeof Select> = {
350
+ args: {
351
+ ...Playground.args,
352
+ helperText: undefined,
353
+ warningHelperText: "Warning message",
354
+ warningHelperTextIcon: "warning_amber",
355
+ },
356
+ };
357
+
358
+ export const WithHorizontalOrientation: StoryObj<typeof Select> = {
359
+ args: {
360
+ ...Playground.args,
361
+ fullWidth: false,
362
+ orientation: "horizontal",
363
+ description: undefined,
364
+ wrapperProps: undefined,
365
+ },
366
+ };
367
+
368
+ export const Open: StoryObj<typeof Select> = {
369
+ args: {
370
+ ...Playground.args,
371
+ open: true,
372
+ },
373
+ };
374
+
375
+ export const WithTallDropdown: StoryObj<typeof Select> = {
376
+ args: {
377
+ ...Open.args,
378
+ tallDropdown: true,
379
+ children: (
380
+ <>
381
+ {Array.from({ length: 20 }, (_, i) => (
382
+ <SelectItem key={i + 1} text={`Option ${i + 1}`} id={`${i + 1}`} />
383
+ ))}
384
+ </>
385
+ ),
386
+ },
387
+ play: async () => {
388
+ const listbox = document.querySelector(
389
+ '[role="listbox"][data-tall="true"]',
390
+ );
391
+ await expect(listbox).not.toBeNull();
392
+ },
393
+ };
394
+
395
+ export const WithTriggerContentRight: StoryObj<typeof Select> = {
396
+ args: {
397
+ ...Playground.args,
398
+ wrapperProps: {
399
+ style: { width: 300 },
400
+ },
401
+ triggerContentRight: <Badge value="+3" />,
402
+ },
403
+ };
404
+
405
+ export const FullWidth: StoryObj<typeof Select> = {
406
+ parameters: {
407
+ ...meta.parameters,
408
+ layout: "padded",
409
+ },
410
+ args: {
411
+ ...Playground.args,
412
+ wrapperProps: undefined,
413
+ buttonProps: undefined,
414
+ fullWidth: true,
415
+ },
416
+ };
417
+ ```
418
+
419
+ ## How to test this component
420
+
421
+ Here are some more advanced stories with more testing coverage and examples that you can read to understand how to test this component.
422
+
423
+ ### Single mode
424
+
425
+ ```tsx
426
+ import { Meta, StoryObj } from "@storybook/react-vite";
427
+ import { userEvent, within, screen, expect } from "storybook/test";
428
+
429
+ import Select from "../../Select";
430
+ import SelectItem from "../../../SelectItem/SelectItem";
431
+ import { expectNotPresent } from "@internal/test-utils-storybook/test-utils-storybook";
432
+ import { useState } from "react";
433
+ import { useController, useForm } from "react-hook-form";
434
+ import { Playground } from "../SingleMode.stories";
435
+
436
+ const meta: Meta<typeof Select> = {
437
+ component: Select,
438
+ argTypes: {
439
+ open: { control: { type: "boolean" } },
440
+ helperText: { control: { type: "text" } },
441
+ label: { control: { type: "text" } },
442
+ description: { control: { type: "text" } },
443
+ },
444
+ parameters: {
445
+ layout: "centered",
446
+ chromatic: { disableSnapshot: true },
447
+ },
448
+ globals: {
449
+ backgrounds: { value: "light" },
450
+ },
451
+ };
452
+ export default meta;
453
+
454
+ export const TestShouldSelectAValue: StoryObj<typeof Select> = {
455
+ args: {
456
+ ...Playground.args,
457
+ },
458
+ play: async ({ canvasElement, args }) => {
459
+ const canvas = within(canvasElement);
460
+ const user = userEvent.setup({ delay: 50 });
461
+
462
+ await user.click(canvas.getByLabelText("[Insert label]"));
463
+ await screen.findByText("Option 2", { selector: "span" });
464
+ await user.click(screen.getByText("Option 2", { selector: "span" }));
465
+ await expectNotPresent(() =>
466
+ screen.queryByText("Option 1", { selector: "span" }),
467
+ );
468
+ await expect(canvas.getByLabelText("[Insert label]")).toHaveTextContent(
469
+ "Option 2",
470
+ );
471
+ await expect((args as any).onChange).toHaveBeenCalledWith("2");
472
+ },
473
+ };
474
+
475
+ export const TestWithCustomAriaLabel: StoryObj<typeof Select> = {
476
+ args: {
477
+ ...Playground.args,
478
+ label: (
479
+ <div>
480
+ Something <i>not</i> serializable
481
+ </div>
482
+ ),
483
+ "aria-label": "Select me",
484
+ },
485
+ play: async ({ canvasElement, args }) => {
486
+ const canvas = within(canvasElement);
487
+ const user = userEvent.setup({ delay: 50 });
488
+
489
+ await user.click(canvas.getByLabelText("Select me"));
490
+ await screen.findByText("Option 2", { selector: "span" });
491
+ await user.click(screen.getByText("Option 2", { selector: "span" }));
492
+ await expectNotPresent(() =>
493
+ screen.queryByText("Option 1", { selector: "span" }),
494
+ );
495
+ await expect(canvas.getByLabelText("Select me")).toHaveTextContent(
496
+ "Option 2",
497
+ );
498
+ await expect((args as any).onChange).toHaveBeenCalledWith("2");
499
+ },
500
+ };
501
+
502
+ export const TestShouldNotSelectADisabledOption: StoryObj<typeof Select> = {
503
+ args: {
504
+ ...Playground.args,
505
+ },
506
+ play: async ({ canvasElement, args }) => {
507
+ const canvas = within(canvasElement);
508
+ const user = userEvent.setup({ delay: 50 });
509
+ await user.click(canvas.getByLabelText("[Insert label]"));
510
+ await screen.findByText("Option disabled", { selector: "span" });
511
+ await user.click(screen.getByText("Option disabled", { selector: "span" }));
512
+ await expect(canvas.getByLabelText("[Insert label]")).not.toHaveTextContent(
513
+ "Option disabled",
514
+ );
515
+ await expect((args as any).onChange).not.toHaveBeenCalled();
516
+ },
517
+ };
518
+
519
+ export const TestDefaultOpened: StoryObj<typeof Select> = {
520
+ args: {
521
+ ...Playground.args,
522
+ defaultOpen: true,
523
+ },
524
+ play: async () => {
525
+ await screen.findByText("Option 1", { selector: "span" });
526
+ await screen.findByText("Option 2", { selector: "span" });
527
+ await screen.findByText("Option 3", { selector: "span" });
528
+ await screen.findByText("Option disabled", { selector: "span" });
529
+ },
530
+ };
531
+
532
+ export const TestDisabledSelect: StoryObj<typeof Select> = {
533
+ args: {
534
+ ...Playground.args,
535
+ disabled: true,
536
+ value: "2",
537
+ mode: "single",
538
+ },
539
+ play: async ({ canvasElement }) => {
540
+ const canvas = within(canvasElement);
541
+ const user = userEvent.setup({ delay: 50 });
542
+ await expect(canvas.getByLabelText("[Insert label]")).toHaveTextContent(
543
+ "Option 2",
544
+ );
545
+ await expect(canvas.getByLabelText("[Insert label]")).toBeDisabled();
546
+ await user.click(canvas.getByLabelText("[Insert label]"));
547
+ await expectNotPresent(() =>
548
+ screen.queryByText("Option 1", { selector: "span" }),
549
+ );
550
+ await expectNotPresent(() =>
551
+ screen.queryByText("Option disabled", { selector: "span" }),
552
+ );
553
+ },
554
+ };
555
+
556
+ export const ExampleControlled: StoryObj<typeof Select> = {
557
+ render: () => {
558
+ const Form = () => {
559
+ const [value, setValue] = useState<string>("");
560
+
561
+ return (
562
+ <>
563
+ <div>The selected value is : {value}</div>
564
+ <Select
565
+ id="my-select"
566
+ label="[Insert label]"
567
+ value={value}
568
+ onChange={setValue}
569
+ mode="single"
570
+ >
571
+ <SelectItem text="Option 1" id="1" />
572
+ <SelectItem text="Option 2" id="2" />
573
+ <SelectItem text="Option 3" id="3" />
574
+ </Select>
575
+ </>
576
+ );
577
+ };
578
+
579
+ return <Form />;
580
+ },
581
+ };
582
+
583
+ export const TestControlledMode: StoryObj<typeof Select> = {
584
+ render: ExampleControlled.render,
585
+ play: async ({ canvasElement }) => {
586
+ const canvas = within(canvasElement);
587
+ const user = userEvent.setup({ delay: 50 });
588
+ await user.click(canvas.getByLabelText("[Insert label]")); // Open the dropdown
589
+ await screen.findByText("Option 2", { selector: "span" }); // Wait for the option to show up
590
+ await user.click(screen.getByText("Option 2", { selector: "span" })); // Click on the option
591
+ await expect(canvas.getByLabelText("[Insert label]")).toHaveTextContent(
592
+ "Option 2",
593
+ );
594
+ await canvas.findByText("The selected value is : 2"); // Check that the value is selected (will probably be different depending on the implementation)
595
+ },
596
+ };
597
+
598
+ export const ExampleWithReactHookForm: StoryObj<typeof Select> = {
599
+ render: (args) => {
600
+ const ReactHookFormExample = () => {
601
+ const { control } = useForm<any>();
602
+ const selectField = useController({ control, name: "select" });
603
+
604
+ return (
605
+ <>
606
+ <div>The selected value is : {selectField.field.value}</div>
607
+ <Select
608
+ id="my-select"
609
+ label="[Insert label]"
610
+ value={selectField.field.value}
611
+ onChange={selectField.field.onChange}
612
+ mode="single"
613
+ >
614
+ <SelectItem text="Option 1" id="1" />
615
+ <SelectItem text="Option 2" id="2" />
616
+ <SelectItem text="Option 3" id="3" />
617
+ </Select>
618
+ </>
619
+ );
620
+ };
621
+
622
+ return <ReactHookFormExample {...args} />;
623
+ },
624
+ };
625
+
626
+ export const TestReactHookForm: StoryObj<typeof Select> = {
627
+ render: ExampleWithReactHookForm.render as any,
628
+ play: async ({ canvasElement }) => {
629
+ const canvas = within(canvasElement);
630
+ const user = userEvent.setup({ delay: 50 });
631
+ await user.click(canvas.getByLabelText("[Insert label]"));
632
+ await screen.findByText("Option 2", { selector: "span" });
633
+ await user.click(screen.getByText("Option 2", { selector: "span" }));
634
+ await expectNotPresent(() =>
635
+ screen.queryByText("Option 1", { selector: "span" }),
636
+ );
637
+ await expect(canvas.getByLabelText("[Insert label]")).toHaveTextContent(
638
+ "Option 2",
639
+ );
640
+ await canvas.findByText("The selected value is : 2");
641
+ },
642
+ };
643
+ ```
644
+
645
+ ### Multiple mode
646
+
647
+ ```tsx
648
+ import { Meta, StoryObj } from "@storybook/react-vite";
649
+ import { userEvent, within, screen, expect } from "storybook/test";
650
+ import Select from "../../Select";
651
+ import SelectItem from "@components/SelectItem/SelectItem";
652
+ import {
653
+ expectNotPresent,
654
+ expectPresent,
655
+ } from "@internal/test-utils-storybook/test-utils-storybook";
656
+ import { useController, useForm } from "react-hook-form";
657
+ import baseMeta, { Playground } from "../MultipleMode.stories";
658
+
659
+ const meta: Meta<typeof Select> = {
660
+ component: Select,
661
+ argTypes: {
662
+ ...baseMeta.argTypes,
663
+ },
664
+ parameters: {
665
+ ...baseMeta.parameters,
666
+ chromatic: { disableSnapshot: true },
667
+ },
668
+ render: baseMeta.render,
669
+ };
670
+ export default meta;
671
+
672
+ export const TestShouldSelectAValue: StoryObj<typeof Select> = {
673
+ args: {
674
+ ...Playground.args,
675
+ },
676
+ parameters: {
677
+ chromatic: { disableSnapshot: true },
678
+ },
679
+ play: async ({ canvasElement }) => {
680
+ const canvas = within(canvasElement);
681
+ const user = userEvent.setup({ delay: 50 });
682
+ await user.click(canvas.getByLabelText("[Insert label]"));
683
+ await screen.findByText("Option 2", { selector: "span" });
684
+ await user.click(screen.getByText("Option 2", { selector: "span" }));
685
+ await expect(canvas.getByLabelText("[Insert label]")).toHaveTextContent(
686
+ "1",
687
+ );
688
+ await expect(canvas.getByLabelText("[Insert label]")).toHaveTextContent(
689
+ "item selected",
690
+ );
691
+ await canvas.findByText("Debug selected values : 2");
692
+ await user.click(canvas.getByLabelText("[Insert label]"));
693
+ await expectNotPresent(() =>
694
+ canvas.queryByText("Option 2", { selector: "span" }),
695
+ );
696
+ },
697
+ };
698
+
699
+ export const TestWithCustomAriaLabel: StoryObj<typeof Select> = {
700
+ args: {
701
+ ...Playground.args,
702
+ label: (
703
+ <div>
704
+ Something <i>not</i> serializable
705
+ </div>
706
+ ),
707
+ "aria-label": "Select me",
708
+ },
709
+ parameters: {
710
+ chromatic: { disableSnapshot: true },
711
+ },
712
+ play: async ({ canvasElement }) => {
713
+ const canvas = within(canvasElement);
714
+ const user = userEvent.setup({ delay: 50 });
715
+ await user.click(canvas.getByLabelText("Select me"));
716
+ await screen.findByText("Option 2", { selector: "span" });
717
+ await user.click(screen.getByText("Option 2", { selector: "span" }));
718
+ await expect(canvas.getByLabelText("Select me")).toHaveTextContent("1");
719
+ await expect(canvas.getByLabelText("Select me")).toHaveTextContent(
720
+ "item selected",
721
+ );
722
+ await canvas.findByText("Debug selected values : 2");
723
+ await user.click(canvas.getByLabelText("Select me"));
724
+ await expectNotPresent(() =>
725
+ canvas.queryByText("Option 2", { selector: "span" }),
726
+ );
727
+ },
728
+ };
729
+
730
+ export const TestShouldDeselectAValue: StoryObj<typeof Select> = {
731
+ args: {
732
+ ...Playground.args,
733
+ mode: "multiple",
734
+ selectedKeys: ["2"],
735
+ },
736
+ parameters: {
737
+ chromatic: { disableSnapshot: true },
738
+ },
739
+ play: async ({ canvasElement }) => {
740
+ const canvas = within(canvasElement);
741
+ const user = userEvent.setup({ delay: 50 });
742
+ await user.click(canvas.getByLabelText("[Insert label]"));
743
+ await screen.findByText("Option 2", { selector: "span" });
744
+ await user.click(screen.getByText("Option 2", { selector: "span" }));
745
+ await expect(canvas.getByLabelText("[Insert label]")).toHaveTextContent(
746
+ "Please select an option",
747
+ );
748
+ await canvas.findByText("Debug selected values :");
749
+ await user.click(canvas.getByLabelText("[Insert label]"));
750
+ await expectNotPresent(() =>
751
+ canvas.queryByText("Option 2", { selector: "span" }),
752
+ );
753
+ },
754
+ };
755
+
756
+ export const TestShouldSelectMultipleValues: StoryObj<typeof Select> = {
757
+ args: {
758
+ ...Playground.args,
759
+ },
760
+ parameters: {
761
+ chromatic: { disableSnapshot: true },
762
+ },
763
+ play: async ({ canvasElement }) => {
764
+ const canvas = within(canvasElement);
765
+ const user = userEvent.setup({ delay: 50 });
766
+ await user.click(canvas.getByLabelText("[Insert label]"));
767
+ await screen.findByText("Option 2", { selector: "span" });
768
+ await user.click(screen.getByText("Option 1", { selector: "span" }));
769
+ await user.click(screen.getByText("Option 2", { selector: "span" }));
770
+ await user.click(screen.getByText("Option 3", { selector: "span" }));
771
+ await user.click(screen.getByText("Option 2", { selector: "span" }));
772
+ await expect(canvas.getByLabelText("[Insert label]")).toHaveTextContent(
773
+ "2",
774
+ );
775
+ await expect(canvas.getByLabelText("[Insert label]")).toHaveTextContent(
776
+ "items selected",
777
+ );
778
+ await canvas.findByText("Debug selected values : 1, 3");
779
+ await user.click(canvas.getByLabelText("[Insert label]"));
780
+ await expectNotPresent(() =>
781
+ canvas.queryByText("Option 1", { selector: "span" }),
782
+ );
783
+ },
784
+ };
785
+
786
+ export const TestShouldNotSelectADisabledOption: StoryObj<typeof Select> = {
787
+ args: {
788
+ ...Playground.args,
789
+ },
790
+ parameters: {
791
+ chromatic: { disableSnapshot: true },
792
+ },
793
+ play: async ({ canvasElement }) => {
794
+ const canvas = within(canvasElement);
795
+ const user = userEvent.setup({ delay: 50 });
796
+ await user.click(canvas.getByLabelText("[Insert label]"));
797
+ await screen.findByText("Option disabled", { selector: "span" });
798
+ await user.click(screen.getByText("Option disabled", { selector: "span" }));
799
+ await expect(canvas.getByLabelText("[Insert label]")).toHaveTextContent(
800
+ "Please select an option",
801
+ );
802
+ },
803
+ };
804
+
805
+ export const TestDefaultOpened: StoryObj<typeof Select> = {
806
+ args: {
807
+ ...Playground.args,
808
+ defaultOpen: true,
809
+ },
810
+ parameters: {
811
+ chromatic: { disableSnapshot: true },
812
+ },
813
+ play: async () => {
814
+ await screen.findByText("Option 1", { selector: "span" });
815
+ await screen.findByText("Option 2", { selector: "span" });
816
+ await screen.findByText("Option 3", { selector: "span" });
817
+ await screen.findByText("Option disabled", { selector: "span" });
818
+ },
819
+ };
820
+
821
+ export const TestDisabledSelect: StoryObj<typeof Select> = {
822
+ args: {
823
+ ...Playground.args,
824
+ disabled: true,
825
+ },
826
+ parameters: {
827
+ chromatic: { disableSnapshot: true },
828
+ },
829
+ play: async ({ canvasElement }) => {
830
+ const canvas = within(canvasElement);
831
+ const user = userEvent.setup({ delay: 50 });
832
+ await expect(canvas.getByLabelText("[Insert label]")).toHaveTextContent(
833
+ "Please select an option",
834
+ );
835
+ await expect(canvas.getByLabelText("[Insert label]")).toBeDisabled();
836
+ await user.click(canvas.getByLabelText("[Insert label]"));
837
+ await expectNotPresent(() =>
838
+ screen.queryByText("Option 1", { selector: "span" }),
839
+ );
840
+ await expectNotPresent(() =>
841
+ screen.queryByText("Option disabled", { selector: "span" }),
842
+ );
843
+ },
844
+ };
845
+
846
+ export const ExampleWithReactHookForm: StoryObj<typeof Select> = {
847
+ parameters: {
848
+ chromatic: { disableSnapshot: true },
849
+ },
850
+ render: (args) => {
851
+ const ReactHookFormExample = () => {
852
+ const { control } = useForm<any>({
853
+ defaultValues: {
854
+ select: [],
855
+ },
856
+ });
857
+ const selectField = useController({ control, name: "select" });
858
+
859
+ return (
860
+ <>
861
+ <div style={{ marginBottom: 16 }}>
862
+ Selected values : {selectField.field.value.join(", ")}
863
+ </div>
864
+
865
+ <Select
866
+ id="my-select"
867
+ label="[Insert label]"
868
+ selectedKeys={selectField.field.value}
869
+ onSelectionChange={selectField.field.onChange}
870
+ mode="multiple"
871
+ placeholder="Please select an option"
872
+ >
873
+ <SelectItem text="Option 1" id="1" />
874
+ <SelectItem text="Option 2" id="2" />
875
+ <SelectItem text="Option 3" id="3" />
876
+ </Select>
877
+ </>
878
+ );
879
+ };
880
+
881
+ return <ReactHookFormExample {...args} />;
882
+ },
883
+ };
884
+
885
+ export const TestReactHookForm: StoryObj<typeof Select> = {
886
+ parameters: {
887
+ chromatic: { disableSnapshot: true },
888
+ },
889
+ render: ExampleWithReactHookForm.render,
890
+ play: async ({ canvasElement }) => {
891
+ const canvas = within(canvasElement);
892
+ const user = userEvent.setup({ delay: 50 });
893
+ await user.click(canvas.getByLabelText("[Insert label]"));
894
+ await screen.findByText("Option 2", { selector: "span" });
895
+ await user.click(screen.getByText("Option 1", { selector: "span" }));
896
+ await user.click(screen.getByText("Option 2", { selector: "span" }));
897
+ await user.click(screen.getByText("Option 3", { selector: "span" }));
898
+ await user.click(screen.getByText("Option 2", { selector: "span" }));
899
+ await expect(canvas.getByLabelText("[Insert label]")).toHaveTextContent(
900
+ "2",
901
+ );
902
+ await expect(canvas.getByLabelText("[Insert label]")).toHaveTextContent(
903
+ "items selected",
904
+ );
905
+ await expectPresent(() => canvas.getByText("Selected values : 1, 3"));
906
+ await user.click(canvas.getByLabelText("[Insert label]"));
907
+ await expectNotPresent(() =>
908
+ canvas.queryByText("Option 1", { selector: "span" }),
909
+ );
910
+ },
911
+ };
912
+
913
+ export const ExampleWithSelectAllOption: StoryObj<typeof Select> = {
914
+ parameters: {
915
+ chromatic: { disableSnapshot: true },
916
+ },
917
+ render: (args) => {
918
+ const ReactHookFormExample = () => {
919
+ const { control } = useForm<any>({
920
+ defaultValues: {
921
+ select: [],
922
+ },
923
+ });
924
+ const selectField = useController({ control, name: "select" });
925
+
926
+ const options = [
927
+ { text: "Option 1", id: "1" },
928
+ { text: "Option 2", id: "2" },
929
+ { text: "Option 3", id: "3" },
930
+ ];
931
+
932
+ const areAllSelected = selectField.field.value.length === options.length;
933
+
934
+ return (
935
+ <form>
936
+ <div style={{ marginBottom: 16 }}>
937
+ Selected values : {JSON.stringify(selectField.field.value)}
938
+ </div>
939
+
940
+ <Select
941
+ id="my-select"
942
+ label="Example with a 'Select All' option"
943
+ selectedKeys={selectField.field.value}
944
+ onSelectionChange={(selection) => {
945
+ if (selection.includes("all")) {
946
+ if (areAllSelected) selectField.field.onChange([]);
947
+ else selectField.field.onChange(options.map((o) => o.id));
948
+ } else {
949
+ selectField.field.onChange(selection);
950
+ }
951
+ }}
952
+ mode="multiple"
953
+ fullWidth
954
+ >
955
+ <SelectItem
956
+ text="Select All"
957
+ id="all"
958
+ isSelected={areAllSelected}
959
+ />
960
+ {options.map((option) => (
961
+ <SelectItem key={option.id} {...option} />
962
+ ))}
963
+ </Select>
964
+ </form>
965
+ );
966
+ };
967
+
968
+ return <ReactHookFormExample {...args} />;
969
+ },
970
+ };
971
+ ```
972
+
973
+ ## Developer notes
974
+
975
+ Here are the notes available for the developer on the built Storybook, you can read them to understand the component and how to use it.
976
+
977
+ ### Single mode
978
+
979
+ ```mdx
980
+ import {
981
+ Meta,
982
+ Canvas,
983
+ Controls,
984
+ Source,
985
+ ArgTypes,
986
+ } from "@storybook/addon-docs/blocks";
987
+
988
+ import * as Select from "./SingleMode.stories";
989
+ import * as SelectTests from "./tests/SingleMode.stories";
990
+ import SelectItem from "../../SelectItem/SelectItem";
991
+
992
+ <Meta of={Select} />
993
+
994
+ # Select (single mode)
995
+
996
+ <Canvas of={Select.Playground} />
997
+
998
+ ## Select props
999
+
1000
+ <Controls
1001
+ of={Select.Playground}
1002
+ exclude={["onSelectionChange", "selectedKeys"]}
1003
+ />
1004
+
1005
+ ## SelectItem props
1006
+
1007
+ This component **should be used as a child of the Select component** and corresponds to its options.
1008
+
1009
+ It's just a simple wrapper around DropdownListItem to make it work with the Select.
1010
+
1011
+ <ArgTypes of={SelectItem} />
1012
+
1013
+ ## Basic usage
1014
+
1015
+ This is how you generally want to use the `Select` component.
1016
+ You can control the value of the dropdown by passing the `value` and `onChange` props.
1017
+
1018
+ <Source of={SelectTests.ExampleControlled} type="code" dark />
1019
+
1020
+ ## Example with react-hook-form
1021
+
1022
+ Here is another example of how to use the `Select` component with `react-hook-form`.
1023
+ Since the Select component manages its state internally, you have to use `useController` (or the Controller component) from React Hook Form to integrate with it in a controlled mode.
1024
+
1025
+ More information about this technical limitation on the [react-hook-form docs](https://www.react-hook-form.com/get-started/#IntegratingwithUIlibraries) and also in [React Aria docs](https://react-spectrum.adobe.com/react-aria/forms.html#react-hook-form)
1026
+
1027
+ <Source of={SelectTests.ExampleWithReactHookForm} type="code" dark />
1028
+
1029
+ ## How to test this component
1030
+
1031
+ Here is a code snippet that you can use as an example to know how to test this component with react testing library:
1032
+
1033
+ <Source of={SelectTests.TestControlledMode} type="code" dark />
1034
+
1035
+ ## Stories
1036
+
1037
+ ### With long option
1038
+
1039
+ <Canvas of={Select.WithLongOptions} />
1040
+
1041
+ ### Disabled
1042
+
1043
+ <Canvas of={Select.Disabled} />
1044
+
1045
+ ### Focus
1046
+
1047
+ <Canvas of={Select.Focus} />
1048
+
1049
+ ### With error
1050
+
1051
+ <Canvas of={Select.WithError} />
1052
+
1053
+ ### With success
1054
+
1055
+ <Canvas of={Select.WithSuccess} />
1056
+
1057
+ ### Full width
1058
+
1059
+ <Canvas of={Select.FullWidth} />
1060
+ ```
1061
+
1062
+ ### Multiple mode
1063
+
1064
+ ```mdx
1065
+ import {
1066
+ Meta,
1067
+ Canvas,
1068
+ Controls,
1069
+ Source,
1070
+ ArgTypes,
1071
+ } from "@storybook/addon-docs/blocks";
1072
+
1073
+ import * as Select from "./MultipleMode.stories";
1074
+ import * as SelectTests from "./tests/MultipleMode.stories";
1075
+ import SelectItem from "../../SelectItem/SelectItem";
1076
+
1077
+ <Meta of={Select} />
1078
+
1079
+ # Select (multiple mode)
1080
+
1081
+ <Canvas of={Select.Playground} />
1082
+
1083
+ ## Select props
1084
+
1085
+ <Controls of={Select.Playground} exclude={["value", "onChange", "name"]} />
1086
+
1087
+ ## SelectItem props
1088
+
1089
+ This component **should be used as a child of the Select component** and corresponds to its options.
1090
+
1091
+ It's just a simple wrapper around DropdownListItem to make it work with the Select.
1092
+
1093
+ <ArgTypes of={SelectItem} />
1094
+
1095
+ ## Code examples
1096
+
1097
+ ### Example with react-hook-form
1098
+
1099
+ Here is an example of how to use the `Select` component with `react-hook-form`.
1100
+ Since the Select component manages its state internally, you have to use `useController` (or the Controller component) from React Hook Form to integrate with it in a controlled mode.
1101
+
1102
+ More information about this technical limitation on the [react-hook-form docs](https://www.react-hook-form.com/get-started/#IntegratingwithUIlibraries) and also in [React Aria docs](https://react-spectrum.adobe.com/react-aria/forms.html#react-hook-form)
1103
+
1104
+ <Source of={SelectTests.ExampleWithReactHookForm} type="code" dark />
1105
+
1106
+ ### Example with a "Select all" option
1107
+
1108
+ Here is an example of how to use the `Select` component with a "Select all" option.
1109
+
1110
+ <Source of={SelectTests.ExampleWithSelectAllOption} type="code" dark />
1111
+
1112
+ ## Stories
1113
+
1114
+ ### Disabled
1115
+
1116
+ <Canvas of={Select.Disabled} />
1117
+
1118
+ ### Focus
1119
+
1120
+ <Canvas of={Select.Focus} />
1121
+
1122
+ ### With error
1123
+
1124
+ <Canvas of={Select.WithError} />
1125
+
1126
+ ### With success
1127
+
1128
+ <Canvas of={Select.WithSuccess} />
1129
+
1130
+ ### Full width
1131
+
1132
+ <Canvas of={Select.FullWidth} />
1133
+ ```