feat: implement new Tasks BigData example for useVirtualList usage

This commit is contained in:
hrynevych.romann 2024-02-13 20:20:19 +02:00
parent 08d6ea6a53
commit 651e6ae19c
21 changed files with 26852 additions and 8 deletions

View File

@ -70,6 +70,13 @@ export default defineConfig({
rewrites: {
'content/(.*)': '(.*)',
},
vue: {
template: {
compilerOptions: {
isCustomElement: tag => tag === 'option.icon',
},
},
},
vite: {
css: {
postcss: {

View File

@ -23,6 +23,11 @@ const examples = [
href: '/examples/tasks',
code: 'https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/examples/tasks',
},
{
name: 'Tasks (Big Data)',
href: '/examples/big-data',
code: 'https://github.com/radix-vue/shadcn-vue/tree/dev/apps/www/src/examples/big-data',
},
{
name: 'Playground',
href: '/examples/playground',

View File

@ -0,0 +1,5 @@
<script setup>
import BigDataExample from "@/examples/big-data/Example.vue"
</script>
<BigDataExample />

View File

@ -0,0 +1,36 @@
<script setup lang="ts">
import tasks from './data/tasks.json'
import DataTable from './components/DataTable.vue'
import UserNav from './components/UserNav.vue'
import { columns } from './components/columns'
</script>
<template>
<div class="md:hidden">
<VPImage
alt="Tasks"
width="1280"
height="1214" class="block" :image="{
dark: '/examples/tasks-dark.png',
light: '/examples/tasks-light.png',
}"
/>
</div>
<div class="hidden h-full flex-1 flex-col space-y-8 p-8 md:flex">
<div class="flex items-center justify-between space-y-2">
<div>
<h2 class="text-2xl font-bold tracking-tight">
Welcome back!
</h2>
<p class="text-muted-foreground">
Here&apos;s a list of your tasks for this month!
</p>
</div>
<div class="flex items-center space-x-2">
<UserNav />
</div>
</div>
<DataTable :data="tasks" :columns="columns" />
</div>
</template>

View File

@ -0,0 +1,120 @@
<script setup lang="ts">
import type {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
} from '@tanstack/vue-table'
import {
FlexRender,
getCoreRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useVueTable,
} from '@tanstack/vue-table'
import { useVirtualList } from '@vueuse/core'
import { computed, ref } from 'vue'
import { type Task } from '../data/schema'
import DataTablePagination from './DataTablePagination.vue'
import DataTableToolbar from './DataTableToolbar.vue'
import { valueUpdater } from '@/lib/utils'
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/lib/registry/new-york/ui/table'
interface DataTableProps {
columns: ColumnDef<Task, any>[]
data: Task[]
}
const props = defineProps<DataTableProps>()
const sorting = ref<SortingState>([])
const columnFilters = ref<ColumnFiltersState>([])
const columnVisibility = ref<VisibilityState>({})
const rowSelection = ref({})
const table = useVueTable({
get data() { return props.data },
get columns() { return props.columns },
state: {
get sorting() { return sorting.value },
get columnFilters() { return columnFilters.value },
get columnVisibility() { return columnVisibility.value },
get rowSelection() { return rowSelection.value },
},
enableRowSelection: true,
onSortingChange: updaterOrValue => valueUpdater(updaterOrValue, sorting),
onColumnFiltersChange: updaterOrValue => valueUpdater(updaterOrValue, columnFilters),
onColumnVisibilityChange: updaterOrValue => valueUpdater(updaterOrValue, columnVisibility),
onRowSelectionChange: updaterOrValue => valueUpdater(updaterOrValue, rowSelection),
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
initialState: {
pagination: {
pageSize: 500,
},
},
})
const tableRows = computed(() => table.getRowModel().rows)
const { list, containerProps, wrapperProps } = useVirtualList(tableRows, {
itemHeight: 49,
overscan: 15,
})
</script>
<template>
<div class="space-y-4">
<DataTableToolbar :table="table" />
<div class="rounded-md border max-h-[75dvh]" v-bind="containerProps">
<Table v-bind="wrapperProps">
<TableHeader style="height: 49px">
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
<TableHead v-for="header in headerGroup.headers" :key="header.id">
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header" :props="header.getContext()" />
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
<template v-if="tableRows?.length">
<TableRow
v-for="row in list"
:key="row.data.id"
:data-state="row.data.getIsSelected() && 'selected'"
>
<TableCell v-for="cell in row.data.getVisibleCells()" :key="cell.id">
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" />
</TableCell>
</TableRow>
</template>
<TableRow v-else>
<TableCell
:colspan="columns.length"
class="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
<DataTablePagination :table="table" />
</div>
</template>

View File

@ -0,0 +1,69 @@
<script setup lang="ts">
import type { Column } from '@tanstack/vue-table'
import { type Task } from '../data/schema'
import ArrowDownIcon from '~icons/radix-icons/arrow-down'
import ArrowUpIcon from '~icons/radix-icons/arrow-up'
import CaretSortIcon from '~icons/radix-icons/caret-sort'
import EyeNoneIcon from '~icons/radix-icons/eye-none'
import { cn } from '@/lib/utils'
import { Button } from '@/lib/registry/new-york/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/lib/registry/new-york/ui/dropdown-menu'
interface DataTableColumnHeaderProps {
column: Column<Task, any>
title: string
}
defineProps<DataTableColumnHeaderProps>()
</script>
<script lang="ts">
export default {
inheritAttrs: false,
}
</script>
<template>
<div v-if="column.getCanSort()" :class="cn('flex items-center space-x-2', $attrs.class ?? '')">
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button
variant="ghost"
size="sm"
class="-ml-3 h-8 data-[state=open]:bg-accent"
>
<span>{{ title }}</span>
<ArrowDownIcon v-if="column.getIsSorted() === 'desc'" class="ml-2 h-4 w-4" />
<ArrowUpIcon v-else-if=" column.getIsSorted() === 'asc'" class="ml-2 h-4 w-4" />
<CaretSortIcon v-else class="ml-2 h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem @click="column.toggleSorting(false)">
<ArrowUpIcon class="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
Asc
</DropdownMenuItem>
<DropdownMenuItem @click="column.toggleSorting(true)">
<ArrowDownIcon class="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
Desc
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem @click="column.toggleVisibility(false)">
<EyeNoneIcon class="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
Hide
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
<div v-else :class="$attrs.class">
{{ title }}
</div>
</template>

View File

@ -0,0 +1,135 @@
<script setup lang="ts">
import type { Column } from '@tanstack/vue-table'
import type { Component } from 'vue'
import { computed } from 'vue'
import { type Task } from '../data/schema'
import PlusCircledIcon from '~icons/radix-icons/plus-circled'
import CheckIcon from '~icons/radix-icons/check'
import { Badge } from '@/lib/registry/new-york/ui/badge'
import { Button } from '@/lib/registry/new-york/ui/button'
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from '@/lib/registry/new-york/ui/command'
import {
Popover,
PopoverContent,
PopoverTrigger,
} from '@/lib/registry/new-york/ui/popover'
import { Separator } from '@/lib/registry/new-york/ui/separator'
import { cn } from '@/lib/utils'
interface DataTableFacetedFilter {
column?: Column<Task, any>
title?: string
options: {
label: string
value: string
icon?: Component
}[]
}
const props = defineProps<DataTableFacetedFilter>()
const facets = computed(() => props.column?.getFacetedUniqueValues())
const selectedValues = computed(() => new Set(props.column?.getFilterValue() as string[]))
</script>
<template>
<Popover>
<PopoverTrigger as-child>
<Button variant="outline" size="sm" class="h-8 border-dashed">
<PlusCircledIcon class="mr-2 h-4 w-4" />
{{ title }}
<template v-if="selectedValues.size > 0">
<Separator orientation="vertical" class="mx-2 h-4" />
<Badge
variant="secondary"
class="rounded-sm px-1 font-normal lg:hidden"
>
{{ selectedValues.size }}
</Badge>
<div class="hidden space-x-1 lg:flex">
<Badge
v-if="selectedValues.size > 2"
variant="secondary"
class="rounded-sm px-1 font-normal"
>
{{ selectedValues.size }} selected
</Badge>
<template v-else>
<Badge
v-for="option in options
.filter((option) => selectedValues.has(option.value))"
:key="option.value"
variant="secondary"
class="rounded-sm px-1 font-normal"
>
{{ option.label }}
</Badge>
</template>
</div>
</template>
</Button>
</PopoverTrigger>
<PopoverContent class="w-[200px] p-0" align="start">
<Command
:filter-function="(list: DataTableFacetedFilter['options'], term) => list.filter(i => i.label.toLowerCase()?.includes(term)) "
>
<CommandInput :placeholder="title" />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup>
<CommandItem
v-for="option in options"
:key="option.value"
:value="option"
@select="() => {
const isSelected = selectedValues.has(option.value)
if (isSelected) {
selectedValues.delete(option.value)
}
else {
selectedValues.add(option.value)
}
const filterValues = Array.from(selectedValues)
column?.setFilterValue(
filterValues.length ? filterValues : undefined,
)
}"
>
<div
:class="cn(
'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
selectedValues.has(option.value)
? 'bg-primary text-primary-foreground'
: 'opacity-50 [&_svg]:invisible',
)"
>
<CheckIcon :class="cn('h-4 w-4')" />
</div>
<option.icon v-if="option.icon" class="mr-2 h-4 w-4 text-muted-foreground" />
<span>{{ option.label }}</span>
<span v-if="facets?.get(option.value)" class="ml-auto flex h-4 w-4 items-center justify-center font-mono text-xs">
{{ facets.get(option.value) }}
</span>
</CommandItem>
</CommandGroup>
<template v-if="selectedValues.size > 0">
<CommandSeparator />
<CommandGroup>
<CommandItem
:value="{ label: 'Clear filters' }"
class="justify-center text-center"
@select="column?.setFilterValue(undefined)"
>
Clear filters
</CommandItem>
</CommandGroup>
</template>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</template>

View File

@ -0,0 +1,93 @@
<script setup lang="ts">
import { type Table } from '@tanstack/vue-table'
import { type Task } from '../data/schema'
import ChevronLeftIcon from '~icons/radix-icons/chevron-left'
import ChevronRightIcon from '~icons/radix-icons/chevron-right'
import DoubleArrowLeftIcon from '~icons/radix-icons/double-arrow-left'
import DoubleArrowRightIcon from '~icons/radix-icons/double-arrow-right'
import { Button } from '@/lib/registry/new-york/ui/button'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/lib/registry/new-york/ui/select'
interface DataTablePaginationProps {
table: Table<Task>
}
defineProps<DataTablePaginationProps>()
</script>
<template>
<div class="flex items-center justify-between px-2">
<div class="flex-1 text-sm text-muted-foreground">
{{ table.getFilteredSelectedRowModel().rows.length }} of
{{ table.getFilteredRowModel().rows.length }} row(s) selected.
</div>
<div class="flex items-center space-x-6 lg:space-x-8">
<div class="flex items-center space-x-2">
<p class="text-sm font-medium">
Rows per page
</p>
<Select
:model-value="`${table.getState().pagination.pageSize}`"
@update:model-value="table.setPageSize"
>
<SelectTrigger class="h-8 w-[80px]">
<SelectValue :placeholder="`${table.getState().pagination.pageSize}`" />
</SelectTrigger>
<SelectContent side="top">
<SelectItem v-for="pageSize in [500, 1000, 2000]" :key="pageSize" :value="`${pageSize}`">
{{ pageSize }}
</SelectItem>
</SelectContent>
</Select>
</div>
<div class="flex w-[100px] items-center justify-center text-sm font-medium">
Page {{ table.getState().pagination.pageIndex + 1 }} of
{{ table.getPageCount() }}
</div>
<div class="flex items-center space-x-2">
<Button
variant="outline"
class="hidden h-8 w-8 p-0 lg:flex"
:disabled="!table.getCanPreviousPage()"
@click="table.setPageIndex(0)"
>
<span class="sr-only">Go to first page</span>
<DoubleArrowLeftIcon class="h-4 w-4" />
</Button>
<Button
variant="outline"
class="h-8 w-8 p-0"
:disabled="!table.getCanPreviousPage()"
@click="table.previousPage()"
>
<span class="sr-only">Go to previous page</span>
<ChevronLeftIcon class="h-4 w-4" />
</Button>
<Button
variant="outline"
class="h-8 w-8 p-0"
:disabled="!table.getCanNextPage()"
@click="table.nextPage()"
>
<span class="sr-only">Go to next page</span>
<ChevronRightIcon class="h-4 w-4" />
</Button>
<Button
variant="outline"
class="hidden h-8 w-8 p-0 lg:flex"
:disabled="!table.getCanNextPage()"
@click="table.setPageIndex(table.getPageCount() - 1)"
>
<span class="sr-only">Go to last page</span>
<DoubleArrowRightIcon class="h-4 w-4" />
</Button>
</div>
</div>
</div>
</template>

View File

@ -0,0 +1,65 @@
<script setup lang="ts">
import type { Row } from '@tanstack/vue-table'
import { computed } from 'vue'
import { labels } from '../data/data'
import { taskSchema } from '../data/schema'
import { type Task } from '../data/schema'
import DotsHorizontalIcon from '~icons/radix-icons/dots-horizontal'
import { Button } from '@/lib/registry/new-york/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from '@/lib/registry/new-york/ui/dropdown-menu'
interface DataTableRowActionsProps {
row: Row<Task>
}
const props = defineProps<DataTableRowActionsProps>()
const task = computed(() => taskSchema.parse(props.row.original))
</script>
<template>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button
variant="ghost"
class="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
>
<DotsHorizontalIcon class="h-4 w-4" />
<span class="sr-only">Open menu</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" class="w-[160px]">
<DropdownMenuItem>Edit</DropdownMenuItem>
<DropdownMenuItem>Make a copy</DropdownMenuItem>
<DropdownMenuItem>Favorite</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuSub>
<DropdownMenuSubTrigger>Labels</DropdownMenuSubTrigger>
<DropdownMenuSubContent>
<DropdownMenuRadioGroup :value="task.label">
<DropdownMenuRadioItem v-for="label in labels" :key="label.value" :value="label.value">
{{ label.label }}
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuSeparator />
<DropdownMenuItem>
Delete
<DropdownMenuShortcut></DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</template>

View File

@ -0,0 +1,56 @@
<script setup lang="ts">
import { type Table } from '@tanstack/vue-table'
import { computed } from 'vue'
import { type Task } from '../data/schema'
import { priorities, statuses } from '../data/data'
import DataTableFacetedFilter from './DataTableFacetedFilter.vue'
import DataTableViewOptions from './DataTableViewOptions.vue'
import Cross2Icon from '~icons/radix-icons/cross-2'
import { Button } from '@/lib/registry/new-york/ui/button'
import { Input } from '@/lib/registry/new-york/ui/input'
interface DataTableToolbarProps {
table: Table<Task>
}
const props = defineProps<DataTableToolbarProps>()
const isFiltered = computed(() => props.table.getState().columnFilters.length > 0)
</script>
<template>
<div class="flex items-center justify-between">
<div class="flex flex-1 items-center space-x-2">
<Input
placeholder="Filter tasks..."
:model-value="(table.getColumn('title')?.getFilterValue() as string) ?? ''"
class="h-8 w-[150px] lg:w-[250px]"
@input="table.getColumn('title')?.setFilterValue($event.target.value)"
/>
<DataTableFacetedFilter
v-if="table.getColumn('status')"
:column="table.getColumn('status')"
title="Status"
:options="statuses"
/>
<DataTableFacetedFilter
v-if="table.getColumn('priority')"
:column="table.getColumn('priority')"
title="Priority"
:options="priorities"
/>
<Button
v-if="isFiltered"
variant="ghost"
class="h-8 px-2 lg:px-3"
@click="table.resetColumnFilters()"
>
Reset
<Cross2Icon class="ml-2 h-4 w-4" />
</Button>
</div>
<DataTableViewOptions :table="table" />
</div>
</template>

View File

@ -0,0 +1,57 @@
<script setup lang="ts">
import type { Table } from '@tanstack/vue-table'
import { computed } from 'vue'
import { type Task } from '../data/schema'
import MixerHorizontalIcon from '~icons/radix-icons/mixer-horizontal'
import { Button } from '@/lib/registry/new-york/ui/button'
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/lib/registry/new-york/ui/dropdown-menu'
interface DataTableViewOptionsProps {
table: Table<Task>
}
const props = defineProps<DataTableViewOptionsProps>()
const columns = computed(() => props.table.getAllColumns()
.filter(
column =>
typeof column.accessorFn !== 'undefined' && column.getCanHide(),
))
</script>
<template>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button
variant="outline"
size="sm"
class="ml-auto hidden h-8 lg:flex"
>
<MixerHorizontalIcon class="mr-2 h-4 w-4" />
View
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" class="w-[150px]">
<DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuCheckboxItem
v-for="column in columns"
:key="column.id"
class="capitalize"
:checked="column.getIsVisible()"
@update:checked="(value) => column.toggleVisibility(!!value)"
>
{{ column.id }}
</DropdownMenuCheckboxItem>
</DropdownMenuContent>
</DropdownMenu>
</template>

View File

@ -0,0 +1,64 @@
<script setup lang="ts">
import {
Avatar,
AvatarFallback,
AvatarImage,
} from '@/lib/registry/new-york/ui/avatar'
import { Button } from '@/lib/registry/new-york/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuTrigger,
} from '@/lib/registry/new-york/ui/dropdown-menu'
</script>
<template>
<DropdownMenu>
<DropdownMenuTrigger as-child>
<Button variant="ghost" class="relative h-8 w-8 rounded-full">
<Avatar class="h-9 w-9">
<AvatarImage src="/avatars/03.png" alt="@shadcn" />
<AvatarFallback>SC</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent class="w-56" align="end">
<DropdownMenuLabel class="font-normal flex">
<div class="flex flex-col space-y-1">
<p class="text-sm font-medium leading-none">
shadcn
</p>
<p class="text-xs leading-none text-muted-foreground">
m@example.com
</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuGroup>
<DropdownMenuItem>
Profile
<DropdownMenuShortcut>P</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Billing
<DropdownMenuShortcut>B</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
Settings
<DropdownMenuShortcut>S</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>New Team</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem>
Log out
<DropdownMenuShortcut>Q</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</template>

View File

@ -0,0 +1,86 @@
import type { ColumnDef } from '@tanstack/vue-table'
import { h } from 'vue'
import { labels, priorities, statuses } from '../data/data'
import type { Task } from '../data/schema'
import DataTableColumnHeader from './DataTableColumnHeader.vue'
import DataTableRowActions from './DataTableRowActions.vue'
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
import { Badge } from '@/lib/registry/new-york/ui/badge'
export const columns: ColumnDef<Task>[] = [
{
id: 'select',
header: ({ table }) => h(Checkbox,
{ 'checked': table.getIsAllPageRowsSelected(), 'onUpdate:checked': value => table.toggleAllPageRowsSelected(!!value), 'ariaLabel': 'Select all', 'class': 'translate-y-0.5' }),
cell: ({ row }) => h(Checkbox,
{ 'checked': row.getIsSelected(), 'onUpdate:checked': value => row.toggleSelected(!!value), 'ariaLabel': 'Select row', 'class': 'translate-y-0.5' }),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: 'id',
header: ({ column }) => h(DataTableColumnHeader, { column, title: 'Task' }),
cell: ({ row }) => h('div', { class: 'w-20' }, row.getValue('id')),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: 'title',
header: ({ column }) => h(DataTableColumnHeader, { column, title: 'Title' }),
cell: ({ row }) => {
const label = labels.find(label => label.value === row.original.label)
return h('div', { class: 'flex space-x-2' }, [
label && h(Badge, { variant: 'outline' }, label.label),
h('span', { class: 'max-w-[500px] truncate font-medium' }, row.getValue('title')),
])
},
},
{
accessorKey: 'status',
header: ({ column }) => h(DataTableColumnHeader, { column, title: 'Status' }),
cell: ({ row }) => {
const status = statuses.find(
status => status.value === row.getValue('status'),
)
if (!status)
return null
return h('div', { class: 'flex w-[100px] items-center' }, [
status.icon && h(status.icon, { class: 'mr-2 h-4 w-4 text-muted-foreground' }),
h('span', status.label),
])
},
filterFn: (row, id, value) => {
return value.includes(row.getValue(id))
},
},
{
accessorKey: 'priority',
header: ({ column }) => h(DataTableColumnHeader, { column, title: 'Priority' }),
cell: ({ row }) => {
const priority = priorities.find(
priority => priority.value === row.getValue('priority'),
)
if (!priority)
return null
return h('div', { class: 'flex items-center' }, [
priority.icon && h(priority.icon, { class: 'mr-2 h-4 w-4 text-muted-foreground' }),
h('span', priority.label),
])
},
filterFn: (row, id, value) => {
return value.includes(row.getValue(id))
},
},
{
id: 'actions',
cell: ({ row }) => h(DataTableRowActions, { row }),
},
]

View File

@ -0,0 +1,70 @@
import { h } from 'vue'
import ArrowDownIcon from '~icons/radix-icons/arrow-down'
import ArrowRightIcon from '~icons/radix-icons/arrow-right'
import ArrowUpIcon from '~icons/radix-icons/arrow-up'
import CheckCircledIcon from '~icons/radix-icons/check-circled'
import CircleIcon from '~icons/radix-icons/circle'
import CrossCircledIcon from '~icons/radix-icons/cross-circled'
import QuestionMarkCircledIcon from '~icons/radix-icons/question-mark-circled'
import StopwatchIcon from '~icons/radix-icons/stopwatch'
export const labels = [
{
value: 'bug',
label: 'Bug',
},
{
value: 'feature',
label: 'Feature',
},
{
value: 'documentation',
label: 'Documentation',
},
]
export const statuses = [
{
value: 'backlog',
label: 'Backlog',
icon: h(QuestionMarkCircledIcon),
},
{
value: 'todo',
label: 'Todo',
icon: h(CircleIcon),
},
{
value: 'in progress',
label: 'In Progress',
icon: h(StopwatchIcon),
},
{
value: 'done',
label: 'Done',
icon: h(CheckCircledIcon),
},
{
value: 'canceled',
label: 'Canceled',
icon: h(CrossCircledIcon),
},
]
export const priorities = [
{
label: 'Low',
value: 'low',
icon: h(ArrowDownIcon),
},
{
label: 'Medium',
value: 'medium',
icon: h(ArrowRightIcon),
},
{
label: 'High',
value: 'high',
icon: h(ArrowUpIcon),
},
]

View File

@ -0,0 +1,13 @@
import { z } from 'zod'
// We're keeping a simple non-relational schema here.
// IRL, you will have a schema for your data models.
export const taskSchema = z.object({
id: z.string(),
title: z.string(),
status: z.string(),
label: z.string(),
priority: z.string(),
})
export type Task = z.infer<typeof taskSchema>

File diff suppressed because it is too large Load Diff

View File

@ -40,7 +40,7 @@ defineProps<DataTablePaginationProps>()
<SelectValue :placeholder="`${table.getState().pagination.pageSize}`" />
</SelectTrigger>
<SelectContent side="top">
<SelectItem v-for="pageSize in [10, 20, 30, 40, 50]" :key="pageSize" :value="`${pageSize}`">
<SelectItem v-for="pageSize in [10, 20, 30, 40, 50, 100, 1000]" :key="pageSize" :value="`${pageSize}`">
{{ pageSize }}
</SelectItem>
</SelectContent>

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ const props = defineProps<{
</script>
<template>
<div class="relative w-full overflow-auto">
<div class="relative w-full">
<table :class="cn('w-full caption-bottom text-sm', props.class)">
<slot />
</table>

View File

@ -8,7 +8,7 @@ const props = defineProps<{
</script>
<template>
<div class="relative w-full overflow-auto">
<div class="relative w-full">
<table :class="cn('w-full caption-bottom text-sm', props.class)">
<slot />
</table>

View File

@ -179,7 +179,7 @@ importers:
version: 2.2.1
tailwindcss:
specifier: ^3.4.1
version: 3.4.1(ts-node@10.9.2)
version: 3.4.1
tsx:
specifier: ^4.7.0
version: 4.7.0
@ -1123,6 +1123,7 @@ packages:
engines: {node: '>=12'}
dependencies:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@docsearch/css@3.5.2:
resolution: {integrity: sha512-SPiDHaWKQZpwR2siD0KQUwlStvIAnEyK6tAE2h2Wuoq8ue9skzhlyVQ1ddzOxX6khULnAALDiR/isSF3bnuciA==}
@ -2057,6 +2058,7 @@ packages:
dependencies:
'@jridgewell/resolve-uri': 3.1.1
'@jridgewell/sourcemap-codec': 1.4.15
dev: true
/@jsdevtools/ez-spawn@3.0.4:
resolution: {integrity: sha512-f5DRIOZf7wxogefH03RjMPMdBF7ADTWUMoOs9kaJo06EfwF+aFhMZMDZxHg/Xe12hptN9xoZjGso2fdjapBRIA==}
@ -3103,6 +3105,20 @@ packages:
picomatch: 2.3.1
dev: true
/@rollup/pluginutils@5.1.0:
resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
peerDependenciesMeta:
rollup:
optional: true
dependencies:
'@types/estree': 1.0.5
estree-walker: 2.0.2
picomatch: 2.3.1
dev: false
/@rollup/pluginutils@5.1.0(rollup@3.29.4):
resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
engines: {node: '>=14.0.0'}
@ -3130,12 +3146,14 @@ packages:
estree-walker: 2.0.2
picomatch: 2.3.1
rollup: 4.9.6
dev: true
/@rollup/rollup-android-arm-eabi@4.9.6:
resolution: {integrity: sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==}
cpu: [arm]
os: [android]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-android-arm64@4.9.6:
@ -3143,6 +3161,7 @@ packages:
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-darwin-arm64@4.9.6:
@ -3150,6 +3169,7 @@ packages:
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-darwin-x64@4.9.6:
@ -3157,6 +3177,7 @@ packages:
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-arm-gnueabihf@4.9.6:
@ -3164,6 +3185,7 @@ packages:
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-arm64-gnu@4.9.6:
@ -3171,6 +3193,7 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-arm64-musl@4.9.6:
@ -3178,6 +3201,7 @@ packages:
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-riscv64-gnu@4.9.6:
@ -3185,6 +3209,7 @@ packages:
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-x64-gnu@4.9.6:
@ -3192,6 +3217,7 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-linux-x64-musl@4.9.6:
@ -3199,6 +3225,7 @@ packages:
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-win32-arm64-msvc@4.9.6:
@ -3206,6 +3233,7 @@ packages:
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-win32-ia32-msvc@4.9.6:
@ -3213,6 +3241,7 @@ packages:
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@rollup/rollup-win32-x64-msvc@4.9.6:
@ -3220,6 +3249,7 @@ packages:
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/@rushstack/eslint-patch@1.7.2:
@ -3355,15 +3385,19 @@ packages:
/@tsconfig/node10@1.0.9:
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
dev: true
/@tsconfig/node12@1.0.11:
resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
dev: true
/@tsconfig/node14@1.0.3:
resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
dev: true
/@tsconfig/node16@1.0.4:
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
dev: true
/@tufjs/canonical-json@2.0.0:
resolution: {integrity: sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==}
@ -3719,6 +3753,7 @@ packages:
/@types/node@20.5.1:
resolution: {integrity: sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==}
dev: true
/@types/normalize-package-data@2.4.4:
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
@ -4581,6 +4616,7 @@ packages:
/acorn-walk@8.3.2:
resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==}
engines: {node: '>=0.4.0'}
dev: true
/acorn@8.11.3:
resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
@ -4790,6 +4826,7 @@ packages:
/arg@4.1.3:
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
dev: true
/arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
@ -5690,6 +5727,7 @@ packages:
/create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
dev: true
/cross-spawn@5.1.0:
resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
@ -6368,6 +6406,7 @@ packages:
/diff@4.0.2:
resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
engines: {node: '>=0.3.1'}
dev: true
/diff@5.1.0:
resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
@ -8958,6 +8997,7 @@ packages:
/make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
dev: true
/make-fetch-happen@13.0.0:
resolution: {integrity: sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A==}
@ -10447,6 +10487,22 @@ packages:
camelcase-css: 2.0.1
postcss: 8.4.33
/postcss-load-config@4.0.2(postcss@8.4.33):
resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
engines: {node: '>= 14'}
peerDependencies:
postcss: '>=8.0.9'
ts-node: '>=9.0.0'
peerDependenciesMeta:
postcss:
optional: true
ts-node:
optional: true
dependencies:
lilconfig: 3.0.0
postcss: 8.4.33
yaml: 2.3.4
/postcss-load-config@4.0.2(postcss@8.4.33)(ts-node@10.9.2):
resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==}
engines: {node: '>= 14'}
@ -10463,6 +10519,7 @@ packages:
postcss: 8.4.33
ts-node: 10.9.2(@types/node@20.5.1)(typescript@5.3.3)
yaml: 2.3.4
dev: true
/postcss-merge-longhand@6.0.2(postcss@8.4.33):
resolution: {integrity: sha512-+yfVB7gEM8SrCo9w2lCApKIEzrTKl5yS1F4yGhV3kSim6JzbfLGJyhR1B6X+6vOT0U33Mgx7iv4X9MVWuaSAfw==}
@ -11227,6 +11284,7 @@ packages:
'@rollup/rollup-win32-ia32-msvc': 4.9.6
'@rollup/rollup-win32-x64-msvc': 4.9.6
fsevents: 2.3.3
dev: true
/run-applescript@7.0.0:
resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==}
@ -11879,10 +11937,10 @@ packages:
peerDependencies:
tailwindcss: '>=3.0.0 || insiders'
dependencies:
tailwindcss: 3.4.1(ts-node@10.9.2)
tailwindcss: 3.4.1
dev: false
/tailwindcss@3.4.1(ts-node@10.9.2):
/tailwindcss@3.4.1:
resolution: {integrity: sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==}
engines: {node: '>=14.0.0'}
hasBin: true
@ -11904,7 +11962,7 @@ packages:
postcss: 8.4.33
postcss-import: 15.1.0(postcss@8.4.33)
postcss-js: 4.0.1(postcss@8.4.33)
postcss-load-config: 4.0.2(postcss@8.4.33)(ts-node@10.9.2)
postcss-load-config: 4.0.2(postcss@8.4.33)
postcss-nested: 6.0.1(postcss@8.4.33)
postcss-selector-parser: 6.0.15
resolve: 1.22.8
@ -12191,6 +12249,7 @@ packages:
typescript: 5.3.3
v8-compile-cache-lib: 3.0.1
yn: 3.1.1
dev: true
/tsconfck@3.0.1(typescript@5.3.3):
resolution: {integrity: sha512-7ppiBlF3UEddCLeI1JRx5m2Ryq+xk4JrZuq4EuYXykipebaq1dV0Fhgr1hb7CkmHt32QSgOZlcqVLEtHBG4/mg==}
@ -12452,7 +12511,7 @@ packages:
/unimport@3.7.1:
resolution: {integrity: sha512-V9HpXYfsZye5bPPYUgs0Otn3ODS1mDUciaBlXljI4C2fTwfFpvFZRywmlOu943puN9sncxROMZhsZCjNXEpzEQ==}
dependencies:
'@rollup/pluginutils': 5.1.0(rollup@4.9.6)
'@rollup/pluginutils': 5.1.0
acorn: 8.11.3
escape-string-regexp: 5.0.0
estree-walker: 3.0.3
@ -12784,6 +12843,7 @@ packages:
/v8-compile-cache-lib@3.0.1:
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
dev: true
/validate-npm-package-license@3.0.4:
resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
@ -13655,6 +13715,7 @@ packages:
/yn@3.1.1:
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
engines: {node: '>=6'}
dev: true
/yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}