@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,4 +1,6 @@
1
- import React, { useEffect, useRef, useState } from "react";
1
+ import React, { useRef } from "react";
2
+ import { type UniqueIdentifier } from "@dnd-kit/core";
3
+ import { useSortable } from "@dnd-kit/sortable";
2
4
  import { Button } from "@purpurds/button";
3
5
  import { Checkbox, type CheckedState } from "@purpurds/checkbox";
4
6
  import { IconArrowDown } from "@purpurds/icon/arrow-down";
@@ -18,9 +20,15 @@ import {
18
20
  } from "@tanstack/react-table";
19
21
  import c from "classnames/bind";
20
22
 
23
+ import { DragIndicatorCircle } from "./drag-indicator-circle";
21
24
  import styles from "./table.module.scss";
25
+ import { TableColumnDragHandle, useDragHandle } from "./use-drag-handle.hook";
26
+ import { useDragIndicatorPosition } from "./use-drag-indicator-position.hook";
27
+ import { useDropIndicator } from "./use-drop-indicator.hook";
28
+ import { useElementVisibility } from "./use-element-visibility.hook";
22
29
  import useTruncatedTooltip from "./use-truncated-hook";
23
30
  import { pxToRemString } from "./utils/unit-conversions";
31
+
24
32
  const cx = c.bind(styles);
25
33
 
26
34
  export type SortingEnabledProps = {
@@ -33,6 +41,16 @@ export type SortingDisabledProps = {
33
41
  sortingAriaLabels?: never;
34
42
  };
35
43
 
44
+ export type ColumnDragEnabledProps = {
45
+ enableColumnDrag: true;
46
+ columnDragAriaLabel: string;
47
+ };
48
+
49
+ export type ColumnDragDisabledProps = {
50
+ enableColumnDrag?: false;
51
+ columnDragAriaLabel?: never;
52
+ };
53
+
36
54
  export type TableColumnHeaderCellProps<TData> = {
37
55
  className?: string;
38
56
  stickyColumn: boolean;
@@ -41,7 +59,12 @@ export type TableColumnHeaderCellProps<TData> = {
41
59
  tanstackTable: Table<TData>;
42
60
  isScrolled?: boolean;
43
61
  showBorder?: boolean;
44
- } & (SortingEnabledProps | SortingDisabledProps);
62
+ sortableId?: string;
63
+ activeId?: UniqueIdentifier | null;
64
+ overlayActive?: boolean;
65
+ tableHasFilters: boolean;
66
+ } & (SortingEnabledProps | SortingDisabledProps) &
67
+ (ColumnDragEnabledProps | ColumnDragDisabledProps);
45
68
 
46
69
  export type SortingAriaLabels = {
47
70
  desc: string;
@@ -63,35 +86,35 @@ export const TableColumnHeaderCell = <TData extends RowData>({
63
86
  tanstackTable,
64
87
  isScrolled,
65
88
  showBorder,
89
+ enableColumnDrag,
90
+ sortableId,
91
+ activeId,
92
+ overlayActive,
93
+ columnDragAriaLabel,
94
+ tableHasFilters,
66
95
  }: TableColumnHeaderCellProps<TData>) => {
96
+ const headerRef = useRef<HTMLTableCellElement | null>(null);
97
+
98
+ const { attributes, listeners, setNodeRef, isSorting } = useSortable({
99
+ id: sortableId || header.id,
100
+ disabled: !enableColumnDrag || activeId !== null,
101
+ });
102
+
103
+ const { mouseDownActive, handleMouseDown } = useDragHandle();
104
+ const isActiveColumn = activeId === header.id || sortableId === activeId;
105
+ const draggingActive = (overlayActive || isSorting || mouseDownActive) && isActiveColumn;
106
+ const isCheckBox = header.column.id === "row-selection" || header.id === "row-selection";
107
+ const isRadiobutton = header.column.id === "row-radio" || header.id === "row-radio";
67
108
  const canSort = enableSorting && header.column.getCanSort();
68
- const hasFilter = header.column.getCanFilter();
69
- const isCheckBox = header.column.columnDef.meta?.cellType === "rowSelection";
70
- const isRadiobutton = header.column.columnDef.meta?.cellType === "rowToggle";
71
- const [isVisible, setIsVisible] = useState(false);
72
- const elementRef = useRef<HTMLTableCellElement>(null);
73
-
74
- useEffect(() => {
75
- const currentElement = elementRef.current;
76
- if (!currentElement) {
77
- return;
78
- }
109
+ const hasFilter = header.column.columnDef.meta?.filterVariant !== undefined && tableHasFilters;
79
110
 
80
- const observer = new IntersectionObserver(
81
- ([entry]) => {
82
- setIsVisible(entry.isIntersecting);
83
- },
84
- { threshold: 1 }
85
- );
86
-
87
- observer.observe(currentElement);
111
+ const dropIndicatorPosition = useDropIndicator({
112
+ itemId: header.id,
113
+ sortableId,
114
+ enableDrag: !!enableColumnDrag,
115
+ });
88
116
 
89
- return () => {
90
- if (currentElement) {
91
- observer.unobserve(currentElement);
92
- }
93
- };
94
- }, []);
117
+ const isVisible = useElementVisibility(headerRef, 1);
95
118
 
96
119
  const getSortingDirection = (sortingDirection: SortDirection | false) => {
97
120
  switch (sortingDirection) {
@@ -116,47 +139,95 @@ export const TableColumnHeaderCell = <TData extends RowData>({
116
139
  ? `${header.column.columnDef.header}`
117
140
  : undefined;
118
141
 
142
+ const setRefs = (node: HTMLTableCellElement | null) => {
143
+ headerRef.current = node;
144
+ if (setNodeRef) {
145
+ setNodeRef(node);
146
+ }
147
+ };
148
+
149
+ const circleIndicatorPosition = useDragIndicatorPosition({
150
+ elementRef: headerRef,
151
+ dropIndicatorPosition,
152
+ });
153
+
119
154
  return (
120
- <th
121
- ref={elementRef}
122
- className={cx(className, rootClassName, {
123
- [`${rootClassName}__checkbox`]: isCheckBox,
124
- [`${rootClassName}__sticky-column`]: stickyColumn,
125
- [`${rootClassName}__sticky-header`]: stickyHeaders,
126
- [`${rootClassName}__sticky-column__with-sticky-border`]: isScrolled && showBorder,
127
- [`${rootClassName}__first-header-visible`]: isFirstColumn && isVisible,
128
- [`${rootClassName}__last-header-visible`]: isLastColumn && isVisible,
129
- [`${rootClassName}__border-radius-first-cell`]: isFirstColumn,
130
- [`${rootClassName}__border-radius-last-cell`]: isLastColumn,
131
- })}
132
- style={{
133
- // If column resizing is enabled Don't use column.getSize() on every header and every data cell. Instead, calculate all column widths once upfront, memoized! https://tanstack.com/table/latest/docs/guide/column-sizing#column-sizing-guide
134
- maxWidth: widthInRemString,
135
- minWidth: widthInRemString,
136
- }}
137
- scope="col"
138
- aria-sort={getSortingDirection(header.column.getIsSorted())}
139
- aria-label={ariaLabel}
140
- >
141
- <div className={cx(`${rootClassName}__content`)}>
142
- <div className={cx(`${rootClassName}__title`)}>
143
- <HeaderContent
144
- header={header}
145
- tanstackTable={tanstackTable}
146
- canSort={canSort}
147
- isCheckBox={isCheckBox}
148
- isRadiobutton={isRadiobutton}
149
- sortingAriaLabels={sortingAriaLabels}
155
+ <>
156
+ {circleIndicatorPosition && (
157
+ <DragIndicatorCircle
158
+ top={circleIndicatorPosition.top}
159
+ left={circleIndicatorPosition.left}
160
+ container={circleIndicatorPosition.container}
161
+ />
162
+ )}
163
+ <th
164
+ ref={setRefs}
165
+ className={cx(className, rootClassName, {
166
+ [`${rootClassName}__checkbox`]: isCheckBox,
167
+ [`${rootClassName}__sticky-column`]: stickyColumn,
168
+ [`${rootClassName}__sticky-header`]: stickyHeaders,
169
+ [`${rootClassName}__sticky-column__with-sticky-border`]: isScrolled && showBorder,
170
+ [`${rootClassName}__first-header-visible`]: isFirstColumn && isVisible,
171
+ [`${rootClassName}__last-header-visible`]: isLastColumn && isVisible,
172
+ [`${rootClassName}__border-radius-first-cell`]: isFirstColumn && !overlayActive,
173
+ [`${rootClassName}__border-radius-last-cell`]: isLastColumn && !overlayActive,
174
+ [`${rootClassName}__draggable`]: enableColumnDrag,
175
+ [`${rootClassName}__column-drag-enabled`]: enableColumnDrag,
176
+ [`${rootClassName}__dragging`]: draggingActive && !overlayActive,
177
+ [`${rootClassName}--drop-indicator-before`]: dropIndicatorPosition === "before",
178
+ [`${rootClassName}--drop-indicator-after`]: dropIndicatorPosition === "after",
179
+ })}
180
+ style={{
181
+ width: isCheckBox || isRadiobutton ? widthInRemString : undefined,
182
+ }}
183
+ scope="col"
184
+ aria-sort={getSortingDirection(header.column.getIsSorted())}
185
+ aria-label={ariaLabel}
186
+ {...(enableColumnDrag
187
+ ? { ...attributes, ...listeners, id: `header-${sortableId || header.id}` }
188
+ : {})}
189
+ >
190
+ {enableColumnDrag && (
191
+ <TableColumnDragHandle
192
+ onMouseDown={handleMouseDown}
193
+ overlayActive={overlayActive}
194
+ isFirstColumn={isFirstColumn}
195
+ isLastColumn={isLastColumn}
196
+ columnDragAriaLabel={columnDragAriaLabel}
150
197
  />
151
- </div>
198
+ )}
152
199
 
153
- {hasFilter && !isCheckBox && (
154
- <div className={cx(`${rootClassName}__filter-wrapper`)}>
155
- <Filter header={header} />
200
+ <div
201
+ className={cx(`${rootClassName}__inner`)}
202
+ style={{
203
+ maxWidth: widthInRemString,
204
+ minWidth: widthInRemString,
205
+ }}
206
+ >
207
+ <div className={cx(`${rootClassName}__content`)}>
208
+ <div className={cx(`${rootClassName}__title`)}>
209
+ <HeaderContent
210
+ header={header}
211
+ tanstackTable={tanstackTable}
212
+ canSort={canSort}
213
+ isCheckBox={isCheckBox}
214
+ isRadiobutton={isRadiobutton}
215
+ sortingAriaLabels={sortingAriaLabels}
216
+ />
217
+ </div>
218
+ {hasFilter && !isCheckBox && (
219
+ <div className={cx(`${rootClassName}__filter-wrapper`)}>
220
+ <Filter header={header} />
221
+ </div>
222
+ )}
223
+ {/* Placeholder element for columns without filters when table has filters. Its only used within the drag overlay */}
224
+ {tableHasFilters && !hasFilter && !isCheckBox && !isRadiobutton && overlayActive && (
225
+ <div className={cx(`${rootClassName}__filter-placeholder`)}></div>
226
+ )}
156
227
  </div>
157
- )}
158
- </div>
159
- </th>
228
+ </div>
229
+ </th>
230
+ </>
160
231
  );
161
232
  };
162
233