Skip to content

Commit 88b9773

Browse files
authored
docs(examples): use spacer rows instead of translateY in the table examples (#1338)
1 parent 2343004 commit 88b9773

6 files changed

Lines changed: 365 additions & 252 deletions

File tree

‎examples/angular/table/src/app/app.component.ts‎

Lines changed: 103 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -25,91 +25,107 @@ import { makeData, type Person } from './make-data'
2525
imports: [FlexRenderDirective],
2626
template: `
2727
<p>
28-
For tables, the basis for the offset of the translate css function is from
29-
the row's initial position itself. Because of this, we need to calculate
30-
the translateY pixel count different and base it off the the index.
28+
Table rows can't be absolutely positioned, so only the visible rows are
29+
rendered, with a spacer row above and below them. The spacers keep the
30+
table as tall as all of its rows, so the sticky header stays in place
31+
while scrolling.
3132
</p>
3233
<br />
3334
3435
<div #scrollElement class="list scroll-container">
35-
<div [style.height.px]="virtualizer.getTotalSize()">
36-
<table>
37-
<thead>
38-
@for (
39-
headerGroup of table.getHeaderGroups();
40-
track headerGroup.id
41-
) {
42-
<tr>
43-
@for (header of headerGroup.headers; track header.id) {
44-
<th
45-
[attr.colspan]="header.colSpan"
46-
[style.width.px]="header.getSize()"
47-
>
48-
@if (!header.isPlaceholder) {
49-
<div
50-
[class.cursor-pointer]="header.column.getCanSort()"
51-
[class.select-none]="header.column.getCanSort()"
52-
(click)="
53-
header.column.getToggleSortingHandler()?.($event)
54-
"
55-
>
56-
<ng-container
57-
*flexRender="
58-
header.column.columnDef.header;
59-
props: header.getContext();
60-
let headerText
61-
"
62-
>
63-
<span>{{ headerText }}</span>
64-
{{ getSortIcon(header.column.getIsSorted()) }}
65-
</ng-container>
66-
</div>
67-
}
68-
</th>
69-
}
70-
</tr>
71-
}
72-
</thead>
73-
<tbody>
74-
@for (
75-
virtualRow of virtualizer.getVirtualItems();
76-
track data[virtualRow.index].id
77-
) {
78-
<tr
79-
[style.height.px]="virtualRow.size"
80-
[style.transform]="
81-
'translateY(' +
82-
(virtualRow.start - $index * virtualRow.size) +
83-
'px)'
84-
"
85-
>
86-
@for (
87-
cell of rows()[virtualRow.index].getVisibleCells();
88-
track cell.id
89-
) {
90-
<td>
91-
<ng-container
92-
*flexRender="
93-
cell.column.columnDef.cell;
94-
props: cell.getContext();
95-
let cellText
36+
<table>
37+
<thead>
38+
@for (headerGroup of table.getHeaderGroups(); track headerGroup.id) {
39+
<tr>
40+
@for (header of headerGroup.headers; track header.id) {
41+
<th
42+
[attr.colspan]="header.colSpan"
43+
[style.width.px]="header.getSize()"
44+
>
45+
@if (!header.isPlaceholder) {
46+
<div
47+
[class.cursor-pointer]="header.column.getCanSort()"
48+
[class.select-none]="header.column.getCanSort()"
49+
(click)="
50+
header.column.getToggleSortingHandler()?.($event)
9651
"
9752
>
98-
<span>{{ cellText }}</span>
99-
</ng-container>
100-
</td>
101-
}
102-
</tr>
103-
}
104-
</tbody>
105-
</table>
106-
</div>
53+
<ng-container
54+
*flexRender="
55+
header.column.columnDef.header;
56+
props: header.getContext();
57+
let headerText
58+
"
59+
>
60+
<span>{{ headerText }}</span>
61+
{{ getSortIcon(header.column.getIsSorted()) }}
62+
</ng-container>
63+
</div>
64+
}
65+
</th>
66+
}
67+
</tr>
68+
}
69+
</thead>
70+
<tbody>
71+
@if (paddingTop() > 0) {
72+
<tr>
73+
<td
74+
[attr.colspan]="table.getVisibleLeafColumns().length"
75+
[style.height.px]="paddingTop()"
76+
></td>
77+
</tr>
78+
}
79+
@for (
80+
virtualRow of virtualizer.getVirtualItems();
81+
track data[virtualRow.index].id
82+
) {
83+
<tr [style.height.px]="virtualRow.size">
84+
@for (
85+
cell of rows()[virtualRow.index].getVisibleCells();
86+
track cell.id
87+
) {
88+
<td>
89+
<ng-container
90+
*flexRender="
91+
cell.column.columnDef.cell;
92+
props: cell.getContext();
93+
let cellText
94+
"
95+
>
96+
<span>{{ cellText }}</span>
97+
</ng-container>
98+
</td>
99+
}
100+
</tr>
101+
}
102+
@if (paddingBottom() > 0) {
103+
<tr>
104+
<td
105+
[attr.colspan]="table.getVisibleLeafColumns().length"
106+
[style.height.px]="paddingBottom()"
107+
></td>
108+
</tr>
109+
}
110+
</tbody>
111+
</table>
107112
</div>
108113
`,
109114
styles: `
110115
.scroll-container {
111116
height: 600px;
112117
overflow: auto;
118+
overflow-anchor: none;
119+
}
120+
121+
table {
122+
border-collapse: collapse;
123+
}
124+
125+
thead {
126+
position: sticky;
127+
top: 0;
128+
background: white;
113129
}
114130
`,
115131
})
@@ -191,4 +207,18 @@ export class AppComponent {
191207
estimateSize: () => 34,
192208
overscan: 20,
193209
}))
210+
211+
// Height of the spacer rows rendered above and below the visible rows
212+
paddingTop = computed(() => {
213+
const virtualRows = this.virtualizer.getVirtualItems()
214+
return virtualRows.length > 0 ? virtualRows[0].start : 0
215+
})
216+
217+
paddingBottom = computed(() => {
218+
const virtualRows = this.virtualizer.getVirtualItems()
219+
return virtualRows.length > 0
220+
? this.virtualizer.getTotalSize() -
221+
virtualRows[virtualRows.length - 1].end
222+
: 0
223+
})
194224
}

‎examples/react/table/src/index.css‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,15 @@ body {
1616
.container {
1717
height: 600px;
1818
overflow: auto;
19+
overflow-anchor: none;
20+
}
21+
22+
table {
23+
border-collapse: collapse;
24+
}
25+
26+
thead {
27+
position: sticky;
28+
top: 0;
29+
background: white;
1930
}

‎examples/react/table/src/main.tsx‎

Lines changed: 78 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -86,74 +86,87 @@ function ReactTableVirtualized() {
8686
overscan: 20,
8787
})
8888

89+
const virtualRows = virtualizer.getVirtualItems()
90+
91+
// Height of the spacer rows rendered above and below the visible rows
92+
const [paddingTop, paddingBottom] =
93+
virtualRows.length > 0
94+
? [
95+
virtualRows[0].start,
96+
virtualizer.getTotalSize() - virtualRows[virtualRows.length - 1].end,
97+
]
98+
: [0, 0]
99+
100+
const columnCount = table.getVisibleLeafColumns().length
101+
89102
return (
90103
<div ref={parentRef} className="container">
91-
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
92-
<table>
93-
<thead>
94-
{table.getHeaderGroups().map((headerGroup) => (
95-
<tr key={headerGroup.id}>
96-
{headerGroup.headers.map((header) => {
104+
<table>
105+
<thead>
106+
{table.getHeaderGroups().map((headerGroup) => (
107+
<tr key={headerGroup.id}>
108+
{headerGroup.headers.map((header) => {
109+
return (
110+
<th
111+
key={header.id}
112+
colSpan={header.colSpan}
113+
style={{ width: header.getSize() }}
114+
>
115+
{header.isPlaceholder ? null : (
116+
<div
117+
{...{
118+
className: header.column.getCanSort()
119+
? 'cursor-pointer select-none'
120+
: '',
121+
onClick: header.column.getToggleSortingHandler(),
122+
}}
123+
>
124+
{flexRender(
125+
header.column.columnDef.header,
126+
header.getContext(),
127+
)}
128+
{{
129+
asc: ' 🔼',
130+
desc: ' 🔽',
131+
}[header.column.getIsSorted() as string] ?? null}
132+
</div>
133+
)}
134+
</th>
135+
)
136+
})}
137+
</tr>
138+
))}
139+
</thead>
140+
<tbody>
141+
{paddingTop > 0 && (
142+
<tr>
143+
<td colSpan={columnCount} style={{ height: paddingTop }} />
144+
</tr>
145+
)}
146+
{virtualRows.map((virtualRow) => {
147+
const row = rows[virtualRow.index]
148+
return (
149+
<tr key={row.id} style={{ height: `${virtualRow.size}px` }}>
150+
{row.getVisibleCells().map((cell) => {
97151
return (
98-
<th
99-
key={header.id}
100-
colSpan={header.colSpan}
101-
style={{ width: header.getSize() }}
102-
>
103-
{header.isPlaceholder ? null : (
104-
<div
105-
{...{
106-
className: header.column.getCanSort()
107-
? 'cursor-pointer select-none'
108-
: '',
109-
onClick: header.column.getToggleSortingHandler(),
110-
}}
111-
>
112-
{flexRender(
113-
header.column.columnDef.header,
114-
header.getContext(),
115-
)}
116-
{{
117-
asc: ' 🔼',
118-
desc: ' 🔽',
119-
}[header.column.getIsSorted() as string] ?? null}
120-
</div>
152+
<td key={cell.id}>
153+
{flexRender(
154+
cell.column.columnDef.cell,
155+
cell.getContext(),
121156
)}
122-
</th>
157+
</td>
123158
)
124159
})}
125160
</tr>
126-
))}
127-
</thead>
128-
<tbody>
129-
{virtualizer.getVirtualItems().map((virtualRow, index) => {
130-
const row = rows[virtualRow.index]
131-
return (
132-
<tr
133-
key={row.id}
134-
style={{
135-
height: `${virtualRow.size}px`,
136-
transform: `translateY(${
137-
virtualRow.start - index * virtualRow.size
138-
}px)`,
139-
}}
140-
>
141-
{row.getVisibleCells().map((cell) => {
142-
return (
143-
<td key={cell.id}>
144-
{flexRender(
145-
cell.column.columnDef.cell,
146-
cell.getContext(),
147-
)}
148-
</td>
149-
)
150-
})}
151-
</tr>
152-
)
153-
})}
154-
</tbody>
155-
</table>
156-
</div>
161+
)
162+
})}
163+
{paddingBottom > 0 && (
164+
<tr>
165+
<td colSpan={columnCount} style={{ height: paddingBottom }} />
166+
</tr>
167+
)}
168+
</tbody>
169+
</table>
157170
</div>
158171
)
159172
}
@@ -162,10 +175,10 @@ function App() {
162175
return (
163176
<div>
164177
<p>
165-
For tables, the basis for the offset of the translate css function is
166-
from the row's initial position itself. Because of this, we need to
167-
calculate the translateY pixel count differently and base it off the
168-
index.
178+
Table rows can't be absolutely positioned, so only the visible rows are
179+
rendered, with a spacer row above and below them. The spacers keep the
180+
table as tall as all of its rows, so the sticky header stays in place
181+
while scrolling.
169182
</p>
170183
<ReactTableVirtualized />
171184
<br />

0 commit comments

Comments
 (0)