{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "elements-flow-graph",
  "type": "registry:component",
  "title": "Flow graph",
  "description": "Work as a graph rather than a list: branches that fan out and rejoin.",
  "registryDependencies": [
    "https://r.assistant-ui.com/elements-surfaces.json",
    "https://r.assistant-ui.com/elements-range.json"
  ],
  "files": [
    {
      "type": "registry:component",
      "path": "components/assistant-ui/elements/flow-graph.tsx",
      "sourcePath": "packages/ui/src/components/react/assistant-ui/elements/flow-graph.tsx",
      "content": "\"use client\";\n\nimport type { ComponentProps } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport { mono, paper } from \"./surfaces\";\nimport { take } from \"../utils/range\";\n\nexport type FlowNodeState = \"done\" | \"active\" | \"pending\";\n\nexport interface FlowNode {\n  id: string;\n  label: string;\n  column: number;\n  row: number;\n  state: FlowNodeState;\n}\n\nexport interface FlowEdge {\n  from: string;\n  to: string;\n}\n\nconst COL_W = 96;\nconst ROW_H = 58;\nconst NODE_W = 78;\nconst NODE_H = 30;\n\nexport function FlowGraph({\n  nodes,\n  edges,\n  visibleCount,\n  className,\n  ...props\n}: Omit<\n  ComponentProps<\"div\">,\n  \"children\" | \"nodes\" | \"edges\" | \"visibleCount\"\n> & {\n  nodes: readonly FlowNode[];\n  edges: readonly FlowEdge[];\n  visibleCount: number;\n}) {\n  const shown = take(nodes, visibleCount);\n  const shownIds = new Set(shown.map((node) => node.id));\n  const columns = Math.max(0, ...nodes.map((node) => node.column)) + 1;\n  const rows = Math.max(0, ...nodes.map((node) => node.row)) + 1;\n  const width = (columns - 1) * COL_W + NODE_W;\n  const height = (rows - 1) * ROW_H + NODE_H;\n\n  const center = (node: FlowNode) => ({\n    x: node.column * COL_W + NODE_W / 2,\n    y: node.row * ROW_H + NODE_H / 2,\n  });\n\n  return (\n    <div\n      data-slot=\"flow-graph\"\n      className={cn(\n        paper,\n        \"w-full max-w-md overflow-x-auto rounded-2xl p-4\",\n        className,\n      )}\n\n      {...props}\n    >\n      <div className=\"relative\" style={{ width, height, minWidth: width }}>\n        <svg\n          aria-hidden\n          className=\"absolute inset-0 overflow-visible\"\n          width={width}\n          height={height}\n        >\n          {edges.map((edge) => {\n            const from = nodes.find((node) => node.id === edge.from);\n            const to = nodes.find((node) => node.id === edge.to);\n            if (!from || !to) return null;\n            const live = shownIds.has(edge.from) && shownIds.has(edge.to);\n            const a = center(from);\n            const b = center(to);\n            const midX = (a.x + b.x) / 2;\n            return (\n              <path\n                key={`${edge.from}-${edge.to}`}\n                d={`M ${a.x + NODE_W / 2} ${a.y} C ${midX} ${a.y}, ${midX} ${b.y}, ${b.x - NODE_W / 2} ${b.y}`}\n                fill=\"none\"\n                strokeWidth=\"1.5\"\n                className={cn(\n                  \"transition-opacity duration-500 motion-reduce:transition-none\",\n                  live ? \"stroke-foreground/20\" : \"stroke-foreground/5\",\n                )}\n              />\n            );\n          })}\n        </svg>\n\n        {shown.map((node) => (\n          <div\n            key={node.id}\n            className={cn(\n              \"fade-in zoom-in-95 animate-in fill-mode-both absolute flex items-center justify-center rounded-xl border text-center text-[11.5px] leading-tight duration-300\",\n              node.state === \"done\" &&\n                \"border-foreground/10 bg-foreground/[0.04] text-foreground/50\",\n              node.state === \"active\" &&\n                \"text-foreground/90 border-blue-500/30 bg-blue-500/10 dark:border-blue-400/30\",\n              node.state === \"pending\" &&\n                \"border-foreground/8 text-foreground/35 border-dashed\",\n            )}\n            style={{\n              left: node.column * COL_W,\n              top: node.row * ROW_H,\n              width: NODE_W,\n              height: NODE_H,\n            }}\n          >\n            <span className={cn(mono, \"px-2\")}>{node.label}</span>\n          </div>\n        ))}\n      </div>\n    </div>\n  );\n}\n"
    }
  ]
}