@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.
- package/dist/design-system.cjs +9 -5
- package/dist/design-system.js +14 -6
- package/dist/packages/components/Accordion/doc.md +342 -0
- package/dist/packages/components/Badge/doc.md +192 -0
- package/dist/packages/components/Breadcrumbs/doc.md +332 -0
- package/dist/packages/components/Button/doc.md +425 -0
- package/dist/packages/components/Calendar/doc.md +465 -0
- package/dist/packages/components/ChartLegend/doc.md +151 -0
- package/dist/packages/components/ChartTooltip/doc.md +124 -0
- package/dist/packages/components/Checkbox/doc.md +329 -0
- package/dist/packages/components/CheckboxGroup/doc.md +242 -0
- package/dist/packages/components/Chip/doc.md +99 -0
- package/dist/packages/components/Combobox/Combobox.d.ts +8 -0
- package/dist/packages/components/Combobox/doc.md +680 -0
- package/dist/packages/components/DataTable/doc.md +1124 -0
- package/dist/packages/components/DatePicker/doc.md +579 -0
- package/dist/packages/components/DateRangePicker/doc.md +638 -0
- package/dist/packages/components/Drawer/doc.md +338 -0
- package/dist/packages/components/Dropdown/Dropdown.d.ts +4 -0
- package/dist/packages/components/Dropdown/doc.md +205 -0
- package/dist/packages/components/EmptyState/doc.md +101 -0
- package/dist/packages/components/FileUpload/doc.md +449 -0
- package/dist/packages/components/Filter/doc.md +196 -0
- package/dist/packages/components/Header/doc.md +373 -0
- package/dist/packages/components/I18nProvider/doc.md +187 -0
- package/dist/packages/components/Icon/doc.md +63 -0
- package/dist/packages/components/Label/doc.md +60 -0
- package/dist/packages/components/LinearProgressBar/doc.md +148 -0
- package/dist/packages/components/Link/doc.md +206 -0
- package/dist/packages/components/List/doc.md +481 -0
- package/dist/packages/components/Loader/doc.md +53 -0
- package/dist/packages/components/Menu/Menu.d.ts +5 -1
- package/dist/packages/components/Menu/doc.md +231 -0
- package/dist/packages/components/Message/doc.md +166 -0
- package/dist/packages/components/Modal/doc.md +289 -0
- package/dist/packages/components/Navigation/doc.md +992 -0
- package/dist/packages/components/NavigationItem/doc.md +167 -0
- package/dist/packages/components/NotificationCard/doc.md +206 -0
- package/dist/packages/components/Notifications/doc.md +240 -0
- package/dist/packages/components/NumberField/doc.md +582 -0
- package/dist/packages/components/PageLayout/doc.md +651 -0
- package/dist/packages/components/Pagination/doc.md +227 -0
- package/dist/packages/components/Popover/doc.md +245 -0
- package/dist/packages/components/Radio/doc.md +370 -0
- package/dist/packages/components/RouterProvider/doc.md +64 -0
- package/dist/packages/components/SearchBar/doc.md +504 -0
- package/dist/packages/components/SegmentedControl/doc.md +398 -0
- package/dist/packages/components/Select/Select.d.ts +4 -0
- package/dist/packages/components/Select/doc.md +1133 -0
- package/dist/packages/components/Skeleton/doc.md +129 -0
- package/dist/packages/components/Slider/doc.md +362 -0
- package/dist/packages/components/Stepper/doc.md +104 -0
- package/dist/packages/components/Switch/doc.md +296 -0
- package/dist/packages/components/Tabs/doc.md +295 -0
- package/dist/packages/components/Tag/doc.md +81 -0
- package/dist/packages/components/TextInput/doc.md +490 -0
- package/dist/packages/components/TimeField/doc.md +353 -0
- package/dist/packages/components/Timeline/doc.md +1046 -0
- package/dist/packages/components/Toaster/doc.md +263 -0
- package/dist/packages/components/ToggleButton/doc.md +108 -0
- package/dist/packages/components/ToggleButtonGroup/doc.md +307 -0
- package/dist/packages/components/Tooltip/doc.md +206 -0
- package/dist/packages/components/YearMonthPicker/YearMonthPicker.d.ts +8 -0
- package/dist/packages/components/YearMonthPicker/doc.md +638 -0
- package/dist/public_docs/components.md +68 -0
- package/dist/public_docs/index.md +30 -0
- package/dist/public_docs/tokens.md +121 -0
- 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
|
+
```
|