fix: provide fixed widths for DataTable columns (#17853)
Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
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";
|
||||
import { useMemo } from "react";
|
||||
import { useVirtual } from "react-virtual";
|
||||
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { Icon, TableNew, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@calcom/ui";
|
||||
@@ -32,15 +32,20 @@ export function DataTable<TData, TValue>({
|
||||
}: DataTableProps<TData, TValue> & React.ComponentPropsWithoutRef<"div">) {
|
||||
const { rows } = table.getRowModel();
|
||||
|
||||
const rowVirtualizer = useVirtual({
|
||||
parentRef: tableContainerRef,
|
||||
size: rows.length,
|
||||
// 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,
|
||||
});
|
||||
const { virtualItems: virtualRows, totalSize } = rowVirtualizer;
|
||||
const paddingTop = virtualRows.length > 0 ? virtualRows?.[0]?.start || 0 : 0;
|
||||
const paddingBottom =
|
||||
virtualRows.length > 0 ? totalSize - (virtualRows?.[virtualRows.length - 1]?.end || 0) : 0;
|
||||
|
||||
const virtualRows = rowVirtualizer.getVirtualItems();
|
||||
|
||||
const columnSizeVars = useMemo(() => {
|
||||
const headers = table.getFlatHeaders();
|
||||
@@ -52,14 +57,11 @@ export function DataTable<TData, TValue>({
|
||||
colSizes[`--col-${header.column.id}-size`] = header.column.getSize();
|
||||
}
|
||||
return colSizes;
|
||||
}, [table.getState().columnSizingInfo, table.getState().columnSizing]);
|
||||
}, [table.getFlatHeaders(), table.getState().columnSizingInfo, table.getState().columnSizing]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"grid h-[85dvh]", // Set a fixed height for the container
|
||||
rest.className
|
||||
)}
|
||||
className={classNames("grid", rest.className)}
|
||||
style={{
|
||||
gridTemplateRows: "auto 1fr auto",
|
||||
gridTemplateAreas: "'header' 'body' 'footer'",
|
||||
@@ -70,12 +72,15 @@ export function DataTable<TData, TValue>({
|
||||
<div
|
||||
ref={tableContainerRef}
|
||||
onScroll={onScroll}
|
||||
className="scrollbar-thin border-subtle relative h-full overflow-auto rounded-md border"
|
||||
className={classNames(
|
||||
"relative h-[80dvh] overflow-auto", // Set a fixed height for the container
|
||||
"scrollbar-thin border-subtle relative rounded-md border"
|
||||
)}
|
||||
style={{ gridArea: "body" }}>
|
||||
<TableNew className="border-0">
|
||||
<TableNew className="grid border-0">
|
||||
<TableHeader className="bg-subtle sticky top-0 z-10">
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<TableRow key={headerGroup.id}>
|
||||
<TableRow key={headerGroup.id} className="flex w-full">
|
||||
{headerGroup.headers.map((header) => {
|
||||
const meta = header.column.columnDef.meta;
|
||||
return (
|
||||
@@ -87,8 +92,9 @@ export function DataTable<TData, TValue>({
|
||||
width: `calc(var(--header-${header?.id}-size) * 1px)`,
|
||||
}}
|
||||
className={classNames(
|
||||
"flex shrink-0 items-center",
|
||||
header.column.getCanSort() ? "cursor-pointer select-none" : "",
|
||||
meta?.sticky && "bg-subtle sticky top-0 z-20"
|
||||
meta?.sticky && "sticky top-0 z-20"
|
||||
)}>
|
||||
<div className="flex items-center" onClick={header.column.getToggleSortingHandler()}>
|
||||
{header.isPlaceholder
|
||||
@@ -112,20 +118,23 @@ export function DataTable<TData, TValue>({
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{paddingTop > 0 && (
|
||||
<tr>
|
||||
<td style={{ height: `${paddingTop}px` }} />
|
||||
</tr>
|
||||
)}
|
||||
<TableBody className="relative grid" style={{ height: `${rowVirtualizer.getTotalSize()}px` }}>
|
||||
{virtualRows && !isPending ? (
|
||||
virtualRows.map((virtualRow) => {
|
||||
const row = rows[virtualRow.index] as Row<TData>;
|
||||
return (
|
||||
<TableRow
|
||||
ref={(node) => 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",
|
||||
@@ -143,6 +152,7 @@ export function DataTable<TData, TValue>({
|
||||
width: `calc(var(--col-${cell.column.id}-size) * 1px)`,
|
||||
}}
|
||||
className={classNames(
|
||||
"flex shrink-0 items-center overflow-auto",
|
||||
variant === "compact" && "p-1.5",
|
||||
meta?.sticky && "group-hover:bg-muted bg-default sticky"
|
||||
)}>
|
||||
@@ -160,11 +170,6 @@ export function DataTable<TData, TValue>({
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
{paddingBottom > 0 && (
|
||||
<tr>
|
||||
<td style={{ height: `${paddingBottom}px` }} />
|
||||
</tr>
|
||||
)}
|
||||
</TableBody>
|
||||
</TableNew>
|
||||
</div>
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
"@calcom/ui": "*",
|
||||
"@lexical/react": "^0.9.0",
|
||||
"@tanstack/react-table": "^8.9.3",
|
||||
"@tanstack/react-virtual": "^3.10.9",
|
||||
"@vercel/functions": "^1.4.0",
|
||||
"framer-motion": "^10.12.8",
|
||||
"lexical": "^0.9.0",
|
||||
"react-select": "^5.7.0",
|
||||
"react-sticky-box": "^2.0.4",
|
||||
"react-virtual": "^2.10.4",
|
||||
"stripe-event-types": "^3.1.0",
|
||||
"web-push": "^3.6.7",
|
||||
"zustand": "^4.3.2"
|
||||
|
||||
@@ -8,6 +8,6 @@
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": [".", "../types/next-auth.d.ts"],
|
||||
"include": [".", "../types/next-auth.d.ts", "../types/tanstack-table.d.ts"],
|
||||
"exclude": ["dist", "build", "node_modules", "**/*.test.*", "**/__mocks__/*", "**/__tests__/*"]
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
isTextFilterValue,
|
||||
} from "@calcom/features/data-table";
|
||||
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import {
|
||||
downloadAsCsv,
|
||||
@@ -125,7 +126,7 @@ export function UserListTable() {
|
||||
const { data, isPending, fetchNextPage, isFetching } =
|
||||
trpc.viewer.organizations.listMembers.useInfiniteQuery(
|
||||
{
|
||||
limit: 10,
|
||||
limit: 30,
|
||||
searchTerm: debouncedSearchTerm,
|
||||
expand: ["attributes"],
|
||||
filters: columnFilters,
|
||||
@@ -182,13 +183,13 @@ export function UserListTable() {
|
||||
);
|
||||
if (attributeValues.length === 0) return null;
|
||||
return (
|
||||
<>
|
||||
<div className={classNames(attribute.type === "NUMBER" ? "flex w-full justify-center" : "")}>
|
||||
{attributeValues.map((attributeValue, index) => (
|
||||
<Badge key={index} variant="gray" className="mr-1">
|
||||
{attributeValue.value}
|
||||
</Badge>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
filterFn: (row, id, filterValue) => {
|
||||
@@ -237,7 +238,7 @@ export function UserListTable() {
|
||||
id: "member",
|
||||
accessorFn: (data) => data.email,
|
||||
enableHiding: false,
|
||||
size: 170,
|
||||
size: 200,
|
||||
header: () => {
|
||||
return `Members`;
|
||||
},
|
||||
@@ -306,6 +307,7 @@ export function UserListTable() {
|
||||
id: "teams",
|
||||
accessorFn: (data) => data.teams.map((team) => team.name),
|
||||
header: "Teams",
|
||||
size: 200,
|
||||
cell: ({ row, table }) => {
|
||||
const { teams, accepted, email, username } = row.original;
|
||||
// TODO: Implement click to filter
|
||||
@@ -323,6 +325,7 @@ export function UserListTable() {
|
||||
Pending
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{teams.map((team) => (
|
||||
<Badge
|
||||
key={team.id}
|
||||
@@ -350,7 +353,7 @@ export function UserListTable() {
|
||||
{
|
||||
id: "actions",
|
||||
enableHiding: false,
|
||||
size: 50,
|
||||
size: 80,
|
||||
meta: {
|
||||
sticky: { position: "right" },
|
||||
},
|
||||
@@ -392,6 +395,9 @@ export function UserListTable() {
|
||||
initialState: {
|
||||
columnVisibility: initalColumnVisibility,
|
||||
},
|
||||
defaultColumn: {
|
||||
size: 150,
|
||||
},
|
||||
state: {
|
||||
columnFilters,
|
||||
rowSelection,
|
||||
@@ -534,6 +540,7 @@ export function UserListTable() {
|
||||
<DataTableFilters.ActiveFilters table={table} />
|
||||
</div>
|
||||
</DataTableToolbar.Root>
|
||||
|
||||
<div style={{ gridArea: "footer", marginTop: "1rem" }}>
|
||||
<DataTablePagination table={table} totalDbDataCount={totalDBRowCount} />
|
||||
</div>
|
||||
@@ -567,7 +574,6 @@ export function UserListTable() {
|
||||
</DataTableSelectionBar.Root>
|
||||
)}
|
||||
</DataTable>
|
||||
|
||||
{state.deleteMember.showModal && <DeleteMemberModal state={state} dispatch={dispatch} />}
|
||||
{state.inviteMember.showModal && <InviteMemberModal dispatch={dispatch} />}
|
||||
{state.impersonateMember.showModal && <ImpersonationMemberModal dispatch={dispatch} state={state} />}
|
||||
|
||||
@@ -53,7 +53,7 @@ export function TableActions({
|
||||
const orgId = session?.user?.org?.id;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex w-full justify-center">
|
||||
<ButtonGroup combined containerProps={{ className: "border-default hidden md:flex" }}>
|
||||
<Tooltip content={t("view_public_page")}>
|
||||
<Button
|
||||
@@ -212,6 +212,6 @@ export function TableActions({
|
||||
</DropdownMenuPortal>
|
||||
</Dropdown>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -91,8 +91,7 @@
|
||||
"react-day-picker": "^8.10.1",
|
||||
"react-hook-form": "^7.43.3",
|
||||
"react-inlinesvg": "^4.1.3",
|
||||
"react-select": "^5.7.0",
|
||||
"react-virtual": "^2.10.4"
|
||||
"react-select": "^5.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/config": "*",
|
||||
|
||||
@@ -4147,6 +4147,7 @@ __metadata:
|
||||
"@calcom/ui": "*"
|
||||
"@lexical/react": ^0.9.0
|
||||
"@tanstack/react-table": ^8.9.3
|
||||
"@tanstack/react-virtual": ^3.10.9
|
||||
"@testing-library/react-hooks": ^8.0.1
|
||||
"@types/web-push": ^3.6.3
|
||||
"@vercel/functions": ^1.4.0
|
||||
@@ -4154,7 +4155,6 @@ __metadata:
|
||||
lexical: ^0.9.0
|
||||
react-select: ^5.7.0
|
||||
react-sticky-box: ^2.0.4
|
||||
react-virtual: ^2.10.4
|
||||
stripe-event-types: ^3.1.0
|
||||
web-push: ^3.6.7
|
||||
zustand: ^4.3.2
|
||||
@@ -4946,7 +4946,6 @@ __metadata:
|
||||
react-hook-form: ^7.43.3
|
||||
react-inlinesvg: ^4.1.3
|
||||
react-select: ^5.7.0
|
||||
react-virtual: ^2.10.4
|
||||
typescript: ^4.9.4
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
@@ -15285,6 +15284,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tanstack/react-virtual@npm:^3.10.9":
|
||||
version: 3.10.9
|
||||
resolution: "@tanstack/react-virtual@npm:3.10.9"
|
||||
dependencies:
|
||||
"@tanstack/virtual-core": 3.10.9
|
||||
peerDependencies:
|
||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||
checksum: a6c90118d0b084aedf0a2b02bc718df5cc1e594fb60d1dededf8a393d3e519e574e2ba67bb7adcaf8b4d6b206f6a10b37166f006bc7e50ad566475323d545b8c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tanstack/table-core@npm:8.9.3":
|
||||
version: 8.9.3
|
||||
resolution: "@tanstack/table-core@npm:8.9.3"
|
||||
@@ -15292,6 +15303,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tanstack/virtual-core@npm:3.10.9":
|
||||
version: 3.10.9
|
||||
resolution: "@tanstack/virtual-core@npm:3.10.9"
|
||||
checksum: df1c673040e3700ba12774ef1fec775f84342e80fb5f1586096a1ed347ee9d35b6db6829e665fed86fa3f08e86235a68bbd331fd5aedec4314c2a565384199ba
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@tediousjs/connection-string@npm:^0.5.0":
|
||||
version: 0.5.0
|
||||
resolution: "@tediousjs/connection-string@npm:0.5.0"
|
||||
@@ -38100,7 +38118,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-virtual@npm:^2.10.4, react-virtual@npm:^2.8.2":
|
||||
"react-virtual@npm:^2.8.2":
|
||||
version: 2.10.4
|
||||
resolution: "react-virtual@npm:2.10.4"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user