Files
ui/playground/app/pages/components/table.vue
2024-10-23 17:32:30 +02:00

355 lines
8.5 KiB
Vue

<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import { upperFirst } from 'scule'
import type { TableColumn } from '@nuxt/ui'
const UButton = resolveComponent('UButton')
const UCheckbox = resolveComponent('UCheckbox')
const UBadge = resolveComponent('UBadge')
const UDropdownMenu = resolveComponent('UDropdownMenu')
const toast = useToast()
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const table = useTemplateRef('table')
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 columns: TableColumn<Payment>[] = [{
id: 'select',
header: ({ table }) => h(UCheckbox, {
'modelValue': table.getIsAllPageRowsSelected(),
'indeterminate': table.getIsSomePageRowsSelected(),
'onUpdate:modelValue': (value: boolean) => table.toggleAllPageRowsSelected(!!value),
'ariaLabel': 'Select all'
}),
cell: ({ row }) => h(UCheckbox, {
'modelValue': row.getIsSelected(),
'onUpdate:modelValue': (value: boolean) => row.toggleSelected(!!value),
'ariaLabel': 'Select row'
}),
enableSorting: false,
enableHiding: false
}, {
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: 'status',
header: 'Status',
cell: ({ row }) => {
const color = ({
paid: 'success' as const,
failed: 'error' as const,
refunded: 'neutral' as const
})[row.getValue('status') as string]
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
}
}, {
accessorKey: 'email',
header: ({ column }) => {
const isSorted = column.getIsSorted()
return h(UButton, {
color: 'neutral',
variant: 'ghost',
label: 'Email',
icon: isSorted ? (isSorted === 'asc' ? 'i-heroicons-bars-arrow-up-20-solid' : 'i-heroicons-bars-arrow-down-20-solid') : 'i-heroicons-arrows-up-down-20-solid',
class: '-mx-2.5',
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc')
})
},
cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('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)
}
}, {
id: 'actions',
enableHiding: false,
cell: ({ row }) => {
const items = [{
type: 'label',
label: 'Actions'
}, {
label: 'Copy payment ID',
onSelect() {
navigator.clipboard.writeText(row.original.id)
toast.add({
title: 'Payment ID copied to clipboard!',
color: 'success',
icon: 'i-heroicons-check-circle'
})
}
}, {
label: row.getIsExpanded() ? 'Collapse' : 'Expand',
onSelect() {
row.toggleExpanded()
}
}, {
type: 'separator'
}, {
label: 'View customer'
}, {
label: 'View payment details'
}]
return h('div', { class: 'text-right' }, h(UDropdownMenu, {
content: {
align: 'end'
},
items
}, () => h(UButton, {
icon: 'i-heroicons-ellipsis-vertical-20-solid',
color: 'neutral',
variant: 'ghost',
class: 'ml-auto'
})))
}
}]
const loading = ref(true)
const columnPinning = ref({
left: ['id'],
right: ['actions']
})
function randomize() {
data.value = [...data.value].sort(() => Math.random() - 0.5)
}
onMounted(() => {
setTimeout(() => {
loading.value = false
}, 1300)
})
</script>
<template>
<div class="h-full flex flex-col flex-1 gap-4 w-full -my-8">
<div class="flex gap-2 items-center">
<UInput
:model-value="(table?.tableApi?.getColumn('email')?.getFilterValue() as string)"
class="max-w-sm"
placeholder="Filter emails..."
@update:model-value="table?.tableApi?.getColumn('email')?.setFilterValue($event)"
/>
<UButton color="neutral" label="Randomize" @click="randomize" />
<UDropdownMenu
:items="table?.tableApi?.getAllColumns().filter(column => column.getCanHide()).map(column => ({
label: upperFirst(column.id),
type: 'checkbox' as const,
checked: column.getIsVisible(),
onUpdateChecked(checked: boolean) {
table?.tableApi?.getColumn(column.id)?.toggleVisibility(!!checked)
},
onSelect(e?: Event) {
e?.preventDefault()
}
}))"
:content="{ align: 'end' }"
>
<UButton
label="Columns"
color="neutral"
variant="outline"
trailing-icon="i-heroicons-chevron-down-20-solid"
class="ml-auto"
/>
</UDropdownMenu>
</div>
<UTable
ref="table"
:data="data"
:columns="columns"
:column-pinning="columnPinning"
:loading="loading"
sticky
class="border border-[var(--ui-border-accented)] rounded-[var(--ui-radius)] flex-1"
>
<template #expanded="{ row }">
<pre>{{ row.original }}</pre>
</template>
</UTable>
<div class="flex items-center justify-between gap-3">
<div class="text-sm text-[var(--ui-text-muted)]">
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
</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>
</div> -->
</div>
</div>
</template>