'use client'; import { useEffect, useLayoutEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { AnimatePresence, motion } from '@/lib/motion'; import { EllipsisVertical, Edit, Trash2 } from 'lucide-react'; const MENU_WIDTH = 148; const MENU_ESTIMATED_HEIGHT = 84; const GAP = 6; const VIEWPORT_PAD = 8; interface RowActionsMenuProps { open: boolean; onOpenChange: (open: boolean) => void; onEdit: () => void; onDelete: () => void; } export function RowActionsMenu({ open, onOpenChange, onEdit, onDelete }: RowActionsMenuProps) { const triggerRef = useRef(null); const menuRef = useRef(null); const [mounted, setMounted] = useState(false); const [coords, setCoords] = useState({ top: 0, left: 0 }); useEffect(() => { setMounted(true); }, []); const updatePosition = () => { const trigger = triggerRef.current; if (!trigger) return; const rect = trigger.getBoundingClientRect(); const menuHeight = menuRef.current?.offsetHeight || MENU_ESTIMATED_HEIGHT; const spaceBelow = window.innerHeight - rect.bottom - VIEWPORT_PAD; const placeAbove = spaceBelow < menuHeight && rect.top > menuHeight + GAP; let top = placeAbove ? rect.top - menuHeight - GAP : rect.bottom + GAP; let left = rect.right - MENU_WIDTH; if (left < VIEWPORT_PAD) left = VIEWPORT_PAD; if (left + MENU_WIDTH > window.innerWidth - VIEWPORT_PAD) { left = window.innerWidth - MENU_WIDTH - VIEWPORT_PAD; } if (top < VIEWPORT_PAD) top = VIEWPORT_PAD; if (top + menuHeight > window.innerHeight - VIEWPORT_PAD) { top = Math.max(VIEWPORT_PAD, window.innerHeight - menuHeight - VIEWPORT_PAD); } setCoords({ top, left }); }; useLayoutEffect(() => { if (!open) return; updatePosition(); const frame = requestAnimationFrame(updatePosition); return () => cancelAnimationFrame(frame); }, [open]); useEffect(() => { if (!open) return; const onKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') onOpenChange(false); }; const onPointerDown = (event: MouseEvent) => { const target = event.target as Node; if (triggerRef.current?.contains(target) || menuRef.current?.contains(target)) return; onOpenChange(false); }; const onReposition = () => updatePosition(); document.addEventListener('keydown', onKeyDown); document.addEventListener('mousedown', onPointerDown); window.addEventListener('resize', onReposition); window.addEventListener('scroll', onReposition, true); return () => { document.removeEventListener('keydown', onKeyDown); document.removeEventListener('mousedown', onPointerDown); window.removeEventListener('resize', onReposition); window.removeEventListener('scroll', onReposition, true); }; }, [open, onOpenChange]); return ( <> {mounted && createPortal( {open && ( )} , document.body )} ); }