@indico-data/design-system 2.50.0 → 2.51.1
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 +45 -0
- package/lib/components/tanstackTable/TanstackTable.stories.d.ts +16 -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 +11 -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 +14 -0
- package/lib/index.css +262 -0
- package/lib/index.d.ts +67 -17
- package/lib/index.esm.css +262 -0
- package/lib/index.esm.js +20432 -56
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +20432 -55
- package/lib/index.js.map +1 -1
- package/package.json +2 -1
- package/src/components/tanstackTable/TankstackTable.types.ts +44 -0
- package/src/components/tanstackTable/TanstackTable.mdx +122 -0
- package/src/components/tanstackTable/TanstackTable.stories.tsx +284 -0
- package/src/components/tanstackTable/TanstakTable.tsx +156 -0
- package/src/components/tanstackTable/__tests__/TanstackTable.test.tsx +73 -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 +43 -0
- package/src/components/tanstackTable/components/ActionBar/__tests__/ActionBar.test.tsx +65 -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 +77 -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 +222 -0
- package/src/components/tanstackTable/styles/_variables.scss +35 -0
- package/src/components/tanstackTable/styles/table.scss +218 -0
- package/src/components/tanstackTable/styles/test.scss +19 -0
- package/src/components/tanstackTable/tanstack-table.d.ts +19 -0
- package/src/components/tanstackTable/useTanstackTable.tsx +39 -0
- package/src/index.ts +1 -0
- package/src/legacy/components/loading-indicators/CirclePulse/CirclePulse.tsx +1 -0
- package/src/styles/index.scss +1 -0
- package/src/stylesAndAnimations/utilityClasses/UtilityClassesTable.tsx +16 -2
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Checkbox } from "@/components/forms/checkbox";
|
|
2
|
+
import { ColumnDef } from "@tanstack/react-table";
|
|
3
|
+
|
|
4
|
+
export interface TestData {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
age: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const columns: ColumnDef<TestData>[] = [
|
|
11
|
+
{
|
|
12
|
+
id: 'select',
|
|
13
|
+
size: 48,
|
|
14
|
+
meta: {
|
|
15
|
+
styles: {
|
|
16
|
+
definedColumnSize: true,
|
|
17
|
+
header: {
|
|
18
|
+
hasNoPadding: true,
|
|
19
|
+
},
|
|
20
|
+
cell: {
|
|
21
|
+
hasNoPadding: true,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
header: ({ table }) => (
|
|
26
|
+
<div className="p-3">
|
|
27
|
+
<Checkbox
|
|
28
|
+
name="select-all"
|
|
29
|
+
id="select-all"
|
|
30
|
+
label=""
|
|
31
|
+
isChecked={table.getIsAllPageRowsSelected()}
|
|
32
|
+
onChange={() => {
|
|
33
|
+
table
|
|
34
|
+
.getRowModel()
|
|
35
|
+
.rows.forEach((row) =>
|
|
36
|
+
row.toggleSelected(!table.getRowModel().rows[0].getIsSelected()),
|
|
37
|
+
);
|
|
38
|
+
}}
|
|
39
|
+
isDisabled={false}
|
|
40
|
+
aria-label="select-rows-header"
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
),
|
|
44
|
+
cell: ({ row }) => (
|
|
45
|
+
<div className="p-3">
|
|
46
|
+
<Checkbox
|
|
47
|
+
name="select"
|
|
48
|
+
id={row.id}
|
|
49
|
+
label=""
|
|
50
|
+
data-testid={`checkbox-${row.id}`}
|
|
51
|
+
aria-label={`checkbox-${row.id}`}
|
|
52
|
+
isChecked={row.getIsSelected()}
|
|
53
|
+
isDisabled={!row.getCanSelect()}
|
|
54
|
+
onChange={row.getToggleSelectedHandler()}
|
|
55
|
+
/>
|
|
56
|
+
</div>
|
|
57
|
+
),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'firstName',
|
|
61
|
+
accessorKey: 'firstName',
|
|
62
|
+
header: () => 'First Name',
|
|
63
|
+
cell: (info) => info.getValue(),
|
|
64
|
+
footer: (props) => props.column.id,
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'name',
|
|
68
|
+
accessorKey: 'name',
|
|
69
|
+
header: 'Name',
|
|
70
|
+
cell: ({ getValue }) => getValue(),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'age',
|
|
74
|
+
accessorKey: 'age',
|
|
75
|
+
header: 'Age',
|
|
76
|
+
cell: ({ getValue }) => getValue(),
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
export const data: TestData[] = [
|
|
81
|
+
{ id: '1', name: 'Alice', age: 30 },
|
|
82
|
+
{ id: '2', name: 'Bob', age: 25 },
|
|
83
|
+
];
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Canvas, Meta, Controls } from '@storybook/blocks';
|
|
2
|
+
import * as ActionBarStories from './ActionBar.stories';
|
|
3
|
+
|
|
4
|
+
<Meta title="Layout/Tanstack Table/Action Bar" name="ActionBar" />
|
|
5
|
+
|
|
6
|
+
# Table
|
|
7
|
+
|
|
8
|
+
<Canvas of={ActionBarStories.Default} />
|
|
9
|
+
|
|
10
|
+
<Controls of={ActionBarStories.Default} />
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.tanstack-table__action-bar {
|
|
2
|
+
position: fixed;
|
|
3
|
+
bottom: 170px;
|
|
4
|
+
left: 50%;
|
|
5
|
+
transform: translateX(-50%);
|
|
6
|
+
z-index: 90;
|
|
7
|
+
box-shadow: 3px 1px 15px 0 rgba(0, 0, 0, 0.85);
|
|
8
|
+
&__container {
|
|
9
|
+
display: flex;
|
|
10
|
+
align-items: center;
|
|
11
|
+
gap: var(--pf-size-2);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
&__text-container {
|
|
15
|
+
padding: var(--pf-padding-2);
|
|
16
|
+
|
|
17
|
+
span {
|
|
18
|
+
font-size: var(--pf-font-size-base);
|
|
19
|
+
font-weight: var(--pf-font-weight-medium);
|
|
20
|
+
line-height: var(--pf-line-height-lg);
|
|
21
|
+
color: var(--pf-white-color);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&__button-container {
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: var(--pf-size-4);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { ActionBar } from './ActionBar';
|
|
3
|
+
import { Table } from '@tanstack/react-table';
|
|
4
|
+
import { Button } from '@/components/button';
|
|
5
|
+
|
|
6
|
+
type DummyRow = {
|
|
7
|
+
id: string;
|
|
8
|
+
isSelected: boolean;
|
|
9
|
+
getIsSelected: () => boolean;
|
|
10
|
+
toggleSelected: (value: boolean) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const createDummyRow = (id: string, isSelected = false): DummyRow => ({
|
|
14
|
+
id,
|
|
15
|
+
isSelected,
|
|
16
|
+
getIsSelected() {
|
|
17
|
+
return this.isSelected;
|
|
18
|
+
},
|
|
19
|
+
toggleSelected(value: boolean) {
|
|
20
|
+
this.isSelected = value;
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const dummyTable = {
|
|
25
|
+
getRowModel: () => ({
|
|
26
|
+
rows: [createDummyRow('1', true), createDummyRow('2', false), createDummyRow('3', true)],
|
|
27
|
+
}),
|
|
28
|
+
} as unknown as Table<{ id: string }>;
|
|
29
|
+
|
|
30
|
+
const meta: Meta = {
|
|
31
|
+
title: 'Layout/Tanstack Table/Action Bar',
|
|
32
|
+
component: ActionBar,
|
|
33
|
+
args: {
|
|
34
|
+
className: 'tanstack-table__action-bar-docs',
|
|
35
|
+
table: dummyTable,
|
|
36
|
+
},
|
|
37
|
+
argTypes: {
|
|
38
|
+
table: {
|
|
39
|
+
description: 'Table instance.',
|
|
40
|
+
control: false,
|
|
41
|
+
table: {
|
|
42
|
+
category: 'Props',
|
|
43
|
+
type: { summary: 'Table<any>' },
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
className: {
|
|
47
|
+
description: 'class name for styling.',
|
|
48
|
+
control: false,
|
|
49
|
+
table: {
|
|
50
|
+
category: 'Props',
|
|
51
|
+
type: { summary: 'string' },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
children: {
|
|
55
|
+
description: 'React node to display in the action bar.',
|
|
56
|
+
control: false,
|
|
57
|
+
table: {
|
|
58
|
+
category: 'Props',
|
|
59
|
+
type: { summary: 'React.ReactNode' },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
TableActions: {
|
|
63
|
+
description: 'Component for table actions with selected items.',
|
|
64
|
+
control: false,
|
|
65
|
+
table: {
|
|
66
|
+
category: 'Props',
|
|
67
|
+
type: {
|
|
68
|
+
summary: 'React.ComponentType<{ selectedItems: Row<any>[]; unselectRows: () => void }>',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
decorators: [
|
|
74
|
+
(Story) => (
|
|
75
|
+
<div style={{ width: '100%', height: '500px', overflow: 'hidden' }}>
|
|
76
|
+
<Story />
|
|
77
|
+
</div>
|
|
78
|
+
),
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default meta;
|
|
83
|
+
|
|
84
|
+
type Story = StoryObj<typeof ActionBar<{ id: string }>>;
|
|
85
|
+
|
|
86
|
+
export const Default: Story = {};
|
|
87
|
+
|
|
88
|
+
export const WithCustomTableActions: Story = {
|
|
89
|
+
args: {
|
|
90
|
+
TableActions: () => <Button ariaLabel="Clear">Clear</Button>,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const WithChildren: Story = {
|
|
95
|
+
args: {
|
|
96
|
+
children: <p>test</p>,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Row, Table } from '@tanstack/react-table';
|
|
3
|
+
import { Card } from '@/components/card';
|
|
4
|
+
import classNames from 'classnames';
|
|
5
|
+
|
|
6
|
+
type Props<T extends object> = {
|
|
7
|
+
table: Table<T & { id: string }>;
|
|
8
|
+
TableActions?: React.ComponentType<{
|
|
9
|
+
selectedItems: Row<T & { id: string }>[];
|
|
10
|
+
unselectRows: () => void;
|
|
11
|
+
}>;
|
|
12
|
+
className?: string;
|
|
13
|
+
children?: React.ReactNode;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function ActionBar<T extends object>({
|
|
17
|
+
table,
|
|
18
|
+
TableActions,
|
|
19
|
+
className,
|
|
20
|
+
children,
|
|
21
|
+
}: Props<T>) {
|
|
22
|
+
const selectedItems = table.getRowModel().rows.filter((row) => row.getIsSelected());
|
|
23
|
+
|
|
24
|
+
const unselectRows = () => {
|
|
25
|
+
table.getRowModel().rows.forEach((row) => {
|
|
26
|
+
row.toggleSelected(false);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<Card className={classNames('tanstack-table__action-bar', className)}>
|
|
32
|
+
{children ?? (
|
|
33
|
+
<div className="tanstack-table__action-bar__container">
|
|
34
|
+
{TableActions && (
|
|
35
|
+
<div className="tanstack-table__action-bar__button-container">
|
|
36
|
+
<TableActions selectedItems={selectedItems} unselectRows={unselectRows} />
|
|
37
|
+
</div>
|
|
38
|
+
)}
|
|
39
|
+
</div>
|
|
40
|
+
)}
|
|
41
|
+
</Card>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import { ActionBar } from '../ActionBar';
|
|
4
|
+
import type { Table } from '@tanstack/react-table';
|
|
5
|
+
|
|
6
|
+
type DummyRow = {
|
|
7
|
+
getIsSelected: () => boolean;
|
|
8
|
+
toggleSelected: (value: boolean) => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const createDummyTable = (rows: DummyRow[]): Table<any> =>
|
|
12
|
+
({
|
|
13
|
+
getRowModel: () => ({ rows }),
|
|
14
|
+
}) as Table<any>;
|
|
15
|
+
|
|
16
|
+
describe('ActionBar', () => {
|
|
17
|
+
it('renders correct when use a children prop', () => {
|
|
18
|
+
const dummyRows: DummyRow[] = [
|
|
19
|
+
{ getIsSelected: () => true, toggleSelected: jest.fn() },
|
|
20
|
+
{ getIsSelected: () => true, toggleSelected: jest.fn() },
|
|
21
|
+
];
|
|
22
|
+
const dummyTable = createDummyTable(dummyRows);
|
|
23
|
+
|
|
24
|
+
render(
|
|
25
|
+
<ActionBar table={dummyTable}>
|
|
26
|
+
<div>
|
|
27
|
+
<p>Hello</p>
|
|
28
|
+
</div>
|
|
29
|
+
</ActionBar>,
|
|
30
|
+
);
|
|
31
|
+
expect(screen.getByText('Hello')).toBeDefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders TableActions component when provided and calls unselectRows', () => {
|
|
35
|
+
const toggleRow1 = jest.fn();
|
|
36
|
+
const toggleRow2 = jest.fn();
|
|
37
|
+
const dummyRows: DummyRow[] = [
|
|
38
|
+
{ getIsSelected: () => true, toggleSelected: toggleRow1 },
|
|
39
|
+
{ getIsSelected: () => true, toggleSelected: toggleRow2 },
|
|
40
|
+
];
|
|
41
|
+
const dummyTable = createDummyTable(dummyRows);
|
|
42
|
+
|
|
43
|
+
// Dummy TableActions component which calls unselectRows on button click
|
|
44
|
+
const DummyTableActions: React.FC<{
|
|
45
|
+
selectedItems: DummyRow[];
|
|
46
|
+
unselectRows: () => void;
|
|
47
|
+
}> = ({ selectedItems, unselectRows }) => (
|
|
48
|
+
<button onClick={unselectRows} data-testid="dummy-actions">
|
|
49
|
+
Clear Selection ({selectedItems.length})
|
|
50
|
+
</button>
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
render(<ActionBar table={dummyTable} TableActions={DummyTableActions as any} />);
|
|
54
|
+
const button = screen.getByTestId('dummy-actions');
|
|
55
|
+
expect(button).toBeDefined();
|
|
56
|
+
expect(button.textContent).toContain('2');
|
|
57
|
+
|
|
58
|
+
// Simulate click on the TableActions button
|
|
59
|
+
fireEvent.click(button);
|
|
60
|
+
|
|
61
|
+
// Verify unselectRows toggles all selected rows to false
|
|
62
|
+
expect(toggleRow1).toHaveBeenCalledWith(false);
|
|
63
|
+
expect(toggleRow2).toHaveBeenCalledWith(false);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ActionBar } from './ActionBar';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
.tanstack-table__no-results {
|
|
2
|
+
display: flex;
|
|
3
|
+
justify-content: center;
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
align-items: center;
|
|
6
|
+
|
|
7
|
+
position: sticky;
|
|
8
|
+
left: 0;
|
|
9
|
+
margin: var(--pf-margin-8) auto;
|
|
10
|
+
|
|
11
|
+
&__text {
|
|
12
|
+
font-size: var(--pf-line-height-md);
|
|
13
|
+
font-weight: var(--pf-font-weight-medium);
|
|
14
|
+
color: var(--pf-primary-color-50);
|
|
15
|
+
|
|
16
|
+
span {
|
|
17
|
+
color: var(--pf-white-color);
|
|
18
|
+
cursor: pointer;
|
|
19
|
+
&:hover {
|
|
20
|
+
text-decoration: underline;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Button } from '@/components/button/Button';
|
|
2
|
+
|
|
3
|
+
type Props = {
|
|
4
|
+
clearFilters?: () => void;
|
|
5
|
+
hasFilters?: boolean;
|
|
6
|
+
message: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function NoResults({ clearFilters, hasFilters, message }: Props) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="tanstack-table__no-results" data-testid="tanstack-table-no-results">
|
|
12
|
+
<p className="tanstack-table__no-results__text">{message}</p>
|
|
13
|
+
{hasFilters && (
|
|
14
|
+
<p className="tanstack-table__no-results__text">
|
|
15
|
+
<Button ariaLabel="Reset filters" variant="link" onClick={clearFilters}>
|
|
16
|
+
Reset filters
|
|
17
|
+
</Button>
|
|
18
|
+
</p>
|
|
19
|
+
)}
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import { NoResults } from '../NoResults';
|
|
3
|
+
|
|
4
|
+
describe('NoResults Component', () => {
|
|
5
|
+
it('renders the message without filters applied', () => {
|
|
6
|
+
render(<NoResults message="No results found." />);
|
|
7
|
+
expect(screen.getByText('No results found.')).toBeInTheDocument();
|
|
8
|
+
expect(screen.queryByText('Reset filters')).not.toBeInTheDocument();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('renders the clear filters option when filters are applied', () => {
|
|
12
|
+
render(<NoResults message="No results found." hasFilters />);
|
|
13
|
+
expect(screen.getByText('No results found.')).toBeInTheDocument();
|
|
14
|
+
const clearFiltersElement = screen.getByText('Reset filters');
|
|
15
|
+
expect(clearFiltersElement).toBeInTheDocument();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('calls clearFilters when "Click here" is clicked', () => {
|
|
19
|
+
const mockClearFilters = jest.fn();
|
|
20
|
+
render(<NoResults message="No results found." hasFilters clearFilters={mockClearFilters} />);
|
|
21
|
+
const clearFiltersElement = screen.getByText('Reset filters');
|
|
22
|
+
fireEvent.click(clearFiltersElement);
|
|
23
|
+
expect(mockClearFilters).toHaveBeenCalled();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { NoResults } from './NoResults';
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { flexRender, Table } from '@tanstack/react-table';
|
|
2
|
+
import { Row } from '@tanstack/react-table';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import { getTdStyles } from '../../helpers';
|
|
5
|
+
import { CirclePulse } from '@/legacy/components/loading-indicators/CirclePulse';
|
|
6
|
+
import { Dispatch, SetStateAction } from 'react';
|
|
7
|
+
|
|
8
|
+
export type Props<T> = {
|
|
9
|
+
table: Table<T>;
|
|
10
|
+
onRowClick?: ((row: T & { id: string }) => void) | null | undefined;
|
|
11
|
+
isLoading: boolean;
|
|
12
|
+
columnsLength: number;
|
|
13
|
+
activeRows: string[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const TableBody = <T,>({
|
|
17
|
+
table,
|
|
18
|
+
onRowClick,
|
|
19
|
+
isLoading,
|
|
20
|
+
columnsLength,
|
|
21
|
+
activeRows,
|
|
22
|
+
}: Props<T>) => {
|
|
23
|
+
return (
|
|
24
|
+
<>
|
|
25
|
+
{table.getRowModel().rows.map((row) => (
|
|
26
|
+
<tr
|
|
27
|
+
className={classNames('tanstack-table__tbody__tr', {
|
|
28
|
+
'is-selected': row.getIsSelected(),
|
|
29
|
+
'show-hover': !!onRowClick,
|
|
30
|
+
'is-clicked': activeRows.includes(row.id), // Checkbox Is clicked
|
|
31
|
+
})}
|
|
32
|
+
key={row.id}
|
|
33
|
+
>
|
|
34
|
+
{row.getVisibleCells().map((cell) => {
|
|
35
|
+
const { columnDef } = cell.column;
|
|
36
|
+
return (
|
|
37
|
+
<td
|
|
38
|
+
className={classNames('tanstack-table__tbody__td', {
|
|
39
|
+
'pa-0': !!columnDef.meta?.styles?.cell?.hasNoPadding,
|
|
40
|
+
})}
|
|
41
|
+
key={cell.id}
|
|
42
|
+
style={{
|
|
43
|
+
...getTdStyles(
|
|
44
|
+
cell.column,
|
|
45
|
+
columnDef.meta?.styles?.cell?.textAlign,
|
|
46
|
+
columnDef.meta?.styles?.definedColumnSize,
|
|
47
|
+
cell.column.getSize(),
|
|
48
|
+
),
|
|
49
|
+
}}
|
|
50
|
+
onClick={() => {
|
|
51
|
+
// if the cell is not preventRowSelection, then we can click the row
|
|
52
|
+
if (!columnDef.meta?.styles?.cell?.preventRowSelection) {
|
|
53
|
+
onRowClick?.(row as any);
|
|
54
|
+
}
|
|
55
|
+
}}
|
|
56
|
+
>
|
|
57
|
+
{flexRender(columnDef.cell, cell.getContext())}
|
|
58
|
+
</td>
|
|
59
|
+
);
|
|
60
|
+
})}
|
|
61
|
+
</tr>
|
|
62
|
+
))}
|
|
63
|
+
{isLoading && (
|
|
64
|
+
<tr className="tanstack-table__tbody__tr">
|
|
65
|
+
<td
|
|
66
|
+
className={classNames('tanstack-table__centered-row', {
|
|
67
|
+
'is-Loading': isLoading,
|
|
68
|
+
})}
|
|
69
|
+
colSpan={columnsLength}
|
|
70
|
+
>
|
|
71
|
+
<CirclePulse data-testid="loading-indicator" />
|
|
72
|
+
</td>
|
|
73
|
+
</tr>
|
|
74
|
+
)}
|
|
75
|
+
</>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TableBody } from './TableBody';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { forwardRef } from 'react';
|
|
2
|
+
import { Column, flexRender, Header, Table } from '@tanstack/react-table';
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import { getThStyles } from '../../helpers';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
table: Table<any>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const TableHeader = forwardRef(({ table }: Props, ref: any) => {
|
|
11
|
+
const getStyles = (column: Column<any>, header: Header<any, any>) => {
|
|
12
|
+
return {
|
|
13
|
+
...getThStyles(column),
|
|
14
|
+
...(header.column.columnDef.meta?.styles?.header?.textAlign
|
|
15
|
+
? { textAlign: header.column.columnDef.meta?.styles?.header?.textAlign }
|
|
16
|
+
: {}),
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
{table.getHeaderGroups().map((headerGroup) => (
|
|
23
|
+
<tr key={headerGroup.id}>
|
|
24
|
+
{headerGroup.headers.map((header) => {
|
|
25
|
+
const { column } = header;
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<th
|
|
29
|
+
className={classNames('tanstack-table__thead__th', {
|
|
30
|
+
'pa-0': !!header.column.columnDef.meta?.styles?.header?.hasNoPadding,
|
|
31
|
+
})}
|
|
32
|
+
key={header.id}
|
|
33
|
+
ref={(el: any) => {
|
|
34
|
+
if (el) ref.current[column.id] = el;
|
|
35
|
+
}}
|
|
36
|
+
colSpan={header.colSpan}
|
|
37
|
+
style={{
|
|
38
|
+
...getStyles(column, header),
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
{flexRender(header.column.columnDef.header, header.getContext())}
|
|
42
|
+
</th>
|
|
43
|
+
);
|
|
44
|
+
})}
|
|
45
|
+
</tr>
|
|
46
|
+
))}
|
|
47
|
+
</>
|
|
48
|
+
);
|
|
49
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TableHeader } from './TableHeader';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Pagination as PaginationComponent } from '../../../pagination';
|
|
2
|
+
import { Row, Col } from '../../../grid';
|
|
3
|
+
|
|
4
|
+
interface TablePaginationProps {
|
|
5
|
+
rowsPerPage: number;
|
|
6
|
+
rowCount: number;
|
|
7
|
+
onChangePage: (page: number) => void;
|
|
8
|
+
currentPage: number;
|
|
9
|
+
totalEntriesText?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const TablePagination = ({
|
|
13
|
+
rowsPerPage,
|
|
14
|
+
rowCount,
|
|
15
|
+
onChangePage,
|
|
16
|
+
currentPage,
|
|
17
|
+
totalEntriesText,
|
|
18
|
+
}: TablePaginationProps) => {
|
|
19
|
+
const totalPages = Math.ceil(rowCount / rowsPerPage);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className="table__pagination" data-testid="tanstack-table-pagination">
|
|
23
|
+
<Row align="center" justify="between">
|
|
24
|
+
<Col xs="content">
|
|
25
|
+
{totalEntriesText && (
|
|
26
|
+
<span
|
|
27
|
+
data-testid="table-pagination-total-entries"
|
|
28
|
+
className="table__pagination-total-entries"
|
|
29
|
+
>
|
|
30
|
+
{totalEntriesText}
|
|
31
|
+
</span>
|
|
32
|
+
)}
|
|
33
|
+
</Col>
|
|
34
|
+
<Col xs="content">
|
|
35
|
+
<PaginationComponent
|
|
36
|
+
data-testid="table-pagination-component"
|
|
37
|
+
totalPages={totalPages}
|
|
38
|
+
currentPage={currentPage}
|
|
39
|
+
onChange={(page) => onChangePage(page)}
|
|
40
|
+
/>
|
|
41
|
+
</Col>
|
|
42
|
+
</Row>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
};
|
package/src/components/tanstackTable/components/TablePagination/__tests__/TablePagination.test.tsx
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import { TablePagination } from '../TablePagination';
|
|
3
|
+
|
|
4
|
+
describe('TablePagination', () => {
|
|
5
|
+
it('renders total entries text', () => {
|
|
6
|
+
render(
|
|
7
|
+
<TablePagination
|
|
8
|
+
rowsPerPage={10}
|
|
9
|
+
rowCount={100}
|
|
10
|
+
onChangePage={() => {}}
|
|
11
|
+
currentPage={1}
|
|
12
|
+
totalEntriesText="100 entries"
|
|
13
|
+
/>,
|
|
14
|
+
);
|
|
15
|
+
expect(screen.getByTestId('table-pagination-total-entries')).toBeInTheDocument();
|
|
16
|
+
expect(screen.getByTestId('tanstack-table-pagination')).toBeInTheDocument();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TablePagination';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Canvas, Meta, Controls } from '@storybook/blocks';
|
|
2
|
+
import * as TableStories from './PinnedColumn.stories';
|
|
3
|
+
|
|
4
|
+
<Meta title="Layout/Tanstack Table/Pinned Columns" name="Tanstack Table Pinned Column" />
|
|
5
|
+
|
|
6
|
+
# Tanstack Table Pinned Columns
|
|
7
|
+
|
|
8
|
+
<Canvas of={TableStories.PinnedColumns} />
|
|
9
|
+
|
|
10
|
+
This component enables the rapid creation of responsive, paginated tables using the React TanStack Table library.
|
|
11
|
+
It supports configuring pinned columns by extracting the necessary properties from your column creation object.
|
|
12
|
+
For more details on all configuration options and features, please refer to the official [TanStack Table Pinning Documentation](https://tanstack.com/table/latest/docs/guide/column-pinning#pin-columns-by-default).
|
|
13
|
+
|
|
14
|
+
To enable pinned columns, pass the following properties to your table.
|
|
15
|
+
In your column definition, extract the pinning information from the header object and pass it to your component in this case PinHeaderColumns as shown:
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
header: ({ header }) => (
|
|
19
|
+
<PinHeaderColumns
|
|
20
|
+
content="First Name"
|
|
21
|
+
onPinClick={() => header.column.pin(header.column.getIsPinned() ? false : 'left')}
|
|
22
|
+
isPinned={header.column.getIsPinned() === 'left' ? true : false}
|
|
23
|
+
/>
|
|
24
|
+
),
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This code toggles the pinned state of the column: if the column is already pinned, clicking will unpin it; otherwise, it will pin the column to the left
|
|
29
|
+
|
|
30
|
+
Additionally, the table component accepts a defaultPinnedColumns prop, which is an array of column IDs that will be pinned by default when the table is rendered. This ensures that important columns—such as selection checkboxes or key data fields—remain visible as users scroll through the table.
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
<TanstackTable defaultPinnedColumns={['select', 'firstName', 'lastName']} />
|
|
34
|
+
```
|