mirror of https://github.com/docusealco/docuseal
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
148 lines
3.2 KiB
148 lines
3.2 KiB
<template>
|
|
<div
|
|
class="relative"
|
|
@mouseenter="handleMouseEnter"
|
|
@mouseleave="handleMouseLeave"
|
|
>
|
|
<button
|
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center justify-between text-sm"
|
|
@click.stop="isOpen ? close() : open()"
|
|
>
|
|
<span class="flex items-center space-x-2">
|
|
<component
|
|
:is="icon || 'span'"
|
|
class="w-4 h-4"
|
|
/>
|
|
<span>{{ label }}</span>
|
|
</span>
|
|
<IconChevronRight class="w-4 h-4" />
|
|
</button>
|
|
<div
|
|
v-if="isOpen"
|
|
ref="submenu"
|
|
class="absolute p-1 z-50 left-full bg-white shadow-lg rounded-lg border border-base-300 cursor-default"
|
|
style="min-width: 170px"
|
|
:style="submenuStyle"
|
|
:class="menuClass"
|
|
@click.stop
|
|
>
|
|
<slot>
|
|
<button
|
|
v-for="option in options"
|
|
:key="option.value"
|
|
class="w-full px-2 py-1 rounded-md hover:bg-neutral-100 flex items-center justify-between space-x-2 text-sm cursor-pointer"
|
|
@click="handleSelect(option.value)"
|
|
>
|
|
<span class="whitespace-nowrap">{{ option.label }}</span>
|
|
<IconCheck
|
|
v-if="modelValue === option.value"
|
|
class="w-4 h-4"
|
|
/>
|
|
</button>
|
|
</slot>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { IconChevronRight, IconCheck } from '@tabler/icons-vue'
|
|
|
|
export default {
|
|
name: 'ContextSubmenu',
|
|
components: {
|
|
IconChevronRight,
|
|
IconCheck
|
|
},
|
|
props: {
|
|
icon: {
|
|
type: [Function],
|
|
required: false,
|
|
default: null
|
|
},
|
|
label: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
options: {
|
|
type: Array,
|
|
default: () => []
|
|
},
|
|
modelValue: {
|
|
type: [String, Number],
|
|
default: null
|
|
},
|
|
menuClass: {
|
|
type: String,
|
|
default: ''
|
|
}
|
|
},
|
|
emits: ['select', 'update:modelValue'],
|
|
data () {
|
|
return {
|
|
isOpen: false,
|
|
topOffset: 0
|
|
}
|
|
},
|
|
computed: {
|
|
submenuStyle () {
|
|
return {
|
|
top: this.topOffset + 'px'
|
|
}
|
|
}
|
|
},
|
|
beforeUnmount () {
|
|
this.clearTimeout()
|
|
},
|
|
methods: {
|
|
handleMouseEnter () {
|
|
clearTimeout(this.closeTimeout)
|
|
|
|
this.openTimeout = setTimeout(() => this.open(), 200)
|
|
},
|
|
handleMouseLeave () {
|
|
clearTimeout(this.openTimeout)
|
|
|
|
this.closeTimeout = setTimeout(() => this.close(), 200)
|
|
},
|
|
open () {
|
|
this.clearTimeout()
|
|
|
|
this.isOpen = true
|
|
this.topOffset = 0
|
|
|
|
this.$nextTick(() => setTimeout(() => this.adjustPosition(), 0))
|
|
},
|
|
clearTimeout () {
|
|
if (this.openTimeout) {
|
|
clearTimeout(this.openTimeout)
|
|
}
|
|
|
|
if (this.closeTimeout) {
|
|
clearTimeout(this.closeTimeout)
|
|
}
|
|
},
|
|
close () {
|
|
this.clearTimeout()
|
|
|
|
this.isOpen = false
|
|
},
|
|
handleSelect (value) {
|
|
this.$emit('select', value)
|
|
this.$emit('update:modelValue', value)
|
|
},
|
|
adjustPosition () {
|
|
if (!this.$refs.submenu) return
|
|
|
|
const rect = this.$refs.submenu.getBoundingClientRect()
|
|
const overflow = rect.bottom - window.innerHeight
|
|
|
|
if (overflow > 0) {
|
|
this.topOffset = -overflow - 4
|
|
} else {
|
|
this.topOffset = 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|