1
0
forked from test/crm

167 lines
5.1 KiB
Vue

<template>
<Menu as="div" class="relative inline-block text-left" v-slot="{ open }">
<Popover
:transition="dropdownTransition"
:show="open"
:placement="popoverPlacement"
>
<template #target="{ togglePopover }">
<MenuButton as="template">
<slot v-if="$slots.default" v-bind="{ open, togglePopover }" />
<Button v-else :active="open" v-bind="button">
{{ button ? button?.label || null : 'Options' }}
</Button>
</MenuButton>
</template>
<template #body>
<div
class="mt-2 min-w-40 divide-y divide-outline-gray-modals rounded-lg bg-surface-modal shadow-2xl ring-1 ring-black ring-opacity-5 focus:outline-none"
:class="{
'mt-2': ['bottom', 'left', 'right'].includes(placement),
'ml-2': placement == 'right-start',
}"
>
<MenuItems
class="min-w-40 divide-y divide-outline-gray-modals"
:class="{
'left-0 origin-top-left': placement == 'left',
'right-0 origin-top-right': placement == 'right',
'inset-x-0 origin-top': placement == 'center',
'mt-0 origin-top-right': placement == 'right-start',
}"
>
<div v-for="group in groups" :key="group.key" class="p-1.5">
<div
v-if="group.group && !group.hideLabel"
class="flex h-7 items-center px-2 text-sm font-medium text-ink-gray-4"
>
{{ group.group }}
</div>
<MenuItem
v-for="item in group.items"
:key="item.label"
v-slot="{ active }"
>
<slot name="item" v-bind="{ item, active }">
<component
v-if="item.component"
:is="item.component"
:active="active"
/>
<button
v-else
:class="[
active ? 'bg-surface-gray-3' : 'text-ink-gray-6',
'group flex h-7 w-full items-center rounded px-2 text-base',
]"
@click="item.onClick"
>
<FeatherIcon
v-if="item.icon && typeof item.icon === 'string'"
:name="item.icon"
class="mr-2 h-4 w-4 flex-shrink-0 text-ink-gray-7"
aria-hidden="true"
/>
<component
class="mr-2 h-4 w-4 flex-shrink-0 text-ink-gray-7"
v-else-if="item.icon"
:is="item.icon"
/>
<span class="whitespace-nowrap text-ink-gray-7">
{{ item.label }}
</span>
</button>
</slot>
</MenuItem>
</div>
</MenuItems>
<div v-if="slots.footer" class="border-t p-1.5">
<slot name="footer"></slot>
</div>
</div>
</template>
</Popover>
</Menu>
</template>
<script setup>
import { Menu, MenuButton, MenuItems, MenuItem } from '@headlessui/vue'
import { Popover, Button, FeatherIcon } from 'frappe-ui'
import { computed, useSlots } from 'vue'
import { useRouter } from 'vue-router'
const props = defineProps({
button: {
type: Object,
default: null,
},
options: {
type: Array,
default: () => [],
},
placement: {
type: String,
default: 'left',
},
})
const router = useRouter()
const slots = useSlots()
const dropdownTransition = {
enterActiveClass: 'transition duration-100 ease-out',
enterFromClass: 'transform scale-95 opacity-0',
enterToClass: 'transform scale-100 opacity-100',
leaveActiveClass: 'transition duration-75 ease-in',
leaveFromClass: 'transform scale-100 opacity-100',
leaveToClass: 'transform scale-95 opacity-0',
}
const groups = computed(() => {
let groups = props.options[0]?.group
? props.options
: [{ group: '', items: props.options }]
return groups.map((group, i) => {
return {
key: i,
group: group.group,
hideLabel: group.hideLabel || false,
items: filterOptions(group.items),
}
})
})
const popoverPlacement = computed(() => {
if (props.placement === 'left') return 'bottom-start'
if (props.placement === 'right') return 'bottom-end'
if (props.placement === 'center') return 'bottom-center'
if (props.placement === 'right-start') return 'right-start'
return 'bottom'
})
function normalizeDropdownItem(option) {
let onClick = option.onClick || null
if (!onClick && option.route && router) {
onClick = () => router.push(option.route)
}
return {
name: option.name,
label: option.label,
icon: option.icon,
group: option.group,
component: option.component,
onClick,
}
}
function filterOptions(options) {
return (options || [])
.filter(Boolean)
.filter((option) => (option.condition ? option.condition() : true))
.map((option) => normalizeDropdownItem(option))
}
</script>