"use client" import { Fragment, memo, useEffect, useId, useMemo, useRef, useState, } from "react" import type { CSSProperties, ReactNode } from "react" import { useDataGrid } from "@/components/reui/data-grid/data-grid" import type { DataGridFeatures, DataGridTableInstance, } from "@/components/reui/data-grid/data-grid" import { DataGridTableBase, DataGridTableBody, DataGridTableBodyRow, DataGridTableBodyRowCell, DataGridTableBodyRowExpandded, DataGridTableBodyRowSkeleton, DataGridTableBodyRowSkeletonCell, DataGridTableEmpty, DataGridTableFillBodyCell, DataGridTableFillHeadCell, DataGridTableFoot, DataGridTableHead, DataGridTableHeadRow, DataGridTableHeadRowCell, DataGridTableHeadRowCellResize, DataGridTableRowSpacer, DataGridTableViewport, } from "@/components/reui/data-grid/data-grid-table" import { closestCenter, DndContext, KeyboardSensor, MouseSensor, TouchSensor, useSensor, useSensors, type DragEndEvent, type Modifier, } from "@dnd-kit/core" import { horizontalListSortingStrategy, SortableContext, sortableKeyboardCoordinates, useSortable, } from "@dnd-kit/sortable" import { CSS } from "@dnd-kit/utilities" import { flexRender } from "@tanstack/react-table" import type { Cell, Header, HeaderGroup, Row, } from "@tanstack/react-table" import { Button } from "@/components/ui/button" import { GripVerticalIcon } from "lucide-react" function DataGridTableDndHeader({ header, }: { header: Header }) { const { i18n, props } = useDataGrid() const { column } = header // Check if column ordering is enabled for this column const canOrder = (column.columnDef as { enableColumnOrdering?: boolean }) .enableColumnOrdering !== false const { attributes, isDragging, listeners, setNodeRef, transform, transition, } = useSortable({ id: header.column.id, }) const style: CSSProperties = { opacity: isDragging ? 0.8 : 1, position: "relative", transform: CSS.Translate.toString(transform), transition, cursor: isDragging ? "grabbing" : undefined, whiteSpace: "nowrap", width: props.tableLayout?.columnsResizable ? `calc(var(--header-${header.id}-size) * 1px)` : header.column.getSize(), zIndex: isDragging ? 1 : 0, } return (
{canOrder && ( )}
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
{props.tableLayout?.columnsResizable && column.getCanResize() && ( )}
) } function DataGridTableDndCell({ cell, }: { cell: Cell }) { const { props } = useDataGrid() const { isDragging, setNodeRef, transform, transition } = useSortable({ id: cell.column.id, }) const style: CSSProperties = { opacity: isDragging ? 0.8 : 1, position: "relative", transform: CSS.Translate.toString(transform), transition, cursor: isDragging ? "grabbing" : undefined, width: props.tableLayout?.columnsResizable ? `calc(var(--col-${cell.column.id}-size) * 1px)` : cell.column.getSize(), zIndex: isDragging ? 1 : 0, } return ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ) } function DataGridTableDndBodyRows({ table, }: { table: DataGridTableInstance }) { const { isLoading, props } = useDataGrid() const pagination = table.state.pagination if (props.loadingMode === "skeleton" && isLoading && pagination?.pageSize) { return ( <> {Array.from({ length: pagination.pageSize }).map((_, rowIndex) => ( {table.getVisibleFlatColumns().map((column, colIndex) => { return ( {column.columnDef.meta?.skeleton} ) })} ))} ) } if (!table.getRowModel().rows.length) return return ( <> {table.getRowModel().rows.map((row: Row) => { return ( {row .getVisibleCells() .map((cell: Cell) => ( ))} {row.getIsExpanded() && } ) })} ) } /** * Memoized body rows: skip re-renders during active column resize. * Column widths update via CSS variables on the element, * so the browser handles width changes without React re-renders. */ const MemoizedDataGridTableDndBodyRows = memo( DataGridTableDndBodyRows, (_prev, next) => !!next.table.state.columnResizing.isResizingColumn ) as typeof DataGridTableDndBodyRows function DataGridTableDnd({ handleDragEnd, footerContent, }: { handleDragEnd: (event: DragEndEvent) => void footerContent?: ReactNode }) { const { table, props } = useDataGrid() const containerRef = useRef(null) const [isDraggingColumn, setIsDraggingColumn] = useState(false) const sensors = useSensors( useSensor(MouseSensor, {}), useSensor(TouchSensor, {}), // Keyboard reordering moves one sortable position per keypress instead // of the sensor's raw 25px default. useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates, }) ) useEffect(() => { if (!isDraggingColumn) return const { body, documentElement } = document const previousBodyCursor = body.style.cursor const previousDocumentCursor = documentElement.style.cursor body.style.cursor = "grabbing" documentElement.style.cursor = "grabbing" return () => { body.style.cursor = previousBodyCursor documentElement.style.cursor = previousDocumentCursor } }, [isDraggingColumn]) // Custom modifier to restrict dragging within table bounds with edge offset const modifiers = useMemo(() => { const restrictToTableBounds: Modifier = ({ draggingNodeRect, transform, }) => { if (!draggingNodeRect || !containerRef.current) { return { ...transform, y: 0 } } const containerRect = containerRef.current.getBoundingClientRect() const edgeOffset = 0 const minX = containerRect.left - draggingNodeRect.left - edgeOffset const maxX = containerRect.right - draggingNodeRect.left - draggingNodeRect.width + edgeOffset return { ...transform, x: Math.min(Math.max(transform.x, minX), maxX), y: 0, // Lock vertical movement } } return [restrictToTableBounds] }, []) return ( setIsDraggingColumn(false)} onDragEnd={(event) => { setIsDraggingColumn(false) handleDragEnd(event) }} onDragStart={() => setIsDraggingColumn(true)} sensors={sensors} > {table .getHeaderGroups() .map( (headerGroup: HeaderGroup, index) => { return ( {headerGroup.headers.map((header) => ( ))} ) } )} {(props.tableLayout?.stripped || !props.tableLayout?.rowBorder) && ( )} {footerContent && ( {footerContent} )} ) } export { DataGridTableDnd }