diff --git a/frontend/src/components/network/NetworkCanvas.tsx b/frontend/src/components/network/NetworkCanvas.tsx
index 2c6af6b1..7f19361a 100644
--- a/frontend/src/components/network/NetworkCanvas.tsx
+++ b/frontend/src/components/network/NetworkCanvas.tsx
@@ -24,6 +24,8 @@ interface NetworkCanvasProps {
onEdgeSelect: (edgeId: string | null) => void
onDrop: (event: React.DragEvent) => void
onDragOver: (event: React.DragEvent) => void
+ onDragLeave?: (event: React.DragEvent) => void
+ isDragOver?: boolean
}
export function NetworkCanvas({
@@ -36,6 +38,8 @@ export function NetworkCanvas({
onEdgeSelect,
onDrop,
onDragOver,
+ onDragLeave,
+ isDragOver,
}: NetworkCanvasProps) {
const handleSelectionChange = useCallback(({ nodes: selectedNodes, edges: selectedEdges }: { nodes: Node[]; edges: Edge[] }) => {
if (selectedNodes.length === 1) {
@@ -56,32 +60,41 @@ export function NetworkCanvas({
}, [onNodeSelect, onEdgeSelect])
return (
-
-
-
-
-
+
+
+
+
+
+
+ {isDragOver && (
+
+
+ Drop to add
+
+
+ )}
+
)
}
diff --git a/frontend/src/components/network/panels/DeviceToolbar.tsx b/frontend/src/components/network/panels/DeviceToolbar.tsx
index fa5ea810..2aa38970 100644
--- a/frontend/src/components/network/panels/DeviceToolbar.tsx
+++ b/frontend/src/components/network/panels/DeviceToolbar.tsx
@@ -1,5 +1,5 @@
import { useState, useMemo, useCallback } from 'react'
-import { Search, Plus, ChevronDown, ChevronRight, X, LayoutGrid } from 'lucide-react'
+import { Search, Plus, ChevronDown, ChevronRight, X, LayoutGrid, GripVertical } from 'lucide-react'
import { getDeviceRenderConfig, CATEGORY_LABELS, CATEGORY_ORDER } from '../nodes/deviceRegistry'
import type { DeviceTypeResponse, DeviceTypeCreate } from '@/types'
import { deviceTypesApi } from '@/api'
@@ -107,8 +107,9 @@ export function DeviceToolbar({ deviceTypes, onDeviceTypesChange }: DeviceToolba
key={dt.id}
draggable
onDragStart={e => handleDragStart(e, dt)}
- className="flex cursor-grab items-center gap-2 rounded px-2 py-1.5 text-xs text-primary hover:bg-elevated active:cursor-grabbing"
+ 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"
>
+
{dt.label}
@@ -140,8 +141,9 @@ export function DeviceToolbar({ deviceTypes, onDeviceTypesChange }: DeviceToolba
e.dataTransfer.setData('application/reactflow-group', JSON.stringify(item))
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"
+ 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"
>
+
{item.label}
diff --git a/frontend/src/pages/NetworkDiagrams/DiagramEditor.tsx b/frontend/src/pages/NetworkDiagrams/DiagramEditor.tsx
index d8b82740..88d71b74 100644
--- a/frontend/src/pages/NetworkDiagrams/DiagramEditor.tsx
+++ b/frontend/src/pages/NetworkDiagrams/DiagramEditor.tsx
@@ -50,6 +50,7 @@ function DiagramEditorInner() {
const [deviceTypes, setDeviceTypes] = useState([])
const [loading, setLoading] = useState(!!id)
+ const [isDragOver, setIsDragOver] = useState(false)
useEffect(() => { isDirtyRef.current = isDirty }, [isDirty])
useEffect(() => { diagramIdRef.current = diagramId }, [diagramId])
@@ -196,10 +197,18 @@ function DiagramEditorInner() {
const onDragOver = useCallback((event: React.DragEvent) => {
event.preventDefault()
event.dataTransfer.dropEffect = 'move'
+ setIsDragOver(true)
+ }, [])
+
+ const onDragLeave = useCallback((event: React.DragEvent) => {
+ const relatedTarget = event.relatedTarget as HTMLElement | null
+ if (relatedTarget && (event.currentTarget as HTMLElement).contains(relatedTarget)) return
+ setIsDragOver(false)
}, [])
const onDrop = useCallback((event: React.DragEvent) => {
event.preventDefault()
+ setIsDragOver(false)
const position = screenToFlowPosition({ x: event.clientX, y: event.clientY })
@@ -415,6 +424,8 @@ function DiagramEditorInner() {
onEdgeSelect={setSelectedEdgeId}
onDrop={onDrop}
onDragOver={onDragOver}
+ onDragLeave={onDragLeave}
+ isDragOver={isDragOver}
/>