@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,449 @@
|
|
|
1
|
+
# FileUpload
|
|
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/FileUpload/FileUpload.d.ts
|
|
8
|
+
|
|
9
|
+
## Example usage
|
|
10
|
+
|
|
11
|
+
Here are the Storybook Stories.
|
|
12
|
+
|
|
13
|
+
Base stories:
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { Meta, StoryObj } from "@storybook/react-vite";
|
|
17
|
+
import { fn, within } from "storybook/test";
|
|
18
|
+
import FileUpload from "./FileUpload";
|
|
19
|
+
import { I18nProvider } from "react-aria-components";
|
|
20
|
+
import { useState } from "react";
|
|
21
|
+
import Button from "../Button/Button";
|
|
22
|
+
|
|
23
|
+
const meta: Meta<typeof FileUpload> = {
|
|
24
|
+
component: FileUpload,
|
|
25
|
+
parameters: {
|
|
26
|
+
layout: "centered",
|
|
27
|
+
},
|
|
28
|
+
decorators: [
|
|
29
|
+
(Story) => (
|
|
30
|
+
<I18nProvider locale="en-US">
|
|
31
|
+
<Story />
|
|
32
|
+
</I18nProvider>
|
|
33
|
+
),
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
export default meta;
|
|
37
|
+
|
|
38
|
+
type Story = StoryObj<typeof meta>;
|
|
39
|
+
|
|
40
|
+
export const Playground: Story = {
|
|
41
|
+
args: {
|
|
42
|
+
formGroupWrapperProps: {
|
|
43
|
+
htmlForId: "file-upload",
|
|
44
|
+
label: "[Insert label]",
|
|
45
|
+
description: "[Insert description]",
|
|
46
|
+
errorHelperText: "[Insert helper]",
|
|
47
|
+
errorHelperTextIcon: "error_outline",
|
|
48
|
+
required: true,
|
|
49
|
+
labelIconRight: "help_outline",
|
|
50
|
+
labelIconRightTooltip: "Additional information",
|
|
51
|
+
},
|
|
52
|
+
onSelectionChange: fn(),
|
|
53
|
+
allowsMultiple: true,
|
|
54
|
+
files: [
|
|
55
|
+
{
|
|
56
|
+
id: "1",
|
|
57
|
+
fileName: "[Filename.exe]",
|
|
58
|
+
fileSize: "[Size Mo]",
|
|
59
|
+
isUploading: true,
|
|
60
|
+
onDelete: fn(),
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "2",
|
|
64
|
+
fileName: "test 2.txt",
|
|
65
|
+
fileSize: "100KB",
|
|
66
|
+
isUploading: false,
|
|
67
|
+
onDelete: fn(),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "3",
|
|
71
|
+
fileName: "test 3.txt",
|
|
72
|
+
isUploading: false,
|
|
73
|
+
onDelete: fn(),
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
play: async ({ canvasElement }) => {
|
|
78
|
+
const canvas = within(canvasElement);
|
|
79
|
+
await canvas.findByText("[Insert label]");
|
|
80
|
+
await canvas.findByText("[Insert description]");
|
|
81
|
+
await canvas.findByText("[Insert helper]");
|
|
82
|
+
await canvas.findByText("[Filename.exe]");
|
|
83
|
+
await canvas.findByText("test 2.txt");
|
|
84
|
+
await canvas.findByText("test 3.txt");
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export const FunctionnalForm: Story = {
|
|
89
|
+
args: {
|
|
90
|
+
...Playground.args,
|
|
91
|
+
},
|
|
92
|
+
render: (args) => {
|
|
93
|
+
const ParentComponent = () => {
|
|
94
|
+
const [files, setFiles] = useState<File[]>([]);
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<form
|
|
98
|
+
onSubmit={(e) => {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
// Here put you API call and state changes accordingly
|
|
101
|
+
args.onSelectionChange(files);
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
<FileUpload
|
|
105
|
+
{...args}
|
|
106
|
+
formGroupWrapperProps={args.formGroupWrapperProps}
|
|
107
|
+
onSelectionChange={(files) =>
|
|
108
|
+
setFiles((prev) => [...prev, ...files])
|
|
109
|
+
}
|
|
110
|
+
files={files.map((file) => ({
|
|
111
|
+
id: file.name,
|
|
112
|
+
fileName: file.name,
|
|
113
|
+
fileSize: `${file.size} bytes`, // <- may need a util to format this
|
|
114
|
+
onDelete: () =>
|
|
115
|
+
setFiles((prev) => prev.filter((f) => f !== file)),
|
|
116
|
+
}))}
|
|
117
|
+
/>
|
|
118
|
+
|
|
119
|
+
<hr />
|
|
120
|
+
<Button type="submit" text="Submit" />
|
|
121
|
+
</form>
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return <ParentComponent />;
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export const OnlyOneFile: Story = {
|
|
130
|
+
args: {
|
|
131
|
+
...Playground.args,
|
|
132
|
+
allowsMultiple: false,
|
|
133
|
+
},
|
|
134
|
+
render: FunctionnalForm.render,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export const Disabled: Story = {
|
|
138
|
+
args: {
|
|
139
|
+
...Playground.args,
|
|
140
|
+
isDisabled: true,
|
|
141
|
+
},
|
|
142
|
+
render: FunctionnalForm.render,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
export const TestOverflowingContent: Story = {
|
|
146
|
+
decorators: [
|
|
147
|
+
(Story) => (
|
|
148
|
+
<div style={{ width: "313px", overflow: "hidden" }}>
|
|
149
|
+
<Story />
|
|
150
|
+
</div>
|
|
151
|
+
),
|
|
152
|
+
],
|
|
153
|
+
args: {
|
|
154
|
+
...Playground.args,
|
|
155
|
+
files: [
|
|
156
|
+
{
|
|
157
|
+
id: "1",
|
|
158
|
+
fileName: "verylongfilename-verylongfilename-verylongfilename.txt",
|
|
159
|
+
fileSize: "100000000000000000000000000000000000000000To",
|
|
160
|
+
isUploading: false,
|
|
161
|
+
onDelete: fn(),
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## How to test this component
|
|
169
|
+
|
|
170
|
+
Here are some more advanced stories with more testing coverage and examples that you can read to understand how to test this component.
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
import { Meta, StoryObj } from "@storybook/react-vite";
|
|
174
|
+
import { expect, fn, userEvent, within } from "storybook/test";
|
|
175
|
+
import FileUpload from "../FileUpload";
|
|
176
|
+
import * as FileUploadStories from "../FileUpload.stories";
|
|
177
|
+
import { useState } from "react";
|
|
178
|
+
import Button from "@packages/components/Button/Button";
|
|
179
|
+
import {
|
|
180
|
+
expectNotPresent,
|
|
181
|
+
someTime,
|
|
182
|
+
} from "@internal/test-utils-storybook/test-utils-storybook";
|
|
183
|
+
|
|
184
|
+
const meta: Meta<typeof FileUpload> = {
|
|
185
|
+
...FileUploadStories.default,
|
|
186
|
+
parameters: {
|
|
187
|
+
...FileUploadStories.default.parameters,
|
|
188
|
+
chromatic: { disableSnapshot: true },
|
|
189
|
+
},
|
|
190
|
+
component: FileUpload,
|
|
191
|
+
};
|
|
192
|
+
export default meta;
|
|
193
|
+
|
|
194
|
+
const csvFile = new File(["test"], "test.csv", { type: "text/csv" });
|
|
195
|
+
const txtFile = new File(["test"], "test.txt", { type: "text/plain" });
|
|
196
|
+
|
|
197
|
+
type Story = StoryObj<typeof meta>;
|
|
198
|
+
|
|
199
|
+
export const TestOnlyCsv: Story = {
|
|
200
|
+
args: {
|
|
201
|
+
...FileUploadStories.Playground.args,
|
|
202
|
+
acceptedFileTypes: ["text/csv"],
|
|
203
|
+
formGroupWrapperProps: {
|
|
204
|
+
label: "Upload a CSV file",
|
|
205
|
+
htmlForId: "file-upload",
|
|
206
|
+
},
|
|
207
|
+
files: [],
|
|
208
|
+
},
|
|
209
|
+
play: async ({ canvasElement, args }) => {
|
|
210
|
+
const canvas = within(canvasElement);
|
|
211
|
+
const user = userEvent.setup({ delay: 50 });
|
|
212
|
+
await someTime(200); // Needed for use effect to run
|
|
213
|
+
const fileInput = canvas.getByLabelText("Upload a CSV file");
|
|
214
|
+
await user.upload(fileInput, [txtFile]);
|
|
215
|
+
await expect(args.onSelectionChange).not.toHaveBeenCalled();
|
|
216
|
+
await user.upload(fileInput, [csvFile]);
|
|
217
|
+
await expect(args.onSelectionChange).toHaveBeenCalledTimes(1);
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export const ExampleOnlyCsv: Story = {
|
|
222
|
+
render: () => (
|
|
223
|
+
<FileUpload
|
|
224
|
+
onSelectionChange={fn()}
|
|
225
|
+
acceptedFileTypes={["text/csv"]}
|
|
226
|
+
formGroupWrapperProps={{ label: "Upload a CSV file" }}
|
|
227
|
+
/>
|
|
228
|
+
),
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
export const ExampleMultipleFiles: Story = {
|
|
232
|
+
render: () => (
|
|
233
|
+
<FileUpload
|
|
234
|
+
onSelectionChange={fn()}
|
|
235
|
+
allowsMultiple={true}
|
|
236
|
+
formGroupWrapperProps={{ label: "Upload multiple files" }}
|
|
237
|
+
/>
|
|
238
|
+
),
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
export const SimpleStateAndUploadOnSubmitExample: Story = {
|
|
242
|
+
render: () => {
|
|
243
|
+
const ParentComponent = () => {
|
|
244
|
+
const [files, setFiles] = useState<File[]>([]);
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<form
|
|
248
|
+
onSubmit={(e) => {
|
|
249
|
+
e.preventDefault();
|
|
250
|
+
// Here put you API call and state changes accordingly
|
|
251
|
+
console.log({ files }); // eslint-disable-line no-console
|
|
252
|
+
}}
|
|
253
|
+
>
|
|
254
|
+
<FileUpload
|
|
255
|
+
onSelectionChange={(files) =>
|
|
256
|
+
setFiles((prev) => [...prev, ...files])
|
|
257
|
+
}
|
|
258
|
+
allowsMultiple
|
|
259
|
+
formGroupWrapperProps={{
|
|
260
|
+
label: "Upload a file",
|
|
261
|
+
htmlForId: "file-upload", // <- required for a11y and testing
|
|
262
|
+
}}
|
|
263
|
+
files={files.map((file) => ({
|
|
264
|
+
id: file.name,
|
|
265
|
+
fileName: file.name,
|
|
266
|
+
fileSize: `${file.size} bytes`, // <- may need a util to format this
|
|
267
|
+
onDelete: () =>
|
|
268
|
+
setFiles((prev) => prev.filter((f) => f !== file)),
|
|
269
|
+
}))}
|
|
270
|
+
/>
|
|
271
|
+
|
|
272
|
+
<hr />
|
|
273
|
+
<Button type="submit" text="Submit" />
|
|
274
|
+
</form>
|
|
275
|
+
);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
return <ParentComponent />;
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
export const TestSimpleStateAndUploadOnSubmitExample: Story = {
|
|
283
|
+
// ⬇️ same code as example above
|
|
284
|
+
render: SimpleStateAndUploadOnSubmitExample.render,
|
|
285
|
+
play: async ({ canvasElement }) => {
|
|
286
|
+
const canvas = within(canvasElement);
|
|
287
|
+
const user = userEvent.setup();
|
|
288
|
+
await someTime(100); // This delay is needed due to a hack we had to make in the component to make it work with testing-library
|
|
289
|
+
const fileInput = canvas.getByLabelText("Upload a file");
|
|
290
|
+
|
|
291
|
+
await user.upload(fileInput, [
|
|
292
|
+
new File(["test"], "test.txt", { type: "text/plain" }),
|
|
293
|
+
]);
|
|
294
|
+
await canvas.findByText("test.txt");
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
export const SimpleStateAndUploadDirectlyExample: Story = {
|
|
299
|
+
render: () => {
|
|
300
|
+
const fakeApiCall = async () => {
|
|
301
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const ParentComponent = () => {
|
|
305
|
+
const [files, setFiles] = useState<File[]>([]);
|
|
306
|
+
const [uploadingFiles, setUploadingFiles] = useState<File[]>([]);
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
<form
|
|
310
|
+
onSubmit={(e) => {
|
|
311
|
+
e.preventDefault();
|
|
312
|
+
// Here put you API call and state changes accordingly
|
|
313
|
+
console.log({ files }); // eslint-disable-line no-console
|
|
314
|
+
}}
|
|
315
|
+
>
|
|
316
|
+
<FileUpload
|
|
317
|
+
onSelectionChange={async (files) => {
|
|
318
|
+
// Here is just a naive example implementation, feel free to adjust it based on your own needs and stack (react-query mutation for example).
|
|
319
|
+
// The important thing is to show the user the uploading state.
|
|
320
|
+
setUploadingFiles((prev) => [...prev, ...files]);
|
|
321
|
+
setFiles((prev) => [...prev, ...files]);
|
|
322
|
+
await fakeApiCall();
|
|
323
|
+
setUploadingFiles([]);
|
|
324
|
+
}}
|
|
325
|
+
allowsMultiple
|
|
326
|
+
formGroupWrapperProps={{
|
|
327
|
+
label: "Upload a file",
|
|
328
|
+
htmlForId: "file-upload", // <- required for a11y and testing
|
|
329
|
+
}}
|
|
330
|
+
files={files.map((file) => ({
|
|
331
|
+
id: file.name,
|
|
332
|
+
fileName: file.name,
|
|
333
|
+
fileSize: `${file.size} bytes`, // <- may need a util to format this.
|
|
334
|
+
onDelete: () =>
|
|
335
|
+
setFiles((prev) => prev.filter((f) => f !== file)),
|
|
336
|
+
isUploading: uploadingFiles.includes(file), // <- here you have to show the user the uploading state.
|
|
337
|
+
}))}
|
|
338
|
+
/>
|
|
339
|
+
|
|
340
|
+
<hr />
|
|
341
|
+
<Button type="submit" text="Submit" />
|
|
342
|
+
</form>
|
|
343
|
+
);
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
return <ParentComponent />;
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
export const TestOnlyOneFile: Story = {
|
|
351
|
+
...FileUploadStories.OnlyOneFile,
|
|
352
|
+
play: async ({ canvasElement }) => {
|
|
353
|
+
const canvas = within(canvasElement);
|
|
354
|
+
const user = userEvent.setup();
|
|
355
|
+
await someTime(100);
|
|
356
|
+
const fileInput = canvas.getByLabelText("[Insert label]");
|
|
357
|
+
|
|
358
|
+
await user.upload(fileInput, [
|
|
359
|
+
new File(["test"], "test.txt", { type: "text/plain" }),
|
|
360
|
+
]);
|
|
361
|
+
await canvas.findByText("test.txt");
|
|
362
|
+
await user.upload(fileInput, [
|
|
363
|
+
new File(["test"], "test-2.txt", { type: "text/plain" }),
|
|
364
|
+
]);
|
|
365
|
+
await expectNotPresent(() => canvas.queryByText("test-2.txt"));
|
|
366
|
+
await canvas.findByText("test.txt");
|
|
367
|
+
},
|
|
368
|
+
};
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Developer notes
|
|
372
|
+
|
|
373
|
+
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.
|
|
374
|
+
|
|
375
|
+
```mdx
|
|
376
|
+
import {
|
|
377
|
+
Canvas,
|
|
378
|
+
Meta,
|
|
379
|
+
Stories,
|
|
380
|
+
Controls,
|
|
381
|
+
Source,
|
|
382
|
+
} from "@storybook/addon-docs/blocks";
|
|
383
|
+
|
|
384
|
+
import * as FileUpload from "./FileUpload.stories";
|
|
385
|
+
import * as FileUploadTests from "./tests/FileUpload.stories";
|
|
386
|
+
|
|
387
|
+
<Meta of={FileUpload} />
|
|
388
|
+
|
|
389
|
+
# FileUpload
|
|
390
|
+
|
|
391
|
+
The FileUpload component allows users to upload files from their **file system** by searching or using **drag-and-drop**.
|
|
392
|
+
|
|
393
|
+
## Accepted file types
|
|
394
|
+
|
|
395
|
+
By default, the file trigger will accept any file type.
|
|
396
|
+
To support only certain file types, pass an array of the mime type of files via the acceptedFileTypes prop.
|
|
397
|
+
|
|
398
|
+
<Source of={FileUploadTests.ExampleOnlyCsv} type="code" dark />
|
|
399
|
+
|
|
400
|
+
## Multiple files
|
|
401
|
+
|
|
402
|
+
A file trigger can accept multiple files by passsing the `allowsMultiple` property.
|
|
403
|
+
|
|
404
|
+
<Source of={FileUploadTests.ExampleMultipleFiles} type="code" dark />
|
|
405
|
+
|
|
406
|
+
## Usage with simple state, upload on submit
|
|
407
|
+
|
|
408
|
+
<Source
|
|
409
|
+
of={FileUploadTests.SimpleStateAndUploadOnSubmitExample}
|
|
410
|
+
type="code"
|
|
411
|
+
dark
|
|
412
|
+
/>
|
|
413
|
+
|
|
414
|
+
## Usage with simple state, upload directly
|
|
415
|
+
|
|
416
|
+
<Source
|
|
417
|
+
of={FileUploadTests.SimpleStateAndUploadDirectlyExample}
|
|
418
|
+
type="code"
|
|
419
|
+
dark
|
|
420
|
+
/>
|
|
421
|
+
|
|
422
|
+
## Usage with react-hook-form
|
|
423
|
+
|
|
424
|
+
The usage is the same, you just need to use a `Controller` component (or `useController`) to control the file upload because it does not work on uncontrolled mode.
|
|
425
|
+
|
|
426
|
+
## How to test this component
|
|
427
|
+
|
|
428
|
+
The most important thing is to ensure there is an `htmlForId` prop passed to the `formGroupWrapperProps` component prop.
|
|
429
|
+
|
|
430
|
+
This is needed to link the label to the input.
|
|
431
|
+
|
|
432
|
+
After that here is an example of how to test the component using testing-library and userEvent.
|
|
433
|
+
You will notice that **we need to add a delay** before starting to run assertions.
|
|
434
|
+
This is because of a hack we had to make in the component to make it work with testing-library.
|
|
435
|
+
|
|
436
|
+
<Source
|
|
437
|
+
of={FileUploadTests.TestSimpleStateAndUploadOnSubmitExample}
|
|
438
|
+
type="code"
|
|
439
|
+
dark
|
|
440
|
+
/>
|
|
441
|
+
|
|
442
|
+
## Playground & Props
|
|
443
|
+
|
|
444
|
+
<Canvas of={FileUpload.Playground} />
|
|
445
|
+
|
|
446
|
+
<Controls of={FileUpload.Playground} />
|
|
447
|
+
|
|
448
|
+
<Stories />
|
|
449
|
+
```
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# Filter
|
|
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/Filter/Filter.d.ts
|
|
8
|
+
|
|
9
|
+
## Example usage
|
|
10
|
+
|
|
11
|
+
Here are the Storybook Stories.
|
|
12
|
+
|
|
13
|
+
Base stories:
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { Meta, StoryObj } from "@storybook/react-vite";
|
|
17
|
+
import Filter from "./Filter";
|
|
18
|
+
import { WithReactTableFrontEndManaged } from "../DataTable/DataTable.stories";
|
|
19
|
+
|
|
20
|
+
const meta: Meta<typeof Filter> = {
|
|
21
|
+
component: Filter,
|
|
22
|
+
parameters: {
|
|
23
|
+
layout: "padded",
|
|
24
|
+
chromatic: { disableSnapshot: true },
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
export default meta;
|
|
28
|
+
|
|
29
|
+
// We just forward the DataTable Story to keep all things grouped and avoid duplications
|
|
30
|
+
export const FullFeatured: StoryObj<typeof Filter> = {
|
|
31
|
+
decorators: WithReactTableFrontEndManaged.decorators as any,
|
|
32
|
+
render: WithReactTableFrontEndManaged.render as any,
|
|
33
|
+
};
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## How to test this component
|
|
37
|
+
|
|
38
|
+
Here are some more advanced stories with more testing coverage and examples that you can read to understand how to test this component.
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import { Meta, StoryObj } from "@storybook/react-vite";
|
|
42
|
+
import Filter from "../Filter";
|
|
43
|
+
import { fireEvent, screen, userEvent, within } from "storybook/test";
|
|
44
|
+
import { FullFeatured } from "../Filter.stories";
|
|
45
|
+
import { expectNotPresent } from "@internal/test-utils-storybook/test-utils-storybook";
|
|
46
|
+
|
|
47
|
+
const meta: Meta<typeof Filter> = {
|
|
48
|
+
component: Filter,
|
|
49
|
+
parameters: {
|
|
50
|
+
layout: "padded",
|
|
51
|
+
chromatic: { disableSnapshot: true },
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
export default meta;
|
|
55
|
+
|
|
56
|
+
export const ShouldFilterTheRadio: StoryObj<typeof Filter> = {
|
|
57
|
+
...FullFeatured,
|
|
58
|
+
play: async ({ canvasElement }) => {
|
|
59
|
+
const canvas = within(canvasElement);
|
|
60
|
+
const user = userEvent.setup({ delay: 50 });
|
|
61
|
+
|
|
62
|
+
// Assert initial state (no filter)
|
|
63
|
+
await canvas.findAllByText("Monday");
|
|
64
|
+
await canvas.findAllByText("Tuesday");
|
|
65
|
+
|
|
66
|
+
// Open the filter and select
|
|
67
|
+
await user.click(canvas.getByText("Filter"));
|
|
68
|
+
await fireEvent.click(screen.getByLabelText("Monday")); // fireEvent is used to avoid issue on Firefox
|
|
69
|
+
await user.click(screen.getByText("Apply"));
|
|
70
|
+
await canvas.findAllByText("Monday");
|
|
71
|
+
await canvas.findByText("Day: Monday");
|
|
72
|
+
await expectNotPresent(() => canvas.queryByText("Tuesday"));
|
|
73
|
+
|
|
74
|
+
// Remove the filter with the Chip
|
|
75
|
+
await user.click(canvas.getByLabelText("Close Day: Monday"));
|
|
76
|
+
await expectNotPresent(() => canvas.queryByText("Day: Monday"));
|
|
77
|
+
await canvas.findAllByText("Monday");
|
|
78
|
+
await canvas.findAllByText("Tuesday");
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const ShouldFilterTheCheckboxes: StoryObj<typeof Filter> = {
|
|
83
|
+
...FullFeatured,
|
|
84
|
+
play: async ({ canvasElement }) => {
|
|
85
|
+
const canvas = within(canvasElement);
|
|
86
|
+
const user = userEvent.setup({ delay: 50 });
|
|
87
|
+
|
|
88
|
+
// Assert initial state (no filter)
|
|
89
|
+
await canvas.findAllByText("Pizza, Hamburger");
|
|
90
|
+
await canvas.findAllByText("Hamburger, Ramen");
|
|
91
|
+
|
|
92
|
+
// Open the filter and select
|
|
93
|
+
await user.click(canvas.getByText("Filter"));
|
|
94
|
+
await fireEvent.click(screen.getByLabelText("Pizza")); // fireEvent is used to avoid issue on Firefox
|
|
95
|
+
await user.click(screen.getByText("Apply"));
|
|
96
|
+
await canvas.findAllByText("Pizza, Hamburger");
|
|
97
|
+
await canvas.findByText("Foods: Pizza");
|
|
98
|
+
await expectNotPresent(() => canvas.queryByText("Hamburger, Ramen"));
|
|
99
|
+
|
|
100
|
+
// Remove the filter with the Chip
|
|
101
|
+
await user.click(canvas.getByLabelText("Close Foods: Pizza"));
|
|
102
|
+
await expectNotPresent(() => canvas.queryByText("Foods: Pizza"));
|
|
103
|
+
await canvas.findAllByText("Pizza, Hamburger");
|
|
104
|
+
await canvas.findAllByText("Hamburger, Ramen");
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const ShouldFilterTheOrganization: StoryObj<typeof Filter> = {
|
|
109
|
+
...FullFeatured,
|
|
110
|
+
play: async ({ canvasElement }) => {
|
|
111
|
+
const canvas = within(canvasElement);
|
|
112
|
+
const user = userEvent.setup({ delay: 50 });
|
|
113
|
+
|
|
114
|
+
// Assert initial state (no filter)
|
|
115
|
+
await canvas.findAllByText("EDF Store and Forecast");
|
|
116
|
+
await canvas.findAllByText("Agregio Solutions");
|
|
117
|
+
|
|
118
|
+
// Open the filter and select
|
|
119
|
+
await user.click(canvas.getByText("Filter"));
|
|
120
|
+
await fireEvent.click(screen.getByLabelText("Filter by organization:")); // fireEvent is used to avoid issue on Firefox
|
|
121
|
+
await fireEvent.click(
|
|
122
|
+
screen.getByLabelText("DropdownItem EDF Store and Forecast"),
|
|
123
|
+
); // fireEvent is used to avoid issue on Firefox
|
|
124
|
+
await user.click(screen.getByText("Apply"));
|
|
125
|
+
await canvas.findAllByText("EDF Store and Forecast");
|
|
126
|
+
await canvas.findByText("Organization: EDF Store and Forecast");
|
|
127
|
+
await expectNotPresent(() => canvas.queryByText("Agregio Solutions"));
|
|
128
|
+
|
|
129
|
+
// Remove the filter with the Chip
|
|
130
|
+
await user.click(
|
|
131
|
+
canvas.getByLabelText("Close Organization: EDF Store and Forecast"),
|
|
132
|
+
);
|
|
133
|
+
await expectNotPresent(() =>
|
|
134
|
+
canvas.queryByText("Organization: EDF Store and Forecast"),
|
|
135
|
+
);
|
|
136
|
+
await canvas.findAllByText("EDF Store and Forecast");
|
|
137
|
+
await canvas.findAllByText("Agregio Solutions");
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Developer notes
|
|
143
|
+
|
|
144
|
+
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.
|
|
145
|
+
|
|
146
|
+
````mdx
|
|
147
|
+
import { Meta, Controls, Stories, Source } from "@storybook/addon-docs/blocks";
|
|
148
|
+
|
|
149
|
+
import * as Filter from "./Filter.stories";
|
|
150
|
+
import { WithReactTableFrontEndManaged } from "../DataTable/DataTable.stories";
|
|
151
|
+
|
|
152
|
+
<Meta of={Filter} />
|
|
153
|
+
|
|
154
|
+
# Filter
|
|
155
|
+
|
|
156
|
+
## Filter props
|
|
157
|
+
|
|
158
|
+
<Controls of={Filter.FullFeatured} />
|
|
159
|
+
|
|
160
|
+
## How to enable/disable the search bar?
|
|
161
|
+
|
|
162
|
+
To enable the search bar, when creating your `table` instance with `react-table`, simply set an empty string as the `globalFilter` in the `state` or `initialState` of the table (depending on your use-case).
|
|
163
|
+
|
|
164
|
+
To remove it, simply do not set it (or set it to `undefined`).
|
|
165
|
+
|
|
166
|
+
Code example to **enable** the search bar:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
const table = useReactTable({
|
|
170
|
+
data,
|
|
171
|
+
columns,
|
|
172
|
+
initialState: {
|
|
173
|
+
globalFilter: "", // <- Enable the search bar
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## How to use custom meta on react-table to create a filter?
|
|
179
|
+
|
|
180
|
+
As you can see in the source code for the example story, adding a filter into the drawer is as simple as using the `meta` key on the column definition.
|
|
181
|
+
|
|
182
|
+
Please use the exported type `TypeTableColumnWithFilter` to benefit from the type-safety and autocompletion features of your IDE.
|
|
183
|
+
|
|
184
|
+
Here are the available keys for the `meta` object with filter abilities:
|
|
185
|
+
|
|
186
|
+
- `FilterInDrawer`: function that must return a React node, typically the form to filter the column entry (like a Select, Radios, etc.)
|
|
187
|
+
- `skipFilterChips`: by default, we automatically add little Chips for the user to be able to remove the filter. If you want to hide them, set this to `true`.
|
|
188
|
+
- `filterChips`: when using complex data, we cannot automatically generate the chips for you. So you have to use this function that must return an array of chip data (value and onClose precisely)
|
|
189
|
+
|
|
190
|
+
Here is a complete example on how to use the `meta` key in a lot of different ways.
|
|
191
|
+
It also show you how to connect an external form element (here, a Switch) outside the drawer while still being able to filter the table.
|
|
192
|
+
|
|
193
|
+
Please read it carefully (maybe copy-paste it in your code editor to have better syntax highlighting), with this example you may be able to handle almost all use-cases.
|
|
194
|
+
|
|
195
|
+
<Source of={WithReactTableFrontEndManaged} type="code" dark />
|
|
196
|
+
````
|