feat(meilisearch): add search filter to multi-select dropdown

This commit is contained in:
二丫讲梵 authored and GitHub committed 2026-09-18 22:49:45 +08:00
1 parent 68f905e0c2
commit ccc0361907
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>