62 lines
1.5 KiB
Vue
62 lines
1.5 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 { onBeforeUnmount, 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;
|
|
};
|
|
|
|
const event = (e: MouseEvent) => {
|
|
if (
|
|
wrapper.value.contains(e.target as HTMLElement) &&
|
|
!(e.target as HTMLElement).closest('a')
|
|
) {
|
|
return;
|
|
}
|
|
open.value = false;
|
|
};
|
|
|
|
onMounted(() => {
|
|
window.addEventListener('click', event);
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
window.removeEventListener('click', event);
|
|
});
|
|
|
|
onBeforeRouteLeave(() => {
|
|
toggle(false);
|
|
});
|
|
</script>
|