@purpurds/table 8.6.0 → 8.7.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.
@@ -9,137 +9,53 @@ import { EmptyTable } from "./empty-table";
9
9
  import { LoadingTableRows } from "./loading-table-rows";
10
10
  import styles from "./table.module.scss";
11
11
  import { TableBody } from "./table-body";
12
- import { TableHeader } from "./table-header";
13
12
  import { TableRow } from "./table-row";
14
13
  import { TableRowCell } from "./table-row-cell";
15
14
 
16
15
  const cx = c.bind(styles);
17
16
  const rootClassName = "purpur-table";
18
17
 
19
- type BaseTableContentProps<TData extends RowData> = {
18
+ type TableContentProps<TData extends RowData> = {
20
19
  tanstackTable: Table<TData>;
21
20
  tableRows: Row<TData>[];
22
- showColumnFiltersEnabled: boolean;
23
21
  fullWidth: boolean;
24
- renderTableHeaders: () => React.ReactNode;
25
- };
26
-
27
- type NormalTableContentProps<TData extends RowData> = BaseTableContentProps<TData> & {
28
- stickyFirstColumn: boolean;
29
- getStickyColumn: (index: number) => boolean;
30
- isScrolled: boolean;
31
- showBorder: (index: number) => boolean;
32
- enableColumnDrag: boolean;
33
- activeId: UniqueIdentifier | null;
22
+ tableHeader: React.ReactNode;
23
+ loading?: boolean;
24
+ skeletonRows?: number;
25
+ isEmptyTable?: boolean;
26
+ stickyFirstColumn?: boolean;
27
+ getStickyColumn?: (index: number) => boolean;
28
+ isScrolled?: boolean;
29
+ showBorder?: (index: number) => boolean;
30
+ enableColumnDrag?: boolean;
31
+ activeId?: UniqueIdentifier | null;
32
+ getColumnWidths?: () => (string | number)[];
33
+ variant?: "primary" | "secondary";
34
+ emptyTableHeadingTag?: HeadingTagType;
35
+ emptyTableCopy?: { title: string; description: string };
36
+ emptyTableIcon?: React.ReactNode;
34
37
  };
35
38
 
36
- export function NormalTableContent<TData extends RowData>({
39
+ export function TableContent<TData extends RowData>({
40
+ tanstackTable,
37
41
  tableRows,
38
- showColumnFiltersEnabled,
39
42
  fullWidth,
40
- renderTableHeaders,
43
+ tableHeader,
44
+ loading,
45
+ skeletonRows,
46
+ isEmptyTable,
41
47
  stickyFirstColumn,
42
48
  getStickyColumn,
43
49
  isScrolled,
44
50
  showBorder,
45
51
  enableColumnDrag,
46
52
  activeId,
47
- }: NormalTableContentProps<TData>) {
48
- return (
49
- <table
50
- className={cx([
51
- `${rootClassName}__table`,
52
- { [`${rootClassName}__table--full-width`]: fullWidth },
53
- ])}
54
- >
55
- <TableHeader columnFiltersEnabled={showColumnFiltersEnabled}>
56
- {renderTableHeaders()}
57
- </TableHeader>
58
- <TableBody>
59
- {tableRows.map((row, rowIndex) => (
60
- <TableRow key={row.id} isSelected={row.getIsSelected()}>
61
- {row.getVisibleCells().map((cell, cellIndex) => (
62
- <TableRowCell
63
- key={cell.id}
64
- cell={cell}
65
- isLastRow={rowIndex === tableRows.length - 1}
66
- isFirstCell={cellIndex === 0}
67
- isLastCell={cellIndex === row.getVisibleCells().length - 1}
68
- stickyColumn={stickyFirstColumn && getStickyColumn(cellIndex)}
69
- isScrolled={isScrolled}
70
- showBorder={showBorder(cellIndex)}
71
- enableColumnDrag={enableColumnDrag || false}
72
- draggingActive={activeId === cell.column.id}
73
- />
74
- ))}
75
- </TableRow>
76
- ))}
77
- </TableBody>
78
- </table>
79
- );
80
- }
81
-
82
- type LoadingTableContentProps<TData extends RowData> = BaseTableContentProps<TData> & {
83
- skeletonRows: number;
84
- getStickyColumn: (index: number) => boolean;
85
- stickyFirstColumn: boolean;
86
- isScrolled: boolean;
87
- showBorder: (index: number) => boolean;
88
- getColumnWidths: () => (string | number)[];
89
- };
90
-
91
- export function LoadingTableContent<TData extends RowData>({
92
- showColumnFiltersEnabled,
93
- fullWidth,
94
- renderTableHeaders,
95
- skeletonRows,
96
- getStickyColumn,
97
- stickyFirstColumn,
98
- isScrolled,
99
- showBorder,
100
53
  getColumnWidths,
101
- }: LoadingTableContentProps<TData>) {
102
- return (
103
- <table
104
- className={cx([
105
- `${rootClassName}__table`,
106
- { [`${rootClassName}__table--full-width`]: fullWidth },
107
- ])}
108
- >
109
- <TableHeader columnFiltersEnabled={showColumnFiltersEnabled}>
110
- {renderTableHeaders()}
111
- </TableHeader>
112
- <TableBody>
113
- <LoadingTableRows
114
- rowCount={skeletonRows}
115
- getStickyColumn={getStickyColumn}
116
- stickyFirstColumn={stickyFirstColumn}
117
- isScrolled={isScrolled}
118
- cellWidths={getColumnWidths()}
119
- showBorder={showBorder}
120
- />
121
- </TableBody>
122
- </table>
123
- );
124
- }
125
-
126
- type EmptyTableContentProps<TData extends RowData> = BaseTableContentProps<TData> & {
127
- variant: "primary" | "secondary";
128
- emptyTableHeadingTag: HeadingTagType;
129
- emptyTableCopy: { title: string; description: string };
130
- emptyTableIcon?: React.ReactNode;
131
- };
132
-
133
- export function EmptyTableContent<TData extends RowData>({
134
- tanstackTable,
135
- showColumnFiltersEnabled,
136
- fullWidth,
137
- renderTableHeaders,
138
54
  variant,
139
55
  emptyTableHeadingTag,
140
56
  emptyTableCopy,
141
57
  emptyTableIcon,
142
- }: EmptyTableContentProps<TData>) {
58
+ }: TableContentProps<TData>) {
143
59
  return (
144
60
  <table
145
61
  className={cx([
@@ -147,18 +63,46 @@ export function EmptyTableContent<TData extends RowData>({
147
63
  { [`${rootClassName}__table--full-width`]: fullWidth },
148
64
  ])}
149
65
  >
150
- <TableHeader columnFiltersEnabled={showColumnFiltersEnabled}>
151
- {renderTableHeaders()}
152
- </TableHeader>
66
+ {tableHeader}
153
67
  <TableBody>
154
- <EmptyTable
155
- variant={variant}
156
- tag={emptyTableHeadingTag}
157
- title={emptyTableCopy.title}
158
- description={emptyTableCopy.description}
159
- colSpan={tanstackTable.getVisibleLeafColumns().length}
160
- icon={emptyTableIcon}
161
- />
68
+ {loading && skeletonRows ? (
69
+ <LoadingTableRows
70
+ rowCount={skeletonRows}
71
+ getStickyColumn={getStickyColumn!}
72
+ stickyFirstColumn={stickyFirstColumn!}
73
+ isScrolled={isScrolled!}
74
+ cellWidths={getColumnWidths!()}
75
+ showBorder={showBorder!}
76
+ />
77
+ ) : isEmptyTable && emptyTableCopy && emptyTableHeadingTag ? (
78
+ <EmptyTable
79
+ variant={variant!}
80
+ tag={emptyTableHeadingTag}
81
+ title={emptyTableCopy.title}
82
+ description={emptyTableCopy.description}
83
+ colSpan={tanstackTable.getVisibleLeafColumns().length}
84
+ icon={emptyTableIcon}
85
+ />
86
+ ) : (
87
+ tableRows.map((row, rowIndex) => (
88
+ <TableRow key={row.id} isSelected={row.getIsSelected()}>
89
+ {row.getVisibleCells().map((cell, cellIndex) => (
90
+ <TableRowCell
91
+ key={cell.id}
92
+ cell={cell}
93
+ isLastRow={rowIndex === tableRows.length - 1}
94
+ isFirstCell={cellIndex === 0}
95
+ isLastCell={cellIndex === row.getVisibleCells().length - 1}
96
+ stickyColumn={stickyFirstColumn && getStickyColumn!(cellIndex)}
97
+ isScrolled={isScrolled!}
98
+ showBorder={showBorder!(cellIndex)}
99
+ enableColumnDrag={enableColumnDrag || false}
100
+ draggingActive={activeId === cell.column.id}
101
+ />
102
+ ))}
103
+ </TableRow>
104
+ ))
105
+ )}
162
106
  </TableBody>
163
107
  </table>
164
108
  );
@@ -489,7 +489,7 @@ const DraggableColumnItem = ({
489
489
  >
490
490
  <IconDragVertical className={cx(`${rootClassName}__draggable-handle-icon`)} size="sm" />
491
491
  </div>
492
- <span>{label}</span>
492
+ <Paragraph>{label}</Paragraph>
493
493
  <VisuallyHidden id={`drag-instructions-${id}`}>
494
494
  {copy.visibleColumns.ariaLabels.dragHandle.instructions}
495
495
  </VisuallyHidden>
@@ -403,6 +403,14 @@ manages filtering, sorting and pagination internally.
403
403
  stepNumberPrefix: "Go to page",
404
404
  };
405
405
 
406
+ const emptyTableProps = {
407
+ emptyTableCopy: {
408
+ title: args.emptyTableCopy?.title || "No data found",
409
+ description: args.emptyTableCopy?.description || "There are no items to display.",
410
+ },
411
+ emptyTableHeadingTag: args.emptyTableHeadingTag || "h2",
412
+ };
413
+
406
414
  return renderTableContainer(
407
415
  { ...args, isExpanded: tableExpanded },
408
416
  /* @ts-expect-error Props are incompatible. Storybook issue after adding argTypes. */
@@ -446,6 +454,9 @@ manages filtering, sorting and pagination internally.
446
454
  stickyHeaders={args.stickyHeaders}
447
455
  enableColumnDrag={args.enableColumnDrag}
448
456
  columnDragAriaLabelsCopy={commonColumnDragAriaLabels}
457
+ emptyTableCopy={emptyTableProps.emptyTableCopy}
458
+ emptyTableHeadingTag={emptyTableProps.emptyTableHeadingTag}
459
+ emptyTableIcon={<IllustrativeIconTableQuestionDuocolorStatic />}
449
460
  />
450
461
  );
451
462
  },
package/src/table.tsx CHANGED
@@ -38,8 +38,9 @@ export type {
38
38
 
39
39
  import styles from "./table.module.scss";
40
40
  import { TableActionBar } from "./table-action-bar";
41
- import { EmptyTableContent, LoadingTableContent, NormalTableContent } from "./table-content";
41
+ import { TableContent } from "./table-content";
42
42
  import { TableExportDrawer } from "./table-export-drawer";
43
+ import { TableHeader } from "./table-header";
43
44
  import { SortableTableHeaders, StandardTableHeaders } from "./table-headers";
44
45
  import { TableSettingsDrawer, type TableSettingsDrawerCopyProps } from "./table-settings-drawer";
45
46
  import { TableToolbar } from "./table-toolbar";
@@ -140,12 +141,12 @@ export const Table = <TData extends RowData>({
140
141
  const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
141
142
 
142
143
  const classes = cx([
143
- className,
144
144
  rootClassName,
145
145
  {
146
146
  [`${rootClassName}--${variant}`]: variant,
147
147
  [`${rootClassName}--without-toolbar`]: !enableToolbar || !settingsDrawerCopy,
148
148
  },
149
+ className,
149
150
  ]);
150
151
 
151
152
  // Only add row selection columns when enableRowSelection is true
@@ -316,140 +317,95 @@ export const Table = <TData extends RowData>({
316
317
  const emptyTable = tableRows.length === 0 && Boolean(emptyTableCopy);
317
318
 
318
319
  const memoizedGetColumnWidths = React.useCallback(() => {
319
- return tanstackTable.getAllColumns().map((column) => column.getSize() || "100%");
320
+ return tanstackTable.getVisibleLeafColumns().map((column) => column.getSize() || "100%");
320
321
  }, [tanstackTable]);
321
322
 
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
- }
347
-
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
- }
323
+ // Extract header groups to avoid complex expression in dependency array
324
+ const headerGroups = tanstackTable.getHeaderGroups();
325
+
326
+ // Memoized table header to prevent re-renders and maintain filter focus
327
+ const tableHeader = React.useMemo(
328
+ () => (
329
+ <TableHeader columnFiltersEnabled={showColumnFiltersEnabled}>
330
+ {headerGroups.map((headerGroup) => {
331
+ if (enableColumnDrag && state?.columnOrder) {
332
+ return (
333
+ <SortableTableHeaders
334
+ key={headerGroup.id}
335
+ headerGroup={headerGroup}
336
+ tanstackTable={tanstackTable}
337
+ tableHasFilters={showColumnFiltersEnabled}
338
+ emptyTable={emptyTable}
339
+ stickyFirstColumn={stickyFirstColumn}
340
+ stickyHeaders={stickyHeaders}
341
+ isScrolled={isScrolled}
342
+ getStickyColumn={getStickyColumn}
343
+ showBorder={showBorder}
344
+ enableSorting={props.enableSorting || false}
345
+ sortingAriaLabels={sortingAriaLabels}
346
+ columnOrder={state.columnOrder}
347
+ activeId={activeId}
348
+ enableColumnDrag={enableColumnDrag}
349
+ columnDragAriaLabelsCopy={columnDragAriaLabelsCopy}
350
+ />
351
+ );
352
+ }
353
+
354
+ return (
355
+ <StandardTableHeaders
356
+ key={headerGroup.id}
357
+ headerGroup={headerGroup}
358
+ tanstackTable={tanstackTable}
359
+ tableHasFilters={showColumnFiltersEnabled}
360
+ emptyTable={emptyTable}
361
+ stickyFirstColumn={stickyFirstColumn}
362
+ stickyHeaders={stickyHeaders}
363
+ isScrolled={isScrolled}
364
+ getStickyColumn={getStickyColumn}
365
+ showBorder={showBorder}
366
+ enableSorting={props.enableSorting || false}
367
+ sortingAriaLabels={sortingAriaLabels}
368
+ />
369
+ );
370
+ })}
371
+ </TableHeader>
372
+ ),
373
+ [
374
+ showColumnFiltersEnabled,
375
+ headerGroups,
376
+ enableColumnDrag,
377
+ state?.columnOrder,
378
+ props.enableSorting,
379
+ sortingAriaLabels,
380
+ columnDragAriaLabelsCopy,
381
+ tanstackTable,
382
+ ]
383
+ );
417
384
 
418
- return (
419
- <NormalTableContent
420
- tanstackTable={tanstackTable}
421
- tableRows={tableRows}
422
- showColumnFiltersEnabled={showColumnFiltersEnabled}
423
- fullWidth={fullWidth}
424
- renderTableHeaders={renderTableHeaders}
425
- stickyFirstColumn={stickyFirstColumn}
426
- getStickyColumn={getStickyColumn}
427
- isScrolled={isScrolled}
428
- showBorder={showBorder}
429
- enableColumnDrag={enableColumnDrag || false}
430
- activeId={activeId}
431
- />
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
- ]);
385
+ const isEmptyTable = tableRows.length === 0 && Boolean(emptyTableCopy);
386
+
387
+ const tableContent = (
388
+ <TableContent
389
+ tanstackTable={tanstackTable}
390
+ tableRows={tableRows}
391
+ fullWidth={fullWidth}
392
+ tableHeader={tableHeader}
393
+ loading={loading}
394
+ skeletonRows={skeletonRows}
395
+ isEmptyTable={isEmptyTable}
396
+ stickyFirstColumn={stickyFirstColumn}
397
+ getStickyColumn={getStickyColumn}
398
+ isScrolled={isScrolled}
399
+ showBorder={showBorder}
400
+ enableColumnDrag={enableColumnDrag}
401
+ activeId={activeId}
402
+ getColumnWidths={memoizedGetColumnWidths}
403
+ variant={variant}
404
+ emptyTableHeadingTag={emptyTableHeadingTag}
405
+ emptyTableCopy={emptyTableCopy}
406
+ emptyTableIcon={emptyTableIcon}
407
+ />
408
+ );
453
409
 
454
410
  return (
455
411
  <div id={`${uid}-table`} className={classes}>
@@ -491,10 +447,10 @@ export const Table = <TData extends RowData>({
491
447
  columnDragAriaLabelsCopy={columnDragAriaLabelsCopy}
492
448
  rootClassName={rootClassName}
493
449
  >
494
- {renderTableContent()}
450
+ {tableContent}
495
451
  </DraggableTable>
496
452
  ) : (
497
- renderTableContent()
453
+ tableContent
498
454
  )}
499
455
  </div>
500
456
  {paginationComponent}