docs: add sub components example
This commit is contained in:
parent
8444039cb3
commit
8291ef452d
|
|
@ -1368,3 +1368,41 @@ const columns = computed(() => props.table.getAllColumns()
|
||||||
```vue
|
```vue
|
||||||
<DataTableViewOptions :table="table" />
|
<DataTableViewOptions :table="table" />
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Expanding
|
||||||
|
|
||||||
|
Let's add the display of arbitrary content in expandable rows
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts" generic="TData, TValue">
|
||||||
|
const props = defineProps<{
|
||||||
|
columns: ColumnDef<TData, TValue>[]
|
||||||
|
data: TData[]
|
||||||
|
renderSubComponent: (row: Row<TData>) => VNode
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!-- Table -->
|
||||||
|
<TableRow v-if="row.getIsExpanded()">
|
||||||
|
<TableCell :colspan="row.getAllCells().length">
|
||||||
|
<FlexRender :render="renderSubComponent(row)" />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
<!-- Table -->
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we are not bound to columns content, we can render any component with any content
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
function renderSubComponent(row: Row<Task>): VNode {
|
||||||
|
return h(User, { row })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<DataTable :data="tasks" :columns="columns" :render-sub-component="renderSubComponent" />
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,17 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { Row } from '@tanstack/vue-table'
|
||||||
|
import type { VNode } from 'vue'
|
||||||
|
import { h } from 'vue'
|
||||||
import tasks from './data/tasks.json'
|
import tasks from './data/tasks.json'
|
||||||
import DataTable from './components/DataTable.vue'
|
import DataTable from './components/DataTable.vue'
|
||||||
import UserNav from './components/UserNav.vue'
|
import UserNav from './components/UserNav.vue'
|
||||||
import { columns } from './components/columns'
|
import { columns } from './components/columns'
|
||||||
|
import type { Task } from './data/schema'
|
||||||
|
import User from './components/DataTableUser.vue'
|
||||||
|
|
||||||
|
function renderSubComponent(row: Row<Task>): VNode {
|
||||||
|
return h(User, { row })
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -31,6 +40,6 @@ import { columns } from './components/columns'
|
||||||
<UserNav />
|
<UserNav />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DataTable :data="tasks" :columns="columns" />
|
<DataTable :data="tasks" :columns="columns" :render-sub-component="renderSubComponent" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts" generic="TData, TValue">
|
||||||
import type {
|
import type {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
ColumnFiltersState,
|
ColumnFiltersState,
|
||||||
|
ExpandedState,
|
||||||
|
Row,
|
||||||
SortingState,
|
SortingState,
|
||||||
VisibilityState,
|
VisibilityState,
|
||||||
} from '@tanstack/vue-table'
|
} from '@tanstack/vue-table'
|
||||||
|
|
@ -17,7 +19,7 @@ import {
|
||||||
} from '@tanstack/vue-table'
|
} from '@tanstack/vue-table'
|
||||||
|
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import type { Task } from '../data/schema'
|
import type { VNode } from 'vue'
|
||||||
import DataTablePagination from './DataTablePagination.vue'
|
import DataTablePagination from './DataTablePagination.vue'
|
||||||
import DataTableToolbar from './DataTableToolbar.vue'
|
import DataTableToolbar from './DataTableToolbar.vue'
|
||||||
import { valueUpdater } from '@/lib/utils'
|
import { valueUpdater } from '@/lib/utils'
|
||||||
|
|
@ -31,8 +33,9 @@ import {
|
||||||
} from '@/lib/registry/new-york/ui/table'
|
} from '@/lib/registry/new-york/ui/table'
|
||||||
|
|
||||||
interface DataTableProps {
|
interface DataTableProps {
|
||||||
columns: ColumnDef<Task, any>[]
|
columns: ColumnDef<TData, TValue>[]
|
||||||
data: Task[]
|
data: TData[]
|
||||||
|
renderSubComponent: (row: Row<TData>) => VNode
|
||||||
}
|
}
|
||||||
const props = defineProps<DataTableProps>()
|
const props = defineProps<DataTableProps>()
|
||||||
|
|
||||||
|
|
@ -40,6 +43,7 @@ const sorting = ref<SortingState>([])
|
||||||
const columnFilters = ref<ColumnFiltersState>([])
|
const columnFilters = ref<ColumnFiltersState>([])
|
||||||
const columnVisibility = ref<VisibilityState>({})
|
const columnVisibility = ref<VisibilityState>({})
|
||||||
const rowSelection = ref({})
|
const rowSelection = ref({})
|
||||||
|
const expanded = ref<ExpandedState>({})
|
||||||
|
|
||||||
const table = useVueTable({
|
const table = useVueTable({
|
||||||
get data() { return props.data },
|
get data() { return props.data },
|
||||||
|
|
@ -49,12 +53,14 @@ const table = useVueTable({
|
||||||
get columnFilters() { return columnFilters.value },
|
get columnFilters() { return columnFilters.value },
|
||||||
get columnVisibility() { return columnVisibility.value },
|
get columnVisibility() { return columnVisibility.value },
|
||||||
get rowSelection() { return rowSelection.value },
|
get rowSelection() { return rowSelection.value },
|
||||||
|
get expanded() { return expanded.value },
|
||||||
},
|
},
|
||||||
enableRowSelection: true,
|
enableRowSelection: true,
|
||||||
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
|
||||||
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
|
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
|
||||||
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
|
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
|
||||||
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
|
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
|
||||||
|
onExpandedChange: updaterOrValue => valueUpdater(updaterOrValue, expanded),
|
||||||
getCoreRowModel: getCoreRowModel(),
|
getCoreRowModel: getCoreRowModel(),
|
||||||
getFilteredRowModel: getFilteredRowModel(),
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
getPaginationRowModel: getPaginationRowModel(),
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
|
@ -78,15 +84,18 @@ const table = useVueTable({
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
<template v-if="table.getRowModel().rows?.length">
|
<template v-if="table.getRowModel().rows?.length">
|
||||||
<TableRow
|
<template v-for="row in table.getRowModel().rows" :key="row.id">
|
||||||
v-for="row in table.getRowModel().rows"
|
<TableRow :data-state="row.getIsSelected() && 'selected'">
|
||||||
:key="row.id"
|
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
|
||||||
:data-state="row.getIsSelected() && 'selected'"
|
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
||||||
>
|
</TableCell>
|
||||||
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id">
|
</TableRow>
|
||||||
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
|
<TableRow v-if="row.getIsExpanded()" class="shadow-inner">
|
||||||
</TableCell>
|
<TableCell :colspan="row.getAllCells().length">
|
||||||
</TableRow>
|
<FlexRender :render="renderSubComponent(row)" />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<TableRow v-else>
|
<TableRow v-else>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts" generic="TData">
|
||||||
import type { Column } from '@tanstack/vue-table'
|
import type { Column } from '@tanstack/vue-table'
|
||||||
import type { Component } from 'vue'
|
import type { Component } from 'vue'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import type { Task } from '../data/schema'
|
|
||||||
import PlusCircledIcon from '~icons/radix-icons/plus-circled'
|
import PlusCircledIcon from '~icons/radix-icons/plus-circled'
|
||||||
import CheckIcon from '~icons/radix-icons/check'
|
import CheckIcon from '~icons/radix-icons/check'
|
||||||
|
|
||||||
|
|
@ -19,7 +18,7 @@ import { Separator } from '@/lib/registry/new-york/ui/separator'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
interface DataTableFacetedFilter {
|
interface DataTableFacetedFilter {
|
||||||
column?: Column<Task, any>
|
column?: Column<TData, any>
|
||||||
title?: string
|
title?: string
|
||||||
options: {
|
options: {
|
||||||
label: string
|
label: string
|
||||||
|
|
@ -32,6 +31,22 @@ const props = defineProps<DataTableFacetedFilter>()
|
||||||
|
|
||||||
const facets = computed(() => props.column?.getFacetedUniqueValues())
|
const facets = computed(() => props.column?.getFacetedUniqueValues())
|
||||||
const selectedValues = computed(() => new Set(props.column?.getFilterValue() as string[]))
|
const selectedValues = computed(() => new Set(props.column?.getFilterValue() as string[]))
|
||||||
|
|
||||||
|
type ReturnTypeFilterFunction = string[] | number[] | false[] | true[] | Record<string, any>[]
|
||||||
|
|
||||||
|
function isListOptions(list: ReturnTypeFilterFunction | DataTableFacetedFilter['options']): list is DataTableFacetedFilter['options'] {
|
||||||
|
return Array.isArray(list) && list.length > 0 && 'value' in (list[0] as DataTableFacetedFilter['options'])
|
||||||
|
}
|
||||||
|
|
||||||
|
function filterFunction(list: ReturnTypeFilterFunction | DataTableFacetedFilter['options'], term: string): DataTableFacetedFilter['options'] {
|
||||||
|
if (isListOptions(list)) {
|
||||||
|
return list.filter(i => i.label.toLowerCase()?.includes(term))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error('List is not a DataTableFacetedFilter options')
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -74,7 +89,7 @@ const selectedValues = computed(() => new Set(props.column?.getFilterValue() as
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent class="w-[200px] p-0" align="start">
|
<PopoverContent class="w-[200px] p-0" align="start">
|
||||||
<Command
|
<Command
|
||||||
:filter-function="(list: DataTableFacetedFilter['options'], term) => list.filter(i => i.label.toLowerCase()?.includes(term)) "
|
:filter-function="filterFunction"
|
||||||
>
|
>
|
||||||
<CommandInput :placeholder="title" />
|
<CommandInput :placeholder="title" />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts" generic="TData">
|
||||||
import type { Table } from '@tanstack/vue-table'
|
import type { Table } from '@tanstack/vue-table'
|
||||||
import type { Task } from '../data/schema'
|
|
||||||
import ChevronLeftIcon from '~icons/radix-icons/chevron-left'
|
import ChevronLeftIcon from '~icons/radix-icons/chevron-left'
|
||||||
import ChevronRightIcon from '~icons/radix-icons/chevron-right'
|
import ChevronRightIcon from '~icons/radix-icons/chevron-right'
|
||||||
import DoubleArrowLeftIcon from '~icons/radix-icons/double-arrow-left'
|
import DoubleArrowLeftIcon from '~icons/radix-icons/double-arrow-left'
|
||||||
|
|
@ -16,7 +15,7 @@ import {
|
||||||
} from '@/lib/registry/new-york/ui/select'
|
} from '@/lib/registry/new-york/ui/select'
|
||||||
|
|
||||||
interface DataTablePaginationProps {
|
interface DataTablePaginationProps {
|
||||||
table: Table<Task>
|
table: Table<TData>
|
||||||
}
|
}
|
||||||
defineProps<DataTablePaginationProps>()
|
defineProps<DataTablePaginationProps>()
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,13 @@ import {
|
||||||
interface DataTableRowActionsProps {
|
interface DataTableRowActionsProps {
|
||||||
row: Row<Task>
|
row: Row<Task>
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<DataTableRowActionsProps>()
|
const props = defineProps<DataTableRowActionsProps>()
|
||||||
|
|
||||||
|
defineEmits<{
|
||||||
|
(e: 'expand'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
const task = computed(() => taskSchema.parse(props.row.original))
|
const task = computed(() => taskSchema.parse(props.row.original))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -56,6 +61,9 @@ const task = computed(() => taskSchema.parse(props.row.original))
|
||||||
</DropdownMenuSubContent>
|
</DropdownMenuSubContent>
|
||||||
</DropdownMenuSub>
|
</DropdownMenuSub>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem @click="$emit('expand')">
|
||||||
|
Expand
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
Delete
|
Delete
|
||||||
<DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
|
<DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts" generic="TData">
|
||||||
import type { Table } from '@tanstack/vue-table'
|
import type { Table } from '@tanstack/vue-table'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import type { Task } from '../data/schema'
|
|
||||||
|
|
||||||
import { priorities, statuses } from '../data/data'
|
import { priorities, statuses } from '../data/data'
|
||||||
import DataTableFacetedFilter from './DataTableFacetedFilter.vue'
|
import DataTableFacetedFilter from './DataTableFacetedFilter.vue'
|
||||||
import DataTableViewOptions from './DataTableViewOptions.vue'
|
import DataTableViewOptions from './DataTableViewOptions.vue'
|
||||||
|
|
@ -11,7 +9,7 @@ import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
import { Input } from '@/lib/registry/new-york/ui/input'
|
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||||
|
|
||||||
interface DataTableToolbarProps {
|
interface DataTableToolbarProps {
|
||||||
table: Table<Task>
|
table: Table<TData>
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<DataTableToolbarProps>()
|
const props = defineProps<DataTableToolbarProps>()
|
||||||
|
|
|
||||||
43
apps/www/src/examples/tasks/components/DataTableUser.vue
Normal file
43
apps/www/src/examples/tasks/components/DataTableUser.vue
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Row } from '@tanstack/vue-table'
|
||||||
|
import { CheckIcon, Mail } from 'lucide-vue-next'
|
||||||
|
import { useClipboard } from '@vueuse/core'
|
||||||
|
import type { Task } from '../data/schema'
|
||||||
|
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from '@/lib/registry/new-york/ui/avatar'
|
||||||
|
import { Badge } from '@/lib/registry/new-york/ui/badge'
|
||||||
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
row: Row<Task>
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { id, firstName, lastName, email, avatar } = props.row.original.user
|
||||||
|
|
||||||
|
const { copy, copied } = useClipboard({ source: email })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<Avatar class="h-9 w-9">
|
||||||
|
<AvatarImage :src="avatar" alt="User image" />
|
||||||
|
<AvatarFallback>{{ firstName.slice(0, 1) }}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<span>{{ `${firstName} ${lastName}` }}</span>
|
||||||
|
<Button
|
||||||
|
class="h-7 w-7 [&_svg]:size-3.5"
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
@click="copy()"
|
||||||
|
>
|
||||||
|
<span class="sr-only">Copy</span>
|
||||||
|
<CheckIcon v-if="copied" />
|
||||||
|
<Mail v-else />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Badge class="mr-2" variant="outline">
|
||||||
|
#{{ id }}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts" generic="TData">
|
||||||
import type { Table } from '@tanstack/vue-table'
|
import type { Table } from '@tanstack/vue-table'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import type { Task } from '../data/schema'
|
|
||||||
import MixerHorizontalIcon from '~icons/radix-icons/mixer-horizontal'
|
import MixerHorizontalIcon from '~icons/radix-icons/mixer-horizontal'
|
||||||
|
|
||||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||||
|
|
@ -15,7 +14,7 @@ import {
|
||||||
} from '@/lib/registry/new-york/ui/dropdown-menu'
|
} from '@/lib/registry/new-york/ui/dropdown-menu'
|
||||||
|
|
||||||
interface DataTableViewOptionsProps {
|
interface DataTableViewOptionsProps {
|
||||||
table: Table<Task>
|
table: Table<TData>
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<DataTableViewOptionsProps>()
|
const props = defineProps<DataTableViewOptionsProps>()
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,8 @@ export const columns: ColumnDef<Task>[] = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'actions',
|
id: 'actions',
|
||||||
cell: ({ row }) => h(DataTableRowActions, { row }),
|
cell: ({ row }) => {
|
||||||
|
return h(DataTableRowActions, { row, onExpand: row.toggleExpanded })
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,13 @@ export const taskSchema = z.object({
|
||||||
status: z.string(),
|
status: z.string(),
|
||||||
label: z.string(),
|
label: z.string(),
|
||||||
priority: z.string(),
|
priority: z.string(),
|
||||||
|
user: z.object({
|
||||||
|
id: z.number(),
|
||||||
|
firstName: z.string(),
|
||||||
|
lastName: z.string(),
|
||||||
|
email: z.string().email(),
|
||||||
|
avatar: z.string(),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type Task = z.infer<typeof taskSchema>
|
export type Task = z.infer<typeof taskSchema>
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user