feat(Table): add support for context menu

Resolves #4259
This commit is contained in:
Benjamin Canac
2025-07-01 13:15:00 +02:00
parent b96a1ccbab
commit f62c5ec20c
5 changed files with 259 additions and 54 deletions

View File

@@ -0,0 +1,159 @@
<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: () => h('div', { class: 'text-right' }, 'Amount'),
cell: ({ row }) => {
const amount = Number.parseFloat(row.getValue('amount'))
const formatted = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'EUR'
}).format(amount)
return h('div', { class: 'text-right font-medium' }, formatted)
}
}]
const 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>

View File

@@ -112,7 +112,7 @@ function onSelect(row: TableRow<Payment>, e?: Event) {
</script>
<template>
<div class=" flex w-full flex-1 gap-1">
<div class="flex w-full flex-1 gap-1">
<div class="flex-1">
<UTable
ref="table"

View File

@@ -324,6 +324,22 @@ class: '!p-0'
---
::
### With context menu :badge{label="Soon" class="align-text-top"}
You can wrap the `UTable` component in a [ContextMenu](/components/context-menu) component to make rows right clickable. You also need to add a `@contextmenu` listener to the `UTable` component to determine wich row is being right clicked. The handler function receives the `Event` and `TableRow` instance as the first and second arguments respectively.
::component-example
---
prettier: true
collapse: true
name: 'table-context-menu-example'
highlights:
- 130
- 170
class: '!p-0'
---
::
### With column sorting
You can update a column `header` to render a [Button](/components/button) component inside the `header` to toggle the sorting state using the TanStack Table [Sorting APIs](https://tanstack.com/table/latest/docs/api/features/sorting).