3
3
import type { Row } from "@tanstack/react-table" ;
4
4
import { flexRender } from "@tanstack/react-table" ;
5
5
import type { Table as ReactTableType } from "@tanstack/react-table" ;
6
+ import { useVirtualizer } from "@tanstack/react-virtual" ;
6
7
import { useMemo } from "react" ;
7
- import { useVirtual } from "react-virtual" ;
8
8
9
9
import classNames from "@calcom/lib/classNames" ;
10
10
import { Icon , TableNew , TableBody , TableCell , TableHead , TableHeader , TableRow } from "@calcom/ui" ;
@@ -32,15 +32,20 @@ export function DataTable<TData, TValue>({
32
32
} : DataTableProps < TData , TValue > & React . ComponentPropsWithoutRef < "div" > ) {
33
33
const { rows } = table . getRowModel ( ) ;
34
34
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 ,
38
45
overscan : 10 ,
39
46
} ) ;
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 ( ) ;
44
49
45
50
const columnSizeVars = useMemo ( ( ) => {
46
51
const headers = table . getFlatHeaders ( ) ;
@@ -52,14 +57,11 @@ export function DataTable<TData, TValue>({
52
57
colSizes [ `--col-${ header . column . id } -size` ] = header . column . getSize ( ) ;
53
58
}
54
59
return colSizes ;
55
- } , [ table . getState ( ) . columnSizingInfo , table . getState ( ) . columnSizing ] ) ;
60
+ } , [ table . getFlatHeaders ( ) , table . getState ( ) . columnSizingInfo , table . getState ( ) . columnSizing ] ) ;
56
61
57
62
return (
58
63
< 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 ) }
63
65
style = { {
64
66
gridTemplateRows : "auto 1fr auto" ,
65
67
gridTemplateAreas : "'header' 'body' 'footer'" ,
@@ -70,12 +72,15 @@ export function DataTable<TData, TValue>({
70
72
< div
71
73
ref = { tableContainerRef }
72
74
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
+ ) }
74
79
style = { { gridArea : "body" } } >
75
- < TableNew className = "border-0" >
80
+ < TableNew className = "grid border-0" >
76
81
< TableHeader className = "bg-subtle sticky top-0 z-10" >
77
82
{ table . getHeaderGroups ( ) . map ( ( headerGroup ) => (
78
- < TableRow key = { headerGroup . id } >
83
+ < TableRow key = { headerGroup . id } className = "flex w-full" >
79
84
{ headerGroup . headers . map ( ( header ) => {
80
85
const meta = header . column . columnDef . meta ;
81
86
return (
@@ -87,8 +92,9 @@ export function DataTable<TData, TValue>({
87
92
width : `calc(var(--header-${ header ?. id } -size) * 1px)` ,
88
93
} }
89
94
className = { classNames (
95
+ "flex shrink-0 items-center" ,
90
96
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"
92
98
) } >
93
99
< div className = "flex items-center" onClick = { header . column . getToggleSortingHandler ( ) } >
94
100
{ header . isPlaceholder
@@ -112,20 +118,23 @@ export function DataTable<TData, TValue>({
112
118
</ TableRow >
113
119
) ) }
114
120
</ 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` } } >
121
122
{ virtualRows && ! isPending ? (
122
123
virtualRows . map ( ( virtualRow ) => {
123
124
const row = rows [ virtualRow . index ] as Row < TData > ;
124
125
return (
125
126
< TableRow
127
+ ref = { ( node ) => rowVirtualizer . measureElement ( node ) } //measure dynamic row height
126
128
key = { row . id }
129
+ data-index = { virtualRow . index } //needed for dynamic row height measurement
127
130
data-state = { row . getIsSelected ( ) && "selected" }
128
131
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
+ } }
129
138
className = { classNames (
130
139
onRowMouseclick && "hover:cursor-pointer" ,
131
140
variant === "compact" && "!border-0" ,
@@ -143,6 +152,7 @@ export function DataTable<TData, TValue>({
143
152
width : `calc(var(--col-${ cell . column . id } -size) * 1px)` ,
144
153
} }
145
154
className = { classNames (
155
+ "flex shrink-0 items-center overflow-auto" ,
146
156
variant === "compact" && "p-1.5" ,
147
157
meta ?. sticky && "group-hover:bg-muted bg-default sticky"
148
158
) } >
@@ -160,11 +170,6 @@ export function DataTable<TData, TValue>({
160
170
</ TableCell >
161
171
</ TableRow >
162
172
) }
163
- { paddingBottom > 0 && (
164
- < tr >
165
- < td style = { { height : `${ paddingBottom } px` } } />
166
- </ tr >
167
- ) }
168
173
</ TableBody >
169
174
</ TableNew >
170
175
</ div >
0 commit comments