"use client"; import type { Row } from "@tanstack/react-table"; import { flexRender } from "@tanstack/react-table"; import type { Table as ReactTableType } from "@tanstack/react-table"; import { useVirtualizer } from "@tanstack/react-virtual"; // eslint-disable-next-line no-restricted-imports import kebabCase from "lodash/kebabCase"; import { useMemo, useEffect } from "react"; import classNames from "@calcom/lib/classNames"; import { Icon, TableNew, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@calcom/ui"; export interface DataTableProps { table: ReactTableType; tableContainerRef: React.RefObject; isPending?: boolean; onRowMouseclick?: (row: Row) => void; onScroll?: (e: Pick, "target">) => void; tableOverlay?: React.ReactNode; variant?: "default" | "compact"; "data-testid"?: string; children?: React.ReactNode; } export function DataTable({ table, tableContainerRef, isPending, variant, onRowMouseclick, onScroll, children, ...rest }: DataTableProps & React.ComponentPropsWithoutRef<"div">) { const { rows } = table.getRowModel(); // https://stackblitz.com/github/tanstack/table/tree/main/examples/react/virtualized-infinite-scrolling const rowVirtualizer = useVirtualizer({ count: rows.length, estimateSize: () => 100, 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: 10, }); useEffect(() => { if (rowVirtualizer.getVirtualItems().length >= rows.length && tableContainerRef.current) { const target = tableContainerRef.current; // Right after the last row is rendered, tableContainer's scrollHeight is // temporarily larger than the actual height of the table, so we need to // wait for a short time before calling onScroll to ensure the scrollHeight // is correct. setTimeout(() => { onScroll?.({ target }); }, 100); } }, [rowVirtualizer.getVirtualItems().length, rows.length, tableContainerRef.current]); const virtualRows = rowVirtualizer.getVirtualItems(); const columnSizeVars = useMemo(() => { const headers = table.getFlatHeaders(); const colSizes: { [key: string]: string } = {}; for (let i = 0; i < headers.length; i++) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const header = headers[i]!; const isAutoWidth = header.column.columnDef.meta?.autoWidth; colSizes[`--header-${kebabCase(header.id)}-size`] = isAutoWidth ? "auto" : `${header.getSize()}px`; colSizes[`--col-${kebabCase(header.column.id)}-size`] = isAutoWidth ? "auto" : `${header.column.getSize()}px`; } return colSizes; }, [table.getFlatHeaders(), table.getState().columnSizingInfo, table.getState().columnSizing]); return (
{table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { const meta = header.column.columnDef.meta; return (
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} {header.column.getIsSorted() && ( )}
); })}
))}
{virtualRows && !isPending ? ( virtualRows.map((virtualRow) => { const row = rows[virtualRow.index] as Row; return ( rowVirtualizer.measureElement(node)} //measure dynamic row height key={row.id} data-index={virtualRow.index} //needed for dynamic row height measurement data-state={row.getIsSelected() && "selected"} onClick={() => onRowMouseclick && onRowMouseclick(row)} style={{ display: "flex", position: "absolute", transform: `translateY(${virtualRow.start}px)`, //this should always be a `style` as it changes on scroll width: "100%", }} className={classNames( onRowMouseclick && "hover:cursor-pointer", variant === "compact" && "!border-0", "group" )}> {row.getVisibleCells().map((cell) => { const column = table.getColumn(cell.column.id); const meta = column?.columnDef.meta; return ( {flexRender(cell.column.columnDef.cell, cell.getContext())} ); })} ); }) ) : ( No results. )}
{children}
); }