@purpurds/table 8.9.0 → 8.10.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@purpurds/table",
3
- "version": "8.9.0",
3
+ "version": "8.10.1",
4
4
  "license": "AGPL-3.0-only",
5
5
  "main": "./dist/table.cjs.js",
6
6
  "types": "./dist/table.d.ts",
@@ -21,22 +21,22 @@
21
21
  "@dnd-kit/utilities": "~3.2.2",
22
22
  "@tanstack/react-table": "~8.21.2",
23
23
  "classnames": "~2.5.1",
24
- "@purpurds/badge": "8.9.0",
25
- "@purpurds/button": "8.9.0",
26
- "@purpurds/cta-link": "8.9.0",
27
- "@purpurds/drawer": "8.9.0",
28
- "@purpurds/checkbox": "8.9.0",
29
- "@purpurds/heading": "8.9.0",
30
- "@purpurds/icon": "8.9.0",
31
- "@purpurds/link": "8.9.0",
32
- "@purpurds/select": "8.9.0",
33
- "@purpurds/skeleton": "8.9.0",
34
- "@purpurds/paragraph": "8.9.0",
35
- "@purpurds/text-field": "8.9.0",
36
- "@purpurds/tokens": "8.9.0",
37
- "@purpurds/toggle": "8.9.0",
38
- "@purpurds/visually-hidden": "8.9.0",
39
- "@purpurds/tooltip": "8.9.0"
24
+ "@purpurds/checkbox": "8.10.1",
25
+ "@purpurds/cta-link": "8.10.1",
26
+ "@purpurds/drawer": "8.10.1",
27
+ "@purpurds/badge": "8.10.1",
28
+ "@purpurds/button": "8.10.1",
29
+ "@purpurds/heading": "8.10.1",
30
+ "@purpurds/link": "8.10.1",
31
+ "@purpurds/paragraph": "8.10.1",
32
+ "@purpurds/select": "8.10.1",
33
+ "@purpurds/skeleton": "8.10.1",
34
+ "@purpurds/toggle": "8.10.1",
35
+ "@purpurds/icon": "8.10.1",
36
+ "@purpurds/text-field": "8.10.1",
37
+ "@purpurds/tooltip": "8.10.1",
38
+ "@purpurds/tokens": "8.10.1",
39
+ "@purpurds/visually-hidden": "8.10.1"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@rushstack/eslint-patch": "~1.10.0",
@@ -60,13 +60,13 @@
60
60
  "vitest-axe": "~0.1.0",
61
61
  "vitest-canvas-mock": "~0.3.3",
62
62
  "vitest": "^4.0.10",
63
- "@purpurds/autocomplete": "8.9.0",
64
- "@purpurds/grid": "8.9.0",
63
+ "@purpurds/autocomplete": "8.10.1",
64
+ "@purpurds/grid": "8.10.1",
65
65
  "@purpurds/component-rig": "1.0.0",
66
- "@purpurds/illustrative-icon": "8.9.0",
67
- "@purpurds/label": "8.9.0",
68
- "@purpurds/listbox": "8.9.0",
69
- "@purpurds/pagination": "8.9.0"
66
+ "@purpurds/illustrative-icon": "8.10.1",
67
+ "@purpurds/label": "8.10.1",
68
+ "@purpurds/listbox": "8.10.1",
69
+ "@purpurds/pagination": "8.10.1"
70
70
  },
71
71
  "scripts": {
72
72
  "build:dev": "vite",
@@ -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
- }: EmptyTableProps) => (
30
- <TableRow>
31
- <TableRowCell
32
- colSpan={colSpan}
33
- isLastRow={true}
34
- isFirstCell={true}
35
- isLastCell={true}
36
- enableColumnDrag={false}
37
- >
38
- <div
39
- className={cx([
40
- `${rootClassName}__empty-section`,
41
- `${rootClassName}__empty-section--${variant}`,
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
- {icon && <div className={cx(`${rootClassName}__empty-section__icon`)}>{icon}</div>}
45
- <div className={cx(`${rootClassName}__empty-section__texts`)}>
46
- <Heading data-testid="purpur-table-empty-table-title" variant="title-100" tag={tag}>
47
- {title}
48
- </Heading>
49
- <Paragraph data-testid="purpur-table-empty-table-description">{description}</Paragraph>
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
- </div>
52
- </TableRowCell>
53
- </TableRow>
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
  }
@@ -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) => (
@@ -45,7 +45,7 @@ type TableRowCellProps<TData extends RowData> = {
45
45
  isLastCell: boolean;
46
46
  enableColumnDrag: boolean;
47
47
  draggingActive?: boolean;
48
- enableRowDrag?: boolean;
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
- enableRowDrag,
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",
@@ -63,7 +63,7 @@ $border-width: 1px;
63
63
 
64
64
  div#{$root}__toggle {
65
65
  justify-content: space-between;
66
- width: 100%;
66
+ min-width: 100%;
67
67
  }
68
68
 
69
69
  &__draggable-item {
@@ -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);
@@ -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
- stickyFirstColumn: stickyFirstColumnProp = true,
113
- stickyHeaders: stickyHeadersProp = true,
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
- const [showColumnFiltersEnabled, setShowColumnFiltersEnabled] = useState(
132
- Boolean(props.enableFilters)
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 [stickyFirstColumn, setStickyFirstColumn] = useState(stickyFirstColumnProp);
135
- const [stickyHeaders, setStickyHeaders] = useState(stickyHeadersProp);
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(stickyFirstColumnProp);
303
- setStickyHeaders(stickyHeadersProp);
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
  })}