Skip to content

Commit e22fec9

Browse files
eunjae-leezomars
andauthored
fix: provide fixed widths for DataTable columns (#17853)
Co-authored-by: Omar López <zomars@me.com>
1 parent f81716f commit e22fec9

File tree

7 files changed

+71
-43
lines changed

7 files changed

+71
-43
lines changed

packages/features/data-table/components/DataTable.tsx

+33-28
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import type { Row } from "@tanstack/react-table";
44
import { flexRender } from "@tanstack/react-table";
55
import type { Table as ReactTableType } from "@tanstack/react-table";
6+
import { useVirtualizer } from "@tanstack/react-virtual";
67
import { useMemo } from "react";
7-
import { useVirtual } from "react-virtual";
88

99
import classNames from "@calcom/lib/classNames";
1010
import { Icon, TableNew, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@calcom/ui";
@@ -32,15 +32,20 @@ export function DataTable<TData, TValue>({
3232
}: DataTableProps<TData, TValue> & React.ComponentPropsWithoutRef<"div">) {
3333
const { rows } = table.getRowModel();
3434

35-
const rowVirtualizer = useVirtual({
36-
parentRef: tableContainerRef,
37-
size: rows.length,
35+
// https://stackblitz.com/github/tanstack/table/tree/main/examples/react/virtualized-infinite-scrolling
36+
const rowVirtualizer = useVirtualizer({
37+
count: rows.length,
38+
estimateSize: () => 100,
39+
getScrollElement: () => tableContainerRef.current,
40+
// measure dynamic row height, except in firefox because it measures table border height incorrectly
41+
measureElement:
42+
typeof window !== "undefined" && navigator.userAgent.indexOf("Firefox") === -1
43+
? (element) => element?.getBoundingClientRect().height
44+
: undefined,
3845
overscan: 10,
3946
});
40-
const { virtualItems: virtualRows, totalSize } = rowVirtualizer;
41-
const paddingTop = virtualRows.length > 0 ? virtualRows?.[0]?.start || 0 : 0;
42-
const paddingBottom =
43-
virtualRows.length > 0 ? totalSize - (virtualRows?.[virtualRows.length - 1]?.end || 0) : 0;
47+
48+
const virtualRows = rowVirtualizer.getVirtualItems();
4449

4550
const columnSizeVars = useMemo(() => {
4651
const headers = table.getFlatHeaders();
@@ -52,14 +57,11 @@ export function DataTable<TData, TValue>({
5257
colSizes[`--col-${header.column.id}-size`] = header.column.getSize();
5358
}
5459
return colSizes;
55-
}, [table.getState().columnSizingInfo, table.getState().columnSizing]);
60+
}, [table.getFlatHeaders(), table.getState().columnSizingInfo, table.getState().columnSizing]);
5661

5762
return (
5863
<div
59-
className={classNames(
60-
"grid h-[85dvh]", // Set a fixed height for the container
61-
rest.className
62-
)}
64+
className={classNames("grid", rest.className)}
6365
style={{
6466
gridTemplateRows: "auto 1fr auto",
6567
gridTemplateAreas: "'header' 'body' 'footer'",
@@ -70,12 +72,15 @@ export function DataTable<TData, TValue>({
7072
<div
7173
ref={tableContainerRef}
7274
onScroll={onScroll}
73-
className="scrollbar-thin border-subtle relative h-full overflow-auto rounded-md border"
75+
className={classNames(
76+
"relative h-[80dvh] overflow-auto", // Set a fixed height for the container
77+
"scrollbar-thin border-subtle relative rounded-md border"
78+
)}
7479
style={{ gridArea: "body" }}>
75-
<TableNew className="border-0">
80+
<TableNew className="grid border-0">
7681
<TableHeader className="bg-subtle sticky top-0 z-10">
7782
{table.getHeaderGroups().map((headerGroup) => (
78-
<TableRow key={headerGroup.id}>
83+
<TableRow key={headerGroup.id} className="flex w-full">
7984
{headerGroup.headers.map((header) => {
8085
const meta = header.column.columnDef.meta;
8186
return (
@@ -87,8 +92,9 @@ export function DataTable<TData, TValue>({
8792
width: `calc(var(--header-${header?.id}-size) * 1px)`,
8893
}}
8994
className={classNames(
95+
"flex shrink-0 items-center",
9096
header.column.getCanSort() ? "cursor-pointer select-none" : "",
91-
meta?.sticky && "bg-subtle sticky top-0 z-20"
97+
meta?.sticky && "sticky top-0 z-20"
9298
)}>
9399
<div className="flex items-center" onClick={header.column.getToggleSortingHandler()}>
94100
{header.isPlaceholder
@@ -112,20 +118,23 @@ export function DataTable<TData, TValue>({
112118
</TableRow>
113119
))}
114120
</TableHeader>
115-
<TableBody>
116-
{paddingTop > 0 && (
117-
<tr>
118-
<td style={{ height: `${paddingTop}px` }} />
119-
</tr>
120-
)}
121+
<TableBody className="relative grid" style={{ height: `${rowVirtualizer.getTotalSize()}px` }}>
121122
{virtualRows && !isPending ? (
122123
virtualRows.map((virtualRow) => {
123124
const row = rows[virtualRow.index] as Row<TData>;
124125
return (
125126
<TableRow
127+
ref={(node) => rowVirtualizer.measureElement(node)} //measure dynamic row height
126128
key={row.id}
129+
data-index={virtualRow.index} //needed for dynamic row height measurement
127130
data-state={row.getIsSelected() && "selected"}
128131
onClick={() => onRowMouseclick && onRowMouseclick(row)}
132+
style={{
133+
display: "flex",
134+
position: "absolute",
135+
transform: `translateY(${virtualRow.start}px)`, //this should always be a `style` as it changes on scroll
136+
width: "100%",
137+
}}
129138
className={classNames(
130139
onRowMouseclick && "hover:cursor-pointer",
131140
variant === "compact" && "!border-0",
@@ -143,6 +152,7 @@ export function DataTable<TData, TValue>({
143152
width: `calc(var(--col-${cell.column.id}-size) * 1px)`,
144153
}}
145154
className={classNames(
155+
"flex shrink-0 items-center overflow-auto",
146156
variant === "compact" && "p-1.5",
147157
meta?.sticky && "group-hover:bg-muted bg-default sticky"
148158
)}>
@@ -160,11 +170,6 @@ export function DataTable<TData, TValue>({
160170
</TableCell>
161171
</TableRow>
162172
)}
163-
{paddingBottom > 0 && (
164-
<tr>
165-
<td style={{ height: `${paddingBottom}px` }} />
166-
</tr>
167-
)}
168173
</TableBody>
169174
</TableNew>
170175
</div>

packages/features/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@
1313
"@calcom/ui": "*",
1414
"@lexical/react": "^0.9.0",
1515
"@tanstack/react-table": "^8.9.3",
16+
"@tanstack/react-virtual": "^3.10.9",
1617
"@vercel/functions": "^1.4.0",
1718
"framer-motion": "^10.12.8",
1819
"lexical": "^0.9.0",
1920
"react-select": "^5.7.0",
2021
"react-sticky-box": "^2.0.4",
21-
"react-virtual": "^2.10.4",
2222
"stripe-event-types": "^3.1.0",
2323
"web-push": "^3.6.7",
2424
"zustand": "^4.3.2"

packages/features/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@
88
"resolveJsonModule": true,
99
"esModuleInterop": true
1010
},
11-
"include": [".", "../types/next-auth.d.ts"],
11+
"include": [".", "../types/next-auth.d.ts", "../types/tanstack-table.d.ts"],
1212
"exclude": ["dist", "build", "node_modules", "**/*.test.*", "**/__mocks__/*", "**/__tests__/*"]
1313
}

packages/features/users/components/UserTable/UserListTable.tsx

+12-6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
isTextFilterValue,
2525
} from "@calcom/features/data-table";
2626
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
27+
import classNames from "@calcom/lib/classNames";
2728
import { WEBAPP_URL } from "@calcom/lib/constants";
2829
import {
2930
downloadAsCsv,
@@ -125,7 +126,7 @@ export function UserListTable() {
125126
const { data, isPending, fetchNextPage, isFetching } =
126127
trpc.viewer.organizations.listMembers.useInfiniteQuery(
127128
{
128-
limit: 10,
129+
limit: 30,
129130
searchTerm: debouncedSearchTerm,
130131
expand: ["attributes"],
131132
filters: columnFilters,
@@ -182,13 +183,13 @@ export function UserListTable() {
182183
);
183184
if (attributeValues.length === 0) return null;
184185
return (
185-
<>
186+
<div className={classNames(attribute.type === "NUMBER" ? "flex w-full justify-center" : "")}>
186187
{attributeValues.map((attributeValue, index) => (
187188
<Badge key={index} variant="gray" className="mr-1">
188189
{attributeValue.value}
189190
</Badge>
190191
))}
191-
</>
192+
</div>
192193
);
193194
},
194195
filterFn: (row, id, filterValue) => {
@@ -237,7 +238,7 @@ export function UserListTable() {
237238
id: "member",
238239
accessorFn: (data) => data.email,
239240
enableHiding: false,
240-
size: 170,
241+
size: 200,
241242
header: () => {
242243
return `Members`;
243244
},
@@ -306,6 +307,7 @@ export function UserListTable() {
306307
id: "teams",
307308
accessorFn: (data) => data.teams.map((team) => team.name),
308309
header: "Teams",
310+
size: 200,
309311
cell: ({ row, table }) => {
310312
const { teams, accepted, email, username } = row.original;
311313
// TODO: Implement click to filter
@@ -323,6 +325,7 @@ export function UserListTable() {
323325
Pending
324326
</Badge>
325327
)}
328+
326329
{teams.map((team) => (
327330
<Badge
328331
key={team.id}
@@ -350,7 +353,7 @@ export function UserListTable() {
350353
{
351354
id: "actions",
352355
enableHiding: false,
353-
size: 50,
356+
size: 80,
354357
meta: {
355358
sticky: { position: "right" },
356359
},
@@ -392,6 +395,9 @@ export function UserListTable() {
392395
initialState: {
393396
columnVisibility: initalColumnVisibility,
394397
},
398+
defaultColumn: {
399+
size: 150,
400+
},
395401
state: {
396402
columnFilters,
397403
rowSelection,
@@ -534,6 +540,7 @@ export function UserListTable() {
534540
<DataTableFilters.ActiveFilters table={table} />
535541
</div>
536542
</DataTableToolbar.Root>
543+
537544
<div style={{ gridArea: "footer", marginTop: "1rem" }}>
538545
<DataTablePagination table={table} totalDbDataCount={totalDBRowCount} />
539546
</div>
@@ -567,7 +574,6 @@ export function UserListTable() {
567574
</DataTableSelectionBar.Root>
568575
)}
569576
</DataTable>
570-
571577
{state.deleteMember.showModal && <DeleteMemberModal state={state} dispatch={dispatch} />}
572578
{state.inviteMember.showModal && <InviteMemberModal dispatch={dispatch} />}
573579
{state.impersonateMember.showModal && <ImpersonationMemberModal dispatch={dispatch} state={state} />}

packages/features/users/components/UserTable/UserTableActions.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export function TableActions({
5353
const orgId = session?.user?.org?.id;
5454

5555
return (
56-
<>
56+
<div className="flex w-full justify-center">
5757
<ButtonGroup combined containerProps={{ className: "border-default hidden md:flex" }}>
5858
<Tooltip content={t("view_public_page")}>
5959
<Button
@@ -212,6 +212,6 @@ export function TableActions({
212212
</DropdownMenuPortal>
213213
</Dropdown>
214214
</div>
215-
</>
215+
</div>
216216
);
217217
}

packages/ui/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,7 @@
9191
"react-day-picker": "^8.10.1",
9292
"react-hook-form": "^7.43.3",
9393
"react-inlinesvg": "^4.1.3",
94-
"react-select": "^5.7.0",
95-
"react-virtual": "^2.10.4"
94+
"react-select": "^5.7.0"
9695
},
9796
"devDependencies": {
9897
"@calcom/config": "*",

yarn.lock

+21-3
Original file line numberDiff line numberDiff line change
@@ -4147,14 +4147,14 @@ __metadata:
41474147
"@calcom/ui": "*"
41484148
"@lexical/react": ^0.9.0
41494149
"@tanstack/react-table": ^8.9.3
4150+
"@tanstack/react-virtual": ^3.10.9
41504151
"@testing-library/react-hooks": ^8.0.1
41514152
"@types/web-push": ^3.6.3
41524153
"@vercel/functions": ^1.4.0
41534154
framer-motion: ^10.12.8
41544155
lexical: ^0.9.0
41554156
react-select: ^5.7.0
41564157
react-sticky-box: ^2.0.4
4157-
react-virtual: ^2.10.4
41584158
stripe-event-types: ^3.1.0
41594159
web-push: ^3.6.7
41604160
zustand: ^4.3.2
@@ -4946,7 +4946,6 @@ __metadata:
49464946
react-hook-form: ^7.43.3
49474947
react-inlinesvg: ^4.1.3
49484948
react-select: ^5.7.0
4949-
react-virtual: ^2.10.4
49504949
typescript: ^4.9.4
49514950
languageName: unknown
49524951
linkType: soft
@@ -15285,13 +15284,32 @@ __metadata:
1528515284
languageName: node
1528615285
linkType: hard
1528715286

15287+
"@tanstack/react-virtual@npm:^3.10.9":
15288+
version: 3.10.9
15289+
resolution: "@tanstack/react-virtual@npm:3.10.9"
15290+
dependencies:
15291+
"@tanstack/virtual-core": 3.10.9
15292+
peerDependencies:
15293+
react: ^16.8.0 || ^17.0.0 || ^18.0.0
15294+
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
15295+
checksum: a6c90118d0b084aedf0a2b02bc718df5cc1e594fb60d1dededf8a393d3e519e574e2ba67bb7adcaf8b4d6b206f6a10b37166f006bc7e50ad566475323d545b8c
15296+
languageName: node
15297+
linkType: hard
15298+
1528815299
"@tanstack/table-core@npm:8.9.3":
1528915300
version: 8.9.3
1529015301
resolution: "@tanstack/table-core@npm:8.9.3"
1529115302
checksum: 52c7e57daaa3160450fb0bde702ec0b8aab159e2b19efd1012e03b3e167acc52839f5b39578cc8bf96e91346719f400d0435a5191384c168d9477da82f276c38
1529215303
languageName: node
1529315304
linkType: hard
1529415305

15306+
"@tanstack/virtual-core@npm:3.10.9":
15307+
version: 3.10.9
15308+
resolution: "@tanstack/virtual-core@npm:3.10.9"
15309+
checksum: df1c673040e3700ba12774ef1fec775f84342e80fb5f1586096a1ed347ee9d35b6db6829e665fed86fa3f08e86235a68bbd331fd5aedec4314c2a565384199ba
15310+
languageName: node
15311+
linkType: hard
15312+
1529515313
"@tediousjs/connection-string@npm:^0.5.0":
1529615314
version: 0.5.0
1529715315
resolution: "@tediousjs/connection-string@npm:0.5.0"
@@ -38100,7 +38118,7 @@ __metadata:
3810038118
languageName: node
3810138119
linkType: hard
3810238120

38103-
"react-virtual@npm:^2.10.4, react-virtual@npm:^2.8.2":
38121+
"react-virtual@npm:^2.8.2":
3810438122
version: 2.10.4
3810538123
resolution: "react-virtual@npm:2.10.4"
3810638124
dependencies:

0 commit comments

Comments
 (0)