Table
Bearbeiten
Die Table-Komponente baut auf der TanStack-Tabelle v8 auf und wird von der useVueTable composable unterstützt, um eine flexible und vollständig typsichere API bereitzustellen.
Es rendert Ihre Daten als Zeilen und Spalten und unterstützt Sortierung, Filterung, Paginierung, Zeilenauswahl, Erweiterung, Gruppierung, Pinning und Virtualisierung, sodass Sie alles von einer einfachen Datentabelle bis zu einem voll ausgestatteten Datenraster erstellen können.
| # | Date | Status | Amount | |||
|---|---|---|---|---|---|---|
| #4600 | Mar 11, 15:30 | paid | james.anderson@example.com | €594.00 | ||
| #4599 | Mar 11, 10:10 | failed | mia.white@example.com | €276.00 | ||
| #4598 | Mar 11, 08:50 | refunded | william.brown@example.com | €315.00 | ||
| #4597 | Mar 10, 19:45 | paid | emma.davis@example.com | €529.00 | ||
| #4596 | Mar 10, 15:55 | paid | ethan.harris@example.com | €639.00 | ||
| #4595 | Mar 10, 13:40 | refunded | ava.thomas@example.com | €428.00 | ||
| #4594 | Mar 10, 09:15 | paid | michael.wilson@example.com | €683.00 | ||
| #4593 | Mar 9, 20:25 | failed | olivia.taylor@example.com | €947.00 | ||
| #4592 | Mar 9, 18:45 | paid | benjamin.jackson@example.com | €851.00 | ||
| #4591 | Mar 9, 16:05 | paid | sophia.miller@example.com | €762.00 | ||
| #4590 | Mar 9, 14:20 | paid | noah.clark@example.com | €573.00 | ||
| #4589 | Mar 9, 11:35 | failed | isabella.lee@example.com | €389.00 | ||
| #4588 | Mar 8, 22:50 | refunded | liam.walker@example.com | €701.00 | ||
| #4587 | Mar 8, 20:15 | paid | charlotte.hall@example.com | €856.00 | ||
| #4586 | Mar 8, 17:40 | paid | mason.young@example.com | €492.00 | ||
| #4585 | Mar 8, 14:55 | failed | amelia.king@example.com | €637.00 | ||
| #4584 | Mar 8, 12:30 | paid | elijah.wright@example.com | €784.00 | ||
| #4583 | Mar 8, 09:45 | refunded | harper.scott@example.com | €345.00 | ||
| #4582 | Mar 7, 23:10 | paid | evelyn.green@example.com | €918.00 | ||
| #4581 | Mar 7, 20:25 | paid | logan.baker@example.com | €567.00 |
Table-Komponente. Überprüfen Sie den Quellcode auf GitHub.Data Bearbeiten
Verwenden Sie die data prop als ein Array von Objekten, die Spalten werden basierend auf den Schlüsseln der Objekte generiert.
| Id | Date | Status | Amount | |
|---|---|---|---|---|
| 4600 | 2024-03-11T15:30:00 | paid | james.anderson@example.com | 594 |
| 4599 | 2024-03-11T10:10:00 | failed | mia.white@example.com | 276 |
| 4598 | 2024-03-11T08:50:00 | refunded | william.brown@example.com | 315 |
| 4597 | 2024-03-10T19:45:00 | paid | emma.davis@example.com | 529 |
| 4596 | 2024-03-10T15:55:00 | paid | ethan.harris@example.com | 639 |
<script setup lang="ts">
const data = ref([
{
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
}
])
</script>
<template>
<UTable :data="data" class="flex-1" />
</template><script setup lang="ts">
import { ref } from 'vue'
const data = ref([
{
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
}
])
</script>
<template>
<UTable :data="data" class="flex-1" />
</template>columns (englisch)
Verwenden Sie die columns-Prop als Array von ColumnDef-Objekten mit Eigenschaften wie
accessorKey:Der Schlüssel des Zeilenobjekts, das beim Extrahieren des Werts für die Spalte verwendet werden soll. {class="text-muted"}header(auf Englisch):Der Header, der für die Spalte angezeigt werden soll. Wenn eine Zeichenfolge übergeben wird, kann sie als Standard für die Spalten-ID verwendet werden. Wenn eine Funktion übergeben wird, wird ein Props-Objekt für die Kopfzeile übergeben und sollte den gerenderten Header-Wert zurückgeben (der genaue Typ hängt vom verwendeten Adapter ab). {class="text-muted"}footer:Die Fußzeile, die für die Spalte angezeigt werden soll. Funktioniert genau wie die Kopfzeile, wird aber unter der Tabelle angezeigt. {class="text-muted"}cell(auf Englisch):Die Zelle, die jede Zeile für die Spalte anzeigen soll. Wenn eine Funktion übergeben wird, wird ihr ein Props-Objekt für die Zelle übergeben und sollte den gerenderten Zellwert zurückgeben (der genaue Typ hängt vom verwendeten Adapter ab). {class="text-muted"}meta:Extra properties for the column. {class="text-muted"} Zusätzliche Eigenschaften für die Spalte.class:(deutsch)td:Klassen, die auf dastd-Element angewendet werden sollen. {class="text-muted"}th:Klassen, die auf dasth-Element angewendet werden sollen. {class="text-muted"}
style(englisch):td:Der Stil, der auf dastd-Element angewendet werden soll. {class="text-muted"}th:Der auf dasth-Element anzuwendende Stil. {class="text-muted"}
colspan: xph10103xcolspan](#with-column-span): #with-column-span): xph10103xcolspan](#with-column-span): xph10102xcolspan](]():td:Das colspan-Attribut, das auf dastd-Element angewendet werden soll. {class="text-muted"}
rowspan: #with-column-span): xph1113xrowspan](](#with-column-span): xph1115x): xph11115x): xph11115x):td:Das rowspan Attribut, das auf dastdElement angewendet werden soll. {class="text-muted"}
Um Komponenten oder andere HTML-Elemente zu rendern, müssen Sie die Vue h function innerhalb der header und cell props. This verwenden unterscheidet sich von anderen Komponenten, die Slots verwenden, sondern ermöglicht mehr Flexibilität.
| # | Date | Status | Amount | |
|---|---|---|---|---|
| #4600 | Mar 11, 15:30 | paid | james.anderson@example.com | €594.00 |
| #4599 | Mar 11, 10:10 | failed | mia.white@example.com | €276.00 |
| #4598 | Mar 11, 08:50 | refunded | william.brown@example.com | €315.00 |
| #4597 | Mar 10, 19:45 | paid | emma.davis@example.com | €529.00 |
| #4596 | Mar 10, 15:55 | paid | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
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: '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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
</script>
<template>
<UTable :data="data" :columns="columns" class="flex-1" />
</template><script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
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: '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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
</script>
<template>
<UTable :data="data" :columns="columns" class="flex-1" />
</template>h rendern, können Sie entweder die Funktion resolveComponent verwenden oder aus #components importieren.Meta ist ein
Verwenden Sie die meta-Prop als Objekt (TableMeta), um Eigenschaften zu übergeben wie:
class(englisch):tr:Die Klassen, die auf dastr-Element angewendet werden sollen. {class="text-muted"}
style(englisch)tr:Der auf dastr-Element anzuwendende Stil. {class="text-muted"}
| ID | Date | Status | Amount | |
|---|---|---|---|---|
| 4600 | Mar 11, 15:30 | paid | james.anderson@example.com | $594.00 |
| 4599 | Mar 11, 10:10 | failed | mia.white@example.com | $276.00 |
| 4598 | Mar 11, 08:50 | refunded | william.brown@example.com | $315.00 |
| 4597 | Mar 10, 19:45 | paid | emma.davis@example.com | $529.00 |
| 4596 | Mar 10, 15:55 | paid | ethan.harris@example.com | $639.00 |
<script setup lang="ts">
import type { TableColumn } from '@nuxt/ui'
import type { TableMeta, Row } from '@tanstack/vue-table'
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: 'ID',
meta: {
class: {
th: 'text-center font-semibold',
td: 'text-center font-mono'
}
}
},
{
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',
meta: {
class: {
th: 'text-center',
td: 'text-center'
}
},
cell: ({ row }) => {
const status = row.getValue('status') as string
const colorMap = {
paid: 'text-success',
failed: 'text-error',
refunded: 'text-warning'
}
return h(
'span',
{ class: `font-semibold capitalize ${colorMap[status as keyof typeof colorMap]}` },
status
)
}
},
{
accessorKey: 'email',
header: 'Email',
meta: {
class: {
th: 'text-left',
td: 'text-left'
}
}
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right font-bold text-primary',
td: 'text-right font-mono'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount)
return h('span', { class: 'font-semibold text-success' }, formatted)
}
}
]
const meta: TableMeta<Payment> = {
class: {
tr: (row: Row<Payment>) => {
if (row.original.status === 'failed') {
return 'bg-error/10'
}
if (row.original.status === 'refunded') {
return 'bg-warning/10'
}
return ''
}
}
}
</script>
<template>
<UTable :data="data" :columns="columns" :meta="meta" class="flex-1" />
</template><script setup lang="ts">
import { ref, h } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { TableMeta, Row } from '@tanstack/vue-table'
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: 'ID',
meta: {
class: {
th: 'text-center font-semibold',
td: 'text-center font-mono'
}
}
},
{
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',
meta: {
class: {
th: 'text-center',
td: 'text-center'
}
},
cell: ({ row }) => {
const status = row.getValue('status') as string
const colorMap = {
paid: 'text-success',
failed: 'text-error',
refunded: 'text-warning'
}
return h(
'span',
{ class: `font-semibold capitalize ${colorMap[status as keyof typeof colorMap]}` },
status
)
}
},
{
accessorKey: 'email',
header: 'Email',
meta: {
class: {
th: 'text-left',
td: 'text-left'
}
}
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right font-bold text-primary',
td: 'text-right font-mono'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount)
return h('span', { class: 'font-semibold text-success' }, formatted)
}
}
]
const meta: TableMeta<Payment> = {
class: {
tr: (row: Row<Payment>) => {
if (row.original.status === 'failed') {
return 'bg-error/10'
}
if (row.original.status === 'refunded') {
return 'bg-warning/10'
}
return ''
}
}
}
</script>
<template>
<UTable :data="data" :columns="columns" :meta="meta" class="flex-1" />
</template>loading (englisch)
Verwenden Sie die loading prop, um einen Ladezustand anzuzeigen, die loading-color prop, um ihre Farbe zu ändern, und die loading-animation prop, um ihre Animation zu ändern.
| Id | Date | Status | Amount | |
|---|---|---|---|---|
| 4600 | 2024-03-11T15:30:00 | paid | james.anderson@example.com | 594 |
| 4599 | 2024-03-11T10:10:00 | failed | mia.white@example.com | 276 |
| 4598 | 2024-03-11T08:50:00 | refunded | william.brown@example.com | 315 |
| 4597 | 2024-03-10T19:45:00 | paid | emma.davis@example.com | 529 |
| 4596 | 2024-03-10T15:55:00 | paid | ethan.harris@example.com | 639 |
<script setup lang="ts">
const data = ref([
{
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
}
])
</script>
<template>
<UTable
loading
loading-color="primary"
loading-animation="carousel"
:data="data"
class="flex-1"
/>
</template><script setup lang="ts">
import { ref } from 'vue'
const data = ref([
{
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
}
])
</script>
<template>
<UTable
loading
loading-color="primary"
loading-animation="carousel"
:data="data"
class="flex-1"
/>
</template>Sticky Bearbeiten
Verwenden Sie die sticky prop, um die Kopf-oder Fußzeile klebrig zu machen.
| Id | Date | Status | Amount | |
|---|---|---|---|---|
| 4600 | 2024-03-11T15:30:00 | paid | james.anderson@example.com | 594 |
| 4599 | 2024-03-11T10:10:00 | failed | mia.white@example.com | 276 |
| 4598 | 2024-03-11T08:50:00 | refunded | william.brown@example.com | 315 |
| 4597 | 2024-03-10T19:45:00 | paid | emma.davis@example.com | 529 |
| 4596 | 2024-03-10T15:55:00 | paid | ethan.harris@example.com | 639 |
| 4595 | 2024-03-10T15:55:00 | paid | ethan.harris@example.com | 639 |
| 4594 | 2024-03-10T15:55:00 | paid | ethan.harris@example.com | 639 |
<script setup lang="ts">
const data = ref([
{
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-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
},
{
id: '4594',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
</script>
<template>
<UTable sticky :data="data" class="flex-1 max-h-[312px]" />
</template><script setup lang="ts">
import { ref } from 'vue'
const data = ref([
{
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-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
},
{
id: '4594',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639
}
])
</script>
<template>
<UTable sticky :data="data" class="flex-1 max-h-[312px]" />
</template>Beispiele
With Zeilenaktionen
Sie können eine neue Spalte hinzufügen, die eine DropdownMenu-Komponente in der cell rendert, um Zeilenaktionen zu rendern.
| # | Date | Status | Amount | ||
|---|---|---|---|---|---|
| #4600 | Mar 11, 15:30 | paid | james.anderson@example.com | €594.00 | |
| #4599 | Mar 11, 10:10 | failed | mia.white@example.com | €276.00 | |
| #4598 | Mar 11, 08:50 | refunded | william.brown@example.com | €315.00 | |
| #4597 | Mar 10, 19:45 | paid | emma.davis@example.com | €529.00 | |
| #4596 | Mar 10, 15:55 | paid | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Row } from '@tanstack/vue-table'
import { useClipboard } from '@vueuse/core'
const UButton = resolveComponent('UButton')
const UBadge = resolveComponent('UBadge')
const UDropdownMenu = resolveComponent('UDropdownMenu')
const toast = useToast()
const { copy } = useClipboard()
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
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: '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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
},
{
id: 'actions',
meta: {
class: {
td: 'text-right'
}
},
cell: ({ row }) => {
return h(
UDropdownMenu,
{
content: {
align: 'end'
},
items: getRowItems(row),
'aria-label': 'Actions dropdown'
},
() =>
h(UButton, {
icon: 'i-lucide-ellipsis-vertical',
color: 'neutral',
variant: 'ghost',
'aria-label': 'Actions dropdown'
})
)
}
}
]
function getRowItems(row: Row<Payment>) {
return [
{
type: 'label',
label: 'Actions'
},
{
label: 'Copy payment ID',
onSelect() {
copy(row.original.id)
toast.add({
title: 'Payment ID copied to clipboard!',
color: 'success',
icon: 'i-lucide-circle-check'
})
}
},
{
type: 'separator'
},
{
label: 'View customer'
},
{
label: 'View payment details'
}
]
}
</script>
<template>
<UTable :data="data" :columns="columns" class="flex-1" />
</template><script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Row } from '@tanstack/vue-table'
import { useClipboard } from '@vueuse/core'
const UButton = resolveComponent('UButton')
const UBadge = resolveComponent('UBadge')
const UDropdownMenu = resolveComponent('UDropdownMenu')
const toast = useToast()
const { copy } = useClipboard()
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
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: '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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
},
{
id: 'actions',
meta: {
class: {
td: 'text-right'
}
},
cell: ({ row }) => {
return h(
UDropdownMenu,
{
content: {
align: 'end'
},
items: getRowItems(row),
'aria-label': 'Actions dropdown'
},
() =>
h(UButton, {
icon: 'i-lucide-ellipsis-vertical',
color: 'neutral',
variant: 'ghost',
'aria-label': 'Actions dropdown'
})
)
}
}
]
function getRowItems(row: Row<Payment>) {
return [
{
type: 'label',
label: 'Actions'
},
{
label: 'Copy payment ID',
onSelect() {
copy(row.original.id)
toast.add({
title: 'Payment ID copied to clipboard!',
color: 'success',
icon: 'i-lucide-circle-check'
})
}
},
{
type: 'separator'
},
{
label: 'View customer'
},
{
label: 'View payment details'
}
]
}
</script>
<template>
<UTable :data="data" :columns="columns" class="flex-1" />
</template>With erweiterbare Zeilen
Sie können eine neue Spalte hinzufügen, die eine Button-Komponente innerhalb der cell rendert, um den erweiterbaren Status einer Zeile mithilfe der TanStack-Tabelle Expanding APIs umzuschalten.
#expanded-slot definieren, um den erweiterten inhalt zu rendern, der die zeile als parameter erhält.| # | Date | Status | Amount | ||
|---|---|---|---|---|---|
| #4600 | Mar 11, 15:30 | paid | james.anderson@example.com | €594.00 | |
| #4599 | Mar 11, 10:10 | failed | mia.white@example.com | €276.00 | |
{
"id": "4599",
"date": "2024-03-11T10:10:00",
"status": "failed",
"email": "mia.white@example.com",
"amount": 276
} | |||||
| #4598 | Mar 11, 08:50 | refunded | william.brown@example.com | €315.00 | |
| #4597 | Mar 10, 19:45 | paid | emma.davis@example.com | €529.00 | |
| #4596 | Mar 10, 15:55 | paid | ethan.harris@example.com | €639.00 | |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UButton = resolveComponent('UButton')
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'expand',
cell: ({ row }) =>
h(UButton, {
color: 'neutral',
variant: 'ghost',
icon: 'i-lucide-chevron-down',
square: true,
'aria-label': 'Expand',
ui: {
leadingIcon: [
'transition-transform',
row.getIsExpanded() ? 'duration-200 rotate-180' : ''
]
},
onClick: () => row.toggleExpanded()
})
},
{
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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const expanded = ref({ 1: true })
</script>
<template>
<UTable
v-model:expanded="expanded"
:data="data"
:columns="columns"
:ui="{ tr: 'data-[expanded=true]:bg-elevated/50' }"
class="flex-1"
>
<template #expanded="{ row }">
<pre>{{ row.original }}</pre>
</template>
</UTable>
</template><script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UButton = resolveComponent('UButton')
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'expand',
cell: ({ row }) =>
h(UButton, {
color: 'neutral',
variant: 'ghost',
icon: 'i-lucide-chevron-down',
square: true,
'aria-label': 'Expand',
ui: {
leadingIcon: [
'transition-transform',
row.getIsExpanded() ? 'duration-200 rotate-180' : ''
]
},
onClick: () => row.toggleExpanded()
})
},
{
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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const expanded = ref({ 1: true })
</script>
<template>
<UTable
v-model:expanded="expanded"
:data="data"
:columns="columns"
:ui="{ tr: 'data-[expanded=true]:bg-elevated/50' }"
class="flex-1"
>
<template #expanded="{ row }">
<pre>{{ row.original }}</pre>
</template>
</UTable>
</template>expanded-prop verwenden, um den erweiterbaren Zustand der Zeilen zu steuern (kann mit v-model gebunden werden).DropdownMenu-Komponente in der actions-Spalte hinzufügen.Mit gruppierten Zeilen
Sie können Zeilen basierend auf einem bestimmten Spaltenwert gruppieren und Unterzeilen über eine Schaltfläche ein-und ausblenden, die der Zelle mithilfe der TanStack-Tabelle Grouping APIs hinzugefügt wurde.
Wichtige Teile
- Fügen Sie
groupingprop mit einem Array von Spalten-IDs hinzu, nach denen Sie gruppieren möchten. - Add
grouping-optionsprop. Es mussgetGroupedRowModelenthalten, Sie können es von@tanstack/vue-tableimportieren oder Ihr eigenes implementieren. - Expand rows via
row.toggleExpanded()method on any cell of the row. Denken Sie daran, dass es auch den#expanded-Slot schaltet. - Verwenden Sie
aggregateFnfür die Spaltendefinition, um zu definieren, wie die Zeilen aggregiert werden sollen. Der *agregatedCell-Renderer bei Spaltendefinition funktioniert nur, wenn es keinencell-Renderer gibt.
| Item | # | Date | Amount | |
|---|---|---|---|---|
Account 1 | 3 records | Mar 11, 15:30 | 3 customers | €1,548.00 |
Account 2 | 2 records | Mar 11, 10:10 | 2 customers | €805.00 |
<script setup lang="ts">
import { resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import { getGroupedRowModel } from '@tanstack/vue-table'
import type { GroupingOptions } from '@tanstack/vue-table'
const UBadge = resolveComponent('UBadge')
type Account = {
id: string
name: string
}
type PaymentStatus = 'paid' | 'failed' | 'refunded'
type Payment = {
id: string
date: string
status: PaymentStatus
email: string
amount: number
account: Account
}
const getColorByStatus = (status: PaymentStatus) => {
return {
paid: 'success',
failed: 'error',
refunded: 'neutral'
}[status]
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594,
account: {
id: '1',
name: 'Account 1'
}
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276,
account: {
id: '2',
name: 'Account 2'
}
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315,
account: {
id: '1',
name: 'Account 1'
}
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529,
account: {
id: '2',
name: 'Account 2'
}
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639,
account: {
id: '1',
name: 'Account 1'
}
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'title',
header: 'Item'
},
{
id: 'account_id',
accessorKey: 'account.id'
},
{
accessorKey: 'id',
header: '#',
cell: ({ row }) =>
row.getIsGrouped() ? `${row.getValue('id')} records` : `#${row.getValue('id')}`,
aggregationFn: 'count'
},
{
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
})
},
aggregationFn: 'max'
},
{
accessorKey: 'status',
header: 'Status'
},
{
accessorKey: 'email',
header: 'Email',
meta: {
class: {
td: 'w-full'
}
},
cell: ({ row }) =>
row.getIsGrouped() ? `${row.getValue('email')} customers` : row.getValue('email'),
aggregationFn: 'uniqueCount'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
},
aggregationFn: 'sum'
}
]
const grouping_options = ref<GroupingOptions>({
groupedColumnMode: 'remove',
getGroupedRowModel: getGroupedRowModel()
})
</script>
<template>
<UTable
:data="data"
:columns="columns"
:grouping="['account_id', 'status']"
:grouping-options="grouping_options"
:ui="{
root: 'min-w-full',
td: 'empty:p-0' // helps with the colspaned row added for expand slot
}"
>
<template #title-cell="{ row }">
<div v-if="row.getIsGrouped()" class="flex items-center">
<span class="inline-block" :style="{ width: `calc(${row.depth} * 1rem)` }" />
<UButton
variant="outline"
color="neutral"
class="mr-2"
size="xs"
:icon="row.getIsExpanded() ? 'i-lucide-minus' : 'i-lucide-plus'"
@click="row.toggleExpanded()"
/>
<strong v-if="row.groupingColumnId === 'account_id'">{{
row.original.account.name
}}</strong>
<UBadge
v-else-if="row.groupingColumnId === 'status'"
:color="getColorByStatus(row.original.status)"
class="capitalize"
variant="subtle"
>
{{ row.original.status }}
</UBadge>
</div>
</template>
</UTable>
</template><script setup lang="ts">
import { resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import { getGroupedRowModel } from '@tanstack/vue-table'
import type { GroupingOptions } from '@tanstack/vue-table'
const UBadge = resolveComponent('UBadge')
type Account = {
id: string
name: string
}
type PaymentStatus = 'paid' | 'failed' | 'refunded'
type Payment = {
id: string
date: string
status: PaymentStatus
email: string
amount: number
account: Account
}
const getColorByStatus = (status: PaymentStatus) => {
return {
paid: 'success',
failed: 'error',
refunded: 'neutral'
}[status]
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594,
account: {
id: '1',
name: 'Account 1'
}
},
{
id: '4599',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276,
account: {
id: '2',
name: 'Account 2'
}
},
{
id: '4598',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315,
account: {
id: '1',
name: 'Account 1'
}
},
{
id: '4597',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 529,
account: {
id: '2',
name: 'Account 2'
}
},
{
id: '4596',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639,
account: {
id: '1',
name: 'Account 1'
}
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'title',
header: 'Item'
},
{
id: 'account_id',
accessorKey: 'account.id'
},
{
accessorKey: 'id',
header: '#',
cell: ({ row }) =>
row.getIsGrouped() ? `${row.getValue('id')} records` : `#${row.getValue('id')}`,
aggregationFn: 'count'
},
{
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
})
},
aggregationFn: 'max'
},
{
accessorKey: 'status',
header: 'Status'
},
{
accessorKey: 'email',
header: 'Email',
meta: {
class: {
td: 'w-full'
}
},
cell: ({ row }) =>
row.getIsGrouped() ? `${row.getValue('email')} customers` : row.getValue('email'),
aggregationFn: 'uniqueCount'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
},
aggregationFn: 'sum'
}
]
const grouping_options = ref<GroupingOptions>({
groupedColumnMode: 'remove',
getGroupedRowModel: getGroupedRowModel()
})
</script>
<template>
<UTable
:data="data"
:columns="columns"
:grouping="['account_id', 'status']"
:grouping-options="grouping_options"
:ui="{
root: 'min-w-full',
td: 'empty:p-0' // helps with the colspaned row added for expand slot
}"
>
<template #title-cell="{ row }">
<div v-if="row.getIsGrouped()" class="flex items-center">
<span class="inline-block" :style="{ width: `calc(${row.depth} * 1rem)` }" />
<UButton
variant="outline"
color="neutral"
class="mr-2"
size="xs"
:icon="row.getIsExpanded() ? 'i-lucide-minus' : 'i-lucide-plus'"
@click="row.toggleExpanded()"
/>
<strong v-if="row.groupingColumnId === 'account_id'">{{
row.original.account.name
}}</strong>
<UBadge
v-else-if="row.groupingColumnId === 'status'"
:color="getColorByStatus(row.original.status)"
class="capitalize"
variant="subtle"
>
{{ row.original.status }}
</UBadge>
</div>
</template>
</UTable>
</template>With Zeilenanheftung: badge{label="4.6+" class="align-text-top"}
Sie können eine Spalte hinzufügen, die eine Button-Komponente innerhalb der cell rendert, um den Pinning-Status einer Zeile mithilfe der TanStack-Tabelle Row Pinning APIs umzuschalten.
| Date | Status | Amount | ||
|---|---|---|---|---|
| Mar 11, 10:10 | failed | mia.white@example.com | €276.00 | |
| Mar 10, 19:45 | paid | emma.davis@example.com | €529.00 | |
| Mar 11, 15:30 | paid | james.anderson@example.com | €594.00 | |
| Mar 11, 08:50 | refunded | william.brown@example.com | €315.00 | |
| Mar 10, 15:55 | paid | ethan.harris@example.com | €639.00 | |
| Mar 10, 13:40 | refunded | ava.thomas@example.com | €428.00 | |
| Mar 10, 09:15 | paid | michael.wilson@example.com | €683.00 | |
| Mar 9, 20:25 | failed | olivia.taylor@example.com | €947.00 | |
| Mar 9, 18:45 | paid | benjamin.jackson@example.com | €851.00 | |
| Mar 9, 16:05 | paid | sophia.miller@example.com | €762.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { RowPinningState } from '@tanstack/table-core'
const UButton = resolveComponent('UButton')
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'pin',
cell: ({ row }) =>
h(UButton, {
icon: 'i-lucide-star',
color: row.getIsPinned() ? 'primary' : 'neutral',
variant: 'ghost',
'aria-label': row.getIsPinned() ? 'Unpin row' : 'Pin row to top',
onClick: () => {
if (row.getIsPinned()) {
row.pin(false)
} else {
row.pin('top')
}
}
})
},
{
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,
timeZone: 'UTC'
})
}
},
{
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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const rowPinning = ref<RowPinningState>({ top: ['4599', '4597'], bottom: [] })
</script>
<template>
<UTable
v-model:row-pinning="rowPinning"
:data="data"
:columns="columns"
:get-row-id="(row: Payment) => row.id"
class="flex-1 h-96"
/>
</template><script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { RowPinningState } from '@tanstack/table-core'
const UButton = resolveComponent('UButton')
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'pin',
cell: ({ row }) =>
h(UButton, {
icon: 'i-lucide-star',
color: row.getIsPinned() ? 'primary' : 'neutral',
variant: 'ghost',
'aria-label': row.getIsPinned() ? 'Unpin row' : 'Pin row to top',
onClick: () => {
if (row.getIsPinned()) {
row.pin(false)
} else {
row.pin('top')
}
}
})
},
{
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,
timeZone: 'UTC'
})
}
},
{
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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const rowPinning = ref<RowPinningState>({ top: ['4599', '4597'], bottom: [] })
</script>
<template>
<UTable
v-model:row-pinning="rowPinning"
:data="data"
:columns="columns"
:get-row-id="(row: Payment) => row.id"
class="flex-1 h-96"
/>
</template>row-pinning-prop verwenden, um den Pinning-Zustand der Zeilen zu steuern (kann mit v-model gebunden werden).With Zeilenauswahl
Sie können eine neue Spalte hinzufügen, die eine Checkbox-Komponente innerhalb der header und cell rendert, um Zeilen mit der TanStack-Tabelle Row Selection APIs auszuwählen.
| Date | Status | Amount | ||
|---|---|---|---|---|
| Mar 11, 15:30 | paid | james.anderson@example.com | €594.00 | |
| Mar 11, 10:10 | failed | mia.white@example.com | €276.00 | |
| Mar 11, 08:50 | refunded | william.brown@example.com | €315.00 | |
| Mar 10, 19:45 | paid | emma.davis@example.com | €529.00 | |
| Mar 10, 15:55 | paid | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UCheckbox = resolveComponent('UCheckbox')
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
},
{
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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const table = useTemplateRef('table')
const rowSelection = ref({ 1: true })
</script>
<template>
<div class="flex-1 w-full">
<UTable ref="table" v-model:row-selection="rowSelection" :data="data" :columns="columns" />
<div class="px-4 py-3.5 border-t border-accented text-sm text-muted">
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
</div>
</div>
</template><script setup lang="ts">
import { h, resolveComponent, ref, useTemplateRef } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UCheckbox = resolveComponent('UCheckbox')
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
},
{
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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const table = useTemplateRef('table')
const rowSelection = ref({ 1: true })
</script>
<template>
<div class="flex-1 w-full">
<UTable ref="table" v-model:row-selection="rowSelection" :data="data" :columns="columns" />
<div class="px-4 py-3.5 border-t border-accented text-sm text-muted">
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
</div>
</div>
</template>row-selection-prop verwenden, um den Selektionsstatus der Zeilen zu steuern (kann mit v-model gebunden werden).With row select event (mit Zeilenauswahl-Ereignis)
Sie können einen @select-Listener hinzufügen, um Zeilen mit oder ohne Checkbox-Spalte anklickbar zu machen.
Event-und TableRow-Instanz als erstes bzw. zweites Argument.| Date | Status | Amount | ||
|---|---|---|---|---|
| Mar 11, 15:30 | paid | james.anderson@example.com | €594.00 | |
| Mar 11, 10:10 | failed | mia.white@example.com | €276.00 | |
| Mar 11, 08:50 | refunded | william.brown@example.com | €315.00 | |
| Mar 10, 19:45 | paid | emma.davis@example.com | €529.00 | |
| Mar 10, 15:55 | paid | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
},
{
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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const table = useTemplateRef('table')
const rowSelection = ref<Record<string, boolean>>({})
function onSelect(e: Event, row: TableRow<Payment>) {
/* If you decide to also select the column you can do this */
row.toggleSelected(!row.getIsSelected())
}
</script>
<template>
<div class="flex w-full flex-1 gap-1">
<div class="flex-1">
<UTable
ref="table"
v-model:row-selection="rowSelection"
:data="data"
:columns="columns"
@select="onSelect"
/>
<div class="px-4 py-3.5 border-t border-accented text-sm text-muted">
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
</div>
</div>
</div>
</template><script setup lang="ts">
import { h, resolveComponent, ref, useTemplateRef } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
},
{
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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const table = useTemplateRef('table')
const rowSelection = ref<Record<string, boolean>>({})
function onSelect(e: Event, row: TableRow<Payment>) {
/* If you decide to also select the column you can do this */
row.toggleSelected(!row.getIsSelected())
}
</script>
<template>
<div class="flex w-full flex-1 gap-1">
<div class="flex-1">
<UTable
ref="table"
v-model:row-selection="rowSelection"
:data="data"
:columns="columns"
@select="onSelect"
/>
<div class="px-4 py-3.5 border-t border-accented text-sm text-muted">
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
</div>
</div>
</div>
</template>With row context menu Veranstaltung
Sie können einen @contextmenu-Listener hinzufügen, um Zeilen mit der rechten Maustaste anklickbar zu machen, und die Tabelle in eine ContextMenu-Komponente einwickeln, um Zeilenaktionen anzuzeigen.
Event-und TableRow-Instanz als erstes bzw. zweites Argument.| # | Date | Status | Amount | ||
|---|---|---|---|---|---|
| #4600 | Mar 11, 15:30 | paid | james.anderson@example.com | €594.00 | |
| #4599 | Mar 11, 10:10 | failed | mia.white@example.com | €276.00 | |
| #4598 | Mar 11, 08:50 | refunded | william.brown@example.com | €315.00 | |
| #4597 | Mar 10, 19:45 | paid | emma.davis@example.com | €529.00 | |
| #4596 | Mar 10, 15:55 | paid | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { ContextMenuItem, TableColumn, TableRow } from '@nuxt/ui'
import { useClipboard } from '@vueuse/core'
const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')
const toast = useToast()
const { copy } = useClipboard()
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
},
{
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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const items = ref<ContextMenuItem[]>([])
function getRowItems(row: TableRow<Payment>) {
return [
{
type: 'label' as const,
label: 'Actions'
},
{
label: 'Copy payment ID',
onSelect() {
copy(row.original.id)
toast.add({
title: 'Payment ID copied to clipboard!',
color: 'success',
icon: 'i-lucide-circle-check'
})
}
},
{
label: row.getIsExpanded() ? 'Collapse' : 'Expand',
onSelect() {
row.toggleExpanded()
}
},
{
type: 'separator' as const
},
{
label: 'View customer'
},
{
label: 'View payment details'
}
]
}
function onContextmenu(_e: Event, row: TableRow<Payment>) {
items.value = getRowItems(row)
}
</script>
<template>
<UContextMenu :items="items">
<UTable :data="data" :columns="columns" class="flex-1" @contextmenu="onContextmenu">
<template #expanded="{ row }">
<pre>{{ row.original }}</pre>
</template>
</UTable>
</UContextMenu>
</template><script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { ContextMenuItem, TableColumn, TableRow } from '@nuxt/ui'
import { useClipboard } from '@vueuse/core'
const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')
const toast = useToast()
const { copy } = useClipboard()
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
},
{
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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const items = ref<ContextMenuItem[]>([])
function getRowItems(row: TableRow<Payment>) {
return [
{
type: 'label' as const,
label: 'Actions'
},
{
label: 'Copy payment ID',
onSelect() {
copy(row.original.id)
toast.add({
title: 'Payment ID copied to clipboard!',
color: 'success',
icon: 'i-lucide-circle-check'
})
}
},
{
label: row.getIsExpanded() ? 'Collapse' : 'Expand',
onSelect() {
row.toggleExpanded()
}
},
{
type: 'separator' as const
},
{
label: 'View customer'
},
{
label: 'View payment details'
}
]
}
function onContextmenu(_e: Event, row: TableRow<Payment>) {
items.value = getRowItems(row)
}
</script>
<template>
<UContextMenu :items="items">
<UTable :data="data" :columns="columns" class="flex-1" @contextmenu="onContextmenu">
<template #expanded="{ row }">
<pre>{{ row.original }}</pre>
</template>
</UTable>
</UContextMenu>
</template>With row hover event (Row Hover-Ereignis)
Sie können einen @hover-Listener hinzufügen, um Zeilen schwebefähig zu machen, und eine Popover-oder eine Tooltip-Komponente verwenden, um beispielsweise Zeilendetails anzuzeigen.
Event-und TableRow-Instanz als erstes bzw. zweites Argument.| # | Date | Status | Amount | ||
|---|---|---|---|---|---|
| #4600 | Mar 11, 15:30 | paid | james.anderson@example.com | €594.00 | |
| #4599 | Mar 11, 10:10 | failed | mia.white@example.com | €276.00 | |
| #4598 | Mar 11, 08:50 | refunded | william.brown@example.com | €315.00 | |
| #4597 | Mar 10, 19:45 | paid | emma.davis@example.com | €529.00 | |
| #4596 | Mar 10, 15:55 | paid | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
},
{
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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const anchor = ref({ x: 0, y: 0 })
const reference = computed(() => ({
getBoundingClientRect: () =>
({
width: 0,
height: 0,
left: anchor.value.x,
right: anchor.value.x,
top: anchor.value.y,
bottom: anchor.value.y,
...anchor.value
}) as DOMRect
}))
const open = ref(false)
const openDebounced = refDebounced(open, 10)
const selectedRow = ref<TableRow<Payment> | null>(null)
function onHover(_e: Event, row: TableRow<Payment> | null) {
selectedRow.value = row
open.value = !!row
}
</script>
<template>
<div class="flex w-full flex-1 gap-1">
<UTable
:data="data"
:columns="columns"
class="flex-1"
@pointermove="
(ev: PointerEvent) => {
anchor.x = ev.clientX
anchor.y = ev.clientY
}
"
@hover="onHover"
/>
<UPopover
:content="{ side: 'top', sideOffset: 16, updatePositionStrategy: 'always' }"
:open="openDebounced"
:reference="reference"
>
<template #content>
<div class="p-4">
{{ selectedRow?.original?.id }}
</div>
</template>
</UPopover>
</div>
</template><script setup lang="ts">
import { refDebounced } from '@vueuse/core'
import { h, resolveComponent, ref, computed } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
const UCheckbox = resolveComponent('UCheckbox')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
},
{
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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const anchor = ref({ x: 0, y: 0 })
const reference = computed(() => ({
getBoundingClientRect: () =>
({
width: 0,
height: 0,
left: anchor.value.x,
right: anchor.value.x,
top: anchor.value.y,
bottom: anchor.value.y,
...anchor.value
}) as DOMRect
}))
const open = ref(false)
const openDebounced = refDebounced(open, 10)
const selectedRow = ref<TableRow<Payment> | null>(null)
function onHover(_e: Event, row: TableRow<Payment> | null) {
selectedRow.value = row
open.value = !!row
}
</script>
<template>
<div class="flex w-full flex-1 gap-1">
<UTable
:data="data"
:columns="columns"
class="flex-1"
@pointermove="
(ev: PointerEvent) => {
anchor.x = ev.clientX
anchor.y = ev.clientY
}
"
@hover="onHover"
/>
<UPopover
:content="{ side: 'top', sideOffset: 16, updatePositionStrategy: 'always' }"
:open="openDebounced"
:reference="reference"
>
<template #content>
<div class="p-4">
{{ selectedRow?.original?.id }}
</div>
</template>
</UPopover>
</div>
</template>With Spalte Fußzeile
Sie können der Spaltendefinition eine footer-Eigenschaft hinzufügen, um eine Fußzeile für die Spalte zu rendern.
| # | Date | Status | Amount | |
|---|---|---|---|---|
| #4600 | Mar 11, 15:30 | paid | james.anderson@example.com | €594.00 |
| #4599 | Mar 11, 10:10 | failed | mia.white@example.com | €276.00 |
| #4598 | Mar 11, 08:50 | refunded | william.brown@example.com | €315.00 |
| #4597 | Mar 10, 19:45 | paid | emma.davis@example.com | €529.00 |
| #4596 | Mar 10, 15:55 | paid | ethan.harris@example.com | €639.00 |
| Total: €2,353.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
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: '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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
footer: ({ column }) => {
const total = column
.getFacetedRowModel()
.rows.reduce(
(acc: number, row: TableRow<Payment>) => acc + Number.parseFloat(row.getValue('amount')),
0
)
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(total)
return `Total: ${formatted}`
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
</script>
<template>
<UTable :data="data" :columns="columns" class="flex-1" />
</template><script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn, TableRow } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
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: '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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
footer: ({ column }) => {
const total = column
.getFacetedRowModel()
.rows.reduce(
(acc: number, row: TableRow<Payment>) => acc + Number.parseFloat(row.getValue('amount')),
0
)
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(total)
return `Total: ${formatted}`
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
</script>
<template>
<UTable :data="data" :columns="columns" class="flex-1" />
</template>With Spaltenspanne
Sie können die Eigenschaften colspan und rowspan in der Spalte meta zum Zusammenführen von Zellen verwenden. Diese Eigenschaften akzeptieren einen statischen Wert oder eine Funktion, die die Zelle empfängt und den Wert span zurückgibt.
rowspan verwenden, müssen Zellen, die von der Spanne einer vorherigen Zeile "absorbiert" werden, visuell ausgeblendet werden. Verwenden Sie die class-Meta mit einer Funktion, die 'hidden' für diese Zellen zurückgibt.| Category | Name | Price | Stock |
|---|---|---|---|
| Electronics | Laptop | $999.00 | 45 |
| Phone | $699.00 | 120 | |
| Tablet | $499.00 | 78 | |
| Clothing | T-Shirt | $29.00 | 200 |
| Jeans | $59.00 | 150 |
<script setup lang="ts">
import type { TableColumn } from '@nuxt/ui'
import type { Cell } from '@tanstack/vue-table'
type Product = {
category: string
name: string
price: number
stock: number
}
const data = ref<Product[]>([
{ category: 'Electronics', name: 'Laptop', price: 999, stock: 45 },
{ category: 'Electronics', name: 'Phone', price: 699, stock: 120 },
{ category: 'Electronics', name: 'Tablet', price: 499, stock: 78 },
{ category: 'Clothing', name: 'T-Shirt', price: 29, stock: 200 },
{ category: 'Clothing', name: 'Jeans', price: 59, stock: 150 }
])
function getCategoryRowSpan(cell: Cell<Product, unknown>) {
const category = cell.row.original.category
const rows = cell.getContext().table.getRowModel().rows
const rowIndex = rows.findIndex((r: (typeof rows)[number]) => r.id === cell.row.id)
if (rowIndex > 0 && rows[rowIndex - 1]!.original.category === category) {
return '1'
}
let span = 1
for (let i = rowIndex + 1; i < rows.length; i++) {
if (rows[i]!.original.category === category) span++
else break
}
return `${span}`
}
function getCategoryClass(cell: Cell<Product, unknown>) {
const category = cell.row.original.category
const rows = cell.getContext().table.getRowModel().rows
const rowIndex = rows.findIndex((r: (typeof rows)[number]) => r.id === cell.row.id)
if (rowIndex > 0 && rows[rowIndex - 1]!.original.category === category) {
return 'hidden'
}
return 'font-medium align-middle border-r border-default'
}
const columns: TableColumn<Product>[] = [
{
accessorKey: 'category',
header: 'Category',
meta: {
rowspan: {
td: getCategoryRowSpan
},
class: {
td: getCategoryClass
}
}
},
{
accessorKey: 'name',
header: 'Name'
},
{
accessorKey: 'price',
header: 'Price',
meta: {
class: {
th: 'text-right',
td: 'text-right'
}
},
cell: ({ row }) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(row.getValue('price'))
}
},
{
accessorKey: 'stock',
header: 'Stock',
meta: {
class: {
th: 'text-right',
td: 'text-right'
}
}
}
]
</script>
<template>
<UTable :data="data" :columns="columns" class="flex-1" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Cell } from '@tanstack/vue-table'
type Product = {
category: string
name: string
price: number
stock: number
}
const data = ref<Product[]>([
{ category: 'Electronics', name: 'Laptop', price: 999, stock: 45 },
{ category: 'Electronics', name: 'Phone', price: 699, stock: 120 },
{ category: 'Electronics', name: 'Tablet', price: 499, stock: 78 },
{ category: 'Clothing', name: 'T-Shirt', price: 29, stock: 200 },
{ category: 'Clothing', name: 'Jeans', price: 59, stock: 150 }
])
function getCategoryRowSpan(cell: Cell<Product, unknown>) {
const category = cell.row.original.category
const rows = cell.getContext().table.getRowModel().rows
const rowIndex = rows.findIndex((r: (typeof rows)[number]) => r.id === cell.row.id)
if (rowIndex > 0 && rows[rowIndex - 1]!.original.category === category) {
return '1'
}
let span = 1
for (let i = rowIndex + 1; i < rows.length; i++) {
if (rows[i]!.original.category === category) span++
else break
}
return `${span}`
}
function getCategoryClass(cell: Cell<Product, unknown>) {
const category = cell.row.original.category
const rows = cell.getContext().table.getRowModel().rows
const rowIndex = rows.findIndex((r: (typeof rows)[number]) => r.id === cell.row.id)
if (rowIndex > 0 && rows[rowIndex - 1]!.original.category === category) {
return 'hidden'
}
return 'font-medium align-middle border-r border-default'
}
const columns: TableColumn<Product>[] = [
{
accessorKey: 'category',
header: 'Category',
meta: {
rowspan: {
td: getCategoryRowSpan
},
class: {
td: getCategoryClass
}
}
},
{
accessorKey: 'name',
header: 'Name'
},
{
accessorKey: 'price',
header: 'Price',
meta: {
class: {
th: 'text-right',
td: 'text-right'
}
},
cell: ({ row }) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(row.getValue('price'))
}
},
{
accessorKey: 'stock',
header: 'Stock',
meta: {
class: {
th: 'text-right',
td: 'text-right'
}
}
}
]
</script>
<template>
<UTable :data="data" :columns="columns" class="flex-1" />
</template>With Spalte Sortierung
Sie können eine Spalte header aktualisieren, um eine Button-Komponente innerhalb der header zu rendern, um den Sortierstatus mithilfe der TanStack-Tabelle Sorting APIs umzuschalten.
Dies setzt aria-sort auf die <th>, so dass Screenreader den aktuellen Sortierstatus der Spalte lesen können: none, ascending oder descending.
| # | Date | Status | Amount | |
|---|---|---|---|---|
| #4597 | Mar 10, 19:45 | paid | emma.davis@example.com | €529.00 |
| #4596 | Mar 10, 15:55 | paid | ethan.harris@example.com | €639.00 |
| #4600 | Mar 11, 15:30 | paid | james.anderson@example.com | €594.00 |
| #4599 | Mar 11, 10:10 | failed | mia.white@example.com | €276.00 |
| #4598 | Mar 11, 08:50 | refunded | william.brown@example.com | €315.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
const UButton = resolveComponent('UButton')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
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: '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',
enableSorting: true,
header: ({ column }) => {
const isSorted = column.getIsSorted()
return h(UButton, {
color: 'neutral',
variant: 'ghost',
label: 'Email',
icon: isSorted
? isSorted === 'asc'
? 'i-lucide-arrow-up-narrow-wide'
: 'i-lucide-arrow-down-wide-narrow'
: 'i-lucide-arrow-up-down',
class: '-mx-2.5',
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc')
})
}
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const sorting = ref([
{
id: 'email',
desc: false
}
])
</script>
<template>
<UTable v-model:sorting="sorting" :data="data" :columns="columns" class="flex-1" />
</template><script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
const UButton = resolveComponent('UButton')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
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: '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',
enableSorting: true,
header: ({ column }) => {
const isSorted = column.getIsSorted()
return h(UButton, {
color: 'neutral',
variant: 'ghost',
label: 'Email',
icon: isSorted
? isSorted === 'asc'
? 'i-lucide-arrow-up-narrow-wide'
: 'i-lucide-arrow-down-wide-narrow'
: 'i-lucide-arrow-up-down',
class: '-mx-2.5',
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc')
})
}
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const sorting = ref([
{
id: 'email',
desc: false
}
])
</script>
<template>
<UTable v-model:sorting="sorting" :data="data" :columns="columns" class="flex-1" />
</template>sorting-prop verwenden, um den Sortierzustand der Spalten zu steuern (kann mit v-model gebunden werden).Sie können auch eine wiederverwendbare Komponente erstellen, um jede Spaltenüberschrift sortierbar zu machen.
| #4596 | Mar 10, 15:55 | paid | ethan.harris@example.com | €639.00 |
| #4597 | Mar 10, 19:45 | paid | emma.davis@example.com | €529.00 |
| #4598 | Mar 11, 08:50 | refunded | william.brown@example.com | €315.00 |
| #4599 | Mar 11, 10:10 | failed | mia.white@example.com | €276.00 |
| #4600 | Mar 11, 15:30 | paid | james.anderson@example.com | €594.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Column } from '@tanstack/vue-table'
const UBadge = resolveComponent('UBadge')
const UButton = resolveComponent('UButton')
const UDropdownMenu = resolveComponent('UDropdownMenu')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
enableSorting: true,
header: ({ column }) => getHeader(column, 'ID'),
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
enableSorting: true,
header: ({ column }) => getHeader(column, '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',
enableSorting: true,
header: ({ column }) => getHeader(column, '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',
enableSorting: true,
header: ({ column }) => getHeader(column, 'Email')
},
{
accessorKey: 'amount',
enableSorting: true,
header: ({ column }) => getHeader(column, 'Amount'),
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
function getHeader(column: Column<Payment>, label: string) {
const isSorted = column.getIsSorted()
return h(
UDropdownMenu,
{
content: {
align: 'start'
},
'aria-label': 'Actions dropdown',
items: [
{
label: 'Asc',
type: 'checkbox',
icon: 'i-lucide-arrow-up-narrow-wide',
checked: isSorted === 'asc',
onSelect: () => {
if (isSorted === 'asc') {
column.clearSorting()
} else {
column.toggleSorting(false)
}
}
},
{
label: 'Desc',
icon: 'i-lucide-arrow-down-wide-narrow',
type: 'checkbox',
checked: isSorted === 'desc',
onSelect: () => {
if (isSorted === 'desc') {
column.clearSorting()
} else {
column.toggleSorting(true)
}
}
}
]
},
() =>
h(UButton, {
color: 'neutral',
variant: 'ghost',
label,
icon: isSorted
? isSorted === 'asc'
? 'i-lucide-arrow-up-narrow-wide'
: 'i-lucide-arrow-down-wide-narrow'
: 'i-lucide-arrow-up-down',
class: '-mx-2.5 data-[state=open]:bg-elevated',
'aria-label': `Sort by ${isSorted === 'asc' ? 'descending' : 'ascending'}`
})
)
}
const sorting = ref([
{
id: 'id',
desc: false
}
])
</script>
<template>
<UTable v-model:sorting="sorting" :data="data" :columns="columns" class="flex-1" />
</template><script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Column } from '@tanstack/vue-table'
const UBadge = resolveComponent('UBadge')
const UButton = resolveComponent('UButton')
const UDropdownMenu = resolveComponent('UDropdownMenu')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
enableSorting: true,
header: ({ column }) => getHeader(column, 'ID'),
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
enableSorting: true,
header: ({ column }) => getHeader(column, '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',
enableSorting: true,
header: ({ column }) => getHeader(column, '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',
enableSorting: true,
header: ({ column }) => getHeader(column, 'Email')
},
{
accessorKey: 'amount',
enableSorting: true,
header: ({ column }) => getHeader(column, 'Amount'),
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
function getHeader(column: Column<Payment>, label: string) {
const isSorted = column.getIsSorted()
return h(
UDropdownMenu,
{
content: {
align: 'start'
},
'aria-label': 'Actions dropdown',
items: [
{
label: 'Asc',
type: 'checkbox',
icon: 'i-lucide-arrow-up-narrow-wide',
checked: isSorted === 'asc',
onSelect: () => {
if (isSorted === 'asc') {
column.clearSorting()
} else {
column.toggleSorting(false)
}
}
},
{
label: 'Desc',
icon: 'i-lucide-arrow-down-wide-narrow',
type: 'checkbox',
checked: isSorted === 'desc',
onSelect: () => {
if (isSorted === 'desc') {
column.clearSorting()
} else {
column.toggleSorting(true)
}
}
}
]
},
() =>
h(UButton, {
color: 'neutral',
variant: 'ghost',
label,
icon: isSorted
? isSorted === 'asc'
? 'i-lucide-arrow-up-narrow-wide'
: 'i-lucide-arrow-down-wide-narrow'
: 'i-lucide-arrow-up-down',
class: '-mx-2.5 data-[state=open]:bg-elevated',
'aria-label': `Sort by ${isSorted === 'asc' ? 'descending' : 'ascending'}`
})
)
}
const sorting = ref([
{
id: 'id',
desc: false
}
])
</script>
<template>
<UTable v-model:sorting="sorting" :data="data" :columns="columns" class="flex-1" />
</template>Mit Column Pinning
Sie können eine Spalte header aktualisieren, um eine Button-Komponente innerhalb der header zu rendern, um den Pinning-Status mithilfe der TanStack-Tabelle Column Pinning APIs umzuschalten.
size-Werte für Ihre Spalten definieren, um eine ordnungsgemäße Handhabung der Spaltenbreite sicherzustellen, insbesondere bei mehreren angehefteten Spalten.| #4600000000000000000000000000000000000000 | 2024-03-11T15:30:00 | paid | james.anderson@example.com | €594,000.00 |
| #4599000000000000000000000000000000000000 | 2024-03-11T10:10:00 | failed | mia.white@example.com | €276,000.00 |
| #4598000000000000000000000000000000000000 | 2024-03-11T08:50:00 | refunded | william.brown@example.com | €315,000.00 |
| #4597000000000000000000000000000000000000 | 2024-03-10T19:45:00 | paid | emma.davis@example.com | €5,290,000.00 |
| #4596000000000000000000000000000000000000 | 2024-03-10T15:55:00 | paid | ethan.harris@example.com | €639,000.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Column } from '@tanstack/vue-table'
const UBadge = resolveComponent('UBadge')
const UButton = resolveComponent('UButton')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600000000000000000000000000000000000000',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594000
},
{
id: '4599000000000000000000000000000000000000',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276000
},
{
id: '4598000000000000000000000000000000000000',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315000
},
{
id: '4597000000000000000000000000000000000000',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 5290000
},
{
id: '4596000000000000000000000000000000000000',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639000
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: ({ column }) => getHeader(column, 'ID', 'left'),
cell: ({ row }) => `#${row.getValue('id')}`,
size: 381
},
{
accessorKey: 'date',
header: ({ column }) => getHeader(column, 'Date', 'left'),
size: 172
},
{
accessorKey: 'status',
header: ({ column }) => getHeader(column, 'Status', 'left'),
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')
)
},
size: 103
},
{
accessorKey: 'email',
header: ({ column }) => getHeader(column, 'Email', 'left'),
size: 232
},
{
accessorKey: 'amount',
header: ({ column }) => getHeader(column, 'Amount', 'right'),
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
},
size: 130
}
]
function getHeader(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 columnPinning = ref({
left: ['id'],
right: ['amount']
})
</script>
<template>
<UTable v-model:column-pinning="columnPinning" :data="data" :columns="columns" class="flex-1" />
</template><script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import type { Column } from '@tanstack/vue-table'
const UBadge = resolveComponent('UBadge')
const UButton = resolveComponent('UButton')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>([
{
id: '4600000000000000000000000000000000000000',
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594000
},
{
id: '4599000000000000000000000000000000000000',
date: '2024-03-11T10:10:00',
status: 'failed',
email: 'mia.white@example.com',
amount: 276000
},
{
id: '4598000000000000000000000000000000000000',
date: '2024-03-11T08:50:00',
status: 'refunded',
email: 'william.brown@example.com',
amount: 315000
},
{
id: '4597000000000000000000000000000000000000',
date: '2024-03-10T19:45:00',
status: 'paid',
email: 'emma.davis@example.com',
amount: 5290000
},
{
id: '4596000000000000000000000000000000000000',
date: '2024-03-10T15:55:00',
status: 'paid',
email: 'ethan.harris@example.com',
amount: 639000
}
])
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: ({ column }) => getHeader(column, 'ID', 'left'),
cell: ({ row }) => `#${row.getValue('id')}`,
size: 381
},
{
accessorKey: 'date',
header: ({ column }) => getHeader(column, 'Date', 'left'),
size: 172
},
{
accessorKey: 'status',
header: ({ column }) => getHeader(column, 'Status', 'left'),
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')
)
},
size: 103
},
{
accessorKey: 'email',
header: ({ column }) => getHeader(column, 'Email', 'left'),
size: 232
},
{
accessorKey: 'amount',
header: ({ column }) => getHeader(column, 'Amount', 'right'),
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
},
size: 130
}
]
function getHeader(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 columnPinning = ref({
left: ['id'],
right: ['amount']
})
</script>
<template>
<UTable v-model:column-pinning="columnPinning" :data="data" :columns="columns" class="flex-1" />
</template>column-pinning-prop verwenden, um den Pinning-Zustand der Spalten zu steuern (kann mit v-model gebunden werden).Mit Spaltensichtbarkeit
Sie können eine DropdownMenu-Komponente verwenden, um die Sichtbarkeit der Spalten mithilfe der TanStack-Tabelle Column Visibility APIs umzuschalten.
| Date | Status | Amount | |
|---|---|---|---|
| Mar 11, 15:30 | paid | james.anderson@example.com | €594.00 |
| Mar 11, 10:10 | failed | mia.white@example.com | €276.00 |
| Mar 11, 08:50 | refunded | william.brown@example.com | €315.00 |
| Mar 10, 19:45 | paid | emma.davis@example.com | €529.00 |
| Mar 10, 15:55 | paid | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import { upperFirst } from 'scule'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
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: '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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const table = useTemplateRef('table')
const columnVisibility = ref({
id: false
})
</script>
<template>
<div class="flex flex-col flex-1 w-full">
<div class="flex justify-end px-4 py-3.5 border-b border-accented">
<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-lucide-chevron-down"
/>
</UDropdownMenu>
</div>
<UTable
ref="table"
v-model:column-visibility="columnVisibility"
:data="data"
:columns="columns"
/>
</div>
</template><script setup lang="ts">
import { h, resolveComponent, ref, useTemplateRef } from 'vue'
import { upperFirst } from 'scule'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
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: '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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const table = useTemplateRef('table')
const columnVisibility = ref({
id: false
})
</script>
<template>
<div class="flex flex-col flex-1 w-full">
<div class="flex justify-end px-4 py-3.5 border-b border-accented">
<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-lucide-chevron-down"
/>
</UDropdownMenu>
</div>
<UTable
ref="table"
v-model:column-visibility="columnVisibility"
:data="data"
:columns="columns"
/>
</div>
</template>column-visibility-prop verwenden, um den Sichtbarkeitsstatus der Spalten zu steuern (kann mit v-model gebunden werden).With Spaltenfilter
Sie können eine Input-Komponente verwenden, um die Zeilen mithilfe der TanStack-Tabelle Column Filtering APIs pro Spalte zu filtern.
| # | Date | Status | Amount | |
|---|---|---|---|---|
| #4600 | Mar 11, 15:30 | paid | james.anderson@example.com | €594.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
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: '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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const table = useTemplateRef('table')
const columnFilters = ref([
{
id: 'email',
value: 'james'
}
])
</script>
<template>
<div class="flex flex-col flex-1 w-full">
<div class="flex px-4 py-3.5 border-b border-accented">
<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)"
/>
</div>
<UTable ref="table" v-model:column-filters="columnFilters" :data="data" :columns="columns" />
</div>
</template><script setup lang="ts">
import { h, resolveComponent, ref, useTemplateRef } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
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: '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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const table = useTemplateRef('table')
const columnFilters = ref([
{
id: 'email',
value: 'james'
}
])
</script>
<template>
<div class="flex flex-col flex-1 w-full">
<div class="flex px-4 py-3.5 border-b border-accented">
<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)"
/>
</div>
<UTable ref="table" v-model:column-filters="columnFilters" :data="data" :columns="columns" />
</div>
</template>column-filters-prop verwenden, um den Filterstatus der Spalten zu steuern (kann mit v-model gebunden werden).Mit globalem Filter
Sie können eine Input-Komponente verwenden, um die Zeilen mithilfe der TanStack-Tabelle Global Filtering APIs zu filtern.
| # | Date | Status | Amount | |
|---|---|---|---|---|
| #4599 | Mar 11, 10:10 | failed | mia.white@example.com | €276.00 |
| #4598 | Mar 11, 08:50 | refunded | william.brown@example.com | €315.00 |
| #4597 | Mar 10, 19:45 | paid | emma.davis@example.com | €529.00 |
| #4596 | Mar 10, 15:55 | paid | ethan.harris@example.com | €639.00 |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
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: '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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const globalFilter = ref('45')
</script>
<template>
<div class="flex flex-col flex-1 w-full">
<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:global-filter="globalFilter" :data="data" :columns="columns" />
</div>
</template><script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
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
}
])
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: '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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const globalFilter = ref('45')
</script>
<template>
<div class="flex flex-col flex-1 w-full">
<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:global-filter="globalFilter" :data="data" :columns="columns" />
</div>
</template>global-filter-prop verwenden, um den globalen Filterstatus zu steuern (kann mit v-model gebunden werden).mit Paginierung
Sie können eine Pagination-Komponente verwenden, um den Paginierungsstatus mit dem Pagination-APIs zu steuern.
Es gibt verschiedene Paginierungsansätze, wie in Pagination Guide erläutert. In diesem Beispiel verwenden wir die clientseitige Paginierung, sodass wir die getPaginationRowModel()-Funktion manuell übergeben müssen.
| # | Date | Amount | |
|---|---|---|---|
| #4600 | Mar 11, 15:30 | james.anderson@example.com | €594.00 |
| #4599 | Mar 11, 10:10 | mia.white@example.com | €276.00 |
| #4598 | Mar 11, 08:50 | william.brown@example.com | €315.00 |
| #4597 | Mar 10, 19:45 | emma.davis@example.com | €529.00 |
| #4596 | Mar 10, 15:55 | ethan.harris@example.com | €639.00 |
<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: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
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="{
getPaginationRowModel: getPaginationRowModel()
}"
class="flex-1"
/>
<div class="flex justify-end border-t border-default pt-4 px-4">
<UPagination
: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><script setup lang="ts">
import { ref, useTemplateRef } from 'vue'
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: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
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="{
getPaginationRowModel: getPaginationRowModel()
}"
class="flex-1"
/>
<div class="flex justify-end border-t border-default pt-4 px-4">
<UPagination
: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>pagination prop verwenden, um den Paginierungszustand zu steuern (kann mit v-model gebunden werden).Mit abgeholten Daten
Sie können Daten aus einer API abrufen und in der Tabelle verwenden.
| ID | Name | Company | |
|---|---|---|---|
| No data | |||
<script setup lang="ts">
import type { AvatarProps, TableColumn } from '@nuxt/ui'
const UAvatar = resolveComponent('UAvatar')
type User = {
id: number
name: string
username: string
email: string
avatar: AvatarProps
company: { name: string }
}
const { data, status } = useLazyFetch<User[]>('https://jsonplaceholder.typicode.com/users', {
key: 'table-users',
transform: (data) => {
return (
data?.map((user) => ({
...user,
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}`, alt: `${user.name} avatar` }
})) || []
)
},
server: false
})
const columns: TableColumn<User>[] = [
{
accessorKey: 'id',
header: 'ID'
},
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => {
return h('div', { class: 'flex items-center gap-3' }, [
h(UAvatar, {
...row.original.avatar,
loading: 'lazy',
size: 'lg'
}),
h('div', undefined, [
h('p', { class: 'font-medium text-highlighted' }, row.original.name),
h('p', { class: '' }, `@${row.original.username}`)
])
])
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'company',
header: 'Company',
cell: ({ row }) => row.original.company.name
}
]
</script>
<template>
<UTable
:data="data"
:columns="columns"
:loading="status === 'pending' || status === 'idle'"
class="flex-1 h-80"
/>
</template><script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { AvatarProps, TableColumn } from '@nuxt/ui'
const UAvatar = resolveComponent('UAvatar')
type User = {
id: number
name: string
username: string
email: string
avatar: AvatarProps
company: { name: string }
}
const { data, status } = useLazyFetch<User[]>('https://jsonplaceholder.typicode.com/users', {
key: 'table-users',
transform: (data) => {
return (
data?.map((user) => ({
...user,
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}`, alt: `${user.name} avatar` }
})) || []
)
},
server: false
})
const columns: TableColumn<User>[] = [
{
accessorKey: 'id',
header: 'ID'
},
{
accessorKey: 'name',
header: 'Name',
cell: ({ row }) => {
return h('div', { class: 'flex items-center gap-3' }, [
h(UAvatar, {
...row.original.avatar,
loading: 'lazy',
size: 'lg'
}),
h('div', undefined, [
h('p', { class: 'font-medium text-highlighted' }, row.original.name),
h('p', { class: '' }, `@${row.original.username}`)
])
])
}
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'company',
header: 'Company',
cell: ({ row }) => row.original.company.name
}
]
</script>
<template>
<UTable
:data="data"
:columns="columns"
:loading="status === 'pending' || status === 'idle'"
class="flex-1 h-80"
/>
</template>useLazyFetch mit server: false verwendet, um Daten auf dem Client abzurufen, ohne das anfängliche Rendern zu blockieren. Der Ladezustand überprüft sowohl den pending-als auch den idle-Status, um vor und während des Abrufs einen Ladeindikator anzuzeigen.Mit unendlichem Scrollen
Wenn Sie die serverseitige Paginierung verwenden, können Sie das Composable useInfiniteScroll verwenden, um mehr Daten zu laden, wenn der Benutzer scrollt.
| ID | Avatar | First name | Username | |
|---|---|---|---|---|
| No data | ||||
<script setup lang="ts">
import type { TableColumn } from '@nuxt/ui'
import { useInfiniteScroll } from '@vueuse/core'
const UAvatar = resolveComponent('UAvatar')
type User = {
id: number
firstName: string
username: string
email: string
image: string
}
type UserResponse = {
users: User[]
total: number
skip: number
limit: number
}
const skip = ref(0)
const { data, status } = useLazyFetch(
'https://dummyjson.com/users?limit=10&select=firstName,username,email,image',
{
key: 'table-users-infinite-scroll',
params: { skip },
transform: (data?: UserResponse) => {
return data?.users
},
server: false
}
)
const columns: TableColumn<User>[] = [
{
accessorKey: 'id',
header: 'ID'
},
{
accessorKey: 'image',
header: 'Avatar',
cell: ({ row }) => h(UAvatar, { src: row.original.image, loading: 'lazy' })
},
{
accessorKey: 'firstName',
header: 'First name'
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'username',
header: 'Username'
}
]
const users = ref<User[]>([])
watch(data, () => {
users.value = [...users.value, ...(data.value || [])]
})
const table = useTemplateRef('table')
onMounted(() => {
useInfiniteScroll(
table.value?.$el,
() => {
skip.value += 10
},
{
distance: 200,
canLoadMore: () => {
return status.value !== 'pending'
}
}
)
})
</script>
<template>
<UTable
ref="table"
:data="users"
:columns="columns"
:loading="status === 'pending' || status === 'idle'"
sticky
class="flex-1 h-80"
/>
</template><script setup lang="ts">
import { ref, watch, onMounted, h, resolveComponent, useTemplateRef } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import { useInfiniteScroll } from '@vueuse/core'
const UAvatar = resolveComponent('UAvatar')
type User = {
id: number
firstName: string
username: string
email: string
image: string
}
type UserResponse = {
users: User[]
total: number
skip: number
limit: number
}
const skip = ref(0)
const { data, status } = useLazyFetch(
'https://dummyjson.com/users?limit=10&select=firstName,username,email,image',
{
key: 'table-users-infinite-scroll',
params: { skip },
transform: (data?: UserResponse) => {
return data?.users
},
server: false
}
)
const columns: TableColumn<User>[] = [
{
accessorKey: 'id',
header: 'ID'
},
{
accessorKey: 'image',
header: 'Avatar',
cell: ({ row }) => h(UAvatar, { src: row.original.image, loading: 'lazy' })
},
{
accessorKey: 'firstName',
header: 'First name'
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'username',
header: 'Username'
}
]
const users = ref<User[]>([])
watch(data, () => {
users.value = [...users.value, ...(data.value || [])]
})
const table = useTemplateRef('table')
onMounted(() => {
useInfiniteScroll(
table.value?.$el,
() => {
skip.value += 10
},
{
distance: 200,
canLoadMore: () => {
return status.value !== 'pending'
}
}
)
})
</script>
<template>
<UTable
ref="table"
:data="users"
:columns="columns"
:loading="status === 'pending' || status === 'idle'"
sticky
class="flex-1 h-80"
/>
</template>useLazyFetch mit server: false verwendet, um Daten auf dem Client abzurufen, ohne das anfängliche Rendern zu blockieren. Der Ladezustand überprüft sowohl den pending-als auch den idle-Status, um eine Ladeanzeige vor und während des Abrufs anzuzeigen. Zusätzliche Seiten werden geladen, wenn der Benutzer scrollt.Mit Drag and Drop
Sie können die useSortable composable von @vueuse/integrations verwenden, um die Drag & Drop-Funktionalität auf der Tabelle zu aktivieren.
:ui-Prop eine eindeutige Klasse hinzu, um es mit useSortable (z. B. :ui="{ tbody: 'my-table-tbody' }") anzusprechen.| # | Date | Amount | |
|---|---|---|---|
| #4600 | Mar 11, 15:30 | james.anderson@example.com | €594.00 |
| #4599 | Mar 11, 10:10 | mia.white@example.com | €276.00 |
| #4598 | Mar 11, 08:50 | william.brown@example.com | €315.00 |
| #4597 | Mar 10, 19:45 | emma.davis@example.com | €529.00 |
<script setup lang="ts">
import type { TableColumn } from '@nuxt/ui'
import { useSortable } from '@vueuse/integrations/useSortable'
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
}
])
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: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
useSortable('.my-table-tbody', data, {
animation: 150
})
</script>
<template>
<UTable
ref="table"
:data="data"
:columns="columns"
:ui="{
tbody: 'my-table-tbody'
}"
class="flex-1"
/>
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
import { useSortable } from '@vueuse/integrations/useSortable'
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
}
])
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: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
useSortable('.my-table-tbody', data, {
animation: 150
})
</script>
<template>
<UTable
ref="table"
:data="data"
:columns="columns"
:ui="{
tbody: 'my-table-tbody'
}"
class="flex-1"
/>
</template>Virtualisierung: badge{label="4.1+" class="align-text-top"}
Verwenden Sie die virtualize-prop, um die Virtualisierung für große Datensätze als Boolean oder ein Objekt mit Optionen wie { estimateSize: 65, overscan: 12 } zu aktivieren. Sie können auch andere TanStack Virtual options übergeben, um das Virtualisierungsverhalten anzupassen.
| # | Date | Status | Amount | |
|---|---|---|---|---|
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>(
Array(1000)
.fill(0)
.map((_, i) => ({
id: `4600-${i}`,
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
}))
)
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: '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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
</script>
<template>
<UTable sticky virtualize :data="data" :columns="columns" class="flex-1 h-80" />
</template><script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
status: 'paid' | 'failed' | 'refunded'
email: string
amount: number
}
const data = ref<Payment[]>(
Array(1000)
.fill(0)
.map((_, i) => ({
id: `4600-${i}`,
date: '2024-03-11T15:30:00',
status: 'paid',
email: 'james.anderson@example.com',
amount: 594
}))
)
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: '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: 'Email'
},
{
accessorKey: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
</script>
<template>
<UTable sticky virtualize :data="data" :columns="columns" class="flex-1 h-80" />
</template>class="h-[400px]").Mit externem Scrollelement: badge{label="4.10+" class="align-text-top"}
Übergeben Sie eine getScrollElement-Funktion in der virtualize-Prop, um gegen einen Vorfahren-Scroll-Container anstelle des tabelleneigenen Root zu virtualisieren. Setzen Sie scrollMargin auf den Offset der Tabelle vom Start des Scroll-Elements (z. B. die Höhe des Inhalts darüber), so dass ein Header und der Tabellenkörper eine einzige Scrollleiste teilen.
Payments
The title stays put while the wide table scrolls both axes under one scrollbar.
| # | Date | Customer | Method | Status | Amount | |
|---|---|---|---|---|---|---|
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
customer: string
email: string
method: string
status: 'paid' | 'failed' | 'refunded'
amount: number
}
const names = [
'James Anderson',
'Mary Johnson',
'Robert Williams',
'Patricia Brown',
'Michael Davis'
]
const methods = ['Visa', 'Mastercard', 'PayPal', 'Amex']
const statuses = ['paid', 'failed', 'refunded'] as const
const data = ref<Payment[]>(
Array.from({ length: 1000 }, (_, i) => {
const customer = names[i % names.length]!
return {
id: `4600-${i}`,
date: '2024-03-11T15:30:00',
customer,
email: `${customer.toLowerCase().replace(' ', '.')}@example.com`,
method: methods[i % methods.length]!,
status: statuses[i % statuses.length]!,
amount: 100 + ((i * 37) % 900)
}
})
)
// The container owns the scroll; the table virtualizes against it so the header and rows share one scrollbar.
const container = useTemplateRef('container')
const title = useTemplateRef('title')
const getScrollElement = () => container.value
// `scrollMargin` is the table's offset within the scroll element (border-box height of the title above it).
const { height: titleHeight } = useElementSize(title, undefined, { box: 'border-box' })
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) =>
new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
},
{
accessorKey: 'customer',
header: 'Customer'
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'method',
header: 'Method'
},
{
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: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) =>
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(Number.parseFloat(row.getValue('amount')))
}
]
</script>
<template>
<div ref="container" class="w-full h-96 overflow-auto">
<div
ref="title"
class="sticky left-0 z-10 flex items-end justify-between gap-4 p-6 bg-elevated/50"
>
<div>
<h2 class="text-2xl font-bold text-highlighted">Payments</h2>
<p class="text-muted">
The title stays put while the wide table scrolls both axes under one scrollbar.
</p>
</div>
<UBadge color="neutral" variant="subtle" :label="`${data.length} rows`" />
</div>
<UTable
sticky
:data="data"
:columns="columns"
:virtualize="{ scrollMargin: titleHeight, getScrollElement }"
:ui="{ base: 'min-w-[1200px]' }"
/>
</div>
</template><script setup lang="ts">
import { useElementSize } from '@vueuse/core'
import { h, resolveComponent, ref, useTemplateRef } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UBadge = resolveComponent('UBadge')
type Payment = {
id: string
date: string
customer: string
email: string
method: string
status: 'paid' | 'failed' | 'refunded'
amount: number
}
const names = [
'James Anderson',
'Mary Johnson',
'Robert Williams',
'Patricia Brown',
'Michael Davis'
]
const methods = ['Visa', 'Mastercard', 'PayPal', 'Amex']
const statuses = ['paid', 'failed', 'refunded'] as const
const data = ref<Payment[]>(
Array.from({ length: 1000 }, (_, i) => {
const customer = names[i % names.length]!
return {
id: `4600-${i}`,
date: '2024-03-11T15:30:00',
customer,
email: `${customer.toLowerCase().replace(' ', '.')}@example.com`,
method: methods[i % methods.length]!,
status: statuses[i % statuses.length]!,
amount: 100 + ((i * 37) % 900)
}
})
)
// The container owns the scroll; the table virtualizes against it so the header and rows share one scrollbar.
const container = useTemplateRef('container')
const title = useTemplateRef('title')
const getScrollElement = () => container.value
// `scrollMargin` is the table's offset within the scroll element (border-box height of the title above it).
const { height: titleHeight } = useElementSize(title, undefined, { box: 'border-box' })
const columns: TableColumn<Payment>[] = [
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => `#${row.getValue('id')}`
},
{
accessorKey: 'date',
header: 'Date',
cell: ({ row }) =>
new Date(row.getValue('date')).toLocaleString('en-US', {
day: 'numeric',
month: 'short',
hour: '2-digit',
minute: '2-digit',
hour12: false
})
},
{
accessorKey: 'customer',
header: 'Customer'
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'method',
header: 'Method'
},
{
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: 'amount',
header: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) =>
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(Number.parseFloat(row.getValue('amount')))
}
]
</script>
<template>
<div ref="container" class="w-full h-96 overflow-auto">
<div
ref="title"
class="sticky left-0 z-10 flex items-end justify-between gap-4 p-6 bg-elevated/50"
>
<div>
<h2 class="text-2xl font-bold text-highlighted">Payments</h2>
<p class="text-muted">
The title stays put while the wide table scrolls both axes under one scrollbar.
</p>
</div>
<UBadge color="neutral" variant="subtle" :label="`${data.length} rows`" />
</div>
<UTable
sticky
:data="data"
:columns="columns"
:virtualize="{ scrollMargin: titleHeight, getScrollElement }"
:ui="{ base: 'min-w-[1200px]' }"
/>
</div>
</template>overflow der Tabellenwurzel visible und der externe Container besitzt das Scrollen auf beiden Achsen, also geben Sie ihm overflow-auto (nicht nur overflow-y-auto), um breite Tabellen horizontal scrollbar zu halten.Mit Baumdaten
Sie können die get-sub-rows-prop verwenden, um hierarchische (Baum-) Daten in der Tabelle anzuzeigen.
Wenn Ihre Datenobjekte beispielsweise ein children-Array haben, legen Sie :get-sub-rows="row => row.children" so fest, dass erweiterbare Zeilen aktiviert werden.
| # | Date | Amount | ||
|---|---|---|---|---|
4600 | Mar 11, 15:30 | james.anderson@example.com | €594.00 | |
4599 | Mar 11, 10:10 | mia.white@example.com | €276.00 | |
4598 | Mar 11, 08:50 | william.brown@example.com | €315.00 | |
4597 | Mar 10, 19:45 | emma.davis@example.com | €529.00 | |
4589 | Mar 9, 11:35 | isabella.lee@example.com | €389.00 | |
<script setup lang="ts">
import { h, resolveComponent } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UCheckbox = resolveComponent('UCheckbox')
const UButton = resolveComponent('UButton')
type Payment = {
id: string
date: string
email: string
amount: number
children?: Payment[]
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
email: 'james.anderson@example.com',
amount: 594,
children: [
{
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,
children: [
{
id: '4592',
date: '2024-03-09T18:45:00',
email: 'benjamin.jackson@example.com',
amount: 851
},
{
id: '4591',
date: '2024-03-09T16:05:00',
email: 'sophia.miller@example.com',
amount: 762
},
{
id: '4590',
date: '2024-03-09T14:20:00',
email: 'noah.clark@example.com',
amount: 573,
children: [
{
id: '4596',
date: '2024-03-10T15:55:00',
email: 'ethan.harris@example.com',
amount: 639
},
{
id: '4595',
date: '2024-03-10T13:40:00',
email: 'ava.thomas@example.com',
amount: 428
}
]
}
]
}
]
},
{
id: '4589',
date: '2024-03-09T11:35:00',
email: 'isabella.lee@example.com',
amount: 389
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected() ? true : row.getIsSomeSelected() ? 'indeterminate' : false,
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
},
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => {
return h(
'div',
{
style: {
paddingInlineStart: `${row.depth}rem`
},
class: 'flex items-center gap-2'
},
[
h(UButton, {
color: 'neutral',
variant: 'outline',
size: 'xs',
icon: row.getIsExpanded() ? 'i-lucide-minus' : 'i-lucide-plus',
class: !row.getCanExpand() && 'invisible',
ui: {
base: 'p-0 rounded-sm',
leadingIcon: 'size-4'
},
onClick: row.getToggleExpandedHandler()
}),
row.getValue('id') as string
]
)
}
},
{
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: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const expanded = ref({ 0: true })
</script>
<template>
<UTable
v-model:expanded="expanded"
:data="data"
:columns="columns"
:get-sub-rows="(row) => row.children"
class="flex-1"
:ui="{
base: 'border-separate border-spacing-0',
tbody: '[&>tr]:last:[&>td]:border-b-0',
tr: 'group',
td: 'empty:p-0 group-has-[td:not(:empty)]:border-b border-default'
}"
/>
</template><script setup lang="ts">
import { h, resolveComponent, ref } from 'vue'
import type { TableColumn } from '@nuxt/ui'
const UCheckbox = resolveComponent('UCheckbox')
const UButton = resolveComponent('UButton')
type Payment = {
id: string
date: string
email: string
amount: number
children?: Payment[]
}
const data = ref<Payment[]>([
{
id: '4600',
date: '2024-03-11T15:30:00',
email: 'james.anderson@example.com',
amount: 594,
children: [
{
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,
children: [
{
id: '4592',
date: '2024-03-09T18:45:00',
email: 'benjamin.jackson@example.com',
amount: 851
},
{
id: '4591',
date: '2024-03-09T16:05:00',
email: 'sophia.miller@example.com',
amount: 762
},
{
id: '4590',
date: '2024-03-09T14:20:00',
email: 'noah.clark@example.com',
amount: 573,
children: [
{
id: '4596',
date: '2024-03-10T15:55:00',
email: 'ethan.harris@example.com',
amount: 639
},
{
id: '4595',
date: '2024-03-10T13:40:00',
email: 'ava.thomas@example.com',
amount: 428
}
]
}
]
}
]
},
{
id: '4589',
date: '2024-03-09T11:35:00',
email: 'isabella.lee@example.com',
amount: 389
}
])
const columns: TableColumn<Payment>[] = [
{
id: 'select',
header: ({ table }) =>
h(UCheckbox, {
modelValue: table.getIsSomePageRowsSelected()
? 'indeterminate'
: table.getIsAllPageRowsSelected(),
'onUpdate:modelValue': (value: boolean | 'indeterminate') =>
table.toggleAllPageRowsSelected(!!value),
'aria-label': 'Select all'
}),
cell: ({ row }) =>
h(UCheckbox, {
modelValue: row.getIsSelected() ? true : row.getIsSomeSelected() ? 'indeterminate' : false,
'onUpdate:modelValue': (value: boolean | 'indeterminate') => row.toggleSelected(!!value),
'aria-label': 'Select row'
})
},
{
accessorKey: 'id',
header: '#',
cell: ({ row }) => {
return h(
'div',
{
style: {
paddingInlineStart: `${row.depth}rem`
},
class: 'flex items-center gap-2'
},
[
h(UButton, {
color: 'neutral',
variant: 'outline',
size: 'xs',
icon: row.getIsExpanded() ? 'i-lucide-minus' : 'i-lucide-plus',
class: !row.getCanExpand() && 'invisible',
ui: {
base: 'p-0 rounded-sm',
leadingIcon: 'size-4'
},
onClick: row.getToggleExpandedHandler()
}),
row.getValue('id') as string
]
)
}
},
{
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: 'Amount',
meta: {
class: {
th: 'text-right',
td: 'text-right font-medium'
}
},
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
}
}
]
const expanded = ref({ 0: true })
</script>
<template>
<UTable
v-model:expanded="expanded"
:data="data"
:columns="columns"
:get-sub-rows="(row) => row.children"
class="flex-1"
:ui="{
base: 'border-separate border-spacing-0',
tbody: '[&>tr]:last:[&>td]:border-b-0',
tr: 'group',
td: 'empty:p-0 group-has-[td:not(:empty)]:border-b border-default'
}"
/>
</template>Mit Steckplätzen
Sie können Slots verwenden, um die Kopfzeile und die Datenzellen der Tabelle anzupassen.
Verwenden Sie den #<column>-header-Steckplatz, um die Kopfzeile einer Spalte anzupassen. Sie haben Zugriff auf die Eigenschaften column, header und table im Slotbereich.
Verwenden Sie den #<column>-cell-Steckplatz, um die Zelle einer Spalte anzupassen. Sie haben Zugriff auf die Eigenschaften cell, column, getValue, renderValue, row und table im Slotbereich.
| ID | Name | Role | ||
|---|---|---|---|---|
| 1 | Lindsay Walton Front-end Developer | lindsay.walton@example.com | Member | |
| 2 | Courtney Henry Designer | courtney.henry@example.com | Admin | |
| 3 | Tom Cook Director of Product | tom.cook@example.com | Member | |
| 4 | Whitney Francis Copywriter | whitney.francis@example.com | Admin | |
| 5 | Leonard Krasner Senior Designer | leonard.krasner@example.com | Owner | |
| 6 | Floyd Miles Principal Designer | floyd.miles@example.com | Member |
<script setup lang="ts">
import type { TableColumn, DropdownMenuItem } from '@nuxt/ui'
import { useClipboard } from '@vueuse/core'
interface User {
id: number
name: string
position: string
email: string
role: string
}
const toast = useToast()
const { copy } = useClipboard()
const data = ref<User[]>([
{
id: 1,
name: 'Lindsay Walton',
position: 'Front-end Developer',
email: 'lindsay.walton@example.com',
role: 'Member'
},
{
id: 2,
name: 'Courtney Henry',
position: 'Designer',
email: 'courtney.henry@example.com',
role: 'Admin'
},
{
id: 3,
name: 'Tom Cook',
position: 'Director of Product',
email: 'tom.cook@example.com',
role: 'Member'
},
{
id: 4,
name: 'Whitney Francis',
position: 'Copywriter',
email: 'whitney.francis@example.com',
role: 'Admin'
},
{
id: 5,
name: 'Leonard Krasner',
position: 'Senior Designer',
email: 'leonard.krasner@example.com',
role: 'Owner'
},
{
id: 6,
name: 'Floyd Miles',
position: 'Principal Designer',
email: 'floyd.miles@example.com',
role: 'Member'
}
])
const columns: TableColumn<User>[] = [
{
accessorKey: 'id',
header: 'ID'
},
{
accessorKey: 'name',
header: 'Name'
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'role',
header: 'Role'
},
{
id: 'action'
}
]
function getDropdownActions(user: User): DropdownMenuItem[][] {
return [
[
{
label: 'Copy user Id',
icon: 'i-lucide-copy',
onSelect: () => {
copy(user.id.toString())
toast.add({
title: 'User ID copied to clipboard!',
color: 'success',
icon: 'i-lucide-circle-check'
})
}
}
],
[
{
label: 'Edit',
icon: 'i-lucide-edit'
},
{
label: 'Delete',
icon: 'i-lucide-trash',
color: 'error'
}
]
]
}
</script>
<template>
<UTable :data="data" :columns="columns" class="flex-1">
<template #name-cell="{ row }">
<div class="flex items-center gap-3">
<UAvatar
:src="`https://i.pravatar.cc/120?img=${row.original.id}`"
size="lg"
loading="lazy"
:alt="`${row.original.name} avatar`"
/>
<div>
<p class="font-medium text-highlighted">
{{ row.original.name }}
</p>
<p>
{{ row.original.position }}
</p>
</div>
</div>
</template>
<template #action-cell="{ row }">
<UDropdownMenu :items="getDropdownActions(row.original)">
<UButton
icon="i-lucide-ellipsis-vertical"
color="neutral"
variant="ghost"
aria-label="Actions"
/>
</UDropdownMenu>
</template>
</UTable>
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TableColumn, DropdownMenuItem } from '@nuxt/ui'
import { useClipboard } from '@vueuse/core'
interface User {
id: number
name: string
position: string
email: string
role: string
}
const toast = useToast()
const { copy } = useClipboard()
const data = ref<User[]>([
{
id: 1,
name: 'Lindsay Walton',
position: 'Front-end Developer',
email: 'lindsay.walton@example.com',
role: 'Member'
},
{
id: 2,
name: 'Courtney Henry',
position: 'Designer',
email: 'courtney.henry@example.com',
role: 'Admin'
},
{
id: 3,
name: 'Tom Cook',
position: 'Director of Product',
email: 'tom.cook@example.com',
role: 'Member'
},
{
id: 4,
name: 'Whitney Francis',
position: 'Copywriter',
email: 'whitney.francis@example.com',
role: 'Admin'
},
{
id: 5,
name: 'Leonard Krasner',
position: 'Senior Designer',
email: 'leonard.krasner@example.com',
role: 'Owner'
},
{
id: 6,
name: 'Floyd Miles',
position: 'Principal Designer',
email: 'floyd.miles@example.com',
role: 'Member'
}
])
const columns: TableColumn<User>[] = [
{
accessorKey: 'id',
header: 'ID'
},
{
accessorKey: 'name',
header: 'Name'
},
{
accessorKey: 'email',
header: 'Email'
},
{
accessorKey: 'role',
header: 'Role'
},
{
id: 'action'
}
]
function getDropdownActions(user: User): DropdownMenuItem[][] {
return [
[
{
label: 'Copy user Id',
icon: 'i-lucide-copy',
onSelect: () => {
copy(user.id.toString())
toast.add({
title: 'User ID copied to clipboard!',
color: 'success',
icon: 'i-lucide-circle-check'
})
}
}
],
[
{
label: 'Edit',
icon: 'i-lucide-edit'
},
{
label: 'Delete',
icon: 'i-lucide-trash',
color: 'error'
}
]
]
}
</script>
<template>
<UTable :data="data" :columns="columns" class="flex-1">
<template #name-cell="{ row }">
<div class="flex items-center gap-3">
<UAvatar
:src="`https://i.pravatar.cc/120?img=${row.original.id}`"
size="lg"
loading="lazy"
:alt="`${row.original.name} avatar`"
/>
<div>
<p class="font-medium text-highlighted">
{{ row.original.name }}
</p>
<p>
{{ row.original.position }}
</p>
</div>
</div>
</template>
<template #action-cell="{ row }">
<UDropdownMenu :items="getDropdownActions(row.original)">
<UButton
icon="i-lucide-ellipsis-vertical"
color="neutral"
variant="ghost"
aria-label="Actions"
/>
</UDropdownMenu>
</template>
</UTable>
</template>API (englisch)
Props (englisch)
| Prop | Default | Type |
|---|---|---|
as | 'div' | anyThe element or component this component should render as. |
data | T[] | |
columns | TableColumn<T, unknown>[] | |
caption | string | |
meta | TableMeta<T>You can pass any object to | |
virtualize | false | boolean | (Partial<Omit<VirtualizerOptions<Element, Element>, "count" | "estimateSize" | "overscan">> & { getScrollElement?: (() => Element | null) ; overscan?: number | undefined; estimateSize?: number | ((index: number) => number) | undefined; }) | undefinedEnable virtualization for large datasets. Note: row pinning is not supported when virtualization is enabled. |
empty | t('table.noData') | stringThe text to display when the table is empty. |
sticky | false | boolean | "header" | "footer" Whether the table should have a sticky header or footer. True for both, 'header' for header only, 'footer' for footer only. |
loading | boolean Whether the table should be in loading state. | |
loadingColor | 'primary' | "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral" |
loadingAnimation | 'carousel' | "carousel" | "carousel-inverse" | "swing" | "elastic" |
watchOptions | { deep: true } | WatchOptions<boolean>Use the |
globalFilterOptions | Omit<GlobalFilterOptions<T>, "onGlobalFilterChange"> | |
columnFiltersOptions | Omit<ColumnFiltersOptions<T>, "getFilteredRowModel" | "onColumnFiltersChange"> | |
columnPinningOptions | Omit<ColumnPinningOptions, "onColumnPinningChange"> | |
columnSizingOptions | Omit<ColumnSizingOptions, "onColumnSizingChange" | "onColumnSizingInfoChange"> | |
visibilityOptions | Omit<VisibilityOptions, "onColumnVisibilityChange"> | |
sortingOptions | Omit<SortingOptions<T>, "getSortedRowModel" | "onSortingChange"> | |
groupingOptions | Omit<GroupingOptions, "onGroupingChange"> | |
expandedOptions | Omit<ExpandedOptions<T>, "getExpandedRowModel" | "onExpandedChange"> | |
rowSelectionOptions | Omit<RowSelectionOptions<T>, "onRowSelectionChange"> | |
rowPinningOptions | Omit<RowPinningOptions<T>, "onRowPinningChange"> | |
paginationOptions | Omit<PaginationOptions, "onPaginationChange"> | |
facetedOptions | FacetedOptions<T> | |
onSelect | (e: Event, row: TableRow<T>): void | |
onHover | (e: Event, row: TableRow<T> | null): void | |
onContextmenu | (e: Event, row: TableRow<T>): void | ((e: Event, row: TableRow<T>) => void)[] | |
state | Partial<TableState> | |
onStateChange | (updater: Updater<TableState>): void | |
renderFallbackValue | any | |
_features | TableFeature<any>[]An array of extra features that you can add to the table instance. | |
autoResetAll | boolean Set this option to override any of the | |
debugAll | boolean Set this option to | |
debugCells | boolean Set this option to | |
debugColumns | boolean Set this option to | |
debugHeaders | boolean Set this option to | |
debugRows | boolean Set this option to | |
debugTable | boolean Set this option to | |
defaultColumn | Partial<ColumnDefBase<T, unknown> & StringHeaderIdentifier> | Partial<ColumnDefBase<T, unknown> & IdIdentifier<T, unknown>> | Partial<GroupColumnDefBase<T, unknown> & StringHeaderIdentifier> | Partial<GroupColumnDefBase<T, unknown> & IdIdentifier<T, unknown>> | Partial<AccessorKeyColumnDefBase<T, unknown> & Partial<StringHeaderIdentifier>> | Partial<AccessorKeyColumnDefBase<T, unknown> & Partial<IdIdentifier<T, unknown>>> | Partial<AccessorFnColumnDefBase<T, unknown> & StringHeaderIdentifier> | Partial<AccessorFnColumnDefBase<T, unknown> & IdIdentifier<T, unknown>>Default column options to use for all column defs supplied to the table. | |
getRowId | (originalRow: T, index: number, parent?: Row<T> | undefined): stringThis optional function is used to derive a unique ID for any given row. If not provided the rows index is used (nested rows join together with | |
getSubRows | (originalRow: T, index: number): T[] This optional function is used to access the sub rows for any given row. If you are using nested rows, you will need to use this function to return the sub rows object (or undefined) from the row. | |
initialState | InitialTableStateUse this option to optionally pass initial state to the table. This state will be used when resetting various table states either automatically by the table (eg. Table state will not be reset when this object changes, which also means that the initial state object does not need to be stable. | |
mergeOptions | (defaultOptions: TableOptions<T>, options: Partial<TableOptions<T>>): TableOptions<T>This option is used to optionally implement the merging of table options. | |
cellpadding | string | number | |
cellspacing | string | number | |
summary | string | |
width | string | number | |
globalFilter | string | |
columnFilters | ColumnFiltersState | |
columnOrder | ColumnOrderState | |
columnVisibility | VisibilityState | |
columnPinning | ColumnPinningState | |
columnSizing | ColumnSizingState | |
columnSizingInfo | ColumnSizingInfoState | |
rowSelection | RowSelectionState | |
rowPinning | RowPinningState | |
sorting | SortingState | |
grouping | GroupingState | |
expanded | true | Record<string, boolean> | |
pagination | PaginationState | |
ui | { root?: SlotClass; base?: SlotClass; caption?: SlotClass; thead?: SlotClass; tbody?: SlotClass; tfoot?: SlotClass; tr?: SlotClass; th?: SlotClass; td?: SlotClass; separator?: SlotClass; empty?: SlotClass; loading?: SlotClass; } |
Slots Bearbeiten
| Slot | Type |
|---|---|
expanded | { row: Row<T>; } |
empty | {} |
loading | {} |
caption | {} |
body-top | {} |
body-bottom | {} |
Expose (Englisch)
Sie können auf die typisierte Komponenteninstanz mit useTemplateRef zugreifen.
<script setup lang="ts">
const table = useTemplateRef('table')
</script>
<template>
<UTable ref="table" />
</template>
Dies gibt Ihnen Zugang zu den folgenden:
| Vorname | Typen |
|---|---|
tableRef nicht | Ref<HTMLTableElement | null> (englisch) |
tableApi nicht vorhanden | Table |
Theme Bearbeiten
export default defineAppConfig({
ui: {
table: {
slots: {
root: 'relative overflow-auto outline-primary/25 focus-visible:outline-3',
base: 'min-w-full overflow-clip',
caption: 'sr-only',
thead: 'relative',
tbody: 'isolate [&>tr]:data-[selectable=true]:hover:bg-elevated/50 [&>tr]:data-[selectable=true]:outline-primary/25 [&>tr]:data-[selectable=true]:focus-visible:outline-3 [&>tr]:data-[selectable=true]:-outline-offset-3 divide-y divide-default',
tfoot: 'relative',
tr: 'data-[selected=true]:bg-elevated/50',
th: 'px-4 py-3.5 text-sm text-highlighted text-start font-semibold [&:has([role=checkbox])]:pe-0',
td: 'p-4 text-sm text-muted whitespace-nowrap [&:has([role=checkbox])]:pe-0',
separator: 'absolute z-1 start-0 w-full h-px bg-(--ui-border-accented)',
empty: 'py-6 text-center text-sm text-muted',
loading: 'py-6 text-center'
},
variants: {
pinned: {
true: {
th: 'sticky bg-default/75 z-1',
td: 'sticky bg-default/75 z-1'
}
},
sticky: {
true: {
thead: 'sticky top-0 inset-x-0 bg-default/75 backdrop-blur-sm z-1',
tfoot: 'sticky bottom-0 inset-x-0 bg-default/75 backdrop-blur-sm z-1'
},
header: {
thead: 'sticky top-0 inset-x-0 bg-default/75 backdrop-blur-sm z-1'
},
footer: {
tfoot: 'sticky bottom-0 inset-x-0 bg-default/75 backdrop-blur-sm z-1'
}
},
loading: {
true: {
thead: 'after:absolute after:z-1 after:h-px motion-reduce:after:inset-x-0 motion-reduce:after:animate-pulse'
}
},
externalScroll: {
true: {
root: 'overflow-visible'
}
},
loadingAnimation: {
carousel: '',
'carousel-inverse': '',
swing: '',
elastic: ''
},
loadingColor: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
}
},
compoundVariants: [
{
loading: true,
loadingColor: 'primary',
class: {
thead: 'after:bg-primary'
}
},
{
loading: true,
loadingColor: 'neutral',
class: {
thead: 'after:bg-inverted'
}
},
{
loading: true,
loadingAnimation: 'carousel',
class: {
thead: 'motion-safe:after:animate-[carousel_2s_linear_infinite] motion-safe:rtl:after:animate-[carousel-rtl_2s_linear_infinite]'
}
},
{
loading: true,
loadingAnimation: 'carousel-inverse',
class: {
thead: 'motion-safe:after:animate-[carousel-inverse_2s_linear_infinite] motion-safe:rtl:after:animate-[carousel-inverse-rtl_2s_linear_infinite]'
}
},
{
loading: true,
loadingAnimation: 'swing',
class: {
thead: 'motion-safe:after:animate-[swing_2s_var(--ease-in-out)_infinite]'
}
},
{
loading: true,
loadingAnimation: 'elastic',
class: {
thead: 'motion-safe:after:animate-[elastic_2s_var(--ease-in-out)_infinite]'
}
}
],
defaultVariants: {
loadingColor: 'primary',
loadingAnimation: 'carousel'
}
}
}
})import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
table: {
slots: {
root: 'relative overflow-auto outline-primary/25 focus-visible:outline-3',
base: 'min-w-full overflow-clip',
caption: 'sr-only',
thead: 'relative',
tbody: 'isolate [&>tr]:data-[selectable=true]:hover:bg-elevated/50 [&>tr]:data-[selectable=true]:outline-primary/25 [&>tr]:data-[selectable=true]:focus-visible:outline-3 [&>tr]:data-[selectable=true]:-outline-offset-3 divide-y divide-default',
tfoot: 'relative',
tr: 'data-[selected=true]:bg-elevated/50',
th: 'px-4 py-3.5 text-sm text-highlighted text-start font-semibold [&:has([role=checkbox])]:pe-0',
td: 'p-4 text-sm text-muted whitespace-nowrap [&:has([role=checkbox])]:pe-0',
separator: 'absolute z-1 start-0 w-full h-px bg-(--ui-border-accented)',
empty: 'py-6 text-center text-sm text-muted',
loading: 'py-6 text-center'
},
variants: {
pinned: {
true: {
th: 'sticky bg-default/75 z-1',
td: 'sticky bg-default/75 z-1'
}
},
sticky: {
true: {
thead: 'sticky top-0 inset-x-0 bg-default/75 backdrop-blur-sm z-1',
tfoot: 'sticky bottom-0 inset-x-0 bg-default/75 backdrop-blur-sm z-1'
},
header: {
thead: 'sticky top-0 inset-x-0 bg-default/75 backdrop-blur-sm z-1'
},
footer: {
tfoot: 'sticky bottom-0 inset-x-0 bg-default/75 backdrop-blur-sm z-1'
}
},
loading: {
true: {
thead: 'after:absolute after:z-1 after:h-px motion-reduce:after:inset-x-0 motion-reduce:after:animate-pulse'
}
},
externalScroll: {
true: {
root: 'overflow-visible'
}
},
loadingAnimation: {
carousel: '',
'carousel-inverse': '',
swing: '',
elastic: ''
},
loadingColor: {
primary: '',
secondary: '',
success: '',
info: '',
warning: '',
error: '',
neutral: ''
}
},
compoundVariants: [
{
loading: true,
loadingColor: 'primary',
class: {
thead: 'after:bg-primary'
}
},
{
loading: true,
loadingColor: 'neutral',
class: {
thead: 'after:bg-inverted'
}
},
{
loading: true,
loadingAnimation: 'carousel',
class: {
thead: 'motion-safe:after:animate-[carousel_2s_linear_infinite] motion-safe:rtl:after:animate-[carousel-rtl_2s_linear_infinite]'
}
},
{
loading: true,
loadingAnimation: 'carousel-inverse',
class: {
thead: 'motion-safe:after:animate-[carousel-inverse_2s_linear_infinite] motion-safe:rtl:after:animate-[carousel-inverse-rtl_2s_linear_infinite]'
}
},
{
loading: true,
loadingAnimation: 'swing',
class: {
thead: 'motion-safe:after:animate-[swing_2s_var(--ease-in-out)_infinite]'
}
},
{
loading: true,
loadingAnimation: 'elastic',
class: {
thead: 'motion-safe:after:animate-[elastic_2s_var(--ease-in-out)_infinite]'
}
}
],
defaultVariants: {
loadingColor: 'primary',
loadingAnimation: 'carousel'
}
}
}
})
]
})