fix: context menu dismisses on pane click, ISP in toolbar

Context menu now closes when clicking anywhere on the canvas via
onPaneClick prop. ISP device added as built-in toolbar item under
Internet section so it's always available without a database entry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-04-04 17:44:48 +00:00
parent 6e5614e7b4
commit 2ea56f2563
3 changed files with 31 additions and 2 deletions

View File

@@ -28,6 +28,7 @@ interface NetworkCanvasProps {
isDragOver?: boolean isDragOver?: boolean
onNodeContextMenu?: (event: React.MouseEvent, node: Node) => void onNodeContextMenu?: (event: React.MouseEvent, node: Node) => void
onPaneContextMenu?: (event: MouseEvent | React.MouseEvent) => void onPaneContextMenu?: (event: MouseEvent | React.MouseEvent) => void
onPaneClick?: () => void
} }
export function NetworkCanvas({ export function NetworkCanvas({
@@ -44,6 +45,7 @@ export function NetworkCanvas({
isDragOver, isDragOver,
onNodeContextMenu, onNodeContextMenu,
onPaneContextMenu, onPaneContextMenu,
onPaneClick: onPaneClickProp,
}: NetworkCanvasProps) { }: NetworkCanvasProps) {
const handleSelectionChange = useCallback(({ nodes: selectedNodes, edges: selectedEdges }: { nodes: Node[]; edges: Edge[] }) => { const handleSelectionChange = useCallback(({ nodes: selectedNodes, edges: selectedEdges }: { nodes: Node[]; edges: Edge[] }) => {
if (selectedNodes.length === 1) { if (selectedNodes.length === 1) {
@@ -61,7 +63,8 @@ export function NetworkCanvas({
const handlePaneClick = useCallback(() => { const handlePaneClick = useCallback(() => {
onNodeSelect(null) onNodeSelect(null)
onEdgeSelect(null) onEdgeSelect(null)
}, [onNodeSelect, onEdgeSelect]) onPaneClickProp?.()
}, [onNodeSelect, onEdgeSelect, onPaneClickProp])
return ( return (
<div className="relative h-full w-full" onDragLeave={onDragLeave}> <div className="relative h-full w-full" onDragLeave={onDragLeave}>

View File

@@ -1,5 +1,5 @@
import { useState, useMemo, useCallback } from 'react' import { useState, useMemo, useCallback } from 'react'
import { Search, Plus, ChevronDown, ChevronRight, X, LayoutGrid, GripVertical } from 'lucide-react' import { Search, Plus, ChevronDown, ChevronRight, X, LayoutGrid, GripVertical, Globe } from 'lucide-react'
import { getDeviceRenderConfig, CATEGORY_LABELS, CATEGORY_ORDER } from '../nodes/deviceRegistry' import { getDeviceRenderConfig, CATEGORY_LABELS, CATEGORY_ORDER } from '../nodes/deviceRegistry'
import type { DeviceTypeResponse, DeviceTypeCreate } from '@/types' import type { DeviceTypeResponse, DeviceTypeCreate } from '@/types'
import { deviceTypesApi } from '@/api' import { deviceTypesApi } from '@/api'
@@ -122,6 +122,31 @@ export function DeviceToolbar({ deviceTypes, onDeviceTypesChange }: DeviceToolba
})} })}
</div> </div>
{/* Internet section */}
<div className="mb-1 mt-2 border-t border-default pt-2">
<div className="flex items-center gap-1 px-1 py-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">
Internet
</div>
<div className="flex flex-col gap-0.5">
<div
draggable
onDragStart={e => {
e.dataTransfer.setData('application/reactflow-device', JSON.stringify({
slug: 'isp',
label: 'ISP',
category: 'cloud',
}))
e.dataTransfer.effectAllowed = 'move'
}}
className="flex cursor-grab items-center gap-2 rounded px-2 py-1.5 text-xs text-primary hover:bg-elevated active:cursor-grabbing active:scale-[0.98] transition-transform"
>
<GripVertical size={12} className="shrink-0 text-muted-foreground/50" />
<Globe size={14} style={{ color: 'var(--color-accent)' }} />
<span>ISP</span>
</div>
</div>
</div>
{/* Grouping section */} {/* Grouping section */}
<div className="mb-1 mt-2 border-t border-default pt-2"> <div className="mb-1 mt-2 border-t border-default pt-2">
<div className="flex items-center gap-1 px-1 py-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground"> <div className="flex items-center gap-1 px-1 py-1 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground">

View File

@@ -495,6 +495,7 @@ function DiagramEditorInner() {
isDragOver={isDragOver} isDragOver={isDragOver}
onNodeContextMenu={handleNodeContextMenu} onNodeContextMenu={handleNodeContextMenu}
onPaneContextMenu={handlePaneContextMenu} onPaneContextMenu={handlePaneContextMenu}
onPaneClick={closeContextMenu}
/> />
</div> </div>
<AIAssistPanel <AIAssistPanel