관리자 태그와 목록 메뉴 개선 v1.5.0
This commit is contained in:
@@ -37,6 +37,9 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
const triggerRef = ref(null)
|
||||
const popoverStyle = ref({})
|
||||
|
||||
/** @type {import('vue').ComputedRef<boolean>} */
|
||||
const isOpen = computed(() => openMenuId.value === props.itemId)
|
||||
|
||||
@@ -53,6 +56,32 @@ const triggerToneClass = computed(() => (
|
||||
: 'text-[#394047] hover:bg-[#eceff2]'
|
||||
))
|
||||
|
||||
/**
|
||||
* 행 메뉴 위치를 화면 기준으로 계산한다.
|
||||
* @returns {void}
|
||||
*/
|
||||
const updatePopoverPosition = () => {
|
||||
if (!import.meta.client || !triggerRef.value) {
|
||||
return
|
||||
}
|
||||
|
||||
const rect = triggerRef.value.getBoundingClientRect()
|
||||
const menuWidth = props.size === 'sm' ? 176 : 176
|
||||
const estimatedHeight = 112
|
||||
const margin = 8
|
||||
const left = Math.max(margin, Math.min(rect.right - menuWidth, window.innerWidth - menuWidth - margin))
|
||||
const opensUp = rect.bottom + estimatedHeight + margin > window.innerHeight
|
||||
const top = opensUp
|
||||
? Math.max(margin, rect.top - estimatedHeight - 4)
|
||||
: rect.bottom + 4
|
||||
|
||||
popoverStyle.value = {
|
||||
left: `${left}px`,
|
||||
top: `${top}px`,
|
||||
width: `${menuWidth}px`
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 열기/닫기
|
||||
* @returns {void}
|
||||
@@ -64,6 +93,25 @@ const toggleMenu = () => {
|
||||
|
||||
openMenuId.value = isOpen.value ? '' : props.itemId
|
||||
}
|
||||
|
||||
watch(isOpen, async (open) => {
|
||||
if (!open) {
|
||||
return
|
||||
}
|
||||
|
||||
await nextTick()
|
||||
updatePopoverPosition()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', updatePopoverPosition)
|
||||
window.addEventListener('scroll', updatePopoverPosition, true)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', updatePopoverPosition)
|
||||
window.removeEventListener('scroll', updatePopoverPosition, true)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -73,6 +121,7 @@ const toggleMenu = () => {
|
||||
@mousedown.stop
|
||||
>
|
||||
<button
|
||||
ref="triggerRef"
|
||||
class="admin-row-more-menu__trigger inline-flex items-center justify-center rounded transition-colors focus-visible:outline focus-visible:ring-2 focus-visible:ring-offset-1 disabled:pointer-events-none disabled:opacity-40"
|
||||
:class="[triggerSizeClass, triggerToneClass]"
|
||||
type="button"
|
||||
@@ -100,13 +149,17 @@ const toggleMenu = () => {
|
||||
<path d="M480-160q-33 0-56.5-23.5T400-240q0-33 23.5-56.5T480-320q33 0 56.5 23.5T560-240q0 33-23.5 56.5T480-160Zm0-240q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm0-240q-33 0-56.5-23.5T400-720q0-33 23.5-56.5T480-800q33 0 56.5 23.5T560-720q0 33-23.5 56.5T480-640Z" />
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
v-if="isOpen"
|
||||
class="admin-row-more-menu__popover absolute right-0 top-full z-30 mt-1 min-w-[11rem] overflow-hidden rounded-xl border border-[#e2e5e9] bg-white py-2 text-sm text-[#3f4650] shadow-[0_18px_50px_rgba(15,23,42,0.16)]"
|
||||
role="menu"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-if="isOpen"
|
||||
class="admin-row-more-menu__popover fixed z-[80] overflow-hidden rounded-xl border border-[#e2e5e9] bg-white py-2 text-sm text-[#3f4650] shadow-[0_18px_50px_rgba(15,23,42,0.16)]"
|
||||
:style="popoverStyle"
|
||||
role="menu"
|
||||
data-admin-row-menu
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user