fix: data table fixes (#17141)

* tidy up css

* sticky header

* add sticky top and left on columns

* use group for cell hover when overflowing

* strickly type meta as override isnt working

* fix: type err

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
This commit is contained in:
sean-brydon
2024-10-18 12:41:21 +00:00
committed by GitHub
co-authored by Udit Takkar Udit Takkar
parent 55f1019908
commit 73c35ba5a2
4 changed files with 122 additions and 82 deletions
@@ -9,6 +9,7 @@ import {
useReactTable,
type ColumnDef,
} from "@tanstack/react-table";
import type { ColumnMeta } from "@tanstack/react-table";
import { useSession } from "next-auth/react";
import { useQueryState, parseAsBoolean } from "nuqs";
import { useMemo, useReducer, useRef, useState } from "react";
@@ -45,6 +46,11 @@ import { TableActions } from "./UserTableActions";
import type { UserTableState, UserTableAction, UserTableUser } from "./types";
import { useFetchMoreOnBottomReached } from "./useFetchMoreOnBottomReached";
type CustomColumnMeta<TData, TValue> = ColumnMeta<TData, TValue> & {
sticky?: boolean;
stickLeft?: number;
};
const initialState: UserTableState = {
changeMemberRole: {
showModal: false,
@@ -186,6 +192,10 @@ export function UserListTable() {
id: "select",
enableHiding: false,
enableSorting: false,
meta: {
sticky: true,
stickLeft: 0,
} as CustomColumnMeta<UserTableUser, unknown>,
header: ({ table }) => (
<Checkbox
checked={table.getIsAllPageRowsSelected()}
@@ -210,6 +220,10 @@ export function UserListTable() {
header: () => {
return `Members`;
},
meta: {
sticky: true,
stickyLeft: 24,
} as CustomColumnMeta<UserTableUser, unknown>,
cell: ({ row }) => {
const { username, email, avatarUrl } = row.original;
return (
+95 -75
View File
@@ -59,82 +59,102 @@ export function DataTable<TData, TValue>({
...rest.style,
}}
data-testid={rest["data-testid"] ?? "data-table"}>
<div className="overflow-hidden" style={{ gridArea: "body" }}>
<div ref={tableContainerRef} onScroll={onScroll} className="scrollbar-thin h-full overflow-y-auto">
<div className="inline-block min-w-full align-middle">
<Table>
<TableHeader className="sticky top-0 z-10">
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead
key={header.id}
className={classNames(
header.column.getCanSort() ? "cursor-pointer select-none" : ""
)}>
<div className="flex items-center" onClick={header.column.getToggleSortingHandler()}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
{header.column.getIsSorted() && (
<Icon
name="arrow-up"
className="ml-2 h-4 w-4"
style={{
transform:
header.column.getIsSorted() === "asc" ? "rotate(0deg)" : "rotate(180deg)",
transition: "transform 0.2s ease-in-out",
}}
/>
)}
</div>
</TableHead>
))}
<div
ref={tableContainerRef}
onScroll={onScroll}
className="scrollbar-thin border-subtle relative h-full overflow-auto rounded-md border"
style={{ gridArea: "body" }}>
<Table className="border-0">
<TableHeader className="bg-subtle sticky top-0 z-10">
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
const meta = header.column.columnDef.meta as { sticky?: boolean; stickyLeft?: number };
return (
<TableHead
key={header.id}
style={{
left: meta?.stickyLeft ? `${meta.stickyLeft}px` : undefined,
}}
className={classNames(
header.column.getCanSort() ? "cursor-pointer select-none" : "",
meta?.sticky && "bg-subtle sticky left-0 top-0 z-20"
)}>
<div className="flex items-center" onClick={header.column.getToggleSortingHandler()}>
{header.isPlaceholder
? null
: flexRender(header.column.columnDef.header, header.getContext())}
{header.column.getIsSorted() && (
<Icon
name="arrow-up"
className="ml-2 h-4 w-4"
style={{
transform:
header.column.getIsSorted() === "asc" ? "rotate(0deg)" : "rotate(180deg)",
transition: "transform 0.2s ease-in-out",
}}
/>
)}
</div>
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{paddingTop > 0 && (
<tr>
<td style={{ height: `${paddingTop}px` }} />
</tr>
)}
{virtualRows && !isPending ? (
virtualRows.map((virtualRow) => {
const row = rows[virtualRow.index] as Row<TData>;
return (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
onClick={() => onRowMouseclick && onRowMouseclick(row)}
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 as { sticky?: boolean; stickyLeft?: number };
return (
<TableCell
key={cell.id}
style={{
left: meta?.stickyLeft ? `${meta.stickyLeft}px` : undefined,
}}
className={classNames(
variant === "compact" && "p-1.5",
meta?.sticky && "group-hover:bg-muted bg-default sticky left-0"
)}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{paddingTop > 0 && (
<tr>
<td style={{ height: `${paddingTop}px` }} />
</tr>
)}
{virtualRows && !isPending ? (
virtualRows.map((virtualRow) => {
const row = rows[virtualRow.index] as Row<TData>;
return (
<TableRow
key={row.id}
data-state={row.getIsSelected() && "selected"}
onClick={() => onRowMouseclick && onRowMouseclick(row)}
className={classNames(
onRowMouseclick && "hover:cursor-pointer",
variant === "compact" && "!border-0"
)}>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id} className={classNames(variant === "compact" && "p-1.5")}>
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
);
})
) : (
<TableRow>
<TableCell colSpan={table.getAllColumns().length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
{paddingBottom > 0 && (
<tr>
<td style={{ height: `${paddingBottom}px` }} />
</tr>
)}
</TableBody>
</Table>
</div>
</div>
);
})
) : (
<TableRow>
<TableCell colSpan={table.getAllColumns().length} className="h-24 text-center">
No results.
</TableCell>
</TableRow>
)}
{paddingBottom > 0 && (
<tr>
<td style={{ height: `${paddingBottom}px` }} />
</tr>
)}
</TableBody>
</Table>
</div>
{children}
</div>
+8
View File
@@ -0,0 +1,8 @@
import "@tanstack/react-table";
declare module "@tanstack/react-table" {
interface ColumnMeta<TData extends RowData, TValue> {
sticky?: boolean;
stickyLeft?: number;
}
}
+5 -7
View File
@@ -4,13 +4,11 @@ import { classNames } from "@calcom/lib";
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
({ className, ...props }, ref) => (
<div className="w-full overflow-y-auto">
<table
ref={ref}
className={classNames("border-subtle w-full caption-bottom border text-sm", className)}
{...props}
/>
</div>
<table
ref={ref}
className={classNames("border-subtle w-full caption-bottom border text-sm", className)}
{...props}
/>
)
);
Table.displayName = "Table";