@purpurds/table 8.3.0 → 8.4.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.
Files changed (82) hide show
  1. package/dist/LICENSE.txt +205 -35
  2. package/dist/drag-indicator-circle.d.ts +13 -0
  3. package/dist/drag-indicator-circle.d.ts.map +1 -0
  4. package/dist/draggable-table.d.ts +23 -0
  5. package/dist/draggable-table.d.ts.map +1 -0
  6. package/dist/empty-table.d.ts +14 -0
  7. package/dist/empty-table.d.ts.map +1 -0
  8. package/dist/loading-table-rows.d.ts +13 -0
  9. package/dist/loading-table-rows.d.ts.map +1 -0
  10. package/dist/styles.css +1 -1
  11. package/dist/table-body.d.ts +2 -2
  12. package/dist/table-body.d.ts.map +1 -1
  13. package/dist/table-column-header-cell.d.ts +15 -2
  14. package/dist/table-column-header-cell.d.ts.map +1 -1
  15. package/dist/table-content.d.ts +42 -0
  16. package/dist/table-content.d.ts.map +1 -0
  17. package/dist/table-headers.d.ts +28 -0
  18. package/dist/table-headers.d.ts.map +1 -0
  19. package/dist/table-row-cell-skeleton.d.ts +1 -1
  20. package/dist/table-row-cell-skeleton.d.ts.map +1 -1
  21. package/dist/table-row-cell.d.ts +5 -2
  22. package/dist/table-row-cell.d.ts.map +1 -1
  23. package/dist/table-row.d.ts +2 -2
  24. package/dist/table-row.d.ts.map +1 -1
  25. package/dist/table-settings-drawer.d.ts +44 -11
  26. package/dist/table-settings-drawer.d.ts.map +1 -1
  27. package/dist/table.cjs.js +89 -85
  28. package/dist/table.cjs.js.map +1 -1
  29. package/dist/table.d.ts +3 -3
  30. package/dist/table.d.ts.map +1 -1
  31. package/dist/table.es.js +14397 -10195
  32. package/dist/table.es.js.map +1 -1
  33. package/dist/test-utils/helpers.d.ts +1 -0
  34. package/dist/test-utils/helpers.d.ts.map +1 -1
  35. package/dist/types.d.ts +23 -2
  36. package/dist/types.d.ts.map +1 -1
  37. package/dist/use-drag-handle.hook.d.ts +15 -0
  38. package/dist/use-drag-handle.hook.d.ts.map +1 -0
  39. package/dist/use-drag-indicator-position.hook.d.ts +19 -0
  40. package/dist/use-drag-indicator-position.hook.d.ts.map +1 -0
  41. package/dist/use-drop-indicator.hook.d.ts +15 -0
  42. package/dist/use-drop-indicator.hook.d.ts.map +1 -0
  43. package/dist/use-element-visibility.hook.d.ts +4 -0
  44. package/dist/use-element-visibility.hook.d.ts.map +1 -0
  45. package/dist/use-table-scroll.hook.d.ts +6 -0
  46. package/dist/use-table-scroll.hook.d.ts.map +1 -0
  47. package/dist/utils/custom-keyboard-coordinates.d.ts +8 -0
  48. package/dist/utils/custom-keyboard-coordinates.d.ts.map +1 -0
  49. package/package.json +27 -23
  50. package/src/drag-indicator-circle.tsx +36 -0
  51. package/src/draggable-table.test.tsx +381 -0
  52. package/src/draggable-table.tsx +191 -0
  53. package/src/empty-table.tsx +54 -0
  54. package/src/loading-table-rows.tsx +41 -0
  55. package/src/table-body.tsx +1 -3
  56. package/src/table-column-header-cell.tsx +135 -64
  57. package/src/table-content-drag.test.tsx +505 -0
  58. package/src/table-content.tsx +165 -0
  59. package/src/table-dnd-integration.test.tsx +425 -0
  60. package/src/table-drag-and-drop.test.tsx +276 -0
  61. package/src/table-headers.tsx +118 -0
  62. package/src/table-row-cell-skeleton.tsx +1 -1
  63. package/src/table-row-cell.test.tsx +2 -1
  64. package/src/table-row-cell.tsx +42 -31
  65. package/src/table-row.tsx +1 -3
  66. package/src/table-settings-drawer.module.scss +165 -2
  67. package/src/table-settings-drawer.test.tsx +0 -99
  68. package/src/table-settings-drawer.tsx +359 -53
  69. package/src/table.module.scss +191 -30
  70. package/src/table.stories.tsx +60 -4
  71. package/src/table.test.tsx +5 -1
  72. package/src/table.tsx +255 -213
  73. package/src/test-utils/helpers.ts +2 -0
  74. package/src/types.ts +25 -2
  75. package/src/use-drag-handle.hook.tsx +60 -0
  76. package/src/use-drag-handle.test.tsx +380 -0
  77. package/src/use-drag-indicator-position.hook.ts +74 -0
  78. package/src/use-drop-indicator.hook.ts +46 -0
  79. package/src/use-element-visibility.hook.ts +28 -0
  80. package/src/use-table-scroll.hook.tsx +30 -0
  81. package/src/utils/custom-keyboard-coordinates.ts +83 -0
  82. package/vitest.setup.ts +1 -1
package/src/table.tsx CHANGED
@@ -1,4 +1,5 @@
1
- import React, { type ReactElement, useEffect, useId, useRef, useState } from "react";
1
+ import React, { type ReactElement, useEffect, useId, useMemo, useRef, useState } from "react";
2
+ import type { DragEndEvent, UniqueIdentifier } from "@dnd-kit/core";
2
3
  import type {
3
4
  ColumnDef,
4
5
  ColumnFiltersState,
@@ -18,11 +19,11 @@ import {
18
19
  useReactTable,
19
20
  } from "@tanstack/react-table";
20
21
  export { createColumnHelper } from "@tanstack/react-table";
21
- import { Heading, type HeadingTagType } from "@purpurds/heading";
22
22
  import { type PaginationProps } from "@purpurds/pagination";
23
- import { Paragraph } from "@purpurds/paragraph";
24
23
  import c from "classnames/bind";
25
24
 
25
+ import { DraggableTable } from "./draggable-table";
26
+
26
27
  export type {
27
28
  ColumnDef,
28
29
  ColumnFiltersState,
@@ -37,29 +38,28 @@ export type {
37
38
 
38
39
  import styles from "./table.module.scss";
39
40
  import { TableActionBar } from "./table-action-bar";
40
- import TableBody from "./table-body";
41
- import { TableColumnHeaderCell } from "./table-column-header-cell";
41
+ import { EmptyTableContent, LoadingTableContent, NormalTableContent } from "./table-content";
42
42
  import { TableExportDrawer } from "./table-export-drawer";
43
- import { TableHeader } from "./table-header";
44
- import TableRow from "./table-row";
45
- import TableRowCell from "./table-row-cell";
46
- import TableRowCellSkeleton from "./table-row-cell-skeleton";
47
- import { TableSettingsDrawer } from "./table-settings-drawer";
43
+ import { SortableTableHeaders, StandardTableHeaders } from "./table-headers";
44
+ import { TableSettingsDrawer, type TableSettingsDrawerCopyProps } from "./table-settings-drawer";
48
45
  import { TableToolbar } from "./table-toolbar";
49
- import {
50
- type WithActionBarProps,
51
- type WithEmptyTableProps,
52
- type WithLoadingProps,
53
- type WithoutActionBarProps,
54
- type WithoutEmptyTableProps,
55
- type WithoutLoadingProps,
56
- type WithoutRowSelectionProps,
57
- type WithoutSortingProps,
58
- type WithoutToolbarProps,
59
- type WithRowSelectionProps,
60
- type WithSortingProps,
61
- type WithToolbarProps,
46
+ import type {
47
+ WithActionBarProps,
48
+ WithColumnDragProps,
49
+ WithEmptyTableProps,
50
+ WithLoadingProps,
51
+ WithoutActionBarProps,
52
+ WithoutColumnDragProps,
53
+ WithoutEmptyTableProps,
54
+ WithoutLoadingProps,
55
+ WithoutRowSelectionProps,
56
+ WithoutSortingProps,
57
+ WithoutToolbarProps,
58
+ WithRowSelectionProps,
59
+ WithSortingProps,
60
+ WithToolbarProps,
62
61
  } from "./types";
62
+ import { useTableScroll } from "./use-table-scroll.hook";
63
63
  import { filterOnFilterKey, sortOnBadgeValue, sortOnBadgeVariant } from "./utils/custom-functions";
64
64
 
65
65
  const cx = c.bind(styles);
@@ -81,6 +81,7 @@ export type TableProps<TData extends RowData> = {
81
81
  (WithEmptyTableProps | WithoutEmptyTableProps) &
82
82
  (WithoutLoadingProps | WithLoadingProps) &
83
83
  (WithoutRowSelectionProps | WithRowSelectionProps<TData>) &
84
+ (WithColumnDragProps | WithoutColumnDragProps) &
84
85
  Partial<TableOptions<TData>>;
85
86
 
86
87
  const rootClassName = "purpur-table";
@@ -120,10 +121,10 @@ export const Table = <TData extends RowData>({
120
121
  onSecondaryButtonClick,
121
122
  setShowOnlySelectedRows,
122
123
  drawerZIndex = 6,
124
+ enableColumnDrag,
125
+ columnDragAriaLabelsCopy,
123
126
  ...props
124
127
  }: TableProps<TData>) => {
125
- const [selectedRowsCount, setSelectedRowsCount] = useState(0);
126
- const [actionBarVisible, setActionBarVisible] = useState(false);
127
128
  const [isSettingsDrawerOpen, setSettingsDrawerIsOpen] = useState(false);
128
129
  const [isExportDrawerOpen, setExportDrawerIsOpen] = useState(false);
129
130
  const [showColumnFiltersEnabled, setShowColumnFiltersEnabled] = useState(
@@ -132,10 +133,11 @@ export const Table = <TData extends RowData>({
132
133
  const [stickyFirstColumn, setStickyFirstColumn] = useState(stickyFirstColumnProp);
133
134
  const [stickyHeaders, setStickyHeaders] = useState(stickyHeadersProp);
134
135
  const prevShowOnlySelectedRows = useRef(showOnlySelectedRows);
135
- const [isScrolled, setIsScrolled] = useState(false);
136
136
  const tableContainerRef = useRef<HTMLTableElement>(null);
137
137
  const uid = useId();
138
- const [rowSelectionEnabled, setRowSelectionEnabled] = useState(true);
138
+ const isScrolled = useTableScroll(tableContainerRef);
139
+
140
+ const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
139
141
 
140
142
  const classes = cx([
141
143
  className,
@@ -146,13 +148,13 @@ export const Table = <TData extends RowData>({
146
148
  },
147
149
  ]);
148
150
 
149
- // Only add row selection columns when both enableRowSelection is true and rowSelectionEnabled is true
150
- if (props.enableRowSelection && rowSelectionEnabled) {
151
+ // Only add row selection columns when enableRowSelection is true
152
+ if (props.enableRowSelection) {
151
153
  columns = props.enableMultiRowSelection
152
154
  ? [
153
155
  {
154
156
  id: "row-selection",
155
- header: "Row selection",
157
+ header: rowSelectionAriaLabels?.header,
156
158
  size: 25,
157
159
  meta: {
158
160
  cellType: "rowSelection",
@@ -164,7 +166,7 @@ export const Table = <TData extends RowData>({
164
166
  : [
165
167
  {
166
168
  id: "row-toggle",
167
- header: "Row toggle",
169
+ header: rowSelectionAriaLabels?.header,
168
170
  size: 25,
169
171
  meta: {
170
172
  cellType: "rowToggle",
@@ -202,54 +204,62 @@ export const Table = <TData extends RowData>({
202
204
  ...restProps,
203
205
  });
204
206
 
207
+ const rowCount = useMemo(() => tanstackTable.getRowCount(), [tanstackTable]);
208
+
205
209
  useEffect(() => {
206
- const handleScroll = () => {
207
- if (tableContainerRef.current) {
208
- const scrollLeft = tableContainerRef.current.scrollLeft;
209
- setIsScrolled(scrollLeft > 0);
210
- }
211
- };
210
+ if (onRowsCountChange) {
211
+ onRowsCountChange(rowCount);
212
+ }
213
+ }, [rowCount, onRowsCountChange]);
212
214
 
213
- const container = tableContainerRef.current;
214
- if (container) {
215
- container.addEventListener("scroll", handleScroll);
215
+ // Handle drag end - Only used when DnD is enabled
216
+ const handleDragEnd = (event: DragEndEvent) => {
217
+ const { active, over } = event;
218
+
219
+ // Don't allow moving row selection columns
220
+ if (active.id === "row-selection" || active.id === "row-toggle") {
221
+ setActiveId(null);
222
+ return;
216
223
  }
217
224
 
218
- return () => {
219
- if (container) {
220
- container.removeEventListener("scroll", handleScroll);
221
- }
222
- };
223
- }, []);
225
+ // Don't allow dropping over row selection columns
226
+ if (over && (over.id === "row-selection" || over.id === "row-toggle")) {
227
+ setActiveId(null);
228
+ return;
229
+ }
224
230
 
225
- useEffect(() => {
226
- if (showOnlySelectedRows) {
227
- if (!prevShowOnlySelectedRows.current) {
228
- tanstackTable.setPageIndex(0);
229
- }
230
- prevShowOnlySelectedRows.current = showOnlySelectedRows;
231
- } else {
232
- if (prevShowOnlySelectedRows.current) {
233
- tanstackTable.setPageIndex(0);
231
+ if (over && active.id !== over.id) {
232
+ const currentOrder = tanstackTable.getAllLeafColumns().map((column) => column.id);
233
+ const oldIndex = currentOrder.indexOf(String(active.id));
234
+ const newIndex = currentOrder.indexOf(String(over.id));
235
+
236
+ if (oldIndex !== -1 && newIndex !== -1) {
237
+ const newOrder = [...currentOrder];
238
+ const [removed] = newOrder.splice(oldIndex, 1);
239
+ newOrder.splice(newIndex, 0, removed);
240
+ tanstackTable.setColumnOrder(newOrder);
234
241
  }
235
- prevShowOnlySelectedRows.current = showOnlySelectedRows;
236
242
  }
237
- }, [showOnlySelectedRows, tanstackTable]);
238
243
 
239
- useEffect(() => {
240
- if (onRowsCountChange) {
241
- onRowsCountChange(tanstackTable.getRowCount());
242
- }
243
- }, [state, onRowsCountChange, tanstackTable]);
244
+ setActiveId(null);
245
+ };
244
246
 
245
247
  useEffect(() => {
246
- if (state?.rowSelection) {
247
- const newLength = Object.keys(state.rowSelection).length;
248
- setSelectedRowsCount(newLength);
249
- setActionBarVisible(newLength > 0);
248
+ if (showOnlySelectedRows !== prevShowOnlySelectedRows.current) {
249
+ tanstackTable.setPageIndex(0);
250
+ prevShowOnlySelectedRows.current = showOnlySelectedRows;
250
251
  }
252
+ }, [showOnlySelectedRows, tanstackTable]);
253
+
254
+ const selectedRowsData = useMemo(() => {
255
+ if (!state?.rowSelection) return { count: 0, visible: false };
256
+ const count = Object.keys(state.rowSelection).length;
257
+ return { count, visible: count > 0 };
251
258
  }, [state?.rowSelection]);
252
259
 
260
+ const selectedRowsCount = selectedRowsData.count;
261
+ const actionBarVisible = selectedRowsData.visible;
262
+
253
263
  const handleCancelSelection = () => {
254
264
  tanstackTable.resetRowSelection();
255
265
  setShowOnlySelectedRows?.(false);
@@ -261,11 +271,14 @@ export const Table = <TData extends RowData>({
261
271
  }
262
272
  };
263
273
 
264
- const getStickyColumn = (index: number) => {
265
- // When rowSelectionEnabled is true, the first two columns are sticky
266
- // Otherwise, only the first column is sticky
267
- return props.enableRowSelection && rowSelectionEnabled ? index <= 1 : index === 0;
268
- };
274
+ const getStickyColumn = React.useCallback(
275
+ (index: number) => {
276
+ // When rowSelectionEnabled is true, the first two columns are sticky
277
+ // Otherwise, only the first column is sticky
278
+ return props.enableRowSelection ? index <= 1 : index === 0;
279
+ },
280
+ [props.enableRowSelection]
281
+ );
269
282
 
270
283
  const handleResetColumnFilters = () => {
271
284
  tanstackTable.resetColumnFilters();
@@ -284,62 +297,159 @@ export const Table = <TData extends RowData>({
284
297
  };
285
298
 
286
299
  const handleResetSettings = () => {
287
- setShowColumnFiltersEnabled(true);
288
- setStickyFirstColumn(true);
289
- setStickyHeaders(true);
290
-
300
+ setShowColumnFiltersEnabled(Boolean(props.enableFilters));
301
+ setStickyFirstColumn(stickyFirstColumnProp);
302
+ setStickyHeaders(stickyHeadersProp);
303
+ tanstackTable.resetColumnOrder();
291
304
  tanstackTable.resetColumnVisibility();
292
305
  };
293
306
 
294
- const showBorder = (index: number) => {
295
- // Only show the border for sticky columns when the table is scrolled
296
- return isScrolled && stickyFirstColumn && getStickyColumn(index);
297
- };
307
+ const showBorder = React.useCallback(
308
+ (index: number) => {
309
+ // Only show the border for sticky columns when the table is scrolled
310
+ return isScrolled && stickyFirstColumn && getStickyColumn(index);
311
+ },
312
+ [isScrolled, stickyFirstColumn, getStickyColumn]
313
+ );
298
314
 
299
315
  const tableRows = tanstackTable.getRowModel().rows;
300
316
  const emptyTable = tableRows.length === 0 && Boolean(emptyTableCopy);
301
317
 
302
- const getColumnWidths = (): (string | number)[] => {
318
+ const memoizedGetColumnWidths = React.useCallback(() => {
303
319
  return tanstackTable.getAllColumns().map((column) => column.getSize() || "100%");
304
- };
320
+ }, [tanstackTable]);
321
+
322
+ // Render table headers with or without drag functionality
323
+ const renderTableHeaders = React.useCallback(() => {
324
+ return tanstackTable.getHeaderGroups().map((headerGroup) => {
325
+ if (enableColumnDrag && state?.columnOrder) {
326
+ return (
327
+ <SortableTableHeaders
328
+ key={headerGroup.id}
329
+ headerGroup={headerGroup}
330
+ tanstackTable={tanstackTable}
331
+ tableHasFilters={showColumnFiltersEnabled}
332
+ emptyTable={emptyTable}
333
+ stickyFirstColumn={stickyFirstColumn}
334
+ stickyHeaders={stickyHeaders}
335
+ isScrolled={isScrolled}
336
+ getStickyColumn={getStickyColumn}
337
+ showBorder={showBorder}
338
+ enableSorting={props.enableSorting || false}
339
+ sortingAriaLabels={sortingAriaLabels}
340
+ columnOrder={state.columnOrder}
341
+ activeId={activeId}
342
+ enableColumnDrag={enableColumnDrag}
343
+ columnDragAriaLabelsCopy={columnDragAriaLabelsCopy}
344
+ />
345
+ );
346
+ }
305
347
 
306
- const tableBodyContent =
307
- loading && skeletonRows ? (
308
- <LoadingTableRows
309
- rowCount={skeletonRows}
310
- getStickyColumn={getStickyColumn}
348
+ return (
349
+ <StandardTableHeaders
350
+ key={headerGroup.id}
351
+ headerGroup={headerGroup}
352
+ tanstackTable={tanstackTable}
353
+ tableHasFilters={showColumnFiltersEnabled}
354
+ emptyTable={emptyTable}
355
+ stickyFirstColumn={stickyFirstColumn}
356
+ stickyHeaders={stickyHeaders}
357
+ isScrolled={isScrolled}
358
+ getStickyColumn={getStickyColumn}
359
+ showBorder={showBorder}
360
+ enableSorting={props.enableSorting || false}
361
+ sortingAriaLabels={sortingAriaLabels}
362
+ />
363
+ );
364
+ });
365
+ }, [
366
+ activeId,
367
+ columnDragAriaLabelsCopy,
368
+ emptyTable,
369
+ enableColumnDrag,
370
+ getStickyColumn,
371
+ isScrolled,
372
+ props.enableSorting,
373
+ showBorder,
374
+ showColumnFiltersEnabled,
375
+ sortingAriaLabels,
376
+ state?.columnOrder,
377
+ stickyFirstColumn,
378
+ stickyHeaders,
379
+ tanstackTable,
380
+ ]);
381
+
382
+ const renderTableContent = React.useCallback(() => {
383
+ if (loading && skeletonRows) {
384
+ return (
385
+ <LoadingTableContent
386
+ tanstackTable={tanstackTable}
387
+ tableRows={tableRows}
388
+ showColumnFiltersEnabled={showColumnFiltersEnabled}
389
+ fullWidth={fullWidth}
390
+ renderTableHeaders={renderTableHeaders}
391
+ skeletonRows={skeletonRows}
392
+ getStickyColumn={getStickyColumn}
393
+ stickyFirstColumn={stickyFirstColumn}
394
+ isScrolled={isScrolled}
395
+ showBorder={showBorder}
396
+ getColumnWidths={memoizedGetColumnWidths}
397
+ />
398
+ );
399
+ }
400
+
401
+ const isEmptyTable = tableRows.length === 0 && Boolean(emptyTableCopy);
402
+ if (isEmptyTable && emptyTableCopy && emptyTableHeadingTag) {
403
+ return (
404
+ <EmptyTableContent
405
+ tanstackTable={tanstackTable}
406
+ tableRows={tableRows}
407
+ showColumnFiltersEnabled={showColumnFiltersEnabled}
408
+ fullWidth={fullWidth}
409
+ renderTableHeaders={renderTableHeaders}
410
+ variant={variant}
411
+ emptyTableHeadingTag={emptyTableHeadingTag}
412
+ emptyTableCopy={emptyTableCopy}
413
+ emptyTableIcon={emptyTableIcon}
414
+ />
415
+ );
416
+ }
417
+
418
+ return (
419
+ <NormalTableContent
420
+ tanstackTable={tanstackTable}
421
+ tableRows={tableRows}
422
+ showColumnFiltersEnabled={showColumnFiltersEnabled}
423
+ fullWidth={fullWidth}
424
+ renderTableHeaders={renderTableHeaders}
311
425
  stickyFirstColumn={stickyFirstColumn}
426
+ getStickyColumn={getStickyColumn}
312
427
  isScrolled={isScrolled}
313
- cellWidths={getColumnWidths()}
314
428
  showBorder={showBorder}
429
+ enableColumnDrag={enableColumnDrag || false}
430
+ activeId={activeId}
315
431
  />
316
- ) : emptyTable && emptyTableCopy && emptyTableHeadingTag ? (
317
- <EmptyTable
318
- variant={variant}
319
- tag={emptyTableHeadingTag}
320
- title={emptyTableCopy.title}
321
- description={emptyTableCopy.description}
322
- colSpan={tanstackTable.getVisibleLeafColumns().length}
323
- icon={emptyTableIcon}
324
- />
325
- ) : (
326
- tableRows.map((row, rowIndex) => (
327
- <TableRow key={row.id} isSelected={row.getIsSelected()}>
328
- {row.getVisibleCells().map((cell, cellIndex) => (
329
- <TableRowCell
330
- key={cell.id}
331
- cell={cell}
332
- isLastRow={rowIndex === tableRows.length - 1}
333
- isFirstCell={cellIndex === 0}
334
- isLastCell={cellIndex === row.getVisibleCells().length - 1}
335
- stickyColumn={stickyFirstColumn && getStickyColumn(cellIndex)}
336
- isScrolled={isScrolled}
337
- showBorder={showBorder(cellIndex)}
338
- />
339
- ))}
340
- </TableRow>
341
- ))
342
432
  );
433
+ }, [
434
+ activeId,
435
+ emptyTableCopy,
436
+ emptyTableHeadingTag,
437
+ emptyTableIcon,
438
+ enableColumnDrag,
439
+ fullWidth,
440
+ getStickyColumn,
441
+ isScrolled,
442
+ loading,
443
+ memoizedGetColumnWidths,
444
+ renderTableHeaders,
445
+ showBorder,
446
+ showColumnFiltersEnabled,
447
+ skeletonRows,
448
+ stickyFirstColumn,
449
+ tableRows,
450
+ tanstackTable,
451
+ variant,
452
+ ]);
343
453
 
344
454
  return (
345
455
  <div id={`${uid}-table`} className={classes}>
@@ -368,34 +478,24 @@ export const Table = <TData extends RowData>({
368
478
  })}
369
479
  ref={tableContainerRef}
370
480
  >
371
- <table
372
- className={cx([
373
- `${rootClassName}__table`,
374
- { [`${rootClassName}__table--full-width`]: fullWidth },
375
- ])}
376
- >
377
- <TableHeader columnFiltersEnabled={showColumnFiltersEnabled}>
378
- {tanstackTable.getHeaderGroups().map((headerGroup) => (
379
- <TableRow key={headerGroup.id}>
380
- {headerGroup.headers.map((header, index) => (
381
- <TableColumnHeaderCell
382
- key={header.id}
383
- header={header}
384
- tanstackTable={tanstackTable}
385
- stickyColumn={!emptyTable && stickyFirstColumn && getStickyColumn(index)}
386
- stickyHeaders={!emptyTable && stickyHeaders}
387
- isScrolled={isScrolled}
388
- showBorder={showBorder(index)}
389
- {...(props.enableSorting && sortingAriaLabels
390
- ? { enableSorting: props.enableSorting, sortingAriaLabels }
391
- : { enableSorting: false })}
392
- />
393
- ))}
394
- </TableRow>
395
- ))}
396
- </TableHeader>
397
- <TableBody>{tableBodyContent}</TableBody>
398
- </table>
481
+ {enableColumnDrag && columnDragAriaLabelsCopy ? (
482
+ <DraggableTable
483
+ activeId={activeId}
484
+ setActiveId={setActiveId}
485
+ handleDragEnd={handleDragEnd}
486
+ tableRows={tableRows}
487
+ tanstackTable={tanstackTable}
488
+ variant={variant}
489
+ fullWidth={fullWidth}
490
+ showColumnFiltersEnabled={showColumnFiltersEnabled}
491
+ columnDragAriaLabelsCopy={columnDragAriaLabelsCopy}
492
+ rootClassName={rootClassName}
493
+ >
494
+ {renderTableContent()}
495
+ </DraggableTable>
496
+ ) : (
497
+ renderTableContent()
498
+ )}
399
499
  </div>
400
500
  {paginationComponent}
401
501
  {enableActionBar && actionBarTotalRowCount && actionbarCopy && onPrimaryButtonClick && (
@@ -417,18 +517,27 @@ export const Table = <TData extends RowData>({
417
517
  setShowColumnFiltersEnabled={setShowColumnFiltersEnabled}
418
518
  setStickyFirstColumn={setStickyFirstColumn}
419
519
  setStickyHeaders={setStickyHeaders}
420
- getAllColumns={tanstackTable.getAllColumns}
520
+ getAllColumns={tanstackTable.getAllLeafColumns}
421
521
  isDrawerOpen={isSettingsDrawerOpen}
422
522
  showColumnFilters={showColumnFiltersEnabled}
423
523
  stickyFirstColumn={stickyFirstColumn}
424
524
  stickyHeaders={stickyHeaders}
425
- copy={settingsDrawerCopy}
426
525
  onResetSettings={handleResetSettings}
427
526
  columnFiltersEnabled={Boolean(props.enableFilters)}
428
- enableRowSelection={Boolean(props.enableRowSelection)}
429
- rowSelectionEnabled={rowSelectionEnabled}
430
- setRowSelectionEnabled={setRowSelectionEnabled}
431
527
  zIndex={drawerZIndex}
528
+ {...(enableColumnDrag && state?.columnOrder
529
+ ? {
530
+ enableColumnDrag: true,
531
+ copy: settingsDrawerCopy as TableSettingsDrawerCopyProps<true>,
532
+ columnOrder: state.columnOrder,
533
+ onColumnOrderChange: (newOrder) => {
534
+ tanstackTable.setColumnOrder(newOrder);
535
+ },
536
+ }
537
+ : {
538
+ enableColumnDrag: false,
539
+ copy: settingsDrawerCopy as TableSettingsDrawerCopyProps<false>,
540
+ })}
432
541
  />
433
542
  )}
434
543
  {Array.isArray(exportFormats) && exportDrawerCopy && (
@@ -447,70 +556,3 @@ export const Table = <TData extends RowData>({
447
556
  };
448
557
 
449
558
  Table.displayName = "Table";
450
-
451
- type EmptyTableProps = {
452
- variant: "primary" | "secondary";
453
- tag: HeadingTagType;
454
- title: string;
455
- description: string;
456
- colSpan: number;
457
- icon: React.ReactNode;
458
- };
459
-
460
- const EmptyTable = ({ variant, tag, title, description, colSpan, icon }: EmptyTableProps) => (
461
- <TableRow>
462
- <TableRowCell colSpan={colSpan} isLastRow={true} isFirstCell={true} isLastCell={true}>
463
- <div
464
- className={cx([
465
- `${rootClassName}__empty-section`,
466
- `${rootClassName}__empty-section--${variant}`,
467
- ])}
468
- >
469
- {icon && <div className={cx(`${rootClassName}__empty-section__icon`)}>{icon}</div>}
470
- <div className={cx(`${rootClassName}__empty-section__texts`)}>
471
- <Heading data-testid="purpur-table-empty-table-title" variant="title-100" tag={tag}>
472
- {title}
473
- </Heading>
474
- <Paragraph data-testid="purpur-table-empty-table-description">{description}</Paragraph>
475
- </div>
476
- </div>
477
- </TableRowCell>
478
- </TableRow>
479
- );
480
-
481
- type LoadingTableRowsProps = {
482
- rowCount: number;
483
- isScrolled: boolean;
484
- stickyFirstColumn: boolean;
485
- getStickyColumn: (index: number) => boolean;
486
- cellWidths: (string | number)[];
487
- showBorder: (index: number) => boolean;
488
- };
489
-
490
- const LoadingTableRows = ({
491
- rowCount,
492
- getStickyColumn,
493
- stickyFirstColumn,
494
- isScrolled,
495
- cellWidths,
496
- showBorder,
497
- }: LoadingTableRowsProps) => (
498
- <>
499
- {Array.from({ length: rowCount }, (_value, index) => index).map((row, rowIndex) => (
500
- <TableRow key={`skeleton-row-${row}`}>
501
- {cellWidths.map((cellWidth, cellIndex) => (
502
- <TableRowCellSkeleton
503
- key={`skeleton-cell-${cellIndex}`}
504
- isLastRow={rowIndex === rowCount - 1}
505
- isFirstCell={cellIndex === 0}
506
- isLastCell={cellIndex === cellWidths.length - 1}
507
- stickyColumn={stickyFirstColumn && getStickyColumn(cellIndex)}
508
- isScrolled={isScrolled}
509
- showBorder={showBorder(cellIndex)}
510
- cellWidth={cellWidth}
511
- />
512
- ))}
513
- </TableRow>
514
- ))}
515
- </>
516
- );
@@ -186,6 +186,7 @@ export const copy = {
186
186
  },
187
187
  generalSettings: {
188
188
  header: "General settings",
189
+
189
190
  toggles: {
190
191
  lockFirstcolumn: "Lock first column",
191
192
  showFilters: "Show Filters",
@@ -195,6 +196,7 @@ export const copy = {
195
196
  title: "Table settings",
196
197
  visibleColumns: {
197
198
  header: "Visible columns",
199
+ description: "Change general table settings",
198
200
  },
199
201
  },
200
202
  sortingAriaLabels: {
package/src/types.ts CHANGED
@@ -320,6 +320,7 @@ export type WithoutToolbarProps = {
320
320
  toolbarCopy?: never;
321
321
  toolbarTotalRowCount?: never;
322
322
  exportDrawerCopy?: never;
323
+ enableColumnDrag?: boolean;
323
324
  };
324
325
 
325
326
  type WithFiltersProps = {
@@ -440,7 +441,6 @@ type WithSettingsDrawerProps = {
440
441
  /**
441
442
  * Optional properties for customizing the settings drawer copy.
442
443
  */
443
- settingsDrawerCopy: TableSettingsDrawerCopyProps;
444
444
  toolbarCopy: {
445
445
  buttons: {
446
446
  settings: string;
@@ -449,7 +449,10 @@ type WithSettingsDrawerProps = {
449
449
  settings: string;
450
450
  };
451
451
  };
452
- };
452
+ } & (
453
+ | (WithColumnDragProps & { settingsDrawerCopy: TableSettingsDrawerCopyProps<true> })
454
+ | (WithoutColumnDragProps & { settingsDrawerCopy?: TableSettingsDrawerCopyProps<false> })
455
+ );
453
456
 
454
457
  /**
455
458
  * Props for enabling a single export format in a data table component.
@@ -494,4 +497,24 @@ type WithoutSettingsDrawerProps = {
494
497
  settings?: undefined;
495
498
  };
496
499
  };
500
+ } & WithoutColumnDragProps;
501
+
502
+ /**
503
+ * Props for column drag feature
504
+ */
505
+ export type WithColumnDragProps = {
506
+ enableColumnDrag: true;
507
+ onColumnOrderChange: (newColumnOrder: string[]) => void;
508
+ columnDragAriaLabelsCopy: ColumnDragAriaLabelsCopy;
509
+ };
510
+
511
+ export type WithoutColumnDragProps = {
512
+ enableColumnDrag?: false | undefined;
513
+ onColumnOrderChange?: never;
514
+ columnDragAriaLabelsCopy?: never;
515
+ };
516
+
517
+ type ColumnDragAriaLabelsCopy = {
518
+ grab: string;
519
+ grabbing: string;
497
520
  };