mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 12:14:41 +01:00
chore(ContextMenu): new component
This commit is contained in:
@@ -165,6 +165,20 @@
|
||||
<UButton icon="heroicons-outline:bell" variant="red" label="Trigger an error" @click="onNotificationClick" />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
||||
Context menu:
|
||||
</div>
|
||||
|
||||
<UCard ref="contextMenuRef" class="relative" body-class="h-64" @click="isContextMenuOpen = false" @contextmenu.prevent="isContextMenuOpen = true">
|
||||
<UContextMenu v-model="isContextMenuOpen" :virtual-element="virtualElement" width-class="w-48">
|
||||
<UCard @click.stop>
|
||||
Menu
|
||||
</UCard>
|
||||
</UContextMenu>
|
||||
</UCard>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="font-medium text-sm mb-1 u-text-gray-700">
|
||||
Command palette:
|
||||
@@ -266,6 +280,32 @@ const form = reactive({
|
||||
|
||||
const { $toast } = useNuxtApp()
|
||||
|
||||
const x = ref(0)
|
||||
const y = ref(0)
|
||||
const isContextMenuOpen = ref(false)
|
||||
const contextMenuRef = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('mousemove', ({ clientX, clientY }) => {
|
||||
x.value = clientX
|
||||
y.value = clientY
|
||||
})
|
||||
})
|
||||
|
||||
const virtualElement = computed(() => ({
|
||||
getBoundingClientRect () {
|
||||
return {
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: y.value,
|
||||
right: x.value,
|
||||
bottom: y.value,
|
||||
left: x.value
|
||||
}
|
||||
},
|
||||
contextElement: contextMenuRef.value?.$el
|
||||
}))
|
||||
|
||||
const customQuery = query => computed(() => query.value ? `${query.value} | =1` : '')
|
||||
|
||||
function toggleModalIsOpen () {
|
||||
|
||||
73
src/runtime/components/overlays/ContextMenu.vue
Normal file
73
src/runtime/components/overlays/ContextMenu.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<template>
|
||||
<div v-if="isOpen" ref="container" :class="[containerClass, widthClass]">
|
||||
<transition appear v-bind="transitionClass">
|
||||
<div :class="baseClass">
|
||||
<slot />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { PropType, computed, toRef } from 'vue'
|
||||
import { defu } from 'defu'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import type { PopperOptions } from './../types'
|
||||
import $ui from '#build/ui'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
virtualElement: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: () => $ui.contextMenu.wrapper
|
||||
},
|
||||
containerClass: {
|
||||
type: String,
|
||||
default: () => $ui.contextMenu.container
|
||||
},
|
||||
widthClass: {
|
||||
type: String,
|
||||
default: () => $ui.contextMenu.width
|
||||
},
|
||||
baseClass: {
|
||||
type: String,
|
||||
default: () => $ui.contextMenu.base
|
||||
},
|
||||
transitionClass: {
|
||||
type: Object,
|
||||
default: () => $ui.contextMenu.transition
|
||||
},
|
||||
popperOptions: {
|
||||
type: Object as PropType<Pick<PopperOptions, 'strategy' | 'placement'>>,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'close'])
|
||||
|
||||
const isOpen = computed({
|
||||
get () {
|
||||
return props.modelValue
|
||||
},
|
||||
set (value) {
|
||||
emit('update:modelValue', value)
|
||||
}
|
||||
})
|
||||
|
||||
const virtualElement = toRef(props, 'virtualElement')
|
||||
|
||||
const popperOptions = computed(() => defu({}, props.popperOptions, { placement: 'bottom-start' }))
|
||||
|
||||
const [, container] = usePopper(popperOptions.value, virtualElement)
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default { name: 'UContextMenu' }
|
||||
</script>
|
||||
@@ -1,11 +1,13 @@
|
||||
import { ref, onMounted, watchEffect } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import { popperGenerator, defaultModifiers } from '@popperjs/core/lib/popper-lite'
|
||||
import type { Instance } from '@popperjs/core'
|
||||
import { omitBy, isUndefined } from 'lodash-es'
|
||||
import flip from '@popperjs/core/lib/modifiers/flip'
|
||||
import offset from '@popperjs/core/lib/modifiers/offset'
|
||||
import preventOverflow from '@popperjs/core/lib/modifiers/preventOverflow'
|
||||
|
||||
const createPopper = popperGenerator({
|
||||
export const createPopper = popperGenerator({
|
||||
defaultModifiers: [...defaultModifiers, offset, flip, preventOverflow]
|
||||
})
|
||||
|
||||
@@ -16,22 +18,23 @@ export function usePopper ({
|
||||
offsetSkid = 0,
|
||||
placement,
|
||||
strategy
|
||||
}) {
|
||||
const reference = ref(null)
|
||||
const popper = ref(null)
|
||||
}, virtualReference) {
|
||||
const reference: Ref<HTMLElement> = ref(null)
|
||||
const popper: Ref<HTMLElement> = ref(null)
|
||||
const instance: Ref<Instance> = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
watchEffect((onInvalidate) => {
|
||||
if (!popper.value) { return }
|
||||
if (!reference.value) { return }
|
||||
if (!reference.value && !virtualReference.value) { return }
|
||||
|
||||
const popperEl = popper.value.$el || popper.value
|
||||
const referenceEl = reference.value.$el || reference.value
|
||||
const referenceEl = virtualReference?.value || reference.value.$el || reference.value
|
||||
|
||||
if (!(referenceEl instanceof HTMLElement)) { return }
|
||||
// if (!(referenceEl instanceof HTMLElement)) { return }
|
||||
if (!(popperEl instanceof HTMLElement)) { return }
|
||||
|
||||
const { destroy } = createPopper(referenceEl, popperEl, omitBy({
|
||||
instance.value = createPopper(referenceEl, popperEl, omitBy({
|
||||
placement,
|
||||
strategy,
|
||||
modifiers: [{
|
||||
@@ -50,9 +53,9 @@ export function usePopper ({
|
||||
}]
|
||||
}, isUndefined))
|
||||
|
||||
onInvalidate(destroy)
|
||||
onInvalidate(instance.value.destroy)
|
||||
})
|
||||
})
|
||||
|
||||
return [reference, popper]
|
||||
return [reference, popper, instance]
|
||||
}
|
||||
|
||||
@@ -503,6 +503,21 @@ export default (variantColors: string[]) => {
|
||||
}
|
||||
}
|
||||
|
||||
const contextMenu = {
|
||||
wrapper: 'relative',
|
||||
container: 'z-20',
|
||||
width: '',
|
||||
base: '',
|
||||
transition: {
|
||||
enterActiveClass: 'transition ease-out duration-200',
|
||||
enterFromClass: 'opacity-0 translate-y-1',
|
||||
enterToClass: 'opacity-100 translate-y-0',
|
||||
leaveActiveClass: 'transition ease-in duration-150',
|
||||
leaveFromClass: 'opacity-100 translate-y-0',
|
||||
leaveToClass: 'opacity-0 translate-y-1'
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
card,
|
||||
modal,
|
||||
@@ -527,6 +542,7 @@ export default (variantColors: string[]) => {
|
||||
slideover,
|
||||
notification,
|
||||
tooltip,
|
||||
popover
|
||||
popover,
|
||||
contextMenu
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user