{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "reactflow-canvas",
  "type": "registry:ui",
  "title": "ReactFlowCanvas",
  "description": "Interactive infinite-canvas node diagram powered by React Flow with Array UI tokens, handles, and styling.",
  "dependencies": [
    "@xyflow/react"
  ],
  "registryDependencies": [
    "https://ui.arpitbhayani.me/r/aui.json"
  ],
  "files": [
    {
      "path": "ui/reactflow-canvas.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  Background,\n  BackgroundVariant,\n  Controls,\n  Handle,\n  Position,\n  ReactFlow,\n  type Edge,\n  type Node,\n  type NodeProps,\n  type ReactFlowProps,\n} from \"@xyflow/react\";\nimport \"@xyflow/react/dist/style.css\";\nimport { cn } from \"@/lib/utils\";\n\nexport type ArchitectureStatus = \"ok\" | \"warn\" | \"err\" | \"info\";\nexport type ArchitectureDirection = \"LR\" | \"TB\";\nexport type ArchitectureEdgeRouting = \"smoothstep\" | \"bezier\" | \"straight\";\n\nexport interface ArchitectureNodeItem {\n  id: string;\n  label: string;\n  badge?: string;\n  description?: string;\n  x?: number;\n  y?: number;\n  status?: ArchitectureStatus;\n  metadata?: Record<string, string>;\n  group?: string;\n  visited?: boolean;\n}\n\nexport interface ArchitectureConnectionItem {\n  from: string;\n  to: string;\n  label?: string;\n  animated?: boolean;\n  variant?: \"solid\" | \"dashed\";\n  status?: \"ok\" | \"warn\" | \"err\" | \"primary\";\n  routing?: ArchitectureEdgeRouting;\n}\n\nexport interface ArchitectureGroupItem {\n  id: string;\n  label: string;\n  kind?: \"service\" | \"layer\" | \"package\" | \"external\";\n}\n\nexport interface ArrayUINodeData extends Record<string, unknown> {\n  label: string;\n  badge?: string;\n  description?: string;\n  status?: ArchitectureStatus;\n  visited?: boolean;\n  metadata?: Record<string, string>;\n  group?: string;\n  direction?: ArchitectureDirection;\n}\n\nexport interface ArrayUIGroupNodeData extends Record<string, unknown> {\n  label: string;\n  kind?: \"service\" | \"layer\" | \"package\" | \"external\";\n}\n\nexport type ArrayUIFlowNode = Node<ArrayUINodeData, \"architecture\">;\nexport type ArrayUIFlowGroupNode = Node<ArrayUIGroupNodeData, \"group\">;\n\nconst STATUS_BORDER: Record<ArchitectureStatus, string> = {\n  ok: \"border-l-[3px] border-l-emerald-500\",\n  info: \"\",\n  warn: \"border-l-[3px] border-l-amber-500\",\n  err: \"border-l-[3px] border-l-rose-500\",\n};\n\nexport const ArrayUINode: React.FC<NodeProps<ArrayUIFlowNode>> = ({ data, selected }) => {\n  const isTB = data.direction === \"TB\";\n  const targetPos = isTB ? Position.Top : Position.Left;\n  const sourcePos = isTB ? Position.Bottom : Position.Right;\n\n  return (\n    <div\n      className={cn(\n        \"relative bg-card border border-border rounded-md shadow-xs transition-all hover:border-primary/60 cursor-grab\",\n        selected && \"border-primary ring-2 ring-primary bg-primary/5\",\n        data.status && STATUS_BORDER[data.status],\n        !data.status && data.visited && \"border-l-[3px] border-l-emerald-500\"\n      )}\n      style={{ width: 184, minHeight: 72, padding: \"0.6rem 0.8rem\" }}\n    >\n      <Handle\n        type=\"target\"\n        position={targetPos}\n        className=\"!size-2 !rounded-full !bg-background !border !border-border hover:!bg-primary hover:!border-primary\"\n      />\n\n      <div className=\"flex items-center justify-between gap-1.5 mb-1\">\n        <div className=\"flex items-center gap-1.5 min-w-0\">\n          {data.visited && (\n            <span\n              className=\"inline-flex items-center justify-center size-3.5 rounded-full bg-emerald-500/20 text-emerald-600 dark:text-emerald-400 text-[10px] font-medium shrink-0\"\n              title=\"Visited\"\n              aria-label=\"Visited\"\n            >\n              ✓\n            </span>\n          )}\n          <span className=\"font-mono text-sm font-medium tracking-tight text-foreground truncate\" title={data.label}>\n            {data.label}\n          </span>\n        </div>\n        {data.badge && (\n          <span className=\"font-mono text-xs font-medium px-1.5 py-0.5 rounded bg-muted text-muted-foreground shrink-0\">\n            {data.badge}\n          </span>\n        )}\n      </div>\n\n      {data.description && (\n        <p className=\"text-xs text-muted-foreground line-clamp-2 leading-relaxed\">{data.description}</p>\n      )}\n\n      <Handle\n        type=\"source\"\n        position={sourcePos}\n        className=\"!size-2 !rounded-full !bg-background !border !border-border hover:!bg-primary hover:!border-primary\"\n      />\n    </div>\n  );\n};\n\nexport const ArrayUIGroupNode: React.FC<NodeProps<ArrayUIFlowGroupNode>> = ({ data, selected }) => {\n  return (\n    <div\n      className={cn(\n        \"w-full h-full relative rounded-md border border-dashed border-border bg-card/40 pointer-events-none\",\n        selected && \"border-primary\"\n      )}\n    >\n      <div className=\"flex items-center gap-2 h-7 px-2.5 font-mono text-xs text-muted-foreground\">\n        <span className=\"font-medium text-foreground\">{data.label}</span>\n        {data.kind && <span className=\"uppercase tracking-wider text-[0.68rem]\">{data.kind}</span>}\n      </div>\n    </div>\n  );\n};\n\nexport const arrayUINodeTypes = {\n  architecture: ArrayUINode,\n  group: ArrayUIGroupNode,\n};\n\nexport function toReactFlowNodesAndEdges(\n  nodes: ArchitectureNodeItem[],\n  connections: ArchitectureConnectionItem[] = [],\n  groups: ArchitectureGroupItem[] = [],\n  options: { direction?: ArchitectureDirection; defaultRouting?: ArchitectureEdgeRouting } = {}\n): {\n  nodes: (ArrayUIFlowNode | ArrayUIFlowGroupNode)[];\n  edges: Edge[];\n} {\n  const direction = options.direction ?? \"LR\";\n  const defaultRouting = options.defaultRouting ?? \"smoothstep\";\n\n  const flowNodes: (ArrayUIFlowNode | ArrayUIFlowGroupNode)[] = [];\n\n  // Group nodes\n  for (const g of groups) {\n    const members = nodes.filter((n) => n.group === g.id);\n    if (members.length === 0) continue;\n    flowNodes.push({\n      id: g.id,\n      type: \"group\",\n      position: { x: 0, y: 0 },\n      style: { width: \"100%\", height: \"100%\", zIndex: -1 },\n      data: { label: g.label, kind: g.kind },\n      draggable: false,\n      selectable: false,\n    });\n  }\n\n  // Member nodes\n  nodes.forEach((n, idx) => {\n    const defaultX = direction === \"LR\" ? idx * 240 + 40 : 120;\n    const defaultY = direction === \"LR\" ? 120 : idx * 140 + 40;\n    const x = n.x !== undefined ? n.x * 6 : defaultX;\n    const y = n.y !== undefined ? n.y * 4 : defaultY;\n\n    flowNodes.push({\n      id: n.id,\n      type: \"architecture\",\n      position: { x, y },\n      data: {\n        label: n.label,\n        badge: n.badge,\n        description: n.description,\n        status: n.status,\n        visited: n.visited,\n        metadata: n.metadata,\n        group: n.group,\n        direction,\n      },\n    });\n  });\n\n  // Edges\n  const flowEdges: Edge[] = connections.map((c, i) => ({\n    id: `e-${c.from}-${c.to}-${i}`,\n    source: c.from,\n    target: c.to,\n    type: c.routing ?? defaultRouting,\n    animated: c.animated,\n    label: c.label,\n    style: {\n      stroke: c.status === \"primary\" ? \"hsl(var(--primary))\" : \"hsl(var(--border))\",\n      strokeWidth: 1.5,\n      strokeDasharray: c.variant === \"dashed\" ? \"4 4\" : undefined,\n    },\n  }));\n\n  return { nodes: flowNodes, edges: flowEdges };\n}\n\nexport interface ReactFlowCanvasProps extends Omit<ReactFlowProps, \"nodes\" | \"edges\" | \"height\"> {\n  title?: string;\n  subtitle?: string;\n  badge?: React.ReactNode;\n  nodes?: ArchitectureNodeItem[] | Node[];\n  connections?: ArchitectureConnectionItem[];\n  groups?: ArchitectureGroupItem[];\n  direction?: ArchitectureDirection;\n  defaultRouting?: ArchitectureEdgeRouting;\n  edges?: Edge[];\n  height?: number | string;\n  showControls?: boolean;\n  showBackground?: boolean;\n}\n\nexport function ReactFlowCanvas({\n  title = \"System Architecture\",\n  subtitle,\n  badge,\n  nodes: inputNodes = [],\n  connections = [],\n  groups = [],\n  direction = \"LR\",\n  defaultRouting = \"smoothstep\",\n  edges: controlledEdges,\n  height = 480,\n  showControls = true,\n  showBackground = true,\n  className,\n  ...props\n}: ReactFlowCanvasProps) {\n  const isArrayUINodes =\n    inputNodes.length === 0 ||\n    !(\"position\" in (inputNodes[0] as Record<string, unknown>));\n\n  const { nodes, edges } = React.useMemo(() => {\n    if (!isArrayUINodes) {\n      return {\n        nodes: inputNodes as Node[],\n        edges: controlledEdges ?? [],\n      };\n    }\n    return toReactFlowNodesAndEdges(\n      inputNodes as ArchitectureNodeItem[],\n      connections,\n      groups,\n      { direction, defaultRouting }\n    );\n  }, [isArrayUINodes, inputNodes, connections, groups, direction, defaultRouting, controlledEdges]);\n\n  return (\n    <div\n      className={cn(\n        \"flex flex-col border border-border rounded-lg bg-card overflow-hidden shadow-xs my-6\",\n        className\n      )}\n    >\n      {(title || badge) && (\n        <div className=\"flex items-center justify-between p-3.5 bg-background border-b border-border flex-wrap gap-2\">\n          <div className=\"flex items-center gap-2.5\">\n            <span className=\"font-heading font-bold text-base text-foreground\">{title}</span>\n            {subtitle && <span className=\"text-sm text-muted-foreground\">{subtitle}</span>}\n          </div>\n          {badge && <div>{badge}</div>}\n        </div>\n      )}\n\n      <div style={{ width: \"100%\", height, position: \"relative\" }}>\n        <ReactFlow\n          nodes={nodes}\n          edges={controlledEdges ?? edges}\n          nodeTypes={arrayUINodeTypes}\n          fitView\n          {...props}\n        >\n          {showBackground && (\n            <Background\n              variant={BackgroundVariant.Dots}\n              gap={20}\n              size={1.5}\n              color=\"hsl(var(--border))\"\n            />\n          )}\n          {showControls && <Controls />}\n        </ReactFlow>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:ui"
    }
  ]
}