// Based on https://ui.shadcn.com/docs/components/data-table 'use client' import type { TableOptions, FilterFnOption } from '@tanstack/react-table' import { type ColumnDef, flexRender, getCoreRowModel, type RowData, type SortingState, useReactTable, getSortedRowModel, getFilteredRowModel, } from '@tanstack/react-table' import React from 'react' import { usePlaceholderRows } from '../../hooks' import { cn } from '../../utils' import { Typography } from '../Typography' import { CaretSortIcon } from './CaretSortIcon' import { SkeletonCell } from './SkeletonCell' import { TableRoot, TableBody, TableCell, TableHead, TableHeader, TableRow, } from './Table' import { TablePagination } from './TablePagination' import { usePagination } from './usePagination' // Extend table meta with reusable custom props declare module '@tanstack/table-core' { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface ColumnMeta { cellClassName?: string skeletonWidth?: number } } export type DataTableColumn = ColumnDef // Pagination configuration export interface PaginationConfig { /** Zero-based page index for controlled pagination. */ pageIndex?: number /** Called when the user moves to a different page. */ onPageChange?: (pageIndex: number, pageSize: number) => void /** Needed when totalCount is unknown so the next arrow can still be enabled. */ canNextPage?: boolean /** Temporarily hide the `Showing X to Y` text and render controls only. */ showPaginationDisplay?: boolean /** Needed with currentPageCount to render `Showing X to Y`, and with totalCount to derive select options. */ pageSize?: number /** Needed with pageSize to render `Showing X to Y` when totalCount is unknown. */ currentPageCount?: number /** Enables `of Z` in the range display and page selection when paired with pageSize. */ totalCount?: number serverSide?: boolean /** `select` shows a dropdown when possible; `display` keeps a static `Page N` label. */ pageControlMode?: 'select' | 'display' } interface DataTableProps { columns: DataTableColumn[] data: TData[] isLoading?: boolean pagination?: PaginationConfig tableOptions?: Partial> onRowClick?: (row: TData) => void /** Custom placeholder count when loading. If provided, overrides all other placeholder count logic. * Without this prop, the priority is: data.length > pagination.pageSize > 5 (default) */ placeholderCount?: number } export function DataTable({ columns, data, isLoading, pagination, tableOptions = {}, onRowClick, placeholderCount, }: DataTableProps) { const [internalSorting, setInternalSorting] = React.useState( tableOptions.initialState?.sorting || [], ) // Use default pagination only if pagination is not provided const paginationWithDefaults = pagination ?? { pageSize: 10 } const paginationOptions = usePagination(paginationWithDefaults) // Handle loading state and placeholder rows internally // Priority: placeholderCount > data.length > pagination.pageSize > 5 (default) const tableData = usePlaceholderRows({ data, isLoading: isLoading ?? false, pageSize: pagination?.pageSize ?? paginationWithDefaults.pageSize, placeholderCount, }) // Use external sorting state if provided, otherwise use internal const sorting = tableOptions.state?.sorting ?? internalSorting const onSortingChange = tableOptions.onSortingChange ?? setInternalSorting // Extract state and handlers to avoid overwriting in spreads const { state: externalState, onSortingChange: _onSortingChange, ...restTableOptions } = tableOptions const { sorting: _sorting, ...restExternalState } = externalState ?? {} const { state: paginationState, ...restPaginationOptions } = paginationOptions const tableConfig = React.useMemo(() => { return { data: tableData.data as TData[], columns, getCoreRowModel: getCoreRowModel(), onSortingChange, getSortedRowModel: getSortedRowModel(), getFilteredRowModel: getFilteredRowModel(), globalFilterFn: 'auto' as FilterFnOption, enableSortingRemoval: false, state: { sorting, ...restExternalState, ...paginationState, }, meta: { ...tableOptions.meta, ...paginationOptions.meta, }, ...restTableOptions, ...restPaginationOptions, } }, [ tableData.data, columns, sorting, onSortingChange, tableOptions.meta, paginationOptions.meta, restExternalState, paginationState, restTableOptions, restPaginationOptions, ]) const table = useReactTable(tableConfig) return ( <> {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => ( {header.column.getCanSort() ? ( ) : ( !header.isPlaceholder && flexRender( header.column.columnDef.header, header.getContext(), ) )} ))} ))} {table.getRowModel().rows.length ? ( table.getRowModel().rows.map((row) => ( onRowClick?.(row.original)}> {row.getVisibleCells().map((cell) => tableData.isLoading ? ( ) : ( {flexRender( cell.column.columnDef.cell, cell.getContext(), )} ), )} )) ) : ( No data available. )} ) }