Skip to content

Instantly share code, notes, and snippets.

@bithavoc
Created December 19, 2024 14:08
Show Gist options
  • Save bithavoc/3e9d77bc310e04605c09d4df8c59478d to your computer and use it in GitHub Desktop.
Save bithavoc/3e9d77bc310e04605c09d4df8c59478d to your computer and use it in GitHub Desktop.
RackTable-React-Snippet
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&apos;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