<script setup lang="ts">
import { IconChevronDown, type Icon } from "@tabler/icons-vue";
import FilterPill from "components/Filter/FilterPill.vue";
import { onMounted, onUnmounted, ref } from "vue";
const props = defineProps<{
modelValue: string[];
label: string;
options: { value: string; label: string }[];
placeholder?: string;
icon?: Icon;
}>();
const emit = defineEmits<{
(e: "update:modelValue", value: string[]): void;
(e: "change"): void;
}>();
const isOpen = ref(false);
const dropdownRef = ref<HTMLElement | null>(null);
function toggle() {
isOpen.value = !isOpen.value;
}
function handleClickOutside(e: MouseEvent) {
if (dropdownRef.value && !dropdownRef.value.contains(e.target as Node)) {
isOpen.value = false;
}
}
onMounted(() => {
document.addEventListener("mousedown", handleClickOutside);
});
onUnmounted(() => {
document.removeEventListener("mousedown", handleClickOutside);
});
function addValue(value: string) {
if (!props.modelValue.includes(value)) {
emit("update:modelValue", [...props.modelValue, value]);
emit("change");
}
isOpen.value = false;
}
function removeValue(value: string) {
emit(
"update:modelValue",
props.modelValue.filter((v) => v !== value),
);
emit("change");
}
function clear() {
emit("update:modelValue", []);
emit("change");
}
</script>
<template>
<div class="flex items-center gap-2" ref="dropdownRef">
<span
class="filter-label"
:class="{ 'cursor-pointer': modelValue.length > 0 }"
@click="clear"
>
<component :is="icon" size="16" />
{{ label }}:
</span>
<div class="relative">
<button
@click="toggle"
class="flex min-w-[128px] items-center justify-between gap-2 rounded border border-stone-700 bg-stone-900/70 px-2 py-0.5 text-xs text-stone-300 transition-colors hover:border-stone-600 hover:text-white"
>
<span>{{ placeholder || "Select..." }}</span>
<IconChevronDown
size="14"
:class="{ 'rotate-180': isOpen }"
class="transition-transform"
/>
</button>
<div
v-if="isOpen"
class="absolute top-full left-0 z-100 mt-1 max-h-60 w-full min-w-[150px] overflow-hidden overflow-y-auto rounded border border-stone-700 bg-stone-800 shadow-xl"
>
<button
v-for="opt in options"
:key="opt.value"
@click="addValue(opt.value)"
class="flex w-full items-center px-3 py-1.5 text-left text-xs text-stone-300 transition-colors hover:bg-stone-700 hover:text-white disabled:cursor-not-allowed disabled:opacity-30"
:disabled="modelValue.includes(opt.value)"
>
{{ opt.label }}
</button>
</div>
</div>
<div class="flex flex-wrap items-center" v-if="modelValue.length > 0">
<FilterPill
v-for="value in modelValue"
:key="value"
:label="options.find((o) => o.value === value)?.label || value"
@click="removeValue(value)"
/>
</div>
</div>
</template>