Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,24 @@ const pagination = ref({
pageIndex: 0,
pageSize: 5
})

const globalFilter = ref('')
</script>

<template>
<div class="w-full space-y-4 pb-4">
<div class="flex px-4 py-3.5 border-b border-accented">
<UInput
v-model="globalFilter"
class="max-w-sm"
placeholder="Filter..."
/>
</div>

<UTable
ref="table"
v-model:pagination="pagination"
v-model:global-filter="globalFilter"
:data="data"
:columns="columns"
:pagination-options="{
Expand All @@ -162,9 +173,9 @@ const pagination = ref({
class="flex-1"
/>

<div class="flex justify-center border-t border-default pt-4">
<div class="flex justify-end border-t border-default pt-4 px-4">
<UPagination
:default-page="(table?.tableApi?.getState().pagination.pageIndex || 0) + 1"
: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)"
Expand Down
249 changes: 77 additions & 172 deletions playgrounds/nuxt/app/pages/components/table.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { h, resolveComponent } from 'vue'
import { upperFirst } from 'scule'
import type { TableColumn, TableRow } from '@nuxt/ui'
import type { Column } from '@tanstack/vue-table'
import { getPaginationRowModel } from '@tanstack/vue-table'
import { useClipboard, refDebounced } from '@vueuse/core'

Expand All @@ -17,6 +18,8 @@ type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
firstName: string
lastName: string
email: string
amount: number
}
Expand All @@ -25,137 +28,28 @@ const table = useTemplateRef('table')

const virtualize = ref(false)

const data = ref<Payment[]>([{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
}, {
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276
}, {
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315
}, {
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529
}, {
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}, {
id: '4595',
date: '2024-03-10T13:40:00',
status: 'refunded',
email: 'ava.thomas@example.com',
amount: 428
}, {
id: '4594',
date: '2024-03-10T09:15:00',
status: 'paid',
email: 'michael.wilson@example.com',
amount: 683
}, {
id: '4593',
date: '2024-03-09T20:25:00',
status: 'failed',
email: 'olivia.taylor@example.com',
amount: 947
}, {
id: '4592',
date: '2024-03-09T18:45:00',
status: 'paid',
email: 'benjamin.jackson@example.com',
amount: 851
}, {
id: '4591',
date: '2024-03-09T16:05:00',
status: 'paid',
email: 'sophia.miller@example.com',
amount: 762
}, {
id: '4590',
date: '2024-03-09T14:20:00',
status: 'paid',
email: 'noah.clark@example.com',
amount: 573
}, {
id: '4589',
date: '2024-03-09T11:35:00',
status: 'failed',
email: 'isabella.lee@example.com',
amount: 389
}, {
id: '4588',
date: '2024-03-08T22:50:00',
status: 'refunded',
email: 'liam.walker@example.com',
amount: 701
}, {
id: '4587',
date: '2024-03-08T20:15:00',
status: 'paid',
email: 'charlotte.hall@example.com',
amount: 856
}, {
id: '4586',
date: '2024-03-08T17:40:00',
status: 'paid',
email: 'mason.young@example.com',
amount: 492
}, {
id: '4585',
date: '2024-03-08T14:55:00',
status: 'failed',
email: 'amelia.king@example.com',
amount: 637
}, {
id: '4584',
date: '2024-03-08T12:30:00',
status: 'paid',
email: 'elijah.wright@example.com',
amount: 784
}, {
id: '4583',
date: '2024-03-08T09:45:00',
status: 'refunded',
email: 'harper.scott@example.com',
amount: 345
}, {
id: '4582',
date: '2024-03-07T23:10:00',
status: 'paid',
email: 'evelyn.green@example.com',
amount: 918
}, {
id: '4581',
date: '2024-03-07T20:25:00',
status: 'paid',
email: 'logan.baker@example.com',
amount: 567
}])

const largeData = useState<Payment[]>('largeData', () => Array.from({ length: 1000 }, (_, i) => ({
id: `4580-${i}`,
date: new Date().toISOString(),
status: 'paid',
email: `email-${i}@example.com`,
amount: Math.random() * 1000
})))

const currentID = ref(4601)
const statuses: Payment['status'][] = ['paid', 'failed', 'refunded']
const domains = ['gmail.com', 'outlook.com', 'yahoo.com', 'company.com', 'mail.com']
const firstNames = ['john', 'jane', 'alex', 'sarah', 'mike', 'emma', 'david', 'lisa', 'chris', 'anna']
const lastNames = ['smith', 'johnson', 'williams', 'brown', 'jones', 'garcia', 'miller', 'davis', 'rodriguez', 'martinez']

function makeData(id: number | string, index?: number): Payment {
const i = index ?? Number(id)
const firstName = firstNames[i % firstNames.length]!
const lastName = lastNames[i % lastNames.length]!

return {
id: id.toString(),
date: index !== undefined ? new Date(Date.now() - index * 3600000 * 2).toISOString() : new Date().toISOString(),
firstName,
lastName,
status: statuses[i % statuses.length]!,
email: `${firstName}.${lastName}${i > 100 ? Math.floor(i / 10) : ''}@${domains[i % domains.length]}`,
amount: Math.floor(Math.random() * 900) + 100
}
}

const data = useState<Payment[]>('data', () => Array.from({ length: 1000 }, (_, i) => makeData(45800 - i, i)))

function getRowItems(row: TableRow<Payment>) {
return [{
Expand Down Expand Up @@ -199,11 +93,13 @@ const columns: TableColumn<Payment>[] = [{
'aria-label': 'Select row'
}),
enableSorting: false,
enableHiding: false
enableHiding: false,
size: 32
}, {
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
header: ({ column }) => getPinnedHeader(column, '#', 'left'),
cell: ({ row }) => `#${row.getValue('id')}`,
size: 84
}, {
accessorKey: 'date',
header: 'Date',
Expand All @@ -224,7 +120,7 @@ const columns: TableColumn<Payment>[] = [{
}
}, {
accessorKey: 'status',
header: 'Status',
header: ({ column }) => getPinnedHeader(column, 'Status', 'left'),
cell: ({ row }) => {
const color = ({
paid: 'success' as const,
Expand All @@ -233,7 +129,18 @@ const columns: TableColumn<Payment>[] = [{
})[row.getValue('status') as string]

return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
}
},
size: 102
}, {
accessorKey: 'firstName',
header: ({ column }) => getPinnedHeader(column, 'First Name', 'left'),
cell: ({ row }) => h('div', { class: 'capitalize' }, row.getValue('firstName')),
size: 128
}, {
accessorKey: 'lastName',
header: ({ column }) => getPinnedHeader(column, 'Last Name', 'left'),
cell: ({ row }) => h('div', { class: 'capitalize' }, row.getValue('lastName')),
size: 128
}, {
accessorKey: 'email',
header: ({ column }) => {
Expand All @@ -251,7 +158,7 @@ const columns: TableColumn<Payment>[] = [{
cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email'))
}, {
accessorKey: 'amount',
header: () => h('div', { class: 'text-right' }, 'Amount'),
header: ({ column }) => h('div', { class: 'text-right' }, getPinnedHeader(column, 'Amount', 'right')),
footer: ({ column }) => {
const total = column.getFacetedRowModel().rows.reduce((acc: number, row: TableRow<Payment>) => acc + Number.parseFloat(row.getValue('amount')), 0)

Expand All @@ -271,7 +178,8 @@ const columns: TableColumn<Payment>[] = [{
}).format(amount)

return h('div', { class: 'text-right font-medium' }, formatted)
}
},
size: 117
}, {
id: 'actions',
enableHiding: false,
Expand All @@ -289,33 +197,43 @@ const columns: TableColumn<Payment>[] = [{
'class': 'ms-auto',
'aria-label': 'Actions dropdown'
})))
}
},
size: 64
}]

function getPinnedHeader(column: Column<Payment>, label: string, position: 'left' | 'right') {
const isPinned = column.getIsPinned()

return h(UButton, {
color: 'neutral',
variant: 'ghost',
label,
icon: isPinned ? 'i-lucide-pin-off' : 'i-lucide-pin',
class: '-mx-2.5',
onClick() {
column.pin(isPinned === position ? false : position)
}
})
}

const loading = ref(true)
const columnPinning = ref({
left: ['id'],
left: ['select'],
right: ['actions']
})

const pagination = ref({
pageIndex: 0,
pageSize: 10
pageSize: 50
})

function addElement() {
(virtualize.value ? largeData.value : data.value).unshift({
id: currentID.value.toString(),
date: new Date().toISOString(),
status: 'paid',
email: 'new@example.com',
amount: Math.random() * 1000
})
currentID.value++
const maxId = Math.max(...data.value.map(item => Number(item.id)))
data.value.unshift(makeData(maxId + 1))
}

function randomize() {
(virtualize.value ? largeData : data).value = (virtualize.value ? largeData : data).value.sort(() => Math.random() - 0.5)
data.value.sort(() => Math.random() - 0.5)
}

const rowSelection = ref<Record<string, boolean>>({})
Expand Down Expand Up @@ -405,21 +323,17 @@ onMounted(() => {
<UTable
ref="table"
:key="String(virtualize)"
:data="data"
:columns="columns"
:column-pinning="columnPinning"
:row-selection="rowSelection"
:loading="loading"
:virtualize="virtualize"
v-bind="virtualize ? {
data: largeData
} : {
v-bind="virtualize ? {} : {
data,
pagination,
paginationOptions: {
getPaginationRowModel: getPaginationRowModel()
},
ui: {
tr: 'divide-x divide-default'
}
}"
sticky
Expand Down Expand Up @@ -453,22 +367,13 @@ onMounted(() => {
</div>

<div class="flex items-center gap-1.5">
<UButton
color="neutral"
variant="outline"
:disabled="!table?.tableApi?.getCanPreviousPage()"
@click="table?.tableApi?.previousPage()"
>
Prev
</UButton>
<UButton
color="neutral"
variant="outline"
:disabled="!table?.tableApi?.getCanNextPage()"
@click="table?.tableApi?.nextPage()"
>
Next
</UButton>
<UPagination
:disabled="!!virtualize"
:page="(table?.tableApi?.getState().pagination.pageIndex ?? 0) + 1"
:items-per-page="table?.tableApi?.getState().pagination.pageSize ?? 10"
:total="table?.tableApi?.getFilteredRowModel().rows.length || 0"
@update:page="(p: number) => table?.tableApi?.setPageIndex(p - 1)"
/>
</div>
</div>
</div>
Expand Down
Loading
Loading