@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.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
- const result = columnOrder && (columnOrder.length || 0) > 0
307
- ? columns.sort((a, b) => columnOrder.indexOf(a.id) - columnOrder.indexOf(b.id))
308
- : columns;
309
- return result;
310
- }, [columnOrder, columns]);
311
- const getColumnButtonText = () => {
312
- if (numberOfHiddenColumns > 1) {
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
- if (numberOfHiddenColumns === 1) {
316
- return t("table.columnFilters.hiddenColumnCount", { count: numberOfHiddenColumns });
317
- }
318
- return t("table.columnFilters.columns");
319
- };
320
- 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: "ghost-neutral", children: jsxRuntime.jsx("span", { className: "hidden sm:block", children: getColumnButtonText() }) }) }), jsxRuntime.jsx(reactComponents.PopoverContent, { children: () => (jsxRuntime.jsxs(reactComponents.MenuList, { className: "relative max-h-[55vh] w-72 overflow-y-auto pr-4", children: [jsxRuntime.jsxs("div", { className: "mb-2 flex items-center justify-between", children: [jsxRuntime.jsx(reactComponents.Text, { size: "small", subtle: true, children: t("table.columnFilters.title") }), jsxRuntime.jsx(reactComponents.Button, { className: "p-0", onClick: () => resetHiddenColumns(), size: "small", variant: "ghost-neutral", children: t("layout.actions.reset") })] }), jsxRuntime.jsx(ColumnFiltersDragAndDrop, { columns: sortedColumns, defaultColumnOrder: defaultColumnOrder, onUserEvent: onUserEvent, setColumnOrder: setColumnOrder })] })) })] }) }) }));
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, defaultColumnOrder, }) => {
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 firstElement = defaultColumnOrder[0];
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, defaultColumnOrder]);
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
- return (jsxRuntime.jsx(ColumnFilterItem, { disabled: !column.getCanHide(), id: column.id, index: index, moveColumn: moveColumn, name: columnDef.meta?.columnFilterLabel ?? columnDef.header?.toString() ?? "", onCancelDrop: onCancelColumDrop, onDrop: onColumDrop, onToggle: (toggled, event) => {
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: column.getIsVisible() }, column.id));
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: true,
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: false }), 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 })] })] }));
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: "ghost-neutral", children: jsxRuntime.jsx("span", { className: "hidden sm: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") })] }) }) })] }) }));
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 = 75, ...props }) => {
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-gray-300", onScroll: e => fetchMoreOnBottomReached(e.target), ref: tableContainerRef, children: jsxRuntime.jsxs(reactTableBaseComponents.TableRoot, { style: {
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: props.getHeaderGroups().map(headerGroup => (jsxRuntime.jsx(reactTableBaseComponents.Tr, { className: "flex", children: headerGroup.headers.map(header => {
501
- const tooltipLabel = header.column.columnDef.meta?.tootipLabel ??
502
- (typeof header.column.columnDef.header === "string" ? header.column.columnDef.header : "");
503
- return (jsxRuntime.jsxs(reactTableBaseComponents.Th, { className: "relative", style: {
504
- width: header.getSize(),
505
- minWidth: header.column.columnDef.minSize,
506
- maxWidth: header.column.columnDef.maxSize,
507
- textAlign: header.column.columnDef.meta?.alignment || "left",
508
- flexGrow: header.column.columnDef.meta?.shouldGrow ? 1 : 0,
509
- }, tooltipLabel: tooltipLabel, children: [header.isPlaceholder ? null : (jsxRuntime.jsxs("div", { className: `${header.column.getCanSort() ? "cursor-pointer select-none flex pr-3" : "flex"} items-center gap-2 py-2 overflow-hidden w-full`,
510
- onClick: header.column.getToggleSortingHandler(), children: [jsxRuntime.jsxs("span", { className: "overflow-hidden text-ellipsis whitespace-nowrap", children: [reactTable.flexRender(header.column.columnDef.header, header.getContext()), header.column.columnDef.meta?.subHeader ? (jsxRuntime.jsx(reactComponents.Text, { size: "small", subtle: true, children: header.column.columnDef.meta.subHeader })) : null] }), header.column.getCanSort() ? (jsxRuntime.jsx(reactTableBaseComponents.SortIndicator, { sortingState: header.column.getIsSorted() })) : null] })), !header.column.columnDef.meta?.shouldGrow && header.column.getCanResize() ? (jsxRuntime.jsx(reactTableBaseComponents.ResizeHandle, { isResizing: header.column.getIsResizing(), onMouseDown: header.getResizeHandler(), onTouchStart: header.getResizeHandler() })) : null] }, header.id));
511
- }) }, headerGroup.id))) }), hasResults ? (jsxRuntime.jsx(reactTableBaseComponents.Tbody, { className: "text-sm font-normal", style: {
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
- return (jsxRuntime.jsx(reactTableBaseComponents.Td, { onClick: event => {
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
- }, style: {
544
- width: cell.column.getSize(),
545
- minWidth: cell.column.columnDef.minSize,
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(columns
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
- columns.forEach(column => {
1098
+ columnsList.forEach((column) => {
598
1099
  if (column.id && resultFromInitialState[column.id] === undefined) {
599
- resultFromInitialState[column.id] = column.meta?.hiddenByDefault === true ? false : true;
1100
+ resultFromInitialState[column.id] = column.meta?.hiddenByDefault !== true;
600
1101
  }
601
1102
  });
602
1103
  return resultFromInitialState;
603
- }, [columns, initialState?.columnVisibility]);
1104
+ }, [stableColumns, initialState?.columnVisibility]);
604
1105
  const updatedInitialColumnOrder = react.useMemo(() => {
605
1106
  const resultFromInitialState = initialState?.columnOrder || [];
606
- columns.forEach(column => {
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
- }, [columns, initialState?.columnOrder]);
613
- const [columnVisibility, setColumnVisibility] = react.useState(reactTableProps.state?.columnVisibility || updatedInitialColumnVisibility || {});
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: false,
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
- return (jsxRuntime.jsx(reactFormComponents.Checkbox, { className: "ml-4",
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: !data.length }));
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]);