@trackunit/react-table 1.3.139 → 1.3.141
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/index.cjs.css +12 -0
- package/index.cjs.js +607 -65
- package/index.esm.css +12 -0
- package/index.esm.js +611 -69
- package/package.json +10 -10
- package/src/Table.d.ts +2 -0
- package/src/components/TextWithTooltip.d.ts +13 -0
- package/src/hooks/useCellsOffset.d.ts +21 -0
- package/src/hooks/useColumnDragDrop.d.ts +25 -0
- package/src/menus/ColumnActions.d.ts +14 -0
- package/src/menus/ColumnFilter.d.ts +2 -1
- package/src/menus/ColumnSorting.d.ts +19 -0
- package/src/translation.d.ts +2 -2
- package/src/types.d.ts +12 -0
package/index.cjs.js
CHANGED
|
@@ -20,6 +20,12 @@ var reactCoreContextsApi = require('@trackunit/react-core-contexts-api');
|
|
|
20
20
|
var defaultTranslations = {
|
|
21
21
|
"layout.actions.reset": "Reset",
|
|
22
22
|
"table.actionsheet.selected": "{{count}} selected",
|
|
23
|
+
"table.columnActions.clearSorting": "Clear sorting",
|
|
24
|
+
"table.columnActions.hideColumn": "Hide column",
|
|
25
|
+
"table.columnActions.pinColumn": "Pin column",
|
|
26
|
+
"table.columnActions.sortAscending": "Sort ascending",
|
|
27
|
+
"table.columnActions.sortDescending": "Sort descending",
|
|
28
|
+
"table.columnActions.unPinColumn": "Unpin column",
|
|
23
29
|
"table.columnFilters.columns": "Columns",
|
|
24
30
|
"table.columnFilters.hiddenColumnCount": "{{count}} column hidden",
|
|
25
31
|
"table.columnFilters.hiddenColumnsCount": "{{count}} columns hidden",
|
|
@@ -290,8 +296,8 @@ const cvaColumnFilterGrabbable = cssClassVarianceUtilities.cvaMerge(["flex", "it
|
|
|
290
296
|
*/
|
|
291
297
|
const ColumnFilter = ({ columns, setColumnOrder, defaultColumnOrder = [], columnOrder, onUserEvent, }) => {
|
|
292
298
|
const [t] = useTranslation();
|
|
293
|
-
const numberOfHiddenColumns = columns.filter(column => !column.getIsVisible()).length;
|
|
294
299
|
const initialSetupRef = react.useRef(false);
|
|
300
|
+
const [searchText, setSearchText] = react.useState("");
|
|
295
301
|
react.useEffect(() => {
|
|
296
302
|
if (!initialSetupRef.current && defaultColumnOrder.length > 0) {
|
|
297
303
|
setColumnOrder(defaultColumnOrder);
|
|
@@ -301,37 +307,35 @@ const ColumnFilter = ({ columns, setColumnOrder, defaultColumnOrder = [], column
|
|
|
301
307
|
const resetHiddenColumns = react.useCallback(() => {
|
|
302
308
|
columns.forEach(column => column.toggleVisibility(!column.columnDef.meta?.hiddenByDefault));
|
|
303
309
|
setColumnOrder(defaultColumnOrder);
|
|
310
|
+
columns.forEach(column => column.pin(column.columnDef.meta?.pinned ?? false));
|
|
304
311
|
}, [columns, defaultColumnOrder, setColumnOrder]);
|
|
305
312
|
const sortedColumns = react.useMemo(() => {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
return t("table.columnFilters.hiddenColumnsCount", { count: numberOfHiddenColumns });
|
|
313
|
+
let filteredColumns = columns;
|
|
314
|
+
if (searchText.trim() !== "") {
|
|
315
|
+
const trimmedSearchText = searchText.trim().toLowerCase();
|
|
316
|
+
filteredColumns = columns.filter(column => {
|
|
317
|
+
const label = column.columnDef.meta?.columnFilterLabel ?? column.columnDef.header?.toString() ?? "";
|
|
318
|
+
return label.toLowerCase().includes(trimmedSearchText);
|
|
319
|
+
});
|
|
314
320
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
+
return columnOrder && columnOrder.length > 0
|
|
322
|
+
? filteredColumns.sort((a, b) => columnOrder.indexOf(a.id) - columnOrder.indexOf(b.id))
|
|
323
|
+
: filteredColumns;
|
|
324
|
+
}, [columnOrder, columns, searchText]);
|
|
325
|
+
const visibleColumnsCount = react.useMemo(() => {
|
|
326
|
+
return columns.filter(col => col.getIsVisible()).length;
|
|
327
|
+
}, [columns]);
|
|
328
|
+
return (jsxRuntime.jsx(reactDnd.DndProvider, { backend: reactDndHtml5Backend.HTML5Backend, children: jsxRuntime.jsx(reactComponents.Tooltip, { label: t("table.columnFilters.tooltip"), placement: "bottom", children: jsxRuntime.jsxs(reactComponents.Popover, { placement: "bottom-start", children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx(reactComponents.Button, { prefix: jsxRuntime.jsx(reactComponents.Icon, { name: "ViewColumns", size: "small" }), size: "small", variant: "secondary", children: jsxRuntime.jsx("span", { className: "hidden md:block", children: t("table.columnFilters.columns") }) }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { children: () => (jsxRuntime.jsxs(reactComponents.MenuList, { className: "relative max-h-[55vh] w-72 overflow-y-auto", children: [jsxRuntime.jsxs("div", { className: "mb-2 flex items-center gap-1", children: [jsxRuntime.jsx(reactFormComponents.Search, { autoFocus: true, className: "flex-1", fieldSize: "small", id: "column-search", onChange: e => setSearchText(e.currentTarget.value.toLowerCase()), onClear: () => setSearchText(""), placeholder: "Search", value: searchText }), jsxRuntime.jsx(reactComponents.Button, { onClick: resetHiddenColumns, size: "small", variant: "ghost-neutral", children: t("layout.actions.reset") })] }), jsxRuntime.jsx(ColumnFiltersDragAndDrop, { columns: sortedColumns, isSortDisabled: !!searchText, onUserEvent: onUserEvent, setColumnOrder: setColumnOrder, visibleColumnsCount: visibleColumnsCount })] })) })] }) }) }));
|
|
321
329
|
};
|
|
322
|
-
const ColumnFiltersDragAndDrop = ({ columns, setColumnOrder, onUserEvent,
|
|
330
|
+
const ColumnFiltersDragAndDrop = ({ columns, setColumnOrder, onUserEvent, isSortDisabled, visibleColumnsCount, }) => {
|
|
323
331
|
const [localColumns, setLocalColumns] = react.useState(columns);
|
|
324
332
|
react.useEffect(() => {
|
|
325
333
|
setLocalColumns(columns);
|
|
326
334
|
}, [columns]);
|
|
327
335
|
const onColumDrop = react.useCallback(() => {
|
|
328
|
-
const
|
|
329
|
-
const firstLocalColumnId = localColumns[0]?.id;
|
|
330
|
-
const newColumnOrder = firstElement === firstLocalColumnId
|
|
331
|
-
? localColumns.map(column => column.id)
|
|
332
|
-
: [firstElement, ...localColumns.map(column => column.id)].filter((id) => id !== undefined);
|
|
336
|
+
const newColumnOrder = localColumns.map(column => column.id);
|
|
333
337
|
setColumnOrder(newColumnOrder);
|
|
334
|
-
}, [localColumns, setColumnOrder
|
|
338
|
+
}, [localColumns, setColumnOrder]);
|
|
335
339
|
const onCancelColumDrop = react.useCallback(() => {
|
|
336
340
|
setLocalColumns(columns);
|
|
337
341
|
}, [columns]);
|
|
@@ -355,19 +359,20 @@ const ColumnFiltersDragAndDrop = ({ columns, setColumnOrder, onUserEvent, defaul
|
|
|
355
359
|
(!columnDef.header || columnDef.header.length === 0 || typeof columnDef.header === "function")) {
|
|
356
360
|
return null;
|
|
357
361
|
}
|
|
358
|
-
|
|
362
|
+
const isVisible = column.getIsVisible();
|
|
363
|
+
return (jsxRuntime.jsx(ColumnFilterItem, { disabled: !column.getCanHide() || (isVisible && visibleColumnsCount === 1), id: column.id, index: index, isSortDisabled: isSortDisabled, moveColumn: moveColumn, name: columnDef.meta?.columnFilterLabel ?? columnDef.header?.toString() ?? "", onCancelDrop: onCancelColumDrop, onDrop: onColumDrop, onToggle: (toggled, event) => {
|
|
359
364
|
column.getToggleVisibilityHandler()(event);
|
|
360
365
|
onUserEvent?.("Column Filter", {
|
|
361
366
|
columnName: column.id,
|
|
362
367
|
hidden: !toggled,
|
|
363
368
|
});
|
|
364
|
-
}, toggled:
|
|
369
|
+
}, toggled: isVisible }, column.id));
|
|
365
370
|
}) }));
|
|
366
371
|
};
|
|
367
372
|
const ItemTypes = {
|
|
368
373
|
COLUMN: "column",
|
|
369
374
|
};
|
|
370
|
-
const ColumnFilterItem = ({ name, onToggle, toggled, disabled, index, moveColumn, onDrop, onCancelDrop, id, }) => {
|
|
375
|
+
const ColumnFilterItem = ({ name, onToggle, toggled, disabled, index, moveColumn, onDrop, onCancelDrop, id, isSortDisabled, }) => {
|
|
371
376
|
const ref = react.useRef(null);
|
|
372
377
|
const [, drop] = reactDnd.useDrop({
|
|
373
378
|
accept: ItemTypes.COLUMN,
|
|
@@ -400,7 +405,7 @@ const ColumnFilterItem = ({ name, onToggle, toggled, disabled, index, moveColumn
|
|
|
400
405
|
const [{ isDragging }, drag] = reactDnd.useDrag({
|
|
401
406
|
type: ItemTypes.COLUMN,
|
|
402
407
|
item: { type: ItemTypes.COLUMN, id, index },
|
|
403
|
-
canDrag:
|
|
408
|
+
canDrag: !isSortDisabled,
|
|
404
409
|
collect: (monitor) => ({
|
|
405
410
|
isDragging: monitor.isDragging(),
|
|
406
411
|
}),
|
|
@@ -415,7 +420,7 @@ const ColumnFilterItem = ({ name, onToggle, toggled, disabled, index, moveColumn
|
|
|
415
420
|
return (jsxRuntime.jsxs("div", { className: reactComponents.cvaInteractableItem({
|
|
416
421
|
selected: false,
|
|
417
422
|
className: "grid w-full grid-cols-[min-content,1fr,min-content] items-center gap-2 py-1",
|
|
418
|
-
}), ref: ref, style: { opacity: opacity }, children: [jsxRuntime.jsx("div", { className: cvaColumnFilterGrabbable({ disabled:
|
|
423
|
+
}), ref: ref, style: { opacity: opacity }, children: [jsxRuntime.jsx("div", { className: cvaColumnFilterGrabbable({ disabled: isSortDisabled }), children: jsxRuntime.jsx(reactComponents.Icon, { color: "secondary", name: "EllipsisDrag", size: "small" }) }), jsxRuntime.jsxs("label", { className: "grid w-full grid-cols-[1fr,min-content] items-center gap-2", htmlFor: id, children: [jsxRuntime.jsxs("div", { className: "grid w-full grid-cols-[1fr,min-content] items-center", children: [jsxRuntime.jsx(reactComponents.Text, { className: reactFormComponents.cvaLabel(), children: name }), disabled ? jsxRuntime.jsx(reactComponents.Icon, { name: "LockClosed", size: "small" }) : null] }), jsxRuntime.jsx(reactFormComponents.ToggleSwitch, { disabled: disabled, id: id, onChange: (isToggled, event) => event && onToggle(isToggled, event), showInputFocus: false, size: "small", toggled: toggled })] })] }));
|
|
419
424
|
};
|
|
420
425
|
|
|
421
426
|
/**
|
|
@@ -432,7 +437,7 @@ const RowSpacing = ({ density, setRowDensity, onUserEvent }) => {
|
|
|
432
437
|
densityChosen: selectedDensity,
|
|
433
438
|
});
|
|
434
439
|
};
|
|
435
|
-
return (jsxRuntime.jsx(reactComponents.Tooltip, { dataTestId: "row-spacing", label: t("table.spacing.toolip"), placement: "bottom", children: jsxRuntime.jsxs(reactComponents.Popover, { placement: "bottom-start", children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx(reactComponents.Button, { prefix: jsxRuntime.jsx(reactComponents.Icon, { name: "ArrowsPointingOut", size: "small" }), size: "small", variant: "
|
|
440
|
+
return (jsxRuntime.jsx(reactComponents.Tooltip, { dataTestId: "row-spacing", label: t("table.spacing.toolip"), placement: "bottom", children: jsxRuntime.jsxs(reactComponents.Popover, { placement: "bottom-start", children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx(reactComponents.Button, { prefix: jsxRuntime.jsx(reactComponents.Icon, { name: "ArrowsPointingOut", size: "small" }), size: "small", variant: "secondary", children: jsxRuntime.jsx("span", { className: "hidden md:block", children: t("table.spacing") }) }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { children: jsxRuntime.jsx(reactComponents.MenuList, { children: jsxRuntime.jsxs("div", { className: "flex flex-col justify-start", children: [jsxRuntime.jsx(DensitySelection, { icon: jsxRuntime.jsx(CompactIcon, {}), isSelected: density === "compact", onClick: () => handleClick("compact"), text: t("table.rowDensity.compact") }), jsxRuntime.jsx(DensitySelection, { icon: jsxRuntime.jsx(SpaciousIcon, {}), isSelected: density === "spacious", onClick: () => handleClick("spacious"), text: t("table.rowDensity.spacious") })] }) }) })] }) }));
|
|
436
441
|
};
|
|
437
442
|
const DensitySelection = ({ text, onClick, icon, isSelected }) => {
|
|
438
443
|
return (jsxRuntime.jsxs("div", { className: "flex w-full cursor-pointer items-center p-1 hover:bg-neutral-50", onClick: onClick, children: [jsxRuntime.jsx("div", { className: "mr-1 flex", children: icon }), jsxRuntime.jsx(reactComponents.Text, { weight: "thick", children: text }), isSelected ? (jsxRuntime.jsx("div", { className: "justify-endgrow flex", children: jsxRuntime.jsx(reactComponents.Icon, { name: "Check", size: "small" }) })) : null] }));
|
|
@@ -473,6 +478,363 @@ const Sorting = ({ columns, }) => {
|
|
|
473
478
|
return (jsxRuntime.jsx(reactComponents.Tooltip, { label: t("table.sorting.toolip"), placement: "bottom", children: jsxRuntime.jsxs(reactComponents.Popover, { placement: "bottom-start", children: [jsxRuntime.jsx(reactComponents.PopoverTrigger, { children: jsxRuntime.jsx(reactComponents.Button, { prefix: currentSortDirection === "asc" ? (jsxRuntime.jsx(reactComponents.Icon, { name: "BarsArrowUp", size: "small" })) : (jsxRuntime.jsx(reactComponents.Icon, { name: "BarsArrowDown", size: "small" })), size: "small", variant: "ghost-neutral", children: jsxRuntime.jsx("span", { className: "hidden sm:block", children: t("table.sorting.label") }) }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { children: jsxRuntime.jsxs(reactComponents.MenuList, { className: "max-h-[55vh] overflow-y-auto", children: [jsxRuntime.jsx(reactFormComponents.RadioGroup, { id: "sortProperty", onChange: handleSelectionChange, value: currentSortValue ?? "", children: sortableColumns.map(column => (jsxRuntime.jsx(reactFormComponents.RadioItem, { className: "w-full", label: column.columnDef.header?.toString() ?? "", value: column.id }, column.id))) }), jsxRuntime.jsxs(reactFormComponents.RadioGroup, { id: "sortOrder", onChange: onSelectOrder, value: currentSortDirection, children: [jsxRuntime.jsx(reactFormComponents.RadioItem, { className: "w-full", label: t("table.sorting.ascending"), value: "asc" }), jsxRuntime.jsx(reactFormComponents.RadioItem, { className: "w-full", label: t("table.sorting.descending"), value: "desc" })] })] }) })] }) }));
|
|
474
479
|
};
|
|
475
480
|
|
|
481
|
+
/**
|
|
482
|
+
* This component is used to display a text with a tooltip.
|
|
483
|
+
* The tooltip is displayed if the text is truncated with ellipsis.
|
|
484
|
+
* The text is the text to display.
|
|
485
|
+
* The tooltipLabel is the label to display in the tooltip.
|
|
486
|
+
*/
|
|
487
|
+
const TextWithTooltip = ({ children, tooltipLabel, weight, }) => {
|
|
488
|
+
const [isTextTruncated, setIsTextTruncated] = react.useState(false);
|
|
489
|
+
const textRef = react.useRef(null);
|
|
490
|
+
const resizeObserverRef = react.useRef(null);
|
|
491
|
+
const debounceTimerRef = react.useRef(null);
|
|
492
|
+
const [hideTooltip, setHideTooltip] = react.useState(false);
|
|
493
|
+
const checkIfTruncated = react.useCallback(() => {
|
|
494
|
+
if (textRef.current) {
|
|
495
|
+
const isTruncated = textRef.current.scrollWidth > textRef.current.clientWidth;
|
|
496
|
+
setIsTextTruncated(isTruncated);
|
|
497
|
+
}
|
|
498
|
+
}, []);
|
|
499
|
+
const debouncedCheckTruncation = react.useCallback(() => {
|
|
500
|
+
if (debounceTimerRef.current !== null) {
|
|
501
|
+
window.clearTimeout(debounceTimerRef.current);
|
|
502
|
+
}
|
|
503
|
+
debounceTimerRef.current = window.setTimeout(() => {
|
|
504
|
+
checkIfTruncated();
|
|
505
|
+
debounceTimerRef.current = null;
|
|
506
|
+
}, 150);
|
|
507
|
+
}, [checkIfTruncated]);
|
|
508
|
+
react.useEffect(() => {
|
|
509
|
+
const timeoutId = setTimeout(checkIfTruncated, 0);
|
|
510
|
+
if (resizeObserverRef.current) {
|
|
511
|
+
resizeObserverRef.current.disconnect();
|
|
512
|
+
}
|
|
513
|
+
resizeObserverRef.current = new ResizeObserver(() => {
|
|
514
|
+
debouncedCheckTruncation();
|
|
515
|
+
});
|
|
516
|
+
if (textRef.current) {
|
|
517
|
+
resizeObserverRef.current.observe(textRef.current);
|
|
518
|
+
}
|
|
519
|
+
window.addEventListener("resize", debouncedCheckTruncation);
|
|
520
|
+
return () => {
|
|
521
|
+
clearTimeout(timeoutId);
|
|
522
|
+
if (debounceTimerRef.current !== null) {
|
|
523
|
+
window.clearTimeout(debounceTimerRef.current);
|
|
524
|
+
}
|
|
525
|
+
if (resizeObserverRef.current) {
|
|
526
|
+
resizeObserverRef.current.disconnect();
|
|
527
|
+
resizeObserverRef.current = null;
|
|
528
|
+
}
|
|
529
|
+
window.removeEventListener("resize", debouncedCheckTruncation);
|
|
530
|
+
};
|
|
531
|
+
}, [debouncedCheckTruncation, checkIfTruncated, children]);
|
|
532
|
+
return tooltipLabel ? (jsxRuntime.jsx(reactComponents.Tooltip, { disabled: !isTextTruncated || hideTooltip, label: tooltipLabel, placement: "bottom", children: jsxRuntime.jsx(reactComponents.Text, { className: "truncate", ref: textRef, type: "span", weight: weight, children: children }) })) : (jsxRuntime.jsx("div", { onMouseDown: () => setHideTooltip(true), onMouseUp: () => setHideTooltip(false), children: jsxRuntime.jsx(reactComponents.Text, { className: "truncate", ref: textRef, type: "span", weight: weight, children: children }) }));
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* The `useCellsOffset` hook calculates column offsets for pinned columns ("left" or "right") in a table.
|
|
537
|
+
*
|
|
538
|
+
* @template TData The type of the row data in the table.
|
|
539
|
+
* @param {Header<TData, unknown>[][]} headerGroups - Array of table header groups.
|
|
540
|
+
* @param {string | null} draggingColumnId - The ID of the column currently being dragged. If null, no dragging is occurring.
|
|
541
|
+
* @returns {Array<object>} Returns an array of objects containing `leftOffsets` and `rightOffsets` for each header group.
|
|
542
|
+
*/
|
|
543
|
+
const useCellsOffset = (headerGroups, draggingColumnId) => {
|
|
544
|
+
return react.useMemo(() => {
|
|
545
|
+
return headerGroups.map(headers => {
|
|
546
|
+
const leftOffsets = {};
|
|
547
|
+
const rightOffsets = {};
|
|
548
|
+
let left = 0;
|
|
549
|
+
let right = 0;
|
|
550
|
+
const rightPinnedCells = headers.filter(cell => cell.column.getIsPinned() === "right");
|
|
551
|
+
for (let i = rightPinnedCells.length - 1; i >= 0; i--) {
|
|
552
|
+
const header = rightPinnedCells[i];
|
|
553
|
+
if (header?.column && header.column.id !== draggingColumnId) {
|
|
554
|
+
rightOffsets[header.column.id] = { id: header.column.id, offset: right, pinned: "right" };
|
|
555
|
+
right += header.column.getSize();
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
headers.forEach(header => {
|
|
559
|
+
if (header.column.id === draggingColumnId) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (header.column.getIsPinned() === "left") {
|
|
563
|
+
leftOffsets[header.column.id] = { id: header.column.id, offset: left, pinned: "left" };
|
|
564
|
+
left += header.column.getSize();
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
return {
|
|
568
|
+
leftOffsets,
|
|
569
|
+
rightOffsets,
|
|
570
|
+
};
|
|
571
|
+
});
|
|
572
|
+
}, [headerGroups, draggingColumnId]);
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Hook for handling column drag and drop functionality in a table
|
|
577
|
+
*
|
|
578
|
+
* @param options - Options including the ReactTable instance and optional callbacks
|
|
579
|
+
* @returns Object containing drag state and handler functions
|
|
580
|
+
*/
|
|
581
|
+
const useColumnDragDrop = ({ table, }) => {
|
|
582
|
+
const [draggingColumnId, setDraggingColumnId] = react.useState(null);
|
|
583
|
+
const [dropTargetColumnId, setDropTargetColumnId] = react.useState(null);
|
|
584
|
+
const [dropPosition, setDropPosition] = react.useState(null);
|
|
585
|
+
const [droppedColumnId, setDroppedColumnId] = react.useState(null);
|
|
586
|
+
const startDragging = react.useCallback((columnId) => {
|
|
587
|
+
setDraggingColumnId(columnId);
|
|
588
|
+
}, []);
|
|
589
|
+
const endDragging = react.useCallback(() => {
|
|
590
|
+
setDraggingColumnId(null);
|
|
591
|
+
setDropTargetColumnId(null);
|
|
592
|
+
setDropPosition(null);
|
|
593
|
+
}, []);
|
|
594
|
+
const setDropTarget = react.useCallback((columnId, position) => {
|
|
595
|
+
setDropTargetColumnId(columnId);
|
|
596
|
+
setDropPosition(position);
|
|
597
|
+
}, []);
|
|
598
|
+
// Helper function to generate the right positioning for a column move
|
|
599
|
+
const moveColumn = react.useCallback((columnOrder, fromId, toId, position) => {
|
|
600
|
+
const newColumnOrder = [...columnOrder];
|
|
601
|
+
const fromIndex = newColumnOrder.indexOf(fromId);
|
|
602
|
+
if (fromIndex !== -1) {
|
|
603
|
+
// Remove the moving column
|
|
604
|
+
newColumnOrder.splice(fromIndex, 1);
|
|
605
|
+
// Calculate the new position
|
|
606
|
+
let newIndex = newColumnOrder.indexOf(toId);
|
|
607
|
+
if (position === "right") {
|
|
608
|
+
newIndex += 1;
|
|
609
|
+
}
|
|
610
|
+
// Insert at the new position
|
|
611
|
+
newColumnOrder.splice(newIndex, 0, fromId);
|
|
612
|
+
}
|
|
613
|
+
return newColumnOrder;
|
|
614
|
+
}, []);
|
|
615
|
+
const handleDrop = react.useCallback(() => {
|
|
616
|
+
if (draggingColumnId && dropTargetColumnId && dropPosition) {
|
|
617
|
+
// Find the actual column objects
|
|
618
|
+
const columns = table.getAllColumns();
|
|
619
|
+
const fromColumn = columns.find(col => col.id === draggingColumnId);
|
|
620
|
+
const toColumn = columns.find(col => col.id === dropTargetColumnId);
|
|
621
|
+
if (fromColumn && toColumn) {
|
|
622
|
+
// Calculate new column order
|
|
623
|
+
const newColumnOrder = moveColumn(table.getState().columnOrder, draggingColumnId, dropTargetColumnId, dropPosition);
|
|
624
|
+
// Move the column in the table's column order
|
|
625
|
+
table.setColumnOrder(newColumnOrder);
|
|
626
|
+
setDroppedColumnId(draggingColumnId);
|
|
627
|
+
setTimeout(() => {
|
|
628
|
+
setDroppedColumnId(null);
|
|
629
|
+
}, 800);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
endDragging();
|
|
633
|
+
}, [draggingColumnId, dropTargetColumnId, dropPosition, endDragging, moveColumn, table]);
|
|
634
|
+
// Generate header props to apply drag and drop functionality
|
|
635
|
+
const getHeaderProps = react.useCallback((headerId, styles) => {
|
|
636
|
+
const header = table
|
|
637
|
+
.getHeaderGroups()
|
|
638
|
+
.flatMap(group => group.headers)
|
|
639
|
+
.find(h => h.id === headerId);
|
|
640
|
+
return {
|
|
641
|
+
className: "", // Don't add dragging-column class here, we'll add it in the Table component
|
|
642
|
+
"data-column-id": headerId, // Add this attribute for identification
|
|
643
|
+
style: draggingColumnId !== headerId && header
|
|
644
|
+
? {
|
|
645
|
+
width: header.getSize(),
|
|
646
|
+
minWidth: header.column.columnDef.minSize,
|
|
647
|
+
maxWidth: header.column.columnDef.maxSize,
|
|
648
|
+
textAlign: header.column.columnDef.meta?.alignment || "left",
|
|
649
|
+
flexGrow: header.column.columnDef.meta?.shouldGrow ? 1 : 0,
|
|
650
|
+
...styles,
|
|
651
|
+
}
|
|
652
|
+
: undefined,
|
|
653
|
+
onDragLeave: () => {
|
|
654
|
+
if (dropTargetColumnId === headerId) {
|
|
655
|
+
setDropTarget(null, null);
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
onDragOver: (e) => {
|
|
659
|
+
e.preventDefault();
|
|
660
|
+
if (draggingColumnId && draggingColumnId !== headerId) {
|
|
661
|
+
// Determine drop position based on mouse position
|
|
662
|
+
const rect = e.currentTarget.getBoundingClientRect();
|
|
663
|
+
const mouseX = e.clientX;
|
|
664
|
+
const threshold = rect.left + rect.width / 2;
|
|
665
|
+
const position = mouseX < threshold ? "left" : "right";
|
|
666
|
+
setDropTarget(headerId, position);
|
|
667
|
+
}
|
|
668
|
+
},
|
|
669
|
+
onDrop: (e) => {
|
|
670
|
+
e.preventDefault();
|
|
671
|
+
handleDrop();
|
|
672
|
+
},
|
|
673
|
+
};
|
|
674
|
+
}, [draggingColumnId, dropTargetColumnId, handleDrop, setDropTarget, table]);
|
|
675
|
+
// Generate drag handle props
|
|
676
|
+
const getDragHandleProps = react.useCallback((headerId, isPlaceholder, pinned) => ({
|
|
677
|
+
className: "invisible mr-1 cursor-grab text-gray-400 group-hover:visible",
|
|
678
|
+
draggable: !pinned && !isPlaceholder,
|
|
679
|
+
onDragEnd: (e) => {
|
|
680
|
+
endDragging();
|
|
681
|
+
e.preventDefault();
|
|
682
|
+
e.stopPropagation();
|
|
683
|
+
},
|
|
684
|
+
onDragStart: (e) => {
|
|
685
|
+
e.dataTransfer.setData("columnId", headerId);
|
|
686
|
+
e.dataTransfer.effectAllowed = "move";
|
|
687
|
+
// Create custom drag ghost element
|
|
688
|
+
const column = table.getColumn(headerId);
|
|
689
|
+
if (column) {
|
|
690
|
+
// Get column header text
|
|
691
|
+
const headerText = typeof column.columnDef.header === "string" ? column.columnDef.header : column.id;
|
|
692
|
+
// Create custom drag ghost element
|
|
693
|
+
const dragGhost = document.createElement("div");
|
|
694
|
+
dragGhost.className =
|
|
695
|
+
"bg-white shadow-md rounded p-3 flex items-center opacity-50 gap-1 border border-gray-300";
|
|
696
|
+
dragGhost.style.position = "absolute";
|
|
697
|
+
dragGhost.style.top = "-1000px"; // Position off-screen initially
|
|
698
|
+
dragGhost.style.zIndex = "9999";
|
|
699
|
+
dragGhost.style.pointerEvents = "none";
|
|
700
|
+
// Add minimum width to prevent narrow ghost for short column names
|
|
701
|
+
dragGhost.style.minWidth = "120px";
|
|
702
|
+
// Add drag icon with arrows in all directions
|
|
703
|
+
const iconElement = document.createElement("span");
|
|
704
|
+
iconElement.innerHTML = `
|
|
705
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
706
|
+
<path d="M5 3L2 6L5 9" fill="none"/>
|
|
707
|
+
<path d="M11 3L14 6L11 9" fill="none"/>
|
|
708
|
+
<path d="M2 6H14" fill="none"/>
|
|
709
|
+
</svg>
|
|
710
|
+
|
|
711
|
+
`;
|
|
712
|
+
iconElement.className = "text-gray-500 flex-shrink-0 pl-4 pt-1";
|
|
713
|
+
iconElement.style.marginRight = "6px";
|
|
714
|
+
// Add column title
|
|
715
|
+
const titleElement = document.createElement("span");
|
|
716
|
+
titleElement.textContent = headerText;
|
|
717
|
+
titleElement.className = "font-medium text-sm";
|
|
718
|
+
// Append elements to the drag ghost
|
|
719
|
+
dragGhost.appendChild(iconElement);
|
|
720
|
+
dragGhost.appendChild(titleElement);
|
|
721
|
+
// Append to body temporarily (will be removed after drag)
|
|
722
|
+
document.body.appendChild(dragGhost);
|
|
723
|
+
// Set custom drag image
|
|
724
|
+
e.dataTransfer.setDragImage(dragGhost, 20, 20);
|
|
725
|
+
// Clean up the ghost element after drag operation completes
|
|
726
|
+
setTimeout(() => {
|
|
727
|
+
document.body.removeChild(dragGhost);
|
|
728
|
+
}, 0);
|
|
729
|
+
}
|
|
730
|
+
startDragging(headerId);
|
|
731
|
+
e.stopPropagation();
|
|
732
|
+
},
|
|
733
|
+
}), [endDragging, startDragging, table]);
|
|
734
|
+
// Render the drop indicator
|
|
735
|
+
const renderDropIndicator = react.useCallback((columnId) => {
|
|
736
|
+
if (dropTargetColumnId === columnId && dropPosition) {
|
|
737
|
+
return (jsxRuntime.jsx("div", { className: "bg-primary-300 absolute bottom-[-2px] top-[-2px] w-[3px]", style: {
|
|
738
|
+
height: "calc(100% + 4px)",
|
|
739
|
+
overflow: "visible",
|
|
740
|
+
// Position to overlay the parent's border
|
|
741
|
+
left: dropPosition === "left" ? "-2px" : "auto",
|
|
742
|
+
right: dropPosition === "right" ? "-2px" : "auto",
|
|
743
|
+
pointerEvents: "none",
|
|
744
|
+
zIndex: 100000,
|
|
745
|
+
// Add a subtle shadow to help it stand out
|
|
746
|
+
boxShadow: "0 0 2px rgba(0, 0, 0, 0.2)",
|
|
747
|
+
} }));
|
|
748
|
+
}
|
|
749
|
+
return null;
|
|
750
|
+
}, [dropTargetColumnId, dropPosition]);
|
|
751
|
+
// Check if a cell is a drop target
|
|
752
|
+
const isCellDropTarget = react.useCallback((columnId) => dropTargetColumnId === columnId, [dropTargetColumnId]);
|
|
753
|
+
// Generate cell props to collapse dragged columns
|
|
754
|
+
const getCellProps = react.useCallback((columnId, styles) => {
|
|
755
|
+
// Find a representative cell for this column to get sizing info
|
|
756
|
+
const column = table.getColumn(columnId);
|
|
757
|
+
return {
|
|
758
|
+
className: draggingColumnId === columnId ? "dragging-column" : "",
|
|
759
|
+
style: draggingColumnId !== columnId && column
|
|
760
|
+
? {
|
|
761
|
+
width: column.getSize(),
|
|
762
|
+
minWidth: column.columnDef.minSize,
|
|
763
|
+
maxWidth: column.columnDef.maxSize,
|
|
764
|
+
textAlign: column.columnDef.meta?.alignment || "left",
|
|
765
|
+
flexGrow: column.columnDef.meta?.shouldGrow ? 1 : 0,
|
|
766
|
+
...styles,
|
|
767
|
+
}
|
|
768
|
+
: undefined,
|
|
769
|
+
};
|
|
770
|
+
}, [draggingColumnId, table]);
|
|
771
|
+
return react.useMemo(() => ({
|
|
772
|
+
draggingColumnId,
|
|
773
|
+
dropTargetColumnId,
|
|
774
|
+
dropPosition,
|
|
775
|
+
droppedColumnId,
|
|
776
|
+
getHeaderProps,
|
|
777
|
+
getDragHandleProps,
|
|
778
|
+
renderDropIndicator,
|
|
779
|
+
isCellDropTarget,
|
|
780
|
+
getCellProps,
|
|
781
|
+
}), [
|
|
782
|
+
draggingColumnId,
|
|
783
|
+
dropTargetColumnId,
|
|
784
|
+
dropPosition,
|
|
785
|
+
droppedColumnId,
|
|
786
|
+
getHeaderProps,
|
|
787
|
+
getDragHandleProps,
|
|
788
|
+
renderDropIndicator,
|
|
789
|
+
isCellDropTarget,
|
|
790
|
+
getCellProps,
|
|
791
|
+
]);
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* `ColumnActions` component to render column action controls for a table.
|
|
796
|
+
*
|
|
797
|
+
* @template TData The type of the table row data.
|
|
798
|
+
* @param {object} props Component properties.
|
|
799
|
+
* @param {Header<TData, unknown>} props.header The header of the current column provided by React Table.
|
|
800
|
+
* @param {number} props.visibleColumnsCount The number of visible columns in the table.
|
|
801
|
+
*/
|
|
802
|
+
const ColumnActions = ({ header, visibleColumnsCount, }) => {
|
|
803
|
+
const [t] = useTranslation();
|
|
804
|
+
const isSorted = header.column.getIsSorted();
|
|
805
|
+
const canSort = header.column.getCanSort();
|
|
806
|
+
const canHide = header.column.getCanHide();
|
|
807
|
+
const canPin = header.column.getCanPin();
|
|
808
|
+
const isPinned = header.column.getIsPinned();
|
|
809
|
+
const isAnyActionAvailable = canSort || canPin || (canHide && visibleColumnsCount > 1);
|
|
810
|
+
if (!isAnyActionAvailable) {
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
return (jsxRuntime.jsx(reactComponents.MoreMenu, { customButton: jsxRuntime.jsx(reactComponents.IconButton, { icon: jsxRuntime.jsx(reactComponents.Icon, { name: "EllipsisVertical", size: "small" }), onClick: event => event.stopPropagation(), size: "extraSmall", variant: "ghost-neutral" }), children: close => (jsxRuntime.jsxs(reactComponents.MenuList, { children: [canSort && (isSorted === "desc" || !isSorted) ? (jsxRuntime.jsx(reactComponents.MenuItem, { label: t("table.columnActions.sortAscending"), onClick: () => header.column.toggleSorting(false), prefix: jsxRuntime.jsx(reactComponents.Icon, { name: "ArrowUp", size: "medium" }) })) : null, canSort && (isSorted === "asc" || !isSorted) ? (jsxRuntime.jsx(reactComponents.MenuItem, { label: t("table.columnActions.sortDescending"), onClick: () => header.column.toggleSorting(true), prefix: jsxRuntime.jsx(reactComponents.Icon, { name: "ArrowDown", size: "medium" }) })) : null, canSort && isSorted ? (jsxRuntime.jsx(reactComponents.MenuItem, { label: t("table.columnActions.clearSorting"), onClick: () => header.column.clearSorting(), prefix: jsxRuntime.jsx(reactComponents.Icon, { name: "ArrowsUpDown", size: "medium" }) })) : null, canHide && visibleColumnsCount > 1 ? (jsxRuntime.jsx(reactComponents.MenuItem, { label: t("table.columnActions.hideColumn"), onClick: () => header.column.toggleVisibility(false), prefix: jsxRuntime.jsx(reactComponents.Icon, { name: "EyeSlash", size: "medium" }) })) : null, canPin ? (jsxRuntime.jsx(reactComponents.MenuItem, { label: t(isPinned ? "table.columnActions.unPinColumn" : "table.columnActions.pinColumn"), onClick: () => {
|
|
814
|
+
header.column.pin(isPinned ? false : "left");
|
|
815
|
+
close();
|
|
816
|
+
}, prefix: jsxRuntime.jsx(reactComponents.Icon, { name: isPinned ? "Unpin" : "Pin", size: "medium" }) })) : null] })) }));
|
|
817
|
+
};
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* The ColumnSorting is used in the table header to indicate the current sort order of the column.
|
|
821
|
+
* This is a visual only component and does not handle the sorting logic.
|
|
822
|
+
*
|
|
823
|
+
* In most cases, we recommend using the Table Component which already handles sorting indication.
|
|
824
|
+
* However, if you need to construct a custom table, the Table Base Components can be utilized.
|
|
825
|
+
*
|
|
826
|
+
* @param {object} props - Props for the sorting indicator.
|
|
827
|
+
* @param {boolean} [props.canSort] - Indicates the header sorting ability state (ascending/descending).
|
|
828
|
+
* @param {boolean} [props.sortingState=false] - Indicates the sorting state (ascending/descending).
|
|
829
|
+
* @returns {ReactElement | null} A React element representing the sorting indicator.
|
|
830
|
+
*/
|
|
831
|
+
const ColumnSorting = ({ canSort, sortingState = false }) => {
|
|
832
|
+
if (!canSort || !sortingState) {
|
|
833
|
+
return null;
|
|
834
|
+
}
|
|
835
|
+
return (jsxRuntime.jsx("div", { className: "flex items-center", children: jsxRuntime.jsx(reactComponents.Icon, { className: "text-blue-600", name: sortingState === "asc" ? "ArrowUp" : "ArrowDown", size: "small" }) }));
|
|
836
|
+
};
|
|
837
|
+
|
|
476
838
|
/**
|
|
477
839
|
* Table component for displaying large sets of data in tables with infinite scroll, sorting, filtering and others.
|
|
478
840
|
*
|
|
@@ -482,7 +844,7 @@ const Sorting = ({ columns, }) => {
|
|
|
482
844
|
/**
|
|
483
845
|
*
|
|
484
846
|
*/
|
|
485
|
-
const Table = ({ rowHeight =
|
|
847
|
+
const Table = ({ rowHeight = 50, ...props }) => {
|
|
486
848
|
//we need a reference to the scrolling element for logic down below
|
|
487
849
|
const tableContainerRef = react.useRef(null);
|
|
488
850
|
const [t] = useTranslation();
|
|
@@ -492,23 +854,81 @@ const Table = ({ rowHeight = 75, ...props }) => {
|
|
|
492
854
|
rowSize: props.getRowModel().rows.length || 0,
|
|
493
855
|
rowHeight,
|
|
494
856
|
});
|
|
857
|
+
const visibleColumns = react.useMemo(() => props.getAllColumns().filter(column => column.getIsVisible()), [props]);
|
|
858
|
+
const visibleColumnsCount = visibleColumns.length;
|
|
859
|
+
const headerGroups = react.useMemo(() => props.getHeaderGroups(), [props]);
|
|
860
|
+
const totalVisibleColumnsWidth = react.useMemo(() => {
|
|
861
|
+
return visibleColumns.reduce((sum, column) => sum + column.getSize(), 0);
|
|
862
|
+
}, [visibleColumns]);
|
|
863
|
+
const hasAnyGrowingColumn = react.useMemo(() => {
|
|
864
|
+
return visibleColumns.some(column => column.columnDef.meta?.shouldGrow === true);
|
|
865
|
+
}, [visibleColumns]);
|
|
866
|
+
const [containWholeTable, setContainWholeTable] = react.useState(false);
|
|
867
|
+
react.useEffect(() => {
|
|
868
|
+
const updateContainWholeTable = () => {
|
|
869
|
+
if (tableContainerRef.current) {
|
|
870
|
+
const containerWidth = tableContainerRef.current.offsetWidth;
|
|
871
|
+
setContainWholeTable(totalVisibleColumnsWidth > containerWidth);
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
updateContainWholeTable();
|
|
875
|
+
window.addEventListener("resize", updateContainWholeTable);
|
|
876
|
+
return () => {
|
|
877
|
+
window.removeEventListener("resize", updateContainWholeTable);
|
|
878
|
+
};
|
|
879
|
+
}, [totalVisibleColumnsWidth]);
|
|
880
|
+
const headerOrder = react.useMemo(() => {
|
|
881
|
+
return props
|
|
882
|
+
.getHeaderGroups()
|
|
883
|
+
.flatMap(group => group.headers)
|
|
884
|
+
.map(header => header.id);
|
|
885
|
+
}, [props]);
|
|
886
|
+
const orderedVisibleColumns = react.useMemo(() => {
|
|
887
|
+
return headerOrder.map(headerId => visibleColumns.find(col => col.id === headerId)).filter(Boolean);
|
|
888
|
+
}, [headerOrder, visibleColumns]);
|
|
889
|
+
const lastUnpinnedColumnId = react.useMemo(() => {
|
|
890
|
+
const unpinnedColumns = orderedVisibleColumns.filter(column => column?.getIsPinned() !== "right");
|
|
891
|
+
return unpinnedColumns[unpinnedColumns.length - 1]?.id;
|
|
892
|
+
}, [orderedVisibleColumns]);
|
|
893
|
+
const isLastUnpinned = react.useCallback((columnId) => columnId === lastUnpinnedColumnId, [lastUnpinnedColumnId]);
|
|
894
|
+
// Add our drag and drop hook with enhanced functionality
|
|
895
|
+
const { getHeaderProps, getDragHandleProps, renderDropIndicator, isCellDropTarget, draggingColumnId, droppedColumnId, getCellProps, } = useColumnDragDrop({
|
|
896
|
+
table: props,
|
|
897
|
+
});
|
|
898
|
+
const offsets = useCellsOffset(headerGroups.map(group => group.headers), draggingColumnId);
|
|
495
899
|
const hasResults = props.getRowModel().rows.length > 0;
|
|
496
|
-
return (jsxRuntime.jsxs(reactComponents.Card, { className: tailwindMerge.twMerge("flex flex-col overflow-hidden", props.className), dataTestId: props.dataTestId, children: [props.headerLeftActions || props.headerRightActions ? (jsxRuntime.jsxs("div", { className: "z-default flex justify-between gap-2 p-2", children: [jsxRuntime.jsx("div", { className: "flex items-center", children: props.headerLeftActions }), jsxRuntime.jsx("div", { className: "flex items-center", children: props.headerRightActions })] })) : null, jsxRuntime.jsx("div", { className: "h-full overflow-x-auto overflow-y-scroll border-b border-t border-
|
|
900
|
+
return (jsxRuntime.jsxs(reactComponents.Card, { className: tailwindMerge.twMerge("flex flex-col overflow-hidden", props.className), dataTestId: props.dataTestId, children: [props.headerLeftActions || props.headerRightActions ? (jsxRuntime.jsxs("div", { className: "z-default flex justify-between gap-2 p-2", children: [jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: props.headerLeftActions }), jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: props.headerRightActions })] })) : null, jsxRuntime.jsx("div", { className: "h-full overflow-x-auto overflow-y-scroll border-b border-t border-neutral-200", onScroll: e => fetchMoreOnBottomReached(e.target), ref: tableContainerRef, children: jsxRuntime.jsxs(reactTableBaseComponents.TableRoot, { style: {
|
|
497
901
|
height: hasResults ? "auto" : "100%",
|
|
498
902
|
width: "100%",
|
|
499
903
|
position: "relative",
|
|
500
|
-
}, children: [jsxRuntime.jsx(reactTableBaseComponents.Thead, { className: "z-default", children:
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
904
|
+
}, children: [jsxRuntime.jsx(reactTableBaseComponents.Thead, { className: "z-default", children: headerGroups.map((headerGroup, index) => {
|
|
905
|
+
return (jsxRuntime.jsx(reactTableBaseComponents.Tr, { className: "flex", children: headerGroup.headers.map(header => {
|
|
906
|
+
const tooltipLabel = header.column.columnDef.meta?.tootipLabel ??
|
|
907
|
+
(typeof header.column.columnDef.header === "string" ? header.column.columnDef.header : "");
|
|
908
|
+
const canSort = header.column.getCanSort();
|
|
909
|
+
const offsetData = offsets[index]?.leftOffsets[header.id] || offsets[index]?.rightOffsets[header.id];
|
|
910
|
+
const { offset = 0, pinned = "" } = offsetData ?? {};
|
|
911
|
+
const position = pinned === "right" && !containWholeTable && !hasAnyGrowingColumn ? { position: "absolute" } : {};
|
|
912
|
+
return (react.createElement(reactTableBaseComponents.Th, { ...getHeaderProps(header.id, {
|
|
913
|
+
[pinned]: offset,
|
|
914
|
+
...position,
|
|
915
|
+
}), className: cvaTh({
|
|
916
|
+
pinned: pinned || false,
|
|
917
|
+
lastUnpinnedWithoutBorder: isLastUnpinned(header.column.id) && (containWholeTable || hasAnyGrowingColumn),
|
|
918
|
+
isColumnDragging: draggingColumnId === header.id,
|
|
919
|
+
isColumnDropped: droppedColumnId === header.id,
|
|
920
|
+
}), "data-pinned": pinned, key: header.id },
|
|
921
|
+
renderDropIndicator(header.id),
|
|
922
|
+
header.isPlaceholder ? null : (jsxRuntime.jsxs("div", { ...getDragHandleProps(header.id, header.isPlaceholder, pinned), className: tailwindMerge.twMerge(cvaColumnHeaderContainer({
|
|
923
|
+
sortable: canSort,
|
|
924
|
+
})), onClick: header.column.getToggleSortingHandler(), children: [jsxRuntime.jsxs("div", { className: "flex flex-1 items-center gap-1 truncate", children: [header.column.columnDef.header || header.column.columnDef.meta?.subHeader ? (jsxRuntime.jsxs("span", { className: "subtle min-w-0 text-xs", children: [jsxRuntime.jsx(TextWithTooltip, { tooltipLabel: tooltipLabel, weight: "bold", children: reactTable.flexRender(header.column.columnDef.header, header.getContext()) }), header.column.columnDef.meta?.subHeader
|
|
925
|
+
? header.column.columnDef.meta.subHeader
|
|
926
|
+
: null] })) : null, jsxRuntime.jsx(ColumnSorting, { canSort: canSort, sortingState: header.column.getIsSorted() })] }), jsxRuntime.jsxs("div", { className: "flex flex-none items-center", children: [props.renderFilterButton && header.column.columnDef.meta?.filterName
|
|
927
|
+
? props.renderFilterButton(header.column.columnDef.meta.filterName)
|
|
928
|
+
: null, jsxRuntime.jsx(ColumnActions, { header: header, visibleColumnsCount: visibleColumnsCount })] })] })),
|
|
929
|
+
!header.column.columnDef.meta?.shouldGrow && header.column.getCanResize() ? (jsxRuntime.jsx(reactTableBaseComponents.ResizeHandle, { isResizing: header.column.getIsResizing(), onMouseDown: header.getResizeHandler(), onTouchStart: header.getResizeHandler() })) : null));
|
|
930
|
+
}) }, headerGroup.id));
|
|
931
|
+
}) }), hasResults ? (jsxRuntime.jsx(reactTableBaseComponents.Tbody, { className: "text-sm font-normal", style: {
|
|
512
932
|
height: `${getTotalSize()}px`,
|
|
513
933
|
flexGrow: 1,
|
|
514
934
|
}, children: getVirtualItems().map((virtualRow, index) => {
|
|
@@ -517,14 +937,14 @@ const Table = ({ rowHeight = 75, ...props }) => {
|
|
|
517
937
|
return null;
|
|
518
938
|
}
|
|
519
939
|
else {
|
|
520
|
-
return (jsxRuntime.jsx(reactTableBaseComponents.Tr, { className: reactComponents.cvaInteractableItem({
|
|
940
|
+
return (jsxRuntime.jsx(reactTableBaseComponents.Tr, { className: tailwindMerge.twMerge("group", reactComponents.cvaInteractableItem({
|
|
521
941
|
cursor: (!!props.onRowClick || row.getCanSelect()) &&
|
|
522
942
|
props.isRowClickable &&
|
|
523
943
|
props.isRowClickable(row.original)
|
|
524
944
|
? "pointer"
|
|
525
945
|
: "default",
|
|
526
946
|
selected: "auto",
|
|
527
|
-
}), dataTestId: `table-body-row-${virtualRow.index}`, layout: "flex", onClick: () => {
|
|
947
|
+
})), dataTestId: `table-body-row-${virtualRow.index}`, layout: "flex", onClick: () => {
|
|
528
948
|
if (props.onRowClick) {
|
|
529
949
|
props.onRowClick(row);
|
|
530
950
|
}
|
|
@@ -535,18 +955,32 @@ const Table = ({ rowHeight = 75, ...props }) => {
|
|
|
535
955
|
height: `${virtualRow.size}px`,
|
|
536
956
|
transform: `translateY(${virtualRow.start - index * virtualRow.size}px)`,
|
|
537
957
|
}, children: row.getVisibleCells().map(cell => {
|
|
538
|
-
|
|
958
|
+
// Processing the cell identifier:
|
|
959
|
+
// 1. Remove the segment enclosed by "___" (e.g., "___Asset___").
|
|
960
|
+
// 2. Remove the initial segment of the string, including the first "_" (e.g., "25_" or "7fa7655d-3bc2-402b-99dd-5ea1f64c3b96_").
|
|
961
|
+
// The final value will be the key part of the identifier, e.g., "DEVICE_SERIAL_NUMBER".
|
|
962
|
+
const headerId = cell.id.replace(/___[^_]+___/, "").replace(/^[^_]*_/, "");
|
|
963
|
+
const offsetData = offsets[0]?.leftOffsets[headerId] || offsets[0]?.rightOffsets[headerId];
|
|
964
|
+
const { offset = 0, pinned = "" } = offsetData ?? {};
|
|
965
|
+
const position = pinned === "right" && !containWholeTable && !hasAnyGrowingColumn
|
|
966
|
+
? { position: "absolute", height: "-webkit-fill-available" }
|
|
967
|
+
: {};
|
|
968
|
+
return (react.createElement(reactTableBaseComponents.Td, { ...getCellProps(cell.column.id, {
|
|
969
|
+
[pinned]: offset,
|
|
970
|
+
...position,
|
|
971
|
+
}), className: cvaTd({
|
|
972
|
+
pinned: pinned || false,
|
|
973
|
+
lastUnpinnedWithoutBorder: isLastUnpinned(cell.column.id) && (containWholeTable || hasAnyGrowingColumn),
|
|
974
|
+
isColumnDragging: draggingColumnId === cell.column.id,
|
|
975
|
+
isColumnDropped: droppedColumnId === cell.column.id,
|
|
976
|
+
}), "data-pinned": pinned, key: cell.id, onClick: event => {
|
|
539
977
|
// prevent onRowClick action when clicking on a "selectable" cell with checkbox
|
|
540
978
|
if (props.selectionColId && cell.column.id === props.selectionColId) {
|
|
541
979
|
event.stopPropagation();
|
|
542
980
|
}
|
|
543
|
-
},
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
maxWidth: cell.column.columnDef.maxSize,
|
|
547
|
-
textAlign: cell.column.columnDef.meta?.alignment || "left",
|
|
548
|
-
flexGrow: cell.column.columnDef.meta?.shouldGrow ? 1 : 0,
|
|
549
|
-
}, children: jsxRuntime.jsx("div", { className: "grid h-full items-center", children: reactTable.flexRender(cell.column.columnDef.cell, cell.getContext()) }) }, cell.id));
|
|
981
|
+
} },
|
|
982
|
+
isCellDropTarget(cell.column.id) && renderDropIndicator(cell.column.id),
|
|
983
|
+
jsxRuntime.jsx("div", { className: "grid h-full items-center p-0", children: reactTable.flexRender(cell.column.columnDef.cell, cell.getContext()) })));
|
|
550
984
|
}) }, row.id));
|
|
551
985
|
}
|
|
552
986
|
}) })) : (jsxRuntime.jsx("tbody", { className: cvaTBody({ emptyState: !props.loading && !props.noDataMessage }), children: jsxRuntime.jsx("tr", { children: jsxRuntime.jsx("td", { className: "b-0", children: props.loading ? (jsxRuntime.jsx(reactComponents.Spinner, { centering: "centered", containerClassName: "absolute inset-0" })) : props.noDataMessage ? (props.noDataMessage) : (jsxRuntime.jsx(reactComponents.EmptyState, { className: "absolute inset-0", description: t("table.noResults"), image: "SEARCH_DOCUMENT", ...props.emptyState })) }) }) }))] }) }), props.hideFooter ? null : (jsxRuntime.jsxs("div", { className: "flex items-center p-2", children: [jsxRuntime.jsx("div", { className: "whitespace-nowrap text-xs font-medium text-neutral-600", children: t(props.pagination?.pageInfo?.isCountCapped ? "table.pagination.full.capped" : "table.pagination.full", {
|
|
@@ -564,6 +998,71 @@ const cvaTBody = cssClassVarianceUtilities.cvaMerge(["min-h-[40dvh]"], {
|
|
|
564
998
|
emptyState: false,
|
|
565
999
|
},
|
|
566
1000
|
});
|
|
1001
|
+
const cvaColumnHeaderContainer = cssClassVarianceUtilities.cvaMerge(["flex", "min-w-0", "w-full", "items-center", "self-center", "gap-2", "overflow-hidden"], {
|
|
1002
|
+
variants: {
|
|
1003
|
+
sortable: {
|
|
1004
|
+
true: "cursor-pointer select-none",
|
|
1005
|
+
},
|
|
1006
|
+
},
|
|
1007
|
+
defaultVariants: {
|
|
1008
|
+
sortable: false,
|
|
1009
|
+
},
|
|
1010
|
+
});
|
|
1011
|
+
const cvaTh = cssClassVarianceUtilities.cvaMerge(["flex", "relative"], {
|
|
1012
|
+
variants: {
|
|
1013
|
+
pinned: {
|
|
1014
|
+
left: "sticky left-0 z-[1] bg-neutral-100",
|
|
1015
|
+
right: "sticky right-0 z-[1] border-r-0 bg-neutral-100",
|
|
1016
|
+
false: "relative",
|
|
1017
|
+
},
|
|
1018
|
+
lastUnpinnedWithoutBorder: {
|
|
1019
|
+
true: "border-r-0",
|
|
1020
|
+
false: "",
|
|
1021
|
+
},
|
|
1022
|
+
isColumnDragging: {
|
|
1023
|
+
true: "dragging-column",
|
|
1024
|
+
false: "",
|
|
1025
|
+
},
|
|
1026
|
+
isColumnDropped: {
|
|
1027
|
+
true: "column-dropped",
|
|
1028
|
+
false: "",
|
|
1029
|
+
},
|
|
1030
|
+
},
|
|
1031
|
+
defaultVariants: {
|
|
1032
|
+
pinned: false,
|
|
1033
|
+
isColumnDragging: false,
|
|
1034
|
+
isColumnDropped: false,
|
|
1035
|
+
lastUnpinnedWithoutBorder: false,
|
|
1036
|
+
},
|
|
1037
|
+
});
|
|
1038
|
+
const sharedPinnedStyles = "sticky z-[1] group-has-[:checked]:bg-primary-50 group-hover:has-[:checked]:bg-primary-50 group-focus-within:has-[:checked]:bg-primary-50 bg-white group-hover:bg-neutral-100";
|
|
1039
|
+
const cvaTd = cssClassVarianceUtilities.cvaMerge(["relative"], {
|
|
1040
|
+
variants: {
|
|
1041
|
+
pinned: {
|
|
1042
|
+
left: `${sharedPinnedStyles} left-0`,
|
|
1043
|
+
right: `${sharedPinnedStyles} border-r-0`,
|
|
1044
|
+
false: "",
|
|
1045
|
+
},
|
|
1046
|
+
lastUnpinnedWithoutBorder: {
|
|
1047
|
+
true: "border-r-0",
|
|
1048
|
+
false: "",
|
|
1049
|
+
},
|
|
1050
|
+
isColumnDragging: {
|
|
1051
|
+
true: "dragging-column",
|
|
1052
|
+
false: "",
|
|
1053
|
+
},
|
|
1054
|
+
isColumnDropped: {
|
|
1055
|
+
true: "column-dropped",
|
|
1056
|
+
false: "",
|
|
1057
|
+
},
|
|
1058
|
+
},
|
|
1059
|
+
defaultVariants: {
|
|
1060
|
+
pinned: false,
|
|
1061
|
+
isColumnDragging: false,
|
|
1062
|
+
isColumnDropped: false,
|
|
1063
|
+
lastUnpinnedWithoutBorder: false,
|
|
1064
|
+
},
|
|
1065
|
+
});
|
|
567
1066
|
|
|
568
1067
|
/**
|
|
569
1068
|
* Creates and returns a memoized instance of a columnHelper
|
|
@@ -582,11 +1081,13 @@ const useColumnHelper = () => {
|
|
|
582
1081
|
* @returns {TableOptionsProps<TData>} An object containing the React Table instance and associated state management functions.
|
|
583
1082
|
*/
|
|
584
1083
|
const useTable = ({ onTableStateChange, initialState, columns, ...reactTableProps }) => {
|
|
1084
|
+
const stableColumns = react.useMemo(() => JSON.stringify(columns), [columns]);
|
|
585
1085
|
const updatedInitialColumnVisibility = react.useMemo(() => {
|
|
1086
|
+
const columnsList = JSON.parse(stableColumns);
|
|
586
1087
|
const initialStateColumnVisibility = initialState?.columnVisibility;
|
|
587
1088
|
const resultFromInitialState = initialStateColumnVisibility
|
|
588
|
-
? Object.fromEntries(
|
|
589
|
-
.map(column => {
|
|
1089
|
+
? Object.fromEntries(columnsList
|
|
1090
|
+
.map((column) => {
|
|
590
1091
|
if (column.id && initialStateColumnVisibility[column.id] !== undefined) {
|
|
591
1092
|
return [column.id, initialStateColumnVisibility[column.id]];
|
|
592
1093
|
}
|
|
@@ -594,26 +1095,45 @@ const useTable = ({ onTableStateChange, initialState, columns, ...reactTableProp
|
|
|
594
1095
|
})
|
|
595
1096
|
.filter(sharedUtils.nonNullable))
|
|
596
1097
|
: {};
|
|
597
|
-
|
|
1098
|
+
columnsList.forEach((column) => {
|
|
598
1099
|
if (column.id && resultFromInitialState[column.id] === undefined) {
|
|
599
|
-
resultFromInitialState[column.id] = column.meta?.hiddenByDefault
|
|
1100
|
+
resultFromInitialState[column.id] = column.meta?.hiddenByDefault !== true;
|
|
600
1101
|
}
|
|
601
1102
|
});
|
|
602
1103
|
return resultFromInitialState;
|
|
603
|
-
}, [
|
|
1104
|
+
}, [stableColumns, initialState?.columnVisibility]);
|
|
604
1105
|
const updatedInitialColumnOrder = react.useMemo(() => {
|
|
605
1106
|
const resultFromInitialState = initialState?.columnOrder || [];
|
|
606
|
-
|
|
1107
|
+
JSON.parse(stableColumns).forEach((column) => {
|
|
607
1108
|
if (column.id && !resultFromInitialState.includes(column.id)) {
|
|
608
1109
|
resultFromInitialState.push(column.id);
|
|
609
1110
|
}
|
|
610
1111
|
});
|
|
611
1112
|
return resultFromInitialState;
|
|
612
|
-
}, [
|
|
613
|
-
const
|
|
1113
|
+
}, [stableColumns, initialState?.columnOrder]);
|
|
1114
|
+
const stableUpdatedInitialColumnPinning = react.useMemo(() => {
|
|
1115
|
+
const pinningState = { left: [], right: [] };
|
|
1116
|
+
JSON.parse(stableColumns).forEach((column) => {
|
|
1117
|
+
if (column.id) {
|
|
1118
|
+
if (column.meta?.pinned === "left") {
|
|
1119
|
+
if (pinningState.left) {
|
|
1120
|
+
pinningState.left.push(column.id);
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
else if (column.meta?.pinned === "right") {
|
|
1124
|
+
if (pinningState.right) {
|
|
1125
|
+
pinningState.right.push(column.id);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
});
|
|
1130
|
+
return pinningState;
|
|
1131
|
+
}, [stableColumns]);
|
|
1132
|
+
const [columnVisibility, setColumnVisibility] = react.useState(reactTableProps.state?.columnVisibility || updatedInitialColumnVisibility);
|
|
614
1133
|
const [columnOrder, setColumnOrder] = react.useState(reactTableProps.state?.columnOrder || updatedInitialColumnOrder);
|
|
615
1134
|
const [sorting, setSorting] = react.useState(reactTableProps.state?.sorting || initialState?.sorting || []);
|
|
616
1135
|
const [columnSizing, setColumnSizing] = react.useState(reactTableProps.state?.columnSizing || initialState?.columnSizing || {});
|
|
1136
|
+
const [columnPinning, setColumnPinning] = react.useState(reactTableProps.state?.columnPinning || stableUpdatedInitialColumnPinning);
|
|
617
1137
|
react.useEffect(() => {
|
|
618
1138
|
if (initialState && sharedUtils.objectKeys(initialState).length > 0) {
|
|
619
1139
|
setColumnVisibility(updatedInitialColumnVisibility);
|
|
@@ -621,19 +1141,34 @@ const useTable = ({ onTableStateChange, initialState, columns, ...reactTableProp
|
|
|
621
1141
|
setSorting(initialState.sorting || []);
|
|
622
1142
|
setColumnSizing(initialState.columnSizing || {});
|
|
623
1143
|
}
|
|
624
|
-
}, [initialState, updatedInitialColumnOrder, updatedInitialColumnVisibility]);
|
|
1144
|
+
}, [initialState, updatedInitialColumnOrder, stableUpdatedInitialColumnPinning, updatedInitialColumnVisibility]);
|
|
1145
|
+
react.useEffect(() => {
|
|
1146
|
+
const mergedLeft = initialState?.columnPinning?.left?.length
|
|
1147
|
+
? [...initialState.columnPinning.left]
|
|
1148
|
+
: [...(stableUpdatedInitialColumnPinning.left || [])];
|
|
1149
|
+
const mergedRight = initialState?.columnPinning?.right?.length
|
|
1150
|
+
? [...initialState.columnPinning.right]
|
|
1151
|
+
: [...(stableUpdatedInitialColumnPinning.right || [])];
|
|
1152
|
+
const filteredRight = mergedRight.filter(column => !mergedLeft.includes(column));
|
|
1153
|
+
const mergedPinningState = {
|
|
1154
|
+
left: mergedLeft,
|
|
1155
|
+
right: filteredRight,
|
|
1156
|
+
};
|
|
1157
|
+
setColumnPinning(mergedPinningState);
|
|
1158
|
+
}, [initialState?.columnPinning, stableUpdatedInitialColumnPinning]);
|
|
625
1159
|
const state = react.useMemo(() => {
|
|
626
1160
|
return {
|
|
627
1161
|
sorting,
|
|
628
1162
|
columnVisibility,
|
|
629
1163
|
columnOrder,
|
|
630
1164
|
columnSizing,
|
|
1165
|
+
columnPinning,
|
|
631
1166
|
...reactTableProps.state,
|
|
632
1167
|
};
|
|
633
|
-
}, [sorting, columnVisibility, columnOrder, columnSizing, reactTableProps.state]);
|
|
1168
|
+
}, [sorting, columnVisibility, columnOrder, columnSizing, columnPinning, reactTableProps.state]);
|
|
634
1169
|
const table = reactTable.useReactTable({
|
|
635
1170
|
manualSorting: true,
|
|
636
|
-
enableSortingRemoval:
|
|
1171
|
+
enableSortingRemoval: true,
|
|
637
1172
|
columnResizeMode: "onChange",
|
|
638
1173
|
getSortedRowModel: reactTable.getSortedRowModel(),
|
|
639
1174
|
getCoreRowModel: reactTable.getCoreRowModel(),
|
|
@@ -654,6 +1189,10 @@ const useTable = ({ onTableStateChange, initialState, columns, ...reactTableProp
|
|
|
654
1189
|
setSorting(value);
|
|
655
1190
|
reactTableProps.onSortingChange?.(value);
|
|
656
1191
|
},
|
|
1192
|
+
onColumnPinningChange: value => {
|
|
1193
|
+
setColumnPinning(value);
|
|
1194
|
+
reactTableProps.onColumnPinningChange?.(value);
|
|
1195
|
+
},
|
|
657
1196
|
columns,
|
|
658
1197
|
state,
|
|
659
1198
|
});
|
|
@@ -725,11 +1264,12 @@ const useTableSelection = ({ data, idKey, defaultSelectedIds, enableRowSelection
|
|
|
725
1264
|
if (!hasRowSelection) {
|
|
726
1265
|
return null;
|
|
727
1266
|
}
|
|
728
|
-
|
|
1267
|
+
const isRowSelectionDisabled = !table.options.enableRowSelection || !data.length;
|
|
1268
|
+
return (jsxRuntime.jsx(reactFormComponents.Checkbox, { className: "ml-2.5",
|
|
729
1269
|
checked: table.getIsAllRowsSelected(),
|
|
730
1270
|
indeterminate: table.getIsSomeRowsSelected(),
|
|
731
1271
|
onChange: table.getToggleAllRowsSelectedHandler(),
|
|
732
|
-
disabled:
|
|
1272
|
+
disabled: isRowSelectionDisabled }));
|
|
733
1273
|
},
|
|
734
1274
|
cell: ({ row, table }) => {
|
|
735
1275
|
const hasRowSelection = table.getState().rowSelection !== undefined;
|
|
@@ -748,10 +1288,12 @@ const useTableSelection = ({ data, idKey, defaultSelectedIds, enableRowSelection
|
|
|
748
1288
|
enableSorting: false,
|
|
749
1289
|
enableResizing: false,
|
|
750
1290
|
enableHiding: false,
|
|
1291
|
+
enablePinning: false,
|
|
751
1292
|
size: 70,
|
|
752
1293
|
meta: {
|
|
753
1294
|
alignment: "center",
|
|
754
1295
|
columnFilterLabel: t("table.selection.label"),
|
|
1296
|
+
pinned: "left",
|
|
755
1297
|
},
|
|
756
1298
|
}), [columnHelper, data.length, idKey, t]);
|
|
757
1299
|
const selectionTableState = react.useMemo(() => ({ rowSelection }), [rowSelection]);
|