@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,504 @@
1
+ # SearchBar
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/SearchBar/SearchBar.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 { userEvent, within } from "storybook/test";
18
+ import SearchBar from "./SearchBar";
19
+ import { useState } from "react";
20
+ import Button from "@components/Button/Button";
21
+ import List, { Props as ListProps } from "../List/List";
22
+ import EmptyState from "../EmptyState/EmptyState";
23
+ import Tabs from "../Tabs/Tabs/Tabs";
24
+ import TabList from "../Tabs/TabList/TabList";
25
+ import Tab from "../Tabs/Tab/Tab";
26
+ import TabPanel from "../Tabs/TabPanel/TabPanel";
27
+ import { I18nProvider } from "react-aria-components";
28
+
29
+ const meta: Meta<typeof SearchBar> = {
30
+ component: SearchBar,
31
+ decorators: [
32
+ (Story) => (
33
+ <I18nProvider locale="en">
34
+ <Story />
35
+ </I18nProvider>
36
+ ),
37
+ ],
38
+ };
39
+ export default meta;
40
+
41
+ type Story = StoryObj<typeof meta>;
42
+
43
+ export const ExampleUsage: Story = {
44
+ render: () => {
45
+ // Data, typically fetched from a database
46
+ const data = [
47
+ {
48
+ id: "id-1",
49
+ fullname: "Alice Johnson",
50
+ position: "Chief Executive Officer",
51
+ },
52
+ {
53
+ id: "id-2",
54
+ fullname: "Bob Smith",
55
+ position: "Lead Software Engineer",
56
+ },
57
+ {
58
+ id: "id-3",
59
+ fullname: "Carol Lee",
60
+ position: "Product Manager",
61
+ },
62
+ {
63
+ id: "id-4",
64
+ fullname: "David Kim",
65
+ position: "UX Designer",
66
+ },
67
+ {
68
+ id: "id-5",
69
+ fullname: "Eva Brown",
70
+ position: "HR Specialist",
71
+ },
72
+ ];
73
+
74
+ const Parent = () => {
75
+ const [searchValue, setSearchValue] = useState("");
76
+ const [isOpen, setIsOpen] = useState(false);
77
+
78
+ // Manually filter the data based on the search value (have to be done on your side, not handled by the component)
79
+ const filteredData = data.filter((item) => {
80
+ if (!searchValue) return false; // Case where we want to show nothing if there is no search value
81
+ const searchValueLower = searchValue.toLowerCase();
82
+ return (
83
+ item.fullname.toLowerCase().includes(searchValueLower) ||
84
+ item.position.toLowerCase().includes(searchValueLower) ||
85
+ item.id.toLowerCase() === searchValueLower // It is recommended to use an exact match for the id, as it is used to identify the item in the list.
86
+ );
87
+ });
88
+
89
+ return (
90
+ <div>
91
+ <Button onClick={() => setIsOpen(true)} text="Open" />
92
+
93
+ <SearchBar
94
+ inputProps={{
95
+ value: searchValue,
96
+ onChange: setSearchValue,
97
+ placeholder: "Search in the application",
98
+ }}
99
+ onClear={() => setSearchValue("")}
100
+ isOpen={isOpen}
101
+ onOpenChange={(newIsOpen) => {
102
+ setSearchValue(""); // Reset the search value when the modal is closed. This is not built-in, so you have the choice depending on your use case.
103
+ setIsOpen(newIsOpen);
104
+ }}
105
+ resultsCount={filteredData.length}
106
+ >
107
+ {!!searchValue && ( // Only render the list if there is a search value
108
+ <List
109
+ aria-label="Search results"
110
+ sections={[
111
+ {
112
+ id: "people",
113
+ items: filteredData.map((item) => ({
114
+ id: item.id,
115
+ title: item.fullname,
116
+ subtitle: item.position,
117
+ href: `https://www.google.com?q=${item.fullname}`,
118
+ target: "_blank",
119
+ })),
120
+ },
121
+ ]}
122
+ renderEmptyState={() => (
123
+ <EmptyState
124
+ title="No results"
125
+ description="No results found"
126
+ icon={"help_outline"}
127
+ >
128
+ <Button
129
+ text="Clear search"
130
+ onClick={() => setSearchValue("")}
131
+ mode="secondary"
132
+ />
133
+ <Button text="Close" onClick={() => setIsOpen(false)} />
134
+ </EmptyState>
135
+ )}
136
+ />
137
+ )}
138
+ </SearchBar>
139
+ </div>
140
+ );
141
+ };
142
+ return <Parent />;
143
+ },
144
+ play: async ({ canvasElement }) => {
145
+ const canvas = within(canvasElement.ownerDocument.body);
146
+ const user = userEvent.setup();
147
+ await user.click(canvas.getByText("Open"));
148
+ await canvas.findByPlaceholderText("Search in the application");
149
+ },
150
+ };
151
+
152
+ export const WithTabs: Story = {
153
+ render: () => {
154
+ // Data, typically fetched from a database
155
+ const users = [
156
+ {
157
+ id: "user-id-1",
158
+ fullname: "Alice Johnson",
159
+ position: "Chief Executive Officer",
160
+ },
161
+ {
162
+ id: "user-id-2",
163
+ fullname: "Bob Smith",
164
+ position: "Lead Software Engineer",
165
+ },
166
+ {
167
+ id: "user-id-3",
168
+ fullname: "Carol Lee",
169
+ position: "Product Manager",
170
+ },
171
+ ];
172
+
173
+ const companies = [
174
+ {
175
+ id: "company-id-1",
176
+ name: "Agregio Solutions",
177
+ },
178
+ {
179
+ id: "company-id-2",
180
+ name: "EDF Store and Forecast",
181
+ },
182
+ {
183
+ id: "company-id-3",
184
+ name: "Dalkia",
185
+ },
186
+ ];
187
+
188
+ const Empty = ({
189
+ setSearchValue,
190
+ setIsOpen,
191
+ }: {
192
+ setSearchValue: (value: string) => void;
193
+ setIsOpen: (value: boolean) => void;
194
+ }) => {
195
+ return (
196
+ <EmptyState
197
+ title="No results"
198
+ description="No results found"
199
+ icon={"help_outline"}
200
+ >
201
+ <Button
202
+ text="Clear search"
203
+ onClick={() => setSearchValue("")}
204
+ mode="secondary"
205
+ />
206
+ <Button text="Close" onClick={() => setIsOpen(false)} />
207
+ </EmptyState>
208
+ );
209
+ };
210
+
211
+ const Parent = () => {
212
+ const [searchValue, setSearchValue] = useState("");
213
+ const [isOpen, setIsOpen] = useState(false);
214
+
215
+ // Manually filter the data based on the search value (have to be done on your side, not handled by the component)
216
+ const filteredUsers = users.filter((user) => {
217
+ // Here we do not return an empty array if there is no search value, this is an example of how you can show all the data by default on SearchBar opening.
218
+ const searchValueLower = searchValue.toLowerCase();
219
+ return (
220
+ user.fullname.toLowerCase().includes(searchValueLower) ||
221
+ user.position.toLowerCase().includes(searchValueLower) ||
222
+ user.id.toLowerCase() === searchValueLower
223
+ );
224
+ });
225
+
226
+ const filteredCompanies = companies.filter((company) => {
227
+ const searchValueLower = searchValue.toLowerCase();
228
+ return (
229
+ company.name.toLowerCase().includes(searchValueLower) ||
230
+ company.id.toLowerCase() === searchValueLower
231
+ );
232
+ });
233
+
234
+ const sections: ListProps["sections"] = [
235
+ {
236
+ id: "users",
237
+ title: "Users",
238
+ items: filteredUsers.map((user) => ({
239
+ id: user.id,
240
+ title: user.fullname,
241
+ subtitle: user.position,
242
+ href: `https://www.google.com?q=${user.fullname}`,
243
+ target: "_blank",
244
+ })),
245
+ },
246
+ {
247
+ id: "companies",
248
+ title: "Companies",
249
+ items: filteredCompanies.map((company) => ({
250
+ id: company.id,
251
+ title: company.name,
252
+ icon: "business",
253
+ })),
254
+ },
255
+ ];
256
+
257
+ return (
258
+ <div>
259
+ <Button onClick={() => setIsOpen(true)} text="Open" />
260
+
261
+ <SearchBar
262
+ inputProps={{
263
+ value: searchValue,
264
+ onChange: setSearchValue,
265
+ placeholder: "Search in the application",
266
+ }}
267
+ onClear={() => setSearchValue("")}
268
+ isOpen={isOpen}
269
+ onOpenChange={(newIsOpen) => {
270
+ setSearchValue(""); // Reset the search value when the modal is closed. This is not built-in, so you have the choice depending on your use case.
271
+ setIsOpen(newIsOpen);
272
+ }}
273
+ resultsCount={filteredUsers.length + filteredCompanies.length}
274
+ >
275
+ <Tabs>
276
+ <TabList>
277
+ <Tab id="all">All</Tab>
278
+ <Tab id="users">Users</Tab>
279
+ <Tab id="companies">Companies</Tab>
280
+ </TabList>
281
+ <TabPanel id="all">
282
+ <List
283
+ aria-label="All search results"
284
+ sections={sections}
285
+ renderEmptyState={() => (
286
+ <Empty
287
+ setSearchValue={setSearchValue}
288
+ setIsOpen={setIsOpen}
289
+ />
290
+ )}
291
+ />
292
+ </TabPanel>
293
+ <TabPanel id="users">
294
+ <List
295
+ aria-label="Users search results"
296
+ sections={sections.filter(
297
+ (section) => section.id === "users",
298
+ )}
299
+ renderEmptyState={() => (
300
+ <Empty
301
+ setSearchValue={setSearchValue}
302
+ setIsOpen={setIsOpen}
303
+ />
304
+ )}
305
+ />
306
+ </TabPanel>
307
+ <TabPanel id="companies">
308
+ <List
309
+ aria-label="Companies search results"
310
+ sections={sections.filter(
311
+ (section) => section.id === "companies",
312
+ )}
313
+ renderEmptyState={() => (
314
+ <Empty
315
+ setSearchValue={setSearchValue}
316
+ setIsOpen={setIsOpen}
317
+ />
318
+ )}
319
+ />
320
+ </TabPanel>
321
+ </Tabs>
322
+ </SearchBar>
323
+ </div>
324
+ );
325
+ };
326
+ return <Parent />;
327
+ },
328
+ play: async ({ canvasElement }) => {
329
+ const canvas = within(canvasElement.ownerDocument.body);
330
+ const user = userEvent.setup();
331
+ await user.click(canvas.getByText("Open"));
332
+ },
333
+ };
334
+
335
+ export const NoResultsEmptyState: Story = {
336
+ render: () => {
337
+ const Parent = () => {
338
+ const [searchValue, setSearchValue] = useState("Some search value");
339
+ const [isOpen, setIsOpen] = useState(false);
340
+
341
+ return (
342
+ <div>
343
+ <Button onClick={() => setIsOpen(true)} text="Open" />
344
+
345
+ <SearchBar
346
+ inputProps={{
347
+ value: searchValue,
348
+ onChange: setSearchValue,
349
+ placeholder: "Search in the application",
350
+ }}
351
+ onClear={() => setSearchValue("")}
352
+ isOpen={isOpen}
353
+ onOpenChange={(newIsOpen) => {
354
+ setSearchValue(""); // Reset the search value when the modal is closed. This is not built-in, so you have the choice depending on your use case.
355
+ setIsOpen(newIsOpen);
356
+ }}
357
+ resultsCount={0}
358
+ >
359
+ {/* Here we want to display all the data by default */}
360
+ <List
361
+ aria-label="Search results"
362
+ sections={[]}
363
+ renderEmptyState={() => (
364
+ <EmptyState
365
+ title="No results"
366
+ description="No results found"
367
+ icon={"help_outline"}
368
+ >
369
+ <Button
370
+ text="Clear search"
371
+ onClick={() => setSearchValue("")}
372
+ mode="secondary"
373
+ />
374
+ <Button text="Close" onClick={() => setIsOpen(false)} />
375
+ </EmptyState>
376
+ )}
377
+ />
378
+ </SearchBar>
379
+ </div>
380
+ );
381
+ };
382
+ return <Parent />;
383
+ },
384
+ play: async ({ canvasElement }) => {
385
+ const canvas = within(canvasElement.ownerDocument.body);
386
+ const user = userEvent.setup();
387
+ await user.click(canvas.getByText("Open"));
388
+ },
389
+ };
390
+ ```
391
+
392
+ ## How to test this component
393
+
394
+ Here are some more advanced stories with more testing coverage and examples that you can read to understand how to test this component.
395
+
396
+ ```tsx
397
+ import { Meta, StoryObj } from "@storybook/react-vite";
398
+ import { expect, userEvent, within } from "storybook/test";
399
+ import SearchBar from "../SearchBar";
400
+ import * as SearchBarStories from "../SearchBar.stories";
401
+
402
+ const meta: Meta<typeof SearchBar> = {
403
+ ...SearchBarStories.default,
404
+ component: SearchBar,
405
+ parameters: {
406
+ ...SearchBarStories.default.parameters,
407
+ chromatic: { disableSnapshot: true },
408
+ },
409
+ };
410
+ export default meta;
411
+
412
+ type Story = StoryObj<typeof meta>;
413
+
414
+ export const ShouldShowTheFilteredItems: Story = {
415
+ render: SearchBarStories.ExampleUsage.render,
416
+ play: async ({ canvasElement }) => {
417
+ const canvas = within(canvasElement.ownerDocument.body);
418
+ const user = userEvent.setup({ delay: 25 });
419
+ await user.click(canvas.getByText("Open"));
420
+
421
+ const input = await canvas.findByPlaceholderText(
422
+ "Search in the application",
423
+ );
424
+
425
+ // Check that items are not displayed yet
426
+ await expect(canvas.queryByText(/result/i)).not.toBeInTheDocument();
427
+ await expect(canvas.queryByText("Alice Johnson")).not.toBeInTheDocument();
428
+ await expect(canvas.queryByText("Bob Smith")).not.toBeInTheDocument();
429
+ await expect(canvas.queryByText("Carol Lee")).not.toBeInTheDocument();
430
+ await expect(canvas.queryByText("David Kim")).not.toBeInTheDocument();
431
+ await expect(canvas.queryByText("Eva Brown")).not.toBeInTheDocument();
432
+
433
+ // Check that the legend is not displayed
434
+ await expect(canvas.queryByText("Navigation")).not.toBeInTheDocument();
435
+ await expect(canvas.queryByText("Choose")).not.toBeInTheDocument();
436
+ await expect(canvas.queryByText("Close")).not.toBeInTheDocument();
437
+
438
+ // Type "Alice" in the search bar
439
+ await user.type(input, "Alice");
440
+
441
+ // Check that items are displayed
442
+ await canvas.findByText("1 result");
443
+ await canvas.findByText("Alice Johnson");
444
+ await expect(canvas.queryByText("Bob Smith")).not.toBeInTheDocument();
445
+ await expect(canvas.queryByText("Carol Lee")).not.toBeInTheDocument();
446
+ await expect(canvas.queryByText("David Kim")).not.toBeInTheDocument();
447
+ await expect(canvas.queryByText("Eva Brown")).not.toBeInTheDocument();
448
+ await canvas.findByText("Navigation");
449
+ await canvas.findByText("Choose");
450
+ await canvas.findByText("Close");
451
+
452
+ // Clear the search bar
453
+ await user.click(canvas.getByLabelText("Clear"));
454
+ await expect(input).toHaveValue("");
455
+ await expect(canvas.queryByText(/result/i)).not.toBeInTheDocument();
456
+ await expect(canvas.queryByText("Alice Johnson")).not.toBeInTheDocument();
457
+ await expect(canvas.queryByText("Bob Smith")).not.toBeInTheDocument();
458
+ await expect(canvas.queryByText("Carol Lee")).not.toBeInTheDocument();
459
+ await expect(canvas.queryByText("David Kim")).not.toBeInTheDocument();
460
+ await expect(canvas.queryByText("Eva Brown")).not.toBeInTheDocument();
461
+ await expect(canvas.queryByText("Navigation")).not.toBeInTheDocument();
462
+ await expect(canvas.queryByText("Choose")).not.toBeInTheDocument();
463
+ await expect(canvas.queryByText("Close")).not.toBeInTheDocument();
464
+ },
465
+ };
466
+ ```
467
+
468
+ ## Developer notes
469
+
470
+ 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.
471
+
472
+ ```mdx
473
+ import { Meta, Controls, Source, Canvas } from "@storybook/addon-docs/blocks";
474
+
475
+ import * as SearchBar from "./SearchBar.stories";
476
+
477
+ <Meta of={SearchBar} />
478
+
479
+ # SearchBar
480
+
481
+ <Canvas of={SearchBar.ExampleUsage} />
482
+ <Controls of={SearchBar.ExampleUsage} />
483
+
484
+ ## Basic example usage
485
+
486
+ Below you can find a basic example usage of the SearchBar component.
487
+ You will find that there is a lot of customization and flexibility, some UX aspects can totally be decided on your side.
488
+
489
+ For example, here we choose to not display items if there is no search value in the input. Feel free to change this behavior to your needs.
490
+
491
+ Also, the filtering is done on your side, so you can decide all the aspects of this feature (like which keys to filter on, how to filter them, add fuzzy search, etc.).
492
+ Even if there is no example usage for this use case, it should be really easy to implement a back-end filtering as well, based on the search value.
493
+
494
+ Click on "Show code" to see the code.
495
+
496
+ <Canvas of={SearchBar.ExampleUsage} />
497
+
498
+ ### With tabs example usage
499
+
500
+ Here is another example where we use tabs to display different types of items.
501
+ Some of the UX decisions voluntary differ from the basic example usage, so you can see how easy it is to customize the component to your needs (i.e. here we display results even if there is no search value in the input).
502
+
503
+ <Canvas of={SearchBar.WithTabs} />
504
+ ```