133 lines
3.3 KiB
Vue
133 lines
3.3 KiB
Vue
<script setup>
|
|
import { computed, ref, watch, onMounted, onUnmounted } from 'vue';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuTrigger,
|
|
} from '@/Components/ui/dropdown-menu';
|
|
|
|
const props = defineProps({
|
|
align: {
|
|
type: String,
|
|
default: 'right',
|
|
},
|
|
width: {
|
|
type: String,
|
|
default: '48',
|
|
},
|
|
contentClasses: {
|
|
type: Array,
|
|
default: () => ['py-1', 'bg-white'],
|
|
},
|
|
closeOnContentClick: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
});
|
|
|
|
const open = ref(false);
|
|
|
|
// Expose close method for parent components
|
|
const close = () => {
|
|
open.value = false;
|
|
};
|
|
|
|
defineExpose({
|
|
close,
|
|
open,
|
|
});
|
|
|
|
// Close dropdown when dialog opens
|
|
const handleDialogOpen = () => {
|
|
if (open.value) {
|
|
open.value = false;
|
|
}
|
|
};
|
|
|
|
// Watch for dialog opens using MutationObserver
|
|
let observer = null;
|
|
|
|
onMounted(() => {
|
|
// Listen for custom dialog open events
|
|
window.addEventListener('dialog:open', handleDialogOpen);
|
|
|
|
// Watch for dialog state changes in the DOM
|
|
observer = new MutationObserver((mutations) => {
|
|
// Check if any dialog has data-state="open"
|
|
const openDialogs = document.querySelectorAll('[data-state="open"]');
|
|
const hasOpenDialog = Array.from(openDialogs).some((dialog) => {
|
|
// Check if it's a dialog element (has role="dialog" or is DialogContent)
|
|
const role = dialog.getAttribute('role');
|
|
const isDialogContent = dialog.classList?.contains('DialogContent') ||
|
|
dialog.querySelector('[role="dialog"]') ||
|
|
dialog.closest('[role="dialog"]');
|
|
|
|
return role === 'dialog' || isDialogContent;
|
|
});
|
|
|
|
if (hasOpenDialog && open.value) {
|
|
handleDialogOpen();
|
|
}
|
|
});
|
|
|
|
observer.observe(document.body, {
|
|
attributes: true,
|
|
attributeFilter: ['data-state'],
|
|
subtree: true,
|
|
childList: true,
|
|
});
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
window.removeEventListener('dialog:open', handleDialogOpen);
|
|
if (observer) {
|
|
observer.disconnect();
|
|
}
|
|
});
|
|
|
|
const widthClass = computed(() => {
|
|
const map = {
|
|
'48': 'w-48', // 12rem
|
|
'64': 'w-64', // 16rem
|
|
'72': 'w-72', // 18rem
|
|
'80': 'w-80', // 20rem
|
|
'96': 'w-96', // 24rem
|
|
'wide': 'w-[34rem] max-w-[90vw]',
|
|
'auto': '',
|
|
};
|
|
return map[props.width.toString()] || '';
|
|
});
|
|
|
|
// Map align prop to shadcn-vue's align prop
|
|
// 'left' -> 'start', 'right' -> 'end'
|
|
const alignProp = computed(() => {
|
|
if (props.align === 'left') return 'start';
|
|
if (props.align === 'right') return 'end';
|
|
return 'start';
|
|
});
|
|
|
|
const combinedContentClasses = computed(() => {
|
|
// Merge width class with custom content classes
|
|
// Note: shadcn-vue already provides base styling, so we append custom classes
|
|
const classes = [widthClass.value];
|
|
if (props.contentClasses && props.contentClasses.length) {
|
|
classes.push(...props.contentClasses);
|
|
}
|
|
return classes.filter(Boolean).join(' ');
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<DropdownMenu v-model:open="open">
|
|
<DropdownMenuTrigger as-child>
|
|
<slot name="trigger" />
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
:align="alignProp"
|
|
:class="combinedContentClasses"
|
|
>
|
|
<slot name="content" />
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</template>
|