@indico-data/design-system 2.49.0 → 2.51.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/lib/components/tanstackTable/TankstackTable.types.d.ts +40 -0
- package/lib/components/tanstackTable/TanstackTable.stories.d.ts +15 -0
- package/lib/components/tanstackTable/TanstakTable.d.ts +4 -0
- package/lib/components/tanstackTable/__tests__/TanstackTable.test.d.ts +1 -0
- package/lib/components/tanstackTable/__tests__/__mocks__/test-mock-data.d.ts +8 -0
- package/lib/components/tanstackTable/components/ActionBar/ActionBar.d.ts +17 -0
- package/lib/components/tanstackTable/components/ActionBar/ActionBar.stories.d.ts +10 -0
- package/lib/components/tanstackTable/components/ActionBar/__tests__/ActionBar.test.d.ts +1 -0
- package/lib/components/tanstackTable/components/ActionBar/index.d.ts +1 -0
- package/lib/components/tanstackTable/components/NoResults/NoResults.d.ts +7 -0
- package/lib/components/tanstackTable/components/NoResults/__tests__/NoResult.test.d.ts +1 -0
- package/lib/components/tanstackTable/components/NoResults/index.d.ts +1 -0
- package/lib/components/tanstackTable/components/TableBody/TableBody.d.ts +12 -0
- package/lib/components/tanstackTable/components/TableBody/index.d.ts +1 -0
- package/lib/components/tanstackTable/components/TableHeader/TableHeader.d.ts +6 -0
- package/lib/components/tanstackTable/components/TableHeader/index.d.ts +1 -0
- package/lib/components/tanstackTable/components/TablePagination/TablePagination.d.ts +9 -0
- package/lib/components/tanstackTable/components/TablePagination/__tests__/TablePagination.test.d.ts +1 -0
- package/lib/components/tanstackTable/components/TablePagination/index.d.ts +1 -0
- package/lib/components/tanstackTable/docs/pinnedColumns/PinnedColumn.stories.d.ts +7 -0
- package/lib/components/tanstackTable/docs/withRowClick/WithRowClick.stories.d.ts +7 -0
- package/lib/components/tanstackTable/helpers.d.ts +830 -0
- package/lib/components/tanstackTable/index.d.ts +2 -0
- package/lib/components/tanstackTable/mock-data/mock-data.d.ts +14 -0
- package/lib/components/tanstackTable/mock-data/table-configuration.d.ts +3 -0
- package/lib/components/tanstackTable/useTanstackTable.d.ts +16 -0
- package/lib/index.css +407 -80
- package/lib/index.d.ts +62 -17
- package/lib/index.esm.css +407 -80
- package/lib/index.esm.js +20455 -56
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +20455 -55
- package/lib/index.js.map +1 -1
- package/lib/stylesAndAnimations/utilityClasses/UtilityClassesData.d.ts +7 -0
- package/lib/stylesAndAnimations/utilityClasses/UtilityClassesTable.d.ts +1 -0
- package/lib/stylesAndAnimations/utilityClasses/UtilityClassesTable.stories.d.ts +6 -0
- package/package.json +2 -1
- package/src/components/tanstackTable/TankstackTable.types.ts +39 -0
- package/src/components/tanstackTable/TanstackTable.mdx +122 -0
- package/src/components/tanstackTable/TanstackTable.stories.tsx +260 -0
- package/src/components/tanstackTable/TanstakTable.tsx +157 -0
- package/src/components/tanstackTable/__tests__/TanstackTable.test.tsx +87 -0
- package/src/components/tanstackTable/__tests__/__mocks__/test-mock-data.tsx +83 -0
- package/src/components/tanstackTable/components/ActionBar/ActionBar.mdx +10 -0
- package/src/components/tanstackTable/components/ActionBar/ActionBar.scss +30 -0
- package/src/components/tanstackTable/components/ActionBar/ActionBar.stories.tsx +98 -0
- package/src/components/tanstackTable/components/ActionBar/ActionBar.tsx +51 -0
- package/src/components/tanstackTable/components/ActionBar/__tests__/ActionBar.test.tsx +87 -0
- package/src/components/tanstackTable/components/ActionBar/index.ts +1 -0
- package/src/components/tanstackTable/components/NoResults/NoResults.scss +24 -0
- package/src/components/tanstackTable/components/NoResults/NoResults.tsx +22 -0
- package/src/components/tanstackTable/components/NoResults/__tests__/NoResult.test.tsx +25 -0
- package/src/components/tanstackTable/components/NoResults/index.ts +1 -0
- package/src/components/tanstackTable/components/TableBody/TableBody.tsx +87 -0
- package/src/components/tanstackTable/components/TableBody/index.ts +1 -0
- package/src/components/tanstackTable/components/TableHeader/TableHeader.tsx +49 -0
- package/src/components/tanstackTable/components/TableHeader/index.ts +1 -0
- package/src/components/tanstackTable/components/TablePagination/TablePagination.tsx +45 -0
- package/src/components/tanstackTable/components/TablePagination/__tests__/TablePagination.test.tsx +18 -0
- package/src/components/tanstackTable/components/TablePagination/index.ts +1 -0
- package/src/components/tanstackTable/docs/pinnedColumns/PinnedColumn.mdx +34 -0
- package/src/components/tanstackTable/docs/pinnedColumns/PinnedColumn.stories.tsx +40 -0
- package/src/components/tanstackTable/docs/withRowClick/WithRowClick.mdx +48 -0
- package/src/components/tanstackTable/docs/withRowClick/WithRowClick.stories.tsx +32 -0
- package/src/components/tanstackTable/helpers.ts +45 -0
- package/src/components/tanstackTable/index.ts +2 -0
- package/src/components/tanstackTable/mock-data/mock-data.ts +256 -0
- package/src/components/tanstackTable/mock-data/table-configuration.tsx +219 -0
- package/src/components/tanstackTable/styles/_variables.scss +35 -0
- package/src/components/tanstackTable/styles/table.scss +204 -0
- package/src/components/tanstackTable/styles/test.scss +19 -0
- package/src/components/tanstackTable/tanstack-table.d.ts +18 -0
- package/src/components/tanstackTable/useTanstackTable.tsx +42 -0
- package/src/index.ts +1 -0
- package/src/legacy/components/loading-indicators/CirclePulse/CirclePulse.tsx +1 -0
- package/src/storybookDocs/Permafrost.mdx +22 -11
- package/src/styles/_borders.scss +2 -1
- package/src/styles/index.scss +1 -0
- package/src/stylesAndAnimations/borders/BorderColor.tsx +14 -6
- package/src/stylesAndAnimations/utilityClasses/UtilityClasses.mdx +24 -0
- package/src/stylesAndAnimations/utilityClasses/UtilityClassesData.ts +230 -0
- package/src/stylesAndAnimations/utilityClasses/UtilityClassesTable.stories.tsx +13 -0
- package/src/stylesAndAnimations/utilityClasses/UtilityClassesTable.tsx +146 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const UtilityClassesTable: () => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { UtilityClassesTable } from './UtilityClassesTable';
|
|
3
|
+
declare const meta: Meta<typeof UtilityClassesTable>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof UtilityClassesTable>;
|
|
6
|
+
export declare const Default: Story;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indico-data/design-system",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.51.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"author": "",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"@react-types/button": "^3.9.3",
|
|
40
40
|
"@react-types/checkbox": "^3.8.1",
|
|
41
41
|
"@react-types/radio": "^3.8.0",
|
|
42
|
+
"@tanstack/react-table": "^8.21.2",
|
|
42
43
|
"@testing-library/react": "^16.0.0",
|
|
43
44
|
"classnames": "^2.5.1",
|
|
44
45
|
"date-fns": "^3.6.0",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Row, ColumnDef } from '@tanstack/react-table';
|
|
2
|
+
|
|
3
|
+
export type WithPaginationProps = {
|
|
4
|
+
rowsPerPage: number;
|
|
5
|
+
rowCount: number;
|
|
6
|
+
onChangePage: (page: number, perPage: number) => void;
|
|
7
|
+
currentPage: number;
|
|
8
|
+
totalEntriesText?: string;
|
|
9
|
+
showPagination: true;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type WithoutPaginationProps = {
|
|
13
|
+
rowsPerPage?: number;
|
|
14
|
+
rowCount?: number;
|
|
15
|
+
onChangePage?: (page: number, perPage: number) => void;
|
|
16
|
+
currentPage?: number;
|
|
17
|
+
totalEntriesText?: string;
|
|
18
|
+
showPagination?: false;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type PaginationProps = WithPaginationProps | WithoutPaginationProps;
|
|
22
|
+
|
|
23
|
+
export type Props<T extends object> = {
|
|
24
|
+
data: T[];
|
|
25
|
+
columns: ColumnDef<T & { id: string }>[];
|
|
26
|
+
className?: string;
|
|
27
|
+
actionBarClassName?: string;
|
|
28
|
+
TableActions?: React.ComponentType<{ selectedItems: Row<any>[]; unselectRows: () => void }>;
|
|
29
|
+
error?: {
|
|
30
|
+
hasError: boolean;
|
|
31
|
+
errorMessage?: string;
|
|
32
|
+
};
|
|
33
|
+
enableRowSelection?: boolean | ((row: Row<T>) => boolean);
|
|
34
|
+
clearFilters?: () => void;
|
|
35
|
+
hasFilters?: boolean;
|
|
36
|
+
isLoading?: boolean;
|
|
37
|
+
defaultPinnedColumns?: string[];
|
|
38
|
+
onClickRow?: ((row: Row<T>) => void) | null;
|
|
39
|
+
} & PaginationProps;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Canvas, Meta, Controls } from '@storybook/blocks';
|
|
2
|
+
import * as TableStories from './TanstackTable.stories';
|
|
3
|
+
|
|
4
|
+
<Meta title="Layout/Tanstack Table" name="TanstackTable" />
|
|
5
|
+
|
|
6
|
+
# Table
|
|
7
|
+
|
|
8
|
+
This component facilitates the rapid creation of responsive and paginated tables. It was built using the React TanStack Table library as a foundation. For more details on additional configuration options and features that are not covered in this documentation, please refer to the official TanStack Table documentation at this [link](https://tanstack.com/table/latest/docs/introduction).
|
|
9
|
+
|
|
10
|
+
<Canvas of={TableStories.Default} />
|
|
11
|
+
|
|
12
|
+
<Controls of={TableStories.Default} />
|
|
13
|
+
|
|
14
|
+
# Data Structure
|
|
15
|
+
These are some examples of how we would set up data for our table.
|
|
16
|
+
|
|
17
|
+
## Data
|
|
18
|
+
You can define your data for your columns the following way.
|
|
19
|
+
```tsx
|
|
20
|
+
const data = [
|
|
21
|
+
{
|
|
22
|
+
firstName: 'John',
|
|
23
|
+
lastName: 'Doe',
|
|
24
|
+
age: 28,
|
|
25
|
+
visits: 10,
|
|
26
|
+
progress: 75,
|
|
27
|
+
status: 'single',
|
|
28
|
+
id: '1',
|
|
29
|
+
country: 'USA',
|
|
30
|
+
city: 'New York',
|
|
31
|
+
civility: 'Mr.',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
firstName: 'Jane',
|
|
35
|
+
lastName: 'Smith',
|
|
36
|
+
age: 32,
|
|
37
|
+
visits: 15,
|
|
38
|
+
progress: 50,
|
|
39
|
+
status: 'relationship',
|
|
40
|
+
id: '2',
|
|
41
|
+
country: 'USA',
|
|
42
|
+
city: 'Los Angeles',
|
|
43
|
+
civility: 'Ms.',
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Headers/Columns
|
|
49
|
+
You can define your structure for your headers and columns in the following way.
|
|
50
|
+
```tsx
|
|
51
|
+
const columns = [
|
|
52
|
+
{
|
|
53
|
+
id: 'select',
|
|
54
|
+
size: 48,
|
|
55
|
+
meta: {
|
|
56
|
+
styles: {
|
|
57
|
+
definedColumnSize: true,
|
|
58
|
+
header: {
|
|
59
|
+
hasNoPadding: true,
|
|
60
|
+
},
|
|
61
|
+
cell: {
|
|
62
|
+
hasNoPadding: true,
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
header: ({ table }) => (
|
|
67
|
+
<CheckboxContainer>
|
|
68
|
+
<Checkbox
|
|
69
|
+
name="select-all"
|
|
70
|
+
id="select-all"
|
|
71
|
+
label=""
|
|
72
|
+
isChecked={table.getIsAllPageRowsSelected()}
|
|
73
|
+
onChange={() => {
|
|
74
|
+
table
|
|
75
|
+
.getRowModel()
|
|
76
|
+
.rows.forEach((row) =>
|
|
77
|
+
row.toggleSelected(!table.getRowModel().rows[0].getIsSelected()),
|
|
78
|
+
);
|
|
79
|
+
}}
|
|
80
|
+
isDisabled={false}
|
|
81
|
+
aria-label="select-rows-header"
|
|
82
|
+
/>
|
|
83
|
+
</CheckboxContainer>
|
|
84
|
+
),
|
|
85
|
+
cell: ({ row }) => (
|
|
86
|
+
<CheckboxContainer>
|
|
87
|
+
<Checkbox
|
|
88
|
+
name="select"
|
|
89
|
+
id={row.id}
|
|
90
|
+
label=""
|
|
91
|
+
aria-label={`checkbox-${row.id}`}
|
|
92
|
+
isChecked={row.getIsSelected()}
|
|
93
|
+
isDisabled={!row.getCanSelect()}
|
|
94
|
+
onChange={row.getToggleSelectedHandler()}
|
|
95
|
+
/>
|
|
96
|
+
</CheckboxContainer>
|
|
97
|
+
),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 'firstName',
|
|
101
|
+
accessorKey: 'firstName',
|
|
102
|
+
header: ({ header }) => (
|
|
103
|
+
<PinHeaderColumns
|
|
104
|
+
content="First Name"
|
|
105
|
+
onPinClick={() => header.column.pin(header.column.getIsPinned() ? false : 'left')}
|
|
106
|
+
isPinned={header.column.getIsPinned() === 'left' ? true : false}
|
|
107
|
+
/>
|
|
108
|
+
),
|
|
109
|
+
cell: (info) => info.getValue(),
|
|
110
|
+
footer: (props) => props.column.id,
|
|
111
|
+
},
|
|
112
|
+
...
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
# Future Upgrades
|
|
118
|
+
- Only pin checkboxes by defaultif the table is responsive (or has a scrollbar that requires pinning)
|
|
119
|
+
- More examples of table setups with code previews.
|
|
120
|
+
- Visual Testing.
|
|
121
|
+
- Prebuilt filter components for the header that follow our universal design for tables.
|
|
122
|
+
- Items per Page Dropdown
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import { TanstackTable } from './TanstakTable';
|
|
4
|
+
import { people, Person } from './mock-data/mock-data';
|
|
5
|
+
import { columns } from './mock-data/table-configuration';
|
|
6
|
+
import { ColumnDef, Row } from '@tanstack/react-table';
|
|
7
|
+
import { Button } from '../button';
|
|
8
|
+
|
|
9
|
+
const meta: Meta = {
|
|
10
|
+
title: 'Layout/Tanstack Table',
|
|
11
|
+
component: TanstackTable,
|
|
12
|
+
args: {
|
|
13
|
+
data: people as (Person & { id: string })[],
|
|
14
|
+
columns: columns as ColumnDef<Person & { id: string }>[],
|
|
15
|
+
rowCount: people.length,
|
|
16
|
+
currentPage: 0,
|
|
17
|
+
rowsPerPage: 1000,
|
|
18
|
+
defaultPinnedColumns: ['select'],
|
|
19
|
+
clearFilters: () => {},
|
|
20
|
+
hasFilters: false,
|
|
21
|
+
isLoading: false,
|
|
22
|
+
enableRowSelection: true,
|
|
23
|
+
showPagination: false,
|
|
24
|
+
onClickRow: null,
|
|
25
|
+
},
|
|
26
|
+
argTypes: {
|
|
27
|
+
data: {
|
|
28
|
+
description:
|
|
29
|
+
'Array of data items. These are the items that will be displayed in the table cell.',
|
|
30
|
+
control: false,
|
|
31
|
+
table: {
|
|
32
|
+
category: 'Props',
|
|
33
|
+
type: { summary: 'T[{}]' },
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
columns: {
|
|
37
|
+
description:
|
|
38
|
+
'Column definitions including an `id` field. This is how you define your table structure.',
|
|
39
|
+
control: false,
|
|
40
|
+
table: {
|
|
41
|
+
category: 'Props',
|
|
42
|
+
type: { summary: 'ColumnDef<T & { id: string }>[]' },
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
className: {
|
|
46
|
+
description: 'Additional option for class names.',
|
|
47
|
+
control: false,
|
|
48
|
+
table: {
|
|
49
|
+
category: 'Props',
|
|
50
|
+
type: { summary: 'string' },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
actionBarClassName: {
|
|
54
|
+
description: 'class name for styling the action bar.',
|
|
55
|
+
control: false,
|
|
56
|
+
table: {
|
|
57
|
+
category: 'Props',
|
|
58
|
+
type: { summary: 'string' },
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
TableActions: {
|
|
62
|
+
description: 'Component for table actions with selected items.',
|
|
63
|
+
control: false,
|
|
64
|
+
table: {
|
|
65
|
+
category: 'Props',
|
|
66
|
+
type: {
|
|
67
|
+
summary: 'React.ComponentType<{ selectedItems: Row<any>[]; unselectRows: () => void }>',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
error: {
|
|
72
|
+
description: 'Error state of the table.',
|
|
73
|
+
control: false,
|
|
74
|
+
table: {
|
|
75
|
+
category: 'Props',
|
|
76
|
+
type: { summary: '{ hasError: boolean; errorMessage?: string }' },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
enableRowSelection: {
|
|
80
|
+
description: 'Enables row selection or defines selection conditions.',
|
|
81
|
+
control: { type: 'boolean' },
|
|
82
|
+
table: {
|
|
83
|
+
category: 'Props',
|
|
84
|
+
type: { summary: 'boolean | ((row: Row<T>) => boolean)' },
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
clearFilters: {
|
|
88
|
+
description: 'Function to clear applied filters.',
|
|
89
|
+
action: 'clearFilters',
|
|
90
|
+
control: false,
|
|
91
|
+
table: {
|
|
92
|
+
category: 'Callbacks',
|
|
93
|
+
type: { summary: '() => void' },
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
hasFilters: {
|
|
97
|
+
description: 'Indicates if filters are applied.',
|
|
98
|
+
control: { type: 'boolean' },
|
|
99
|
+
table: {
|
|
100
|
+
category: 'Props',
|
|
101
|
+
type: { summary: 'boolean' },
|
|
102
|
+
defaultValue: { summary: 'false' },
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
isLoading: {
|
|
106
|
+
description: 'When this is true, the table is considered to be in a loading state.',
|
|
107
|
+
control: { type: 'boolean' },
|
|
108
|
+
table: {
|
|
109
|
+
category: 'Props',
|
|
110
|
+
type: { summary: 'boolean' },
|
|
111
|
+
defaultValue: { summary: 'false' },
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
defaultPinnedColumns: {
|
|
115
|
+
description: 'Columns that are pinned by default.',
|
|
116
|
+
control: false,
|
|
117
|
+
table: {
|
|
118
|
+
category: 'Props',
|
|
119
|
+
type: { summary: 'string[]' },
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
onClickRow: {
|
|
123
|
+
description: 'Callback when a row is clicked.',
|
|
124
|
+
action: 'onClickRow',
|
|
125
|
+
control: false,
|
|
126
|
+
table: {
|
|
127
|
+
category: 'Callbacks',
|
|
128
|
+
type: { summary: '(row: Row<T>) => void' },
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
showPagination: {
|
|
132
|
+
description: 'Toggles pagination display.',
|
|
133
|
+
control: { type: 'boolean' },
|
|
134
|
+
table: {
|
|
135
|
+
category: 'Props',
|
|
136
|
+
type: { summary: 'boolean' },
|
|
137
|
+
defaultValue: { summary: 'true' },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
rowsPerPage: {
|
|
141
|
+
description: 'Number of rows to display per pagination page.',
|
|
142
|
+
control: { type: 'number' },
|
|
143
|
+
table: {
|
|
144
|
+
category: 'Props',
|
|
145
|
+
type: { summary: 'number' },
|
|
146
|
+
defaultValue: { summary: '1000' },
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
rowCount: {
|
|
150
|
+
description: 'Total number of rows in the table.',
|
|
151
|
+
control: { type: 'number' },
|
|
152
|
+
table: {
|
|
153
|
+
category: 'Props',
|
|
154
|
+
type: { summary: 'number' },
|
|
155
|
+
defaultValue: { summary: '1000' },
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
onChangePage: {
|
|
159
|
+
description: 'Callback when the page changes.',
|
|
160
|
+
action: 'onChangePage',
|
|
161
|
+
control: false,
|
|
162
|
+
table: {
|
|
163
|
+
category: 'Callbacks',
|
|
164
|
+
type: { summary: '(page: number, perPage: number) => void' },
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
currentPage: {
|
|
168
|
+
description: 'Current page number.',
|
|
169
|
+
control: { type: 'number' },
|
|
170
|
+
table: {
|
|
171
|
+
category: 'Props',
|
|
172
|
+
type: { summary: 'number' },
|
|
173
|
+
defaultValue: { summary: '1' },
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
totalEntriesText: {
|
|
177
|
+
description: 'Text to display for the total number of entries.',
|
|
178
|
+
control: false,
|
|
179
|
+
table: {
|
|
180
|
+
category: 'Props',
|
|
181
|
+
type: { summary: 'string' },
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
decorators: [
|
|
186
|
+
(Story) => (
|
|
187
|
+
<div style={{ width: '100%', height: '500px', overflow: 'hidden' }}>
|
|
188
|
+
<Story />
|
|
189
|
+
</div>
|
|
190
|
+
),
|
|
191
|
+
],
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
export default meta;
|
|
195
|
+
|
|
196
|
+
type Story = StoryObj<typeof TanstackTable<Person>>;
|
|
197
|
+
|
|
198
|
+
export const Default: Story = {};
|
|
199
|
+
|
|
200
|
+
export const WithRowClick: Story = {
|
|
201
|
+
args: {
|
|
202
|
+
onClickRow: (row: Row<Person>) => {
|
|
203
|
+
console.log(row);
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export const PinnedColumns: Story = {
|
|
209
|
+
args: {
|
|
210
|
+
defaultPinnedColumns: ['select', 'firstName', 'lastName'],
|
|
211
|
+
actionBarClassName: 'tanstack-table__action-bar-test',
|
|
212
|
+
},
|
|
213
|
+
render: (args) => (
|
|
214
|
+
<div style={{ width: '700px', height: '500px', overflow: 'hidden' }}>
|
|
215
|
+
<TanstackTable<Person> {...args} />
|
|
216
|
+
</div>
|
|
217
|
+
),
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export const WithTableActions: Story = {
|
|
221
|
+
args: {
|
|
222
|
+
TableActions: () => <Button ariaLabel="Actions">Actions</Button>,
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export const NoResults: Story = {
|
|
227
|
+
args: {
|
|
228
|
+
data: [],
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
export const NoResultsWithFilters: Story = {
|
|
233
|
+
args: {
|
|
234
|
+
data: [],
|
|
235
|
+
hasFilters: true,
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export const IsLoading: Story = {
|
|
240
|
+
args: {
|
|
241
|
+
isLoading: true,
|
|
242
|
+
data: [],
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
export const IsLoadingWithData: Story = {
|
|
247
|
+
args: {
|
|
248
|
+
isLoading: true,
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
export const WithPagination: Story = {
|
|
253
|
+
args: {
|
|
254
|
+
showPagination: true,
|
|
255
|
+
rowCount: people.length,
|
|
256
|
+
currentPage: 1,
|
|
257
|
+
rowsPerPage: 20,
|
|
258
|
+
onChangePage: () => {},
|
|
259
|
+
},
|
|
260
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import { useReactTable, getCoreRowModel, ColumnDef } from '@tanstack/react-table';
|
|
4
|
+
import classNames from 'classnames';
|
|
5
|
+
|
|
6
|
+
import { ActionBar } from './components/ActionBar';
|
|
7
|
+
import { NoResults } from './components/NoResults';
|
|
8
|
+
import { isNil } from 'lodash';
|
|
9
|
+
import { TablePagination } from './components/TablePagination';
|
|
10
|
+
import { Props } from './TankstackTable.types';
|
|
11
|
+
import { useTanstackTable } from './useTanstackTable';
|
|
12
|
+
import { TableHeader } from './components/TableHeader';
|
|
13
|
+
import { TableBody } from './components/TableBody';
|
|
14
|
+
|
|
15
|
+
export function TanstackTable<T extends object>({
|
|
16
|
+
columns: defaultColumns,
|
|
17
|
+
data,
|
|
18
|
+
className,
|
|
19
|
+
currentPage,
|
|
20
|
+
rowCount,
|
|
21
|
+
rowsPerPage = 1000,
|
|
22
|
+
onChangePage,
|
|
23
|
+
totalEntriesText,
|
|
24
|
+
TableActions,
|
|
25
|
+
error,
|
|
26
|
+
enableRowSelection = true,
|
|
27
|
+
clearFilters,
|
|
28
|
+
hasFilters,
|
|
29
|
+
showPagination = true,
|
|
30
|
+
isLoading = false,
|
|
31
|
+
defaultPinnedColumns,
|
|
32
|
+
onClickRow = null,
|
|
33
|
+
actionBarClassName,
|
|
34
|
+
}: Props<T & { id: string }>) {
|
|
35
|
+
const {
|
|
36
|
+
columns,
|
|
37
|
+
defaultData,
|
|
38
|
+
windowWidth,
|
|
39
|
+
rowSelection,
|
|
40
|
+
setRowSelection,
|
|
41
|
+
formattedColumns,
|
|
42
|
+
setFormattedColumns,
|
|
43
|
+
isClickedRow,
|
|
44
|
+
setIsClickedRow,
|
|
45
|
+
} = useTanstackTable({
|
|
46
|
+
defaultColumns,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const thRefs = useRef<Record<string, HTMLTableCellElement | null>>({});
|
|
50
|
+
|
|
51
|
+
// Sets formattedColumns with correct column widths
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
const updatedColumns: ColumnDef<T & { id: string }>[] = [];
|
|
54
|
+
|
|
55
|
+
formattedColumns.forEach((column) => {
|
|
56
|
+
const columnWidth = thRefs.current[column.id as keyof typeof thRefs.current]!.offsetWidth;
|
|
57
|
+
|
|
58
|
+
updatedColumns.push({
|
|
59
|
+
...column,
|
|
60
|
+
size: columnWidth,
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
setFormattedColumns(updatedColumns);
|
|
64
|
+
}, [data, columns, windowWidth]);
|
|
65
|
+
|
|
66
|
+
const table = useReactTable({
|
|
67
|
+
data: data ?? defaultData,
|
|
68
|
+
columns: formattedColumns,
|
|
69
|
+
state: {
|
|
70
|
+
rowSelection,
|
|
71
|
+
},
|
|
72
|
+
enableRowSelection,
|
|
73
|
+
onRowSelectionChange: setRowSelection,
|
|
74
|
+
getCoreRowModel: getCoreRowModel(),
|
|
75
|
+
manualPagination: true,
|
|
76
|
+
debugTable: true,
|
|
77
|
+
initialState: {
|
|
78
|
+
columnPinning: {
|
|
79
|
+
left: defaultPinnedColumns,
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const totalRowsOnPage = table.getRowModel().rows.length;
|
|
85
|
+
const hasPaginationProps =
|
|
86
|
+
!isNil(currentPage) &&
|
|
87
|
+
!isNil(rowCount) &&
|
|
88
|
+
!isNil(totalRowsOnPage) &&
|
|
89
|
+
onChangePage !== undefined;
|
|
90
|
+
|
|
91
|
+
const hasSelectedRows = Object.keys(rowSelection).length > 0;
|
|
92
|
+
|
|
93
|
+
const hasErrorAndIsNotLoading = error?.hasError && !isLoading;
|
|
94
|
+
const hasNoResults = data.length === 0 && !isLoading;
|
|
95
|
+
const shouldRenderPagination = showPagination && hasPaginationProps;
|
|
96
|
+
|
|
97
|
+
const renderBody = () => {
|
|
98
|
+
if (hasErrorAndIsNotLoading || hasNoResults) {
|
|
99
|
+
return (
|
|
100
|
+
<tr className="tanstack-table__tbody__tr">
|
|
101
|
+
<td className="tanstack-table__centered-row" colSpan={columns.length}>
|
|
102
|
+
<NoResults
|
|
103
|
+
clearFilters={clearFilters}
|
|
104
|
+
hasFilters={hasFilters}
|
|
105
|
+
message={
|
|
106
|
+
hasErrorAndIsNotLoading
|
|
107
|
+
? error?.errorMessage ?? 'There was an error isLoading the data.'
|
|
108
|
+
: 'No results found.'
|
|
109
|
+
}
|
|
110
|
+
/>
|
|
111
|
+
</td>
|
|
112
|
+
</tr>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<TableBody<T & { id: string }>
|
|
118
|
+
table={table}
|
|
119
|
+
onClickRow={onClickRow}
|
|
120
|
+
isLoading={isLoading}
|
|
121
|
+
columnsLength={columns.length}
|
|
122
|
+
isClickedRow={isClickedRow}
|
|
123
|
+
setIsClickedRow={setIsClickedRow}
|
|
124
|
+
/>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div className="tanstack-table__outer-container">
|
|
130
|
+
<div className="tanstack-table__container">
|
|
131
|
+
<table
|
|
132
|
+
className={classNames('tanstack-table', className, {
|
|
133
|
+
'is-Loading': isLoading,
|
|
134
|
+
})}
|
|
135
|
+
>
|
|
136
|
+
<thead className="tanstack-table__thead">
|
|
137
|
+
<TableHeader table={table} ref={thRefs} />
|
|
138
|
+
</thead>
|
|
139
|
+
<tbody className="tanstack-table__tbody">{renderBody()}</tbody>
|
|
140
|
+
</table>
|
|
141
|
+
|
|
142
|
+
{hasSelectedRows && (
|
|
143
|
+
<ActionBar table={table} TableActions={TableActions} className={actionBarClassName} />
|
|
144
|
+
)}
|
|
145
|
+
</div>
|
|
146
|
+
{shouldRenderPagination ? (
|
|
147
|
+
<TablePagination
|
|
148
|
+
currentPage={currentPage}
|
|
149
|
+
rowCount={rowCount}
|
|
150
|
+
onChangePage={onChangePage}
|
|
151
|
+
rowsPerPage={rowsPerPage}
|
|
152
|
+
totalEntriesText={totalEntriesText ?? `${totalRowsOnPage} of ${rowCount} entries`}
|
|
153
|
+
/>
|
|
154
|
+
) : null}
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import '@testing-library/jest-dom';
|
|
3
|
+
import { TanstackTable } from '../TanstakTable';
|
|
4
|
+
import { data, columns } from './__mocks__/test-mock-data';
|
|
5
|
+
|
|
6
|
+
describe('Tanstack Table Component', () => {
|
|
7
|
+
test('renders the table with provided data', () => {
|
|
8
|
+
render(<TanstackTable columns={columns} data={data} />);
|
|
9
|
+
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
10
|
+
expect(screen.getByText('Age')).toBeInTheDocument();
|
|
11
|
+
expect(screen.getByText('Alice')).toBeInTheDocument();
|
|
12
|
+
expect(screen.getByText('Bob')).toBeInTheDocument();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('displays loading state when loading is true', () => {
|
|
16
|
+
render(<TanstackTable columns={columns} data={[]} isLoading={true} />);
|
|
17
|
+
expect(screen.getByTestId('loading-indicator')).toBeInTheDocument();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('shows error message when error exists', () => {
|
|
21
|
+
render(
|
|
22
|
+
<TanstackTable
|
|
23
|
+
columns={columns}
|
|
24
|
+
data={[]}
|
|
25
|
+
error={{ hasError: true, errorMessage: 'Failed to load data' }}
|
|
26
|
+
/>,
|
|
27
|
+
);
|
|
28
|
+
expect(screen.getByText('Failed to load data')).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('handles click on checkbox to select row', () => {
|
|
32
|
+
render(<TanstackTable columns={columns} data={data} enableRowSelection={true} />);
|
|
33
|
+
const indexRow = 0;
|
|
34
|
+
const firstCheckbox = screen.getByTestId(`checkbox-${indexRow}`);
|
|
35
|
+
fireEvent.click(firstCheckbox);
|
|
36
|
+
const firstRow = screen.getByText(data[indexRow].name).closest('tr');
|
|
37
|
+
expect(firstRow).toHaveClass('is-selected');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('handles click on a row with onClickRow property', () => {
|
|
41
|
+
const onClickRow = jest.fn();
|
|
42
|
+
render(
|
|
43
|
+
<TanstackTable
|
|
44
|
+
columns={columns}
|
|
45
|
+
data={data}
|
|
46
|
+
enableRowSelection={true}
|
|
47
|
+
onClickRow={onClickRow}
|
|
48
|
+
/>,
|
|
49
|
+
);
|
|
50
|
+
const firstRow = screen.getByText(data[0].name);
|
|
51
|
+
fireEvent.click(firstRow);
|
|
52
|
+
expect(onClickRow).toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('renders pagination when showPagination is true', () => {
|
|
56
|
+
render(
|
|
57
|
+
<TanstackTable
|
|
58
|
+
columns={columns}
|
|
59
|
+
data={data}
|
|
60
|
+
currentPage={1}
|
|
61
|
+
rowCount={10}
|
|
62
|
+
rowsPerPage={5}
|
|
63
|
+
onChangePage={jest.fn()}
|
|
64
|
+
showPagination={true}
|
|
65
|
+
/>,
|
|
66
|
+
);
|
|
67
|
+
expect(screen.getByTestId('tanstack-table-pagination')).toBeInTheDocument();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('renders No Result when data is empty', () => {
|
|
71
|
+
render(
|
|
72
|
+
<TanstackTable
|
|
73
|
+
columns={columns}
|
|
74
|
+
data={[]}
|
|
75
|
+
currentPage={1}
|
|
76
|
+
rowCount={10}
|
|
77
|
+
rowsPerPage={5}
|
|
78
|
+
onChangePage={jest.fn()}
|
|
79
|
+
showPagination={true}
|
|
80
|
+
/>,
|
|
81
|
+
);
|
|
82
|
+
expect(screen.getByTestId('tanstack-table-no-results')).toBeInTheDocument();
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// may require visual testing
|
|
87
|
+
it.todo('Test pinned columns');
|