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>
This commit is contained in:
Muhammad Mahmoud
2025-02-05 23:04:00 +02:00
committed by GitHub
parent c5bb540519
commit 4aa317944e
5 changed files with 312 additions and 31 deletions

View File

@@ -90,8 +90,6 @@ const columns: TableColumn<Payment>[] = [{
}
}]
const table = useTemplateRef('table')
const globalFilter = ref('45')
</script>

View File

@@ -0,0 +1,174 @@
<script setup lang="ts">
import { getPaginationRowModel } from '@tanstack/vue-table'
import type { TableColumn } from '@nuxt/ui'
const table = useTemplateRef('table')
type Payment = {
id: string
date: string
email: string
amount: number
}
const data = ref<Payment[]>([{
id: '4600',
date: '2024-03-11T15:30:00',
email: 'james.anderson@example.com',
amount: 594
}, {
id: '4599',
date: '2024-03-11T10:10:00',
email: 'mia.white@example.com',
amount: 276
}, {
id: '4598',
date: '2024-03-11T08:50:00',
email: 'william.brown@example.com',
amount: 315
}, {
id: '4597',
date: '2024-03-10T19:45:00',
email: 'emma.davis@example.com',
amount: 529
}, {
id: '4596',
date: '2024-03-10T15:55:00',
email: 'ethan.harris@example.com',
amount: 639
}, {
id: '4595',
date: '2024-03-10T13:20:00',
email: 'sophia.miller@example.com',
amount: 428
}, {
id: '4594',
date: '2024-03-10T11:05:00',
email: 'noah.wilson@example.com',
amount: 673
}, {
id: '4593',
date: '2024-03-09T22:15:00',
email: 'olivia.jones@example.com',
amount: 382
}, {
id: '4592',
date: '2024-03-09T20:30:00',
email: 'liam.taylor@example.com',
amount: 547
}, {
id: '4591',
date: '2024-03-09T18:45:00',
email: 'ava.thomas@example.com',
amount: 291
}, {
id: '4590',
date: '2024-03-09T16:20:00',
email: 'lucas.martin@example.com',
amount: 624
}, {
id: '4589',
date: '2024-03-09T14:10:00',
email: 'isabella.clark@example.com',
amount: 438
}, {
id: '4588',
date: '2024-03-09T12:05:00',
email: 'mason.rodriguez@example.com',
amount: 583
}, {
id: '4587',
date: '2024-03-09T10:30:00',
email: 'sophia.lee@example.com',
amount: 347
}, {
id: '4586',
date: '2024-03-09T08:15:00',
email: 'ethan.walker@example.com',
amount: 692
}, {
id: '4585',
date: '2024-03-08T23:40:00',
email: 'amelia.hall@example.com',
amount: 419
}, {
id: '4584',
date: '2024-03-08T21:25:00',
email: 'oliver.young@example.com',
amount: 563
}, {
id: '4583',
date: '2024-03-08T19:50:00',
email: 'aria.king@example.com',
amount: 328
}, {
id: '4582',
date: '2024-03-08T17:35:00',
email: 'henry.wright@example.com',
amount: 647
}, {
id: '4581',
date: '2024-03-08T15:20:00',
email: 'luna.lopez@example.com',
amount: 482
}])
const columns: TableColumn<Payment>[] = [{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
}, {
accessorKey: 'date',
header: 'Date',
cell: ({ row }) => {
return new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
}
}, {
accessorKey: 'email',
header: 'Email'
}, {
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}]
const pagination = ref({
pageIndex: 0,
pageSize: 5
})
</script>
<template>
<div class="w-full space-y-4 pb-4">
<UTable
ref="table"
v-model:pagination="pagination"
:data="data"
:columns="columns"
:pagination-options="{
getPaginationRowModel: getPaginationRowModel()
}"
class="flex-1"
/>
<div class="flex justify-center border-t border-[var(--ui-border)] pt-4">
<UPagination
:default-page="(table?.tableApi?.getState().pagination.pageIndex || 0) + 1"
:items-per-page="table?.tableApi?.getState().pagination.pageSize"
:total="table?.tableApi?.getFilteredRowModel().rows.length"
@update:page="(p) => table?.tableApi?.setPageIndex(p - 1)"
/>
</div>
</div>
</template>

View File

@@ -336,7 +336,7 @@ You can use the `column-pinning` prop to control the pinning state of the column
### With column visibility
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).
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).
::component-example
---
@@ -391,6 +391,25 @@ class: '!p-0'
You can use the `global-filter` prop to control the global filter state (can be binded with `v-model`).
::
### With pagination
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).
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.
::component-example
---
prettier: true
collapse: true
name: 'table-pagination-example'
class: '!p-0'
---
::
::tip
You can use the `pagination` prop to control the pagination state (can be binded with `v-model`).
::
### With fetched data
You can fetch data from an API and use them in the Table.

View File

@@ -2,6 +2,7 @@
import { h, resolveComponent } from 'vue'
import { upperFirst } from 'scule'
import type { TableColumn } from '@nuxt/ui'
import { getPaginationRowModel } from '@tanstack/vue-table'
const UButton = resolveComponent('UButton')
const UCheckbox = resolveComponent('UCheckbox')
@@ -269,6 +270,11 @@ const columnPinning = ref({
right: ['actions']
})
const pagination = ref({
pageIndex: 0,
pageSize: 10
})
function randomize() {
data.value = [...data.value].sort(() => Math.random() - 0.5)
}
@@ -322,11 +328,15 @@ onMounted(() => {
:columns="columns"
:column-pinning="columnPinning"
:loading="loading"
sticky
:pagination="pagination"
:pagination-options="{
getPaginationRowModel: getPaginationRowModel()
}"
:ui="{
tr: 'divide-x divide-[var(--ui-border)]'
}"
class="border border-[var(--ui-border-accented)] rounded-[var(--ui-radius)] flex-1"
sticky
class="border border-[var(--ui-border-accented)] rounded-[var(--ui-radius)]"
>
<template #expanded="{ row }">
<pre>{{ row.original }}</pre>
@@ -339,7 +349,7 @@ onMounted(() => {
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
</div>
<!-- <div class="flex items-center gap-1.5">
<div class="flex items-center gap-1.5">
<UButton
color="neutral"
variant="outline"
@@ -356,7 +366,7 @@ onMounted(() => {
>
Next
</UButton>
</div> -->
</div>
</div>
</div>
</template>

View File

@@ -3,26 +3,38 @@
import type { Ref } from 'vue'
import type { VariantProps } from 'tailwind-variants'
import type { AppConfig } from '@nuxt/schema'
import type { RowData } from '@tanstack/table-core'
import type {
Row,
ColumnDef,
ColumnFiltersState,
ColumnPinningState,
RowSelectionState,
SortingState,
ExpandedState,
VisibilityState,
GlobalFilterOptions,
ColumnFiltersOptions,
ColumnPinningOptions,
VisibilityOptions,
ExpandedOptions,
SortingOptions,
RowSelectionOptions,
Updater,
CellContext,
HeaderContext
ColumnDef,
ColumnFiltersOptions,
ColumnFiltersState,
ColumnOrderState,
ColumnPinningOptions,
ColumnPinningState,
ColumnSizingInfoState,
ColumnSizingOptions,
ColumnSizingState,
CoreOptions,
ExpandedOptions,
ExpandedState,
FacetedOptions,
GlobalFilterOptions,
GroupingOptions,
GroupingState,
HeaderContext,
PaginationOptions,
PaginationState,
Row,
RowData,
RowPinningOptions,
RowPinningState,
RowSelectionOptions,
RowSelectionState,
SortingOptions,
SortingState,
Updater,
VisibilityOptions,
VisibilityState
} from '@tanstack/vue-table'
import _appConfig from '#build/app.config'
import theme from '#build/ui/table'
@@ -44,13 +56,17 @@ const table = tv({ extend: tv(theme), ...(appConfigTable.ui?.table || {}) })
type TableVariants = VariantProps<typeof table>
export type TableColumn<T> = ColumnDef<T>
export type TableData = RowData
export interface TableData {
[key: string]: any
export type TableColumn<T extends TableData, D = unknown> = ColumnDef<T, D>
export interface TableOptions<T extends TableData> extends Omit<CoreOptions<T>, 'data' | 'columns' | 'getCoreRowModel' | 'state' | 'onStateChange' | 'renderFallbackValue'> {
state?: CoreOptions<T>['state']
onStateChange?: CoreOptions<T>['onStateChange']
renderFallbackValue?: CoreOptions<T>['renderFallbackValue']
}
export interface TableProps<T> {
export interface TableProps<T extends TableData> extends TableOptions<T> {
/**
* The element or component this component should render as.
* @defaultValue 'div'
@@ -83,6 +99,11 @@ export interface TableProps<T> {
* @link [Guide](https://tanstack.com/table/v8/docs/guide/column-pinning)
*/
columnPinningOptions?: Omit<ColumnPinningOptions, 'onColumnPinningChange'>
/**
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/column-sizing#table-options)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/column-sizing)
*/
columnSizingOptions?: Omit<ColumnSizingOptions, 'onColumnSizingChange' | 'onColumnSizingInfoChange'>
/**
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/column-visibility#table-options)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/column-visibility)
@@ -93,6 +114,11 @@ export interface TableProps<T> {
* @link [Guide](https://tanstack.com/table/v8/docs/guide/sorting)
*/
sortingOptions?: Omit<SortingOptions<T>, 'getSortedRowModel' | 'onSortingChange'>
/**
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/grouping#table-options)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/grouping)
*/
groupingOptions?: Omit<GroupingOptions, 'onGroupingChange'>
/**
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/expanding#table-options)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/expanding)
@@ -103,6 +129,21 @@ export interface TableProps<T> {
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-selection)
*/
rowSelectionOptions?: Omit<RowSelectionOptions<T>, 'onRowSelectionChange'>
/**
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/row-pinning#table-options)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/row-pinning)
*/
rowPinningOptions?: Omit<RowPinningOptions<T>, 'onRowPinningChange'>
/**
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/pagination#table-options)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/pagination)
*/
paginationOptions?: Omit<PaginationOptions, 'onPaginationChange'>
/**
* @link [API Docs](https://tanstack.com/table/v8/docs/api/features/column-faceting#table-options)
* @link [Guide](https://tanstack.com/table/v8/docs/guide/column-faceting)
*/
facetedOptions?: FacetedOptions<T>
class?: any
ui?: Partial<typeof table.slots>
}
@@ -120,9 +161,10 @@ export type TableSlots<T> = {
<script setup lang="ts" generic="T extends TableData">
import { computed } from 'vue'
import { Primitive } from 'reka-ui'
import { FlexRender, getCoreRowModel, getFilteredRowModel, getSortedRowModel, getExpandedRowModel, useVueTable } from '@tanstack/vue-table'
import { Primitive, useForwardProps } from 'reka-ui'
import { upperFirst } from 'scule'
import { FlexRender, getCoreRowModel, getFilteredRowModel, getSortedRowModel, getExpandedRowModel, useVueTable } from '@tanstack/vue-table'
import { reactiveOmit } from '@vueuse/core'
import { useLocale } from '../composables/useLocale'
const props = defineProps<TableProps<T>>()
@@ -142,13 +184,22 @@ const ui = computed(() => table({
const globalFilterState = defineModel<string>('globalFilter', { default: undefined })
const columnFiltersState = defineModel<ColumnFiltersState>('columnFilters', { default: [] })
const columnOrderState = defineModel<ColumnOrderState>('columnOrder', { default: [] })
const columnVisibilityState = defineModel<VisibilityState>('columnVisibility', { default: {} })
const columnPinningState = defineModel<ColumnPinningState>('columnPinning', { default: {} })
const columnSizingState = defineModel<ColumnSizingState>('columnSizing', { default: {} })
const columnSizingInfoState = defineModel<ColumnSizingInfoState>('columnSizingInfo', { default: {} })
const rowSelectionState = defineModel<RowSelectionState>('rowSelection', { default: {} })
const rowPinningState = defineModel<RowPinningState>('rowPinning', { default: {} })
const sortingState = defineModel<SortingState>('sorting', { default: [] })
const groupingState = defineModel<GroupingState>('grouping', { default: [] })
const expandedState = defineModel<ExpandedState>('expanded', { default: {} })
const paginationState = defineModel<PaginationState>('pagination', { default: {} })
const tableProps = useForwardProps(reactiveOmit(props, 'as', 'data', 'columns', 'caption', 'sticky', 'loading', 'loadingColor', 'loadingAnimation', 'class', 'ui'))
const tableApi = useVueTable({
...tableProps,
data,
columns: columns.value,
getCoreRowModel: getCoreRowModel(),
@@ -157,18 +208,29 @@ const tableApi = useVueTable({
...(props.columnFiltersOptions || {}),
getFilteredRowModel: getFilteredRowModel(),
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFiltersState),
onColumnOrderChange: updaterOrValue => valueUpdater(updaterOrValue, columnOrderState),
...(props.visibilityOptions || {}),
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibilityState),
...(props.columnPinningOptions || {}),
onColumnPinningChange: updaterOrValue => valueUpdater(updaterOrValue, columnPinningState),
...(props.columnSizingOptions || {}),
onColumnSizingChange: updaterOrValue => valueUpdater(updaterOrValue, columnSizingState),
onColumnSizingInfoChange: updaterOrValue => valueUpdater(updaterOrValue, columnSizingInfoState),
...(props.rowSelectionOptions || {}),
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelectionState),
...(props.rowPinningOptions || {}),
onRowPinningChange: updaterOrValue => valueUpdater(updaterOrValue, rowPinningState),
...(props.sortingOptions || {}),
getSortedRowModel: getSortedRowModel(),
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sortingState),
...(props.groupingOptions || {}),
onGroupingChange: updaterOrValue => valueUpdater(updaterOrValue, groupingState),
...(props.expandedOptions || {}),
getExpandedRowModel: getExpandedRowModel(),
onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expandedState),
...(props.paginationOptions || {}),
onPaginationChange: updaterOrValue => valueUpdater(updaterOrValue, paginationState),
...(props.facetedOptions || {}),
state: {
get globalFilter() {
return globalFilterState.value
@@ -176,6 +238,9 @@ const tableApi = useVueTable({
get columnFilters() {
return columnFiltersState.value
},
get columnOrder() {
return columnOrderState.value
},
get columnVisibility() {
return columnVisibilityState.value
},
@@ -190,6 +255,21 @@ const tableApi = useVueTable({
},
get sorting() {
return sortingState.value
},
get grouping() {
return groupingState.value
},
get rowPinning() {
return rowPinningState.value
},
get columnSizing() {
return columnSizingState.value
},
get columnSizingInfo() {
return columnSizingInfoState.value
},
get pagination() {
return paginationState.value
}
}
})