Skip to content

Commit 6503d0a

Browse files
author
Mor Kadosh
committed
feat: sorting example working
1 parent 924f434 commit 6503d0a

File tree

12 files changed

+370
-42
lines changed

12 files changed

+370
-42
lines changed

examples/lit/basic/src/index.css

-26
This file was deleted.

examples/lit/sorting/.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/sorting/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/sorting/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/sorting/package.json

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"name": "tanstack-lit-table-example-sorting",
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+
}
3.67 MB
Binary file not shown.

examples/lit/sorting/src/main.ts

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import { customElement } from 'lit/decorators.js'
2+
import {
3+
html,
4+
LitElement
5+
} from 'lit'
6+
import {
7+
ColumnDef,
8+
flexRender,
9+
getCoreRowModel,
10+
getSortedRowModel,
11+
SortingFn,
12+
type SortingState,
13+
TableController,
14+
} from '@tanstack/lit-table'
15+
import { repeat } from 'lit/directives/repeat.js'
16+
17+
import { makeData, Person } from './makeData.ts'
18+
import { state } from 'lit/decorators/state.js'
19+
20+
const sortStatusFn: SortingFn<Person> = (rowA, rowB, _columnId) => {
21+
const statusA = rowA.original.status
22+
const statusB = rowB.original.status
23+
const statusOrder = ['single', 'complicated', 'relationship']
24+
return statusOrder.indexOf(statusA) - statusOrder.indexOf(statusB)
25+
}
26+
27+
const columns: ColumnDef<Person>[] = [
28+
{
29+
accessorKey: 'firstName',
30+
cell: info => info.getValue(),
31+
//this column will sort in ascending order by default since it is a string column
32+
},
33+
{
34+
accessorFn: row => row.lastName,
35+
id: 'lastName',
36+
cell: info => info.getValue(),
37+
header: () => html`<span>Last Name</span>`,
38+
sortUndefined: 'last', //force undefined values to the end
39+
sortDescFirst: false, //first sort order will be ascending (nullable values can mess up auto detection of sort order)
40+
},
41+
{
42+
accessorKey: 'age',
43+
header: () => 'Age',
44+
//this column will sort in descending order by default since it is a number column
45+
},
46+
{
47+
accessorKey: 'visits',
48+
header: () => html`<span>Visits</span>`,
49+
sortUndefined: 'last', //force undefined values to the end
50+
},
51+
{
52+
accessorKey: 'status',
53+
header: 'Status',
54+
sortingFn: sortStatusFn, //use our custom sorting function for this enum column
55+
},
56+
{
57+
accessorKey: 'progress',
58+
header: 'Profile Progress',
59+
// enableSorting: false, //disable sorting for this column
60+
},
61+
{
62+
accessorKey: 'rank',
63+
header: 'Rank',
64+
invertSorting: true, //invert the sorting order (golf score-like where smaller is better)
65+
},
66+
{
67+
accessorKey: 'createdAt',
68+
header: 'Created At',
69+
// sortingFn: 'datetime' //make sure table knows this is a datetime column (usually can detect if no null values)
70+
},
71+
]
72+
73+
const data: Person[] = makeData(100_000)
74+
75+
@customElement('lit-table-example')
76+
class LitTableExample extends LitElement {
77+
@state()
78+
private _sorting: SortingState = []
79+
80+
private tableController = new TableController<Person>(this)
81+
82+
protected render(): unknown {
83+
const table = this.tableController.getTable({
84+
columns,
85+
data,
86+
state: {
87+
sorting: this._sorting,
88+
},
89+
onSortingChange: updaterOrValue => {
90+
if (typeof updaterOrValue === 'function') {
91+
this._sorting = updaterOrValue(this._sorting)
92+
} else {
93+
this._sorting = updaterOrValue
94+
}
95+
},
96+
getSortedRowModel: getSortedRowModel(),
97+
getCoreRowModel: getCoreRowModel(),
98+
})
99+
100+
return html`
101+
<table>
102+
<thead>
103+
${repeat(
104+
table.getHeaderGroups(),
105+
headerGroup => headerGroup.id,
106+
headerGroup => html`
107+
<tr>
108+
${repeat(
109+
headerGroup.headers,
110+
header => header.id,
111+
header => html`
112+
<th colspan="${header.colSpan}">
113+
${header.isPlaceholder
114+
? null
115+
: html`<div
116+
title=${header.column.getCanSort()
117+
? header.column.getNextSortingOrder() === 'asc'
118+
? 'Sort ascending'
119+
: header.column.getNextSortingOrder() === 'desc'
120+
? 'Sort descending'
121+
: 'Clear sort'
122+
: undefined}
123+
@click="${header.column.getToggleSortingHandler()}"
124+
style="cursor: ${header.column.getCanSort()
125+
? 'pointer'
126+
: 'not-allowed'}"
127+
>
128+
${flexRender(
129+
header.column.columnDef.header,
130+
header.getContext()
131+
)}
132+
${{ asc: ' 🔼', desc: ' 🔽' }[
133+
header.column.getIsSorted() as string
134+
] ?? null}
135+
</div>`}
136+
</th>
137+
`
138+
)}
139+
</tr>
140+
`
141+
)}
142+
</thead>
143+
<tbody>
144+
${repeat(
145+
table.getRowModel().rows.slice(0, 10),
146+
row => row.id,
147+
row => html`
148+
<tr>
149+
${repeat(
150+
row.getVisibleCells(),
151+
cell => cell.id,
152+
cell => html`
153+
<td>
154+
${flexRender(
155+
cell.column.columnDef.cell,
156+
cell.getContext()
157+
)}
158+
</td>
159+
`
160+
)}
161+
</tr>
162+
`
163+
)}
164+
</tbody>
165+
</table>
166+
<pre>${JSON.stringify(this._sorting, null, 2)}</pre>
167+
<style>
168+
* {
169+
font-family: sans-serif;
170+
font-size: 14px;
171+
box-sizing: border-box;
172+
}
173+
174+
table {
175+
border: 1px solid lightgray;
176+
border-collapse: collapse;
177+
}
178+
179+
tbody {
180+
border-bottom: 1px solid lightgray;
181+
}
182+
183+
th {
184+
border-bottom: 1px solid lightgray;
185+
border-right: 1px solid lightgray;
186+
padding: 2px 4px;
187+
}
188+
189+
tfoot {
190+
color: gray;
191+
}
192+
193+
tfoot th {
194+
font-weight: normal;
195+
}
196+
</style>
197+
`
198+
}
199+
}

examples/lit/sorting/src/makeData.ts

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

examples/lit/sorting/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+
"experimentalDecorators": true,
15+
"emitDecoratorMetadata": 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/sorting/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+
})

0 commit comments

Comments
 (0)