Skip to content

Commit 18cce30

Browse files
KevinVandyclaude
andauthored
feat(data-table): migrate to TanStack Table v9 (#1928)
* feat(data-table): migrate to tanstack table v9 Replaces @tanstack/vue-table@8 usage with the v9 API across the demo, tasks and dashboard examples, the dashboard-01 block, and the payments card: - useVueTable -> useTable with explicit tableFeatures(); row models are feature slots and the built-in filter/sort fns are registered (v9 tree-shakes them; unregistered string fns silently no-op) - delete the valueUpdater helper and the table item's utils.ts from the registry (fixes the dead helper on v9); the table item no longer depends on @tanstack/vue-table - drop controlled state refs in favor of v9's internal reactive state; each example keeps one externally managed rowSelection slice to show the pattern - FlexRender :header/:cell shorthand; wrap a bare row.toggleExpanded reference that would lose `this` on v9's prototype methods - rewrite the data-table guide for v9 and update tanstack.com links - regenerate registry output; add the dashboard-01 features.ts to its manifest Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(data-table): make the row selection comment reader-facing Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 36fa539 commit 18cce30

56 files changed

Lines changed: 719 additions & 1138 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/v4/components/cards/Payments.vue

Lines changed: 53 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
<script setup lang="ts">
2-
import type {
3-
ColumnDef,
4-
ColumnFiltersState,
5-
SortingState,
6-
VisibilityState,
7-
} from '@tanstack/vue-table'
2+
import type { RowSelectionState } from '@tanstack/vue-table'
83
import { ArrowUpDown, ChevronDown } from '@lucide/vue'
94
import {
5+
columnFilteringFeature,
6+
columnVisibilityFeature,
7+
createColumnHelper,
8+
createFilteredRowModel,
9+
createPaginatedRowModel,
10+
createSortedRowModel,
11+
filterFn_includesString,
1012
FlexRender,
11-
getCoreRowModel,
12-
getFilteredRowModel,
13-
getPaginationRowModel,
14-
getSortedRowModel,
15-
useVueTable,
13+
rowPaginationFeature,
14+
rowSelectionFeature,
15+
rowSortingFeature,
16+
sortFn_alphanumeric,
17+
sortFn_text,
18+
tableFeatures,
19+
useTable,
1620
} from '@tanstack/vue-table'
1721
import { h, ref } from 'vue'
1822
import DropdownAction from '@/components/_internal/sink/DataTableDemoColumn.vue'
@@ -28,7 +32,6 @@ import {
2832
} from '@/registry/new-york-v4/ui/dropdown-menu'
2933
import { Input } from '@/registry/new-york-v4/ui/input'
3034
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/registry/new-york-v4/ui/table'
31-
import { valueUpdater } from '@/registry/new-york-v4/ui/table/utils'
3235
3336
export interface Payment {
3437
id: string
@@ -70,8 +73,25 @@ const data: Payment[] = [
7073
},
7174
]
7275
73-
const columns: ColumnDef<Payment>[] = [
74-
{
76+
// New in v9: declare the features this table uses — anything you don't
77+
// register is tree-shaken out of the bundle.
78+
const features = tableFeatures({
79+
columnFilteringFeature,
80+
columnVisibilityFeature,
81+
rowPaginationFeature,
82+
rowSelectionFeature,
83+
rowSortingFeature,
84+
filteredRowModel: createFilteredRowModel(),
85+
paginatedRowModel: createPaginatedRowModel(),
86+
sortedRowModel: createSortedRowModel(),
87+
filterFns: { includesString: filterFn_includesString },
88+
sortFns: { alphanumeric: sortFn_alphanumeric, text: sortFn_text },
89+
})
90+
91+
const columnHelper = createColumnHelper<typeof features, Payment>()
92+
93+
const columns = columnHelper.columns([
94+
columnHelper.display({
7595
id: 'select',
7696
header: ({ table }) => h(Checkbox, {
7797
'modelValue': table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate'),
@@ -85,24 +105,21 @@ const columns: ColumnDef<Payment>[] = [
85105
}),
86106
enableSorting: false,
87107
enableHiding: false,
88-
},
89-
{
90-
accessorKey: 'status',
108+
}),
109+
columnHelper.accessor('status', {
91110
header: 'Status',
92111
cell: ({ row }) => h('div', { class: 'capitalize' }, row.getValue('status')),
93-
},
94-
{
95-
accessorKey: 'email',
112+
}),
113+
columnHelper.accessor('email', {
96114
header: ({ column }) => {
97115
return h(Button, {
98116
variant: 'ghost',
99117
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),
100118
}, () => ['Email', h(ArrowUpDown, { class: 'ml-2 h-4 w-4' })])
101119
},
102120
cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email')),
103-
},
104-
{
105-
accessorKey: 'amount',
121+
}),
122+
columnHelper.accessor('amount', {
106123
header: () => h('div', { class: 'text-right' }, 'Amount'),
107124
cell: ({ row }) => {
108125
const amount = Number.parseFloat(row.getValue('amount'))
@@ -115,8 +132,8 @@ const columns: ColumnDef<Payment>[] = [
115132
116133
return h('div', { class: 'text-right font-medium' }, formatted)
117134
},
118-
},
119-
{
135+
}),
136+
columnHelper.display({
120137
id: 'actions',
121138
enableHiding: false,
122139
cell: ({ row }) => {
@@ -126,31 +143,22 @@ const columns: ColumnDef<Payment>[] = [
126143
payment,
127144
})
128145
},
129-
},
130-
]
146+
}),
147+
])
131148
132-
const sorting = ref<SortingState>([])
133-
const columnFilters = ref<ColumnFiltersState>([])
134-
const columnVisibility = ref<VisibilityState>({})
135-
const rowSelection = ref({})
149+
// Keep row selection outside the table so the rest of the app can read or update it.
150+
const rowSelection = ref<RowSelectionState>({})
136151
137-
const table = useVueTable({
152+
const table = useTable({
153+
features,
138154
data,
139155
columns,
140-
getCoreRowModel: getCoreRowModel(),
141-
getPaginationRowModel: getPaginationRowModel(),
142-
getSortedRowModel: getSortedRowModel(),
143-
getFilteredRowModel: getFilteredRowModel(),
144-
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
145-
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
146-
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
147-
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
148156
state: {
149-
get sorting() { return sorting.value },
150-
get columnFilters() { return columnFilters.value },
151-
get columnVisibility() { return columnVisibility.value },
152157
get rowSelection() { return rowSelection.value },
153158
},
159+
onRowSelectionChange: (updater) => {
160+
rowSelection.value = typeof updater === 'function' ? updater(rowSelection.value) : updater
161+
},
154162
})
155163
</script>
156164

@@ -195,7 +203,7 @@ const table = useVueTable({
195203
<TableHeader>
196204
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
197205
<TableHead v-for="header in headerGroup.headers" :key="header.id" class="[&:has([role=checkbox])]:pl-3">
198-
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header" :props="header.getContext()" />
206+
<FlexRender v-if="!header.isPlaceholder" :header="header" />
199207
</TableHead>
200208
</TableRow>
201209
</TableHeader>
@@ -207,7 +215,7 @@ const table = useVueTable({
207215
:data-state="row.getIsSelected() && 'selected'"
208216
>
209217
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id" class="[&:has([role=checkbox])]:pl-3">
210-
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
218+
<FlexRender :cell="cell" />
211219
</TableCell>
212220
</TableRow>
213221
</template>

apps/v4/components/demo/DataTableDemo.vue

Lines changed: 60 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,27 @@
11
<script setup lang="ts">
2-
import type {
3-
ColumnDef,
4-
ColumnFiltersState,
5-
ExpandedState,
6-
SortingState,
7-
VisibilityState,
8-
} from '@tanstack/vue-table'
2+
import type { RowSelectionState } from '@tanstack/vue-table'
93
import { ArrowUpDown, ChevronDown, MoreHorizontal } from '@lucide/vue'
104
import {
5+
columnFilteringFeature,
6+
columnVisibilityFeature,
7+
createColumnHelper,
8+
createExpandedRowModel,
9+
createFilteredRowModel,
10+
createPaginatedRowModel,
11+
createSortedRowModel,
12+
filterFn_includesString,
1113
FlexRender,
12-
getCoreRowModel,
13-
getExpandedRowModel,
14-
getFilteredRowModel,
15-
getPaginationRowModel,
16-
getSortedRowModel,
17-
useVueTable,
14+
rowExpandingFeature,
15+
rowPaginationFeature,
16+
rowSelectionFeature,
17+
rowSortingFeature,
18+
sortFn_alphanumeric,
19+
sortFn_text,
20+
tableFeatures,
21+
useTable,
1822
} from '@tanstack/vue-table'
1923
import { createReusableTemplate } from '@vueuse/core'
2024
import { h, ref } from 'vue'
21-
22-
import { valueUpdater } from '@/lib/utils'
2325
import { Button } from '@/registry/new-york-v4/ui/button'
2426
import { Checkbox } from '@/registry/new-york-v4/ui/checkbox'
2527
import {
@@ -88,39 +90,55 @@ const [DefineTemplate, ReuseTemplate] = createReusableTemplate<{
8890
onExpand: () => void
8991
}>()
9092
91-
const columns: ColumnDef<Payment>[] = [
92-
{
93+
// New in v9: declare the features this table uses — anything you don't
94+
// register is tree-shaken out of the bundle.
95+
const features = tableFeatures({
96+
columnFilteringFeature,
97+
columnVisibilityFeature,
98+
rowExpandingFeature,
99+
rowPaginationFeature,
100+
rowSelectionFeature,
101+
rowSortingFeature,
102+
expandedRowModel: createExpandedRowModel(),
103+
filteredRowModel: createFilteredRowModel(),
104+
paginatedRowModel: createPaginatedRowModel(),
105+
sortedRowModel: createSortedRowModel(),
106+
filterFns: { includesString: filterFn_includesString },
107+
sortFns: { alphanumeric: sortFn_alphanumeric, text: sortFn_text },
108+
})
109+
110+
const columnHelper = createColumnHelper<typeof features, Payment>()
111+
112+
const columns = columnHelper.columns([
113+
columnHelper.display({
93114
id: 'select',
94115
header: ({ table }) => h(Checkbox, {
95116
'modelValue': table.getIsAllPageRowsSelected() || (table.getIsSomePageRowsSelected() && 'indeterminate'),
96-
'onUpdate:modelValue': value => table.toggleAllPageRowsSelected(!!value),
117+
'onUpdate:modelValue': (value: boolean | 'indeterminate') => table.toggleAllPageRowsSelected(!!value),
97118
'ariaLabel': 'Select all',
98119
}),
99120
cell: ({ row }) => h(Checkbox, {
100121
'modelValue': row.getIsSelected(),
101-
'onUpdate:modelValue': value => row.toggleSelected(!!value),
122+
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
102123
'ariaLabel': 'Select row',
103124
}),
104125
enableSorting: false,
105126
enableHiding: false,
106-
},
107-
{
108-
accessorKey: 'status',
127+
}),
128+
columnHelper.accessor('status', {
109129
header: 'Status',
110130
cell: ({ row }) => h('div', { class: 'capitalize' }, row.getValue('status')),
111-
},
112-
{
113-
accessorKey: 'email',
131+
}),
132+
columnHelper.accessor('email', {
114133
header: ({ column }) => {
115134
return h(Button, {
116135
variant: 'ghost',
117136
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc'),
118137
}, () => ['Email', h(ArrowUpDown, { class: 'ml-2 h-4 w-4' })])
119138
},
120139
cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email')),
121-
},
122-
{
123-
accessorKey: 'amount',
140+
}),
141+
columnHelper.accessor('amount', {
124142
header: () => h('div', { class: 'text-right' }, 'Amount'),
125143
cell: ({ row }) => {
126144
const amount = Number.parseFloat(row.getValue('amount'))
@@ -133,46 +151,33 @@ const columns: ColumnDef<Payment>[] = [
133151
134152
return h('div', { class: 'text-right font-medium' }, formatted)
135153
},
136-
},
137-
{
154+
}),
155+
columnHelper.display({
138156
id: 'actions',
139157
enableHiding: false,
140158
cell: ({ row }) => {
141159
const payment = row.original
142160
143161
return h(ReuseTemplate, {
144162
payment,
145-
onExpand: row.toggleExpanded,
163+
onExpand: () => row.toggleExpanded(),
146164
})
147165
},
148-
},
149-
]
166+
}),
167+
])
150168
151-
const sorting = ref<SortingState>([])
152-
const columnFilters = ref<ColumnFiltersState>([])
153-
const columnVisibility = ref<VisibilityState>({})
154-
const rowSelection = ref({})
155-
const expanded = ref<ExpandedState>({})
169+
// Keep row selection outside the table so the rest of the app can read or update it.
170+
const rowSelection = ref<RowSelectionState>({})
156171
157-
const table = useVueTable({
172+
const table = useTable({
173+
features,
158174
data,
159175
columns,
160-
getCoreRowModel: getCoreRowModel(),
161-
getPaginationRowModel: getPaginationRowModel(),
162-
getSortedRowModel: getSortedRowModel(),
163-
getFilteredRowModel: getFilteredRowModel(),
164-
getExpandedRowModel: getExpandedRowModel(),
165-
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
166-
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
167-
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
168-
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
169-
onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expanded),
170176
state: {
171-
get sorting() { return sorting.value },
172-
get columnFilters() { return columnFilters.value },
173-
get columnVisibility() { return columnVisibility.value },
174177
get rowSelection() { return rowSelection.value },
175-
get expanded() { return expanded.value },
178+
},
179+
onRowSelectionChange: (updater) => {
180+
rowSelection.value = typeof updater === 'function' ? updater(rowSelection.value) : updater
176181
},
177182
})
178183
@@ -235,7 +240,7 @@ function copy(id: string) {
235240
<TableHeader>
236241
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
237242
<TableHead v-for="header in headerGroup.headers" :key="header.id">
238-
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header" :props="header.getContext()" />
243+
<FlexRender v-if="!header.isPlaceholder" :header="header" />
239244
</TableHead>
240245
</TableRow>
241246
</TableHeader>
@@ -244,7 +249,7 @@ function copy(id: string) {
244249
<template v-for="row in table.getRowModel().rows" :key="row.id">
245250
<TableRow :data-state="row.getIsSelected() && 'selected'">
246251
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
247-
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
252+
<FlexRender :cell="cell" />
248253
</TableCell>
249254
</TableRow>
250255
<TableRow v-if="row.getIsExpanded()">

0 commit comments

Comments
 (0)