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

View File

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