import { useEffect, useRef } from "react"; // Generic right-click context menu for the sidebar tree. Each call site // (directive folder, contract row, step row, task row, …) builds its own // items array. Lifts the viewport-clamping + click-outside / Esc logic // out of the deleted DirectiveContextMenu so we don't end up with one // component per entity type. export interface ContextMenuItem { /** Display text. Empty for separators. */ label: string; /** Click handler. Ignored when `separator` is true. */ onClick?: () => void; /** Render in red — for destructive operations (delete, archive). */ danger?: boolean; /** Render as a thin divider instead of a button. */ separator?: boolean; /** Greyed out, non-clickable. Used for items whose preconditions * aren't met (e.g. "Reopen" on an already-draft contract). */ disabled?: boolean; } interface SidebarContextMenuProps { x: number; y: number; items: ContextMenuItem[]; onClose: () => void; } export function SidebarContextMenu({ x, y, items, onClose }: SidebarContextMenuProps) { const menuRef = useRef(null); // Close on click outside or Esc. useEffect(() => { const handleClickOutside = (e: MouseEvent) => { if (menuRef.current && !menuRef.current.contains(e.target as Node)) { onClose(); } }; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === "Escape") onClose(); }; document.addEventListener("mousedown", handleClickOutside); document.addEventListener("keydown", handleKeyDown); return () => { document.removeEventListener("mousedown", handleClickOutside); document.removeEventListener("keydown", handleKeyDown); }; }, [onClose]); // Clamp into viewport if the menu would overflow off the right or // bottom edge. useEffect(() => { if (menuRef.current) { const rect = menuRef.current.getBoundingClientRect(); const viewportWidth = window.innerWidth; const viewportHeight = window.innerHeight; if (rect.right > viewportWidth) { menuRef.current.style.left = `${x - rect.width}px`; } if (rect.bottom > viewportHeight) { menuRef.current.style.top = `${y - rect.height}px`; } } }, [x, y]); const baseItemClass = "w-full px-3 py-1.5 text-left text-xs font-mono flex items-center gap-2"; const enabledClass = "text-[#9bc3ff] hover:bg-[rgba(117,170,252,0.1)]"; const dangerClass = "text-red-400 hover:bg-[rgba(239,68,68,0.1)]"; const disabledClass = "text-[#3a4a6a] cursor-not-allowed"; const dividerClass = "border-t border-[rgba(117,170,252,0.2)] my-1"; return (
e.preventDefault()} > {items.map((item, i) => { if (item.separator) { return
; } const cls = item.disabled ? disabledClass : item.danger ? dangerClass : enabledClass; return ( ); })}
); }