diff --git a/packages/features/data-table/components/DataTable.tsx b/packages/features/data-table/components/DataTable.tsx index c8a218ba560430..2bacfa76ef3119 100644 --- a/packages/features/data-table/components/DataTable.tsx +++ b/packages/features/data-table/components/DataTable.tsx @@ -10,10 +10,33 @@ import { usePathname } from "next/navigation"; import { useEffect, memo } from "react"; import classNames from "@calcom/lib/classNames"; -import { Icon, TableNew, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@calcom/ui"; +import { Icon } from "@calcom/ui"; import { useColumnSizingVars } from "../hooks"; import { usePersistentColumnResizing } from "../lib/resizing"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "./Table"; + +const getPinningStyles = (column: Column, isHeader: boolean): CSSProperties => { + const isPinned = column.getIsPinned(); + let zIndex = 0; + if (isHeader && isPinned) { + zIndex = 20; + } else if (isHeader && !isPinned) { + zIndex = 10; + } else if (!isHeader && isPinned) { + zIndex = 1; + } else { + zIndex = 0; + } + + return { + left: isPinned === "left" ? `${column.getStart("left")}px` : undefined, + right: isPinned === "right" ? `${column.getAfter("right")}px` : undefined, + position: isPinned ? "sticky" : "relative", + width: column.getSize(), + zIndex, + }; +}; export type DataTableProps = { table: ReactTableType; @@ -103,64 +126,106 @@ export function DataTable({ ref={tableContainerRef} onScroll={onScroll} className={classNames( - "relative h-[80dvh] overflow-auto", // Set a fixed height for the container - "scrollbar-thin border-subtle relative rounded-md border", + "relative w-full", + "scrollbar-thin h-[80dvh] overflow-auto", // Set a fixed height for the container + "bg-background border-subtle rounded-lg border", containerClassName )} style={{ gridArea: "body" }}> - {!hideHeader && ( {table.getHeaderGroups().map((headerGroup) => ( - + {headerGroup.headers.map((header) => { const meta = header.column.columnDef.meta; + const { column } = header; + const isPinned = column.getIsPinned(); + const isLastLeftPinned = isPinned === "left" && column.getIsLastColumn("left"); + const isFirstRightPinned = isPinned === "right" && column.getIsFirstColumn("right"); + return ( -
- {header.isPlaceholder - ? null - : flexRender(header.column.columnDef.header, header.getContext())} - {header.column.getIsSorted() && ( - +
+ {header.isPlaceholder ? null : ( +
{ + // Enhanced keyboard handling for sorting + if (header.column.getCanSort() && (e.key === "Enter" || e.key === " ")) { + e.preventDefault(); + header.column.getToggleSortingHandler()?.(e); + } }} - /> + tabIndex={header.column.getCanSort() ? 0 : undefined}> + + {flexRender(header.column.columnDef.header, header.getContext())} + + {{ + asc: ( +
+ )} + {Boolean(enableColumnResizing) && header.column.getCanResize() && ( +
+
+
)}
{Boolean(enableColumnResizing) && header.column.getCanResize() && (
)} @@ -192,7 +257,7 @@ export function DataTable({ onRowMouseclick={onRowMouseclick} /> )} - +
{children}
@@ -232,10 +297,7 @@ function DataTableBody({ }: DataTableBodyProps) { const virtualRows = rowVirtualizer.getVirtualItems(); return ( - + {virtualRows && !isPending ? ( virtualRows.map((virtualRow) => { const row = rows[virtualRow.index] as Row; @@ -245,31 +307,34 @@ function DataTableBody({ 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", "group")}> + className={classNames( + onRowMouseclick && "hover:cursor-pointer", + "has-[[data-state=selected]]:bg-muted/50 group" + )} + onClick={() => onRowMouseclick && onRowMouseclick(row)}> {row.getVisibleCells().map((cell) => { const column = table.getColumn(cell.column.id); const meta = column?.columnDef.meta; + const isPinned = column.getIsPinned(); + const isLastLeftPinned = isPinned === "left" && column.getIsLastColumn("left"); + const isFirstRightPinned = isPinned === "right" && column.getIsFirstColumn("right"); + return ( + }}> {flexRender(cell.column.columnDef.cell, cell.getContext())} ); diff --git a/packages/features/data-table/components/Table.tsx b/packages/features/data-table/components/Table.tsx new file mode 100644 index 00000000000000..c753592fa02d0c --- /dev/null +++ b/packages/features/data-table/components/Table.tsx @@ -0,0 +1,84 @@ +import * as React from "react"; + +import { classNames as cn } from "@calcom/lib"; + +const Table = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ) +); +Table.displayName = "Table"; + +const TableHeader = React.forwardRef>( + ({ className, ...props }, ref) => +); +TableHeader.displayName = "TableHeader"; + +const TableBody = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ) +); +TableBody.displayName = "TableBody"; + +const TableFooter = React.forwardRef>( + ({ className, ...props }, ref) => ( + tr]:last:border-b-0", className)} + {...props} + /> + ) +); +TableFooter.displayName = "TableFooter"; + +const TableRow = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ) +); +TableRow.displayName = "TableRow"; + +const TableHead = React.forwardRef>( + ({ className, ...props }, ref) => ( +
[role=checkbox]]:translate-y-0.5", + className + )} + {...props} + /> + ) +); +TableHead.displayName = "TableHead"; + +const TableCell = React.forwardRef>( + ({ className, ...props }, ref) => ( + [role=checkbox]]:translate-y-0.5", + className + )} + {...props} + /> + ) +); +TableCell.displayName = "TableCell"; + +const TableCaption = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +TableCaption.displayName = "TableCaption"; + +export { Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow }; diff --git a/packages/features/package.json b/packages/features/package.json index 199222d951f3e5..d8258072de96dc 100644 --- a/packages/features/package.json +++ b/packages/features/package.json @@ -12,7 +12,7 @@ "@calcom/trpc": "*", "@calcom/ui": "*", "@lexical/react": "^0.9.0", - "@tanstack/react-table": "^8.9.3", + "@tanstack/react-table": "^8.20.6", "@tanstack/react-virtual": "^3.10.9", "@vercel/functions": "^1.4.0", "framer-motion": "^10.12.8", diff --git a/packages/features/users/components/UserTable/UserListTable.tsx b/packages/features/users/components/UserTable/UserListTable.tsx index b35c407223ac7a..5b26ac42386385 100644 --- a/packages/features/users/components/UserTable/UserListTable.tsx +++ b/packages/features/users/components/UserTable/UserListTable.tsx @@ -265,11 +265,6 @@ function UserListTableContent() { enableSorting: false, enableResizing: false, size: 30, - meta: { - sticky: { - position: "left", - }, - }, header: ({ table }) => ( { return `Members`; }, - meta: { - sticky: { position: "left", gap: 24 }, - }, cell: ({ row }) => { const { username, email, avatarUrl } = row.original; return ( @@ -411,9 +403,6 @@ function UserListTableContent() { enableSorting: false, enableResizing: false, size: 80, - meta: { - sticky: { position: "right" }, - }, cell: ({ row }) => { const user = row.original; const permissionsRaw = permissions; @@ -452,6 +441,10 @@ function UserListTableContent() { manualPagination: true, initialState: { columnVisibility: initalColumnVisibility, + columnPinning: { + left: ["select", "member"], + right: ["actions"], + }, }, defaultColumn: { size: 150, diff --git a/packages/ui/package.json b/packages/ui/package.json index c9b6be02c06122..e86c53a59bc742 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -78,7 +78,7 @@ "@storybook/blocks": "^7.6.3", "@storybook/react": "^7.6.3", "@tanstack/react-query": "^5.17.15", - "@tanstack/react-table": "^8.9.3", + "@tanstack/react-table": "^8.20.6", "@wojtekmaj/react-daterange-picker": "^3.3.1", "class-variance-authority": "^0.4.0", "cmdk": "^0.2.0",