@purpurds/table 8.8.1 → 8.10.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/LICENSE.txt +16 -16
- package/dist/empty-table.d.ts +2 -1
- package/dist/empty-table.d.ts.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/table-column-header-cell.d.ts.map +1 -1
- package/dist/table-content.d.ts +2 -1
- package/dist/table-content.d.ts.map +1 -1
- package/dist/table-row-cell.d.ts +2 -2
- package/dist/table-row-cell.d.ts.map +1 -1
- package/dist/table.cjs.js +19 -233
- package/dist/table.cjs.js.map +1 -1
- package/dist/table.d.ts +16 -1
- package/dist/table.d.ts.map +1 -1
- package/dist/table.es.js +5817 -6153
- package/dist/table.es.js.map +1 -1
- package/package.json +39 -39
- package/src/draggable-table.test.tsx +1 -0
- package/src/empty-table.tsx +53 -25
- package/src/table-column-header-cell.tsx +2 -1
- package/src/table-content-drag.test.tsx +1 -0
- package/src/table-content.tsx +3 -0
- package/src/table-row-cell.tsx +3 -2
- package/src/table-settings-drawer.module.scss +1 -1
- package/src/table-settings-drawer.test.tsx +7 -2
- package/src/table.module.scss +12 -0
- package/src/table.stories.tsx +132 -0
- package/src/table.tsx +104 -9
- package/vitest.setup.ts +23 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@purpurds/table",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.10.0",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"main": "./dist/table.cjs.js",
|
|
6
6
|
"types": "./dist/table.d.ts",
|
|
@@ -20,53 +20,53 @@
|
|
|
20
20
|
"@dnd-kit/modifiers": "~9.0.0",
|
|
21
21
|
"@dnd-kit/utilities": "~3.2.2",
|
|
22
22
|
"@tanstack/react-table": "~8.21.2",
|
|
23
|
-
"classnames": "~2.5.
|
|
24
|
-
"@purpurds/badge": "8.
|
|
25
|
-
"@purpurds/
|
|
26
|
-
"@purpurds/checkbox": "8.
|
|
27
|
-
"@purpurds/
|
|
28
|
-
"@purpurds/
|
|
29
|
-
"@purpurds/heading": "8.
|
|
30
|
-
"@purpurds/icon": "8.
|
|
31
|
-
"@purpurds/
|
|
32
|
-
"@purpurds/
|
|
33
|
-
"@purpurds/
|
|
34
|
-
"@purpurds/
|
|
35
|
-
"@purpurds/
|
|
36
|
-
"@purpurds/toggle": "8.
|
|
37
|
-
"@purpurds/
|
|
38
|
-
"@purpurds/
|
|
39
|
-
"@purpurds/visually-hidden": "8.
|
|
23
|
+
"classnames": "~2.5.1",
|
|
24
|
+
"@purpurds/badge": "8.10.0",
|
|
25
|
+
"@purpurds/button": "8.10.0",
|
|
26
|
+
"@purpurds/checkbox": "8.10.0",
|
|
27
|
+
"@purpurds/cta-link": "8.10.0",
|
|
28
|
+
"@purpurds/drawer": "8.10.0",
|
|
29
|
+
"@purpurds/heading": "8.10.0",
|
|
30
|
+
"@purpurds/icon": "8.10.0",
|
|
31
|
+
"@purpurds/link": "8.10.0",
|
|
32
|
+
"@purpurds/paragraph": "8.10.0",
|
|
33
|
+
"@purpurds/select": "8.10.0",
|
|
34
|
+
"@purpurds/skeleton": "8.10.0",
|
|
35
|
+
"@purpurds/text-field": "8.10.0",
|
|
36
|
+
"@purpurds/toggle": "8.10.0",
|
|
37
|
+
"@purpurds/tooltip": "8.10.0",
|
|
38
|
+
"@purpurds/tokens": "8.10.0",
|
|
39
|
+
"@purpurds/visually-hidden": "8.10.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@rushstack/eslint-patch": "~1.10.0",
|
|
43
|
-
"@storybook/react-vite": "^10.0.
|
|
44
|
-
"@testing-library/dom": "~10.4.
|
|
45
|
-
"@testing-library/jest-dom": "~6.
|
|
46
|
-
"@testing-library/react": "~16.
|
|
43
|
+
"@storybook/react-vite": "^10.0.8",
|
|
44
|
+
"@testing-library/dom": "~10.4.1",
|
|
45
|
+
"@testing-library/jest-dom": "~6.9.1",
|
|
46
|
+
"@testing-library/react": "~16.3.0",
|
|
47
47
|
"@testing-library/user-event": "~14.5.1",
|
|
48
48
|
"@types/node": "22.17",
|
|
49
|
-
"@types/react-dom": "^19.
|
|
50
|
-
"@types/react": "^19.
|
|
51
|
-
"eslint": "9.
|
|
52
|
-
"jsdom": "~
|
|
53
|
-
"lint-staged": "
|
|
49
|
+
"@types/react-dom": "^19.2.3",
|
|
50
|
+
"@types/react": "^19.2.6",
|
|
51
|
+
"eslint": "9.39.1",
|
|
52
|
+
"jsdom": "~27.2.0",
|
|
53
|
+
"lint-staged": "16.2.6",
|
|
54
54
|
"prettier": "~2.8.8",
|
|
55
|
-
"react-dom": "^19.
|
|
56
|
-
"react": "^19.
|
|
57
|
-
"storybook": "^10.0.
|
|
58
|
-
"typescript": "^5.
|
|
59
|
-
"vite": "^
|
|
55
|
+
"react-dom": "^19.2.0",
|
|
56
|
+
"react": "^19.2.0",
|
|
57
|
+
"storybook": "^10.0.8",
|
|
58
|
+
"typescript": "^5.9.3",
|
|
59
|
+
"vite": "^7.2.2",
|
|
60
60
|
"vitest-axe": "~0.1.0",
|
|
61
61
|
"vitest-canvas-mock": "~0.3.3",
|
|
62
|
-
"vitest": "^
|
|
63
|
-
"@purpurds/autocomplete": "8.
|
|
62
|
+
"vitest": "^4.0.10",
|
|
63
|
+
"@purpurds/autocomplete": "8.10.0",
|
|
64
64
|
"@purpurds/component-rig": "1.0.0",
|
|
65
|
-
"@purpurds/grid": "8.
|
|
66
|
-
"@purpurds/label": "8.
|
|
67
|
-
"@purpurds/
|
|
68
|
-
"@purpurds/
|
|
69
|
-
"@purpurds/
|
|
65
|
+
"@purpurds/grid": "8.10.0",
|
|
66
|
+
"@purpurds/label": "8.10.0",
|
|
67
|
+
"@purpurds/illustrative-icon": "8.10.0",
|
|
68
|
+
"@purpurds/listbox": "8.10.0",
|
|
69
|
+
"@purpurds/pagination": "8.10.0"
|
|
70
70
|
},
|
|
71
71
|
"scripts": {
|
|
72
72
|
"build:dev": "vite",
|
package/src/empty-table.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React from "react";
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
2
|
import { Heading, type HeadingTagType } from "@purpurds/heading";
|
|
3
3
|
import { Paragraph } from "@purpurds/paragraph";
|
|
4
4
|
import c from "classnames/bind";
|
|
@@ -17,6 +17,7 @@ type EmptyTableProps = {
|
|
|
17
17
|
description: string;
|
|
18
18
|
colSpan: number;
|
|
19
19
|
icon: React.ReactNode;
|
|
20
|
+
tableContainerId: string;
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
export const EmptyTable = ({
|
|
@@ -26,29 +27,56 @@ export const EmptyTable = ({
|
|
|
26
27
|
description,
|
|
27
28
|
colSpan,
|
|
28
29
|
icon,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
30
|
+
tableContainerId,
|
|
31
|
+
}: EmptyTableProps) => {
|
|
32
|
+
const [visibleWidth, setVisibleWidth] = useState<number | undefined>(undefined);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const updateWidth = () => {
|
|
36
|
+
const container = document.getElementById(tableContainerId);
|
|
37
|
+
if (container) {
|
|
38
|
+
setVisibleWidth(container.clientWidth);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
updateWidth();
|
|
43
|
+
window.addEventListener("resize", updateWidth);
|
|
44
|
+
|
|
45
|
+
const container = document.getElementById(tableContainerId);
|
|
46
|
+
container?.addEventListener("scroll", updateWidth);
|
|
47
|
+
|
|
48
|
+
return () => {
|
|
49
|
+
window.removeEventListener("resize", updateWidth);
|
|
50
|
+
container?.removeEventListener("scroll", updateWidth);
|
|
51
|
+
};
|
|
52
|
+
}, [tableContainerId]);
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<TableRow>
|
|
56
|
+
<TableRowCell
|
|
57
|
+
colSpan={colSpan}
|
|
58
|
+
isLastRow={true}
|
|
59
|
+
isFirstCell={true}
|
|
60
|
+
isLastCell={true}
|
|
61
|
+
enableColumnDrag={false}
|
|
62
|
+
isEmptyTable={true}
|
|
43
63
|
>
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{
|
|
48
|
-
|
|
49
|
-
|
|
64
|
+
<div
|
|
65
|
+
className={cx([
|
|
66
|
+
`${rootClassName}__empty-section`,
|
|
67
|
+
`${rootClassName}__empty-section--${variant}`,
|
|
68
|
+
])}
|
|
69
|
+
style={visibleWidth ? { width: `${visibleWidth}px` } : undefined}
|
|
70
|
+
>
|
|
71
|
+
{icon && <div className={cx(`${rootClassName}__empty-section__icon`)}>{icon}</div>}
|
|
72
|
+
<div className={cx(`${rootClassName}__empty-section__texts`)}>
|
|
73
|
+
<Heading data-testid="purpur-table-empty-table-title" variant="title-100" tag={tag}>
|
|
74
|
+
{title}
|
|
75
|
+
</Heading>
|
|
76
|
+
<Paragraph data-testid="purpur-table-empty-table-description">{description}</Paragraph>
|
|
77
|
+
</div>
|
|
50
78
|
</div>
|
|
51
|
-
</
|
|
52
|
-
</
|
|
53
|
-
|
|
54
|
-
|
|
79
|
+
</TableRowCell>
|
|
80
|
+
</TableRow>
|
|
81
|
+
);
|
|
82
|
+
};
|
|
@@ -5,6 +5,7 @@ import { Button } from "@purpurds/button";
|
|
|
5
5
|
import { Checkbox, type CheckedState } from "@purpurds/checkbox";
|
|
6
6
|
import { IconArrowDown } from "@purpurds/icon/arrow-down";
|
|
7
7
|
import { IconArrowUp } from "@purpurds/icon/arrow-up";
|
|
8
|
+
import { IconSearch } from "@purpurds/icon/search";
|
|
8
9
|
import { IconSorter } from "@purpurds/icon/sorter";
|
|
9
10
|
import { Paragraph } from "@purpurds/paragraph";
|
|
10
11
|
import { Select, type SelectProps } from "@purpurds/select";
|
|
@@ -204,7 +205,6 @@ export const TableColumnHeaderCell = <TData extends RowData>({
|
|
|
204
205
|
<div
|
|
205
206
|
className={cx(`${rootClassName}__inner`)}
|
|
206
207
|
style={{
|
|
207
|
-
maxWidth: widthInRemString,
|
|
208
208
|
minWidth: widthInRemString,
|
|
209
209
|
}}
|
|
210
210
|
>
|
|
@@ -406,6 +406,7 @@ const Filter = <TData extends RowData>({ header }: { header: Header<TData, unkno
|
|
|
406
406
|
}
|
|
407
407
|
placeholder={filterPlaceholder}
|
|
408
408
|
aria-label={filterAriaLabel}
|
|
409
|
+
startAdornment={<IconSearch key="search-icon" size="xs" />}
|
|
409
410
|
/>
|
|
410
411
|
);
|
|
411
412
|
}
|
package/src/table-content.tsx
CHANGED
|
@@ -34,6 +34,7 @@ type TableContentProps<TData extends RowData> = {
|
|
|
34
34
|
emptyTableHeadingTag?: HeadingTagType;
|
|
35
35
|
emptyTableCopy?: { title: string; description: string };
|
|
36
36
|
emptyTableIcon?: React.ReactNode;
|
|
37
|
+
tableContainerId?: string;
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
export function TableContent<TData extends RowData>({
|
|
@@ -55,6 +56,7 @@ export function TableContent<TData extends RowData>({
|
|
|
55
56
|
emptyTableHeadingTag,
|
|
56
57
|
emptyTableCopy,
|
|
57
58
|
emptyTableIcon,
|
|
59
|
+
tableContainerId,
|
|
58
60
|
}: TableContentProps<TData>) {
|
|
59
61
|
return (
|
|
60
62
|
<table
|
|
@@ -82,6 +84,7 @@ export function TableContent<TData extends RowData>({
|
|
|
82
84
|
description={emptyTableCopy.description}
|
|
83
85
|
colSpan={tanstackTable.getVisibleLeafColumns().length}
|
|
84
86
|
icon={emptyTableIcon}
|
|
87
|
+
tableContainerId={tableContainerId!}
|
|
85
88
|
/>
|
|
86
89
|
) : (
|
|
87
90
|
tableRows.map((row, rowIndex) => (
|
package/src/table-row-cell.tsx
CHANGED
|
@@ -45,7 +45,7 @@ type TableRowCellProps<TData extends RowData> = {
|
|
|
45
45
|
isLastCell: boolean;
|
|
46
46
|
enableColumnDrag: boolean;
|
|
47
47
|
draggingActive?: boolean;
|
|
48
|
-
|
|
48
|
+
isEmptyTable?: boolean;
|
|
49
49
|
} & (EmptyTableCell | TableRowCell<TData>);
|
|
50
50
|
|
|
51
51
|
const rootClassName = "purpur-table-row-cell";
|
|
@@ -64,7 +64,7 @@ const TableRowCell = <TData extends RowData>({
|
|
|
64
64
|
isLastCell,
|
|
65
65
|
enableColumnDrag,
|
|
66
66
|
draggingActive,
|
|
67
|
-
|
|
67
|
+
isEmptyTable,
|
|
68
68
|
}: TableRowCellProps<TData>) => {
|
|
69
69
|
const elementRef = useRef<HTMLTableCellElement | null>(null);
|
|
70
70
|
const isVisible = useElementVisibility(elementRef, 1);
|
|
@@ -98,6 +98,7 @@ const TableRowCell = <TData extends RowData>({
|
|
|
98
98
|
(cell?.column.getIsLastColumn() || isLastCell) && isVisible && isLastRow,
|
|
99
99
|
[`${rootClassName}__column-drag-enabled`]: enableColumnDrag,
|
|
100
100
|
[`${rootClassName}__dragging`]: draggingActive,
|
|
101
|
+
[`${rootClassName}__empty-table`]: isEmptyTable,
|
|
101
102
|
[`${rootClassName}--drop-indicator-before`]:
|
|
102
103
|
!isRowSelector && dropIndicatorPosition === "before",
|
|
103
104
|
[`${rootClassName}--drop-indicator-after`]: !isRowSelector && dropIndicatorPosition === "after",
|
|
@@ -2,6 +2,7 @@ import React from "react";
|
|
|
2
2
|
import { type Column } from "@tanstack/react-table";
|
|
3
3
|
import { render, screen, within } from "@testing-library/react";
|
|
4
4
|
import userEvent from "@testing-library/user-event";
|
|
5
|
+
import { vi } from "vitest";
|
|
5
6
|
import { axe } from "vitest-axe";
|
|
6
7
|
|
|
7
8
|
import { TableSettingsDrawer } from "./table-settings-drawer";
|
|
@@ -292,7 +293,9 @@ describe("Data Table - Settings drawer", () => {
|
|
|
292
293
|
name: copy.settingsDrawerWithColumnDrag.visibleColumns.ariaLabels.buttons.rearrange,
|
|
293
294
|
});
|
|
294
295
|
expect(rearrangeButton).toBeInTheDocument();
|
|
295
|
-
expect(rearrangeButton).toHaveTextContent(
|
|
296
|
+
expect(rearrangeButton).toHaveTextContent(
|
|
297
|
+
copy.settingsDrawerWithColumnDrag.visibleColumns.buttons.rearrange
|
|
298
|
+
);
|
|
296
299
|
});
|
|
297
300
|
|
|
298
301
|
it("should update aria-label when entering rearrange mode", async () => {
|
|
@@ -306,7 +309,9 @@ describe("Data Table - Settings drawer", () => {
|
|
|
306
309
|
name: copy.settingsDrawerWithColumnDrag.visibleColumns.ariaLabels.buttons.done,
|
|
307
310
|
});
|
|
308
311
|
expect(doneButton).toBeInTheDocument();
|
|
309
|
-
expect(doneButton).toHaveTextContent(
|
|
312
|
+
expect(doneButton).toHaveTextContent(
|
|
313
|
+
copy.settingsDrawerWithColumnDrag.visibleColumns.buttons.done
|
|
314
|
+
);
|
|
310
315
|
});
|
|
311
316
|
});
|
|
312
317
|
});
|
package/src/table.module.scss
CHANGED
|
@@ -104,6 +104,7 @@ $indicatorWidth: 3px;
|
|
|
104
104
|
padding: var(--purpur-spacing-100) var(--purpur-spacing-150) var(--purpur-spacing-100)
|
|
105
105
|
var(--purpur-spacing-300);
|
|
106
106
|
align-self: stretch;
|
|
107
|
+
max-width: fit-content;
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
&__content {
|
|
@@ -316,6 +317,15 @@ $indicatorWidth: 3px;
|
|
|
316
317
|
white-space: nowrap;
|
|
317
318
|
text-overflow: ellipsis;
|
|
318
319
|
|
|
320
|
+
&__empty-table {
|
|
321
|
+
padding: 0;
|
|
322
|
+
|
|
323
|
+
.purpur-table__empty-section {
|
|
324
|
+
border-bottom-left-radius: var(--table-border-radius);
|
|
325
|
+
border-bottom-right-radius: var(--table-border-radius);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
319
329
|
&__date {
|
|
320
330
|
display: flex;
|
|
321
331
|
align-items: center;
|
|
@@ -493,6 +503,8 @@ $indicatorWidth: 3px;
|
|
|
493
503
|
align-items: center;
|
|
494
504
|
gap: var(--purpur-spacing-150);
|
|
495
505
|
align-self: stretch;
|
|
506
|
+
position: sticky;
|
|
507
|
+
left: 0;
|
|
496
508
|
|
|
497
509
|
&__icon {
|
|
498
510
|
width: var(--purpur-spacing-1600);
|
package/src/table.stories.tsx
CHANGED
|
@@ -1309,6 +1309,138 @@ This example demonstrates the secondary visual variant of the Table.
|
|
|
1309
1309
|
},
|
|
1310
1310
|
};
|
|
1311
1311
|
|
|
1312
|
+
/**
|
|
1313
|
+
* Demonstrates controlled table settings with user preferences that differ from defaults.
|
|
1314
|
+
*/
|
|
1315
|
+
export const ControlledTableSettings: StoryTableData = {
|
|
1316
|
+
args: {
|
|
1317
|
+
variant: "primary",
|
|
1318
|
+
enableFilters: true,
|
|
1319
|
+
enableToolbar: true,
|
|
1320
|
+
settingsDrawerCopy: commonSettingsDrawerCopy,
|
|
1321
|
+
toolbarCopy: commonToolbarCopy,
|
|
1322
|
+
},
|
|
1323
|
+
parameters: {
|
|
1324
|
+
docs: {
|
|
1325
|
+
description: {
|
|
1326
|
+
story: `
|
|
1327
|
+
This example demonstrates controlled table settings where the user's saved preferences
|
|
1328
|
+
differ from the default values. This is useful for persisting user preferences across sessions.
|
|
1329
|
+
|
|
1330
|
+
### How to enable this feature:
|
|
1331
|
+
\`\`\`jsx
|
|
1332
|
+
const [tableSettings, setTableSettings] = useState({
|
|
1333
|
+
showColumnFilters: false, // User turned off filters
|
|
1334
|
+
stickyHeaders: false, // User turned off sticky headers
|
|
1335
|
+
stickyFirstColumn: false, // User turned off sticky first column
|
|
1336
|
+
});
|
|
1337
|
+
|
|
1338
|
+
<Table
|
|
1339
|
+
enableFilters={true}
|
|
1340
|
+
|
|
1341
|
+
// Controlled state (user preferences)
|
|
1342
|
+
controlledShowColumnFilters={tableSettings.showColumnFilters}
|
|
1343
|
+
onShowColumnFiltersChange={(value) =>
|
|
1344
|
+
setTableSettings(prev => ({ ...prev, showColumnFilters: value }))
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
controlledStickyHeaders={tableSettings.stickyHeaders}
|
|
1348
|
+
onStickyHeadersChange={(value) =>
|
|
1349
|
+
setTableSettings(prev => ({ ...prev, stickyHeaders: value }))
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
controlledStickyFirstColumn={tableSettings.stickyFirstColumn}
|
|
1353
|
+
onStickyFirstColumnChange={(value) =>
|
|
1354
|
+
setTableSettings(prev => ({ ...prev, stickyFirstColumn: value }))
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// Default values (for reset button)
|
|
1358
|
+
defaultShowColumnFilters={true}
|
|
1359
|
+
defaultStickyHeaders={true}
|
|
1360
|
+
defaultStickyFirstColumn={true}
|
|
1361
|
+
...other props
|
|
1362
|
+
/>
|
|
1363
|
+
\`\`\`
|
|
1364
|
+
|
|
1365
|
+
### Key points:
|
|
1366
|
+
- User preferences are stored in state (could be localStorage, API, etc.)
|
|
1367
|
+
- Defaults are different from current user preferences
|
|
1368
|
+
- Reset button in settings drawer will restore to defaults
|
|
1369
|
+
- State changes are captured via onChange callbacks
|
|
1370
|
+
- Demonstrates state persistence pattern
|
|
1371
|
+
- Uses larger dataset with more columns and rows to properly test sticky functionality
|
|
1372
|
+
|
|
1373
|
+
### Try it out:
|
|
1374
|
+
1. Open the Settings drawer
|
|
1375
|
+
2. Notice filters are hidden, headers aren't sticky, and first column isn't locked
|
|
1376
|
+
3. Scroll horizontally and vertically to see the difference
|
|
1377
|
+
4. Toggle "Sticky header" to see headers stay fixed while scrolling down
|
|
1378
|
+
5. Toggle "Lock first column" to see the first column stay fixed while scrolling horizontally
|
|
1379
|
+
6. Click "Reset settings" to restore all to defaults (all enabled)
|
|
1380
|
+
`,
|
|
1381
|
+
},
|
|
1382
|
+
},
|
|
1383
|
+
},
|
|
1384
|
+
render: (args) => {
|
|
1385
|
+
const [tableSettings, setTableSettings] = useState({
|
|
1386
|
+
showColumnFilters: false, // User turned off filters (default is true)
|
|
1387
|
+
stickyHeaders: false, // User turned off sticky headers (default is true)
|
|
1388
|
+
stickyFirstColumn: false, // User turned off sticky first column (default is true)
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1391
|
+
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
|
|
1392
|
+
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
|
|
1393
|
+
|
|
1394
|
+
// Simulate saving to localStorage when settings change
|
|
1395
|
+
const handleShowColumnFiltersChange = (value: boolean) => {
|
|
1396
|
+
setTableSettings((prev) => ({ ...prev, showColumnFilters: value }));
|
|
1397
|
+
console.log("Saving showColumnFilters to localStorage:", value);
|
|
1398
|
+
};
|
|
1399
|
+
|
|
1400
|
+
const handleStickyHeadersChange = (value: boolean) => {
|
|
1401
|
+
setTableSettings((prev) => ({ ...prev, stickyHeaders: value }));
|
|
1402
|
+
console.log("Saving stickyHeaders to localStorage:", value);
|
|
1403
|
+
};
|
|
1404
|
+
|
|
1405
|
+
const handleStickyFirstColumnChange = (value: boolean) => {
|
|
1406
|
+
setTableSettings((prev) => ({ ...prev, stickyFirstColumn: value }));
|
|
1407
|
+
console.log("Saving stickyFirstColumn to localStorage:", value);
|
|
1408
|
+
};
|
|
1409
|
+
|
|
1410
|
+
return renderTableContainer(
|
|
1411
|
+
args,
|
|
1412
|
+
<Table
|
|
1413
|
+
className={c("table-max-height")}
|
|
1414
|
+
variant={args.variant}
|
|
1415
|
+
columns={columnDef}
|
|
1416
|
+
data={generatedTableData.slice(0, 50)}
|
|
1417
|
+
enableFilters={true}
|
|
1418
|
+
enableToolbar={true}
|
|
1419
|
+
enableSorting={false}
|
|
1420
|
+
enableRowSelection={false}
|
|
1421
|
+
enableActionBar={false}
|
|
1422
|
+
toolbarCopy={commonToolbarCopy}
|
|
1423
|
+
settingsDrawerCopy={commonSettingsDrawerCopy}
|
|
1424
|
+
controlledShowColumnFilters={tableSettings.showColumnFilters}
|
|
1425
|
+
onShowColumnFiltersChange={handleShowColumnFiltersChange}
|
|
1426
|
+
controlledStickyHeaders={tableSettings.stickyHeaders}
|
|
1427
|
+
onStickyHeadersChange={handleStickyHeadersChange}
|
|
1428
|
+
controlledStickyFirstColumn={tableSettings.stickyFirstColumn}
|
|
1429
|
+
onStickyFirstColumnChange={handleStickyFirstColumnChange}
|
|
1430
|
+
defaultShowColumnFilters={true}
|
|
1431
|
+
defaultStickyHeaders={true}
|
|
1432
|
+
defaultStickyFirstColumn={true}
|
|
1433
|
+
state={{ columnFilters, columnVisibility }}
|
|
1434
|
+
onColumnFiltersChange={setColumnFilters}
|
|
1435
|
+
onColumnVisibilityChange={setColumnVisibility}
|
|
1436
|
+
onExportData={(e) => console.log("Export data", e)}
|
|
1437
|
+
exportFormats={"csv"}
|
|
1438
|
+
onToggleExpand={() => console.log("Toggle expand")}
|
|
1439
|
+
/>
|
|
1440
|
+
);
|
|
1441
|
+
},
|
|
1442
|
+
};
|
|
1443
|
+
|
|
1312
1444
|
export const WithColumnDragAndDrop = {
|
|
1313
1445
|
name: "With column drag and drop",
|
|
1314
1446
|
args: {
|
package/src/table.tsx
CHANGED
|
@@ -72,7 +72,22 @@ export type TableProps<TData extends RowData> = {
|
|
|
72
72
|
data: TData[];
|
|
73
73
|
paginationComponent?: ReactElement<PaginationProps>;
|
|
74
74
|
fullWidth?: boolean;
|
|
75
|
+
controlledShowColumnFilters?: boolean;
|
|
76
|
+
onShowColumnFiltersChange?: (value: boolean) => void;
|
|
77
|
+
controlledStickyHeaders?: boolean;
|
|
78
|
+
onStickyHeadersChange?: (value: boolean) => void;
|
|
79
|
+
controlledStickyFirstColumn?: boolean;
|
|
80
|
+
onStickyFirstColumnChange?: (value: boolean) => void;
|
|
81
|
+
defaultShowColumnFilters?: boolean;
|
|
82
|
+
defaultStickyHeaders?: boolean;
|
|
83
|
+
defaultStickyFirstColumn?: boolean;
|
|
84
|
+
/**
|
|
85
|
+
* @deprecated Use defaultStickyHeaders instead
|
|
86
|
+
*/
|
|
75
87
|
stickyHeaders?: boolean;
|
|
88
|
+
/**
|
|
89
|
+
* @deprecated Use defaultStickyFirstColumn instead
|
|
90
|
+
*/
|
|
76
91
|
stickyFirstColumn?: boolean;
|
|
77
92
|
onRowsCountChange?: (rowsCount: number) => void;
|
|
78
93
|
drawerZIndex?: number;
|
|
@@ -109,8 +124,17 @@ export const Table = <TData extends RowData>({
|
|
|
109
124
|
skeletonRows,
|
|
110
125
|
sortingAriaLabels,
|
|
111
126
|
state,
|
|
112
|
-
|
|
113
|
-
|
|
127
|
+
controlledShowColumnFilters,
|
|
128
|
+
onShowColumnFiltersChange,
|
|
129
|
+
controlledStickyFirstColumn,
|
|
130
|
+
onStickyFirstColumnChange,
|
|
131
|
+
controlledStickyHeaders,
|
|
132
|
+
onStickyHeadersChange,
|
|
133
|
+
defaultShowColumnFilters,
|
|
134
|
+
defaultStickyFirstColumn,
|
|
135
|
+
defaultStickyHeaders,
|
|
136
|
+
stickyHeaders: stickyHeadersDeprecated,
|
|
137
|
+
stickyFirstColumn: stickyFirstColumnDeprecated,
|
|
114
138
|
toolbarCopy,
|
|
115
139
|
toolbarTotalRowCount,
|
|
116
140
|
variant = "primary",
|
|
@@ -128,11 +152,80 @@ export const Table = <TData extends RowData>({
|
|
|
128
152
|
}: TableProps<TData>) => {
|
|
129
153
|
const [isSettingsDrawerOpen, setSettingsDrawerIsOpen] = useState(false);
|
|
130
154
|
const [isExportDrawerOpen, setExportDrawerIsOpen] = useState(false);
|
|
131
|
-
|
|
132
|
-
|
|
155
|
+
|
|
156
|
+
// Determine if controlled based on BOTH prop AND handler being provided
|
|
157
|
+
const isShowColumnFiltersControlled =
|
|
158
|
+
controlledShowColumnFilters !== undefined && onShowColumnFiltersChange !== undefined;
|
|
159
|
+
const isStickyFirstColumnControlled =
|
|
160
|
+
controlledStickyFirstColumn !== undefined && onStickyFirstColumnChange !== undefined;
|
|
161
|
+
const isStickyHeadersControlled =
|
|
162
|
+
controlledStickyHeaders !== undefined && onStickyHeadersChange !== undefined;
|
|
163
|
+
|
|
164
|
+
// Support deprecated props as fallbacks for defaults (backwards compatibility)
|
|
165
|
+
const effectiveDefaultStickyHeaders =
|
|
166
|
+
defaultStickyHeaders !== undefined
|
|
167
|
+
? defaultStickyHeaders
|
|
168
|
+
: stickyHeadersDeprecated !== undefined
|
|
169
|
+
? stickyHeadersDeprecated
|
|
170
|
+
: true;
|
|
171
|
+
const effectiveDefaultStickyFirstColumn =
|
|
172
|
+
defaultStickyFirstColumn !== undefined
|
|
173
|
+
? defaultStickyFirstColumn
|
|
174
|
+
: stickyFirstColumnDeprecated !== undefined
|
|
175
|
+
? stickyFirstColumnDeprecated
|
|
176
|
+
: true;
|
|
177
|
+
|
|
178
|
+
// Internal state - only used when not controlled
|
|
179
|
+
const [showColumnFiltersInternal, setShowColumnFiltersInternal] = useState(
|
|
180
|
+
defaultShowColumnFilters ?? Boolean(props.enableFilters)
|
|
181
|
+
);
|
|
182
|
+
const [stickyFirstColumnInternal, setStickyFirstColumnInternal] = useState(
|
|
183
|
+
effectiveDefaultStickyFirstColumn
|
|
133
184
|
);
|
|
134
|
-
const [
|
|
135
|
-
|
|
185
|
+
const [stickyHeadersInternal, setStickyHeadersInternal] = useState(effectiveDefaultStickyHeaders);
|
|
186
|
+
|
|
187
|
+
// Use controlled value if fully controlled, otherwise use internal state
|
|
188
|
+
const showColumnFiltersEnabled = isShowColumnFiltersControlled
|
|
189
|
+
? controlledShowColumnFilters!
|
|
190
|
+
: showColumnFiltersInternal;
|
|
191
|
+
const stickyFirstColumn = isStickyFirstColumnControlled
|
|
192
|
+
? controlledStickyFirstColumn!
|
|
193
|
+
: stickyFirstColumnInternal;
|
|
194
|
+
const stickyHeaders = isStickyHeadersControlled
|
|
195
|
+
? controlledStickyHeaders!
|
|
196
|
+
: stickyHeadersInternal;
|
|
197
|
+
|
|
198
|
+
// Wrapper functions to handle both controlled and uncontrolled state
|
|
199
|
+
const setShowColumnFiltersEnabled = (value: boolean | ((prev: boolean) => boolean)) => {
|
|
200
|
+
const newValue = typeof value === "function" ? value(showColumnFiltersEnabled) : value;
|
|
201
|
+
|
|
202
|
+
if (isShowColumnFiltersControlled) {
|
|
203
|
+
onShowColumnFiltersChange!(newValue);
|
|
204
|
+
} else {
|
|
205
|
+
setShowColumnFiltersInternal(newValue);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const setStickyFirstColumn = (value: boolean | ((prev: boolean) => boolean)) => {
|
|
210
|
+
const newValue = typeof value === "function" ? value(stickyFirstColumn) : value;
|
|
211
|
+
|
|
212
|
+
if (isStickyFirstColumnControlled) {
|
|
213
|
+
onStickyFirstColumnChange!(newValue);
|
|
214
|
+
} else {
|
|
215
|
+
setStickyFirstColumnInternal(newValue);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const setStickyHeaders = (value: boolean | ((prev: boolean) => boolean)) => {
|
|
220
|
+
const newValue = typeof value === "function" ? value(stickyHeaders) : value;
|
|
221
|
+
|
|
222
|
+
if (isStickyHeadersControlled) {
|
|
223
|
+
onStickyHeadersChange!(newValue);
|
|
224
|
+
} else {
|
|
225
|
+
setStickyHeadersInternal(newValue);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
136
229
|
const prevShowOnlySelectedRows = useRef(showOnlySelectedRows);
|
|
137
230
|
const tableContainerRef = useRef<HTMLTableElement>(null);
|
|
138
231
|
const uid = useId();
|
|
@@ -298,9 +391,9 @@ export const Table = <TData extends RowData>({
|
|
|
298
391
|
};
|
|
299
392
|
|
|
300
393
|
const handleResetSettings = () => {
|
|
301
|
-
setShowColumnFiltersEnabled(Boolean(props.enableFilters));
|
|
302
|
-
setStickyFirstColumn(
|
|
303
|
-
setStickyHeaders(
|
|
394
|
+
setShowColumnFiltersEnabled(defaultShowColumnFilters ?? Boolean(props.enableFilters));
|
|
395
|
+
setStickyFirstColumn(effectiveDefaultStickyFirstColumn);
|
|
396
|
+
setStickyHeaders(effectiveDefaultStickyHeaders);
|
|
304
397
|
tanstackTable.resetColumnOrder();
|
|
305
398
|
tanstackTable.resetColumnVisibility();
|
|
306
399
|
};
|
|
@@ -409,6 +502,7 @@ export const Table = <TData extends RowData>({
|
|
|
409
502
|
emptyTableHeadingTag={emptyTableHeadingTag}
|
|
410
503
|
emptyTableCopy={emptyTableCopy}
|
|
411
504
|
emptyTableIcon={emptyTableIcon}
|
|
505
|
+
tableContainerId={`${uid}-table-container`}
|
|
412
506
|
/>
|
|
413
507
|
);
|
|
414
508
|
|
|
@@ -434,6 +528,7 @@ export const Table = <TData extends RowData>({
|
|
|
434
528
|
/>
|
|
435
529
|
)}
|
|
436
530
|
<div
|
|
531
|
+
id={`${uid}-table-container`}
|
|
437
532
|
className={cx(`${rootClassName}__container`, {
|
|
438
533
|
[`${rootClassName}__container--scrolled`]: isScrolled,
|
|
439
534
|
})}
|