{"$schema":"https://ui.shadcn.com/schema/registry-item.json","name":"data-grid","type":"registry:ui","title":"Data Grid","description":"","dependencies":["@base-ui/react","@dnd-kit/core","@dnd-kit/modifiers","@dnd-kit/sortable","@dnd-kit/utilities","@tanstack/react-table","@tanstack/react-virtual","class-variance-authority"],"registryDependencies":["@reui/badge","button","checkbox","dropdown-menu","input","popover","select","separator","skeleton","spinner"],"files":[{"path":"data-grid.tsx","type":"registry:ui","content":"\"use client\"\n\nimport { createContext, ReactNode, useContext, useMemo } from \"react\"\nimport {\n Column,\n ColumnFiltersState,\n RowData,\n SortingState,\n Table,\n} from \"@tanstack/react-table\"\n\nimport { cn } from \"@/lib/utils\"\n\ndeclare module \"@tanstack/react-table\" {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n interface ColumnMeta {\n headerTitle?: string\n headerClassName?: string\n cellClassName?: string\n skeleton?: ReactNode\n expandedContent?: (row: TData) => ReactNode\n }\n}\n\n/** Label for headers / column visibility: `meta.headerTitle`, string `columnDef.header`, or `column.id`. */\nexport function getColumnHeaderLabel(\n column: Column\n): string {\n const meta = column.columnDef.meta as { headerTitle?: string } | undefined\n if (typeof meta?.headerTitle === \"string\") return meta.headerTitle\n const defHeader = column.columnDef.header\n if (typeof defHeader === \"string\") return defHeader\n return String(column.id)\n}\n\nexport type DataGridApiFetchParams = {\n pageIndex: number\n pageSize: number\n sorting?: SortingState\n filters?: ColumnFiltersState\n searchQuery?: string\n}\n\nexport type DataGridApiResponse = {\n data: T[]\n empty: boolean\n pagination: {\n total: number\n page: number\n }\n}\n\nexport interface DataGridContextProps {\n props: DataGridProps\n table: Table\n recordCount: number\n isLoading: boolean\n}\n\nexport type DataGridRequestParams = {\n pageIndex: number\n pageSize: number\n sorting?: SortingState\n columnFilters?: ColumnFiltersState\n}\n\nexport interface DataGridProps {\n className?: string\n table?: Table\n recordCount: number\n children?: ReactNode\n onRowClick?: (row: TData) => void\n isLoading?: boolean\n loadingMode?: \"skeleton\" | \"spinner\"\n loadingMessage?: ReactNode | string\n fetchingMoreMessage?: ReactNode | string\n allRowsLoadedMessage?: ReactNode | string\n emptyMessage?: ReactNode | string\n tableLayout?: {\n dense?: boolean\n cellBorder?: boolean\n rowBorder?: boolean\n rowRounded?: boolean\n stripped?: boolean\n headerBackground?: boolean\n headerBorder?: boolean\n headerSticky?: boolean\n width?: \"auto\" | \"fixed\"\n columnsVisibility?: boolean\n columnsResizable?: boolean\n columnsResizeMode?: \"onChange\" | \"onEnd\"\n columnsPinnable?: boolean\n columnsMovable?: boolean\n columnsDraggable?: boolean\n rowsDraggable?: boolean\n rowsPinnable?: boolean\n }\n tableClassNames?: {\n base?: string\n header?: string\n headerRow?: string\n headerSticky?: string\n body?: string\n bodyRow?: string\n footer?: string\n edgeCell?: string\n }\n}\n\nconst DataGridContext = createContext<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n DataGridContextProps | undefined\n>(undefined)\n\nfunction useDataGrid() {\n const context = useContext(DataGridContext)\n if (!context) {\n throw new Error(\"useDataGrid must be used within a DataGridProvider\")\n }\n return context\n}\n\nfunction DataGridProvider({\n children,\n table,\n ...props\n}: DataGridProps & { table: Table }) {\n const tableState = table.getState()\n const resolvedColumnsResizeMode =\n props.tableLayout?.columnsResizeMode ?? \"onEnd\"\n\n // Keep resize mode aligned with the DataGrid contract every render so\n // consumer-level useReactTable options cannot flip it back between drags.\n if (props.tableLayout?.columnsResizable) {\n table.options.columnResizeMode = resolvedColumnsResizeMode\n }\n\n // Memoize context value so consumers don't re-render during column resize.\n // Column sizing state is intentionally excluded from deps -- CSS variables\n // on the element handle width updates without React re-renders.\n const value = useMemo(\n () => ({\n props,\n table,\n recordCount: props.recordCount,\n isLoading: props.isLoading || false,\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [\n table,\n props.recordCount,\n props.isLoading,\n props.loadingMode,\n props.loadingMessage,\n props.fetchingMoreMessage,\n props.allRowsLoadedMessage,\n props.emptyMessage,\n props.onRowClick,\n props.className,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n JSON.stringify(props.tableLayout),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n JSON.stringify(props.tableClassNames),\n tableState.sorting,\n tableState.pagination,\n tableState.columnFilters,\n tableState.rowSelection,\n tableState.expanded,\n tableState.columnVisibility,\n tableState.columnOrder,\n tableState.columnPinning,\n tableState.globalFilter,\n ]\n )\n\n return (\n \n {children}\n \n )\n}\n\nfunction DataGrid({\n children,\n table,\n ...props\n}: DataGridProps) {\n const defaultProps: Partial> = {\n loadingMode: \"skeleton\",\n tableLayout: {\n dense: false,\n cellBorder: false,\n rowBorder: true,\n rowRounded: false,\n stripped: false,\n headerSticky: false,\n headerBackground: true,\n headerBorder: true,\n width: \"fixed\",\n columnsVisibility: false,\n columnsResizable: false,\n columnsResizeMode: \"onEnd\",\n columnsPinnable: false,\n columnsMovable: false,\n columnsDraggable: false,\n rowsDraggable: false,\n rowsPinnable: false,\n },\n tableClassNames: {\n base: \"\",\n header: \"\",\n headerRow: \"\",\n headerSticky: \"sticky top-0 z-15 bg-background/90 backdrop-blur-xs\",\n body: \"\",\n bodyRow: \"\",\n footer: \"\",\n edgeCell: \"\",\n },\n }\n\n const mergedProps: DataGridProps = {\n ...defaultProps,\n ...props,\n tableLayout: {\n ...defaultProps.tableLayout,\n ...(props.tableLayout || {}),\n },\n tableClassNames: {\n ...defaultProps.tableClassNames,\n ...(props.tableClassNames || {}),\n },\n }\n\n // Ensure table is provided\n if (!table) {\n throw new Error('DataGrid requires a \"table\" prop')\n }\n\n return (\n \n {children}\n \n )\n}\n\nfunction DataGridContainer({\n children,\n className,\n border = true,\n}: {\n children: ReactNode\n className?: string\n border?: boolean\n}) {\n return (\n \n {children}\n \n )\n}\n\nexport { useDataGrid, DataGridProvider, DataGrid, DataGridContainer }","target":"components/reui/data-grid/data-grid.tsx"},{"path":"data-grid-column-filter.tsx","type":"registry:ui","content":"\"use client\"\n\nimport { useMemo, useState } from \"react\"\nimport { Badge } from \"@/components/reui/badge\"\nimport { Column } from \"@tanstack/react-table\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { Input } from \"@/components/ui/input\"\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/components/ui/popover\"\nimport { Separator } from \"@/components/ui/separator\"\nimport { IconPlaceholder } from \"@/app/(create)/components/icon-placeholder\"\n\ninterface DataGridColumnFilterProps {\n column?: Column\n title?: string\n options: {\n label: string\n value: string\n icon?: React.ComponentType<{ className?: string }>\n }[]\n}\n\nfunction DataGridColumnFilter({\n column,\n title,\n options,\n}: DataGridColumnFilterProps) {\n const facets = column?.getFacetedUniqueValues()\n const selectedValues = new Set(column?.getFilterValue() as string[])\n const [searchQuery, setSearchQuery] = useState(\"\")\n\n const filteredOptions = useMemo(() => {\n if (!searchQuery) return options\n return options.filter((option) =>\n option.label.toLowerCase().includes(searchQuery.toLowerCase())\n )\n }, [options, searchQuery])\n\n return (\n \n \n \n {title}\n {selectedValues?.size > 0 && (\n <>\n \n \n {selectedValues.size}\n \n
\n {selectedValues.size > 2 ? (\n \n {selectedValues.size} selected\n \n ) : (\n options\n .filter((option) => selectedValues.has(option.value))\n .map((option) => (\n \n {option.label}\n \n ))\n )}\n
\n \n )}\n \n }\n />\n \n
\n setSearchQuery(e.target.value)}\n className=\"h-8\"\n />\n
\n
\n {filteredOptions.length === 0 ? (\n
\n No results found.\n
\n ) : (\n
\n {filteredOptions.map((option) => {\n const isSelected = selectedValues.has(option.value)\n return (\n {\n if (isSelected) {\n selectedValues.delete(option.value)\n } else {\n selectedValues.add(option.value)\n }\n const filterValues = Array.from(selectedValues)\n column?.setFilterValue(\n filterValues.length ? filterValues : undefined\n )\n }}\n className={cn(\n \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none\",\n \"hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground\"\n )}\n >\n \n \n
\n {option.icon && (\n \n )}\n {option.label}\n {facets?.get(option.value) && (\n \n {facets.get(option.value)}\n \n )}\n
\n )\n })}\n \n )}\n {selectedValues.size > 0 && (\n <>\n
\n
\n column?.setFilterValue(undefined)}\n className=\"hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center justify-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none\"\n >\n Clear filters\n
\n
\n \n )}\n \n
\n
\n )\n}\n\nexport { DataGridColumnFilter, type DataGridColumnFilterProps }","target":"components/reui/data-grid/data-grid-column-filter.tsx"},{"path":"data-grid-column-header.tsx","type":"registry:ui","content":"\"use client\"\n\nimport { HTMLAttributes, memo, ReactNode, useMemo } from \"react\"\nimport {\n getColumnHeaderLabel,\n useDataGrid,\n} from \"@/components/reui/data-grid/data-grid\"\nimport { Column } from \"@tanstack/react-table\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\nimport { IconPlaceholder } from \"@/app/(create)/components/icon-placeholder\"\n\ninterface DataGridColumnHeaderProps<\n TData,\n TValue,\n> extends HTMLAttributes {\n column: Column\n /** When omitted, uses `column.columnDef.meta.headerTitle`, then a string `columnDef.header`, then `column.id`. */\n title?: string\n icon?: ReactNode\n pinnable?: boolean\n filter?: ReactNode\n visibility?: boolean\n}\n\nfunction DataGridColumnHeaderInner({\n column,\n title,\n icon,\n className,\n filter,\n visibility = false,\n}: DataGridColumnHeaderProps) {\n const { isLoading, table, props, recordCount } = useDataGrid()\n const resolvedTitle = title ?? getColumnHeaderLabel(column)\n\n const columnOrder = table.getState().columnOrder\n const columnVisibilityKey = JSON.stringify(table.getState().columnVisibility)\n const isSorted = column.getIsSorted()\n const isPinned = column.getIsPinned()\n const canSort = column.getCanSort()\n const canPin = column.getCanPin()\n const canResize = column.getCanResize()\n\n const columnIndex = columnOrder.indexOf(column.id)\n const canMoveLeft = columnIndex > 0\n const canMoveRight = columnIndex < columnOrder.length - 1\n\n const handleSort = () => {\n if (isSorted === \"asc\") {\n column.toggleSorting(true)\n } else if (isSorted === \"desc\") {\n column.clearSorting()\n } else {\n column.toggleSorting(false)\n }\n }\n\n const headerLabelClassName = cn(\n \"text-secondary-foreground/80 inline-flex h-full items-center gap-1.5 font-normal [&_svg]:opacity-60 text-[0.8125rem] leading-[calc(1.125/0.8125)] [&_svg]:size-3.5\",\n className\n )\n\n const headerButtonClassName = cn(\n \"text-secondary-foreground/80 hover:bg-secondary data-[state=open]:bg-secondary hover:text-foreground data-[state=open]:text-foreground -ms-2 px-2 font-normal h-7 rounded-md\",\n className\n )\n\n const sortIcon =\n canSort &&\n (isSorted === \"desc\" ? (\n \n ) : isSorted === \"asc\" ? (\n \n ) : (\n \n ))\n\n const hasControls =\n props.tableLayout?.columnsMovable ||\n (props.tableLayout?.columnsVisibility && visibility) ||\n (props.tableLayout?.columnsPinnable && canPin) ||\n filter\n\n const menuItems = useMemo(() => {\n const items: ReactNode[] = []\n let hasPreviousSection = false\n\n // Filter section\n if (filter) {\n items.push(\n \n {filter}\n \n )\n hasPreviousSection = true\n }\n\n // Sort section\n if (canSort) {\n if (hasPreviousSection) {\n items.push()\n }\n items.push(\n {\n if (isSorted === \"asc\") {\n column.clearSorting()\n } else {\n column.toggleSorting(false)\n }\n }}\n disabled={!canSort}\n >\n \n Asc\n {isSorted === \"asc\" && (\n \n )}\n ,\n {\n if (isSorted === \"desc\") {\n column.clearSorting()\n } else {\n column.toggleSorting(true)\n }\n }}\n disabled={!canSort}\n >\n \n Desc\n {isSorted === \"desc\" && (\n \n )}\n \n )\n hasPreviousSection = true\n }\n\n // Pin section\n if (props.tableLayout?.columnsPinnable && canPin) {\n if (hasPreviousSection) {\n items.push()\n }\n items.push(\n column.pin(isPinned === \"left\" ? false : \"left\")}\n >\n \n Pin to left\n {isPinned === \"left\" && (\n \n )}\n ,\n column.pin(isPinned === \"right\" ? false : \"right\")}\n >\n \n Pin to right\n {isPinned === \"right\" && (\n \n )}\n \n )\n hasPreviousSection = true\n }\n\n // Move section\n if (props.tableLayout?.columnsMovable) {\n if (hasPreviousSection) {\n items.push()\n }\n items.push(\n {\n if (columnIndex > 0) {\n const newOrder = [...columnOrder]\n const [movedColumn] = newOrder.splice(columnIndex, 1)\n newOrder.splice(columnIndex - 1, 0, movedColumn)\n table.setColumnOrder(newOrder)\n }\n }}\n disabled={!canMoveLeft || isPinned !== false}\n >\n \n Move to Left\n ,\n {\n if (columnIndex < columnOrder.length - 1) {\n const newOrder = [...columnOrder]\n const [movedColumn] = newOrder.splice(columnIndex, 1)\n newOrder.splice(columnIndex + 1, 0, movedColumn)\n table.setColumnOrder(newOrder)\n }\n }}\n disabled={!canMoveRight || isPinned !== false}\n >\n \n Move to Right\n \n )\n hasPreviousSection = true\n }\n\n // Visibility section\n if (props.tableLayout?.columnsVisibility && visibility) {\n if (hasPreviousSection) {\n items.push()\n }\n items.push(\n \n \n \n Columns\n \n \n {table\n .getAllColumns()\n .filter((col) => col.getCanHide())\n .map((col) => (\n event.preventDefault()}\n onCheckedChange={(value) => col.toggleVisibility(!!value)}\n className=\"capitalize\"\n >\n {getColumnHeaderLabel(col)}\n \n ))}\n \n \n )\n }\n\n return items\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n filter,\n canSort,\n isSorted,\n column,\n props.tableLayout?.columnsPinnable,\n props.tableLayout?.columnsMovable,\n props.tableLayout?.columnsVisibility,\n canPin,\n isPinned,\n canMoveLeft,\n canMoveRight,\n visibility,\n table,\n columnIndex,\n columnOrder,\n columnVisibilityKey, // Needed to update checkbox states when visibility changes\n ])\n\n if (hasControls) {\n return (\n
\n \n \n {icon && icon}\n {resolvedTitle}\n {sortIcon}\n \n }\n />\n \n {menuItems}\n \n \n {props.tableLayout?.columnsPinnable && canPin && isPinned && (\n column.pin(false)}\n aria-label={`Unpin ${resolvedTitle} column`}\n title={`Unpin ${resolvedTitle} column`}\n >\n \n \n )}\n
\n )\n }\n\n if (canSort || (props.tableLayout?.columnsResizable && canResize)) {\n return (\n
\n \n {icon && icon}\n {resolvedTitle}\n {sortIcon}\n \n
\n )\n }\n\n return (\n
\n {icon && icon}\n {resolvedTitle}\n
\n )\n}\n\nconst DataGridColumnHeader = memo(\n DataGridColumnHeaderInner\n) as typeof DataGridColumnHeaderInner\n\nexport { DataGridColumnHeader, type DataGridColumnHeaderProps }","target":"components/reui/data-grid/data-grid-column-header.tsx"},{"path":"data-grid-column-visibility.tsx","type":"registry:ui","content":"\"use client\"\n\nimport { ReactElement } from \"react\"\nimport { getColumnHeaderLabel } from \"@/components/reui/data-grid/data-grid\"\nimport { Table } from \"@tanstack/react-table\"\n\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuLabel,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\"\n\nfunction DataGridColumnVisibility({\n table,\n trigger,\n}: {\n table: Table\n trigger: ReactElement>\n}) {\n return (\n \n \n \n \n \n Toggle Columns\n \n {table\n .getAllColumns()\n .filter((column) => column.getCanHide())\n .map((column) => {\n return (\n event.preventDefault()}\n onCheckedChange={(value) => column.toggleVisibility(!!value)}\n >\n {getColumnHeaderLabel(column)}\n \n )\n })}\n \n \n \n )\n}\n\nexport { DataGridColumnVisibility }","target":"components/reui/data-grid/data-grid-column-visibility.tsx"},{"path":"data-grid-pagination.tsx","type":"registry:ui","content":"\"use client\"\n\nimport React, { ReactNode } from \"react\"\nimport { useDataGrid } from \"@/components/reui/data-grid/data-grid\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { IconPlaceholder } from \"@/app/(create)/components/icon-placeholder\"\n\ninterface DataGridPaginationProps {\n sizes?: number[]\n sizesInfo?: string\n sizesLabel?: string\n sizesDescription?: string\n sizesSkeleton?: ReactNode\n more?: boolean\n moreLimit?: number\n info?: string\n infoSkeleton?: ReactNode\n className?: string\n rowsPerPageLabel?: string\n previousPageLabel?: string\n nextPageLabel?: string\n ellipsisText?: string\n}\n\nfunction DataGridPagination(props: DataGridPaginationProps): React.JSX.Element {\n const { table, recordCount, isLoading } = useDataGrid()\n\n const defaultProps: Partial = {\n sizes: [5, 10, 25, 50, 100],\n sizesLabel: \"Show\",\n sizesDescription: \"per page\",\n sizesSkeleton: ,\n moreLimit: 5,\n more: false,\n info: \"{from} - {to} of {count}\",\n infoSkeleton: ,\n rowsPerPageLabel: \"Rows per page\",\n previousPageLabel: \"Go to previous page\",\n nextPageLabel: \"Go to next page\",\n ellipsisText: \"...\",\n }\n\n const mergedProps: DataGridPaginationProps = { ...defaultProps, ...props }\n\n const btnBaseClasses = \"size-7 p-0 text-sm\"\n const btnArrowClasses = btnBaseClasses + \" rtl:transform rtl:rotate-180\"\n const pageIndex = table.getState().pagination.pageIndex\n const pageSize = table.getState().pagination.pageSize\n const from = pageIndex * pageSize + 1\n const to = Math.min((pageIndex + 1) * pageSize, recordCount)\n const pageCount = table.getPageCount()\n\n // Replace placeholders in paginationInfo\n const paginationInfo = mergedProps?.info\n ? mergedProps.info\n .replace(\"{from}\", from.toString())\n .replace(\"{to}\", to.toString())\n .replace(\"{count}\", recordCount.toString())\n : `${from} - ${to} of ${recordCount}`\n\n // Pagination limit logic\n const paginationMoreLimit = mergedProps?.moreLimit || 5\n\n // Determine the start and end of the pagination group\n const currentGroupStart =\n Math.floor(pageIndex / paginationMoreLimit) * paginationMoreLimit\n const currentGroupEnd = Math.min(\n currentGroupStart + paginationMoreLimit,\n pageCount\n )\n\n // Render page buttons based on the current group\n const renderPageButtons = () => {\n const buttons = []\n for (let i = currentGroupStart; i < currentGroupEnd; i++) {\n buttons.push(\n {\n if (pageIndex !== i) {\n table.setPageIndex(i)\n }\n }}\n >\n {i + 1}\n \n )\n }\n return buttons\n }\n\n // Render a \"previous\" ellipsis button if there are previous pages to show\n const renderEllipsisPrevButton = () => {\n if (currentGroupStart > 0) {\n return (\n table.setPageIndex(currentGroupStart - 1)}\n >\n {mergedProps.ellipsisText}\n \n )\n }\n return null\n }\n\n // Render a \"next\" ellipsis button if there are more pages to show after the current group\n const renderEllipsisNextButton = () => {\n if (currentGroupEnd < pageCount) {\n return (\n table.setPageIndex(currentGroupEnd)}\n >\n {mergedProps.ellipsisText}\n \n )\n }\n return null\n }\n\n return (\n \n
\n {isLoading ? (\n mergedProps?.sizesSkeleton\n ) : (\n <>\n
\n {mergedProps.rowsPerPageLabel}\n
\n {\n const newPageSize = Number(value)\n table.setPageSize(newPageSize)\n }}\n >\n \n \n \n \n {mergedProps?.sizes?.map((size: number) => (\n \n {size}\n \n ))}\n \n \n \n )}\n
\n
\n {isLoading ? (\n mergedProps?.infoSkeleton\n ) : (\n <>\n
\n {paginationInfo}\n
\n {pageCount > 1 && (\n
\n table.previousPage()}\n disabled={!table.getCanPreviousPage()}\n >\n \n {mergedProps.previousPageLabel}\n \n \n \n\n {renderEllipsisPrevButton()}\n\n {renderPageButtons()}\n\n {renderEllipsisNextButton()}\n\n table.nextPage()}\n disabled={!table.getCanNextPage()}\n >\n {mergedProps.nextPageLabel}\n \n \n
\n )}\n \n )}\n
\n \n )\n}\n\nexport { DataGridPagination, type DataGridPaginationProps }","target":"components/reui/data-grid/data-grid-pagination.tsx"},{"path":"data-grid-scroll-area.tsx","type":"registry:ui","content":"\"use client\"\n\nimport {\n PointerEvent,\n ReactNode,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\"\nimport { useDataGrid } from \"@/components/reui/data-grid/data-grid\"\nimport { ScrollArea as ScrollAreaPrimitive } from \"@base-ui/react/scroll-area\"\n\nimport { cn } from \"@/lib/utils\"\n\nconst MIN_THUMB_SIZE = 24\nconst FALLBACK_SCROLLBAR_SIZE = 12\n\nconst INITIAL_METRICS = {\n hasVerticalOverflow: false,\n headerHeight: 0,\n horizontalScrollbarSize: 0,\n thumbHeight: 0,\n thumbTop: 0,\n trackHeight: 0,\n} as const\n\ntype DataGridScrollAreaOrientation = \"horizontal\" | \"vertical\" | \"both\"\n\ntype ScrollbarMetrics = {\n hasVerticalOverflow: boolean\n headerHeight: number\n horizontalScrollbarSize: number\n thumbHeight: number\n thumbTop: number\n trackHeight: number\n}\n\ntype ObservedElements = {\n header: HTMLElement | null\n horizontalScrollbar: HTMLElement | null\n table: HTMLElement | null\n tableViewport: HTMLElement | null\n}\n\ntype DataGridScrollAreaProps = Omit<\n ScrollAreaPrimitive.Root.Props,\n \"children\"\n> & {\n children: ReactNode\n orientation?: DataGridScrollAreaOrientation\n}\n\nfunction clamp(value: number, min: number, max: number) {\n return Math.min(max, Math.max(min, value))\n}\n\nfunction areMetricsEqual(next: ScrollbarMetrics, prev: ScrollbarMetrics) {\n return (\n next.hasVerticalOverflow === prev.hasVerticalOverflow &&\n next.headerHeight === prev.headerHeight &&\n next.horizontalScrollbarSize === prev.horizontalScrollbarSize &&\n next.thumbHeight === prev.thumbHeight &&\n next.thumbTop === prev.thumbTop &&\n next.trackHeight === prev.trackHeight\n )\n}\n\nfunction applyMetrics(element: HTMLElement, metrics: ScrollbarMetrics) {\n element.style.setProperty(\n \"--data-grid-scrollbar-header-height\",\n `${metrics.headerHeight}px`\n )\n element.style.setProperty(\n \"--data-grid-scrollbar-thumb-height\",\n `${metrics.thumbHeight}px`\n )\n element.style.setProperty(\n \"--data-grid-scrollbar-thumb-top\",\n `${metrics.thumbTop}px`\n )\n element.style.setProperty(\n \"--data-grid-scrollbar-track-height\",\n `${metrics.trackHeight}px`\n )\n}\n\nfunction DataGridScrollArea({\n children,\n className,\n orientation = \"both\",\n ...props\n}: DataGridScrollAreaProps) {\n const { props: dataGridProps } = useDataGrid()\n const containerRef = useRef(null)\n const viewportRef = useRef(null)\n const dragRef = useRef<{\n pointerId: number\n startScrollTop: number\n startY: number\n } | null>(null)\n const metricsRef = useRef(INITIAL_METRICS)\n const observedElementsRef = useRef({\n header: null,\n horizontalScrollbar: null,\n table: null,\n tableViewport: null,\n })\n\n const showHorizontal = orientation !== \"vertical\"\n const showVertical = orientation !== \"horizontal\"\n const usesCustomVerticalScrollbar =\n showVertical && !!dataGridProps.tableLayout?.headerSticky\n const [hasCustomVerticalOverflow, setHasCustomVerticalOverflow] =\n useState(false)\n\n const clearDragState = useCallback(() => {\n dragRef.current = null\n document.body.style.userSelect = \"\"\n document.body.style.webkitUserSelect = \"\"\n }, [])\n\n const resetMetrics = useCallback(() => {\n const container = containerRef.current\n\n if (container && !areMetricsEqual(INITIAL_METRICS, metricsRef.current)) {\n applyMetrics(container, INITIAL_METRICS)\n metricsRef.current = INITIAL_METRICS\n }\n\n setHasCustomVerticalOverflow((prev) => (prev ? false : prev))\n }, [])\n\n const syncCustomVerticalScrollbar = useCallback(() => {\n const container = containerRef.current\n const viewport = viewportRef.current\n\n if (!container || !viewport || !usesCustomVerticalScrollbar) {\n resetMetrics()\n return\n }\n\n const { header, horizontalScrollbar } = observedElementsRef.current\n const headerHeight = header?.getBoundingClientRect().height ?? 0\n const viewportHeight = viewport.clientHeight\n const viewportWidth = viewport.clientWidth\n const scrollHeight = viewport.scrollHeight\n const scrollWidth = viewport.scrollWidth\n const hasHorizontalOverflow =\n showHorizontal && scrollWidth > viewportWidth + 0.5\n const horizontalScrollbarSize = hasHorizontalOverflow\n ? horizontalScrollbar?.offsetHeight || FALLBACK_SCROLLBAR_SIZE\n : 0\n const trackHeight = Math.max(\n 0,\n viewportHeight - headerHeight - horizontalScrollbarSize\n )\n const maxScroll = Math.max(0, scrollHeight - viewportHeight)\n\n let nextMetrics: ScrollbarMetrics\n\n if (trackHeight === 0 || maxScroll === 0) {\n nextMetrics = {\n hasVerticalOverflow: false,\n headerHeight,\n horizontalScrollbarSize,\n thumbHeight: trackHeight,\n thumbTop: 0,\n trackHeight,\n }\n } else {\n const bodyContentHeight = Math.max(\n trackHeight,\n scrollHeight - headerHeight\n )\n const thumbHeight = clamp(\n trackHeight * (trackHeight / bodyContentHeight),\n MIN_THUMB_SIZE,\n trackHeight\n )\n const maxThumbTop = Math.max(0, trackHeight - thumbHeight)\n const thumbTop =\n maxThumbTop > 0 ? (viewport.scrollTop / maxScroll) * maxThumbTop : 0\n\n nextMetrics = {\n hasVerticalOverflow: true,\n headerHeight,\n horizontalScrollbarSize,\n thumbHeight,\n thumbTop,\n trackHeight,\n }\n }\n\n if (!areMetricsEqual(nextMetrics, metricsRef.current)) {\n applyMetrics(container, nextMetrics)\n metricsRef.current = nextMetrics\n }\n\n setHasCustomVerticalOverflow((prev) =>\n prev === nextMetrics.hasVerticalOverflow\n ? prev\n : nextMetrics.hasVerticalOverflow\n )\n }, [resetMetrics, showHorizontal, usesCustomVerticalScrollbar])\n\n useEffect(() => {\n const container = containerRef.current\n const viewport = viewportRef.current\n\n if (!container || !viewport) return\n\n if (!usesCustomVerticalScrollbar) {\n resetMetrics()\n return\n }\n\n observedElementsRef.current = {\n header: container.querySelector(\n '[data-slot=\"data-grid-table\"] thead'\n ) as HTMLElement | null,\n horizontalScrollbar: container.querySelector(\n '[data-slot=\"data-grid-scrollbar\"][data-orientation=\"horizontal\"]'\n ) as HTMLElement | null,\n table: container.querySelector(\n '[data-slot=\"data-grid-table\"]'\n ) as HTMLElement | null,\n tableViewport: container.querySelector(\n '[data-slot=\"data-grid-table-viewport\"]'\n ) as HTMLElement | null,\n }\n\n let frame = 0\n\n const scheduleSync = () => {\n cancelAnimationFrame(frame)\n frame = window.requestAnimationFrame(syncCustomVerticalScrollbar)\n }\n\n scheduleSync()\n viewport.addEventListener(\"scroll\", scheduleSync, { passive: true })\n\n const observer =\n typeof ResizeObserver === \"undefined\"\n ? null\n : new ResizeObserver(scheduleSync)\n\n observer?.observe(viewport)\n observedElementsRef.current.header &&\n observer?.observe(observedElementsRef.current.header)\n observedElementsRef.current.table &&\n observer?.observe(observedElementsRef.current.table)\n observedElementsRef.current.tableViewport &&\n observer?.observe(observedElementsRef.current.tableViewport)\n\n return () => {\n cancelAnimationFrame(frame)\n observer?.disconnect()\n viewport.removeEventListener(\"scroll\", scheduleSync)\n clearDragState()\n }\n }, [\n clearDragState,\n resetMetrics,\n syncCustomVerticalScrollbar,\n usesCustomVerticalScrollbar,\n ])\n\n const scrollToThumbOffset = (nextThumbTop: number) => {\n const viewport = viewportRef.current\n const { thumbHeight, trackHeight } = metricsRef.current\n\n if (!viewport) return\n\n const maxScroll = Math.max(0, viewport.scrollHeight - viewport.clientHeight)\n const maxThumbTop = Math.max(0, trackHeight - thumbHeight)\n\n if (maxScroll === 0 || maxThumbTop === 0) {\n viewport.scrollTop = 0\n return\n }\n\n const ratio = clamp(nextThumbTop, 0, maxThumbTop) / maxThumbTop\n viewport.scrollTop = ratio * maxScroll\n }\n\n const handleThumbPointerDown = (event: PointerEvent) => {\n const viewport = viewportRef.current\n\n if (!viewport) return\n\n event.preventDefault()\n event.stopPropagation()\n event.currentTarget.setPointerCapture(event.pointerId)\n\n dragRef.current = {\n pointerId: event.pointerId,\n startScrollTop: viewport.scrollTop,\n startY: event.clientY,\n }\n\n document.body.style.userSelect = \"none\"\n document.body.style.webkitUserSelect = \"none\"\n }\n\n const handleThumbPointerMove = (event: PointerEvent) => {\n const viewport = viewportRef.current\n const dragState = dragRef.current\n const { thumbHeight, trackHeight } = metricsRef.current\n\n if (!viewport || !dragState || dragState.pointerId !== event.pointerId) {\n return\n }\n\n const maxThumbTop = Math.max(0, trackHeight - thumbHeight)\n const maxScroll = Math.max(0, viewport.scrollHeight - viewport.clientHeight)\n\n if (maxThumbTop === 0 || maxScroll === 0) return\n\n const deltaY = event.clientY - dragState.startY\n const nextScrollTop =\n dragState.startScrollTop + (deltaY / maxThumbTop) * maxScroll\n\n viewport.scrollTop = clamp(nextScrollTop, 0, maxScroll)\n }\n\n const handleThumbPointerUp = (event: PointerEvent) => {\n if (dragRef.current?.pointerId !== event.pointerId) return\n clearDragState()\n }\n\n const handleTrackPointerDown = (event: PointerEvent) => {\n const { thumbHeight } = metricsRef.current\n\n if (event.target !== event.currentTarget) return\n\n event.preventDefault()\n event.stopPropagation()\n\n const rect = event.currentTarget.getBoundingClientRect()\n const offsetY = event.clientY - rect.top - thumbHeight / 2\n\n scrollToThumbOffset(offsetY)\n }\n\n return (\n
\n \n \n \n {children}\n \n \n\n {showHorizontal && (\n \n \n \n )}\n\n {showVertical && !usesCustomVerticalScrollbar && (\n \n \n \n )}\n \n\n {usesCustomVerticalScrollbar && hasCustomVerticalOverflow && (\n \n \n \n
\n \n )}\n \n )\n}\n\nexport { DataGridScrollArea }\nexport type { DataGridScrollAreaOrientation, DataGridScrollAreaProps }","target":"components/reui/data-grid/data-grid-scroll-area.tsx"},{"path":"data-grid-table-dnd-rows.tsx","type":"registry:ui","content":"\"use client\"\n\nimport {\n createContext,\n CSSProperties,\n ReactNode,\n useContext,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\"\nimport { useDataGrid } from \"@/components/reui/data-grid/data-grid\"\nimport {\n DataGridTableBase,\n DataGridTableBody,\n DataGridTableBodyRow,\n DataGridTableBodyRowCell,\n DataGridTableBodyRowSkeleton,\n DataGridTableBodyRowSkeletonCell,\n DataGridTableEmpty,\n DataGridTableFoot,\n DataGridTableHead,\n DataGridTableHeadRow,\n DataGridTableHeadRowCell,\n DataGridTableHeadRowCellResize,\n DataGridTableRowSpacer,\n DataGridTableViewport,\n} from \"@/components/reui/data-grid/data-grid-table\"\nimport {\n closestCenter,\n DndContext,\n KeyboardSensor,\n MouseSensor,\n TouchSensor,\n UniqueIdentifier,\n useSensor,\n useSensors,\n type DragEndEvent,\n type Modifier,\n} from \"@dnd-kit/core\"\nimport { restrictToVerticalAxis } from \"@dnd-kit/modifiers\"\nimport {\n SortableContext,\n useSortable,\n verticalListSortingStrategy,\n} from \"@dnd-kit/sortable\"\nimport { CSS } from \"@dnd-kit/utilities\"\nimport { Cell, flexRender, HeaderGroup, Row } from \"@tanstack/react-table\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Button } from \"@/components/ui/button\"\nimport { IconPlaceholder } from \"@/app/(create)/components/icon-placeholder\"\n\n// Context to share sortable listeners from row to handle\ntype SortableContextValue = ReturnType\nconst SortableRowContext = createContext | null>(null)\n\nfunction DataGridTableDndRowHandle({ className }: { className?: string }) {\n const context = useContext(SortableRowContext)\n\n if (!context) {\n // Fallback if context is not available (shouldn't happen in normal usage)\n return (\n \n \n \n )\n }\n\n return (\n \n \n \n )\n}\n\nfunction DataGridTableDndRow({ row }: { row: Row }) {\n const {\n transform,\n transition,\n setNodeRef,\n isDragging,\n attributes,\n listeners,\n } = useSortable({\n id: row.id,\n })\n\n const style: CSSProperties = {\n transform: CSS.Transform.toString(transform),\n transition: transition,\n opacity: isDragging ? 0.8 : 1,\n zIndex: isDragging ? 1 : 0,\n position: \"relative\",\n cursor: isDragging ? \"grabbing\" : undefined,\n }\n\n return (\n \n \n {row.getVisibleCells().map((cell: Cell, colIndex) => {\n return (\n \n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n \n )\n })}\n \n \n )\n}\n\nfunction DataGridTableDndRows({\n handleDragEnd,\n dataIds,\n footerContent,\n}: {\n handleDragEnd: (event: DragEndEvent) => void\n dataIds: UniqueIdentifier[]\n footerContent?: ReactNode\n}) {\n const { table, isLoading, props } = useDataGrid()\n const pagination = table.getState().pagination\n const tableContainerRef = useRef(null)\n const [isDraggingRow, setIsDraggingRow] = useState(false)\n\n const sensors = useSensors(\n useSensor(MouseSensor, {}),\n useSensor(TouchSensor, {}),\n useSensor(KeyboardSensor, {})\n )\n\n useEffect(() => {\n if (!isDraggingRow) return\n\n const { body, documentElement } = document\n const previousBodyCursor = body.style.cursor\n const previousDocumentCursor = documentElement.style.cursor\n\n body.style.cursor = \"grabbing\"\n documentElement.style.cursor = \"grabbing\"\n\n return () => {\n body.style.cursor = previousBodyCursor\n documentElement.style.cursor = previousDocumentCursor\n }\n }, [isDraggingRow])\n\n const modifiers = useMemo(() => {\n const restrictToTableContainer: Modifier = ({\n transform,\n draggingNodeRect,\n }) => {\n if (!tableContainerRef.current || !draggingNodeRect) {\n return transform\n }\n\n const containerRect = tableContainerRef.current.getBoundingClientRect()\n const { x, y } = transform\n\n const minX = containerRect.left - draggingNodeRect.left\n const maxX = containerRect.right - draggingNodeRect.right\n const minY = containerRect.top - draggingNodeRect.top\n const maxY = containerRect.bottom - draggingNodeRect.bottom\n\n return {\n ...transform,\n x: Math.max(minX, Math.min(maxX, x)),\n y: Math.max(minY, Math.min(maxY, y)),\n }\n }\n\n return [restrictToVerticalAxis, restrictToTableContainer]\n }, [])\n\n return (\n setIsDraggingRow(false)}\n onDragEnd={(event) => {\n setIsDraggingRow(false)\n handleDragEnd(event)\n }}\n onDragStart={() => setIsDraggingRow(true)}\n sensors={sensors}\n >\n \n \n \n {table\n .getHeaderGroups()\n .map((headerGroup: HeaderGroup, index) => {\n return (\n \n {headerGroup.headers.map((header, index) => {\n const { column } = header\n\n return (\n \n {header.isPlaceholder ? null : props.tableLayout\n ?.columnsResizable && column.getCanResize() ? (\n
\n {flexRender(\n header.column.columnDef.header,\n header.getContext()\n )}\n
\n ) : (\n flexRender(\n header.column.columnDef.header,\n header.getContext()\n )\n )}\n {props.tableLayout?.columnsResizable &&\n column.getCanResize() && (\n \n )}\n
\n )\n })}\n
\n )\n })}\n
\n\n {(props.tableLayout?.stripped || !props.tableLayout?.rowBorder) && (\n \n )}\n\n \n {props.loadingMode === \"skeleton\" &&\n isLoading &&\n pagination?.pageSize ? (\n Array.from({ length: pagination.pageSize }).map((_, rowIndex) => (\n \n {table.getVisibleFlatColumns().map((column, colIndex) => {\n return (\n \n {column.columnDef.meta?.skeleton}\n \n )\n })}\n \n ))\n ) : table.getRowModel().rows.length ? (\n \n {table.getRowModel().rows.map((row: Row) => {\n return \n })}\n \n ) : (\n \n )}\n \n\n {footerContent && (\n {footerContent}\n )}\n
\n \n \n )\n}\n\nexport { DataGridTableDndRowHandle, DataGridTableDndRows }","target":"components/reui/data-grid/data-grid-table-dnd-rows.tsx"},{"path":"data-grid-table-dnd.tsx","type":"registry:ui","content":"\"use client\"\n\nimport {\n CSSProperties,\n Fragment,\n ReactNode,\n useEffect,\n useId,\n useRef,\n useState,\n} from \"react\"\nimport { useDataGrid } from \"@/components/reui/data-grid/data-grid\"\nimport {\n DataGridTableBase,\n DataGridTableBody,\n DataGridTableBodyRow,\n DataGridTableBodyRowCell,\n DataGridTableBodyRowExpandded,\n DataGridTableBodyRowSkeleton,\n DataGridTableBodyRowSkeletonCell,\n DataGridTableEmpty,\n DataGridTableFoot,\n DataGridTableHead,\n DataGridTableHeadRow,\n DataGridTableHeadRowCell,\n DataGridTableHeadRowCellResize,\n DataGridTableRowSpacer,\n DataGridTableViewport,\n} from \"@/components/reui/data-grid/data-grid-table\"\nimport {\n closestCenter,\n DndContext,\n KeyboardSensor,\n Modifier,\n MouseSensor,\n TouchSensor,\n useSensor,\n useSensors,\n type DragEndEvent,\n} from \"@dnd-kit/core\"\nimport {\n horizontalListSortingStrategy,\n SortableContext,\n useSortable,\n} from \"@dnd-kit/sortable\"\nimport { CSS } from \"@dnd-kit/utilities\"\nimport {\n Cell,\n flexRender,\n Header,\n HeaderGroup,\n Row,\n} from \"@tanstack/react-table\"\n\nimport { Button } from \"@/components/ui/button\"\nimport { IconPlaceholder } from \"@/app/(create)/components/icon-placeholder\"\n\nfunction DataGridTableDndHeader({\n header,\n}: {\n header: Header\n}) {\n const { props } = useDataGrid()\n const { column } = header\n\n // Check if column ordering is enabled for this column\n const canOrder =\n (column.columnDef as { enableColumnOrdering?: boolean })\n .enableColumnOrdering !== false\n\n const {\n attributes,\n isDragging,\n listeners,\n setNodeRef,\n transform,\n transition,\n } = useSortable({\n id: header.column.id,\n })\n\n const style: CSSProperties = {\n opacity: isDragging ? 0.8 : 1,\n position: \"relative\",\n transform: CSS.Translate.toString(transform),\n transition,\n cursor: isDragging ? \"grabbing\" : undefined,\n whiteSpace: \"nowrap\",\n width: props.tableLayout?.columnsResizable\n ? `calc(var(--header-${header.id}-size) * 1px)`\n : header.column.getSize(),\n zIndex: isDragging ? 1 : 0,\n }\n\n return (\n \n
\n {canOrder && (\n \n \n \n )}\n \n {header.isPlaceholder\n ? null\n : flexRender(header.column.columnDef.header, header.getContext())}\n \n {props.tableLayout?.columnsResizable && column.getCanResize() && (\n \n )}\n
\n \n )\n}\n\nfunction DataGridTableDndCell({ cell }: { cell: Cell }) {\n const { props } = useDataGrid()\n const { isDragging, setNodeRef, transform, transition } = useSortable({\n id: cell.column.id,\n })\n\n const style: CSSProperties = {\n opacity: isDragging ? 0.8 : 1,\n position: \"relative\",\n transform: CSS.Translate.toString(transform),\n transition,\n cursor: isDragging ? \"grabbing\" : undefined,\n width: props.tableLayout?.columnsResizable\n ? `calc(var(--col-${cell.column.id}-size) * 1px)`\n : cell.column.getSize(),\n zIndex: isDragging ? 1 : 0,\n }\n\n return (\n \n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n \n )\n}\n\nfunction DataGridTableDnd({\n handleDragEnd,\n footerContent,\n}: {\n handleDragEnd: (event: DragEndEvent) => void\n footerContent?: ReactNode\n}) {\n const { table, isLoading, props } = useDataGrid()\n const pagination = table.getState().pagination\n const containerRef = useRef(null)\n const [isDraggingColumn, setIsDraggingColumn] = useState(false)\n\n const sensors = useSensors(\n useSensor(MouseSensor, {}),\n useSensor(TouchSensor, {}),\n useSensor(KeyboardSensor, {})\n )\n\n useEffect(() => {\n if (!isDraggingColumn) return\n\n const { body, documentElement } = document\n const previousBodyCursor = body.style.cursor\n const previousDocumentCursor = documentElement.style.cursor\n\n body.style.cursor = \"grabbing\"\n documentElement.style.cursor = \"grabbing\"\n\n return () => {\n body.style.cursor = previousBodyCursor\n documentElement.style.cursor = previousDocumentCursor\n }\n }, [isDraggingColumn])\n\n // Custom modifier to restrict dragging within table bounds with edge offset\n const restrictToTableBounds: Modifier = ({ draggingNodeRect, transform }) => {\n if (!draggingNodeRect || !containerRef.current) {\n return { ...transform, y: 0 }\n }\n\n const containerRect = containerRef.current.getBoundingClientRect()\n const edgeOffset = 0\n\n const minX = containerRect.left - draggingNodeRect.left - edgeOffset\n const maxX =\n containerRect.right -\n draggingNodeRect.left -\n draggingNodeRect.width +\n edgeOffset\n\n return {\n ...transform,\n x: Math.min(Math.max(transform.x, minX), maxX),\n y: 0, // Lock vertical movement\n }\n }\n\n return (\n setIsDraggingColumn(false)}\n onDragEnd={(event) => {\n setIsDraggingColumn(false)\n handleDragEnd(event)\n }}\n onDragStart={() => setIsDraggingColumn(true)}\n sensors={sensors}\n >\n \n \n \n {table\n .getHeaderGroups()\n .map((headerGroup: HeaderGroup, index) => {\n return (\n \n \n {headerGroup.headers.map((header) => (\n \n ))}\n \n \n )\n })}\n \n\n {(props.tableLayout?.stripped || !props.tableLayout?.rowBorder) && (\n \n )}\n\n \n {props.loadingMode === \"skeleton\" &&\n isLoading &&\n pagination?.pageSize ? (\n Array.from({ length: pagination.pageSize }).map((_, rowIndex) => (\n \n {table.getVisibleFlatColumns().map((column, colIndex) => {\n return (\n \n {column.columnDef.meta?.skeleton}\n \n )\n })}\n \n ))\n ) : table.getRowModel().rows.length ? (\n table.getRowModel().rows.map((row: Row) => {\n return (\n \n \n {row\n .getVisibleCells()\n .map((cell: Cell) => {\n return (\n \n \n \n )\n })}\n \n {row.getIsExpanded() && (\n \n )}\n \n )\n })\n ) : (\n \n )}\n \n\n {footerContent && (\n {footerContent}\n )}\n \n \n \n )\n}\n\nexport { DataGridTableDnd }","target":"components/reui/data-grid/data-grid-table-dnd.tsx"},{"path":"data-grid-table-virtual.tsx","type":"registry:ui","content":"\"use client\"\n\nimport {\n memo,\n ReactNode,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\"\nimport { useDataGrid } from \"@/components/reui/data-grid/data-grid\"\nimport {\n DataGridTableBase,\n DataGridTableBody,\n DataGridTableEmpty,\n DataGridTableFoot,\n DataGridTableHead,\n DataGridTableHeadRow,\n DataGridTableHeadRowCell,\n DataGridTableHeadRowCellResize,\n DataGridTableRenderedRow,\n DataGridTableRowSpacer,\n DataGridTableViewport,\n getDataGridTableRowSections,\n} from \"@/components/reui/data-grid/data-grid-table\"\nimport { flexRender, HeaderGroup, Row, Table } from \"@tanstack/react-table\"\nimport {\n useVirtualizer,\n VirtualItem,\n Virtualizer,\n VirtualizerOptions,\n} from \"@tanstack/react-virtual\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Spinner } from \"@/components/ui/spinner\"\n\ntype DataGridTableVirtualScrollElements = {\n containerElement: HTMLDivElement | null\n scrollElement: HTMLElement | null\n}\n\ntype DataGridTableVirtualizerInstance = Virtualizer<\n HTMLElement,\n HTMLTableRowElement\n>\n\ntype DataGridTableVirtualizerOptions = Omit<\n VirtualizerOptions,\n \"count\" | \"estimateSize\" | \"getItemKey\" | \"getScrollElement\"\n> & {\n estimateSize?: (index: number, row: Row) => number\n getItemKey?: (index: number, row: Row) => string | number\n getScrollElement?: (\n elements: DataGridTableVirtualScrollElements\n ) => HTMLElement | null\n}\n\ninterface DataGridTableVirtualProps {\n height?: number | string\n estimateSize?: number\n overscan?: number\n footerContent?: ReactNode\n renderHeader?: boolean\n onFetchMore?: () => void\n isFetchingMore?: boolean\n hasMore?: boolean\n fetchMoreOffset?: number\n virtualizerOptions?: DataGridTableVirtualizerOptions\n}\n\ninterface VirtualBodyProps {\n table: Table\n columnCount: number\n topRows: Row[]\n centerRows: Row[]\n bottomRows: Row[]\n virtualItems: VirtualItem[]\n totalSize: number\n isVirtualizationEnabled: boolean\n isInfiniteMode: boolean\n isFetchingMore: boolean\n hasMore?: boolean\n loadingMoreMessage: ReactNode\n allRowsLoadedMessage: ReactNode\n measureRowRef?: (element: HTMLTableRowElement | null) => void\n}\n\nfunction DataGridTableVirtualSpacer({\n columnCount,\n height,\n}: {\n columnCount: number\n height: number\n}) {\n if (height <= 0) return null\n\n return (\n
\n \n )\n}\n\nfunction DataGridTableVirtualStatusRow({\n children,\n className,\n columnCount,\n}: {\n children: ReactNode\n className?: string\n columnCount: number\n}) {\n return (\n \n \n {children}\n \n \n )\n}\n\nfunction DataGridTableVirtualBody({\n table,\n columnCount,\n topRows,\n centerRows,\n bottomRows,\n virtualItems,\n totalSize,\n isVirtualizationEnabled,\n isInfiniteMode,\n isFetchingMore,\n hasMore,\n loadingMoreMessage,\n allRowsLoadedMessage,\n measureRowRef,\n}: VirtualBodyProps) {\n const totalRows = topRows.length + centerRows.length + bottomRows.length\n\n if (!totalRows) return \n\n const hasCenterRows = centerRows.length > 0\n const showFetchingRow = isInfiniteMode && isFetchingMore\n const showCompleteRow = isInfiniteMode && hasMore === false && totalRows > 0\n const hasMiddleSection = hasCenterRows || showFetchingRow || showCompleteRow\n const leadingSpacerHeight =\n isVirtualizationEnabled && hasCenterRows && virtualItems.length > 0\n ? (virtualItems[0]?.start ?? 0)\n : 0\n const trailingSpacerHeight =\n isVirtualizationEnabled && hasCenterRows && virtualItems.length > 0\n ? Math.max(\n 0,\n totalSize - (virtualItems[virtualItems.length - 1]?.end ?? 0)\n )\n : 0\n\n const renderedRows: ReactNode[] = []\n\n topRows.forEach((row, index) => {\n renderedRows.push(\n \n )\n })\n\n if (isVirtualizationEnabled) {\n if (leadingSpacerHeight > 0) {\n renderedRows.push(\n \n )\n }\n\n virtualItems.forEach((virtualRow) => {\n const row = centerRows[virtualRow.index]\n\n if (!row) return\n\n renderedRows.push(\n \n )\n })\n\n if (trailingSpacerHeight > 0) {\n renderedRows.push(\n \n )\n }\n } else {\n centerRows.forEach((row) => {\n renderedRows.push()\n })\n }\n\n if (showFetchingRow) {\n renderedRows.push(\n \n
\n \n {loadingMoreMessage}\n
\n \n )\n }\n\n if (showCompleteRow) {\n renderedRows.push(\n \n {allRowsLoadedMessage}\n \n )\n }\n\n bottomRows.forEach((row, index) => {\n renderedRows.push(\n 0 || hasMiddleSection)\n ? \"bottom\"\n : undefined\n }\n />\n )\n })\n\n return <>{renderedRows}\n}\n\n/**\n * Memoized virtual body: skip re-renders during active column resize.\n * Column widths update via CSS variables on the
\n
element,\n * so the browser handles width changes without React re-renders.\n */\nconst MemoizedVirtualBody = memo(\n DataGridTableVirtualBody,\n (_prev, next) => !!next.table.getState().columnSizingInfo.isResizingColumn\n) as typeof DataGridTableVirtualBody\n\nfunction DataGridTableVirtual({\n height,\n estimateSize = 48,\n overscan = 10,\n footerContent,\n renderHeader = true,\n onFetchMore,\n isFetchingMore = false,\n hasMore,\n fetchMoreOffset = 0,\n virtualizerOptions,\n}: DataGridTableVirtualProps) {\n const { table, props } = useDataGrid()\n const { topRows, centerRows, bottomRows } = getDataGridTableRowSections(\n table,\n props.tableLayout?.rowsPinnable\n )\n const columnCount =\n table.getVisibleFlatColumns().length +\n (props.tableLayout?.columnsResizable ? 1 : 0)\n const isInfiniteMode = typeof onFetchMore === \"function\"\n const [viewportElements, setViewportElements] =\n useState({\n containerElement: null,\n scrollElement: null,\n })\n\n const {\n estimateSize: customEstimateSize,\n getItemKey: customGetItemKey,\n getScrollElement: customGetScrollElement,\n measureElement: customMeasureElement,\n overscan: customOverscan,\n ...virtualizerOptionsRest\n } = virtualizerOptions ?? {}\n\n const isVirtualizationEnabled = virtualizerOptions?.enabled !== false\n const loadingMoreMessage =\n props.fetchingMoreMessage || props.loadingMessage || \"Loading...\"\n const allRowsLoadedMessage =\n props.allRowsLoadedMessage || \"All records loaded\"\n\n const handleViewportRef = useCallback((node: HTMLDivElement | null) => {\n setViewportElements({\n containerElement: node,\n scrollElement:\n (node?.closest(\n '[data-slot=\"scroll-area-viewport\"]'\n ) as HTMLElement | null) ?? node,\n })\n }, [])\n\n const usesExternalScrollArea =\n viewportElements.scrollElement !== null &&\n viewportElements.scrollElement !== viewportElements.containerElement\n\n const resolveScrollElement = useCallback(() => {\n if (customGetScrollElement) {\n return customGetScrollElement(viewportElements)\n }\n\n return viewportElements.scrollElement\n }, [customGetScrollElement, viewportElements])\n\n const resolveItemKey = useCallback(\n (index: number) => {\n const row = centerRows[index]\n\n if (!row) return index\n\n return customGetItemKey?.(index, row) ?? row.id ?? index\n },\n [centerRows, customGetItemKey]\n )\n\n const resolveEstimateSize = useCallback(\n (index: number) => {\n const row = centerRows[index]\n\n return row\n ? (customEstimateSize?.(index, row) ?? estimateSize)\n : estimateSize\n },\n [centerRows, customEstimateSize, estimateSize]\n )\n\n const virtualizer = useVirtualizer({\n count: centerRows.length,\n getScrollElement: resolveScrollElement,\n getItemKey: resolveItemKey,\n estimateSize: resolveEstimateSize,\n overscan: customOverscan ?? overscan,\n measureElement: customMeasureElement,\n ...virtualizerOptionsRest,\n }) as DataGridTableVirtualizerInstance\n\n const virtualItems = isVirtualizationEnabled\n ? virtualizer.getVirtualItems()\n : []\n const totalSize = isVirtualizationEnabled ? virtualizer.getTotalSize() : 0\n const measureRowRef =\n isVirtualizationEnabled && customMeasureElement\n ? virtualizer.measureElement\n : undefined\n const resolvedFetchMoreOffset = useMemo(\n () => Math.max(0, fetchMoreOffset),\n [fetchMoreOffset]\n )\n\n useEffect(() => {\n if (\n !isVirtualizationEnabled ||\n !isInfiniteMode ||\n hasMore === false ||\n isFetchingMore\n ) {\n return\n }\n\n const lastItem = virtualItems[virtualItems.length - 1]\n if (!lastItem) return\n\n if (lastItem.index >= centerRows.length - 1 - resolvedFetchMoreOffset) {\n onFetchMore?.()\n }\n }, [\n centerRows.length,\n hasMore,\n isFetchingMore,\n isInfiniteMode,\n isVirtualizationEnabled,\n onFetchMore,\n resolvedFetchMoreOffset,\n virtualItems,\n ])\n\n return (\n \n \n {renderHeader && (\n \n {table\n .getHeaderGroups()\n .map((headerGroup: HeaderGroup, index) => (\n \n {headerGroup.headers.map((header, hIndex) => {\n const { column } = header\n\n return (\n \n {header.isPlaceholder ? null : props.tableLayout\n ?.columnsResizable && column.getCanResize() ? (\n
\n {flexRender(\n header.column.columnDef.header,\n header.getContext()\n )}\n
\n ) : (\n flexRender(\n header.column.columnDef.header,\n header.getContext()\n )\n )}\n {props.tableLayout?.columnsResizable &&\n column.getCanResize() && (\n \n )}\n
\n )\n })}\n
\n ))}\n
\n )}\n\n {renderHeader &&\n (props.tableLayout?.stripped || !props.tableLayout?.rowBorder) && (\n \n )}\n\n \n \n \n\n {footerContent && (\n {footerContent}\n )}\n
\n \n )\n}\n\nexport { DataGridTableVirtual }\nexport type {\n DataGridTableVirtualProps,\n DataGridTableVirtualScrollElements,\n DataGridTableVirtualizerOptions,\n}","target":"components/reui/data-grid/data-grid-table-virtual.tsx"},{"path":"data-grid-table.tsx","type":"registry:ui","content":"\"use client\"\n\nimport {\n CSSProperties,\n Fragment,\n memo,\n MouseEvent as ReactMouseEvent,\n ReactNode,\n TouchEvent as ReactTouchEvent,\n Ref,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\"\nimport { useDataGrid } from \"@/components/reui/data-grid/data-grid\"\nimport {\n Cell,\n Column,\n flexRender,\n Header,\n HeaderGroup,\n Row,\n Table,\n} from \"@tanstack/react-table\"\nimport { cva } from \"class-variance-authority\"\n\nimport { cn } from \"@/lib/utils\"\nimport { Checkbox } from \"@/components/ui/checkbox\"\nimport { Spinner } from \"@/components/ui/spinner\"\n\nconst headerCellSpacingVariants = cva(\"\", {\n variants: {\n size: {\n dense:\n \"px-2.5 h-9\",\n default:\n \"px-4\",\n },\n },\n defaultVariants: {\n size: \"default\",\n },\n})\n\nconst bodyCellSpacingVariants = cva(\"\", {\n variants: {\n size: {\n dense:\n \"px-2.5 py-2\",\n default:\n \"px-4 py-2.5\",\n },\n },\n defaultVariants: {\n size: \"default\",\n },\n})\n\nconst footerCellSpacingVariants = cva(\"\", {\n variants: {\n size: {\n dense:\n \"px-2.5 py-2\",\n default:\n \"px-4 py-2.5\",\n },\n },\n defaultVariants: {\n size: \"default\",\n },\n})\n\nfunction getPinningStyles(column: Column): CSSProperties {\n const isPinned = column.getIsPinned()\n\n return {\n left: isPinned === \"left\" ? `${column.getStart(\"left\")}px` : undefined,\n right: isPinned === \"right\" ? `${column.getAfter(\"right\")}px` : undefined,\n position: isPinned ? \"sticky\" : \"relative\",\n width: column.getSize(),\n zIndex: isPinned ? 1 : 0,\n }\n}\n\nfunction assignRef(ref: Ref | undefined, value: T | null) {\n if (!ref) return\n\n if (typeof ref === \"function\") {\n ref(value)\n return\n }\n\n ;(ref as { current: T | null }).current = value\n}\n\ntype DataGridResizeStartEvent =\n | ReactMouseEvent\n | ReactTouchEvent\n\ntype DataGridResizeDocumentEvent = globalThis.MouseEvent | globalThis.TouchEvent\n\nfunction isDataGridTouchEvent(\n event: DataGridResizeStartEvent | DataGridResizeDocumentEvent\n): event is ReactTouchEvent | globalThis.TouchEvent {\n return \"touches\" in event\n}\n\nfunction getDataGridResizeEventClientX(\n event: DataGridResizeStartEvent | DataGridResizeDocumentEvent\n) {\n if (isDataGridTouchEvent(event)) {\n return event.touches[0]?.clientX ?? event.changedTouches[0]?.clientX\n }\n\n return event.clientX\n}\n\nfunction startDataGridColumnResizeOnEnd(\n event: DataGridResizeStartEvent,\n header: Header,\n table: Table\n) {\n const column = table.getColumn(header.column.id)\n\n if (!column || !column.getCanResize()) return\n if (isDataGridTouchEvent(event) && event.touches.length > 1) return\n\n event.persist?.()\n\n const ownerDocument = event.currentTarget.ownerDocument\n const previousBodyCursor = ownerDocument.body.style.cursor\n const previousDocumentCursor = ownerDocument.documentElement.style.cursor\n const startSize = header.getSize()\n const dragStartClientX = getDataGridResizeEventClientX(event)\n const headerCell = event.currentTarget.closest(\"th\")\n const headerRect = headerCell?.getBoundingClientRect()\n const startOffset =\n headerRect &&\n Number.isFinite(\n table.options.columnResizeDirection === \"rtl\"\n ? headerRect.left\n : headerRect.right\n )\n ? table.options.columnResizeDirection === \"rtl\"\n ? headerRect.left\n : headerRect.right\n : dragStartClientX\n\n if (typeof dragStartClientX !== \"number\" || typeof startOffset !== \"number\") {\n return\n }\n\n ownerDocument.body.style.cursor = \"col-resize\"\n ownerDocument.documentElement.style.cursor = \"col-resize\"\n\n const columnSizingStart = header\n .getLeafHeaders()\n .map(\n (leafHeader) =>\n [leafHeader.column.id, leafHeader.column.getSize()] as [string, number]\n )\n const directionMultiplier =\n table.options.columnResizeDirection === \"rtl\" ? -1 : 1\n\n const updateOffset = (clientXPos?: number, commit = false) => {\n if (typeof clientXPos !== \"number\") return\n\n let nextColumnSizing: Record = {}\n const deltaOffset = (clientXPos - dragStartClientX) * directionMultiplier\n const deltaPercentage = Math.max(deltaOffset / startSize, -0.999999)\n\n columnSizingStart.forEach(([columnId, headerSize]) => {\n nextColumnSizing[columnId] =\n Math.round(\n Math.max(headerSize + headerSize * deltaPercentage, 0) * 100\n ) / 100\n })\n\n table.setColumnSizingInfo((old) => ({\n ...old,\n startOffset,\n startSize,\n deltaOffset,\n deltaPercentage,\n columnSizingStart,\n isResizingColumn: column.id,\n }))\n\n if (commit) {\n table.setColumnSizing((old) => ({\n ...old,\n ...nextColumnSizing,\n }))\n }\n }\n\n const endResize = (clientXPos?: number) => {\n updateOffset(clientXPos, true)\n table.setColumnSizingInfo((old) => ({\n ...old,\n isResizingColumn: false,\n startOffset: null,\n startSize: null,\n deltaOffset: null,\n deltaPercentage: null,\n columnSizingStart: [],\n }))\n ownerDocument.body.style.cursor = previousBodyCursor\n ownerDocument.documentElement.style.cursor = previousDocumentCursor\n }\n\n const mouseMoveHandler = (moveEvent: globalThis.MouseEvent) => {\n updateOffset(moveEvent.clientX)\n }\n const mouseUpHandler = (upEvent: globalThis.MouseEvent) => {\n ownerDocument.removeEventListener(\"mousemove\", mouseMoveHandler)\n ownerDocument.removeEventListener(\"mouseup\", mouseUpHandler)\n endResize(upEvent.clientX)\n }\n const touchMoveHandler = (moveEvent: globalThis.TouchEvent) => {\n if (moveEvent.cancelable) {\n moveEvent.preventDefault()\n moveEvent.stopPropagation()\n }\n\n updateOffset(getDataGridResizeEventClientX(moveEvent))\n }\n const touchEndHandler = (endEvent: globalThis.TouchEvent) => {\n ownerDocument.removeEventListener(\"touchmove\", touchMoveHandler)\n ownerDocument.removeEventListener(\"touchend\", touchEndHandler)\n\n if (endEvent.cancelable) {\n endEvent.preventDefault()\n endEvent.stopPropagation()\n }\n\n endResize(getDataGridResizeEventClientX(endEvent))\n }\n\n const passiveIfSupported = { passive: false } as const\n\n if (isDataGridTouchEvent(event)) {\n ownerDocument.addEventListener(\n \"touchmove\",\n touchMoveHandler,\n passiveIfSupported\n )\n ownerDocument.addEventListener(\n \"touchend\",\n touchEndHandler,\n passiveIfSupported\n )\n } else {\n ownerDocument.addEventListener(\n \"mousemove\",\n mouseMoveHandler,\n passiveIfSupported\n )\n ownerDocument.addEventListener(\n \"mouseup\",\n mouseUpHandler,\n passiveIfSupported\n )\n }\n\n table.setColumnSizingInfo((old) => ({\n ...old,\n startOffset,\n startSize,\n deltaOffset: 0,\n deltaPercentage: 0,\n columnSizingStart,\n isResizingColumn: column.id,\n }))\n}\n\ntype DataGridTablePinnedBoundary = \"top\" | \"bottom\"\n\nfunction getDataGridTableRowSections(\n table: Table,\n rowsPinnable?: boolean\n) {\n if (!rowsPinnable) {\n return {\n topRows: [] as Row[],\n centerRows: table.getRowModel().rows as Row[],\n bottomRows: [] as Row[],\n }\n }\n\n return {\n topRows: table.getTopRows() as Row[],\n centerRows: table.getCenterRows() as Row[],\n bottomRows: table.getBottomRows() as Row[],\n }\n}\n\nfunction getDataGridTableResolvedRows(\n table: Table,\n rowsPinnable?: boolean\n) {\n const { topRows, centerRows, bottomRows } = getDataGridTableRowSections(\n table,\n rowsPinnable\n )\n const resolvedRows: Array<{\n row: Row\n pinnedBoundary?: DataGridTablePinnedBoundary\n }> = []\n\n topRows.forEach((row, index) => {\n resolvedRows.push({\n row,\n pinnedBoundary:\n index === topRows.length - 1 &&\n (centerRows.length > 0 || bottomRows.length > 0)\n ? \"top\"\n : undefined,\n })\n })\n\n centerRows.forEach((row) => {\n resolvedRows.push({ row })\n })\n\n bottomRows.forEach((row, index) => {\n resolvedRows.push({\n row,\n pinnedBoundary:\n index === 0 && (centerRows.length > 0 || topRows.length > 0)\n ? \"bottom\"\n : undefined,\n })\n })\n\n return resolvedRows\n}\n\nfunction DataGridTableFillCol() {\n const { props } = useDataGrid()\n\n if (!props.tableLayout?.columnsResizable) return null\n\n return (\n \n )\n}\n\nfunction DataGridTableFillHeadCell() {\n const { props } = useDataGrid()\n\n if (!props.tableLayout?.columnsResizable) return null\n\n return (\n \n )\n}\n\nfunction DataGridTableFillBodyCell() {\n const { props } = useDataGrid()\n\n if (!props.tableLayout?.columnsResizable) return null\n\n return (\n \n )\n}\n\nfunction DataGridTableFillFootCell() {\n const { props } = useDataGrid()\n\n if (!props.tableLayout?.columnsResizable) return null\n\n return (\n \n )\n}\n\nfunction DataGridTableBase({ children }: { children: ReactNode }) {\n const { props, table } = useDataGrid()\n const visibleColumns = table.getVisibleLeafColumns()\n\n /**\n * Compute column widths as CSS custom properties once upfront (memoized).\n * Cells reference these via calc(var(--col-X-size) * 1px) so the browser\n * handles width propagation without per-cell getSize() calls or React\n * re-renders of the body.\n */\n const columnSizeVars = useMemo(() => {\n if (!props.tableLayout?.columnsResizable) return undefined\n const headers = table.getFlatHeaders()\n const colSizes: Record = {}\n for (let i = 0; i < headers.length; i++) {\n const header = headers[i]!\n colSizes[`--header-${header.id}-size`] = header.getSize()\n colSizes[`--col-${header.column.id}-size`] = header.column.getSize()\n }\n return colSizes\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n props.tableLayout?.columnsResizable,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n table.getState().columnSizing,\n ])\n\n return (\n \n
\n {visibleColumns.map((column) => (\n \n ))}\n \n \n {children}\n
\n )\n}\n\nfunction DataGridTableViewport({\n children,\n className,\n viewportRef,\n style,\n}: {\n children: ReactNode\n className?: string\n viewportRef?: Ref\n style?: CSSProperties\n}) {\n const { props, table } = useDataGrid()\n const [viewportElement, setViewportElement] = useState(\n null\n )\n const [containerWidth, setContainerWidth] = useState(0)\n const handleViewportRef = useCallback(\n (node: HTMLDivElement | null) => {\n setViewportElement(node)\n assignRef(viewportRef, node)\n },\n [viewportRef]\n )\n const fillWidth =\n props.tableLayout?.columnsResizable && containerWidth > 0\n ? Math.max(0, containerWidth - table.getTotalSize())\n : 0\n\n useEffect(() => {\n if (!viewportElement || !props.tableLayout?.columnsResizable) {\n setContainerWidth(0)\n return\n }\n\n const scrollViewport =\n (viewportElement.closest(\n '[data-slot=\"scroll-area-viewport\"]'\n ) as HTMLElement | null) ?? viewportElement.parentElement\n const measurementTarget = scrollViewport ?? viewportElement\n\n const syncContainerWidth = () => {\n setContainerWidth(measurementTarget.clientWidth)\n }\n\n syncContainerWidth()\n\n if (typeof ResizeObserver === \"undefined\") return\n\n const observer = new ResizeObserver(syncContainerWidth)\n observer.observe(measurementTarget)\n\n return () => {\n observer.disconnect()\n }\n }, [props.tableLayout?.columnsResizable, viewportElement])\n\n return (\n \n {children}\n \n \n )\n}\n\nfunction DataGridTableHead({ children }: { children: ReactNode }) {\n const { props } = useDataGrid()\n\n return (\n \n {children}\n \n )\n}\n\nfunction DataGridTableHeadRow({\n children,\n headerGroup,\n}: {\n children: ReactNode\n headerGroup: HeaderGroup\n}) {\n const { props } = useDataGrid()\n\n return (\n th]:border-b\",\n props.tableLayout?.cellBorder && \"*:last:border-e-0\",\n props.tableLayout?.stripped && \"bg-transparent\",\n props.tableLayout?.headerBackground === false && \"bg-transparent\",\n props.tableClassNames?.headerRow\n )}\n >\n {children}\n \n \n )\n}\n\nfunction DataGridTableHeadRowCell({\n children,\n header,\n dndRef,\n dndStyle,\n}: {\n children: ReactNode\n header: Header\n dndRef?: React.Ref\n dndStyle?: CSSProperties\n}) {\n const { props } = useDataGrid()\n\n const { column } = header\n const isPinned = column.getIsPinned()\n const isLastLeftPinned = isPinned === \"left\" && column.getIsLastColumn(\"left\")\n const isFirstRightPinned =\n isPinned === \"right\" && column.getIsFirstColumn(\"right\")\n const isLastVisibleColumn =\n column.getIndex() ===\n header.getContext().table.getVisibleLeafColumns().length - 1\n const headerCellSpacing = headerCellSpacingVariants({\n size: props.tableLayout?.dense ? \"dense\" : \"default\",\n })\n\n return (\n \n {children}\n \n )\n}\n\nfunction DataGridTableHeadRowCellResize({\n header,\n}: {\n header: Header\n}) {\n const { props, table } = useDataGrid()\n const { column } = header\n const isLastVisibleColumn =\n column.getIndex() ===\n header.getContext().table.getVisibleLeafColumns().length - 1\n const isResizeModeOnEnd =\n (props.tableLayout?.columnsResizeMode ?? table.options.columnResizeMode) ===\n \"onEnd\"\n\n const handleMouseDown = (event: ReactMouseEvent) => {\n event.preventDefault()\n event.stopPropagation()\n\n if (isResizeModeOnEnd) {\n startDataGridColumnResizeOnEnd(event, header, table)\n return\n }\n\n header.getResizeHandler()(event)\n }\n\n const handleTouchStart = (event: ReactTouchEvent) => {\n event.preventDefault()\n event.stopPropagation()\n\n if (isResizeModeOnEnd) {\n startDataGridColumnResizeOnEnd(event, header, table)\n return\n }\n\n header.getResizeHandler()(event)\n }\n\n return (\n column.resetSize(),\n onMouseDown: handleMouseDown,\n onTouchStart: handleTouchStart,\n className: cn(\n \"absolute top-0 h-full cursor-col-resize user-select-none touch-none z-10 flex\",\n isLastVisibleColumn\n ? \"end-0 w-5 justify-end before:hidden\"\n : \"-end-2 w-5 justify-center before:absolute before:inset-y-0 before:w-px before:-translate-x-px before:bg-border\",\n column.getIsResizing() &&\n (isResizeModeOnEnd\n ? \"opacity-100\"\n : isLastVisibleColumn\n ? \"before:absolute before:end-0 before:block before:inset-y-0 before:w-0.5 before:bg-primary opacity-100\"\n : \"before:block before:bg-primary before:w-0.5 opacity-100\")\n ),\n }}\n />\n )\n}\n\nfunction DataGridTableResizeIndicator({\n viewportElement,\n}: {\n viewportElement: HTMLDivElement | null\n}) {\n const { props, table } = useDataGrid()\n const columnSizingInfo = table.getState().columnSizingInfo\n const resizingColumnId = columnSizingInfo.isResizingColumn\n const resizeMode =\n props.tableLayout?.columnsResizeMode ?? table.options.columnResizeMode\n\n if (\n !props.tableLayout?.columnsResizable ||\n resizeMode !== \"onEnd\" ||\n !resizingColumnId\n ) {\n return null\n }\n\n const resizingHeader = table\n .getFlatHeaders()\n .find(\n (header) =>\n header.column.id === resizingColumnId || header.id === resizingColumnId\n )\n\n if (!resizingHeader) return null\n\n const deltaOffset = columnSizingInfo.deltaOffset ?? 0\n const headerHeight =\n viewportElement\n ?.querySelector('[data-slot=\"data-grid-table\"] thead')\n ?.getBoundingClientRect().height ?? 0\n const indicatorLeft =\n typeof columnSizingInfo.startOffset === \"number\" && viewportElement\n ? columnSizingInfo.startOffset -\n viewportElement.getBoundingClientRect().left\n : resizingHeader.getStart() + resizingHeader.getSize()\n\n return (\n \n
\n \n
\n )\n}\n\nfunction DataGridTableRowSpacer() {\n return \n}\n\nfunction DataGridTableBody({ children }: { children: ReactNode }) {\n const { props } = useDataGrid()\n\n return (\n \n {children}\n \n )\n}\n\nfunction DataGridTableFoot({ children }: { children: ReactNode }) {\n const { props } = useDataGrid()\n return (\n \n {children}\n \n )\n}\n\nfunction DataGridTableFootRow({ children }: { children: ReactNode }) {\n const { props } = useDataGrid()\n return (\n \n {children}\n \n \n )\n}\n\nfunction DataGridTableFootRowCell({\n children,\n colSpan,\n className,\n}: {\n children?: ReactNode\n colSpan?: number\n className?: string\n}) {\n const { props } = useDataGrid()\n const spacing = footerCellSpacingVariants({\n size: props.tableLayout?.dense ? \"dense\" : \"default\",\n })\n return (\n \n {children}\n \n )\n}\n\nfunction DataGridTableBodyRowSkeleton({ children }: { children: ReactNode }) {\n const { table, props } = useDataGrid()\n\n return (\n td]:border-b\",\n props.tableLayout?.cellBorder && \"*:last:border-e-0\",\n props.tableLayout?.stripped &&\n \"odd:bg-muted/90 odd:hover:bg-muted hover:bg-transparent\",\n table.options.enableRowSelection && \"*:first:relative\",\n props.tableClassNames?.bodyRow\n )}\n >\n {children}\n \n \n )\n}\n\nfunction DataGridTableBodyRowSkeletonCell({\n children,\n column,\n}: {\n children: ReactNode\n column: Column\n}) {\n const { props, table } = useDataGrid()\n const bodyCellSpacing = bodyCellSpacingVariants({\n size: props.tableLayout?.dense ? \"dense\" : \"default\",\n })\n\n return (\n \n {children}\n \n )\n}\n\nfunction DataGridTableBodyRow({\n children,\n row,\n pinnedBoundary,\n rowRef,\n dndRef,\n dndStyle,\n}: {\n children: ReactNode\n row: Row\n pinnedBoundary?: DataGridTablePinnedBoundary\n rowRef?: React.Ref\n dndRef?: React.Ref\n dndStyle?: CSSProperties\n}) {\n const { props, table } = useDataGrid()\n const isRowPinned = row.getIsPinned()\n\n return (\n {\n assignRef(rowRef, node)\n assignRef(dndRef, node)\n }}\n style={{ ...(dndStyle ? dndStyle : null) }}\n data-state={\n table.options.enableRowSelection && row.getIsSelected()\n ? \"selected\"\n : undefined\n }\n data-row-pinned={isRowPinned || undefined}\n data-row-pinned-boundary={pinnedBoundary}\n onClick={() => props.onRowClick && props.onRowClick(row.original)}\n className={cn(\n \"hover:bg-muted/40 data-[state=selected]:bg-muted/50\",\n props.onRowClick && \"cursor-pointer\",\n !props.tableLayout?.stripped &&\n props.tableLayout?.rowBorder &&\n \"border-border border-b [&:not(:last-child)>td]:border-b\",\n props.tableLayout?.cellBorder && \"*:last:border-e-0\",\n props.tableLayout?.stripped &&\n \"odd:bg-muted/90 odd:hover:bg-muted hover:bg-transparent\",\n table.options.enableRowSelection && \"*:first:relative\",\n props.tableLayout?.rowsPinnable &&\n isRowPinned &&\n \"bg-muted/30 hover:bg-muted/50\",\n pinnedBoundary === \"top\" && \"[&>td]:shadow-[0_2px_0_rgba(0,0,0,0.03)]\",\n pinnedBoundary === \"bottom\" &&\n \"[&>td]:shadow-[0_2px_0_rgba(0,0,0,0.03)]\",\n props.tableClassNames?.bodyRow\n )}\n >\n {children}\n \n \n )\n}\n\nfunction DataGridTableBodyRowExpandded({ row }: { row: Row }) {\n const { props, table } = useDataGrid()\n\n return (\n td]:border-b\"\n )}\n >\n \n {table\n .getAllColumns()\n .find((column) => column.columnDef.meta?.expandedContent)\n ?.columnDef.meta?.expandedContent?.(row.original)}\n \n \n )\n}\n\nfunction DataGridTableBodyRowCell({\n children,\n cell,\n dndRef,\n dndStyle,\n}: {\n children: ReactNode\n cell: Cell\n dndRef?: React.Ref\n dndStyle?: CSSProperties\n}) {\n const { props } = useDataGrid()\n\n const { column, row } = cell\n const isPinned = column.getIsPinned()\n const isLastLeftPinned = isPinned === \"left\" && column.getIsLastColumn(\"left\")\n const isFirstRightPinned =\n isPinned === \"right\" && column.getIsFirstColumn(\"right\")\n const bodyCellSpacing = bodyCellSpacingVariants({\n size: props.tableLayout?.dense ? \"dense\" : \"default\",\n })\n\n return (\n \n {children}\n \n )\n}\n\nfunction DataGridTableRenderedRow({\n row,\n pinnedBoundary,\n rowRef,\n}: {\n row: Row\n pinnedBoundary?: DataGridTablePinnedBoundary\n rowRef?: React.Ref\n}) {\n return (\n \n \n {row.getVisibleCells().map((cell: Cell) => (\n \n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n \n ))}\n \n {row.getIsExpanded() && }\n \n )\n}\n\nfunction DataGridTableEmpty() {\n const { table, props } = useDataGrid()\n const visibleColumnCount =\n table.getVisibleLeafColumns().length +\n (props.tableLayout?.columnsResizable ? 1 : 0)\n\n return (\n \n \n {props.emptyMessage || \"No data available\"}\n \n \n )\n}\n\nfunction DataGridTableLoader() {\n const { props } = useDataGrid()\n\n return (\n
\n
\n \n {props.loadingMessage || \"Loading...\"}\n
\n
\n )\n}\n\nfunction DataGridTableRowPin({ row }: { row: Row }) {\n const isPinned = row.getIsPinned()\n\n return (\n {\n if (isPinned) {\n row.pin(false)\n } else {\n row.pin(\"top\")\n }\n }}\n className={cn(\n \"text-muted-foreground hover:text-foreground inline-flex size-7 items-center justify-center rounded-md transition-colors\",\n isPinned && \"text-primary hover:text-primary/80\"\n )}\n >\n {isPinned ? (\n \n \n \n ) : (\n \n \n \n \n )}\n \n )\n}\n\nfunction DataGridTableRowSelect({ row }: { row: Row }) {\n return (\n <>\n