{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "flowchart",
  "title": "Flowchart",
  "author": "AlgorizeTech",
  "description": "A node-and-connector flow diagram with highlightable paths and GSAP transitions. Use it to explain a pipeline or decision flow.",
  "dependencies": [],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/custom/blocks/FlowchartBlock.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\nimport React, { forwardRef, useLayoutEffect, useRef, useState } from \"react\";\nimport { FlowchartContext, useFlowchart } from \"./flowchart-context\";\n\n// ==========================================\n// MOTION\n// ==========================================\n// GSAP's back.out(1.2) and power2.out, exact as cubic-beziers.\nconst NODE_IN_EASE = \"cubic-bezier(.3333, 1.4, .6667, 1)\";\nconst DRAW_EASE = \"cubic-bezier(.3333, 1, .6667, 1)\";\n\nconst FLOWCHART_CSS = `\n@keyframes flowchart-node-in {\n  from { opacity: 0; transform: translateX(-10px) scale(0.9); }\n  to { opacity: 1; transform: none; }\n}\n@keyframes flowchart-draw {\n  from { stroke-dashoffset: var(--flowchart-len); }\n  to { stroke-dashoffset: 0; }\n}\n`;\n\n// ==========================================\n// COMPONENTS\n// ==========================================\n\nexport interface FlowchartProps extends React.HTMLAttributes<HTMLDivElement> {\n  initialActivePath?: string | null;\n  color?: \"primary\" | \"blue\" | \"indigo\" | \"rose\" | \"emerald\" | \"zinc\";\n  /**\n   * `fixed` is the 900×400 blueprint canvas with a grid. `fluid` is just the\n   * context and a positioned box, for connectors drawn between ordinary\n   * layout — size the `FlowCanvas` yourself with `viewBox`.\n   */\n  layout?: \"fixed\" | \"fluid\";\n}\n\nexport const Flowchart = forwardRef<HTMLDivElement, FlowchartProps>(\n  (\n    {\n      children,\n      initialActivePath = null,\n      color = \"primary\",\n      layout = \"fixed\",\n      className,\n      ...props\n    },\n    ref,\n  ) => {\n    const [activePath, setActivePath] = useState<string | null>(\n      initialActivePath,\n    );\n\n    if (layout === \"fluid\") {\n      return (\n        <FlowchartContext.Provider value={{ activePath, setActivePath, color }}>\n          <div ref={ref} className={cn(\"relative w-full\", className)} {...props}>\n            {children}\n          </div>\n        </FlowchartContext.Provider>\n      );\n    }\n\n    return (\n      <FlowchartContext.Provider value={{ activePath, setActivePath, color }}>\n        <div\n          ref={ref}\n          className={cn(\n            \"w-full overflow-x-auto bg-background border border-border/80 rounded-2xl select-none shadow-inner custom-scrollbar\",\n            className,\n          )}\n          {...props}\n        >\n          {/* Locked Canvas Matrix Workspace Container */}\n          <div className=\"relative w-[900px] h-[400px] mx-auto overflow-hidden\">\n            {/* Architectural Blueprint Grid */}\n            <div className=\"absolute inset-0 bg-[linear-gradient(to_right,#8080800d_1px,transparent_1px),linear-gradient(to_bottom,#8080800d_1px,transparent_1px)] bg-[size:24px_24px] pointer-events-none z-0\" />\n\n            {/* Content Container Layer */}\n            <div className=\"relative z-10 w-full h-full\">{children}</div>\n          </div>\n        </div>\n      </FlowchartContext.Provider>\n    );\n  },\n);\nFlowchart.displayName = \"Flowchart\";\n\nexport interface FlowNodeProps extends React.HTMLAttributes<HTMLDivElement> {\n  id: string;\n  title: string;\n  description?: string;\n  icon?: React.ElementType;\n  connectedPaths?: string[];\n}\n\nexport const FlowNode = forwardRef<HTMLDivElement, FlowNodeProps>(\n  (\n    {\n      title,\n      description,\n      icon: Icon,\n      connectedPaths = [],\n      className,\n      style,\n      ...props\n    },\n    ref,\n  ) => {\n    const { activePath, setActivePath, color } = useFlowchart();\n    const nodeRef = useRef<HTMLDivElement>(null);\n\n    React.useImperativeHandle(ref, () => nodeRef.current as HTMLDivElement);\n\n    const isActiveNode = connectedPaths.includes(activePath || \"\");\n\n    const colorThemes: Record<\n      string,\n      { ring: string; glow: string; iconBg: string }\n    > = {\n      indigo: {\n        ring: \"ring-indigo-500/80 dark:ring-indigo-400\",\n        glow: \"shadow-[0_0_30px_-5px_rgba(99,102,241,0.35)]\",\n        iconBg: \"bg-indigo-500 text-white border-indigo-400\",\n      },\n      blue: {\n        ring: \"ring-blue-500/80 dark:ring-blue-400\",\n        glow: \"shadow-[0_0_30px_-5px_rgba(59,130,246,0.35)]\",\n        iconBg: \"bg-blue-500 text-white border-blue-400\",\n      },\n      rose: {\n        ring: \"ring-rose-500/80 dark:ring-rose-400\",\n        glow: \"shadow-[0_0_30px_-5px_rgba(244,63,94,0.35)]\",\n        iconBg: \"bg-rose-500 text-white border-rose-400\",\n      },\n      emerald: {\n        ring: \"ring-emerald-500/80 dark:ring-emerald-400\",\n        glow: \"shadow-[0_0_30px_-5px_rgba(16,185,129,0.35)]\",\n        iconBg: \"bg-emerald-500 text-white border-emerald-400\",\n      },\n      zinc: {\n        ring: \"ring-primary/30\",\n        glow: \"shadow-[0_0_30px_-5px_rgba(0,0,0,0.2)] dark:shadow-[0_0_30px_-5px_rgba(255,255,255,0.15)]\",\n        iconBg:\n          \"bg-primary text-primary-foreground border-zinc-700\",\n      },\n      // The default. Unlike the fixed palettes above it follows whatever\n      // --primary the consumer's theme sets, so a flowchart matches the rest of\n      // their app without being told which colour to use.\n      primary: {\n        ring: \"ring-primary/80\",\n        glow: \"shadow-[0_0_30px_-5px_var(--primary)]\",\n        iconBg: \"bg-primary text-primary-foreground border-primary/30\",\n      },\n    };\n\n    const currentTheme = colorThemes[color] ?? colorThemes[\"primary\"] ?? { ring: \"\", glow: \"\", iconBg: \"\" };\n\n    return (\n      <div\n        ref={nodeRef}\n        onClick={() => {\n          const first = connectedPaths[0];\n          if (first) setActivePath(first);\n        }}\n        // Enforced strict absolute height & width to secure connection nodes\n        className={cn(\n          \"absolute w-[240px] h-[68px] p-3 rounded-xl transition-all duration-300 cursor-pointer flex items-center gap-3 backdrop-blur-xl select-none\",\n          isActiveNode\n            ? cn(\n                \"bg-card/95 scale-[1.02] ring-2 z-30\",\n                currentTheme.ring,\n                currentTheme.glow,\n              )\n            : \"bg-card/50 border border-border/80 hover:bg-card/80 hover:scale-[1.01] shadow-sm z-20\",\n          className,\n        )}\n        // Mount entrance: fades and scales in from the left. `scale: none`\n        // keeps the active / hover scale classes inert, as they have always been.\n        style={{\n          animation: `flowchart-node-in 0.5s ${NODE_IN_EASE} backwards`,\n          scale: \"none\",\n          ...style,\n        }}\n        {...props}\n      >\n        <style>{FLOWCHART_CSS}</style>\n        {Icon && (\n          <div\n            className={cn(\n              \"w-10 h-10 rounded-lg flex-shrink-0 flex items-center justify-center border transition-all duration-300\",\n              isActiveNode\n                ? currentTheme.iconBg\n                : \"bg-muted border-border text-muted-foreground\",\n            )}\n          >\n            <Icon className=\"w-[18px] h-[18px]\" />\n          </div>\n        )}\n        <div className=\"min-w-0 flex-1\">\n          <h4 className=\"text-[13.5px] font-semibold tracking-tight text-foreground truncate\">\n            {title}\n          </h4>\n          {description && (\n            <p className=\"text-[11.5px] font-medium text-muted-foreground truncate mt-0.5\">\n              {description}\n            </p>\n          )}\n        </div>\n      </div>\n    );\n  },\n);\nFlowNode.displayName = \"FlowNode\";\n\n// ==========================================\n// SVG CANVAS OVERLAY\n// ==========================================\nexport type FlowCanvasProps = React.SVGAttributes<SVGSVGElement>;\n\nexport const FlowCanvas = forwardRef<SVGSVGElement, FlowCanvasProps>(\n  ({ children, className, ...props }, ref) => {\n    return (\n      <svg\n        ref={ref}\n        width=\"900\"\n        height=\"400\"\n        viewBox=\"0 0 900 400\"\n        className={cn(\n          \"absolute inset-0 w-full h-full pointer-events-none z-10\",\n          className,\n        )}\n        {...props}\n      >\n        {children}\n      </svg>\n    );\n  },\n);\nFlowCanvas.displayName = \"FlowCanvas\";\n\nexport interface FlowConnectionProps\n  extends React.SVGAttributes<SVGPathElement> {\n  id: string;\n  path: string;\n  /** Draw the resting line dashed — `true` for \"4 6\", or your own pattern. */\n  dashed?: boolean | string;\n  /** Dash pattern of the moving overlay while the route is active. */\n  flowDash?: string;\n  /** The soft blurred halo under the active line. */\n  glow?: boolean;\n  /** Seconds the draw-in takes on mount. */\n  drawDuration?: number;\n}\n\nexport const FlowConnection = forwardRef<SVGPathElement, FlowConnectionProps>(\n  (\n    { id, path, dashed = false, flowDash = \"6 10\", glow = true, drawDuration = 1.2, className, ...props },\n    ref,\n  ) => {\n    const { activePath, color } = useFlowchart();\n    const staticLineRef = useRef<SVGPathElement>(null);\n\n    React.useImperativeHandle(\n      ref,\n      () => staticLineRef.current as SVGPathElement,\n    );\n    const isActive = activePath === id;\n    const restingDash = dashed === true ? \"4 6\" : dashed || null;\n    // The moving overlay waits for the line to finish drawing itself.\n    const [drawn, setDrawn] = useState(false);\n\n    useLayoutEffect(() => {\n      const el = staticLineRef.current;\n      if (!el) return;\n      const reduced =\n        typeof window !== \"undefined\" &&\n        window.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches;\n      if (reduced) {\n        el.style.strokeDasharray = restingDash ?? \"none\";\n        el.style.strokeDashoffset = \"0\";\n        setDrawn(true);\n        return;\n      }\n      const length = el.getTotalLength();\n      // Draw-in: one dash as long as the line, its offset run from the full\n      // length down to 0 (held hidden through the delay by `backwards`).\n      el.style.strokeDasharray = `${length}px`;\n      el.style.setProperty(\"--flowchart-len\", `${length}px`);\n      el.style.animation = `flowchart-draw ${drawDuration}s ${DRAW_EASE} 0.1s backwards`;\n\n      let done = false;\n      // Once drawn, hand the line its resting pattern — the draw-in borrowed\n      // the dash array, so a dashed line has to be restored here.\n      const finish = () => {\n        if (done) return;\n        done = true;\n        window.clearTimeout(fallback);\n        el.removeEventListener(\"animationend\", onEnd);\n        el.style.animation = \"\";\n        el.style.strokeDasharray = restingDash ?? \"none\";\n        el.style.strokeDashoffset = \"0\";\n        setDrawn(true);\n      };\n      const onEnd = (e: AnimationEvent) => {\n        if (e.target === el && e.animationName === \"flowchart-draw\") finish();\n      };\n      el.addEventListener(\"animationend\", onEnd);\n      // A canvas under `display: none` (e.g. `hidden lg:block`) never runs the\n      // animation, so no animationend comes; finish on time regardless.\n      const fallback = window.setTimeout(finish, (0.1 + drawDuration) * 1000 + 100);\n      return () => {\n        window.clearTimeout(fallback);\n        el.removeEventListener(\"animationend\", onEnd);\n      };\n      // Runs once: the draw-in is a mount effect, later `path` changes just move the line.\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n    }, []);\n\n    // One dash + one gap is the loop length, so the flow never jumps.\n    const flowCycle = flowDash\n      .split(/[\\s,]+/)\n      .map(Number)\n      .filter((n) => !Number.isNaN(n))\n      .reduce((a, b) => a + b, 0);\n\n    const strokeColors: Record<string, string> = {\n      // A var() rather than a hex: SVG stroke resolves it, so the active\n      // connector tracks the consumer's theme like the nodes do.\n      primary: \"var(--primary)\",\n      indigo: \"#6366f1\",\n      blue: \"#3b82f6\",\n      rose: \"#f43f5e\",\n      emerald: \"#10b981\",\n      zinc: \"#71717a\",\n    };\n    const activeStroke = strokeColors[color] ?? strokeColors[\"primary\"] ?? \"var(--primary)\";\n\n    return (\n      <>\n        {/* Base Blueprint Structural Track Vector Line */}\n        <path\n          ref={staticLineRef}\n          d={path}\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeWidth=\"1.5\"\n          className={cn(\n            \"text-border transition-colors duration-500\",\n            className,\n          )}\n          {...props}\n        />\n\n        {/* Active Streaming Overlay Line */}\n        {isActive && drawn && (\n          <>\n            <path\n              d={path}\n              fill=\"none\"\n              stroke={activeStroke}\n              strokeWidth=\"2\"\n              strokeLinecap=\"round\"\n              strokeDasharray={flowDash}\n              className=\"animate-[flow-dash_1.2s_linear_infinite] motion-reduce:animate-none\"\n              style={{ \"--flow-cycle\": `${flowCycle || 16}px` } as React.CSSProperties}\n            />\n            {glow && (\n              <path\n                d={path}\n                fill=\"none\"\n                stroke={activeStroke}\n                strokeWidth=\"5\"\n                className=\"opacity-20 dark:opacity-30 blur-[2px]\"\n              />\n            )}\n          </>\n        )}\n\n        <style>{FLOWCHART_CSS}</style>\n        <style>{`\n          @keyframes flow-dash {\n            to { stroke-dashoffset: calc(-1 * var(--flow-cycle, 16px)); }\n          }\n        `}</style>\n      </>\n    );\n  },\n);\nFlowConnection.displayName = \"FlowConnection\";\n",
      "type": "registry:component",
      "target": "components/custom/blocks/FlowchartBlock.tsx"
    },
    {
      "path": "src/components/custom/blocks/flowchart-context.ts",
      "content": "\"use client\";\n\nimport { createContext, useContext } from \"react\";\n\ninterface FlowchartContextType {\n  activePath: string | null;\n  setActivePath: (pathId: string | null) => void;\n  color: string;\n}\n\nexport const FlowchartContext = createContext<FlowchartContextType | null>(null);\n\nexport function useFlowchart() {\n  const context = useContext(FlowchartContext);\n  if (!context) {\n    throw new Error(\n      \"Flowchart sub-components must be used within a Flowchart root.\",\n    );\n  }\n  return context;\n}\n",
      "type": "registry:component",
      "target": "components/custom/blocks/flowchart-context.ts"
    }
  ],
  "docs": "Named exports: Flowchart, FlowCanvas, FlowNode, FlowConnection, useFlowchart. Nodes declare `connectedPaths`; setting the active path highlights every node and connection on it. Positions are authored by you — there is no auto-layout. `layout=\"fluid\"` drops the fixed canvas so connections can be drawn over your own layout; `FlowConnection` takes `dashed`, `flowDash`, `glow` and `drawDuration`.",
  "categories": [
    "data-display"
  ],
  "type": "registry:component"
}
