feat: example of dataTableFacetedFilters virtualization
This commit is contained in:
parent
1f1e6f339f
commit
a1fcbf8ea9
|
|
@ -2,7 +2,8 @@
|
||||||
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 { useVirtualList } from '@vueuse/core'
|
||||||
|
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'
|
||||||
|
|
||||||
|
|
@ -30,8 +31,16 @@ interface DataTableFacetedFilter {
|
||||||
|
|
||||||
const props = defineProps<DataTableFacetedFilter>()
|
const props = defineProps<DataTableFacetedFilter>()
|
||||||
|
|
||||||
|
function repeat<T>(array: T[], times: number) {
|
||||||
|
return Array.from({ length: times }, () => array).flat()
|
||||||
|
}
|
||||||
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[]))
|
||||||
|
const options = computed(() => repeat(props.options, 100))
|
||||||
|
|
||||||
|
const { list, containerProps, wrapperProps } = useVirtualList(options, {
|
||||||
|
itemHeight: 32,
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
@ -73,48 +82,53 @@ const selectedValues = computed(() => new Set(props.column?.getFilterValue() as
|
||||||
</Button>
|
</Button>
|
||||||
</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)) "
|
|
||||||
>
|
|
||||||
<CommandInput :placeholder="title" />
|
<CommandInput :placeholder="title" />
|
||||||
<CommandList>
|
<CommandList>
|
||||||
<CommandEmpty>No results found.</CommandEmpty>
|
<CommandEmpty>
|
||||||
<CommandGroup>
|
No results found.
|
||||||
<CommandItem
|
</CommandEmpty>
|
||||||
v-for="option in options"
|
<div v-bind="containerProps" class="max-h-52">
|
||||||
:key="option.value"
|
<div v-bind="wrapperProps">
|
||||||
:value="option"
|
<CommandGroup>
|
||||||
@select="() => {
|
<CommandItem
|
||||||
const isSelected = selectedValues.has(option.value)
|
v-for="option in list"
|
||||||
if (isSelected) {
|
:key="option.index"
|
||||||
selectedValues.delete(option.value)
|
style="height: 32px"
|
||||||
}
|
:value="option.data"
|
||||||
else {
|
@select="() => {
|
||||||
selectedValues.add(option.value)
|
const isSelected = selectedValues.has(option.data.value)
|
||||||
}
|
if (isSelected) {
|
||||||
const filterValues = Array.from(selectedValues)
|
selectedValues.delete(option.data.value)
|
||||||
column?.setFilterValue(
|
}
|
||||||
filterValues.length ? filterValues : undefined,
|
else {
|
||||||
)
|
selectedValues.add(option.data.value)
|
||||||
}"
|
}
|
||||||
>
|
const filterValues = Array.from(selectedValues)
|
||||||
<div
|
column?.setFilterValue(
|
||||||
:class="cn(
|
filterValues.length ? filterValues : undefined,
|
||||||
'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',
|
<div
|
||||||
)"
|
:class="cn(
|
||||||
>
|
'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
|
||||||
<CheckIcon :class="cn('h-4 w-4')" />
|
selectedValues.has(option.data.value)
|
||||||
</div>
|
? 'bg-primary text-primary-foreground'
|
||||||
<component :is="option.icon" v-if="option.icon" class="mr-2 h-4 w-4 text-muted-foreground" />
|
: 'opacity-50 [&_svg]:invisible',
|
||||||
<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) }}
|
<CheckIcon :class="cn('h-4 w-4')" />
|
||||||
</span>
|
</div>
|
||||||
</CommandItem>
|
<component :is="option.data.icon" v-if="option.data.icon" class="mr-2 h-4 w-4 text-muted-foreground" />
|
||||||
</CommandGroup>
|
<span>{{ option.data.label }}</span>
|
||||||
|
<span v-if="facets?.get(option.data.value)" class="ml-auto flex h-4 w-4 items-center justify-center font-mono text-xs">
|
||||||
|
{{ facets.get(option.data.value) }}
|
||||||
|
</span>
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<template v-if="selectedValues.size > 0">
|
<template v-if="selectedValues.size > 0">
|
||||||
<CommandSeparator />
|
<CommandSeparator />
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
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 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'
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user