import { useCallback, useEffect, useRef } from 'react'; import './ContextMenu.css'; export interface ContextMenuAction { label: string; icon: string; disabled?: boolean; onClick: () => void; } export interface ContextMenuProps { x: number; y: number; actions: ContextMenuAction[]; dividerAfter?: number[]; onClose: () => void; } export default function ContextMenu({ x, y, actions, dividerAfter = [], onClose, }: ContextMenuProps) { const menuRef = useRef(null); // Adjust position so menu stays within viewport const adjustedPosition = useCallback(() => { const el = menuRef.current; if (!el) return { left: x, top: y }; const rect = el.getBoundingClientRect(); const left = x + rect.width > window.innerWidth ? x - rect.width : x; const top = y + rect.height > window.innerHeight ? y - rect.height : y; return { left: Math.max(0, left), top: Math.max(0, top) }; }, [x, y]); // Close on click outside useEffect(() => { const handler = (e: MouseEvent) => { if (menuRef.current && !menuRef.current.contains(e.target as Node)) { onClose(); } }; // Use capture so we catch clicks before any other handler document.addEventListener('mousedown', handler, true); return () => document.removeEventListener('mousedown', handler, true); }, [onClose]); // Close on Escape useEffect(() => { const handler = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', handler); return () => document.removeEventListener('keydown', handler); }, [onClose]); // After mount, adjust position useEffect(() => { const el = menuRef.current; if (!el) return; const pos = adjustedPosition(); el.style.left = `${pos.left}px`; el.style.top = `${pos.top}px`; }, [adjustedPosition]); const dividerSet = new Set(dividerAfter); return (
{actions.map((action, i) => (
{dividerSet.has(i) &&
}
))}
); }