|
|
@@ -0,0 +1,108 @@
|
|
|
+<template>
|
|
|
+ <div ref="raiz" class="relative shrink-0">
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ class="flex items-center gap-1 rounded-full border border-gray-200 px-3 py-1 text-xs font-medium text-gray-600 transition-colors hover:border-gray-300 dark:border-gray-700 dark:text-gray-300 dark:hover:border-gray-600"
|
|
|
+ :class="modelValue ? 'border-primary/40 bg-primary/10 text-primary dark:border-primary/40 dark:bg-primary/10 dark:text-primary' : ''"
|
|
|
+ :aria-expanded="aberto"
|
|
|
+ aria-haspopup="listbox"
|
|
|
+ @click="alternar"
|
|
|
+ >
|
|
|
+ <span class="max-w-[120px] truncate">{{ rotuloAtual }}</span>
|
|
|
+ <svg class="h-3 w-3 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+
|
|
|
+ <transition
|
|
|
+ enter-active-class="transition duration-150 ease-out"
|
|
|
+ enter-from-class="opacity-0 -translate-y-1 scale-95"
|
|
|
+ enter-to-class="opacity-100 translate-y-0 scale-100"
|
|
|
+ leave-active-class="transition duration-100 ease-in"
|
|
|
+ leave-from-class="opacity-100 translate-y-0 scale-100"
|
|
|
+ leave-to-class="opacity-0 -translate-y-1 scale-95"
|
|
|
+ >
|
|
|
+ <ul
|
|
|
+ v-if="aberto"
|
|
|
+ role="listbox"
|
|
|
+ class="absolute left-0 top-full z-20 mt-2 max-h-72 w-56 origin-top-left overflow-auto rounded-xl border border-gray-200 bg-background py-1 text-sm shadow-lg dark:border-gray-700"
|
|
|
+ >
|
|
|
+ <li v-for="opt in opcoesComPadrao" :key="opt.value">
|
|
|
+ <button
|
|
|
+ type="button"
|
|
|
+ role="option"
|
|
|
+ :aria-selected="opt.value === modelValue"
|
|
|
+ class="flex w-full items-center justify-between gap-2 px-3 py-1.5 text-left hover:bg-black/[0.04] dark:hover:bg-white/[0.06]"
|
|
|
+ :class="opt.value === modelValue ? 'font-medium text-primary' : 'text-gray-700 dark:text-gray-200'"
|
|
|
+ @click="selecionar(opt.value)"
|
|
|
+ >
|
|
|
+ <span class="truncate">{{ opt.label }}</span>
|
|
|
+ <svg
|
|
|
+ v-if="opt.value === modelValue"
|
|
|
+ class="h-3.5 w-3.5 shrink-0"
|
|
|
+ viewBox="0 0 24 24"
|
|
|
+ fill="none"
|
|
|
+ stroke="currentColor"
|
|
|
+ stroke-width="2"
|
|
|
+ >
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
|
|
|
+ </svg>
|
|
|
+ </button>
|
|
|
+ </li>
|
|
|
+ </ul>
|
|
|
+ </transition>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { computed, onBeforeUnmount, onMounted, ref } from "vue";
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ modelValue: { type: String, default: "" },
|
|
|
+ rotulo: { type: String, required: true },
|
|
|
+ rotuloPadrao: { type: String, required: true },
|
|
|
+ opcoes: { type: Array, default: () => [] },
|
|
|
+});
|
|
|
+const emit = defineEmits(["update:modelValue", "change"]);
|
|
|
+
|
|
|
+const aberto = ref(false);
|
|
|
+const raiz = ref(null);
|
|
|
+
|
|
|
+const opcoesComPadrao = computed(() => [
|
|
|
+ { value: "", label: `${props.rotulo}: ${props.rotuloPadrao}` },
|
|
|
+ ...props.opcoes,
|
|
|
+]);
|
|
|
+
|
|
|
+const rotuloAtual = computed(() => {
|
|
|
+ const selecionada = opcoesComPadrao.value.find((o) => o.value === props.modelValue);
|
|
|
+ return selecionada ? selecionada.label : `${props.rotulo}: ${props.rotuloPadrao}`;
|
|
|
+});
|
|
|
+
|
|
|
+function alternar() {
|
|
|
+ aberto.value = !aberto.value;
|
|
|
+}
|
|
|
+function fechar() {
|
|
|
+ aberto.value = false;
|
|
|
+}
|
|
|
+function selecionar(valor) {
|
|
|
+ emit("update:modelValue", valor);
|
|
|
+ emit("change");
|
|
|
+ fechar();
|
|
|
+}
|
|
|
+function aoClicarFora(e) {
|
|
|
+ if (!aberto.value) return;
|
|
|
+ if (!raiz.value?.contains(e.target)) fechar();
|
|
|
+}
|
|
|
+function aoTeclar(e) {
|
|
|
+ if (e.key === "Escape" && aberto.value) fechar();
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ document.addEventListener("click", aoClicarFora);
|
|
|
+ window.addEventListener("keydown", aoTeclar);
|
|
|
+});
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ document.removeEventListener("click", aoClicarFora);
|
|
|
+ window.removeEventListener("keydown", aoTeclar);
|
|
|
+});
|
|
|
+</script>
|