59 lines
2.0 KiB
TypeScript
59 lines
2.0 KiB
TypeScript
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
|
|
import type { CSSProperties } from 'react'
|
|
import { fixedMenuPosition, type MenuPlacement } from '../lib/fixed-menu-position'
|
|
|
|
const HIDDEN_FIXED: CSSProperties = { position: 'fixed', zIndex: 9999, visibility: 'hidden' }
|
|
|
|
export function useFixedMenu(placement: MenuPlacement) {
|
|
const [isOpen, setIsOpen] = useState(false)
|
|
const triggerRef = useRef<HTMLDivElement>(null)
|
|
const menuRef = useRef<HTMLDivElement>(null)
|
|
const [style, setStyle] = useState<CSSProperties>(HIDDEN_FIXED)
|
|
|
|
const updatePosition = useCallback(() => {
|
|
const trigger = triggerRef.current?.getBoundingClientRect()
|
|
if (!trigger) return
|
|
const menuWidth = menuRef.current?.offsetWidth
|
|
const pos = fixedMenuPosition({
|
|
trigger,
|
|
placement,
|
|
viewport: { width: window.innerWidth, height: window.innerHeight },
|
|
menuWidth: menuWidth || undefined,
|
|
})
|
|
setStyle({
|
|
position: 'fixed',
|
|
zIndex: 9999,
|
|
visibility: 'visible',
|
|
left: pos.left,
|
|
...(pos.top != null ? { top: pos.top } : { bottom: pos.bottom }),
|
|
})
|
|
}, [placement])
|
|
|
|
useLayoutEffect(() => {
|
|
if (!isOpen) {
|
|
setStyle(HIDDEN_FIXED)
|
|
return
|
|
}
|
|
updatePosition()
|
|
window.addEventListener('resize', updatePosition)
|
|
window.addEventListener('scroll', updatePosition, true)
|
|
return () => {
|
|
window.removeEventListener('resize', updatePosition)
|
|
window.removeEventListener('scroll', updatePosition, true)
|
|
}
|
|
}, [isOpen, updatePosition])
|
|
|
|
useEffect(() => {
|
|
if (!isOpen) return
|
|
const onPointerDown = (event: MouseEvent) => {
|
|
const node = event.target as Node
|
|
if (triggerRef.current?.contains(node) || menuRef.current?.contains(node)) return
|
|
setIsOpen(false)
|
|
}
|
|
document.addEventListener('mousedown', onPointerDown)
|
|
return () => document.removeEventListener('mousedown', onPointerDown)
|
|
}, [isOpen])
|
|
|
|
return { isOpen, setIsOpen, triggerRef, menuRef, style }
|
|
}
|