import { useEffect, useRef, type ReactNode } from 'react'; interface MenuItem { label: string; icon?: ReactNode; onClick: () => void; variant?: 'default' | 'danger'; checked?: boolean; separator?: false; } interface SeparatorItem { separator: true; } export type ContextMenuEntry = MenuItem | SeparatorItem; interface ContextMenuProps { x: number; y: number; items: ContextMenuEntry[]; onClose: () => void; } export function ContextMenu({ x, y, items, onClose }: ContextMenuProps) { const menuRef = useRef(null); useEffect(() => { function handleMouseDown(e: MouseEvent) { if (menuRef.current && !menuRef.current.contains(e.target as Node)) { onClose(); } } function handleKeyDown(e: KeyboardEvent) { if (e.key === 'Escape') onClose(); } document.addEventListener('mousedown', handleMouseDown); document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('mousedown', handleMouseDown); document.removeEventListener('keydown', handleKeyDown); }; }, [onClose]); // Clamp so menu doesn't overflow right/bottom edge const menuWidth = 192; const menuHeight = items.length * 34; const clampedX = Math.min(x, window.innerWidth - menuWidth - 8); const clampedY = Math.min(y, window.innerHeight - menuHeight - 8); return (
{items.map((item, i) => { if ('separator' in item && item.separator) { return
; } const mi = item as MenuItem; return ( ); })}
); }