mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-24 00:40:34 +01:00
feat(Table): implement component (#2364)
This commit is contained in:
@@ -32,6 +32,10 @@ const props = defineProps<{
|
||||
* @defaultValue false
|
||||
*/
|
||||
collapse?: boolean
|
||||
/**
|
||||
* A list of line numbers to highlight in the code block
|
||||
*/
|
||||
highlights?: number[]
|
||||
}>()
|
||||
|
||||
const route = useRoute()
|
||||
@@ -105,7 +109,7 @@ const code = computed(() => {
|
||||
`
|
||||
}
|
||||
|
||||
code += `\`\`\`vue`
|
||||
code += `\`\`\`vue${props.highlights?.length ? ` {${props.highlights.join('-')}}` : ''}`
|
||||
|
||||
if (props.external?.length) {
|
||||
code += `
|
||||
@@ -201,7 +205,8 @@ const { data: ast } = await useAsyncData(`component-code-${name}-${hash({ props:
|
||||
formatted = await $prettier.format(code.value, {
|
||||
trailingComma: 'none',
|
||||
semi: false,
|
||||
singleQuote: true
|
||||
singleQuote: true,
|
||||
printWidth: 100
|
||||
})
|
||||
} catch {
|
||||
formatted = code.value
|
||||
|
||||
@@ -22,13 +22,11 @@ const props = withDefaults(defineProps<{
|
||||
* @defaultValue true
|
||||
*/
|
||||
preview?: boolean
|
||||
|
||||
/**
|
||||
* Whether to show the source code
|
||||
* @defaultValue true
|
||||
*/
|
||||
source?: boolean
|
||||
|
||||
/**
|
||||
* A list of variable props to link to the component.
|
||||
*/
|
||||
@@ -40,6 +38,10 @@ const props = withDefaults(defineProps<{
|
||||
default: any
|
||||
multiple?: boolean
|
||||
}>
|
||||
/**
|
||||
* A list of line numbers to highlight in the code block
|
||||
*/
|
||||
highlights?: number[]
|
||||
}>(), {
|
||||
preview: true,
|
||||
source: true
|
||||
@@ -65,7 +67,7 @@ const code = computed(() => {
|
||||
`
|
||||
}
|
||||
|
||||
code += `\`\`\`vue${props.preview ? '' : ` [${data.pascalName}.vue]`}
|
||||
code += `\`\`\`vue ${props.preview ? '' : ` [${data.pascalName}.vue]`}${props.highlights?.length ? `{${props.highlights.join('-')}}` : ''}
|
||||
${data?.code ?? ''}
|
||||
\`\`\``
|
||||
|
||||
@@ -87,7 +89,8 @@ const { data: ast } = await useAsyncData(`component-example-${camelName}`, async
|
||||
formatted = await $prettier.format(code.value, {
|
||||
trailingComma: 'none',
|
||||
semi: false,
|
||||
singleQuote: true
|
||||
singleQuote: true,
|
||||
printWidth: 100
|
||||
})
|
||||
} catch {
|
||||
formatted = code.value
|
||||
@@ -113,7 +116,7 @@ const optionsValues = ref(props.options?.reduce((acc, option) => {
|
||||
|
||||
<template>
|
||||
<div class="my-5">
|
||||
<div v-if="preview">
|
||||
<template v-if="preview">
|
||||
<div class="border border-[var(--ui-color-neutral-200)] dark:border-[var(--ui-color-neutral-700)] relative z-[1]" :class="[{ 'border-b-0 rounded-t-[calc(var(--ui-radius)*1.5)]': props.source, 'rounded-[calc(var(--ui-radius)*1.5)]': !props.source }]">
|
||||
<div v-if="props.options?.length || !!slots.options" class="flex gap-4 p-4 border-b border-[var(--ui-color-neutral-200)] dark:border-[var(--ui-color-neutral-700)]">
|
||||
<slot name="options" />
|
||||
@@ -170,7 +173,7 @@ const optionsValues = ref(props.options?.reduce((acc, option) => {
|
||||
<component :is="camelName" v-bind="{ ...componentProps, ...optionsValues }" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<MDCRenderer v-if="ast && props.source" :body="ast.body" :data="ast.data" class="[&_pre]:!rounded-t-none [&_div.my-5]:!mt-0" />
|
||||
</div>
|
||||
|
||||
@@ -99,6 +99,7 @@ const metaProps: ComputedRef<ComponentMeta['props']> = computed(() => {
|
||||
|
||||
<MDC v-if="prop.description" :value="prop.description" class="text-[var(--ui-text-toned)] mt-1" />
|
||||
|
||||
<ComponentPropsLinks v-if="prop.tags?.length" :prop="prop" />
|
||||
<ComponentPropsSchema v-if="prop.schema" :prop="prop" :ignore="ignore" />
|
||||
</ProseTd>
|
||||
</ProseTr>
|
||||
|
||||
17
docs/app/components/content/ComponentPropsLinks.vue
Normal file
17
docs/app/components/content/ComponentPropsLinks.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import type { PropertyMeta } from 'vue-component-meta'
|
||||
|
||||
const props = defineProps<{
|
||||
prop: PropertyMeta
|
||||
}>()
|
||||
|
||||
const links = computed(() => props.prop.tags?.filter((tag: any) => tag.name === 'link'))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ProseUl v-if="links?.length">
|
||||
<ProseLi v-for="link in links" :key="link.name">
|
||||
<MDC :value="link.text ?? ''" class="my-1" />
|
||||
</ProseLi>
|
||||
</ProseUl>
|
||||
</template>
|
||||
@@ -28,6 +28,14 @@ function stripCompoundVariants(component?: any) {
|
||||
}
|
||||
}
|
||||
|
||||
if (compoundVariant.loadingColor) {
|
||||
if (!['primary', 'neutral'].includes(compoundVariant.loadingColor)) {
|
||||
strippedCompoundVariants.value = true
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<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: () => 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 table = useTemplateRef('table')
|
||||
|
||||
const columnFilters = ref([{
|
||||
id: 'email',
|
||||
value: 'james'
|
||||
}])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex px-4 py-3.5 border-b border-[var(--ui-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>
|
||||
@@ -0,0 +1,114 @@
|
||||
<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: '46000000000000000000000000000000000000000',
|
||||
date: '2024-03-11T15:30:00',
|
||||
status: 'paid',
|
||||
email: 'james.anderson@example.com',
|
||||
amount: 594000
|
||||
}, {
|
||||
id: '45990000000000000000000000000000000000000',
|
||||
date: '2024-03-11T10:10:00',
|
||||
status: 'failed',
|
||||
email: 'mia.white@example.com',
|
||||
amount: 276000
|
||||
}, {
|
||||
id: '45980000000000000000000000000000000000000',
|
||||
date: '2024-03-11T08:50:00',
|
||||
status: 'refunded',
|
||||
email: 'william.brown@example.com',
|
||||
amount: 315000
|
||||
}, {
|
||||
id: '45970000000000000000000000000000000000000',
|
||||
date: '2024-03-10T19:45:00',
|
||||
status: 'paid',
|
||||
email: 'emma.davis@example.com',
|
||||
amount: 5290000
|
||||
}, {
|
||||
id: '45960000000000000000000000000000000000000',
|
||||
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')}`
|
||||
}, {
|
||||
accessorKey: 'date',
|
||||
header: ({ column }) => getHeader(column, 'Date', 'left')
|
||||
}, {
|
||||
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'))
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'email',
|
||||
header: ({ column }) => getHeader(column, 'Email', 'left')
|
||||
}, {
|
||||
accessorKey: 'amount',
|
||||
header: ({ column }) => h('div', { class: 'text-right' }, getHeader(column, 'Amount', 'right')),
|
||||
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)
|
||||
}
|
||||
}]
|
||||
|
||||
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-heroicons-star-20-solid' : 'i-heroicons-star',
|
||||
class: '-mx-2.5',
|
||||
onClick() {
|
||||
column.pin(isPinned === position ? false : position)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const columnPinning = ref({
|
||||
left: [],
|
||||
right: ['amount']
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable
|
||||
v-model:column-pinning="columnPinning"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
class="flex-1"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,118 @@
|
||||
<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',
|
||||
header: ({ column }) => {
|
||||
const isSorted = column.getIsSorted()
|
||||
|
||||
return h(UButton, {
|
||||
color: 'neutral',
|
||||
variant: 'ghost',
|
||||
label: 'Email',
|
||||
icon: isSorted ? (isSorted === 'asc' ? 'i-heroicons-bars-arrow-up-20-solid' : 'i-heroicons-bars-arrow-down-20-solid') : 'i-heroicons-arrows-up-down-20-solid',
|
||||
class: '-mx-2.5',
|
||||
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc')
|
||||
})
|
||||
}
|
||||
}, {
|
||||
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 sorting = ref([{
|
||||
id: 'email',
|
||||
desc: false
|
||||
}])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable
|
||||
v-model:sorting="sorting"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
class="flex-1"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,150 @@
|
||||
<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',
|
||||
header: ({ column }) => getHeader(column, 'ID'),
|
||||
cell: ({ row }) => `#${row.getValue('id')}`
|
||||
}, {
|
||||
accessorKey: 'date',
|
||||
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',
|
||||
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',
|
||||
header: ({ column }) => getHeader(column, 'Email')
|
||||
}, {
|
||||
accessorKey: 'amount',
|
||||
header: ({ column }) => h('div', { class: 'text-right' }, getHeader(column, '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)
|
||||
}
|
||||
}]
|
||||
|
||||
function getHeader(column: Column<Payment>, label: string) {
|
||||
const isSorted = column.getIsSorted()
|
||||
|
||||
return h(UDropdownMenu, {
|
||||
content: {
|
||||
align: 'start'
|
||||
},
|
||||
items: [{
|
||||
label: 'Asc',
|
||||
type: 'checkbox',
|
||||
icon: 'i-heroicons-bars-arrow-up-20-solid',
|
||||
checked: isSorted === 'asc',
|
||||
onSelect: () => {
|
||||
if (isSorted === 'asc') {
|
||||
column.clearSorting()
|
||||
} else {
|
||||
column.toggleSorting(false)
|
||||
}
|
||||
}
|
||||
}, {
|
||||
label: 'Desc',
|
||||
icon: 'i-heroicons-bars-arrow-down-20-solid',
|
||||
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-heroicons-bars-arrow-up-20-solid' : 'i-heroicons-bars-arrow-down-20-solid') : 'i-heroicons-arrows-up-down-20-solid',
|
||||
class: '-mx-2.5 data-[state=open]:bg-[var(--ui-bg-elevated)]'
|
||||
}))
|
||||
}
|
||||
|
||||
const sorting = ref([{
|
||||
id: 'id',
|
||||
desc: false
|
||||
}])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable
|
||||
v-model:sorting="sorting"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
class="flex-1"
|
||||
/>
|
||||
</template>
|
||||
@@ -0,0 +1,134 @@
|
||||
<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: () => 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 table = useTemplateRef('table')
|
||||
|
||||
const columnVisibility = ref({
|
||||
id: false
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex justify-end px-4 py-3.5 border-b border-[var(--ui-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-heroicons-chevron-down-20-solid"
|
||||
/>
|
||||
</UDropdownMenu>
|
||||
</div>
|
||||
|
||||
<UTable
|
||||
ref="table"
|
||||
v-model:column-visibility="columnVisibility"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,96 @@
|
||||
<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: () => 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)
|
||||
}
|
||||
}]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :data="data" :columns="columns" class="flex-1" />
|
||||
</template>
|
||||
319
docs/app/components/content/examples/table/TableExample.vue
Normal file
319
docs/app/components/content/examples/table/TableExample.vue
Normal file
@@ -0,0 +1,319 @@
|
||||
<script setup lang="ts">
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import { upperFirst } from 'scule'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
|
||||
const UButton = resolveComponent('UButton')
|
||||
const UCheckbox = resolveComponent('UCheckbox')
|
||||
const UBadge = resolveComponent('UBadge')
|
||||
const UDropdownMenu = resolveComponent('UDropdownMenu')
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
type Payment = {
|
||||
id: string
|
||||
date: string
|
||||
status: 'paid' | 'failed' | 'refunded'
|
||||
email: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
const data = ref<Payment[]>([{
|
||||
id: '4600',
|
||||
date: '2024-03-11T15:30:00',
|
||||
status: 'paid',
|
||||
email: 'james.anderson@example.com',
|
||||
amount: 594
|
||||
}, {
|
||||
id: '4599',
|
||||
date: '2024-03-11T10:10:00',
|
||||
status: 'failed',
|
||||
email: 'mia.white@example.com',
|
||||
amount: 276
|
||||
}, {
|
||||
id: '4598',
|
||||
date: '2024-03-11T08:50:00',
|
||||
status: 'refunded',
|
||||
email: 'william.brown@example.com',
|
||||
amount: 315
|
||||
}, {
|
||||
id: '4597',
|
||||
date: '2024-03-10T19:45:00',
|
||||
status: 'paid',
|
||||
email: 'emma.davis@example.com',
|
||||
amount: 529
|
||||
}, {
|
||||
id: '4596',
|
||||
date: '2024-03-10T15:55:00',
|
||||
status: 'paid',
|
||||
email: 'ethan.harris@example.com',
|
||||
amount: 639
|
||||
}, {
|
||||
id: '4595',
|
||||
date: '2024-03-10T13:40:00',
|
||||
status: 'refunded',
|
||||
email: 'ava.thomas@example.com',
|
||||
amount: 428
|
||||
}, {
|
||||
id: '4594',
|
||||
date: '2024-03-10T09:15:00',
|
||||
status: 'paid',
|
||||
email: 'michael.wilson@example.com',
|
||||
amount: 683
|
||||
}, {
|
||||
id: '4593',
|
||||
date: '2024-03-09T20:25:00',
|
||||
status: 'failed',
|
||||
email: 'olivia.taylor@example.com',
|
||||
amount: 947
|
||||
}, {
|
||||
id: '4592',
|
||||
date: '2024-03-09T18:45:00',
|
||||
status: 'paid',
|
||||
email: 'benjamin.jackson@example.com',
|
||||
amount: 851
|
||||
}, {
|
||||
id: '4591',
|
||||
date: '2024-03-09T16:05:00',
|
||||
status: 'paid',
|
||||
email: 'sophia.miller@example.com',
|
||||
amount: 762
|
||||
}, {
|
||||
id: '4590',
|
||||
date: '2024-03-09T14:20:00',
|
||||
status: 'paid',
|
||||
email: 'noah.clark@example.com',
|
||||
amount: 573
|
||||
}, {
|
||||
id: '4589',
|
||||
date: '2024-03-09T11:35:00',
|
||||
status: 'failed',
|
||||
email: 'isabella.lee@example.com',
|
||||
amount: 389
|
||||
}, {
|
||||
id: '4588',
|
||||
date: '2024-03-08T22:50:00',
|
||||
status: 'refunded',
|
||||
email: 'liam.walker@example.com',
|
||||
amount: 701
|
||||
}, {
|
||||
id: '4587',
|
||||
date: '2024-03-08T20:15:00',
|
||||
status: 'paid',
|
||||
email: 'charlotte.hall@example.com',
|
||||
amount: 856
|
||||
}, {
|
||||
id: '4586',
|
||||
date: '2024-03-08T17:40:00',
|
||||
status: 'paid',
|
||||
email: 'mason.young@example.com',
|
||||
amount: 492
|
||||
}, {
|
||||
id: '4585',
|
||||
date: '2024-03-08T14:55:00',
|
||||
status: 'failed',
|
||||
email: 'amelia.king@example.com',
|
||||
amount: 637
|
||||
}, {
|
||||
id: '4584',
|
||||
date: '2024-03-08T12:30:00',
|
||||
status: 'paid',
|
||||
email: 'elijah.wright@example.com',
|
||||
amount: 784
|
||||
}, {
|
||||
id: '4583',
|
||||
date: '2024-03-08T09:45:00',
|
||||
status: 'refunded',
|
||||
email: 'harper.scott@example.com',
|
||||
amount: 345
|
||||
}, {
|
||||
id: '4582',
|
||||
date: '2024-03-07T23:10:00',
|
||||
status: 'paid',
|
||||
email: 'evelyn.green@example.com',
|
||||
amount: 918
|
||||
}, {
|
||||
id: '4581',
|
||||
date: '2024-03-07T20:25:00',
|
||||
status: 'paid',
|
||||
email: 'logan.baker@example.com',
|
||||
amount: 567
|
||||
}])
|
||||
|
||||
const columns: TableColumn<Payment>[] = [{
|
||||
id: 'select',
|
||||
header: ({ table }) => h(UCheckbox, {
|
||||
'modelValue': table.getIsAllPageRowsSelected(),
|
||||
'indeterminate': table.getIsSomePageRowsSelected(),
|
||||
'onUpdate:modelValue': (value: boolean) => table.toggleAllPageRowsSelected(!!value),
|
||||
'ariaLabel': 'Select all'
|
||||
}),
|
||||
cell: ({ row }) => h(UCheckbox, {
|
||||
'modelValue': row.getIsSelected(),
|
||||
'onUpdate:modelValue': (value: boolean) => row.toggleSelected(!!value),
|
||||
'ariaLabel': 'Select row'
|
||||
}),
|
||||
enableSorting: false,
|
||||
enableHiding: false
|
||||
}, {
|
||||
accessorKey: 'id',
|
||||
header: '#',
|
||||
cell: ({ row }) => `#${row.getValue('id')}`
|
||||
}, {
|
||||
accessorKey: 'date',
|
||||
header: 'Date',
|
||||
cell: ({ row }) => {
|
||||
return new Date(row.getValue('date')).toLocaleString('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
})
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'status',
|
||||
header: 'Status',
|
||||
cell: ({ row }) => {
|
||||
const color = ({
|
||||
paid: 'success' as const,
|
||||
failed: 'error' as const,
|
||||
refunded: 'neutral' as const
|
||||
})[row.getValue('status') as string]
|
||||
|
||||
return h(UBadge, { class: 'capitalize', variant: 'subtle', color }, () => row.getValue('status'))
|
||||
}
|
||||
}, {
|
||||
accessorKey: 'email',
|
||||
header: ({ column }) => {
|
||||
const isSorted = column.getIsSorted()
|
||||
|
||||
return h(UButton, {
|
||||
color: 'neutral',
|
||||
variant: 'ghost',
|
||||
label: 'Email',
|
||||
icon: isSorted ? (isSorted === 'asc' ? 'i-heroicons-bars-arrow-up-20-solid' : 'i-heroicons-bars-arrow-down-20-solid') : 'i-heroicons-arrows-up-down-20-solid',
|
||||
class: '-mx-2.5',
|
||||
onClick: () => column.toggleSorting(column.getIsSorted() === 'asc')
|
||||
})
|
||||
},
|
||||
cell: ({ row }) => h('div', { class: 'lowercase' }, row.getValue('email'))
|
||||
}, {
|
||||
accessorKey: 'amount',
|
||||
header: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||
cell: ({ row }) => {
|
||||
const amount = Number.parseFloat(row.getValue('amount'))
|
||||
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(amount)
|
||||
|
||||
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||
}
|
||||
}, {
|
||||
id: 'actions',
|
||||
enableHiding: false,
|
||||
cell: ({ row }) => {
|
||||
const items = [{
|
||||
type: 'label',
|
||||
label: 'Actions'
|
||||
}, {
|
||||
label: 'Copy payment ID',
|
||||
onSelect() {
|
||||
navigator.clipboard.writeText(row.original.id)
|
||||
|
||||
toast.add({
|
||||
title: 'Payment ID copied to clipboard!',
|
||||
color: 'success',
|
||||
icon: 'i-heroicons-check-circle'
|
||||
})
|
||||
}
|
||||
}, {
|
||||
label: row.getIsExpanded() ? 'Collapse' : 'Expand',
|
||||
onSelect() {
|
||||
row.toggleExpanded()
|
||||
}
|
||||
}, {
|
||||
type: 'separator'
|
||||
}, {
|
||||
label: 'View customer'
|
||||
}, {
|
||||
label: 'View payment details'
|
||||
}]
|
||||
|
||||
return h('div', { class: 'text-right' }, h(UDropdownMenu, {
|
||||
content: {
|
||||
align: 'end'
|
||||
},
|
||||
items
|
||||
}, () => h(UButton, {
|
||||
icon: 'i-heroicons-ellipsis-vertical-20-solid',
|
||||
color: 'neutral',
|
||||
variant: 'ghost',
|
||||
class: 'ml-auto'
|
||||
})))
|
||||
}
|
||||
}]
|
||||
|
||||
const table = useTemplateRef('table')
|
||||
|
||||
function randomize() {
|
||||
data.value = [...data.value].sort(() => Math.random() - 0.5)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-1 divide-y divide-[var(--ui-border-accented)]">
|
||||
<div class="flex items-center gap-2 px-4 py-3.5">
|
||||
<UInput
|
||||
:model-value="(table?.tableApi?.getColumn('email')?.getFilterValue() as string)"
|
||||
class="max-w-sm"
|
||||
placeholder="Filter emails..."
|
||||
@update:model-value="table?.tableApi?.getColumn('email')?.setFilterValue($event)"
|
||||
/>
|
||||
|
||||
<UButton color="neutral" label="Randomize" @click="randomize" />
|
||||
|
||||
<UDropdownMenu
|
||||
:items="table?.tableApi?.getAllColumns().filter(column => column.getCanHide()).map(column => ({
|
||||
label: upperFirst(column.id),
|
||||
type: 'checkbox' as const,
|
||||
checked: column.getIsVisible(),
|
||||
onUpdateChecked(checked: boolean) {
|
||||
table?.tableApi?.getColumn(column.id)?.toggleVisibility(!!checked)
|
||||
},
|
||||
onSelect(e?: Event) {
|
||||
e?.preventDefault()
|
||||
}
|
||||
}))"
|
||||
:content="{ align: 'end' }"
|
||||
>
|
||||
<UButton
|
||||
label="Columns"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
trailing-icon="i-heroicons-chevron-down-20-solid"
|
||||
class="ml-auto"
|
||||
/>
|
||||
</UDropdownMenu>
|
||||
</div>
|
||||
|
||||
<UTable
|
||||
ref="table"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
sticky
|
||||
class="h-96"
|
||||
>
|
||||
<template #expanded="{ row }">
|
||||
<pre>{{ row.original }}</pre>
|
||||
</template>
|
||||
</UTable>
|
||||
|
||||
<div class="px-4 py-3.5 text-sm text-[var(--ui-text-muted)]">
|
||||
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
|
||||
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,55 @@
|
||||
<script setup lang="ts">
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
|
||||
const UAvatar = resolveComponent('UAvatar')
|
||||
|
||||
type User = {
|
||||
id: number
|
||||
name: string
|
||||
username: string
|
||||
email: string
|
||||
avatar: { src: string }
|
||||
company: { name: string }
|
||||
}
|
||||
|
||||
const { data, status } = await useFetch<User[]>('https://jsonplaceholder.typicode.com/users', {
|
||||
transform: (data) => {
|
||||
return data?.map(user => ({
|
||||
...user,
|
||||
avatar: { src: `https://i.pravatar.cc/120?img=${user.id}` }
|
||||
})) || []
|
||||
},
|
||||
lazy: true
|
||||
})
|
||||
|
||||
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,
|
||||
size: 'lg'
|
||||
}),
|
||||
h('div', undefined, [
|
||||
h('p', { class: 'font-medium text-[var(--ui-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'" class="flex-1" />
|
||||
</template>
|
||||
@@ -0,0 +1,115 @@
|
||||
<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: () => 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 table = useTemplateRef('table')
|
||||
|
||||
const globalFilter = ref('45')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col flex-1">
|
||||
<div class="flex px-4 py-3.5 border-b border-[var(--ui-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>
|
||||
@@ -0,0 +1,140 @@
|
||||
<script setup lang="ts">
|
||||
import { h, resolveComponent } from 'vue'
|
||||
import type { TableColumn } from '@nuxt/ui'
|
||||
import type { Row } from '@tanstack/vue-table'
|
||||
|
||||
const UButton = resolveComponent('UButton')
|
||||
const UBadge = resolveComponent('UBadge')
|
||||
const UDropdownMenu = resolveComponent('UDropdownMenu')
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
type Payment = {
|
||||
id: string
|
||||
date: string
|
||||
status: 'paid' | 'failed' | 'refunded'
|
||||
email: string
|
||||
amount: number
|
||||
}
|
||||
|
||||
const 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: () => h('div', { class: 'text-right' }, 'Amount'),
|
||||
cell: ({ row }) => {
|
||||
const amount = Number.parseFloat(row.getValue('amount'))
|
||||
|
||||
const formatted = new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'EUR'
|
||||
}).format(amount)
|
||||
|
||||
return h('div', { class: 'text-right font-medium' }, formatted)
|
||||
}
|
||||
}, {
|
||||
id: 'actions',
|
||||
cell: ({ row }) => {
|
||||
return h('div', { class: 'text-right' }, h(UDropdownMenu, {
|
||||
content: {
|
||||
align: 'end'
|
||||
},
|
||||
items: getRowItems(row)
|
||||
}, () => h(UButton, {
|
||||
icon: 'i-heroicons-ellipsis-vertical-20-solid',
|
||||
color: 'neutral',
|
||||
variant: 'ghost',
|
||||
class: 'ml-auto'
|
||||
})))
|
||||
}
|
||||
}]
|
||||
|
||||
function getRowItems(row: Row<Payment>) {
|
||||
return [{
|
||||
type: 'label',
|
||||
label: 'Actions'
|
||||
}, {
|
||||
label: 'Copy payment ID',
|
||||
onSelect() {
|
||||
navigator.clipboard.writeText(row.original.id)
|
||||
|
||||
toast.add({
|
||||
title: 'Payment ID copied to clipboard!',
|
||||
color: 'success',
|
||||
icon: 'i-heroicons-check-circle'
|
||||
})
|
||||
}
|
||||
}, {
|
||||
type: 'separator'
|
||||
}, {
|
||||
label: 'View customer'
|
||||
}, {
|
||||
label: 'View payment details'
|
||||
}]
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable :data="data" :columns="columns" class="flex-1" />
|
||||
</template>
|
||||
@@ -0,0 +1,121 @@
|
||||
<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-heroicons-chevron-down-20-solid',
|
||||
square: true,
|
||||
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: () => 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 expanded = ref({ 1: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable
|
||||
v-model:expanded="expanded"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
:ui="{ tr: 'data-[expanded=true]:bg-[var(--ui-bg-elevated)]/50' }"
|
||||
class="flex-1"
|
||||
>
|
||||
<template #expanded="{ row }">
|
||||
<pre>{{ row.original }}</pre>
|
||||
</template>
|
||||
</UTable>
|
||||
</template>
|
||||
@@ -0,0 +1,122 @@
|
||||
<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.getIsAllPageRowsSelected(),
|
||||
'indeterminate': table.getIsSomePageRowsSelected(),
|
||||
'onUpdate:modelValue': (value: boolean) => table.toggleAllPageRowsSelected(!!value),
|
||||
'ariaLabel': 'Select all'
|
||||
}),
|
||||
cell: ({ row }) => h(UCheckbox, {
|
||||
'modelValue': row.getIsSelected(),
|
||||
'onUpdate:modelValue': (value: boolean) => row.toggleSelected(!!value),
|
||||
'ariaLabel': 'Select row'
|
||||
})
|
||||
}, {
|
||||
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 table = useTemplateRef('table')
|
||||
|
||||
const rowSelection = ref({ 1: true })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex-1">
|
||||
<UTable
|
||||
ref="table"
|
||||
v-model:row-selection="rowSelection"
|
||||
:data="data"
|
||||
:columns="columns"
|
||||
/>
|
||||
|
||||
<div class="px-4 py-3.5 border-t border-[var(--ui-border-accented)] text-sm text-[var(--ui-text-muted)]">
|
||||
{{ table?.tableApi?.getFilteredSelectedRowModel().rows.length || 0 }} of
|
||||
{{ table?.tableApi?.getFilteredRowModel().rows.length || 0 }} row(s) selected.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -75,10 +75,24 @@ const communityLinks = computed(() => [{
|
||||
|
||||
<template>
|
||||
<UPage v-if="page">
|
||||
<UPageHeader :title="page.title" :links="page.links" :headline="headline">
|
||||
<UPageHeader :title="page.title" :headline="headline">
|
||||
<template #description>
|
||||
<MDC v-if="page.description" :value="page.description" unwrap="p" />
|
||||
</template>
|
||||
|
||||
<template #links>
|
||||
<UButton
|
||||
v-for="link in page.links"
|
||||
:key="link.label"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
v-bind="link"
|
||||
>
|
||||
<template v-if="link.avatar" #leading>
|
||||
<UAvatar v-bind="link.avatar" size="2xs" />
|
||||
</template>
|
||||
</UButton>
|
||||
</template>
|
||||
</UPageHeader>
|
||||
|
||||
<UPageBody>
|
||||
|
||||
@@ -251,10 +251,6 @@ This will give you access to the following:
|
||||
| `emblaRef`{lang="ts-type"} | `Ref<HTMLElement \| null>`{lang="ts-type"} |
|
||||
| `emblaApi`{lang="ts-type"} | [`Ref<EmblaCarouselType \| null>`{lang="ts-type"}](https://www.embla-carousel.com/api/methods/#typescript) |
|
||||
|
||||
::note{to="https://vuejs.org/api/composition-api-helpers.html#usetemplateref" target="_blank"}
|
||||
You can use `useTemplateRef` to get the component instance.
|
||||
::
|
||||
|
||||
## Theme
|
||||
|
||||
:component-theme
|
||||
|
||||
@@ -1,21 +1,406 @@
|
||||
---
|
||||
description: A responsive table element to display data in rows and columns.
|
||||
links:
|
||||
- label: TanStack Table
|
||||
avatar:
|
||||
src: https://github.com/tanstack.png
|
||||
to: https://tanstack.com/table/latest
|
||||
- label: GitHub
|
||||
icon: i-simple-icons-github
|
||||
to: https://github.com/nuxt/ui/tree/v3/src/runtime/components/Table.vue
|
||||
navigation:
|
||||
badge:
|
||||
label: Todo
|
||||
color: neutral
|
||||
disabled: true
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
The Table component is built on top of [TanStack Table](https://tanstack.com/table/latest) and is powered by the [useVueTable](https://tanstack.com/table/latest/docs/framework/vue/vue-table#usevuetable) composable to provide a flexible and fully type-safe API.
|
||||
|
||||
::note
|
||||
Some features of **TanStack Table** are not supported yet, we'll add more over time.
|
||||
::
|
||||
|
||||
::component-example
|
||||
---
|
||||
source: false
|
||||
name: 'table-example'
|
||||
class: '!p-0'
|
||||
---
|
||||
::
|
||||
|
||||
::callout{icon="i-simple-icons-github" to="https://github.com/nuxt/ui/tree/v3/docs/app/components/content/examples/table/TableExample.vue"}
|
||||
This example demonstrates the most common use case of the `Table` component. Check out the source code on GitHub.
|
||||
::
|
||||
|
||||
### Data
|
||||
|
||||
Use the `data` prop as an array of objects, the columns will be generated based on the keys of the objects.
|
||||
|
||||
::component-code
|
||||
---
|
||||
collapse: true
|
||||
class: '!p-0'
|
||||
ignore:
|
||||
- data
|
||||
- class
|
||||
external:
|
||||
- data
|
||||
props:
|
||||
data:
|
||||
- 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
|
||||
class: 'flex-1'
|
||||
---
|
||||
::
|
||||
|
||||
### Columns
|
||||
|
||||
Use the `columns` prop as an array of [ColumnDef](https://tanstack.com/table/latest/docs/api/core/column-def) objects with properties like:
|
||||
|
||||
- `accessorKey`: [The key of the row object to use when extracting the value for the column.]{class="text-[var(--ui-text-muted)]"}
|
||||
- `header`: [The header to display for the column. If a string is passed, it can be used as a default for the column ID. If a function is passed, it will be passed a props object for the header and should return the rendered header value (the exact type depends on the adapter being used).]{class="text-[var(--ui-text-muted)]"}
|
||||
- `cell`: [The cell to display each row for the column. If a function is passed, it will be passed a props object for the cell and should return the rendered cell value (the exact type depends on the adapter being used).]{class="text-[var(--ui-text-muted)]"}
|
||||
|
||||
In order to render components or other HTML elements, you will need to use the Vue [`h` function](https://vuejs.org/api/render-function.html#h) inside the `header` and `cell` props. This is different from other components that use slots but allows for more flexibility.
|
||||
|
||||
::component-example
|
||||
---
|
||||
prettier: true
|
||||
collapse: true
|
||||
class: '!p-0'
|
||||
name: 'table-columns-example'
|
||||
highlights:
|
||||
- 53
|
||||
- 105
|
||||
---
|
||||
::
|
||||
|
||||
::tip
|
||||
When rendering components with the `h` function, you can utilize the `resolveComponent` function to dynamically resolve and reference components.
|
||||
::
|
||||
|
||||
### Loading
|
||||
|
||||
Use the `loading` prop to display a loading state, the `loading-color` prop to change its color and the `loading-animation` prop to change its animation.
|
||||
|
||||
::component-code
|
||||
---
|
||||
collapse: true
|
||||
class: '!p-0'
|
||||
ignore:
|
||||
- data
|
||||
- class
|
||||
external:
|
||||
- data
|
||||
props:
|
||||
loading: true
|
||||
loadingColor: primary
|
||||
loadingAnimation: carousel
|
||||
data:
|
||||
- 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
|
||||
class: 'flex-1'
|
||||
---
|
||||
::
|
||||
|
||||
### Sticky
|
||||
|
||||
Use the `sticky` prop to make the header sticky.
|
||||
|
||||
::component-code
|
||||
---
|
||||
collapse: true
|
||||
class: '!p-0'
|
||||
ignore:
|
||||
- data
|
||||
- class
|
||||
external:
|
||||
- data
|
||||
props:
|
||||
sticky: true
|
||||
data:
|
||||
- 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
|
||||
class: 'flex-1 max-h-[312px]'
|
||||
---
|
||||
::
|
||||
|
||||
## Examples
|
||||
|
||||
<!-- ## API
|
||||
### With row actions
|
||||
|
||||
You can add a new column that renders a [DropdownMenu](/components/dropdown-menu) component inside the `cell` to render row actions.
|
||||
|
||||
::component-example
|
||||
---
|
||||
prettier: true
|
||||
collapse: true
|
||||
name: 'table-row-actions-example'
|
||||
highlights:
|
||||
- 110
|
||||
- 134
|
||||
class: '!p-0'
|
||||
---
|
||||
::
|
||||
|
||||
### With expandable rows
|
||||
|
||||
You can add a new column that renders a [Button](/components/button) component inside the `cell` to toggle the expandable state of a row using the TanStack Table [Expanding APIs](https://tanstack.com/table/latest/docs/api/features/expanding).
|
||||
|
||||
::caution
|
||||
You need to define the `#expanded` slot to render the expanded content which will receive the row as a parameter.
|
||||
::
|
||||
|
||||
::component-example
|
||||
---
|
||||
prettier: true
|
||||
collapse: true
|
||||
name: 'table-row-expandable-example'
|
||||
highlights:
|
||||
- 55
|
||||
- 71
|
||||
class: '!p-0'
|
||||
---
|
||||
::
|
||||
|
||||
::tip
|
||||
You can use the `expanded` prop to control the expandable state of the rows (can be binded with `v-model`).
|
||||
::
|
||||
|
||||
::note
|
||||
You could also add this action to the [DropdownMenu](/components/dropdown-menu) component inside the `actions` column.
|
||||
::
|
||||
|
||||
### With row selection
|
||||
|
||||
You can add a new column that renders a [Checkbox](/components/checkbox) component inside the `header` and `cell` to select rows using the TanStack Table [Row Selection APIs](https://tanstack.com/table/latest/docs/api/features/row-selection).
|
||||
|
||||
::component-example
|
||||
---
|
||||
prettier: true
|
||||
collapse: true
|
||||
name: 'table-row-selection-example'
|
||||
highlights:
|
||||
- 55
|
||||
- 70
|
||||
class: '!p-0'
|
||||
---
|
||||
::
|
||||
|
||||
::tip
|
||||
You can use the `row-selection` prop to control the selection state of the rows (can be binded with `v-model`).
|
||||
::
|
||||
|
||||
### 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).
|
||||
|
||||
::component-example
|
||||
---
|
||||
prettier: true
|
||||
collapse: true
|
||||
name: 'table-column-sorting-example'
|
||||
highlights:
|
||||
- 90
|
||||
- 105
|
||||
class: '!p-0'
|
||||
---
|
||||
::
|
||||
|
||||
::tip
|
||||
You can use the `sorting` prop to control the sorting state of the columns (can be binded with `v-model`).
|
||||
::
|
||||
|
||||
You can also create a reusable component to make any column header sortable.
|
||||
|
||||
::component-example
|
||||
---
|
||||
prettier: true
|
||||
collapse: true
|
||||
name: 'table-column-sorting-reusable-example'
|
||||
highlights:
|
||||
- 110
|
||||
- 161
|
||||
class: '!p-0'
|
||||
---
|
||||
::
|
||||
|
||||
::note
|
||||
In this example, we use a function to define the column header but you can also create an actual component.
|
||||
::
|
||||
|
||||
### With column pinning
|
||||
|
||||
You can update a column `header` to render a [Button](/components/button) component inside the `header` to toggle the pinning state using the TanStack Table [Pinning APIs](https://tanstack.com/table/latest/docs/api/features/row-pinning).
|
||||
|
||||
::note
|
||||
A pinned column will become sticky on the left or right side of the table.
|
||||
::
|
||||
|
||||
::component-example
|
||||
---
|
||||
prettier: true
|
||||
collapse: true
|
||||
name: 'table-column-pinning-example'
|
||||
highlights:
|
||||
- 100
|
||||
- 113
|
||||
class: '!p-0 overflow-clip'
|
||||
---
|
||||
::
|
||||
|
||||
::tip
|
||||
You can use the `column-pinning` prop to control the pinning state of the columns (can be binded with `v-model`).
|
||||
::
|
||||
|
||||
### With column visibility
|
||||
|
||||
You can add use [DropdownMenu](/components/dropdown-menu) component to toggle the visibility of the columns using the TanStack Table [Column Visibility APIs](https://tanstack.com/table/latest/docs/api/features/column-visibility).
|
||||
|
||||
::component-example
|
||||
---
|
||||
prettier: true
|
||||
collapse: true
|
||||
name: 'table-column-visibility-example'
|
||||
highlights:
|
||||
- 135
|
||||
- 142
|
||||
class: '!p-0'
|
||||
---
|
||||
::
|
||||
|
||||
::tip
|
||||
You can use the `column-visibility` prop to control the visibility state of the columns (can be binded with `v-model`).
|
||||
::
|
||||
|
||||
### With column filters
|
||||
|
||||
You can use an [Input](/components/input) component to filter per column the rows using the TanStack Table [Column Filtering APIs](https://tanstack.com/table/latest/docs/api/features/column-filtering).
|
||||
|
||||
::component-example
|
||||
---
|
||||
prettier: true
|
||||
collapse: true
|
||||
name: 'table-column-filters-example'
|
||||
highlights:
|
||||
- 135
|
||||
- 142
|
||||
class: '!p-0'
|
||||
---
|
||||
::
|
||||
|
||||
::tip
|
||||
You can use the `column-filters` prop to control the filters state of the columns (can be binded with `v-model`).
|
||||
::
|
||||
|
||||
### With global filter
|
||||
|
||||
You can use an [Input](/components/input) component to filter the rows using the TanStack Table [Global Filtering APIs](https://tanstack.com/table/latest/docs/api/features/global-filtering).
|
||||
|
||||
::component-example
|
||||
---
|
||||
prettier: true
|
||||
collapse: true
|
||||
name: 'table-global-filter-example'
|
||||
class: '!p-0'
|
||||
---
|
||||
::
|
||||
|
||||
::tip
|
||||
You can use the `global-filter` prop to control the global filter state (can be binded with `v-model`).
|
||||
::
|
||||
|
||||
### With fetched data
|
||||
|
||||
You can fetch data from an API and use them in the Table.
|
||||
|
||||
::component-example
|
||||
---
|
||||
prettier: true
|
||||
collapse: true
|
||||
name: 'table-fetch-example'
|
||||
class: '!p-0'
|
||||
---
|
||||
::
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
@@ -25,10 +410,26 @@ navigation:
|
||||
|
||||
:component-slots
|
||||
|
||||
### Emits
|
||||
### Expose
|
||||
|
||||
:component-emits
|
||||
You can access the typed component instance using [`useTemplateRef`](https://vuejs.org/api/composition-api-helpers.html#usetemplateref).
|
||||
|
||||
```vue
|
||||
<script setup lang="ts">
|
||||
const table = useTemplateRef('table')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UTable ref="table" />
|
||||
</template>
|
||||
```
|
||||
|
||||
This will give you access to the following:
|
||||
|
||||
| Name | Type |
|
||||
| ---- | ---- |
|
||||
| `tableApi`{lang="ts-type"} | [`Ref<Table \| null>`{lang="ts-type"}](https://tanstack.com/table/latest/docs/api/core/table#table-api) |
|
||||
|
||||
## Theme
|
||||
|
||||
:component-theme -->
|
||||
:component-theme
|
||||
|
||||
@@ -179,6 +179,7 @@ export default defineNuxtConfig({
|
||||
'USlider',
|
||||
'USlideover',
|
||||
'USwitch',
|
||||
'UTable',
|
||||
'UTabs',
|
||||
'UTextarea',
|
||||
'UTooltip'
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"@nuxt/content": "^2.13.4",
|
||||
"@nuxt/image": "^1.8.1",
|
||||
"@nuxt/ui": "latest",
|
||||
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@041ca1f",
|
||||
"@nuxt/ui-pro": "https://pkg.pr.new/@nuxt/ui-pro@5c9e5b4",
|
||||
"@nuxthub/core": "^0.7.33",
|
||||
"@nuxtjs/plausible": "^1.0.3",
|
||||
"@octokit/rest": "^21.0.2",
|
||||
|
||||
Reference in New Issue
Block a user