Skip to content

Commit 4aa3179

Browse files
MuhammadM1998sandros94benjamincanac
authoredFeb 5, 2025··
feat(Table): extends core options and support other options like pagination (#3177)
Co-authored-by: Sandros94 <sandro.circi@digitoolmedia.com> Co-authored-by: Benjamin Canac <canacb1@gmail.com>
1 parent c5bb540 commit 4aa3179

File tree

5 files changed

+306
-25
lines changed

5 files changed

+306
-25
lines changed
 

‎docs/app/components/content/examples/table/TableGlobalFilterExample.vue

-2
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,6 @@ const columns: TableColumn<Payment>[] = [{
9090
}
9191
}]
9292
93-
const table = useTemplateRef('table')
94-
9593
const globalFilter = ref('45')
9694
</script>
9795

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
<script setup lang="ts">
2+
import { getPaginationRowModel } from '@tanstack/vue-table'
3+
import type { TableColumn } from '@nuxt/ui'
4+
5+
const table = useTemplateRef('table')
6+
7+
type Payment = {
8+
id: string
9+
date: string
10+
email: string
11+
amount: number
12+
}
13+
const data = ref<Payment[]>([{
14+
id: '4600',
15+
date: '2024-03-11T15:30:00',
16+
email: 'james.anderson@example.com',
17+
amount: 594
18+
}, {
19+
id: '4599',
20+
date: '2024-03-11T10:10:00',
21+
email: 'mia.white@example.com',
22+
amount: 276
23+
}, {
24+
id: '4598',
25+
date: '2024-03-11T08:50:00',
26+
email: 'william.brown@example.com',
27+
amount: 315
28+
}, {
29+
id: '4597',
30+
date: '2024-03-10T19:45:00',
31+
email: 'emma.davis@example.com',
32+
amount: 529
33+
}, {
34+
id: '4596',
35+
date: '2024-03-10T15:55:00',
36+
email: 'ethan.harris@example.com',
37+
amount: 639
38+
}, {
39+
id: '4595',
40+
date: '2024-03-10T13:20:00',
41+
email: 'sophia.miller@example.com',
42+
amount: 428
43+
}, {
44+
id: '4594',
45+
date: '2024-03-10T11:05:00',
46+
email: 'noah.wilson@example.com',
47+
amount: 673
48+
}, {
49+
id: '4593',
50+
date: '2024-03-09T22:15:00',
51+
email: 'olivia.jones@example.com',
52+
amount: 382
53+
}, {
54+
id: '4592',
55+
date: '2024-03-09T20:30:00',
56+
email: 'liam.taylor@example.com',
57+
amount: 547
58+
}, {
59+
id: '4591',
60+
date: '2024-03-09T18:45:00',
61+
email: 'ava.thomas@example.com',
62+
amount: 291
63+
}, {
64+
id: '4590',
65+
date: '2024-03-09T16:20:00',
66+
email: 'lucas.martin@example.com',
67+
amount: 624
68+
}, {
69+
id: '4589',
70+
date: '2024-03-09T14:10:00',
71+
email: 'isabella.clark@example.com',
72+
amount: 438
73+
}, {
74+
id: '4588',
75+
date: '2024-03-09T12:05:00',
76+
email: 'mason.rodriguez@example.com',
77+
amount: 583
78+
}, {
79+
id: '4587',
80+
date: '2024-03-09T10:30:00',
81+
email: 'sophia.lee@example.com',
82+
amount: 347
83+
}, {
84+
id: '4586',
85+
date: '2024-03-09T08:15:00',
86+
email: 'ethan.walker@example.com',
87+
amount: 692
88+
}, {
89+
id: '4585',
90+
date: '2024-03-08T23:40:00',
91+
email: 'amelia.hall@example.com',
92+
amount: 419
93+
}, {
94+
id: '4584',
95+
date: '2024-03-08T21:25:00',
96+
email: 'oliver.young@example.com',
97+
amount: 563
98+
}, {
99+
id: '4583',
100+
date: '2024-03-08T19:50:00',
101+
email: 'aria.king@example.com',
102+
amount: 328
103+
}, {
104+
id: '4582',
105+
date: '2024-03-08T17:35:00',
106+
email: 'henry.wright@example.com',
107+
amount: 647
108+
}, {
109+
id: '4581',
110+
date: '2024-03-08T15:20:00',
111+
email: 'luna.lopez@example.com',
112+
amount: 482
113+
}])
114+
const columns: TableColumn<Payment>[] = [{
115+
accessorKey: 'id',
116+
header: '#',
117+
cell: ({ row }) => `#${row.getValue('id')}`
118+
}, {
119+
accessorKey: 'date',
120+
header: 'Date',
121+
cell: ({ row }) => {
122+
return new Date(row.getValue('date')).toLocaleString('en-US', {
123+
day: 'numeric',
124+
month: 'short',
125+
hour: '2-digit',
126+
minute: '2-digit',
127+
hour12: false
128+
})
129+
}
130+
}, {
131+
accessorKey: 'email',
132+
header: 'Email'
133+
}, {
134+
accessorKey: 'amount',
135+
header: () => h('div', { class: 'text-right' }, 'Amount'),
136+
cell: ({ row }) => {
137+
const amount = Number.parseFloat(row.getValue('amount'))
138+
const formatted = new Intl.NumberFormat('en-US', {
139+
style: 'currency',
140+
currency: 'EUR'
141+
}).format(amount)
142+
return h('div', { class: 'text-right font-medium' }, formatted)
143+
}
144+
}]
145+
146+
const pagination = ref({
147+
pageIndex: 0,
148+
pageSize: 5
149+
})
150+
</script>
151+
152+
<template>
153+
<div class="w-full space-y-4 pb-4">
154+
<UTable
155+
ref="table"
156+
v-model:pagination="pagination"
157+
:data="data"
158+
:columns="columns"
159+
:pagination-options="{
160+
getPaginationRowModel: getPaginationRowModel()
161+
}"
162+
class="flex-1"
163+
/>
164+
165+
<div class="flex justify-center border-t border-[var(--ui-border)] pt-4">
166+
<UPagination
167+
:default-page="(table?.tableApi?.getState().pagination.pageIndex || 0) + 1"
168+
:items-per-page="table?.tableApi?.getState().pagination.pageSize"
169+
:total="table?.tableApi?.getFilteredRowModel().rows.length"
170+
@update:page="(p) => table?.tableApi?.setPageIndex(p - 1)"
171+
/>
172+
</div>
173+
</div>
174+
</template>

‎docs/content/3.components/table.md

+20-1
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ You can use the `column-pinning` prop to control the pinning state of the column
336336

337337
### With column visibility
338338

339-
You can add use [DropdownMenu](/components/dropdown-menu) component to toggle the visibility of the columns using the TanStack Table [Column Visibility APIs](https://tanstack.com/table/latest/docs/api/features/column-visibility).
339+
You can use a [DropdownMenu](/components/dropdown-menu) component to toggle the visibility of the columns using the TanStack Table [Column Visibility APIs](https://tanstack.com/table/latest/docs/api/features/column-visibility).
340340

341341
::component-example
342342
---
@@ -391,6 +391,25 @@ class: '!p-0'
391391
You can use the `global-filter` prop to control the global filter state (can be binded with `v-model`).
392392
::
393393

394+
### With pagination
395+
396+
You can use a [Pagination](/components/pagination) component to control the pagination state using the [Pagination APIs](https://tanstack.com/table/latest/docs/api/features/pagination).
397+
398+
There are different pagination approaches as explained in [Pagination Guide](https://tanstack.com/table/latest/docs/guide/pagination#pagination-guide). In this example, we use client-side pagination so we need to manually pass `getPaginationRowModel()`{lang="ts-type"} function.
399+
400+
::component-example
401+
---
402+
prettier: true
403+
collapse: true
404+
name: 'table-pagination-example'
405+
class: '!p-0'
406+
---
407+
::
408+
409+
::tip
410+
You can use the `pagination` prop to control the pagination state (can be binded with `v-model`).
411+
::
412+
394413
### With fetched data
395414

396415
You can fetch data from an API and use them in the Table.

‎playground/app/pages/components/table.vue

+14-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { h, resolveComponent } from 'vue'
33
import { upperFirst } from 'scule'
44
import type { TableColumn } from '@nuxt/ui'
5+
import { getPaginationRowModel } from '@tanstack/vue-table'
56
67
const UButton = resolveComponent('UButton')
78
const UCheckbox = resolveComponent('UCheckbox')
@@ -269,6 +270,11 @@ const columnPinning = ref({
269270
right: ['actions']
270271
})
271272
273+
const pagination = ref({
274+
pageIndex: 0,
275+
pageSize: 10
276+
})
277+
272278
function randomize() {
273279
data.value = [...data.value].sort(() => Math.random() - 0.5)
274280
}
@@ -322,11 +328,15 @@ onMounted(() => {
322328
:columns="columns"
323329
:column-pinning="columnPinning"
324330
:loading="loading"
325-
sticky
331+
:pagination="pagination"
332+
:pagination-options="{
333+
getPaginationRowModel: getPaginationRowModel()
334+
}"
326335
:ui="{
327336
tr: 'divide-x divide-[var(--ui-border)]'
328337
}"
329-
class="border border-[var(--ui-border-accented)] rounded-[var(--ui-radius)] flex-1"
338+
sticky
339+
class="border border-[var(--ui-border-accented)] rounded-[var(--ui-radius)]"
330340
>
331341
<template #expanded="{ row }">
332342
<pre>{{ row.original }}</pre>
@@ -339,7 +349,7 @@ onMounted(() => {
339349
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
340350
</div>
341351

342-
<!-- <div class="flex items-center gap-1.5">
352+
<div class="flex items-center gap-1.5">
343353
<UButton
344354
color="neutral"
345355
variant="outline"
@@ -356,7 +366,7 @@ onMounted(() => {
356366
>
357367
Next
358368
</UButton>
359-
</div> -->
369+
</div>
360370
</div>
361371
</div>
362372
</template>

‎src/runtime/components/Table.vue

+98-18
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,38 @@
33
import type { Ref } from 'vue'
44
import type { VariantProps } from 'tailwind-variants'
55
import type { AppConfig } from '@nuxt/schema'
6-
import type { RowData } from '@tanstack/table-core'
76
import type {
8-
Row,
7+
CellContext,
98
ColumnDef,
9+
ColumnFiltersOptions,
1010
ColumnFiltersState,
11+
ColumnOrderState,
12+
ColumnPinningOptions,
1113
ColumnPinningState,
12-
RowSelectionState,
13-
SortingState,
14+
ColumnSizingInfoState,
15+
ColumnSizingOptions,
16+
ColumnSizingState,
17+
CoreOptions,
18+
ExpandedOptions,
1419
ExpandedState,
15-
VisibilityState,
20+
FacetedOptions,
1621
GlobalFilterOptions,
17-
ColumnFiltersOptions,
18-
ColumnPinningOptions,
19-
VisibilityOptions,
20-
ExpandedOptions,
21-
SortingOptions,
22+
GroupingOptions,
23+
GroupingState,
24+
HeaderContext,
25+
PaginationOptions,
26+
PaginationState,
27+
Row,
28+
RowData,
29+
RowPinningOptions,
30+
RowPinningState,
2231
RowSelectionOptions,
32+
RowSelectionState,
33+
SortingOptions,
34+
SortingState,
2335
Updater,
24-
CellContext,
25-
HeaderContext
36+
VisibilityOptions,
37+
VisibilityState
2638
} from '@tanstack/vue-table'
2739
import _appConfig from '#build/app.config'
2840
import theme from '#build/ui/table'
@@ -44,13 +56,17 @@ const table = tv({ extend: tv(theme), ...(appConfigTable.ui?.table || {}) })
4456

4557
type TableVariants = VariantProps<typeof table>
4658

47-
export type TableColumn<T> = ColumnDef<T>
59+
export type TableData = RowData
60+
61+
export type TableColumn<T extends TableData, D = unknown> = ColumnDef<T, D>
4862

49-
export interface TableData {
50-
[key: string]: any
63+
export interface TableOptions<T extends TableData> extends Omit<CoreOptions<T>, 'data' | 'columns' | 'getCoreRowModel' | 'state' | 'onStateChange' | 'renderFallbackValue'> {
64+
state?: CoreOptions<T>['state']
65+
onStateChange?: CoreOptions<T>['onStateChange']
66+
renderFallbackValue?: CoreOptions<T>['renderFallbackValue']
5167
}
5268

53-
export interface TableProps<T> {
69+
export interface TableProps<T extends TableData> extends TableOptions<T> {
5470
/**
5571
* The element or component this component should render as.
5672
* @defaultValue 'div'
@@ -83,6 +99,11 @@ export interface TableProps<T> {
8399
* @link [Guide](https://tanstack.com/table/v8/docs/guide/column-pinning)
84100
*/
85101
columnPinningOptions?: Omit<ColumnPinningOptions, 'onColumnPinningChange'>
102+
/**
103+
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/column-sizing#table-options)
104+
* @link [Guide](https://tanstack.com/table/v8/docs/guide/column-sizing)
105+
*/
106+
columnSizingOptions?: Omit<ColumnSizingOptions, 'onColumnSizingChange' | 'onColumnSizingInfoChange'>
86107
/**
87108
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/column-visibility#table-options)
88109
* @link [Guide](https://tanstack.com/table/v8/docs/guide/column-visibility)
@@ -93,6 +114,11 @@ export interface TableProps<T> {
93114
* @link [Guide](https://tanstack.com/table/v8/docs/guide/sorting)
94115
*/
95116
sortingOptions?: Omit<SortingOptions<T>, 'getSortedRowModel' | 'onSortingChange'>
117+
/**
118+
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/grouping#table-options)
119+
* @link [Guide](https://tanstack.com/table/v8/docs/guide/grouping)
120+
*/
121+
groupingOptions?: Omit<GroupingOptions, 'onGroupingChange'>
96122
/**
97123
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/expanding#table-options)
98124
* @link [Guide](https://tanstack.com/table/v8/docs/guide/expanding)
@@ -103,6 +129,21 @@ export interface TableProps<T> {
103129
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
104130
*/
105131
rowSelectionOptions?: Omit<RowSelectionOptions<T>, 'onRowSelectionChange'>
132+
/**
133+
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-pinning#table-options)
134+
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-pinning)
135+
*/
136+
rowPinningOptions?: Omit<RowPinningOptions<T>, 'onRowPinningChange'>
137+
/**
138+
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/pagination#table-options)
139+
* @link [Guide](https://tanstack.com/table/v8/docs/guide/pagination)
140+
*/
141+
paginationOptions?: Omit<PaginationOptions, 'onPaginationChange'>
142+
/**
143+
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/column-faceting#table-options)
144+
* @link [Guide](https://tanstack.com/table/v8/docs/guide/column-faceting)
145+
*/
146+
facetedOptions?: FacetedOptions<T>
106147
class?: any
107148
ui?: Partial<typeof table.slots>
108149
}
@@ -120,9 +161,10 @@ export type TableSlots<T> = {
120161

121162
<script setup lang="ts" generic="T extends TableData">
122163
import { computed } from 'vue'
123-
import { Primitive } from 'reka-ui'
124-
import { FlexRender, getCoreRowModel, getFilteredRowModel, getSortedRowModel, getExpandedRowModel, useVueTable } from '@tanstack/vue-table'
164+
import { Primitive, useForwardProps } from 'reka-ui'
125165
import { upperFirst } from 'scule'
166+
import { FlexRender, getCoreRowModel, getFilteredRowModel, getSortedRowModel, getExpandedRowModel, useVueTable } from '@tanstack/vue-table'
167+
import { reactiveOmit } from '@vueuse/core'
126168
import { useLocale } from '../composables/useLocale'
127169

128170
const props = defineProps<TableProps<T>>()
@@ -142,13 +184,22 @@ const ui = computed(() => table({
142184

143185
const globalFilterState = defineModel<string>('globalFilter', { default: undefined })
144186
const columnFiltersState = defineModel<ColumnFiltersState>('columnFilters', { default: [] })
187+
const columnOrderState = defineModel<ColumnOrderState>('columnOrder', { default: [] })
145188
const columnVisibilityState = defineModel<VisibilityState>('columnVisibility', { default: {} })
146189
const columnPinningState = defineModel<ColumnPinningState>('columnPinning', { default: {} })
190+
const columnSizingState = defineModel<ColumnSizingState>('columnSizing', { default: {} })
191+
const columnSizingInfoState = defineModel<ColumnSizingInfoState>('columnSizingInfo', { default: {} })
147192
const rowSelectionState = defineModel<RowSelectionState>('rowSelection', { default: {} })
193+
const rowPinningState = defineModel<RowPinningState>('rowPinning', { default: {} })
148194
const sortingState = defineModel<SortingState>('sorting', { default: [] })
195+
const groupingState = defineModel<GroupingState>('grouping', { default: [] })
149196
const expandedState = defineModel<ExpandedState>('expanded', { default: {} })
197+
const paginationState = defineModel<PaginationState>('pagination', { default: {} })
198+
199+
const tableProps = useForwardProps(reactiveOmit(props, 'as', 'data', 'columns', 'caption', 'sticky', 'loading', 'loadingColor', 'loadingAnimation', 'class', 'ui'))
150200

151201
const tableApi = useVueTable({
202+
...tableProps,
152203
data,
153204
columns: columns.value,
154205
getCoreRowModel: getCoreRowModel(),
@@ -157,25 +208,39 @@ const tableApi = useVueTable({
157208
...(props.columnFiltersOptions || {}),
158209
getFilteredRowModel: getFilteredRowModel(),
159210
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFiltersState),
211+
onColumnOrderChange: updaterOrValue => valueUpdater(updaterOrValue, columnOrderState),
160212
...(props.visibilityOptions || {}),
161213
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibilityState),
162214
...(props.columnPinningOptions || {}),
163215
onColumnPinningChange: updaterOrValue => valueUpdater(updaterOrValue, columnPinningState),
216+
...(props.columnSizingOptions || {}),
217+
onColumnSizingChange: updaterOrValue => valueUpdater(updaterOrValue, columnSizingState),
218+
onColumnSizingInfoChange: updaterOrValue => valueUpdater(updaterOrValue, columnSizingInfoState),
164219
...(props.rowSelectionOptions || {}),
165220
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelectionState),
221+
...(props.rowPinningOptions || {}),
222+
onRowPinningChange: updaterOrValue => valueUpdater(updaterOrValue, rowPinningState),
166223
...(props.sortingOptions || {}),
167224
getSortedRowModel: getSortedRowModel(),
168225
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sortingState),
226+
...(props.groupingOptions || {}),
227+
onGroupingChange: updaterOrValue => valueUpdater(updaterOrValue, groupingState),
169228
...(props.expandedOptions || {}),
170229
getExpandedRowModel: getExpandedRowModel(),
171230
onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expandedState),
231+
...(props.paginationOptions || {}),
232+
onPaginationChange: updaterOrValue => valueUpdater(updaterOrValue, paginationState),
233+
...(props.facetedOptions || {}),
172234
state: {
173235
get globalFilter() {
174236
return globalFilterState.value
175237
},
176238
get columnFilters() {
177239
return columnFiltersState.value
178240
},
241+
get columnOrder() {
242+
return columnOrderState.value
243+
},
179244
get columnVisibility() {
180245
return columnVisibilityState.value
181246
},
@@ -190,6 +255,21 @@ const tableApi = useVueTable({
190255
},
191256
get sorting() {
192257
return sortingState.value
258+
},
259+
get grouping() {
260+
return groupingState.value
261+
},
262+
get rowPinning() {
263+
return rowPinningState.value
264+
},
265+
get columnSizing() {
266+
return columnSizingState.value
267+
},
268+
get columnSizingInfo() {
269+
return columnSizingInfoState.value
270+
},
271+
get pagination() {
272+
return paginationState.value
193273
}
194274
}
195275
})

0 commit comments

Comments
 (0)
Please sign in to comment.