103 lines
2.9 KiB
Vue
103 lines
2.9 KiB
Vue
<script setup>
|
|
import { createReusableTemplate, useMediaQuery, useUrlSearchParams, watchDebounced } from '@vueuse/core'
|
|
import { safeDestr } from 'destr'
|
|
import { Check, ChevronsUpDown } from 'lucide-vue-next'
|
|
import { VList } from 'virtua/vue'
|
|
|
|
const emit = defineEmits(['change'])
|
|
|
|
const [TriggerTemplate, TriggerComponent] = createReusableTemplate()
|
|
const [FilterTemplate, FilterComponent] = createReusableTemplate()
|
|
|
|
const isDesktop = useMediaQuery('(min-width: 640px)')
|
|
|
|
const links = ref([])
|
|
const isOpen = ref(false)
|
|
const selectedLinks = ref([])
|
|
|
|
async function getLinks() {
|
|
links.value = await useAPI('/api/link/search')
|
|
}
|
|
|
|
onMounted(() => {
|
|
getLinks()
|
|
})
|
|
|
|
watchDebounced(selectedLinks, (value) => {
|
|
emit('change', 'slug', value.join(','))
|
|
}, { debounce: 500, maxWait: 1000 })
|
|
|
|
function restoreFilters() {
|
|
const searchParams = useUrlSearchParams('history')
|
|
if (searchParams.filters) {
|
|
const filters = safeDestr(searchParams.filters)
|
|
if (filters.slug) {
|
|
selectedLinks.value = filters.slug.split(',')
|
|
}
|
|
}
|
|
}
|
|
|
|
onBeforeMount(() => {
|
|
restoreFilters()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<TriggerTemplate>
|
|
<Button
|
|
variant="outline"
|
|
role="combobox"
|
|
:aria-expanded="isOpen"
|
|
class="flex justify-between px-3 w-full sm:w-48"
|
|
>
|
|
<div class="flex-1 text-left truncate" :class="selectedLinks.length ? 'text-foreground' : 'text-muted-foreground'">
|
|
{{ selectedLinks.length ? selectedLinks.join(', ') : $t('dashboard.filter_placeholder') }}
|
|
</div>
|
|
<ChevronsUpDown class="ml-2 w-4 h-4 opacity-50 shrink-0" />
|
|
</Button>
|
|
</TriggerTemplate>
|
|
<FilterTemplate>
|
|
<Command v-model="selectedLinks" multiple>
|
|
<CommandInput :placeholder="selectedLinks.length ? selectedLinks.join(', ') : $t('dashboard.filter_placeholder')" />
|
|
<CommandEmpty>No link found.</CommandEmpty>
|
|
<CommandList :class="{ 'max-h-none': !isDesktop }">
|
|
<CommandGroup>
|
|
<VList
|
|
v-slot="{ item: link }"
|
|
:data="links"
|
|
:style="{ height: isDesktop ? '292px' : '420px' }"
|
|
>
|
|
<CommandItem
|
|
:value="link.slug"
|
|
>
|
|
<Check
|
|
:class="cn(
|
|
'mr-2 h-4 w-4',
|
|
selectedLinks.includes(link.slug) ? 'opacity-100' : 'opacity-0',
|
|
)"
|
|
/>
|
|
{{ link.slug }}
|
|
</CommandItem>
|
|
</VList>
|
|
</CommandGroup>
|
|
</CommandList>
|
|
</Command>
|
|
</FilterTemplate>
|
|
<Popover v-if="isDesktop" v-model:open="isOpen">
|
|
<PopoverTrigger as-child>
|
|
<TriggerComponent />
|
|
</PopoverTrigger>
|
|
<PopoverContent class="p-0 w-full sm:w-48">
|
|
<FilterComponent />
|
|
</PopoverContent>
|
|
</Popover>
|
|
|
|
<Drawer v-else v-model:open="isOpen">
|
|
<DrawerTrigger as-child>
|
|
<TriggerComponent />
|
|
</DrawerTrigger>
|
|
<DrawerContent class="h-[500px]">
|
|
<FilterComponent />
|
|
</DrawerContent>
|
|
</Drawer>
|
|
</template>
|