{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "reasoning",
  "title": "Reasoning",
  "description": "Collapsible extended-thinking fold that opens itself while the model streams and settles to a timed receipt.",
  "dependencies": [
    "@base-ui/react",
    "@hugeicons/react",
    "@hugeicons/core-free-icons",
    "streamdown"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.neon.com/r/neon-tokens.json"
  ],
  "files": [
    {
      "path": "src/components/reasoning/reasoning.tsx",
      "content": "\"use client\";\n\nimport { Collapsible } from \"@base-ui/react/collapsible\";\nimport { ArrowRight01Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport { Streamdown } from \"streamdown\";\n\nimport { cn } from \"@/lib/utils\";\n\n/** Milliseconds to hold the panel open after streaming ends. */\nconst AUTO_CLOSE_DELAY_MS = 1000;\n\nconst MS_PER_SECOND = 1000;\n\ninterface ReasoningContextValue {\n  /** Elapsed thinking time in seconds; undefined while streaming. */\n  duration: number | undefined;\n  isStreaming: boolean;\n  open: boolean;\n  setOpen: (open: boolean) => void;\n}\n\nconst ReasoningContext = createContext<ReasoningContextValue | null>(null);\n\n/** Read the surrounding Reasoning state from a child component. */\nexport const useReasoning = (): ReasoningContextValue => {\n  const context = useContext(ReasoningContext);\n\n  if (!context) {\n    throw new Error(\"useReasoning must be used inside <Reasoning>\");\n  }\n\n  return context;\n};\n\nexport type ReasoningProps = Omit<\n  Collapsible.Root.Props,\n  \"defaultOpen\" | \"onOpenChange\" | \"open\"\n> & {\n  /**\n   * The reasoning is streaming right now: the panel auto-opens, the\n   * trigger shimmers \"Thinking…\", and the clock runs. When it flips\n   * back off the panel folds to a \"Thought for Ns\" receipt — unless\n   * the reader toggled it themselves, in which case their choice wins.\n   */\n  isStreaming?: boolean;\n  /** Controlled open state. */\n  open?: boolean;\n  /** Initial open state when uncontrolled. */\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  /** Thinking time in seconds; measured from `isStreaming` when omitted. */\n  duration?: number;\n};\n\n/* ─────────────────────────────────────────────────────\n * The model's thinking, in the house log-line vocabulary:\n * a rail-indented fold that opens itself while the model\n * thinks and settles to a one-line receipt when it stops.\n * The reader's toggle always beats the automation.\n * Inspired by Reasoning from Vercel's AI SDK Elements\n * (elements.ai-sdk.dev), rebuilt on Base UI.\n * ─────────────────────────────────────────────────── */\nexport const Reasoning = ({\n  children,\n  className,\n  defaultOpen = false,\n  duration: durationProp,\n  isStreaming = false,\n  onOpenChange,\n  open: openProp,\n  ...props\n}: ReasoningProps) => {\n  const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);\n  const [trackedDuration, setTrackedDuration] = useState<number>();\n  const startedAtRef = useRef<number | null>(null);\n  const hasStreamedRef = useRef(false);\n  const userToggledRef = useRef(false);\n\n  const open = openProp ?? uncontrolledOpen;\n  const duration = isStreaming ? undefined : (durationProp ?? trackedDuration);\n\n  const setOpen = useCallback(\n    (next: boolean) => {\n      setUncontrolledOpen(next);\n      onOpenChange?.(next);\n    },\n    [onOpenChange]\n  );\n\n  useEffect(() => {\n    if (isStreaming) {\n      hasStreamedRef.current = true;\n      startedAtRef.current ??= Date.now();\n\n      if (!userToggledRef.current) {\n        setUncontrolledOpen(true);\n      }\n      return;\n    }\n\n    if (startedAtRef.current !== null) {\n      const elapsed = Date.now() - startedAtRef.current;\n      setTrackedDuration(Math.max(1, Math.round(elapsed / MS_PER_SECOND)));\n      startedAtRef.current = null;\n    }\n\n    // Fold only streams we opened ourselves — a static, defaultOpen\n    // reasoning block must not close itself out from under the reader.\n    if (hasStreamedRef.current && !userToggledRef.current) {\n      const timeout = setTimeout(() => {\n        setUncontrolledOpen(false);\n      }, AUTO_CLOSE_DELAY_MS);\n      return () => clearTimeout(timeout);\n    }\n  }, [isStreaming]);\n\n  const contextValue = useMemo(\n    () => ({ duration, isStreaming, open, setOpen }),\n    [duration, isStreaming, open, setOpen]\n  );\n\n  return (\n    <ReasoningContext.Provider value={contextValue}>\n      <Collapsible.Root\n        className={cn(\n          \"w-full min-w-0 border-border/60 border-l-2 pl-2.5\",\n          className\n        )}\n        data-slot=\"reasoning\"\n        data-streaming={isStreaming || undefined}\n        onOpenChange={(next) => {\n          userToggledRef.current = true;\n          setOpen(next);\n        }}\n        open={open}\n        {...props}\n      >\n        {children}\n      </Collapsible.Root>\n    </ReasoningContext.Provider>\n  );\n};\n\nexport type ReasoningTriggerProps = Collapsible.Trigger.Props;\n\n/**\n * The fold's one-line handle: shimmering \"Thinking…\" while the model\n * streams, a quiet \"Thought for Ns\" receipt at rest. Pass children to\n * replace the label; the chevron stays.\n */\nexport const ReasoningTrigger = ({\n  children,\n  className,\n  ...props\n}: ReasoningTriggerProps) => {\n  const { duration, isStreaming } = useReasoning();\n  const label = duration === undefined ? \"Thought\" : `Thought for ${duration}s`;\n\n  return (\n    <Collapsible.Trigger\n      className={cn(\n        \"group inline-flex items-center gap-1 py-0.5 text-muted-foreground text-xs transition-colors hover:text-foreground\",\n        className\n      )}\n      data-slot=\"reasoning-trigger\"\n      {...props}\n    >\n      <HugeiconsIcon\n        aria-hidden\n        className=\"size-3 transition-transform duration-200 group-data-[panel-open]:rotate-90 motion-reduce:transition-none\"\n        icon={ArrowRight01Icon}\n        strokeWidth={2}\n      />\n      {children ??\n        (isStreaming ? (\n          <span className=\"shimmer shimmer-duration-2400\">Thinking…</span>\n        ) : (\n          <span>{label}</span>\n        ))}\n    </Collapsible.Trigger>\n  );\n};\n\nexport type ReasoningContentProps = Omit<\n  Collapsible.Panel.Props,\n  \"children\"\n> & {\n  /** The reasoning text, rendered as markdown. */\n  children: string;\n};\n\n/** The thinking itself: dimmed markdown that unfolds under the trigger. */\nexport const ReasoningContent = ({\n  children,\n  className,\n  ...props\n}: ReasoningContentProps) => (\n  <Collapsible.Panel\n    className={cn(\n      \"h-[var(--collapsible-panel-height)] overflow-hidden transition-[height] duration-200 ease-out data-[ending-style]:h-0 data-[starting-style]:h-0 motion-reduce:transition-none\",\n      className\n    )}\n    data-slot=\"reasoning-content\"\n    {...props}\n  >\n    <div\n      className={cn(\n        \"pt-1 pb-0.5 text-muted-foreground text-sm leading-relaxed\",\n        \"[&_h1]:font-semibold [&_h2]:font-semibold [&_h3]:font-semibold\",\n        \"[&_code]:bg-muted [&_code]:px-1 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-xs\",\n        \"[&_ol]:list-decimal [&_ol]:pl-5 [&_ul]:list-disc [&_ul]:pl-5\",\n        \"[&_p+p]:mt-2\"\n      )}\n    >\n      <Streamdown>{children}</Streamdown>\n    </div>\n  </Collapsible.Panel>\n);\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}