@purpurds/table 8.3.1 → 8.5.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 +14040 -9810
  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
@@ -1,5 +1,7 @@
1
1
  @use "@purpurds/tokens/breakpoint/variables" as *;
2
2
 
3
+ $indicatorWidth: 3px;
4
+
3
5
  .purpur-table {
4
6
  --table-border: var(--purpur-border-width-xs) solid var(--purpur-color-border-weak);
5
7
  --table-border-radius: var(--purpur-border-radius-lg);
@@ -49,7 +51,8 @@
49
51
  .purpur-table-column-header-cell {
50
52
  border-top: var(--table-border);
51
53
 
52
- &:first-of-type {
54
+ &:first-of-type,
55
+ &__column-drag-enabled {
53
56
  border-left: var(--table-border);
54
57
  }
55
58
 
@@ -57,19 +60,24 @@
57
60
  border-right: var(--table-border);
58
61
  }
59
62
 
60
- &__sticky-header {
61
- position: sticky;
62
- top: 0;
63
- z-index: 4;
64
- background-color: inherit;
65
- }
63
+ @media (min-width: $purpur-breakpoint-md) {
64
+ &__sticky-header {
65
+ position: sticky;
66
+ top: 0;
67
+ z-index: 4;
68
+ background-color: inherit;
69
+ }
66
70
 
67
- &__sticky-column {
68
- z-index: 5;
71
+ &__sticky-column {
72
+ z-index: 5;
73
+ }
69
74
  }
70
75
  }
71
76
 
72
77
  &--has-filters {
78
+ th {
79
+ min-height: calc(31.4rem * var(--purpur-rescale));
80
+ }
73
81
  .purpur-table-row {
74
82
  vertical-align: baseline;
75
83
  }
@@ -77,16 +85,9 @@
77
85
  }
78
86
 
79
87
  .purpur-table-column-header-cell {
80
- padding: var(--purpur-spacing-100) var(--purpur-spacing-150) var(--purpur-spacing-100)
81
- var(--purpur-spacing-300);
82
88
  border-bottom: var(--table-border);
83
89
  text-align: left;
84
90
 
85
- &:has([role="checkbox"]) {
86
- padding: calc(var(--purpur-spacing-25) + var(--purpur-spacing-50)) var(--purpur-spacing-150)
87
- calc(var(--purpur-spacing-25) + var(--purpur-spacing-50)) var(--purpur-spacing-300);
88
- }
89
-
90
91
  &__checkbox {
91
92
  vertical-align: middle;
92
93
  }
@@ -99,10 +100,17 @@
99
100
  border-top-right-radius: var(--table-border-radius);
100
101
  }
101
102
 
103
+ &__inner {
104
+ padding: var(--purpur-spacing-100) var(--purpur-spacing-150) var(--purpur-spacing-100)
105
+ var(--purpur-spacing-300);
106
+ align-self: stretch;
107
+ }
108
+
102
109
  &__content {
103
110
  display: flex;
104
111
  flex-direction: column;
105
112
  align-items: flex-start;
113
+
106
114
  gap: var(--purpur-spacing-50);
107
115
  }
108
116
 
@@ -131,13 +139,89 @@
131
139
  padding-right: var(--purpur-spacing-150);
132
140
  padding-bottom: var(--purpur-spacing-150);
133
141
  flex-direction: column;
142
+ width: 100%;
143
+ }
144
+
145
+ &__draggable {
146
+ cursor: grab;
147
+ }
148
+
149
+ &__dragging {
150
+ cursor: grabbing;
151
+ opacity: 0.4;
152
+ }
153
+
154
+ &__content {
155
+ display: flex;
156
+ align-items: center;
157
+ }
158
+
159
+ &__drag-handle {
160
+ display: flex;
161
+ padding: var(--purpur-spacing-25) var(--purpur-spacing-50);
162
+ justify-content: center;
163
+ align-items: center;
134
164
  align-self: stretch;
165
+ border-bottom: var(--purpur-border-width-xs) solid var(--purpur-color-border-weak);
166
+ background: var(--purpur-color-background-secondary);
167
+
168
+ &:hover {
169
+ opacity: 1;
170
+
171
+ &:not(.purpur-table-column-header-cell__drag-handle--active) {
172
+ background: var(--purpur-color-background-interactive-transparent-hover);
173
+ }
174
+ }
175
+
176
+ &:focus,
177
+ &:focus-visible {
178
+ outline: var(--purpur-border-width-sm) solid var(--purpur-color-border-interactive-focus);
179
+ border-radius: var(--purpur-border-radius-xs);
180
+ }
181
+
182
+ &--active {
183
+ border: inherit;
184
+ border-bottom: var(--purpur-border-width-xs) solid var(--purpur-color-border-weak);
185
+ background: var(--purpur-color-background-interactive-transparent-active);
186
+ border-top-left-radius: 7px;
187
+ border-top-right-radius: 7px;
188
+ }
189
+
190
+ &-icon {
191
+ color: var(--purpur-color-text-weak);
192
+ }
193
+ }
194
+
195
+ &__filter-placeholder {
196
+ height: calc(3.4rem * var(--purpur-rescale));
197
+ visibility: hidden;
198
+ }
199
+
200
+ &--drop-indicator-before::before,
201
+ &--drop-indicator-after::after {
202
+ content: "";
203
+ position: absolute;
204
+ top: 0;
205
+ bottom: 0;
206
+ right: 0;
207
+ width: $indicatorWidth;
208
+ background-color: var(--purpur-color-border-interactive-primary);
209
+ pointer-events: none;
210
+ }
211
+
212
+ &--drop-indicator-before::before {
213
+ left: 0;
214
+ }
215
+
216
+ &--drop-indicator-after::after {
217
+ right: 0;
135
218
  }
136
219
  }
137
220
 
138
221
  .purpur-table-row {
139
222
  .purpur-table-row-cell {
140
- &:first-of-type {
223
+ &:first-of-type,
224
+ &__column-drag-enabled {
141
225
  border-left: var(--table-border);
142
226
  }
143
227
 
@@ -203,21 +287,23 @@
203
287
 
204
288
  .purpur-table-column-header-cell,
205
289
  .purpur-table-row-cell {
206
- &__sticky-column {
207
- position: sticky;
208
- left: 0;
209
- z-index: 3;
210
- background-color: inherit;
290
+ @media (min-width: $purpur-breakpoint-md) {
291
+ &__sticky-column {
292
+ position: sticky;
293
+ left: 0;
294
+ z-index: 3;
295
+ background-color: inherit;
211
296
 
212
- & + .purpur-table-column-header-cell__sticky-column,
213
- & + .purpur-table-row-cell__sticky-column {
214
- left: 73px; // 73px is the width of the first column when row selection/toggle is enabled. This rule is only applied when the row selection/toggle is enabled.
215
- }
297
+ & + .purpur-table-column-header-cell__sticky-column,
298
+ & + .purpur-table-row-cell__sticky-column {
299
+ left: 85px; // 85px is the width of the first column when row selection/toggle is enabled. This rule is only applied when the row selection/toggle is enabled.
300
+ }
216
301
 
217
- &__with-sticky-border {
218
- border-right: var(--purpur-border-width-sm) solid var(--purpur-color-border-base);
219
- box-shadow: var(--purpur-border-width-sm) 0 calc(2 * var(--purpur-border-width-sm))
220
- rgba(0, 0, 0, 0.05);
302
+ &__with-sticky-border {
303
+ border-right: var(--purpur-border-width-sm) solid var(--purpur-color-border-base);
304
+ box-shadow: var(--purpur-border-width-sm) 0 calc(2 * var(--purpur-border-width-sm))
305
+ rgba(0, 0, 0, 0.05);
306
+ }
221
307
  }
222
308
  }
223
309
  }
@@ -332,6 +418,34 @@
332
418
  &__tooltip-content {
333
419
  z-index: 10;
334
420
  }
421
+
422
+ &__dragging {
423
+ opacity: 0.4;
424
+ }
425
+
426
+ &--drop-indicator-before,
427
+ &--drop-indicator-after {
428
+ position: sticky;
429
+ }
430
+
431
+ &--drop-indicator-before::before,
432
+ &--drop-indicator-after::after {
433
+ content: "";
434
+ position: absolute;
435
+ top: 0;
436
+ bottom: 0;
437
+ width: $indicatorWidth;
438
+ background-color: var(--purpur-color-border-interactive-primary);
439
+ pointer-events: none;
440
+ }
441
+
442
+ &--drop-indicator-before::before {
443
+ left: 0;
444
+ }
445
+
446
+ &--drop-indicator-after::after {
447
+ right: 0;
448
+ }
335
449
  }
336
450
 
337
451
  .purpur-table--primary {
@@ -408,3 +522,50 @@
408
522
  var(--purpur-spacing-0);
409
523
  }
410
524
  }
525
+
526
+ .purpur-table__drag-overlay {
527
+ z-index: 999;
528
+ position: relative;
529
+ border-radius: 8px;
530
+ box-shadow: var(--purpur-shadow-lg);
531
+
532
+ &--primary {
533
+ .purpur-table-header {
534
+ .purpur-table-row {
535
+ background: var(--purpur-color-background-primary);
536
+
537
+ th {
538
+ border-top-left-radius: var(--purpur-border-radius-md);
539
+ border-top-right-radius: var(--purpur-border-radius-md);
540
+ }
541
+ }
542
+ }
543
+
544
+ .purpur-table-body {
545
+ .purpur-table-row {
546
+ background: var(--purpur-color-background-primary);
547
+
548
+ &:nth-of-type(odd) {
549
+ background: var(--purpur-color-background-secondary);
550
+ }
551
+
552
+ &:last-of-type {
553
+ td {
554
+ border-bottom-left-radius: var(--purpur-border-radius-md);
555
+ border-bottom-right-radius: var(--purpur-border-radius-md);
556
+ }
557
+ }
558
+ }
559
+ }
560
+ }
561
+
562
+ &--secondary {
563
+ .purpur-table-row {
564
+ background-color: var(--purpur-color-background-secondary);
565
+
566
+ &:nth-of-type(odd) {
567
+ background-color: var(--purpur-color-background-primary);
568
+ }
569
+ }
570
+ }
571
+ }
@@ -2,7 +2,7 @@
2
2
 
3
3
  import React, { useEffect, useState } from "react";
4
4
  import { Grid } from "@purpurds/grid";
5
- import { IllustrativeIconSearchQuestionDuocolor } from "@purpurds/illustrative-icon/search-question-duocolor";
5
+ import { IllustrativeIconTableQuestionDuocolorStatic } from "@purpurds/illustrative-icon/table-question-duocolor-static";
6
6
  import { Pagination, type PaginationProps } from "@purpurds/pagination";
7
7
  import type { Meta, StoryObj } from "@storybook/react-vite";
8
8
  import c from "classnames";
@@ -247,6 +247,7 @@ const commonSettingsDrawerCopy = {
247
247
  },
248
248
  visibleColumns: {
249
249
  header: "Visible columns",
250
+ description: "Choose which columns to display.",
250
251
  },
251
252
  buttons: {
252
253
  closeDrawer: "Close drawer",
@@ -254,6 +255,30 @@ const commonSettingsDrawerCopy = {
254
255
  },
255
256
  };
256
257
 
258
+ const commonSettingsDrawerCopyWithColumnDrag = {
259
+ ...commonSettingsDrawerCopy,
260
+ visibleColumns: {
261
+ ...commonSettingsDrawerCopy.visibleColumns,
262
+ rearrangeDescription: "Choose how you want to reorder the columns.",
263
+ buttons: {
264
+ rearrange: "Reorder",
265
+ done: "Done",
266
+ },
267
+ ariaLabels: {
268
+ dragHandle: {
269
+ action: "Drag to reorder column",
270
+ instructions: "(use space or enter to pick up, arrow keys to move, space or enter to drop)",
271
+ },
272
+ arrowButtons: {
273
+ move: "Move",
274
+ up: "up",
275
+ down: "down",
276
+ },
277
+ rearrangeButton: "Reorder columns",
278
+ },
279
+ },
280
+ };
281
+
257
282
  const commonExportDrawerCopy = {
258
283
  title: "Export table",
259
284
  bodyText: "Choose the format you want to export the table in.",
@@ -266,6 +291,11 @@ const commonRowSelectionAriaLabels = {
266
291
  row: "Select row",
267
292
  };
268
293
 
294
+ const commonColumnDragAriaLabels = {
295
+ grab: "Drag to reorder column",
296
+ grabbing: "Dragging",
297
+ };
298
+
269
299
  /**
270
300
  * Showcase demonstrates a fully-featured Table with all available functionality enabled.
271
301
  */
@@ -281,7 +311,7 @@ export const Showcase: StoryTableData = {
281
311
  exportFormats: ["csv", "xlsx"],
282
312
  actionbarCopy: commonActionbarCopy,
283
313
  exportDrawerCopy: commonExportDrawerCopy,
284
- settingsDrawerCopy: commonSettingsDrawerCopy,
314
+ settingsDrawerCopy: commonSettingsDrawerCopyWithColumnDrag,
285
315
  sortingAriaLabels: commonSortingAriaLabels,
286
316
  onToggleExpand: () => {},
287
317
  toolbarCopy: commonToolbarCopy,
@@ -292,6 +322,8 @@ export const Showcase: StoryTableData = {
292
322
  fullWidth: true,
293
323
  stickyFirstColumn: true,
294
324
  stickyHeaders: true,
325
+ enableColumnDrag: true,
326
+ onColumnOrderChange: () => {},
295
327
  },
296
328
  parameters: {
297
329
  docs: {
@@ -321,6 +353,7 @@ manages filtering, sorting and pagination internally.
321
353
  const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
322
354
  const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
323
355
  const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({});
356
+ const [columnOrder, setColumnOrder] = useState<string[]>([]);
324
357
 
325
358
  const [displayData, setDisplayData] = useState(args.data);
326
359
  const [rowCount, setRowCount] = useState(args.data.length);
@@ -378,7 +411,7 @@ manages filtering, sorting and pagination internally.
378
411
  data={displayData}
379
412
  enableRowSelection={args.enableRowSelection}
380
413
  enableMultiRowSelection={args.enableMultiRowSelection}
381
- state={{ pagination, rowSelection, columnFilters, columnVisibility }}
414
+ state={{ pagination, rowSelection, columnFilters, columnVisibility, columnOrder }}
382
415
  exportFormats={args.exportFormats}
383
416
  onExportData={args.exportFormats ? (e) => console.log("Export data", e) : undefined}
384
417
  actionbarCopy={args.enableActionBar ? args.actionbarCopy : undefined}
@@ -397,6 +430,7 @@ manages filtering, sorting and pagination internally.
397
430
  showOnlySelectedRows={showOnlySelectedRows}
398
431
  onRowSelectionChange={setRowSelection}
399
432
  onColumnFiltersChange={setColumnFilters}
433
+ onColumnOrderChange={setColumnOrder}
400
434
  getRowId={(row: TableData) => `${row.id}`}
401
435
  actionBarTotalRowCount={args.data.length}
402
436
  onPaginationChange={setPagination}
@@ -406,6 +440,8 @@ manages filtering, sorting and pagination internally.
406
440
  fullWidth={args.fullWidth}
407
441
  stickyFirstColumn={args.stickyFirstColumn}
408
442
  stickyHeaders={args.stickyHeaders}
443
+ enableColumnDrag={args.enableColumnDrag}
444
+ columnDragAriaLabelsCopy={commonColumnDragAriaLabels}
409
445
  />
410
446
  );
411
447
  },
@@ -1004,7 +1040,7 @@ when there is no data to show.
1004
1040
  enableActionBar={false}
1005
1041
  emptyTableCopy={emptyTableProps.emptyTableCopy}
1006
1042
  emptyTableHeadingTag={emptyTableProps.emptyTableHeadingTag}
1007
- emptyTableIcon={<IllustrativeIconSearchQuestionDuocolor />}
1043
+ emptyTableIcon={<IllustrativeIconTableQuestionDuocolorStatic />}
1008
1044
  />
1009
1045
  );
1010
1046
  },
@@ -1257,3 +1293,23 @@ This example demonstrates the secondary visual variant of the Table.
1257
1293
  );
1258
1294
  },
1259
1295
  };
1296
+
1297
+ export const WithColumnDragAndDrop = {
1298
+ name: "With column drag and drop",
1299
+ args: {
1300
+ columns: simpleColumnDef,
1301
+ data: simpleTableData,
1302
+ enableColumnDrag: true,
1303
+ columnDragAriaLabelsCopy: { commonColumnDragAriaLabels },
1304
+ },
1305
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1306
+ render: (args: any) => {
1307
+ const [columnOrder, setColumnOrder] = useState<string[]>([]);
1308
+
1309
+ return (
1310
+ <div style={{ padding: "20px" }}>
1311
+ <Table {...args} state={{ columnOrder }} onColumnOrderChange={setColumnOrder} />
1312
+ </div>
1313
+ );
1314
+ },
1315
+ };
@@ -290,6 +290,11 @@ describe("Data Table", () => {
290
290
  skeletonRows={5}
291
291
  />
292
292
  );
293
+
294
+ table = screen.getByRole("table");
295
+ tableHeaders = within(getTableHead(table)).getAllByRole("columnheader");
296
+
297
+ tableRows = within(getTableBody(table)).getAllByRole("row");
293
298
  });
294
299
 
295
300
  it("should show table data", () => {
@@ -300,7 +305,6 @@ describe("Data Table", () => {
300
305
 
301
306
  const firstDataRow = tableRows[0];
302
307
  const dataCells = within(firstDataRow).getAllByRole("cell");
303
-
304
308
  expect(dataCells[0]).toHaveTextContent("12345"); // ID
305
309
  expect(dataCells[1]).toHaveTextContent("Name 1"); // Name
306
310
  expect(dataCells[2]).toHaveTextContent("Link 2"); // Link