feat: add glow edge system with directional selection animation

Custom bezier edges with gradient glow for the flow editor:
- Default: subtle white/gray gradient with soft glow
- Downstream (cyan): animated flowing dashes from selected node through subtree
- Upstream (amber): animated flow from selected node back to root
- Cross-reference: dashed cyan with arrow markers
- SVG gradient + filter defs for performant rendering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-03-09 04:36:46 -04:00
parent d06abe5829
commit b28a096738
4 changed files with 311 additions and 15 deletions

View File

@@ -12,10 +12,10 @@ import {
PanOnScrollMode,
type NodeMouseHandler,
} from '@xyflow/react'
import '@xyflow/react/dist/style.css'
import { FlowCanvasNode, NODE_TYPE_CONFIG } from './FlowCanvasNode'
import { FlowCanvasAnswerNode } from './FlowCanvasAnswerNode'
import { GlowEdge, GlowEdgeDefs } from './GlowEdge'
import { useTreeLayout } from './useTreeLayout'
import { cn } from '@/lib/utils'
import { Map as MapIcon, MapPinOff } from 'lucide-react'
@@ -27,6 +27,10 @@ const nodeTypes = {
answerStub: FlowCanvasAnswerNode,
}
const edgeTypes = {
glowEdge: GlowEdge,
}
interface FlowCanvasProps {
selectedNodeId: string | null
onNodeSelect: (nodeId: string | null) => void
@@ -36,7 +40,7 @@ interface FlowCanvasProps {
function FlowCanvasInner({ selectedNodeId, onNodeSelect, onSelectAnswerType, onNodeContextMenu }: FlowCanvasProps) {
const { fitView, setCenter } = useReactFlow()
const { nodes: layoutNodes, edges: layoutEdges, collapsedNodeIds, toggleCollapse, onNodesMeasured } = useTreeLayout()
const { nodes: layoutNodes, edges: layoutEdges, collapsedNodeIds, toggleCollapse, onNodesMeasured } = useTreeLayout(selectedNodeId)
const [minimapVisible, setMinimapVisible] = useState(true)
// Inject callbacks into node data (because useTreeLayout creates placeholder functions)
@@ -124,6 +128,7 @@ function FlowCanvasInner({ selectedNodeId, onNodeSelect, onSelectAnswerType, onN
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
nodeTypes={nodeTypes}
edgeTypes={edgeTypes}
onNodeClick={handleNodeClick}
onPaneClick={handlePaneClick}
fitView
@@ -139,6 +144,7 @@ function FlowCanvasInner({ selectedNodeId, onNodeSelect, onSelectAnswerType, onN
proOptions={{ hideAttribution: true }}
className="dark bg-accent/30"
>
<GlowEdgeDefs />
<Background variant={BackgroundVariant.Dots} gap={20} size={1.5} color="oklch(0.63 0.02 260 / 0.25)" />
<Controls showInteractive={false} className="bg-card! border-border! shadow-lg!" />
{minimapVisible && (