mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 12:14:41 +01:00
@@ -0,0 +1,157 @@
|
|||||||
|
<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: () => 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 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>
|
||||||
@@ -266,8 +266,8 @@ You can group rows based on a given column value and show/hide sub rows via some
|
|||||||
|
|
||||||
#### Important parts:
|
#### Important parts:
|
||||||
|
|
||||||
* Add prop `grouping` to `UTable` component with an array of column ids you want to group by.
|
* Add `grouping` prop with an array of column ids you want to group by.
|
||||||
* Add prop `grouping-options` to `UTable`. It must include `getGroupedRowModel`, you can import it from `@tanstack/vue-table` or implement your own.
|
* Add `grouping-options` prop. It must include `getGroupedRowModel`, you can import it from `@tanstack/vue-table` or implement your own.
|
||||||
* Expand rows via `row.toggleExpanded()` method on any cell of the row. Keep in mind, it also toggles `#expanded` slot.
|
* Expand rows via `row.toggleExpanded()` method on any cell of the row. Keep in mind, it also toggles `#expanded` slot.
|
||||||
* Use `aggregateFn` on column definition to define how to aggregate the rows.
|
* Use `aggregateFn` on column definition to define how to aggregate the rows.
|
||||||
* `agregatedCell` renderer on column definition only works if there is no `cell` renderer.
|
* `agregatedCell` renderer on column definition only works if there is no `cell` renderer.
|
||||||
@@ -304,19 +304,19 @@ class: '!p-0'
|
|||||||
You can use the `row-selection` prop to control the selection state of the rows (can be binded with `v-model`).
|
You can use the `row-selection` prop to control the selection state of the rows (can be binded with `v-model`).
|
||||||
::
|
::
|
||||||
|
|
||||||
### With `@select` event
|
### With row select event
|
||||||
|
|
||||||
You can add a `@select` listener to make rows clickable. The handler function receives the `TableRow` instance as the first argument and an optional `Event` as the second argument.
|
You can add a `@select` listener to make rows clickable with or without a checkbox column.
|
||||||
|
|
||||||
::note
|
::note
|
||||||
You can use this to navigate to a page, open a modal or even to select the row manually.
|
The handler function receives the `TableRow` instance as the first argument and an optional `Event` as the second argument.
|
||||||
::
|
::
|
||||||
|
|
||||||
::component-example
|
::component-example
|
||||||
---
|
---
|
||||||
prettier: true
|
prettier: true
|
||||||
collapse: true
|
collapse: true
|
||||||
name: 'table-row-selection-event-example'
|
name: 'table-row-select-event-example'
|
||||||
highlights:
|
highlights:
|
||||||
- 123
|
- 123
|
||||||
- 130
|
- 130
|
||||||
@@ -324,15 +324,23 @@ class: '!p-0'
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
### With context menu :badge{label="Soon" class="align-text-top"}
|
::tip
|
||||||
|
You can use this to navigate to a page, open a modal or even to select the row manually.
|
||||||
|
::
|
||||||
|
|
||||||
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.
|
### With row context menu event :badge{label="Soon" class="align-text-top"}
|
||||||
|
|
||||||
|
You can add a `@contextmenu` listener to make rows right clickable and wrap the Table in a [ContextMenu](/components/context-menu) component to display row actions for example.
|
||||||
|
|
||||||
|
::note
|
||||||
|
The handler function receives the `Event` and `TableRow` instance as the first and second arguments respectively.
|
||||||
|
::
|
||||||
|
|
||||||
::component-example
|
::component-example
|
||||||
---
|
---
|
||||||
prettier: true
|
prettier: true
|
||||||
collapse: true
|
collapse: true
|
||||||
name: 'table-context-menu-example'
|
name: 'table-row-context-menu-event-example'
|
||||||
highlights:
|
highlights:
|
||||||
- 130
|
- 130
|
||||||
- 170
|
- 170
|
||||||
@@ -340,6 +348,30 @@ class: '!p-0'
|
|||||||
---
|
---
|
||||||
::
|
::
|
||||||
|
|
||||||
|
### With row hover event :badge{label="Soon" class="align-text-top"}
|
||||||
|
|
||||||
|
You can add a `@hover` listener to make rows hoverable and use a [Popover](/components/popover) or a [Tooltip](/components/tooltip) component to display row details for example.
|
||||||
|
|
||||||
|
::note
|
||||||
|
The handler function receives the `Event` and `TableRow` instance as the first and second arguments respectively.
|
||||||
|
::
|
||||||
|
|
||||||
|
::component-example
|
||||||
|
---
|
||||||
|
prettier: true
|
||||||
|
collapse: true
|
||||||
|
name: 'table-row-hover-event-example'
|
||||||
|
highlights:
|
||||||
|
- 126
|
||||||
|
- 149
|
||||||
|
class: '!p-0'
|
||||||
|
---
|
||||||
|
::
|
||||||
|
|
||||||
|
::note
|
||||||
|
This example is similar as the Popover [with following cursor example](/components/popover#with-following-cursor) and uses a [`refDebounced`](https://vueuse.org/shared/refDebounced/#refdebounced) to prevent the Popover from opening and closing too quickly when moving the cursor from one row to another.
|
||||||
|
::
|
||||||
|
|
||||||
### With column sorting
|
### 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).
|
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).
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { h, resolveComponent } from 'vue'
|
|||||||
import { upperFirst } from 'scule'
|
import { upperFirst } from 'scule'
|
||||||
import type { TableColumn, TableRow } from '@nuxt/ui'
|
import type { TableColumn, TableRow } from '@nuxt/ui'
|
||||||
import { getPaginationRowModel } from '@tanstack/vue-table'
|
import { getPaginationRowModel } from '@tanstack/vue-table'
|
||||||
import { useClipboard } from '@vueuse/core'
|
import { useClipboard, refDebounced } from '@vueuse/core'
|
||||||
|
|
||||||
const UButton = resolveComponent('UButton')
|
const UButton = resolveComponent('UButton')
|
||||||
const UCheckbox = resolveComponent('UCheckbox')
|
const UCheckbox = resolveComponent('UCheckbox')
|
||||||
@@ -311,6 +311,30 @@ function onContextmenu(e: Event, row: TableRow<Payment>) {
|
|||||||
contextmenuRow.value = row
|
contextmenuRow.value = row
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const popoverOpen = ref(false)
|
||||||
|
const popoverOpenDebounced = refDebounced(popoverOpen, 1)
|
||||||
|
const popoverAnchor = ref({ x: 0, y: 0 })
|
||||||
|
const popoverRow = ref<TableRow<Payment> | null>(null)
|
||||||
|
|
||||||
|
const reference = computed(() => ({
|
||||||
|
getBoundingClientRect: () =>
|
||||||
|
({
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
left: popoverAnchor.value.x,
|
||||||
|
right: popoverAnchor.value.x,
|
||||||
|
top: popoverAnchor.value.y,
|
||||||
|
bottom: popoverAnchor.value.y,
|
||||||
|
...popoverAnchor.value
|
||||||
|
} as DOMRect)
|
||||||
|
}))
|
||||||
|
|
||||||
|
function onHover(_e: Event, row: TableRow<Payment> | null) {
|
||||||
|
popoverRow.value = row
|
||||||
|
|
||||||
|
popoverOpen.value = !!row
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
@@ -374,6 +398,11 @@ onMounted(() => {
|
|||||||
class="border border-accented rounded-sm"
|
class="border border-accented rounded-sm"
|
||||||
@select="onSelect"
|
@select="onSelect"
|
||||||
@contextmenu="onContextmenu"
|
@contextmenu="onContextmenu"
|
||||||
|
@pointermove="(ev: PointerEvent) => {
|
||||||
|
popoverAnchor.x = ev.clientX
|
||||||
|
popoverAnchor.y = ev.clientY
|
||||||
|
}"
|
||||||
|
@hover="onHover"
|
||||||
>
|
>
|
||||||
<template #expanded="{ row }">
|
<template #expanded="{ row }">
|
||||||
<pre>{{ row.original }}</pre>
|
<pre>{{ row.original }}</pre>
|
||||||
@@ -381,6 +410,14 @@ onMounted(() => {
|
|||||||
</UTable>
|
</UTable>
|
||||||
</UContextMenu>
|
</UContextMenu>
|
||||||
|
|
||||||
|
<UPopover :content="{ side: 'top', sideOffset: 16, updatePositionStrategy: 'always' }" :open="popoverOpenDebounced" :reference="reference">
|
||||||
|
<template #content>
|
||||||
|
<div class="p-4">
|
||||||
|
{{ popoverRow?.original?.id }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</UPopover>
|
||||||
|
|
||||||
<div class="flex items-center justify-between gap-3">
|
<div class="flex items-center justify-between gap-3">
|
||||||
<div class="text-sm text-muted">
|
<div class="text-sm text-muted">
|
||||||
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
|
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ export interface TableProps<T extends TableData = TableData> extends TableOption
|
|||||||
*/
|
*/
|
||||||
facetedOptions?: FacetedOptions<T>
|
facetedOptions?: FacetedOptions<T>
|
||||||
onSelect?: (row: TableRow<T>, e?: Event) => void
|
onSelect?: (row: TableRow<T>, e?: Event) => void
|
||||||
|
onHover?: (e: Event, row: TableRow<T> | null) => void
|
||||||
onContextmenu?: ((e: Event, row: TableRow<T>) => void) | Array<((e: Event, row: TableRow<T>) => void)>
|
onContextmenu?: ((e: Event, row: TableRow<T>) => void) | Array<((e: Event, row: TableRow<T>) => void)>
|
||||||
class?: any
|
class?: any
|
||||||
ui?: Table['slots']
|
ui?: Table['slots']
|
||||||
@@ -331,6 +332,14 @@ function onRowSelect(e: Event, row: TableRow<T>) {
|
|||||||
props.onSelect(row, e)
|
props.onSelect(row, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onRowHover(e: Event, row: TableRow<T> | null) {
|
||||||
|
if (!props.onHover) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
props.onHover(e, row)
|
||||||
|
}
|
||||||
|
|
||||||
function onRowContextmenu(e: Event, row: TableRow<T>) {
|
function onRowContextmenu(e: Event, row: TableRow<T>) {
|
||||||
if (!props.onContextmenu) {
|
if (!props.onContextmenu) {
|
||||||
return
|
return
|
||||||
@@ -396,7 +405,7 @@ defineExpose({
|
|||||||
<template v-for="row in tableApi.getRowModel().rows" :key="row.id">
|
<template v-for="row in tableApi.getRowModel().rows" :key="row.id">
|
||||||
<tr
|
<tr
|
||||||
:data-selected="row.getIsSelected()"
|
:data-selected="row.getIsSelected()"
|
||||||
:data-selectable="!!props.onSelect || !!props.onContextmenu"
|
:data-selectable="!!props.onSelect || !!props.onHover || !!props.onContextmenu"
|
||||||
:data-expanded="row.getIsExpanded()"
|
:data-expanded="row.getIsExpanded()"
|
||||||
:role="props.onSelect ? 'button' : undefined"
|
:role="props.onSelect ? 'button' : undefined"
|
||||||
:tabindex="props.onSelect ? 0 : undefined"
|
:tabindex="props.onSelect ? 0 : undefined"
|
||||||
@@ -407,6 +416,8 @@ defineExpose({
|
|||||||
]
|
]
|
||||||
})"
|
})"
|
||||||
@click="onRowSelect($event, row)"
|
@click="onRowSelect($event, row)"
|
||||||
|
@pointerenter="onRowHover($event, row)"
|
||||||
|
@pointerleave="onRowHover($event, null)"
|
||||||
@contextmenu="onRowContextmenu($event, row)"
|
@contextmenu="onRowContextmenu($event, row)"
|
||||||
>
|
>
|
||||||
<td
|
<td
|
||||||
|
|||||||
Reference in New Issue
Block a user