import { SyntheticEvent, useRef, useEffect, CSSProperties } from 'react'; export interface MenuOption { icon?: string; label: string; onClick: (event: SyntheticEvent) => void; variant?: 'primary' | 'danger'; iconClassName?: string; iconWidth?: number; iconHeight?: number; } interface ContextMenuProps { isOpen: boolean; setIsOpen: (isOpen: boolean) => void; options: MenuOption[]; anchorRef: React.RefObject; className?: string; position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'; offset?: { x: number; y: number }; } export default function ContextMenu({ isOpen, setIsOpen, options, anchorRef, className = '', position = 'bottom-right', offset = { x: 0, y: 8 }, }: ContextMenuProps) { const menuRef = useRef(null); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( menuRef.current && !menuRef.current.contains(event.target as Node) && !anchorRef.current?.contains(event.target as Node) ) { setIsOpen(false); } }; if (isOpen) { document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); } }, [isOpen, setIsOpen]); if (!isOpen) return null; const getMenuPosition = (): CSSProperties => { if (!anchorRef.current) return {}; const rect = anchorRef.current.getBoundingClientRect(); const scrollY = window.scrollY || document.documentElement.scrollTop; const scrollX = window.scrollX || document.documentElement.scrollLeft; let top = rect.bottom + scrollY + offset.y; let left = rect.right + scrollX + offset.x; switch (position) { case 'bottom-left': left = rect.left + scrollX - offset.x; break; case 'top-right': top = rect.top + scrollY - offset.y; break; case 'top-left': top = rect.top + scrollY - offset.y; left = rect.left + scrollX - offset.x; break; // bottom-right is default } return { position: 'fixed', top: `${top}px`, left: `${left}px`, }; }; return (
e.stopPropagation()} >
{options.map((option, index) => ( ))}
); }