Skip to content

Commit 8e1baac

Browse files
kadoshmsMor KadoshKevinVandy
authoredJun 29, 2024··
docs(lit): Add example for virtualized rows (#5599)
* docs: add example for virtualized rows * chore: remove unused imports * update lock file * chore: add packagemanager entry * chore: update deps * fix: remove unused twind from example * update dep lock * add example to sidebar --------- Co-authored-by: Mor Kadosh <mkadosh@paloaltonetworks.com> Co-authored-by: Kevin Van Cott <kevinvandy656@gmail.com>
1 parent 439e689 commit 8e1baac

11 files changed

+393
-0
lines changed
 

‎docs/config.json

+4
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,10 @@
463463
{
464464
"to": "framework/lit/examples/sorting",
465465
"label": "Sorting"
466+
},
467+
{
468+
"to": "framework/lit/examples/virtualized-rows",
469+
"label": "Virtualized Rows"
466470
}
467471
]
468472
},
+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
node_modules
2+
.DS_Store
3+
dist
4+
dist-ssr
5+
*.local
+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Example
2+
3+
To run this example:
4+
5+
- `npm install` or `yarn`
6+
- `npm run start` or `yarn start`
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Vite App</title>
7+
<script type="module" src="https://cdn.skypack.dev/twind/shim"></script>
8+
</head>
9+
<body>
10+
<div id="root"></div>
11+
<script type="module" src="/src/main.ts"></script>
12+
<lit-table-example></lit-table-example>
13+
</body>
14+
</html>
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "tanstack-lit-table-virtualized-rows",
3+
"version": "0.0.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "vite",
7+
"build": "vite build",
8+
"serve": "vite preview",
9+
"start": "vite"
10+
},
11+
"dependencies": {
12+
"@faker-js/faker": "^8.4.1",
13+
"@lit-labs/virtualizer": "^2.0.13",
14+
"@tanstack/lit-table": "^8.19.1",
15+
"lit": "^3.1.3"
16+
},
17+
"devDependencies": {
18+
"@rollup/plugin-replace": "^5.0.5",
19+
"typescript": "5.4.5",
20+
"vite": "^5.2.10"
21+
}
22+
}
+211
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
import { customElement } from 'lit/decorators.js'
2+
import { html, LitElement } from 'lit'
3+
import { repeat } from 'lit/directives/repeat.js'
4+
import {
5+
ColumnDef,
6+
flexRender,
7+
getCoreRowModel,
8+
getSortedRowModel,
9+
TableController,
10+
} from '@tanstack/lit-table'
11+
import config from '../twind.config'
12+
import { styleMap } from 'lit/directives/style-map.js'
13+
import { virtualize, virtualizerRef } from '@lit-labs/virtualizer/virtualize.js'
14+
import { makeData, Person } from './makeData.ts'
15+
16+
const columns: ColumnDef<Person>[] = [
17+
{
18+
accessorKey: 'id',
19+
header: 'ID',
20+
size: 60,
21+
},
22+
{
23+
accessorKey: 'firstName',
24+
cell: info => info.getValue(),
25+
},
26+
{
27+
accessorFn: row => row.lastName,
28+
id: 'lastName',
29+
cell: info => info.getValue(),
30+
header: () => html`<span>Last Name</span>`,
31+
},
32+
{
33+
accessorKey: 'age',
34+
header: () => 'Age',
35+
size: 50,
36+
},
37+
{
38+
accessorKey: 'visits',
39+
header: () => html`<span>Visits</span>`,
40+
size: 50,
41+
},
42+
{
43+
accessorKey: 'status',
44+
header: 'Status',
45+
},
46+
{
47+
accessorKey: 'progress',
48+
header: 'Profile Progress',
49+
size: 80,
50+
},
51+
{
52+
accessorKey: 'createdAt',
53+
header: 'Created At',
54+
cell: info => info.getValue<Date>().toLocaleString(),
55+
size: 250,
56+
},
57+
]
58+
const data = makeData(50_000)
59+
60+
@customElement('lit-table-example')
61+
class LitTableExample extends LitElement {
62+
private tableController = new TableController<Person>(this)
63+
64+
protected render(): unknown {
65+
const table = this.tableController.table({
66+
columns,
67+
data,
68+
getSortedRowModel: getSortedRowModel(),
69+
getCoreRowModel: getCoreRowModel(),
70+
})
71+
const { rows } = table.getRowModel()
72+
return html`
73+
<div class="app">
74+
(${data.length} rows)
75+
<div
76+
class="container"
77+
style="${styleMap({
78+
overflow: 'auto', //our scrollable table container
79+
position: 'relative', //needed for sticky header
80+
height: '800px', //should be a fixed height
81+
})}"
82+
>
83+
<table style="display: grid">
84+
<thead
85+
style="${styleMap({
86+
display: 'grid',
87+
position: 'sticky',
88+
top: 0,
89+
zIndex: 1,
90+
})}"
91+
>
92+
${repeat(
93+
table.getHeaderGroups(),
94+
headerGroup => headerGroup.id,
95+
headerGroup => html`
96+
<tr style="${styleMap({ display: 'flex', width: '100%' })}">
97+
${repeat(
98+
headerGroup.headers,
99+
header => header.id,
100+
header => html`
101+
<th
102+
style="${styleMap({
103+
display: 'flex',
104+
width: `${header.getSize()}px`,
105+
})}"
106+
@click="${header.column.getToggleSortingHandler()}"
107+
>
108+
${flexRender(
109+
header.column.columnDef.header,
110+
header.getContext()
111+
)}
112+
${{ asc: ' 🔼', desc: ' 🔽' }[
113+
header.column.getIsSorted() as string
114+
] ?? null}
115+
</th>
116+
`
117+
)}
118+
</tr>
119+
`
120+
)}
121+
</thead>
122+
<tbody
123+
style=${styleMap({
124+
display: 'grid',
125+
height: 500,
126+
// height: `${rowVirtualizer.getTotalSize()}px`, //tells scrollbar how big the table is
127+
position: 'relative', //needed for absolute positioning of rows
128+
})}
129+
>
130+
${virtualize({
131+
items: data,
132+
renderItem: (_, index) => {
133+
const row = rows[index]
134+
return html`
135+
<tr
136+
style=${styleMap({
137+
display: 'flex',
138+
width: '100%',
139+
})}
140+
>
141+
${repeat(
142+
row.getVisibleCells(),
143+
cell => cell.id,
144+
cell => html`
145+
<td
146+
style=${styleMap({
147+
display: 'flex',
148+
width: `${cell.column.getSize()}px`,
149+
})}
150+
>
151+
${flexRender(
152+
cell.column.columnDef.cell,
153+
cell.getContext()
154+
)}
155+
</td>
156+
`
157+
)}
158+
</tr>
159+
`
160+
},
161+
})}
162+
</tbody>
163+
</table>
164+
</div>
165+
</div>
166+
167+
<style>
168+
html {
169+
font-family: sans-serif;
170+
font-size: 14px;
171+
}
172+
173+
table {
174+
border-collapse: collapse;
175+
border-spacing: 0;
176+
font-family: arial, sans-serif;
177+
table-layout: fixed;
178+
}
179+
180+
thead {
181+
background: lightgray;
182+
}
183+
184+
tr {
185+
border-bottom: 1px solid lightgray;
186+
}
187+
188+
th {
189+
border-bottom: 1px solid lightgray;
190+
border-right: 1px solid lightgray;
191+
padding: 2px 4px;
192+
text-align: left;
193+
}
194+
195+
td {
196+
padding: 6px;
197+
}
198+
199+
.container {
200+
border: 1px solid lightgray;
201+
margin: 1rem auto;
202+
}
203+
204+
.app {
205+
margin: 1rem auto;
206+
text-align: center;
207+
}
208+
</style>
209+
`
210+
}
211+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { faker } from '@faker-js/faker'
2+
3+
export type Person = {
4+
id: number
5+
firstName: string
6+
lastName: string
7+
age: number
8+
visits: number
9+
progress: number
10+
status: 'relationship' | 'complicated' | 'single'
11+
createdAt: Date
12+
}
13+
14+
const range = (len: number) => {
15+
const arr: number[] = []
16+
for (let i = 0; i < len; i++) {
17+
arr.push(i)
18+
}
19+
return arr
20+
}
21+
22+
const newPerson = (index: number): Person => {
23+
return {
24+
id: index + 1,
25+
firstName: faker.person.firstName(),
26+
lastName: faker.person.lastName(),
27+
age: faker.number.int(40),
28+
visits: faker.number.int(1000),
29+
progress: faker.number.int(100),
30+
createdAt: faker.date.anytime(),
31+
status: faker.helpers.shuffle<Person['status']>([
32+
'relationship',
33+
'complicated',
34+
'single',
35+
])[0]!,
36+
}
37+
}
38+
39+
export function makeData(...lens: number[]) {
40+
const makeDataLevel = (depth = 0): Person[] => {
41+
const len = lens[depth]!
42+
return range(len).map((d): Person => {
43+
return {
44+
...newPerson(d),
45+
}
46+
})
47+
}
48+
49+
return makeDataLevel()
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2020",
4+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
5+
"module": "ESNext",
6+
"skipLibCheck": true,
7+
8+
/* Bundler mode */
9+
"moduleResolution": "bundler",
10+
"allowImportingTsExtensions": true,
11+
"resolveJsonModule": true,
12+
"isolatedModules": true,
13+
"emitDecoratorMetadata": true,
14+
"noEmit": true,
15+
"jsx": "react-jsx",
16+
"experimentalDecorators": true,
17+
"useDefineForClassFields": false,
18+
19+
/* Linting */
20+
"strict": true,
21+
"noUnusedLocals": false,
22+
"noUnusedParameters": true,
23+
"noFallthroughCasesInSwitch": true
24+
},
25+
"include": ["src"]
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { defineConfig } from '@twind/core'
2+
import presetAutoprefix from '@twind/preset-autoprefix'
3+
import presetTailwind from '@twind/preset-tailwind/base'
4+
5+
export default defineConfig({
6+
presets: [presetAutoprefix(), presetTailwind()],
7+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { defineConfig } from 'vite'
2+
import rollupReplace from '@rollup/plugin-replace'
3+
4+
// https://vitejs.dev/config/
5+
export default defineConfig({
6+
plugins: [
7+
rollupReplace({
8+
preventAssignment: true,
9+
values: {
10+
__DEV__: JSON.stringify(true),
11+
'process.env.NODE_ENV': JSON.stringify('development'),
12+
},
13+
}),
14+
],
15+
})

‎pnpm-lock.yaml

+33
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)
Please sign in to comment.