diff --git a/packages/features/data-table/components/DataTable.tsx b/packages/features/data-table/components/DataTable.tsx index fd9dda8a68..07f27a03c8 100644 --- a/packages/features/data-table/components/DataTable.tsx +++ b/packages/features/data-table/components/DataTable.tsx @@ -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({ }: DataTableProps & 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({ 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 (
({
- + {table.getHeaderGroups().map((headerGroup) => ( - + {headerGroup.headers.map((header) => { const meta = header.column.columnDef.meta; return ( @@ -87,8 +92,9 @@ export function DataTable({ 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" )}>
{header.isPlaceholder @@ -112,20 +118,23 @@ export function DataTable({ ))} - - {paddingTop > 0 && ( - - - - )} + {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", @@ -143,6 +152,7 @@ export function DataTable({ 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({ )} - {paddingBottom > 0 && ( - - - - )}
diff --git a/packages/features/package.json b/packages/features/package.json index 70b311d0f2..199222d951 100644 --- a/packages/features/package.json +++ b/packages/features/package.json @@ -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" diff --git a/packages/features/tsconfig.json b/packages/features/tsconfig.json index 3c686980ac..2943b44c34 100644 --- a/packages/features/tsconfig.json +++ b/packages/features/tsconfig.json @@ -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__/*"] } diff --git a/packages/features/users/components/UserTable/UserListTable.tsx b/packages/features/users/components/UserTable/UserListTable.tsx index c84eb5a231..e02ee4d4dc 100644 --- a/packages/features/users/components/UserTable/UserListTable.tsx +++ b/packages/features/users/components/UserTable/UserListTable.tsx @@ -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 ( - <> +
{attributeValues.map((attributeValue, index) => ( {attributeValue.value} ))} - +
); }, 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 )} + {teams.map((team) => (
+
@@ -567,7 +574,6 @@ export function UserListTable() { )} - {state.deleteMember.showModal && } {state.inviteMember.showModal && } {state.impersonateMember.showModal && } diff --git a/packages/features/users/components/UserTable/UserTableActions.tsx b/packages/features/users/components/UserTable/UserTableActions.tsx index 4056172d44..40ee9d9a58 100644 --- a/packages/features/users/components/UserTable/UserTableActions.tsx +++ b/packages/features/users/components/UserTable/UserTableActions.tsx @@ -53,7 +53,7 @@ export function TableActions({ const orgId = session?.user?.org?.id; return ( - <> +
- +
); } diff --git a/packages/ui/package.json b/packages/ui/package.json index 04f6575c72..065517be7d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -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": "*", diff --git a/yarn.lock b/yarn.lock index 7e030d61b6..833b9d2310 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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: