@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,651 @@
|
|
|
1
|
+
# PageLayout
|
|
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/PageLayout/PageLayout.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 } from "storybook/test";
|
|
18
|
+
import PageLayout from "./PageLayout";
|
|
19
|
+
import { STORYBOOK_VIEWPORTS } from "@internal/test-utils-storybook/test-utils-storybook";
|
|
20
|
+
import NavigationItem from "../NavigationItem/NavigationItem";
|
|
21
|
+
import Button from "../Button/Button";
|
|
22
|
+
import Navigation from "../Navigation/Navigation";
|
|
23
|
+
import Tag from "../Tag/Tag";
|
|
24
|
+
import Breadcrumbs from "../Breadcrumbs/Breadcrumbs";
|
|
25
|
+
import Header from "../Header/Header";
|
|
26
|
+
import Select from "../Select/Select";
|
|
27
|
+
import SelectItem from "../SelectItem/SelectItem";
|
|
28
|
+
import Tabs from "../Tabs/Tabs/Tabs";
|
|
29
|
+
import TabList from "../Tabs/TabList/TabList";
|
|
30
|
+
import Tab from "../Tabs/Tab/Tab";
|
|
31
|
+
import TabPanel from "../Tabs/TabPanel/TabPanel";
|
|
32
|
+
import arrayOfLength from "@internal/arrayOfLength/arrayOfLength";
|
|
33
|
+
import { I18nProvider } from "react-aria-components";
|
|
34
|
+
import { useMemo } from "react";
|
|
35
|
+
import {
|
|
36
|
+
createColumnHelper,
|
|
37
|
+
getCoreRowModel,
|
|
38
|
+
getFilteredRowModel,
|
|
39
|
+
getPaginationRowModel,
|
|
40
|
+
getSortedRowModel,
|
|
41
|
+
useReactTable,
|
|
42
|
+
} from "@tanstack/react-table";
|
|
43
|
+
import DataTableHeader from "../DataTable/DataTableHeader/DataTableHeader";
|
|
44
|
+
import DataTableCell from "../DataTable/DataTableCell/DataTableCell";
|
|
45
|
+
import Radio from "../Radio/Radio";
|
|
46
|
+
import Filter, { TypeTableColumnWithFilter } from "../Filter/Filter";
|
|
47
|
+
import CheckboxGroup from "../CheckboxGroup/CheckboxGroup";
|
|
48
|
+
import Checkbox from "../Checkbox/Checkbox";
|
|
49
|
+
import Switch from "../Switch/Switch";
|
|
50
|
+
import DataTable from "../DataTable/DataTable";
|
|
51
|
+
|
|
52
|
+
const meta: Meta<typeof PageLayout> = {
|
|
53
|
+
component: PageLayout,
|
|
54
|
+
parameters: {
|
|
55
|
+
layout: "none",
|
|
56
|
+
},
|
|
57
|
+
decorators: [
|
|
58
|
+
(Story) => {
|
|
59
|
+
return (
|
|
60
|
+
<I18nProvider locale="fr">
|
|
61
|
+
<Story />
|
|
62
|
+
</I18nProvider>
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
export default meta;
|
|
68
|
+
|
|
69
|
+
type Story = StoryObj<typeof meta>;
|
|
70
|
+
|
|
71
|
+
export const Desktop: Story = {
|
|
72
|
+
args: {
|
|
73
|
+
navigationComponent: (
|
|
74
|
+
<Navigation logo="agregio" onParametersClick={fn()} onLogoutClick={fn()}>
|
|
75
|
+
<>
|
|
76
|
+
{arrayOfLength(10).map((index) => (
|
|
77
|
+
<NavigationItem
|
|
78
|
+
key={index}
|
|
79
|
+
label={`Item ${index}`}
|
|
80
|
+
href={`/item-${index}`}
|
|
81
|
+
iconLeft="home"
|
|
82
|
+
/>
|
|
83
|
+
))}
|
|
84
|
+
<NavigationItem
|
|
85
|
+
label={`Item Dropdown`}
|
|
86
|
+
iconLeft="home"
|
|
87
|
+
subLinks={[
|
|
88
|
+
{ href: "/", label: "Sous-libellé 1" },
|
|
89
|
+
{ href: "/", label: "Sous-libellé 2" },
|
|
90
|
+
]}
|
|
91
|
+
/>
|
|
92
|
+
</>
|
|
93
|
+
</Navigation>
|
|
94
|
+
),
|
|
95
|
+
children: (
|
|
96
|
+
<>
|
|
97
|
+
<Header
|
|
98
|
+
title="Insert title"
|
|
99
|
+
subtitle="Insert subtitle"
|
|
100
|
+
tagComponent={<Tag iconLeft="help_outline">[Insert tag]</Tag>}
|
|
101
|
+
logo="agregio"
|
|
102
|
+
onGoBack={fn()}
|
|
103
|
+
breadcrumbsComponent={
|
|
104
|
+
<Breadcrumbs
|
|
105
|
+
links={[
|
|
106
|
+
{ href: "#link-1", text: "Link 1" },
|
|
107
|
+
{ href: "#link-2", text: "Link 2" },
|
|
108
|
+
{ href: "#link-3", text: "Link 3" },
|
|
109
|
+
]}
|
|
110
|
+
/>
|
|
111
|
+
}
|
|
112
|
+
helperIconComponent={
|
|
113
|
+
<Button
|
|
114
|
+
iconLeft="help_outline"
|
|
115
|
+
onClick={() => alert("Help")}
|
|
116
|
+
mode="tertiary"
|
|
117
|
+
/>
|
|
118
|
+
}
|
|
119
|
+
notificationIconComponent={
|
|
120
|
+
<Button
|
|
121
|
+
iconLeft="notifications_none"
|
|
122
|
+
onClick={() => alert("Notifications")}
|
|
123
|
+
mode="tertiary"
|
|
124
|
+
/>
|
|
125
|
+
}
|
|
126
|
+
additionalActions={
|
|
127
|
+
<Select
|
|
128
|
+
mode="single"
|
|
129
|
+
id="select"
|
|
130
|
+
aria-label="Select"
|
|
131
|
+
value={null}
|
|
132
|
+
onChange={fn()}
|
|
133
|
+
placeholder="Select"
|
|
134
|
+
>
|
|
135
|
+
<SelectItem id="select-item-1" text="Select Item 1" />
|
|
136
|
+
<SelectItem id="select-item-2" text="Select Item 2" />
|
|
137
|
+
<SelectItem id="select-item-3" text="Select Item 3" />
|
|
138
|
+
</Select>
|
|
139
|
+
}
|
|
140
|
+
tabsComponent={
|
|
141
|
+
<Tabs>
|
|
142
|
+
<TabList>
|
|
143
|
+
<Tab id="tab-1">Tab 1</Tab>
|
|
144
|
+
<Tab
|
|
145
|
+
id="tab-2"
|
|
146
|
+
badgeProps={{ nature: "negative", variant: "dot" }}
|
|
147
|
+
>
|
|
148
|
+
Tab 2
|
|
149
|
+
</Tab>
|
|
150
|
+
<Tab id="tab-3" badgeProps={{ value: 8, nature: "negative" }}>
|
|
151
|
+
Tab 3
|
|
152
|
+
</Tab>
|
|
153
|
+
</TabList>
|
|
154
|
+
<div style={{ marginTop: "var(--spacing-md" }}>
|
|
155
|
+
<TabPanel id="tab-1">Content for the panel 1</TabPanel>
|
|
156
|
+
<TabPanel id="tab-2">Content for the panel 2</TabPanel>
|
|
157
|
+
<TabPanel id="tab-3">Content for the panel 3</TabPanel>
|
|
158
|
+
</div>
|
|
159
|
+
</Tabs>
|
|
160
|
+
}
|
|
161
|
+
/>
|
|
162
|
+
</>
|
|
163
|
+
),
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const Mobile: Story = {
|
|
168
|
+
args: {
|
|
169
|
+
...Desktop.args,
|
|
170
|
+
},
|
|
171
|
+
parameters: {
|
|
172
|
+
...STORYBOOK_VIEWPORTS,
|
|
173
|
+
layout: "none",
|
|
174
|
+
},
|
|
175
|
+
globals: {
|
|
176
|
+
viewport: { value: "iphone6", isRotated: false },
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
export const PageLayoutWithDataTable: Story = {
|
|
181
|
+
args: {
|
|
182
|
+
...Desktop.args,
|
|
183
|
+
},
|
|
184
|
+
render: () => {
|
|
185
|
+
const ParentComponent = () => {
|
|
186
|
+
// Below are just type definition of the data and its generation
|
|
187
|
+
// Typically you would get this from your API and will not have to create them here
|
|
188
|
+
// We let it so you can fully understand how the filter works based on the data provided
|
|
189
|
+
|
|
190
|
+
type User = {
|
|
191
|
+
firstname: string;
|
|
192
|
+
lastname: string;
|
|
193
|
+
email: string;
|
|
194
|
+
isValidated?: boolean;
|
|
195
|
+
day?: string;
|
|
196
|
+
foods?: Array<string>;
|
|
197
|
+
organization?: { id: string; name: string };
|
|
198
|
+
isActive?: boolean;
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const ALL_FOODS = useMemo(
|
|
202
|
+
() => ["Pizza", "Hamburger", "Ramen", "Sushi", "Fries"],
|
|
203
|
+
[],
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
const FOODS = useMemo(
|
|
207
|
+
() => [
|
|
208
|
+
[ALL_FOODS[0], ALL_FOODS[1]],
|
|
209
|
+
[ALL_FOODS[1], ALL_FOODS[2]],
|
|
210
|
+
[ALL_FOODS[2], ALL_FOODS[3]],
|
|
211
|
+
[ALL_FOODS[3], ALL_FOODS[4]],
|
|
212
|
+
[ALL_FOODS[4], ALL_FOODS[0]],
|
|
213
|
+
],
|
|
214
|
+
[ALL_FOODS],
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
const ORGANIZATIONS = useMemo(
|
|
218
|
+
() => [
|
|
219
|
+
{ id: "1", name: "EDF Store and Forecast" },
|
|
220
|
+
{ id: "2", name: "Agregio Solutions" },
|
|
221
|
+
{ id: "3", name: "Dalkia" },
|
|
222
|
+
],
|
|
223
|
+
[],
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const DAYS = useMemo(
|
|
227
|
+
() => ["Monday", "Tuesday", "Wednesday", "Thursday"],
|
|
228
|
+
[],
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
const data: Array<User> = useMemo(
|
|
232
|
+
() =>
|
|
233
|
+
arrayOfLength(300).map((index) => ({
|
|
234
|
+
firstname: `Firstname ${index + 1}`,
|
|
235
|
+
lastname: `Lastname ${index + 1}`,
|
|
236
|
+
email: `john-doe${index + 1}@example.com`,
|
|
237
|
+
isValidated: index % 2 === 0,
|
|
238
|
+
foods: FOODS[index % FOODS.length],
|
|
239
|
+
day: DAYS[index % DAYS.length],
|
|
240
|
+
organization: ORGANIZATIONS[index % ORGANIZATIONS.length],
|
|
241
|
+
isActive: index % 2 === 0,
|
|
242
|
+
})),
|
|
243
|
+
[DAYS, FOODS, ORGANIZATIONS],
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
// Here starts the interesting stuff: columns definitions.
|
|
247
|
+
// All the features of the filters comes from those columns definitions
|
|
248
|
+
// Please read carefully each column, they all serve as a different example of how to use the filters
|
|
249
|
+
const columnHelper = createColumnHelper<User>();
|
|
250
|
+
|
|
251
|
+
const columns = useMemo(
|
|
252
|
+
() => [
|
|
253
|
+
columnHelper.accessor("firstname", {
|
|
254
|
+
// Here, because there is no "FilterInDrawer" component, there will be nothing in the filter drawer related to the firstname
|
|
255
|
+
// But the firstname can still be filtered by the global filter (search input)
|
|
256
|
+
header: ({ header }) => (
|
|
257
|
+
<DataTableHeader
|
|
258
|
+
onSortClick={header.column.getToggleSortingHandler()}
|
|
259
|
+
sortDirection={header.column.getIsSorted() || "none"}
|
|
260
|
+
text="Firstname"
|
|
261
|
+
/>
|
|
262
|
+
),
|
|
263
|
+
cell: (props) => <DataTableCell>{props.getValue()}</DataTableCell>,
|
|
264
|
+
}),
|
|
265
|
+
// Here is an example using a "FilterInDrawer" component that render a radio group to filter the day
|
|
266
|
+
// Please note the "filterFn" is "arrIncludes" for this king of value (Array<string>)
|
|
267
|
+
// Also note the usage of `satisfies TypeTableColumnWithFilter<User>` to help you type check the column definition (because meta is a custom property)
|
|
268
|
+
// Finally, you will note that the Chips are auto-generated, if you do not want to use them you can set the `skipFilterChips` to true
|
|
269
|
+
columnHelper.accessor("day", {
|
|
270
|
+
id: "Day",
|
|
271
|
+
header: () => <DataTableHeader text="Day" />,
|
|
272
|
+
cell: (props) => <DataTableCell>{props.getValue()}</DataTableCell>,
|
|
273
|
+
filterFn: "equals",
|
|
274
|
+
meta: {
|
|
275
|
+
FilterInDrawer: ({ column }) => (
|
|
276
|
+
<div>
|
|
277
|
+
<b>Filter by day:</b>
|
|
278
|
+
|
|
279
|
+
{DAYS.map((day) => (
|
|
280
|
+
<Radio
|
|
281
|
+
label={day}
|
|
282
|
+
key={day}
|
|
283
|
+
checked={column.getFilterValue() === day}
|
|
284
|
+
onChange={() => column.setFilterValue(day)}
|
|
285
|
+
/>
|
|
286
|
+
))}
|
|
287
|
+
</div>
|
|
288
|
+
),
|
|
289
|
+
} satisfies TypeTableColumnWithFilter<User>,
|
|
290
|
+
}),
|
|
291
|
+
// Here is another example using a "FilterInDrawer" component that render a checkbox group to filter the food
|
|
292
|
+
// Please note the "filterFn" is "arrIncludesSome" -> we want to include all entries that match at least one food
|
|
293
|
+
// Also note the usage of `satisfies TypeTableColumnWithFilter<User>` to help you type check the column definition (because meta is a custom property)
|
|
294
|
+
// Finally, you will note that the Chips are auto-generated, if you do not want to use them you can set the `skipFilterChips` to true
|
|
295
|
+
columnHelper.accessor("foods", {
|
|
296
|
+
id: "Foods",
|
|
297
|
+
header: () => <DataTableHeader text="Foods" />,
|
|
298
|
+
cell: (props) => (
|
|
299
|
+
<DataTableCell>{props.getValue()?.join(", ")}</DataTableCell>
|
|
300
|
+
),
|
|
301
|
+
filterFn: "arrIncludesSome",
|
|
302
|
+
meta: {
|
|
303
|
+
FilterInDrawer: ({ column }) => (
|
|
304
|
+
<div style={{ marginTop: 8 }}>
|
|
305
|
+
<b>Filter by food (at least one):</b>
|
|
306
|
+
<CheckboxGroup
|
|
307
|
+
value={column.getFilterValue() as any}
|
|
308
|
+
onChange={column.setFilterValue}
|
|
309
|
+
aria-label="Filter by food (at least one)"
|
|
310
|
+
>
|
|
311
|
+
{ALL_FOODS.map((food) => (
|
|
312
|
+
<div key={food}>
|
|
313
|
+
<Checkbox label={food} value={food} />
|
|
314
|
+
</div>
|
|
315
|
+
))}
|
|
316
|
+
</CheckboxGroup>
|
|
317
|
+
</div>
|
|
318
|
+
),
|
|
319
|
+
} satisfies TypeTableColumnWithFilter<User>,
|
|
320
|
+
}),
|
|
321
|
+
// Here is another example using a "FilterInDrawer" component that render a select to filter the organization
|
|
322
|
+
// Please note the "filterFn" is a custom function that will filter the data based on the organization id
|
|
323
|
+
// Also note the usage of `satisfies TypeTableColumnWithFilter<User>` to help you type check the column definition (because meta is a custom property)
|
|
324
|
+
// And most important here, we have a custom meta property `filterChips` that will generate a chip to display the selected organization
|
|
325
|
+
// The Filter can not handle automatic chips when the data is a complex object, so you have to handle that manually
|
|
326
|
+
columnHelper.accessor("organization", {
|
|
327
|
+
id: "Organization",
|
|
328
|
+
header: () => <DataTableHeader text="Organization" />,
|
|
329
|
+
cell: (props) => (
|
|
330
|
+
<DataTableCell>{props.getValue()?.name}</DataTableCell>
|
|
331
|
+
),
|
|
332
|
+
filterFn: (row, _, filterValue) => {
|
|
333
|
+
if (!filterValue) return true;
|
|
334
|
+
return row.original.organization?.id === filterValue;
|
|
335
|
+
},
|
|
336
|
+
meta: {
|
|
337
|
+
filterChips: ({ column }) => {
|
|
338
|
+
const value = column.getFilterValue();
|
|
339
|
+
const organization = ORGANIZATIONS.find(
|
|
340
|
+
(org) => org.id === value,
|
|
341
|
+
);
|
|
342
|
+
if (!organization) return [];
|
|
343
|
+
return [
|
|
344
|
+
{
|
|
345
|
+
value: `Organization: ${organization.name}`,
|
|
346
|
+
onClose: () => column.setFilterValue(null),
|
|
347
|
+
},
|
|
348
|
+
];
|
|
349
|
+
},
|
|
350
|
+
FilterInDrawer: ({ column }) => (
|
|
351
|
+
<Select
|
|
352
|
+
id="organization-select"
|
|
353
|
+
label="Filter by organization:"
|
|
354
|
+
mode="single"
|
|
355
|
+
value={column.getFilterValue() as any}
|
|
356
|
+
onChange={column.setFilterValue}
|
|
357
|
+
fullWidth
|
|
358
|
+
wrapperProps={{ style: { marginBottom: 8 } }}
|
|
359
|
+
>
|
|
360
|
+
{ORGANIZATIONS.map((organization) => (
|
|
361
|
+
<SelectItem
|
|
362
|
+
text={organization.name}
|
|
363
|
+
id={organization.id}
|
|
364
|
+
key={organization.id}
|
|
365
|
+
/>
|
|
366
|
+
))}
|
|
367
|
+
</Select>
|
|
368
|
+
),
|
|
369
|
+
} satisfies TypeTableColumnWithFilter<User>,
|
|
370
|
+
}),
|
|
371
|
+
// Here is an example that uses a filter outside of the drawer.
|
|
372
|
+
// See the "extraLeftContent" prop to see how to connect it to react-table filtering features
|
|
373
|
+
columnHelper.accessor("isActive", {
|
|
374
|
+
header: () => <DataTableHeader text="Active" />,
|
|
375
|
+
cell: (props) => (
|
|
376
|
+
<DataTableCell>{props.getValue() ? "✅" : "❌"}</DataTableCell>
|
|
377
|
+
),
|
|
378
|
+
filterFn: (row, _, filterValue) => {
|
|
379
|
+
if (!filterValue) return true;
|
|
380
|
+
return !!row.original.isActive;
|
|
381
|
+
},
|
|
382
|
+
}),
|
|
383
|
+
// Here is an example of an empty column, for example to display an actions bar
|
|
384
|
+
// You can note that the header is empty, the text is optional
|
|
385
|
+
columnHelper.display({
|
|
386
|
+
id: "actions",
|
|
387
|
+
header: () => <DataTableHeader />,
|
|
388
|
+
cell: () => (
|
|
389
|
+
<DataTableCell>
|
|
390
|
+
<Button mode="secondary" iconLeft="edit" />
|
|
391
|
+
</DataTableCell>
|
|
392
|
+
),
|
|
393
|
+
}),
|
|
394
|
+
],
|
|
395
|
+
[ALL_FOODS, DAYS, ORGANIZATIONS, columnHelper],
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
// And here we define the table as usual
|
|
399
|
+
const table = useReactTable({
|
|
400
|
+
data,
|
|
401
|
+
columns,
|
|
402
|
+
initialState: {
|
|
403
|
+
globalFilter: "",
|
|
404
|
+
pagination: {
|
|
405
|
+
pageSize: 50,
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
getCoreRowModel: getCoreRowModel(),
|
|
409
|
+
getSortedRowModel: getSortedRowModel(),
|
|
410
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
411
|
+
getPaginationRowModel: getPaginationRowModel(),
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Get the pagination state
|
|
415
|
+
const pagination = table.getState().pagination;
|
|
416
|
+
|
|
417
|
+
// Render the table using the base DataTable component
|
|
418
|
+
return (
|
|
419
|
+
<PageLayout
|
|
420
|
+
navigationComponent={
|
|
421
|
+
<Navigation
|
|
422
|
+
logo="agregio"
|
|
423
|
+
onParametersClick={fn()}
|
|
424
|
+
onLogoutClick={fn()}
|
|
425
|
+
>
|
|
426
|
+
<>
|
|
427
|
+
{arrayOfLength(10).map((index) => (
|
|
428
|
+
<NavigationItem
|
|
429
|
+
key={index}
|
|
430
|
+
label={`Item ${index}`}
|
|
431
|
+
href={`/item-${index}`}
|
|
432
|
+
iconLeft="home"
|
|
433
|
+
/>
|
|
434
|
+
))}
|
|
435
|
+
<NavigationItem
|
|
436
|
+
label={`Item Dropdown`}
|
|
437
|
+
iconLeft="home"
|
|
438
|
+
subLinks={[
|
|
439
|
+
{ href: "/", label: "Sous-libellé 1" },
|
|
440
|
+
{ href: "/", label: "Sous-libellé 2" },
|
|
441
|
+
]}
|
|
442
|
+
/>
|
|
443
|
+
</>
|
|
444
|
+
</Navigation>
|
|
445
|
+
}
|
|
446
|
+
>
|
|
447
|
+
<>
|
|
448
|
+
<Header title={"Test"} />
|
|
449
|
+
<Filter<User>
|
|
450
|
+
onDownloadClick={() => alert("Download")}
|
|
451
|
+
onSettingsClick={() => alert("Settings")}
|
|
452
|
+
table={table}
|
|
453
|
+
extraLeftContent={
|
|
454
|
+
<>
|
|
455
|
+
<Switch
|
|
456
|
+
label="Only active"
|
|
457
|
+
isSelected={!!table.getColumn("isActive")?.getFilterValue()}
|
|
458
|
+
onChange={() =>
|
|
459
|
+
table
|
|
460
|
+
.getColumn("isActive")
|
|
461
|
+
?.setFilterValue((prevValue: boolean) => !prevValue)
|
|
462
|
+
}
|
|
463
|
+
/>
|
|
464
|
+
</>
|
|
465
|
+
}
|
|
466
|
+
extraRightContent={
|
|
467
|
+
<>
|
|
468
|
+
<Button
|
|
469
|
+
mode="secondary"
|
|
470
|
+
onClick={() => alert("Secondary")}
|
|
471
|
+
text="Secondary"
|
|
472
|
+
/>
|
|
473
|
+
<Button
|
|
474
|
+
mode="primary"
|
|
475
|
+
onClick={() => alert("Primary")}
|
|
476
|
+
text="Primary"
|
|
477
|
+
/>
|
|
478
|
+
</>
|
|
479
|
+
}
|
|
480
|
+
/>
|
|
481
|
+
<DataTable table={table} pagination={pagination} />
|
|
482
|
+
</>
|
|
483
|
+
</PageLayout>
|
|
484
|
+
);
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
return <ParentComponent />;
|
|
488
|
+
},
|
|
489
|
+
};
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
## How to test this component
|
|
493
|
+
|
|
494
|
+
Here are some more advanced stories with more testing coverage and examples that you can read to understand how to test this component.
|
|
495
|
+
|
|
496
|
+
```tsx
|
|
497
|
+
import { Meta, StoryObj } from "@storybook/react-vite";
|
|
498
|
+
import { expect, userEvent, within } from "storybook/test";
|
|
499
|
+
import PageLayout from "../PageLayout";
|
|
500
|
+
import { STORYBOOK_VIEWPORTS } from "@internal/test-utils-storybook/test-utils-storybook";
|
|
501
|
+
import * as PageLayoutStories from "../PageLayout.stories";
|
|
502
|
+
|
|
503
|
+
const meta: Meta<typeof PageLayout> = {
|
|
504
|
+
...PageLayoutStories.default,
|
|
505
|
+
parameters: {
|
|
506
|
+
...PageLayoutStories.default.parameters,
|
|
507
|
+
chromatic: { disableSnapshot: true },
|
|
508
|
+
},
|
|
509
|
+
component: PageLayout,
|
|
510
|
+
};
|
|
511
|
+
export default meta;
|
|
512
|
+
|
|
513
|
+
type Story = StoryObj<typeof meta>;
|
|
514
|
+
|
|
515
|
+
export const Desktop: Story = {
|
|
516
|
+
args: {
|
|
517
|
+
...PageLayoutStories.Desktop.args,
|
|
518
|
+
},
|
|
519
|
+
play: async ({ canvasElement }) => {
|
|
520
|
+
const canvas = within(canvasElement);
|
|
521
|
+
const user = userEvent.setup();
|
|
522
|
+
|
|
523
|
+
await expect(canvas.getByText("Item 0")).toBeVisible();
|
|
524
|
+
await expect(canvas.getByLabelText("Menu mobile")).not.toBeVisible();
|
|
525
|
+
await expect(
|
|
526
|
+
canvas.getByTestId("navigation-mobile-collapsed-menu"),
|
|
527
|
+
).toBeVisible();
|
|
528
|
+
await expect(
|
|
529
|
+
canvas.getByTestId("navigation-mobile-close-menu"),
|
|
530
|
+
).not.toBeVisible();
|
|
531
|
+
|
|
532
|
+
await user.click(canvas.getByTestId("navigation-mobile-collapsed-menu"));
|
|
533
|
+
|
|
534
|
+
await expect(canvas.getByLabelText("Menu mobile")).not.toBeVisible();
|
|
535
|
+
await expect(canvas.getByText("Item 0")).not.toBeVisible();
|
|
536
|
+
await expect(
|
|
537
|
+
canvas.getByTestId("navigation-mobile-collapsed-menu"),
|
|
538
|
+
).toBeVisible();
|
|
539
|
+
await expect(
|
|
540
|
+
canvas.getByTestId("navigation-mobile-close-menu"),
|
|
541
|
+
).not.toBeVisible();
|
|
542
|
+
|
|
543
|
+
await user.click(canvas.getByTestId("navigation-mobile-collapsed-menu"));
|
|
544
|
+
|
|
545
|
+
await expect(canvas.getByText("Item 0")).toBeVisible();
|
|
546
|
+
},
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
export const MobileShouldOpenNavigationOnClickOnMobileBurgerMenu: Story = {
|
|
550
|
+
args: {
|
|
551
|
+
...Desktop.args,
|
|
552
|
+
},
|
|
553
|
+
parameters: {
|
|
554
|
+
...STORYBOOK_VIEWPORTS,
|
|
555
|
+
layout: "none",
|
|
556
|
+
},
|
|
557
|
+
globals: {
|
|
558
|
+
viewport: { value: "iphone6", isRotated: false },
|
|
559
|
+
},
|
|
560
|
+
// TODO: see https://github.com/storybookjs/test-runner/issues/85
|
|
561
|
+
// play: async ({ canvasElement }) => {
|
|
562
|
+
// const canvas = within(canvasElement);
|
|
563
|
+
// const user = userEvent.setup();
|
|
564
|
+
|
|
565
|
+
// await expect(canvas.getByText("Item 0")).not.toBeVisible();
|
|
566
|
+
// await expectPresent(() => canvas.getByLabelText("Menu mobile"));
|
|
567
|
+
// await expect(
|
|
568
|
+
// canvas.getByTestId("navigation-mobile-close-menu"),
|
|
569
|
+
// ).not.toBeVisible();
|
|
570
|
+
|
|
571
|
+
// await user.click(canvas.getByLabelText("Menu mobile"));
|
|
572
|
+
|
|
573
|
+
// await expect(canvas.getByText("Item 0")).toBeVisible();
|
|
574
|
+
// await expect(
|
|
575
|
+
// canvas.getByTestId("navigation-mobile-close-menu"),
|
|
576
|
+
// ).toBeVisible();
|
|
577
|
+
|
|
578
|
+
// await user.click(canvas.getByTestId("navigation-mobile-close-menu"));
|
|
579
|
+
|
|
580
|
+
// await expect(
|
|
581
|
+
// canvas.getByTestId("navigation-mobile-close-menu"),
|
|
582
|
+
// ).not.toBeVisible();
|
|
583
|
+
// await expect(canvas.getByText("Item 0")).not.toBeVisible();
|
|
584
|
+
// },
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
export const MobileShouldCloseNavigationOnClickOnNavigationItem: Story = {
|
|
588
|
+
args: {
|
|
589
|
+
...Desktop.args,
|
|
590
|
+
},
|
|
591
|
+
parameters: {
|
|
592
|
+
...STORYBOOK_VIEWPORTS,
|
|
593
|
+
layout: "none",
|
|
594
|
+
},
|
|
595
|
+
globals: {
|
|
596
|
+
viewport: { value: "iphone6", isRotated: false },
|
|
597
|
+
},
|
|
598
|
+
// TODO: see https://github.com/storybookjs/test-runner/issues/85
|
|
599
|
+
// play: async ({ canvasElement }) => {
|
|
600
|
+
// const canvas = within(canvasElement);
|
|
601
|
+
// const user = userEvent.setup();
|
|
602
|
+
|
|
603
|
+
// await expect(canvas.getByText("Item 0")).not.toBeVisible();
|
|
604
|
+
// await expectPresent(() => canvas.getByLabelText("Menu mobile"));
|
|
605
|
+
// await expect(
|
|
606
|
+
// canvas.getByTestId("navigation-mobile-close-menu"),
|
|
607
|
+
// ).not.toBeVisible();
|
|
608
|
+
|
|
609
|
+
// await user.click(canvas.getByTestId("navigation-mobile-close-menu"));
|
|
610
|
+
|
|
611
|
+
// await expect(canvas.getByText("Item 0")).toBeVisible();
|
|
612
|
+
// await expect(
|
|
613
|
+
// canvas.getByTestId("navigation-mobile-close-menu"),
|
|
614
|
+
// ).toBeVisible();
|
|
615
|
+
|
|
616
|
+
// await user.click(canvas.getByText("Item 1"));
|
|
617
|
+
|
|
618
|
+
// await expect(
|
|
619
|
+
// canvas.getByTestId("navigation-mobile-close-menu"),
|
|
620
|
+
// ).not.toBeVisible();
|
|
621
|
+
// await expect(canvas.queryByText("Item 1")).not.toBeVisible();
|
|
622
|
+
// },
|
|
623
|
+
};
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
## Developer notes
|
|
627
|
+
|
|
628
|
+
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.
|
|
629
|
+
|
|
630
|
+
```mdx
|
|
631
|
+
import {
|
|
632
|
+
Canvas,
|
|
633
|
+
Meta,
|
|
634
|
+
Stories,
|
|
635
|
+
Controls,
|
|
636
|
+
Source,
|
|
637
|
+
} from "@storybook/addon-docs/blocks";
|
|
638
|
+
|
|
639
|
+
import * as PageLayout from "./PageLayout.stories";
|
|
640
|
+
import * as PageLayoutTests from "./tests/PageLayout.stories";
|
|
641
|
+
|
|
642
|
+
<Meta of={PageLayout} />
|
|
643
|
+
|
|
644
|
+
# PageLayout
|
|
645
|
+
|
|
646
|
+
A page layout is a container for all the content of a page.
|
|
647
|
+
|
|
648
|
+
## Example usage
|
|
649
|
+
|
|
650
|
+
Coming soon...
|
|
651
|
+
```
|