{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "reasoning-disclosure",
  "title": "Reasoning Disclosure",
  "description": "A collapsible panel for a model's reasoning that opens while it thinks and collapses to \"Thought for 12s\" when done.",
  "dependencies": [
    "lucide-react",
    "motion",
    "@radix-ui/react-collapsible"
  ],
  "files": [
    {
      "path": "registry/tealmac/reasoning-disclosure.tsx",
      "content": "// Adapted from Ballmac UI (https://github.com/vamsiy78/ballmac-ui), MIT License, Copyright (c) 2026 Ballmac.\n\"use client\"\n\nimport * as React from \"react\"\nimport * as CollapsiblePrimitive from \"@radix-ui/react-collapsible\"\nimport { ChevronRight } from \"lucide-react\"\nimport { motion, useReducedMotion } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n/** \"Thought for 12s\", \"Thought for 1m 5s\", \"Thought for a few seconds\". */\nfunction formatThoughtDuration(seconds: number) {\n  if (!Number.isFinite(seconds) || seconds < 1)\n    return \"Thought for a few seconds\"\n  const total = Math.round(seconds)\n  if (total < 60) return `Thought for ${total}s`\n  const minutes = Math.floor(total / 60)\n  const rest = total % 60\n  return rest ? `Thought for ${minutes}m ${rest}s` : `Thought for ${minutes}m`\n}\n\nconst noopSubscribe = () => () => {}\n\n/** useReducedMotion that returns false until hydration, so server and client markup match. */\nfunction useReducedMotionSafe() {\n  const reduce = useReducedMotion()\n  const hydrated = React.useSyncExternalStore(\n    noopSubscribe,\n    () => true,\n    () => false\n  )\n  return hydrated && !!reduce\n}\n\nfunction ShimmerLabel({ children }: { children: React.ReactNode }) {\n  const reduceMotion = useReducedMotionSafe()\n  if (reduceMotion) return <span>{children}</span>\n  return (\n    <motion.span\n      className=\"bg-clip-text text-transparent\"\n      style={{\n        backgroundImage:\n          \"linear-gradient(90deg, var(--muted-foreground) 35%, var(--foreground) 50%, var(--muted-foreground) 65%)\",\n        backgroundSize: \"250% 100%\",\n      }}\n      initial={{ backgroundPosition: \"100% 0%\" }}\n      animate={{ backgroundPosition: \"0% 0%\" }}\n      transition={{ duration: 1.8, repeat: Infinity, ease: \"linear\" }}\n    >\n      {children}\n    </motion.span>\n  )\n}\n\ntype ReasoningDisclosureProps = Omit<React.ComponentProps<\"div\">, \"dir\"> & {\n  /** True while the model is still reasoning. Shows \"Thinking…\" and opens the panel. */\n  streaming?: boolean\n  /** How long the model reasoned, in seconds. Shown as \"Thought for 12s\" once streaming ends. */\n  duration?: number\n  /** Controlled open state. When set, streaming no longer opens or closes the panel. */\n  open?: boolean\n  /** Initial open state when uncontrolled. Defaults to `streaming`. */\n  defaultOpen?: boolean\n  /** Called when the panel opens or closes, including the automatic open and collapse. */\n  onOpenChange?: (open: boolean) => void\n  /** Collapse automatically when streaming ends (uncontrolled only). */\n  autoCollapse?: boolean\n  /** Replaces the header text. */\n  label?: React.ReactNode\n  /** Classes for the content panel. */\n  contentClassName?: string\n}\n\nfunction ReasoningDisclosure({\n  streaming = false,\n  duration,\n  open: openProp,\n  defaultOpen,\n  onOpenChange,\n  autoCollapse = true,\n  label,\n  contentClassName,\n  className,\n  children,\n  ...props\n}: ReasoningDisclosureProps) {\n  const controlled = openProp !== undefined\n  const [internalOpen, setInternalOpen] = React.useState(\n    defaultOpen ?? streaming\n  )\n  const open = controlled ? openProp : internalOpen\n  const onOpenChangeRef = React.useRef(onOpenChange)\n  React.useEffect(() => {\n    onOpenChangeRef.current = onOpenChange\n  })\n\n  const setOpen = React.useCallback(\n    (next: boolean) => {\n      if (!controlled) setInternalOpen(next)\n      onOpenChangeRef.current?.(next)\n    },\n    [controlled]\n  )\n\n  // Open when streaming starts, collapse when it ends.\n  const wasStreaming = React.useRef(streaming)\n  React.useEffect(() => {\n    if (wasStreaming.current === streaming) return\n    wasStreaming.current = streaming\n    if (controlled) return\n    if (streaming) setOpen(true)\n    else if (autoCollapse) setOpen(false)\n  }, [streaming, controlled, autoCollapse, setOpen])\n\n  const heading =\n    label ??\n    (streaming ? (\n      <ShimmerLabel>Thinking…</ShimmerLabel>\n    ) : duration !== undefined ? (\n      formatThoughtDuration(duration)\n    ) : (\n      \"Reasoning\"\n    ))\n\n  return (\n    <CollapsiblePrimitive.Root\n      data-slot=\"reasoning-disclosure\"\n      data-streaming={streaming || undefined}\n      open={open}\n      onOpenChange={setOpen}\n      className={cn(\"group/reasoning w-full text-sm\", className)}\n      {...props}\n    >\n      <CollapsiblePrimitive.Trigger\n        data-slot=\"reasoning-disclosure-trigger\"\n        className=\"text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 -mx-1.5 inline-flex h-7 items-center gap-1.5 rounded-md px-1.5 transition-colors duration-150 outline-none focus-visible:ring-[3px]\"\n      >\n        <span className=\"font-medium\">{heading}</span>\n        <ChevronRight\n          aria-hidden=\"true\"\n          className=\"size-3.5 transition-transform duration-150 group-data-[state=open]/reasoning:rotate-90 motion-reduce:transition-none rtl:group-data-[state=closed]/reasoning:rotate-180\"\n        />\n      </CollapsiblePrimitive.Trigger>\n      <CollapsiblePrimitive.Content\n        data-slot=\"reasoning-disclosure-content\"\n        className={cn(\n          \"border-border text-muted-foreground mt-1.5 border-s-2 ps-4 text-[13px] leading-6 break-words whitespace-pre-wrap\",\n          contentClassName\n        )}\n      >\n        {children}\n      </CollapsiblePrimitive.Content>\n    </CollapsiblePrimitive.Root>\n  )\n}\n\nexport {\n  ReasoningDisclosure,\n  formatThoughtDuration,\n  type ReasoningDisclosureProps,\n}\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}