Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit fa443e4

Browse files
author
Mor Kadosh
committedMay 10, 2024
feat: add filters example
1 parent 8328b11 commit fa443e4

File tree

9 files changed

+459
-0
lines changed

9 files changed

+459
-0
lines changed
 

‎examples/lit/filters/.gitignore

+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

‎examples/lit/filters/README.md

+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`

‎examples/lit/filters/index.html

+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>

‎examples/lit/filters/package.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "tanstack-lit-table-example-filters",
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+
"@tanstack/lit-table": "workspace:*",
13+
"lit": "^3.1.3"
14+
},
15+
"devDependencies": {
16+
"@rollup/plugin-replace": "^5.0.5",
17+
"typescript": "5.4.5",
18+
"vite": "^5.2.10"
19+
}
20+
}

‎examples/lit/filters/src/main.ts

+307
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
import { customElement, property, state } from 'lit/decorators.js'
2+
import { html, LitElement } from 'lit'
3+
import { repeat } from 'lit/directives/repeat.js'
4+
import {
5+
Column,
6+
ColumnDef,
7+
ColumnFiltersState,
8+
flexRender,
9+
getCoreRowModel,
10+
getFilteredRowModel,
11+
getPaginationRowModel,
12+
getSortedRowModel,
13+
RowData,
14+
TableController,
15+
} from '@tanstack/lit-table'
16+
import { makeData, Person } from './makeData'
17+
18+
const columns: ColumnDef<Person, any>[] = [
19+
{
20+
accessorKey: 'firstName',
21+
cell: info => info.getValue(),
22+
},
23+
{
24+
accessorFn: row => row.lastName,
25+
id: 'lastName',
26+
cell: info => info.getValue(),
27+
header: () => html`<span>Last Name</span>`,
28+
},
29+
{
30+
accessorFn: row => `${row.firstName} ${row.lastName}`,
31+
id: 'fullName',
32+
header: 'Full Name',
33+
cell: info => info.getValue(),
34+
},
35+
{
36+
accessorKey: 'age',
37+
header: () => 'Age',
38+
meta: {
39+
filterVariant: 'range',
40+
},
41+
},
42+
{
43+
accessorKey: 'visits',
44+
header: () => html`<span>Visits</span>`,
45+
meta: {
46+
filterVariant: 'range',
47+
},
48+
},
49+
{
50+
accessorKey: 'status',
51+
header: 'Status',
52+
meta: {
53+
filterVariant: 'select',
54+
},
55+
},
56+
{
57+
accessorKey: 'progress',
58+
header: 'Profile Progress',
59+
meta: {
60+
filterVariant: 'range',
61+
},
62+
},
63+
]
64+
65+
declare module '@tanstack/lit-table' {
66+
//allows us to define custom properties for our columns
67+
interface ColumnMeta<TData extends RowData, TValue> {
68+
filterVariant?: 'text' | 'range' | 'select'
69+
}
70+
}
71+
72+
const data = makeData(50_000)
73+
74+
@customElement('column-filter')
75+
class ColumnFilter extends LitElement {
76+
@property()
77+
private column!: Column<any, {}>
78+
79+
private onChange(evt: InputEvent) {
80+
this.column?.setFilterValue((evt.target as HTMLInputElement).value)
81+
}
82+
83+
render() {
84+
const { filterVariant } = this.column.columnDef.meta ?? {}
85+
const columnFilterValue = this.column.getFilterValue()
86+
87+
switch (filterVariant) {
88+
case 'select':
89+
return html` <select
90+
@change=${(e: Event) =>
91+
this.column.setFilterValue((e.target as HTMLSelectElement).value)}
92+
>
93+
<option value="">All</option>
94+
<option value="complicated">complicated</option>
95+
<option value="relationship">relationship</option>
96+
<option value="single">single</option>
97+
</select>`
98+
case 'range':
99+
return html`
100+
<div style="display:flex;gap:2px">
101+
<input
102+
type="number"
103+
placeholder="Min"
104+
@change="${(e: Event) =>
105+
this.column.setFilterValue((old: [number, number]) => [
106+
parseInt((e.target as HTMLInputElement).value, 10),
107+
old?.[1],
108+
])}"
109+
value=${(columnFilterValue as [number, number])?.[0] ?? ''}
110+
/>
111+
<input
112+
type="number"
113+
placeholder="Max"
114+
@change="${(e: Event) =>
115+
this.column.setFilterValue((old: [number, number]) => [
116+
parseInt((e.target as HTMLInputElement).value, 10),
117+
old?.[0],
118+
])}"
119+
value=${(columnFilterValue as [number, number])?.[1] ?? ''}
120+
/>
121+
</div>
122+
`
123+
default:
124+
return html`<input @input=${this.onChange} />`
125+
}
126+
return null
127+
}
128+
}
129+
130+
@customElement('lit-table-example')
131+
class LitTableExample extends LitElement {
132+
private tableController = new TableController<Person>(this)
133+
134+
@state()
135+
private _columnFilters: ColumnFiltersState = []
136+
137+
protected render(): unknown {
138+
const table = this.tableController.getTable({
139+
data,
140+
columns,
141+
filterFns: {},
142+
state: {
143+
columnFilters: this._columnFilters,
144+
},
145+
onColumnFiltersChange: updater => {
146+
if (typeof updater === 'function') {
147+
this._columnFilters = updater(this._columnFilters)
148+
} else {
149+
this._columnFilters = updater
150+
}
151+
},
152+
getCoreRowModel: getCoreRowModel(),
153+
getFilteredRowModel: getFilteredRowModel(), //client side filtering
154+
getSortedRowModel: getSortedRowModel(),
155+
getPaginationRowModel: getPaginationRowModel(),
156+
debugTable: true,
157+
debugHeaders: true,
158+
debugColumns: false,
159+
})
160+
161+
return html`
162+
<table>
163+
<thead>
164+
${repeat(
165+
table.getHeaderGroups(),
166+
headerGroup => headerGroup.id,
167+
headerGroup => html`
168+
<tr>
169+
${repeat(
170+
headerGroup.headers,
171+
header => header.id,
172+
header => html`
173+
<th colspan="${header.colSpan}">
174+
${header.isPlaceholder
175+
? null
176+
: html`<div
177+
title=${header.column.getCanSort()
178+
? header.column.getNextSortingOrder() === 'asc'
179+
? 'Sort ascending'
180+
: header.column.getNextSortingOrder() ===
181+
'desc'
182+
? 'Sort descending'
183+
: 'Clear sort'
184+
: undefined}
185+
@click="${header.column.getToggleSortingHandler()}"
186+
style="cursor: ${header.column.getCanSort()
187+
? 'pointer'
188+
: 'not-allowed'}"
189+
>
190+
${flexRender(
191+
header.column.columnDef.header,
192+
header.getContext()
193+
)}
194+
${{ asc: ' 🔼', desc: ' 🔽' }[
195+
header.column.getIsSorted() as string
196+
] ?? null}
197+
</div>
198+
${header.column.getCanFilter()
199+
? html` <div>
200+
<column-filter
201+
.column="${header.column}"
202+
></column-filter>
203+
</div>`
204+
: null} `}
205+
</th>
206+
`
207+
)}
208+
</tr>
209+
`
210+
)}
211+
</thead>
212+
<tbody>
213+
${repeat(
214+
table.getRowModel().rows.slice(0, 10),
215+
row => row.id,
216+
row => html`
217+
<tr>
218+
${repeat(
219+
row.getVisibleCells(),
220+
cell => cell.id,
221+
cell => html`
222+
<td>
223+
${flexRender(
224+
cell.column.columnDef.cell,
225+
cell.getContext()
226+
)}
227+
</td>
228+
`
229+
)}
230+
</tr>
231+
`
232+
)}
233+
</tbody>
234+
</table>
235+
<div class="page-controls">
236+
<button
237+
@click=${() => table.setPageIndex(0)}
238+
?disabled="${!table.getCanPreviousPage()}"
239+
>
240+
<<
241+
</button>
242+
<button
243+
@click=${() => table.previousPage()}
244+
?disabled=${!table.getCanPreviousPage()}
245+
>
246+
<
247+
</button>
248+
<button
249+
@click=${() => table.nextPage()}
250+
?disabled=${!table.getCanNextPage()}
251+
>
252+
>
253+
</button>
254+
<button
255+
@click=${() => table.setPageIndex(table.getPageCount() - 1)}
256+
?disabled="${!table.getCanNextPage()}"
257+
>
258+
>>
259+
</button>
260+
<span style="display: flex;gap:2px">
261+
<span>Page</span>
262+
<strong>
263+
${table.getState().pagination.pageIndex + 1} of
264+
${table.getPageCount()}
265+
</strong>
266+
</span>
267+
</div>
268+
<pre>${JSON.stringify(this._columnFilters, null, 2)}</pre>
269+
<style>
270+
* {
271+
font-family: sans-serif;
272+
font-size: 14px;
273+
box-sizing: border-box;
274+
}
275+
276+
table {
277+
border: 1px solid lightgray;
278+
border-collapse: collapse;
279+
}
280+
281+
tbody {
282+
border-bottom: 1px solid lightgray;
283+
}
284+
285+
th {
286+
border-bottom: 1px solid lightgray;
287+
border-right: 1px solid lightgray;
288+
padding: 2px 4px;
289+
}
290+
291+
tfoot {
292+
color: gray;
293+
}
294+
295+
tfoot th {
296+
font-weight: normal;
297+
}
298+
299+
.page-controls {
300+
display: flex;
301+
gap: 10px;
302+
padding: 4px 0;
303+
}
304+
</style>
305+
`
306+
}
307+
}

‎examples/lit/filters/src/makeData.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { faker } from '@faker-js/faker'
2+
3+
export type Person = {
4+
firstName: string
5+
lastName: string
6+
age: number
7+
visits: number
8+
progress: number
9+
status: 'relationship' | 'complicated' | 'single'
10+
subRows?: Person[]
11+
}
12+
13+
const range = (len: number) => {
14+
const arr: number[] = []
15+
for (let i = 0; i < len; i++) {
16+
arr.push(i)
17+
}
18+
return arr
19+
}
20+
21+
const newPerson = (): Person => {
22+
return {
23+
firstName: faker.person.firstName(),
24+
lastName: faker.person.lastName(),
25+
age: faker.number.int(40),
26+
visits: faker.number.int(1000),
27+
progress: faker.number.int(100),
28+
status: faker.helpers.shuffle<Person['status']>([
29+
'relationship',
30+
'complicated',
31+
'single',
32+
])[0]!,
33+
}
34+
}
35+
36+
export function makeData(...lens: number[]) {
37+
const makeDataLevel = (depth = 0): Person[] => {
38+
const len = lens[depth]!
39+
return range(len).map((): Person => {
40+
return {
41+
...newPerson(),
42+
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
43+
}
44+
})
45+
}
46+
47+
return makeDataLevel()
48+
}

‎examples/lit/filters/tsconfig.json

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
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+
"noEmit": true,
14+
"jsx": "react-jsx",
15+
"experimentalDecorators": true,
16+
"useDefineForClassFields": false,
17+
18+
/* Linting */
19+
"strict": true,
20+
"noUnusedLocals": false,
21+
"noUnusedParameters": true,
22+
"noFallthroughCasesInSwitch": true
23+
},
24+
"include": ["src"]
25+
}

‎examples/lit/filters/vite.config.js

+15
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

+19
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.