{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "reasoning",
  "title": "Reasoning",
  "description": "Collapsible AI reasoning trace with streaming support and ordered or free-form steps.",
  "dependencies": [
    "@vllnt/ui@^0.2.1",
    "lucide-react"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/reasoning/reasoning.tsx",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useId, useState } from \"react\";\n\nimport { Brain, ChevronDown } from \"lucide-react\";\n\nimport { cn } from \"@vllnt/ui\";\n\nexport type ReasoningProps = {\n  /** Free-form reasoning content; renders when `steps` is absent. */\n  children?: React.ReactNode;\n  className?: string;\n  /** Seconds the model spent reasoning, shown in the header. */\n  duration?: number;\n  /** Whether the reasoning trace is still streaming; auto-expands the panel. */\n  isStreaming?: boolean;\n  /** Called whenever the expanded state changes. */\n  onOpenChange?: (open: boolean) => void;\n  /** Reasoning steps rendered as an ordered list when expanded. */\n  steps?: string[];\n};\n\nfunction ReasoningTrigger({\n  contentId,\n  duration,\n  isOpen,\n  isStreaming,\n  onToggle,\n}: {\n  contentId: string;\n  duration?: number;\n  isOpen: boolean;\n  isStreaming: boolean;\n  onToggle: () => void;\n}) {\n  return (\n    <button\n      aria-controls={contentId}\n      aria-expanded={isOpen}\n      className=\"flex w-full items-center gap-2 px-3 py-2 text-sm text-muted-foreground transition-colors hover:text-foreground\"\n      onClick={onToggle}\n      type=\"button\"\n    >\n      <Brain className=\"size-4 shrink-0\" />\n      <span className=\"font-medium\">\n        {isStreaming ? \"Reasoning\" : \"Reasoned\"}\n        {typeof duration === \"number\" ? ` for ${duration}s` : null}\n      </span>\n      {isStreaming ? <span className=\"animate-pulse\">&hellip;</span> : null}\n      <ChevronDown\n        className={cn(\n          \"ml-auto size-4 shrink-0 transition-transform\",\n          isOpen ? \"rotate-180\" : \"rotate-0\",\n        )}\n      />\n    </button>\n  );\n}\n\nfunction ReasoningContent({\n  children,\n  contentId,\n  isStreaming,\n  steps,\n}: {\n  children?: React.ReactNode;\n  contentId: string;\n  isStreaming: boolean;\n  steps?: string[];\n}) {\n  const hasSteps = steps !== undefined && steps.length > 0;\n\n  return (\n    <div\n      className=\"border-t border-border px-3 py-2 text-sm text-muted-foreground\"\n      id={contentId}\n    >\n      {hasSteps ? (\n        <ol className=\"list-decimal space-y-1 pl-4\">\n          {steps.map((step, index) => (\n            <li\n              className=\"whitespace-pre-wrap\"\n              key={`${index}-${step.slice(0, 12)}`}\n            >\n              {step}\n            </li>\n          ))}\n        </ol>\n      ) : (\n        <div className=\"whitespace-pre-wrap\">{children}</div>\n      )}\n      {isStreaming ? (\n        <span aria-hidden className=\"ml-0.5 animate-pulse\">\n          &#9611;\n        </span>\n      ) : null}\n    </div>\n  );\n}\n\n/**\n * Collapsible AI reasoning trace block.\n *\n * Shows a toggleable header (\"Reasoning\" while streaming, \"Reasoned\" once\n * settled) and an expandable body that renders either ordered `steps` or\n * free-form `children`. Auto-expands while `isStreaming` is true.\n *\n * @example\n * <Reasoning duration={4} steps={[\"Parse the request\", \"Check the cache\"]} />\n */\nexport const Reasoning = ({\n  children,\n  className,\n  duration,\n  isStreaming = false,\n  onOpenChange,\n  ref,\n  steps,\n}: ReasoningProps & { ref?: React.Ref<HTMLDivElement> }) => {\n  const [isOpen, setIsOpen] = useState(isStreaming);\n  const contentId = useId();\n\n  useEffect(() => {\n    if (isStreaming) {\n      requestAnimationFrame(() => {\n        setIsOpen(true);\n      });\n    }\n  }, [isStreaming]);\n\n  const handleToggle = useCallback(() => {\n    setIsOpen((previous) => {\n      const next = !previous;\n      onOpenChange?.(next);\n      return next;\n    });\n  }, [onOpenChange]);\n\n  return (\n    <div\n      className={cn(\"rounded-lg border border-border bg-muted/30\", className)}\n      ref={ref}\n    >\n      <ReasoningTrigger\n        contentId={contentId}\n        duration={duration}\n        isOpen={isOpen}\n        isStreaming={isStreaming}\n        onToggle={handleToggle}\n      />\n      {isOpen ? (\n        <ReasoningContent\n          contentId={contentId}\n          isStreaming={isStreaming}\n          steps={steps}\n        >\n          {children}\n        </ReasoningContent>\n      ) : null}\n    </div>\n  );\n};\nReasoning.displayName = \"Reasoning\";\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component",
  "version": "0.2.1",
  "stability": "stable"
}
