55 lines
1.4 KiB
Vue
55 lines
1.4 KiB
Vue
<template>
|
|
<div class="relative" ref="wrapper">
|
|
<slot name="trigger" :title="title" :open="open" :toggle="toggle">
|
|
<button type="button" @click="() => toggle()" :aria-expanded="open">
|
|
<span>{{ title }}</span>
|
|
<ChevronDownIcon class="ml-2 h-5 w-5" />
|
|
</button>
|
|
</slot>
|
|
|
|
<Transition
|
|
name="menu-transition"
|
|
enter-active-class="transition ease-out duration-200"
|
|
enter-from-class="opacity-0 translate-y-1"
|
|
enter-to-class="opacity-100 translate-y-0"
|
|
leave-active-class="transition ease-in duration-150"
|
|
leave-from-class="opacity-100 translate-y-0"
|
|
leave-to-class="opacity-0 translate-y-1"
|
|
>
|
|
<slot :title="title" :open="open" :toggle="toggle" v-if="open"></slot>
|
|
</Transition>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ChevronDownIcon } from '@heroicons/vue/24/outline';
|
|
import { onMounted, ref } from 'vue';
|
|
import { onBeforeRouteLeave } from 'vue-router';
|
|
|
|
const open = ref(false);
|
|
const wrapper = ref();
|
|
|
|
const props = defineProps<{
|
|
title: string;
|
|
}>();
|
|
|
|
const toggle = (to?: boolean) => {
|
|
open.value = to ?? !open.value;
|
|
};
|
|
|
|
onMounted(() => {
|
|
const event = (e: MouseEvent) => {
|
|
if (wrapper.value.contains(e.target as HTMLElement)) {
|
|
return;
|
|
}
|
|
open.value = false;
|
|
};
|
|
window.addEventListener('click', event);
|
|
return () => window.removeEventListener('click', event);
|
|
});
|
|
|
|
onBeforeRouteLeave(() => {
|
|
toggle(false);
|
|
});
|
|
</script>
|