{
  "$schema": "https://blode.co/ui/schema/registry-item.json",
  "name": "thinking-steps",
  "title": "Thinking Steps",
  "author": "Matthew Blode",
  "description": "A collapsible chain-of-thought stepper with steps, sources, details, and images.",
  "dependencies": ["motion"],
  "registryDependencies": ["accordion", "badge"],
  "files": [
    {
      "path": "ui/thinking-steps.tsx",
      "content": "\"use client\";\n\nimport { ChevronRightIcon, DotSmallIcon } from \"blode-icons-react\";\nimport { motion } from \"motion/react\";\nimport type * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\nimport {\n  Accordion,\n  AccordionContent,\n  AccordionItem,\n  AccordionTrigger,\n} from \"@/components/ui/accordion\";\nimport { Badge } from \"@/components/ui/badge\";\n\n// Self-contained shimmer for the active step label — no global CSS required.\nconst SHIMMER_GRADIENT =\n  \"linear-gradient(90deg, var(--muted-foreground) 0%, var(--muted-foreground) 35%, var(--foreground) 50%, var(--muted-foreground) 65%, var(--muted-foreground) 100%)\";\n\nconst ShimmerText = ({\n  children,\n  className,\n}: {\n  children: React.ReactNode;\n  className?: string;\n}) => (\n  <motion.span\n    animate={{ backgroundPosition: [\"0% 0\", \"100% 0\"] }}\n    className={cn(\"bg-clip-text text-transparent\", className)}\n    style={{ backgroundImage: SHIMMER_GRADIENT, backgroundSize: \"300% 100%\" }}\n    transition={{ duration: 1.5, ease: \"easeInOut\", repeat: Number.POSITIVE_INFINITY }}\n  >\n    {children}\n  </motion.span>\n);\n\n// ─── ThinkingSteps (root) ───────────────────────────────────────────────────\n\ninterface ThinkingStepsProps extends Omit<\n  React.ComponentProps<\"div\">,\n  \"children\" | \"defaultValue\"\n> {\n  defaultOpen?: boolean;\n  open?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  children: React.ReactNode;\n}\n\nconst ThinkingSteps = ({\n  defaultOpen = true,\n  open,\n  onOpenChange,\n  children,\n  className,\n  ref,\n  ...props\n}: ThinkingStepsProps) => {\n  const controlled = open !== undefined;\n\n  return (\n    <Accordion\n      className={cn(\"w-80 max-w-full\", className)}\n      collapsible\n      data-slot=\"thinking-steps\"\n      ref={ref}\n      type=\"single\"\n      {...(controlled\n        ? { value: open ? \"thinking\" : \"\" }\n        : { defaultValue: defaultOpen ? \"thinking\" : \"\" })}\n      {...(onOpenChange\n        ? { onValueChange: (v: string | string[]) => onOpenChange(v === \"thinking\") }\n        : {})}\n      {...props}\n    >\n      <AccordionItem value=\"thinking\">{children}</AccordionItem>\n    </Accordion>\n  );\n};\n\n// ─── ThinkingStepsHeader ────────────────────────────────────────────────────\n\nconst ThinkingStepsHeader = ({\n  children = \"Thinking\",\n  className,\n  ...props\n}: React.ComponentProps<typeof AccordionTrigger>) => (\n  <div className=\"w-fit\">\n    <AccordionTrigger\n      chevron={false}\n      className={cn(\n        \"w-auto items-center gap-1.5 py-1 [&>span:first-child]:flex-none [&[data-panel-open]_[data-chevron]]:rotate-90\",\n        className,\n      )}\n      {...props}\n    >\n      {children}\n      <ChevronRightIcon\n        className=\"size-3.5 shrink-0 text-muted-foreground transition-transform duration-200\"\n        data-chevron\n      />\n    </AccordionTrigger>\n  </div>\n);\n\n// ─── ThinkingStepsContent ───────────────────────────────────────────────────\n\nconst ThinkingStepsContent = ({ children, className, ...props }: React.ComponentProps<\"div\">) => (\n  <AccordionContent>\n    <div className={cn(\"flex flex-col\", className)} {...props}>\n      {children}\n    </div>\n  </AccordionContent>\n);\n\n// ─── ThinkingStep ───────────────────────────────────────────────────────────\n\ntype StepStatus = \"complete\" | \"active\" | \"pending\";\n\ninterface ThinkingStepProps {\n  /** Icon component (from blode-icons-react) shown in the marker column. */\n  icon?: React.ElementType;\n  showIcon?: boolean;\n  label: string;\n  description?: string;\n  status?: StepStatus;\n  /** Removes the connector line below the last step. */\n  isLast?: boolean;\n  children?: React.ReactNode;\n  className?: string;\n}\n\nconst ThinkingStep = ({\n  icon: Icon = DotSmallIcon,\n  showIcon = true,\n  label,\n  description,\n  status = \"complete\",\n  isLast = false,\n  children,\n  className,\n}: ThinkingStepProps) => {\n  if (status === \"pending\") {\n    return null;\n  }\n\n  const isActive = status === \"active\";\n\n  return (\n    // Outer animates height so space opens smoothly as steps stream in.\n    <motion.div\n      animate={{ height: \"auto\" }}\n      className={cn(\"relative z-10 overflow-hidden\", className)}\n      initial={{ height: 0 }}\n      transition={{ bounce: 0.15, duration: 0.24, type: \"spring\" }}\n    >\n      {/* Inner fades content in once the space starts opening. */}\n      <motion.div\n        animate={{ opacity: 1 }}\n        initial={{ opacity: 0 }}\n        transition={{ delay: 0.08, duration: 0.24, ease: \"easeOut\" }}\n      >\n        <div className=\"flex gap-2.5 rounded-lg px-2 py-1.5\">\n          {/* Marker column with continuous connector line. */}\n          <div className=\"flex w-[14px] shrink-0 flex-col items-center\">\n            <div className=\"pt-0.5\">\n              {showIcon ? (\n                <Icon className=\"size-[14px] text-muted-foreground\" />\n              ) : (\n                <div className=\"flex size-[14px] items-center justify-center\">\n                  <div className=\"size-1.5 rounded-full bg-muted-foreground/60\" />\n                </div>\n              )}\n            </div>\n            {!isLast && <div className=\"mt-1 w-px flex-1 bg-border/60\" />}\n          </div>\n\n          <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n            {isActive ? (\n              <ShimmerText className=\"font-medium text-[13px] leading-tight\">{label}…</ShimmerText>\n            ) : (\n              <span className=\"font-medium text-[13px] text-foreground leading-tight\">{label}</span>\n            )}\n            {description && (\n              <span className=\"text-[13px] text-muted-foreground leading-snug\">{description}</span>\n            )}\n            {children}\n          </div>\n        </div>\n      </motion.div>\n    </motion.div>\n  );\n};\n\n// ─── ThinkingStepDetails (nested accordion) ─────────────────────────────────\n\ninterface ThinkingStepDetailsProps {\n  summary: string;\n  details?: string[];\n  defaultOpen?: boolean;\n  children?: React.ReactNode;\n  className?: string;\n}\n\nconst ThinkingStepDetails = ({\n  summary,\n  details,\n  defaultOpen = false,\n  children,\n  className,\n}: ThinkingStepDetailsProps) => (\n  <Accordion\n    className={cn(\"-ml-3 mt-1\", className)}\n    collapsible\n    defaultValue={defaultOpen ? \"details\" : \"\"}\n    type=\"single\"\n  >\n    <AccordionItem value=\"details\">\n      <div className=\"w-fit\">\n        <AccordionTrigger\n          chevron={false}\n          className=\"w-auto items-center gap-1.5 rounded-full border border-transparent px-3 py-1 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground data-[panel-open]:border-border data-[panel-open]:bg-muted [&>span:first-child]:flex-none [&[data-panel-open]_[data-chevron]]:rotate-90\"\n        >\n          {summary}\n          <ChevronRightIcon\n            className=\"size-3.5 shrink-0 transition-transform duration-200\"\n            data-chevron\n          />\n        </AccordionTrigger>\n      </div>\n      <AccordionContent>\n        <div className=\"flex flex-col gap-0.5 px-3 pt-0.5\">\n          {details?.map((item) => (\n            <span className=\"text-[12px] text-muted-foreground leading-snug\" key={item}>\n              {item}\n            </span>\n          ))}\n          {children}\n        </div>\n      </AccordionContent>\n    </AccordionItem>\n  </Accordion>\n);\n\n// ─── ThinkingStepSources ────────────────────────────────────────────────────\n\nconst ThinkingStepSources = ({ children, className, ...props }: React.ComponentProps<\"div\">) => (\n  <div className={cn(\"mt-1 flex flex-wrap gap-1.5\", className)} {...props}>\n    {children}\n  </div>\n);\n\n// ─── ThinkingStepSource ─────────────────────────────────────────────────────\n\ntype SourceColor = \"gray\" | \"blue\" | \"green\" | \"yellow\" | \"red\";\n\nconst SOURCE_VARIANT: Record<SourceColor, React.ComponentProps<typeof Badge>[\"variant\"]> = {\n  blue: \"default\",\n  gray: \"secondary\",\n  green: \"success\",\n  red: \"destructive\",\n  yellow: \"warning\",\n};\n\ninterface ThinkingStepSourceProps {\n  color?: SourceColor;\n  delay?: number;\n  children: React.ReactNode;\n  className?: string;\n}\n\nconst ThinkingStepSource = ({\n  color = \"gray\",\n  delay = 0,\n  children,\n  className,\n}: ThinkingStepSourceProps) => (\n  <motion.span\n    animate={{ filter: \"blur(0px)\", opacity: 1, scale: 1 }}\n    initial={{ filter: \"blur(4px)\", opacity: 0, scale: 0.85 }}\n    transition={{\n      bounce: 0.15,\n      delay,\n      duration: 0.16,\n      filter: { delay, duration: 0.12 },\n      type: \"spring\",\n    }}\n  >\n    <Badge className={className} variant={SOURCE_VARIANT[color]}>\n      {children}\n    </Badge>\n  </motion.span>\n);\n\n// ─── ThinkingStepImage ──────────────────────────────────────────────────────\n\ninterface ThinkingStepImageProps {\n  src: string;\n  alt?: string;\n  caption?: string;\n  delay?: number;\n  className?: string;\n}\n\nconst ThinkingStepImage = ({\n  src,\n  alt = \"\",\n  caption,\n  delay = 0,\n  className,\n}: ThinkingStepImageProps) => (\n  <motion.div\n    animate={{ filter: \"blur(0px)\", opacity: 1 }}\n    className={cn(\"mt-1.5\", className)}\n    initial={{ filter: \"blur(4px)\", opacity: 0 }}\n    transition={{\n      filter: { delay, duration: 0.15 },\n      opacity: { delay, duration: 0.2, ease: \"easeOut\" },\n    }}\n  >\n    {/* eslint-disable-next-line next/no-img-element -- caller-supplied source, may be a data/blob URL */}\n    <img alt={alt} className=\"w-full max-w-[200px] rounded-xl object-cover\" src={src} />\n    {caption && <span className=\"mt-1 block text-[11px] text-muted-foreground\">{caption}</span>}\n  </motion.div>\n);\n\nexport {\n  ThinkingSteps,\n  ThinkingStepsHeader,\n  ThinkingStepsContent,\n  ThinkingStep,\n  ThinkingStepDetails,\n  ThinkingStepSources,\n  ThinkingStepSource,\n  ThinkingStepImage,\n};\nexport type {\n  ThinkingStepsProps,\n  ThinkingStepProps,\n  ThinkingStepDetailsProps,\n  ThinkingStepSourceProps,\n  ThinkingStepImageProps,\n  StepStatus,\n  SourceColor,\n};\n",
      "type": "registry:ui",
      "target": ""
    }
  ],
  "type": "registry:ui"
}
