@agregio-solutions/design-system 2.2.0 → 2.3.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.
@@ -41,6 +41,15 @@ type Props<TFeatures extends TableFeatures, TData extends RowData> = {
41
41
  * @default "default".
42
42
  */
43
43
  size?: "small" | "default" | "large";
44
+ /**
45
+ * Whether the table is loading its data for the first time.
46
+ * Renders skeleton rows under the real header, as many as the page size
47
+ * (`pagination.pageSize`, else `table.state.pagination.pageSize`, else 10).
48
+ * Map it to react-query `isLoading` (first load, no data yet), not `isFetching`:
49
+ * on refetch, keep the current rows displayed (e.g. with `placeholderData: keepPreviousData`).
50
+ * @default false
51
+ */
52
+ isLoading?: boolean;
44
53
  };
45
- export default function DataTable<TFeatures extends TableFeatures, TData extends RowData>({ table, pagination, renderSubComponent, itemsPerPageOptions, size, ...divProps }: Props<TFeatures, TData>): React.JSX.Element;
54
+ export default function DataTable<TFeatures extends TableFeatures, TData extends RowData>({ table, pagination, renderSubComponent, itemsPerPageOptions, size, isLoading, ...divProps }: Props<TFeatures, TData>): React.JSX.Element;
46
55
  export {};
@@ -8,6 +8,10 @@ export type Props = {
8
8
  * The inline style for the element.
9
9
  */
10
10
  style?: React.CSSProperties;
11
+ /**
12
+ * Whether the table content is loading.
13
+ */
14
+ "aria-busy"?: boolean;
11
15
  children: React.ReactNode;
12
16
  };
13
17
  export default function DataTableRoot({ children, ...otherProps }: Props): React.JSX.Element;
@@ -0,0 +1,11 @@
1
+ export type Props = {
2
+ /**
3
+ * The number of skeleton rows to render.
4
+ */
5
+ rowCount: number;
6
+ /**
7
+ * The number of skeleton cells to render in each row.
8
+ */
9
+ columnCount: number;
10
+ };
11
+ export default function DataTableSkeletonRows({ rowCount, columnCount, }: Props): import('react').JSX.Element;
@@ -52,7 +52,7 @@ import Select from "../Select/Select";
52
52
  import SelectItem from "../SelectItem/SelectItem";
53
53
  import Button from "../Button/Button";
54
54
  import Switch from "../Switch/Switch";
55
- import { within } from "storybook/test";
55
+ import { expect, within } from "storybook/test";
56
56
  import { someTime } from "@internal/test-utils-storybook/test-utils-storybook";
57
57
  import DataTableRoot from "./DataTableRoot/DataTableRoot";
58
58
  import DataTableRow from "./DataTableRow/DataTableRow";
@@ -161,9 +161,13 @@ export const WithReactTableFrontEndManaged: StoryObj<typeof DataTable> = {
161
161
  [],
162
162
  );
163
163
 
164
- const data: Array<User> = useMemo(
165
- () =>
166
- arrayOfLength(300).map((index) => ({
164
+ // Fetch the whole dataset once: filtering, sorting and pagination happen client-side
165
+ // `isLoading` is only true on the first load, while there is no data yet
166
+ const { data, isLoading } = useQuery({
167
+ queryKey: ["all-users"],
168
+ queryFn: async (): Promise<Array<User>> => {
169
+ await someTime(100); // Your API call goes here
170
+ return arrayOfLength(300).map((index) => ({
167
171
  firstname: `Firstname ${index + 1}`,
168
172
  lastname: `Lastname ${index + 1}`,
169
173
  email: `john-doe${index + 1}@example.com`,
@@ -172,9 +176,13 @@ export const WithReactTableFrontEndManaged: StoryObj<typeof DataTable> = {
172
176
  day: DAYS[index % DAYS.length],
173
177
  organization: ORGANIZATIONS[index % ORGANIZATIONS.length],
174
178
  isActive: index % 2 === 0,
175
- })),
176
- [DAYS, FOODS, ORGANIZATIONS],
177
- );
179
+ }));
180
+ },
181
+ });
182
+
183
+ // The table data needs a stable reference: an inline `data ?? []` would create a new array on every render while loading
184
+ // ⚠ This should be memoized!
185
+ const users = useMemo(() => data ?? [], [data]);
178
186
 
179
187
  // Here starts the interesting stuff: columns definitions.
180
188
  // All the features of the filters comes from those columns definitions
@@ -347,7 +355,7 @@ export const WithReactTableFrontEndManaged: StoryObj<typeof DataTable> = {
347
355
  // And here we define the table as usual
348
356
  const table = useTable({
349
357
  features: clientFeatures,
350
- data,
358
+ data: users,
351
359
  columns,
352
360
  initialState: {
353
361
  globalFilter: "", // Add this to show the global filter (aka the Search bar)
@@ -400,6 +408,7 @@ export const WithReactTableFrontEndManaged: StoryObj<typeof DataTable> = {
400
408
  table={table}
401
409
  pagination={pagination}
402
410
  itemsPerPageOptions={[12, 24, 36, 48, 60]} // Add this to customize the page size options available in the select
411
+ isLoading={isLoading}
403
412
  />
404
413
  </>
405
414
  );
@@ -407,6 +416,10 @@ export const WithReactTableFrontEndManaged: StoryObj<typeof DataTable> = {
407
416
 
408
417
  return <ParentComponent />;
409
418
  },
419
+ play: async ({ canvasElement }) => {
420
+ const canvas = within(canvasElement);
421
+ await canvas.findByText("Firstname 1");
422
+ },
410
423
  };
411
424
 
412
425
  type User = {
@@ -485,7 +498,8 @@ export const WithReactTableBackEndManaged: StoryObj<typeof DataTable> = {
485
498
  );
486
499
 
487
500
  // Fetch data using the table state (filters, pagination, sorting, etc.)
488
- const { data } = useQuery({
501
+ // `isLoading` is only true on the first load: on refetch, `keepPreviousData` keeps the current rows displayed
502
+ const { data, isLoading } = useQuery({
489
503
  placeholderData: keepPreviousData, // from @tanstack/react-query, recommended to use it in this scenario
490
504
  queryKey: ["users", pagination, sorting, columnFilters],
491
505
  queryFn: () =>
@@ -499,6 +513,10 @@ export const WithReactTableBackEndManaged: StoryObj<typeof DataTable> = {
499
513
  }),
500
514
  });
501
515
 
516
+ // The table data needs a stable reference: an inline `data?.data ?? []` would create a new array on every render while loading
517
+ // ⚠ This should be memoized!
518
+ const users = useMemo(() => data?.data ?? [], [data]);
519
+
502
520
  // You will need to create a column helper, typed with the registered features
503
521
  // ⚠ This should be memoized!
504
522
  const columnHelper = useMemo(
@@ -595,7 +613,7 @@ export const WithReactTableBackEndManaged: StoryObj<typeof DataTable> = {
595
613
  // Instantiate the table with @tanstack/react-table
596
614
  const table = useTable({
597
615
  features: serverFeatures,
598
- data: data?.data || [],
616
+ data: users,
599
617
  columns,
600
618
  manualPagination: true,
601
619
  manualSorting: true,
@@ -615,7 +633,11 @@ export const WithReactTableBackEndManaged: StoryObj<typeof DataTable> = {
615
633
  return (
616
634
  <>
617
635
  <Filter table={table} />
618
- <DataTable table={table} pagination={pagination} />
636
+ <DataTable
637
+ table={table}
638
+ pagination={pagination}
639
+ isLoading={isLoading}
640
+ />
619
641
  </>
620
642
  );
621
643
  }
@@ -677,6 +699,85 @@ export const Empty: StoryObj<typeof DataTable> = {
677
699
  },
678
700
  };
679
701
 
702
+ export const Loading: StoryObj<typeof DataTable> = {
703
+ render: () => {
704
+ const ParentComponent = () => {
705
+ const columnHelper = useMemo(
706
+ () => createColumnHelper<typeof clientFeatures, User>(),
707
+ [],
708
+ );
709
+
710
+ const data = useMemo(() => [], []);
711
+
712
+ const columns = useMemo(
713
+ () =>
714
+ columnHelper.columns([
715
+ columnHelper.accessor("firstname", {
716
+ header: ({ header }) => (
717
+ <DataTableHeader
718
+ onSortClick={header.column.getToggleSortingHandler()}
719
+ sortDirection={header.column.getIsSorted() || "none"}
720
+ text="Firstname"
721
+ />
722
+ ),
723
+ cell: (props) => (
724
+ <DataTableCell>{props.getValue()}</DataTableCell>
725
+ ),
726
+ }),
727
+ columnHelper.accessor("lastname", {
728
+ header: () => <DataTableHeader text="Lastname" />,
729
+ cell: (props) => (
730
+ <DataTableCell>{props.getValue()}</DataTableCell>
731
+ ),
732
+ }),
733
+ columnHelper.accessor("email", {
734
+ header: () => <DataTableHeader text="Email" />,
735
+ cell: (props) => (
736
+ <DataTableCell>{props.getValue()}</DataTableCell>
737
+ ),
738
+ }),
739
+ ]),
740
+ [columnHelper],
741
+ );
742
+
743
+ const table = useTable({
744
+ features: clientFeatures,
745
+ data,
746
+ columns,
747
+ initialState: {
748
+ pagination: { pageIndex: 0, pageSize: 10 },
749
+ },
750
+ });
751
+
752
+ const pagination = table.state.pagination;
753
+
754
+ return <DataTable table={table} pagination={pagination} isLoading />;
755
+ };
756
+
757
+ return <ParentComponent />;
758
+ },
759
+ play: async ({ canvasElement }) => {
760
+ const canvas = within(canvasElement);
761
+
762
+ await expect(canvas.getByRole("table")).toHaveAttribute(
763
+ "aria-busy",
764
+ "true",
765
+ );
766
+ await expect(canvas.getAllByRole("columnheader")).toHaveLength(3);
767
+ // 1 skeleton row per item of the page (pageSize: 10)
768
+ await expect(
769
+ canvasElement.querySelectorAll(
770
+ '[data-design-system-component="DataTableSkeletonRows"]',
771
+ ),
772
+ ).toHaveLength(10);
773
+ // Skeleton rows are hidden from assistive technologies: only the header row is exposed
774
+ await expect(canvas.getAllByRole("row")).toHaveLength(1);
775
+ await expect(
776
+ canvas.queryByText("Sorry, there is currently no data to display."),
777
+ ).not.toBeInTheDocument();
778
+ },
779
+ };
780
+
680
781
  export const WithSubColumns: StoryObj<typeof DataTable> = {
681
782
  render: () => {
682
783
  const data: Array<User> = arrayOfLength(300).map((index) => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agregio-solutions/design-system",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "React Component library and Storybook that is part of the Design System for Agregio Solutions",
5
5
  "type": "module",
6
6
  "module": "dist/design-system.js",