diff --git a/frontend/src/components/network/ContextMenu.tsx b/frontend/src/components/network/ContextMenu.tsx new file mode 100644 index 00000000..ff798b65 --- /dev/null +++ b/frontend/src/components/network/ContextMenu.tsx @@ -0,0 +1,106 @@ +import { useEffect, useRef } from 'react' +import { Copy, CopyPlus, Trash2, ClipboardPaste, BoxSelect, Maximize2 } from 'lucide-react' +import { cn } from '@/lib/utils' + +interface MenuAction { + label: string + icon: React.ElementType + shortcut: string + onClick: () => void + disabled?: boolean +} + +interface ContextMenuProps { + position: { x: number; y: number } + actions: MenuAction[] + onClose: () => void +} + +export function ContextMenu({ position, actions, onClose }: ContextMenuProps) { + const menuRef = useRef(null) + + const clampedPosition = { ...position } + if (typeof window !== 'undefined') { + const menuWidth = 192 + const menuHeight = actions.length * 36 + 8 + if (clampedPosition.x + menuWidth > window.innerWidth) { + clampedPosition.x = window.innerWidth - menuWidth - 8 + } + if (clampedPosition.y + menuHeight > window.innerHeight) { + clampedPosition.y = window.innerHeight - menuHeight - 8 + } + } + + useEffect(() => { + const handleClickOutside = (e: MouseEvent) => { + if (menuRef.current && !menuRef.current.contains(e.target as HTMLElement)) { + onClose() + } + } + const handleEscape = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose() + } + const handleScroll = () => onClose() + + document.addEventListener('mousedown', handleClickOutside) + document.addEventListener('keydown', handleEscape) + document.addEventListener('scroll', handleScroll, true) + return () => { + document.removeEventListener('mousedown', handleClickOutside) + document.removeEventListener('keydown', handleEscape) + document.removeEventListener('scroll', handleScroll, true) + } + }, [onClose]) + + return ( +
+ {actions.map((action) => ( + + ))} +
+ ) +} + +export function getNodeMenuActions(handlers: { + onCopy: () => void + onDuplicate: () => void + onDelete: () => void +}): MenuAction[] { + return [ + { label: 'Copy', icon: Copy, shortcut: 'Ctrl+C', onClick: handlers.onCopy }, + { label: 'Duplicate', icon: CopyPlus, shortcut: 'Ctrl+D', onClick: handlers.onDuplicate }, + { label: 'Delete', icon: Trash2, shortcut: 'Del', onClick: handlers.onDelete }, + ] +} + +export function getCanvasMenuActions(handlers: { + onPaste: () => void + onSelectAll: () => void + onFitView: () => void + hasClipboard: boolean +}): MenuAction[] { + return [ + { label: 'Paste', icon: ClipboardPaste, shortcut: 'Ctrl+V', onClick: handlers.onPaste, disabled: !handlers.hasClipboard }, + { label: 'Select All', icon: BoxSelect, shortcut: 'Ctrl+A', onClick: handlers.onSelectAll }, + { label: 'Fit View', icon: Maximize2, shortcut: '⌘⇧F', onClick: handlers.onFitView }, + ] +}