feat(meilisearch): add search filter to multi-select dropdown
This commit is contained in:
2 files changed
+28
-13
No files matched your search
@@ -376,13 +376,13 @@ onMounted(() => {
|
||||
><span class="font-medium">{{ t("meilisearch.description") }}</span
|
||||
><Input v-model="form.description" :placeholder="t('meilisearch.descriptionPlaceholder')" /><span class="text-[11px] leading-4 text-muted-foreground">{{ t("meilisearch.descriptionHelp") }}</span></label
|
||||
>
|
||||
<div class="grid gap-1">
|
||||
<div class="grid min-w-0 gap-1">
|
||||
<span class="font-medium">{{ t("meilisearch.indexes") }}</span
|
||||
><MeilisearchMultiSelect v-model="form.indexes" :options="indexOptions" :disabled="Boolean(editing)" :loading="indexesLoading" :error="indexLoadError" :placeholder="t('meilisearch.indexesPlaceholder')" /><span class="text-[11px] leading-4 text-muted-foreground">{{
|
||||
t("meilisearch.indexesHelp")
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="grid gap-1">
|
||||
<div class="grid min-w-0 gap-1">
|
||||
<span class="font-medium">{{ t("meilisearch.actions") }}</span
|
||||
><MeilisearchMultiSelect v-model="form.actions" :options="[...KEY_ACTION_OPTIONS]" :disabled="Boolean(editing)" :placeholder="t('meilisearch.actionsPlaceholder')" /><span class="text-[11px] leading-4 text-muted-foreground">{{ t("meilisearch.actionsHelp") }}</span>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { Check, ChevronDown, Loader2 } from "@lucide/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
|
||||
const props = withDefaults(
|
||||
@@ -20,9 +21,20 @@ const props = withDefaults(
|
||||
);
|
||||
|
||||
const emit = defineEmits<{ "update:modelValue": [value: string[]] }>();
|
||||
const { t } = useI18n();
|
||||
const open = ref(false);
|
||||
const searchQuery = ref("");
|
||||
const selected = computed(() => new Set(props.modelValue));
|
||||
const displayValue = computed(() => props.modelValue.join(", "));
|
||||
const filteredOptions = computed(() => {
|
||||
const query = searchQuery.value.trim().toLocaleLowerCase();
|
||||
if (!query) return props.options;
|
||||
return props.options.filter((option) => option.toLocaleLowerCase().includes(query));
|
||||
});
|
||||
|
||||
watch(open, (isOpen) => {
|
||||
if (!isOpen) searchQuery.value = "";
|
||||
});
|
||||
|
||||
function toggle(value: string) {
|
||||
const next = new Set(props.modelValue);
|
||||
@@ -40,7 +52,7 @@ function toggle(value: string) {
|
||||
<PopoverTrigger as-child>
|
||||
<button
|
||||
type="button"
|
||||
class="flex min-h-9 w-full items-center gap-2 rounded-md border border-input bg-background px-3 py-2 text-left text-sm shadow-sm outline-none hover:bg-muted/30 focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
class="flex min-h-9 min-w-0 w-full max-w-full items-center gap-2 rounded-md border border-input bg-background px-3 py-2 text-left text-sm shadow-sm outline-none hover:bg-muted/30 focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
:disabled="disabled"
|
||||
>
|
||||
<span class="min-w-0 flex-1 truncate" :class="modelValue.length ? 'font-mono text-xs' : 'text-muted-foreground'">{{ displayValue || placeholder }}</span>
|
||||
@@ -48,17 +60,20 @@ function toggle(value: string) {
|
||||
<ChevronDown v-else class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="start" side="bottom" class="z-[80] w-[var(--reka-popover-trigger-width)] gap-1 p-1.5" @open-auto-focus.prevent>
|
||||
<PopoverContent align="start" side="bottom" class="z-[80] w-96 max-w-[calc(100vw-2rem)] gap-1 overflow-hidden p-1.5" @open-auto-focus.prevent>
|
||||
<div v-if="error" class="px-2 py-2 text-xs text-destructive">{{ error }}</div>
|
||||
<div v-if="loading" class="flex items-center gap-2 px-2 py-3 text-xs text-muted-foreground"><Loader2 class="h-3.5 w-3.5 animate-spin" />{{ placeholder }}</div>
|
||||
<div v-else class="max-h-60 overflow-y-auto py-0.5">
|
||||
<button v-for="option in options" :key="option" type="button" class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left font-mono text-xs hover:bg-accent focus-visible:bg-accent focus-visible:outline-none" @click="toggle(option)">
|
||||
<span class="flex h-4 w-4 shrink-0 items-center justify-center rounded border" :class="selected.has(option) ? 'border-primary bg-primary text-primary-foreground' : 'border-input'">
|
||||
<Check v-if="selected.has(option)" class="h-3 w-3" />
|
||||
</span>
|
||||
<span class="min-w-0 flex-1 truncate">{{ option }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<template v-else>
|
||||
<input v-model="searchQuery" type="search" class="h-8 w-full rounded-md border border-input bg-background px-2 text-xs outline-none placeholder:text-muted-foreground focus-visible:ring-1 focus-visible:ring-ring" :placeholder="t('meilisearch.search')" :aria-label="t('meilisearch.search')" />
|
||||
<div class="max-h-60 overflow-y-auto py-0.5">
|
||||
<button v-for="option in filteredOptions" :key="option" type="button" class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-left font-mono text-xs hover:bg-accent focus-visible:bg-accent focus-visible:outline-none" @click="toggle(option)">
|
||||
<span class="flex h-4 w-4 shrink-0 items-center justify-center rounded border" :class="selected.has(option) ? 'border-primary bg-primary text-primary-foreground' : 'border-input'">
|
||||
<Check v-if="selected.has(option)" class="h-3 w-3" />
|
||||
</span>
|
||||
<span class="min-w-0 flex-1 truncate">{{ option }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</template>
|
||||
Reference in new issue
Block a user