"use client"; import * as React from "react"; import { cn } from "./lib/utils"; export interface NetworkNode { id: string; label: string; x: number; y: number; size?: number; } export interface NetworkEdge { from: string; to: string; weight?: number; } export interface NetworkGraphProps extends React.HTMLAttributes { nodes: NetworkNode[]; edges: NetworkEdge[]; height?: number; } /** Node network graph: static layout + bezier curves + node highlight. */ export function NetworkGraph({ nodes, edges, height = 260, className, ...props }: NetworkGraphProps) { const [hover, setHover] = React.useState(null); const byId = new Map(nodes.map((n) => [n.id, n])); const connected = (id: string) => hover !== null && (id === hover || edges.some((e) => (e.from === hover && e.to === id) || (e.to === hover && e.from === id))); return ( {edges.map((edge, idx) => { const a = byId.get(edge.from); const b = byId.get(edge.to); if (!a || !b) return null; const mx = (a.x + b.x) / 2; const my = (a.y + b.y) / 2 - 6; const active = hover !== null && (edge.from === hover || edge.to === hover); return ( ); })} {nodes.map((node, idx) => { const r = 2.5 + (node.size ?? 0) * 1.5; const dim = hover !== null && !connected(node.id) && node.id !== hover; return ( setHover(node.id)} onMouseLeave={() => setHover(null)} style={{ animation: `ngNode 0.35s cubic-bezier(0.34,1.56,0.64,1) both`, animationDelay: `${idx * 70}ms`, transformOrigin: `${node.x}px ${node.y}px`, opacity: dim ? 0.3 : 1, transition: "opacity 0.2s" }} className="cursor-pointer" > {node.label} ); })} ); }