This commit is contained in:
Julian Freeman
2026-04-16 14:07:17 -04:00
parent a12c734aa5
commit 53287eabff
3 changed files with 225 additions and 32 deletions

View File

@@ -0,0 +1,82 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, ref } from "vue";
type Option = {
label: string;
value: string;
};
const props = defineProps<{
label?: string;
modelValue: string;
options: Option[];
}>();
const emit = defineEmits<{
"update:modelValue": [value: string];
}>();
const open = ref(false);
const root = ref<HTMLElement | null>(null);
const selectedLabel = computed(
() =>
props.options.find((option) => option.value === props.modelValue)?.label ??
props.options[0]?.label ??
"",
);
function toggle() {
open.value = !open.value;
}
function select(value: string) {
emit("update:modelValue", value);
open.value = false;
}
function handleDocumentPointer(event: PointerEvent) {
if (!root.value) return;
const target = event.target;
if (target instanceof Node && !root.value.contains(target)) {
open.value = false;
}
}
if (typeof window !== "undefined") {
window.addEventListener("pointerdown", handleDocumentPointer);
}
onBeforeUnmount(() => {
if (typeof window !== "undefined") {
window.removeEventListener("pointerdown", handleDocumentPointer);
}
});
</script>
<template>
<div ref="root" class="sort-dropdown">
<span v-if="label" class="sort-dropdown-label">{{ label }}</span>
<button
class="sort-dropdown-trigger"
:class="{ open }"
type="button"
@click="toggle"
>
<span>{{ selectedLabel }}</span>
<span class="sort-dropdown-caret" aria-hidden="true"></span>
</button>
<div v-if="open" class="sort-dropdown-menu">
<button
v-for="option in options"
:key="option.value"
class="sort-dropdown-option"
:class="{ active: option.value === modelValue }"
type="button"
@click="select(option.value)"
>
{{ option.label }}
</button>
</div>
</div>
</template>