refactor: Teleport 드롭다운 위치/토글 로직 useDropdownMenu 로 통합 (Phase 2)

DropdownSelect·StatusSelect 에 중복되던 fixed 위치 계산·open 토글·
바깥클릭/ESC/스크롤 닫기 로직을 useDropdownMenu 훅으로 추출.

- composables/useDropdownMenu.ts 신설 (align/matchWidth/gap/esc 옵션)
- DropdownSelect, StatusSelect 가 훅을 사용하도록 정리
- 절대배치 메뉴(일정 멀티셀렉트/유형필터/참석자 스택)는 Phase 0 useClickOutside 로 이미 공유

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 23:44:48 +09:00
parent 0c4f0817e6
commit 7425d57134
3 changed files with 74 additions and 64 deletions
@@ -0,0 +1,58 @@
import { nextTick, ref } from 'vue'
import { useClickOutside } from './useClickOutside'
// Teleport(body) + fixed 위치로 띄우는 드롭다운 메뉴 공용 훅.
// 트리거 기준 좌표 계산 · open 토글 · 바깥클릭/ESC/스크롤 닫기를 한 곳으로 모은다.
// (DropdownSelect, StatusSelect 등 스크롤/overflow 컨테이너 안에서도 안 잘리는 메뉴에 사용)
//
// - align: 트리거 좌/우 끝 기준 정렬 (기본 'left')
// - matchWidth: 메뉴 최소 너비를 트리거 너비에 맞출지 (기본 false)
// - gap: 트리거 하단과 메뉴 사이 간격(px) (기본 6)
// - esc: ESC 로 닫을지 (기본 true)
export function useDropdownMenu(
opts: {
align?: 'left' | 'right'
matchWidth?: boolean
gap?: number
esc?: boolean
} = {},
) {
const { align = 'left', matchWidth = false, gap = 6, esc = true } = opts
const open = ref(false)
const triggerRef = ref<HTMLElement | null>(null)
const menuRef = ref<HTMLElement | null>(null)
// 메뉴 fixed 위치(트리거 기준 계산)
const menuStyle = ref<Record<string, string>>({})
function position(): void {
const el = triggerRef.value
if (!el) return
const r = el.getBoundingClientRect()
const style: Record<string, string> = { top: `${r.bottom + gap}px` }
if (matchWidth) style.minWidth = `${r.width}px`
if (align === 'right') {
style.right = `${Math.max(0, window.innerWidth - r.right)}px`
} else {
style.left = `${r.left}px`
}
menuStyle.value = style
}
function toggle(): void {
open.value = !open.value
if (open.value) void nextTick(position)
}
function close(): void {
open.value = false
}
// 바깥클릭(트리거/메뉴 외부)·ESC·스크롤(위치 어긋남) 닫기
useClickOutside(open, close, [triggerRef, menuRef], {
event: 'click',
esc,
closeOnScroll: true,
})
return { open, triggerRef, menuRef, menuStyle, toggle, close, position }
}