Created
December 19, 2024 14:08
-
-
Save bithavoc/3e9d77bc310e04605c09d4df8c59478d to your computer and use it in GitHub Desktop.
RackTable-React-Snippet
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
Table, | |
TableBody, | |
TableCell, | |
TableHead, | |
TableHeader, | |
TableRow, | |
} from '@/componentsV2/ui/table'; | |
import { cn } from '@/lib/utils'; | |
import { | |
Column, | |
ColumnDef, | |
flexRender, | |
getCoreRowModel, | |
getSortedRowModel, | |
Row, | |
useReactTable, | |
VisibilityState, | |
} from '@tanstack/react-table'; | |
import { useVirtualizer } from '@tanstack/react-virtual'; | |
import { ArrowDownWideNarrow, ChartNoAxesColumn } from 'lucide-react'; | |
import { useRef } from 'react'; | |
import { v4 } from 'uuid'; | |
import { Skeleton } from './ui/skeleton'; | |
const placeholders = Array.from({ length: 6 }).map(() => v4()); | |
interface DataTableProps<TData, TValue> { | |
columns: ColumnDef<TData, TValue>[]; | |
data: TData[]; | |
isLoading?: boolean; | |
columnVisibility?: VisibilityState; | |
} | |
const RackTable = <TData, TValue>({ | |
columns, | |
data, | |
isLoading, | |
columnVisibility, | |
}: DataTableProps<TData, TValue>) => { | |
const table = useReactTable({ | |
data, | |
columns, | |
state: { | |
columnVisibility, | |
}, | |
getCoreRowModel: getCoreRowModel(), | |
getSortedRowModel: getSortedRowModel(), | |
}); | |
const { rows } = table.getRowModel(); | |
// The virtualizer needs to know the scrollable container element | |
const tableContainerRef = useRef<HTMLDivElement>(null); | |
const rowVirtualizer = useVirtualizer({ | |
count: rows.length, | |
estimateSize: () => 33, // estimate row height for accurate scrollbar dragging | |
getScrollElement: () => tableContainerRef.current, | |
// measure dynamic row height, except in firefox because it measures table border height incorrectly | |
measureElement: | |
typeof window !== 'undefined' && | |
navigator.userAgent.indexOf('Firefox') === -1 | |
? (element) => element?.getBoundingClientRect().height | |
: undefined, | |
overscan: 5, | |
}); | |
function getSortingTitle(column: Column<TData, unknown>) { | |
if (!column.getCanSort()) { | |
return undefined; | |
} | |
const nextOrder = column.getNextSortingOrder(); | |
if (nextOrder === 'asc') { | |
return 'Sort ascending'; | |
} | |
if (nextOrder === 'desc') { | |
return 'Sort descending'; | |
} | |
return 'Clear sort'; | |
} | |
return ( | |
<div | |
className="max-h-[600px] grid relative overflow-auto w-full" | |
ref={tableContainerRef} | |
style={{ | |
height: 'auto', // should be a fixed height | |
}} | |
> | |
<Table> | |
{data.length > 0 && ( | |
<TableHeader | |
className="sticky top-0 z-10 flex-grow w-full" | |
style={{ | |
display: 'grid', | |
}} | |
> | |
{table.getHeaderGroups().map((headerGroup) => ( | |
<TableRow className="flex-grow flex" key={headerGroup.id}> | |
{headerGroup.headers.map((header, idx) => ( | |
<TableHead | |
className={cn( | |
'flex group flex-grow min-w-52 cursor-pointer items-center justify-between gap-2', | |
{ | |
'border-r': idx === 0, | |
'bg-muted/80': header.column.getIsSorted(), | |
} | |
)} | |
style={{ | |
width: header.getSize(), | |
}} | |
key={header.id} | |
onClick={header.column.getToggleSortingHandler()} | |
title={getSortingTitle(header.column)} | |
> | |
{header.isPlaceholder | |
? null | |
: flexRender( | |
header.column.columnDef.header, | |
header.getContext() | |
)} | |
<span | |
className={cn( | |
'invisible group-hover:visible transition-all', | |
{ | |
visible: header.column.getIsSorted(), | |
} | |
)} | |
> | |
{{ | |
asc: ( | |
<ArrowDownWideNarrow className="w-4 h-4 rotate-180" /> | |
), | |
desc: <ArrowDownWideNarrow className="w-4 h-4" />, | |
}[header.column.getIsSorted() as string] ?? ( | |
<ArrowDownWideNarrow className="w-4 h-4" /> | |
)} | |
</span> | |
</TableHead> | |
))} | |
</TableRow> | |
))} | |
</TableHeader> | |
)} | |
<TableBody | |
className="w-full flex-grow" | |
style={{ | |
display: 'grid', | |
height: `${rowVirtualizer.getTotalSize()}px`, // tells scrollbar how big the table is | |
position: 'relative', // needed for absolute positioning of rows | |
}} | |
> | |
{rowVirtualizer.getVirtualItems().map((row) => { | |
const virtualRow = rows[row.index] as Row<TData>; | |
return ( | |
<TableRow | |
className="flex-grow" | |
key={virtualRow.id} | |
data-index={virtualRow.index} // needed for dynamic row height measurement | |
ref={(node) => rowVirtualizer.measureElement(node)} // measure dynamic row height | |
style={{ | |
display: 'flex', | |
position: 'absolute', | |
transform: `translateY(${row.start}px)`, // this should always be a `style` as it changes on scroll | |
width: '100%', | |
}} | |
> | |
{virtualRow.getVisibleCells().map((cell, idx) => ( | |
<TableCell | |
className={cn('flex-grow', { | |
'border-r': idx === 0, | |
'bg-table-background/40': cell.column.getIsSorted(), | |
})} | |
style={{ | |
display: 'flex', | |
width: cell.column.getSize(), | |
}} | |
key={cell.id} | |
> | |
{flexRender(cell.column.columnDef.cell, cell.getContext())} | |
</TableCell> | |
))} | |
</TableRow> | |
); | |
})} | |
</TableBody> | |
</Table> | |
{data.length < 1 && !isLoading && ( | |
<div className="flex flex-col items-center justify-center h-[50vh] gap-6"> | |
<div className="flex items-center justify-center w-20 h-20 bg-gray-100 rounded-full dark:bg-gray-800"> | |
<ChartNoAxesColumn className="w-10 h-10 text-gray-500 dark:text-gray-400" /> | |
</div> | |
<div className="space-y-2 text-center"> | |
<h2 className="text-2xl font-bold tracking-tight"> | |
No data to display | |
</h2> | |
<p className="text-gray-500 dark:text-gray-400"> | |
It looks like there's no data available. | |
</p> | |
</div> | |
</div> | |
)} | |
{isLoading && ( | |
<div className="divide-y divide-border"> | |
{placeholders.map((placeholder) => ( | |
<div className="p-5" key={placeholder}> | |
<Skeleton className="w-full h-2" /> | |
</div> | |
))} | |
</div> | |
)} | |
</div> | |
); | |
}; | |
export default RackTable; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment