@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,167 @@
1
+ # NavigationItem
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/NavigationItem/NavigationItem.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 { expect, within } from "storybook/test";
18
+ import NavigationItem from "./NavigationItem";
19
+ import { expectNotPresent } from "@internal/test-utils-storybook/test-utils-storybook";
20
+ import { NavigationProvider } from "../Navigation/context";
21
+
22
+ const meta = {
23
+ component: NavigationItem,
24
+ tags: ["autodocs"],
25
+ parameters: {
26
+ layout: "centered",
27
+ },
28
+ globals: {
29
+ backgrounds: { value: "dark" },
30
+ },
31
+ argTypes: {
32
+ href: { control: "text" },
33
+ subLinks: { control: false },
34
+ },
35
+ decorators: [
36
+ (Story) => (
37
+ <NavigationProvider>
38
+ <Story />
39
+ </NavigationProvider>
40
+ ),
41
+ ],
42
+ } satisfies Meta<typeof NavigationItem>;
43
+ export default meta;
44
+
45
+ type Story = StoryObj<typeof meta>;
46
+
47
+ export const Link: Story = {
48
+ args: {
49
+ label: "Libellé",
50
+ href: "/some-url",
51
+ iconLeft: "home",
52
+ },
53
+ play: async ({ canvasElement, args }) => {
54
+ const canvas = within(canvasElement);
55
+ await canvas.findByText(args.label as string);
56
+ await expect(
57
+ canvas.getByText(args.label as string).parentElement,
58
+ ).toHaveAttribute("href", args.href);
59
+ },
60
+ };
61
+
62
+ export const ActiveLink: Story = {
63
+ args: {
64
+ ...Link.args,
65
+ isActive: true,
66
+ },
67
+ play: async ({ canvasElement, args }) => {
68
+ const canvas = within(canvasElement);
69
+ await expect(
70
+ canvas.getByText(args.label as string).parentElement,
71
+ ).toHaveAttribute("data-active", "true");
72
+ },
73
+ };
74
+
75
+ export const Dropdown: Story = {
76
+ args: {
77
+ ...Link.args,
78
+ href: undefined,
79
+ subLinks: [
80
+ {
81
+ label: "Sous-libellé 1",
82
+ href: "/some-url-1",
83
+ },
84
+ {
85
+ label: "Sous-libellé 2",
86
+ href: "/some-url-2",
87
+ },
88
+ ],
89
+ },
90
+ play: async ({ canvasElement, args }) => {
91
+ const canvas = within(canvasElement);
92
+ await canvas.findByText(args.label as string);
93
+ await expect(
94
+ canvas.getByText(args.label as string).parentElement,
95
+ ).not.toHaveAttribute("href");
96
+ await expectNotPresent(() => canvas.queryByText("Sous-libellé 1"));
97
+ await expectNotPresent(() => canvas.queryByText("Sous-libellé 2"));
98
+ },
99
+ };
100
+
101
+ export const DropdownOpenByDefault: Story = {
102
+ args: {
103
+ ...Dropdown.args,
104
+ defaultOpen: true,
105
+ },
106
+ play: async ({ canvasElement, args }) => {
107
+ const canvas = within(canvasElement);
108
+ await canvas.findByText(args.label as string);
109
+ await expect(
110
+ canvas.getByText(args.label as string).parentElement,
111
+ ).not.toHaveAttribute("href");
112
+ await expect(
113
+ canvas.getByText("Sous-libellé 1").parentElement,
114
+ ).toHaveAttribute("href", "/some-url-1");
115
+ await expect(
116
+ canvas.getByText("Sous-libellé 2").parentElement,
117
+ ).toHaveAttribute("href", "/some-url-2");
118
+ },
119
+ };
120
+
121
+ export const LinkCollapsed: Story = {
122
+ decorators: [
123
+ (Story) => (
124
+ <NavigationProvider initialIsCollapsed={true}>
125
+ <Story />
126
+ </NavigationProvider>
127
+ ),
128
+ ],
129
+ args: {
130
+ ...Link.args,
131
+ },
132
+ };
133
+
134
+ export const LinkCollapsedActive: Story = {
135
+ decorators: [
136
+ (Story) => (
137
+ <NavigationProvider initialIsCollapsed={true}>
138
+ <Story />
139
+ </NavigationProvider>
140
+ ),
141
+ ],
142
+ args: {
143
+ ...Link.args,
144
+ isActive: true,
145
+ },
146
+ };
147
+
148
+ export const WithReactNodeLabel: Story = {
149
+ args: {
150
+ ...Link.args,
151
+ "aria-label": "Indisponibilités",
152
+ label: (
153
+ <div>
154
+ <div>Tableau de bord</div>
155
+ <div style={{ fontSize: "10px", lineHeight: "12px" }}>
156
+ Indisponibilités
157
+ </div>
158
+ </div>
159
+ ),
160
+ },
161
+ play: async ({ canvasElement }) => {
162
+ const canvas = within(canvasElement);
163
+ await canvas.findByText("Indisponibilités");
164
+ await canvas.findByText("Tableau de bord");
165
+ },
166
+ };
167
+ ```
@@ -0,0 +1,206 @@
1
+ # NotificationCard
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/NotificationCard/NotificationCard.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, expect } from "storybook/test";
18
+ import NotificationCard from "./NotificationCard";
19
+ import Button from "../Button/Button";
20
+ import { expectNotPresent } from "@internal/test-utils-storybook/test-utils-storybook";
21
+
22
+ const meta: Meta<typeof NotificationCard> = {
23
+ component: NotificationCard,
24
+ tags: ["autodocs"],
25
+ parameters: {
26
+ layout: "centered",
27
+ },
28
+ decorators: [
29
+ (Story) => (
30
+ <div style={{ width: "400px" }}>
31
+ <Story />
32
+ </div>
33
+ ),
34
+ ],
35
+ };
36
+ export default meta;
37
+
38
+ type Story = StoryObj<typeof meta>;
39
+
40
+ export const Playground: Story = {
41
+ args: {
42
+ title: "[Insert title]",
43
+ description: "[Insert description]",
44
+ dateAndTime: "[Day & Hour] • [Timer]",
45
+ isRead: false,
46
+ type: "default",
47
+ menuActions: [
48
+ { text: "Action 1", onClick: fn() },
49
+ { text: "Action 2", onClick: fn() },
50
+ ],
51
+ children: (
52
+ <>
53
+ <Button
54
+ text="[Insert name]"
55
+ mode="secondary"
56
+ size="small"
57
+ onClick={fn()}
58
+ />
59
+ <Button
60
+ text="[Insert name]"
61
+ mode="secondary"
62
+ size="small"
63
+ onClick={fn()}
64
+ />
65
+ </>
66
+ ),
67
+ },
68
+ play: async ({ canvasElement }) => {
69
+ const canvas = within(canvasElement);
70
+ await canvas.findByText("[Insert title]");
71
+ await canvas.findByText("[Insert description]");
72
+ await canvas.findByText("[Day & Hour] • [Timer]");
73
+ await expect(await canvas.findAllByText("[Insert name]")).toHaveLength(2);
74
+ await canvas.findByTestId("unread-badge");
75
+ },
76
+ };
77
+
78
+ export const ReadNotification: Story = {
79
+ args: {
80
+ ...Playground.args,
81
+ isRead: true,
82
+ },
83
+ play: async ({ canvasElement }) => {
84
+ const canvas = within(canvasElement);
85
+ await expectNotPresent(() => canvas.queryByTestId("unread-badge"));
86
+ },
87
+ };
88
+
89
+ export const WithAvatar: Story = {
90
+ args: {
91
+ ...Playground.args,
92
+ avatarUrl: "https://i.pravatar.cc/300?img=5",
93
+ avatarAlt: "John Doe",
94
+ title: "Jesse Pokman",
95
+ description: "You have a new message",
96
+ dateAndTime: "15 min ago",
97
+ children: (
98
+ <Button text="View" mode="secondary" size="small" onClick={fn()} />
99
+ ),
100
+ menuActions: [
101
+ { text: "Mark as read", onClick: fn() },
102
+ { text: "Delete", onClick: fn() },
103
+ ],
104
+ },
105
+ };
106
+
107
+ export const Minimal: Story = {
108
+ args: {
109
+ title: "Minimal example",
110
+ isRead: true,
111
+ menuActions: [],
112
+ },
113
+ };
114
+
115
+ export const InformativeUnread: Story = {
116
+ args: {
117
+ ...Playground.args,
118
+ isRead: false,
119
+ type: "informative",
120
+ },
121
+ };
122
+
123
+ export const InformativeRead: Story = {
124
+ args: {
125
+ ...Playground.args,
126
+ isRead: true,
127
+ type: "informative",
128
+ },
129
+ };
130
+
131
+ export const NegativeUnread: Story = {
132
+ args: {
133
+ ...Playground.args,
134
+ isRead: false,
135
+ type: "negative",
136
+ },
137
+ };
138
+
139
+ export const NegativeRead: Story = {
140
+ args: {
141
+ ...Playground.args,
142
+ isRead: true,
143
+ type: "negative",
144
+ },
145
+ };
146
+
147
+ export const WarningUnread: Story = {
148
+ args: {
149
+ ...Playground.args,
150
+ isRead: false,
151
+ type: "warning",
152
+ },
153
+ };
154
+
155
+ export const WarningRead: Story = {
156
+ args: {
157
+ ...Playground.args,
158
+ isRead: true,
159
+ type: "warning",
160
+ },
161
+ };
162
+
163
+ export const PositiveUnread: Story = {
164
+ args: {
165
+ ...Playground.args,
166
+ isRead: false,
167
+ type: "positive",
168
+ },
169
+ };
170
+
171
+ export const PositiveRead: Story = {
172
+ args: {
173
+ ...Playground.args,
174
+ isRead: true,
175
+ type: "positive",
176
+ },
177
+ };
178
+
179
+ export const WithCustomIcon: Story = {
180
+ args: {
181
+ ...Playground.args,
182
+ customIcon: "bolt",
183
+ },
184
+ };
185
+
186
+ export const WithCloseButton: Story = {
187
+ args: {
188
+ ...Playground.args,
189
+ menuActions: undefined,
190
+ isRead: true,
191
+ onClose: fn(),
192
+ },
193
+ };
194
+
195
+ export const WithLongContent: Story = {
196
+ args: {
197
+ ...Playground.args,
198
+ title:
199
+ "This is a very long title that should overflow. This is a very long title that should overflow",
200
+ description:
201
+ "This is a very long description that should overflow. This is a very long description that should overflow",
202
+ dateAndTime:
203
+ "This is a very long date and time that should overflow. This is a very long date and time that should overflow",
204
+ },
205
+ };
206
+ ```
@@ -0,0 +1,240 @@
1
+ # Notifications
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/Notifications/Notifications.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, userEvent, within } from "storybook/test";
18
+ import Notifications from "./Notifications";
19
+ import NotificationCard from "../NotificationCard/NotificationCard";
20
+ import { expectNotPresent } from "@internal/test-utils-storybook/test-utils-storybook";
21
+ import { I18nProvider } from "react-aria-components";
22
+ import Toaster from "../Toaster/Toaster";
23
+ import { toast } from "sonner";
24
+ import { useEffect } from "react";
25
+
26
+ const meta: Meta<typeof Notifications> = {
27
+ component: Notifications,
28
+ argTypes: {
29
+ children: { control: false },
30
+ },
31
+ decorators: [
32
+ (Story) => (
33
+ <I18nProvider locale="en">
34
+ <Toaster />
35
+ <Story />
36
+ </I18nProvider>
37
+ ),
38
+ ],
39
+ };
40
+ export default meta;
41
+
42
+ type Story = StoryObj<typeof meta>;
43
+
44
+ export const Playground: Story = {
45
+ args: {
46
+ triggerProps: {
47
+ iconLeft: "notifications_none",
48
+ size: "small",
49
+ mode: "tertiary",
50
+ },
51
+ nbOfReadNotifications: 1,
52
+ nbOfUnreadNotifications: 2,
53
+ onParametersClick: fn(),
54
+ onMarkAllAsReadClick: fn(),
55
+ children: (
56
+ <>
57
+ <NotificationCard
58
+ title="Notification 1"
59
+ isRead={false}
60
+ description="Description 1"
61
+ dateAndTime="16/07/2025 • 10:00"
62
+ />
63
+ <NotificationCard
64
+ title="Notification 2"
65
+ isRead={true}
66
+ description="Description 2"
67
+ dateAndTime="18/07/2025 • 11:00"
68
+ />
69
+ <NotificationCard
70
+ title="Notification 3"
71
+ isRead={false}
72
+ description="Description 3"
73
+ dateAndTime="21/07/2025 • 13:00"
74
+ />
75
+ </>
76
+ ),
77
+ },
78
+ play: async ({ canvasElement }) => {
79
+ const canvas = within(canvasElement.ownerDocument.body);
80
+ const user = userEvent.setup({ delay: 25 });
81
+ await user.click(canvas.getByRole("button"));
82
+ await canvas.findByText("Notifications");
83
+ await canvas.findByText("Mark all as read");
84
+ await canvas.findByLabelText("Parameters");
85
+ await canvas.findByLabelText("Close");
86
+ await canvas.findByText("Notification 1");
87
+ await canvas.findByText("Notification 2");
88
+ await canvas.findByText("Notification 3");
89
+ },
90
+ };
91
+
92
+ export const LotsOfItems: Story = {
93
+ args: {
94
+ ...Playground.args,
95
+ nbOfReadNotifications: 20,
96
+ nbOfUnreadNotifications: 20,
97
+ children: (
98
+ <>
99
+ {Array.from({ length: 20 }).map((_, index) => (
100
+ <NotificationCard
101
+ key={index}
102
+ title={`Notification ${index + 1}`}
103
+ isRead={false}
104
+ description={`Description ${index + 1}`}
105
+ dateAndTime={`${index + 1}/07/2025 • ${index + 1}:00`}
106
+ menuActions={[{ text: "Mark as read", onClick: fn() }]}
107
+ />
108
+ ))}
109
+ </>
110
+ ),
111
+ },
112
+ play: async ({ canvasElement }) => {
113
+ const canvas = within(canvasElement.ownerDocument.body);
114
+ const user = userEvent.setup({ delay: 25 });
115
+ await user.click(canvas.getByRole("button"));
116
+ },
117
+ };
118
+
119
+ export const WithoutFooter: Story = {
120
+ args: {
121
+ ...Playground.args,
122
+ onParametersClick: undefined,
123
+ onMarkAllAsReadClick: undefined,
124
+ },
125
+ play: async ({ canvasElement }) => {
126
+ const canvas = within(canvasElement.ownerDocument.body);
127
+ const user = userEvent.setup({ delay: 25 });
128
+ await user.click(canvas.getByRole("button"));
129
+ await canvas.findByText("Notifications");
130
+ await canvas.findByText("Notification 1");
131
+ await canvas.findByText("Notification 2");
132
+ await canvas.findByText("Notification 3");
133
+ await canvas.findByLabelText("Close");
134
+ await expectNotPresent(() => canvas.queryByText("Mark all as read"));
135
+ await expectNotPresent(() => canvas.queryByLabelText("Parameters"));
136
+ },
137
+ };
138
+
139
+ export const Loading: Story = {
140
+ args: {
141
+ ...Playground.args,
142
+ children: undefined,
143
+ isLoading: true,
144
+ nbOfReadNotifications: 0,
145
+ nbOfUnreadNotifications: 0,
146
+ },
147
+ play: async ({ canvasElement }) => {
148
+ const canvas = within(canvasElement.ownerDocument.body);
149
+ const user = userEvent.setup({ delay: 25 });
150
+ await user.click(canvas.getByRole("button"));
151
+ await canvas.findByText("Notifications");
152
+ await canvas.findByLabelText("Close");
153
+ await expectNotPresent(() => canvas.queryByText("Mark all as read"));
154
+ },
155
+ };
156
+
157
+ export const Empty: Story = {
158
+ args: {
159
+ ...Playground.args,
160
+ children: undefined,
161
+ nbOfReadNotifications: 0,
162
+ nbOfUnreadNotifications: 0,
163
+ },
164
+ play: async ({ canvasElement }) => {
165
+ const canvas = within(canvasElement.ownerDocument.body);
166
+ const user = userEvent.setup({ delay: 25 });
167
+ await user.click(canvas.getByRole("button"));
168
+ },
169
+ };
170
+
171
+ export const StandaloneNotification: Story = {
172
+ render: () => {
173
+ const ParentComponent = () => {
174
+ useEffect(() => {
175
+ toast.custom(
176
+ (toastId) => (
177
+ <NotificationCard
178
+ title="Success message"
179
+ isRead={false}
180
+ description="Description"
181
+ dateAndTime="16/07/2025 • 10:00"
182
+ onClose={() => toast.dismiss(toastId)}
183
+ style={{ width: 400 }}
184
+ />
185
+ ),
186
+ {
187
+ unstyled: true,
188
+ position: "top-right",
189
+ closeButton: false,
190
+ },
191
+ );
192
+ }, []);
193
+ return <div />;
194
+ };
195
+ return <ParentComponent />;
196
+ },
197
+ };
198
+ ```
199
+
200
+ ## Developer notes
201
+
202
+ 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.
203
+
204
+ ```mdx
205
+ import { Meta, Controls, Source, Canvas } from "@storybook/addon-docs/blocks";
206
+
207
+ import * as Notifications from "./Notifications.stories";
208
+ import * as NotificationsTests from "./tests/NotificationsTests.stories";
209
+
210
+ <Meta of={Notifications} />
211
+
212
+ # Notifications
213
+
214
+ <Canvas of={Notifications.Playground} />
215
+ <Controls of={Notifications.Playground} />
216
+
217
+ ## Full featured example usage
218
+
219
+ Bellow you can find a full featured example usage of the Notifications component.
220
+
221
+ - integration within a Header component
222
+ - integration with useQuery and useMutation (from Tanstack Query) to handle fetching/updating the notifications
223
+ - generated using composition, to allow you advanced customization (see performance section below)
224
+
225
+ The dataset is abstracted away, please refer to the [NotificationCard component](?path=/docs/components-notificationcard--docs) to see all available props.
226
+
227
+ <Source of={NotificationsTests.FullImplementationExample} type="code" dark />
228
+
229
+ ## Performance and customization
230
+
231
+ If you intend to receive a lot of notifications, you might want to consider using advanced features like pagination, infinite scroll, virtualized list, etc.
232
+
233
+ With the actual implementation, you will notice that the component is not optimized for performance.
234
+ This is intended, Notifications and NotificationCard are only responsible for rendering the UI.
235
+ But the composition allows you to easily add these features.
236
+
237
+ For example, you can easily add a wrapper around the NotificationCards to add infinite scroll, pagination, or even virtualized list.
238
+
239
+ Feel free to contact us if you need help with this.
240
+ ```