{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "conversation",
  "title": "Conversation",
  "description": "A transcript that sticks to the bottom while streaming and lets go when you scroll.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.cuelark.com/r/theme.json",
    "https://ui.cuelark.com/r/motion.json"
  ],
  "files": [
    {
      "path": "src/registry/cuelark/ui/conversation.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { ArrowDownIcon } from \"lucide-react\";\n\nimport { gsap, motionScale } from \"@/lib/motion\";\nimport { cn } from \"@/lib/utils\";\n\ntype ConversationContext = { atBottom: boolean; scrollToBottom: () => void };\nconst Ctx = React.createContext<ConversationContext>({ atBottom: true, scrollToBottom: () => {} });\n\n/**\n * A chat transcript that sticks to the latest message while it streams, but lets go the moment you scroll up to\n * read; a \"Latest\" pill then offers the way back. Edges fade so text never cuts off hard.\n */\nfunction Conversation({ className, children, ...props }: React.ComponentProps<\"div\">) {\n  const ref = React.useRef<HTMLDivElement>(null);\n  const [atBottom, setAtBottom] = React.useState(true);\n  const stick = React.useRef(true);\n\n  const scrollToBottom = React.useCallback(() => {\n    const el = ref.current;\n    if (!el) return;\n    stick.current = true;\n    el.scrollTo({ top: el.scrollHeight, behavior: motionScale(el) ? \"smooth\" : \"auto\" });\n  }, []);\n\n  React.useEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n    const onScroll = () => {\n      const bottom = el.scrollHeight - el.scrollTop - el.clientHeight < 24;\n      stick.current = bottom;\n      setAtBottom(bottom);\n    };\n    const follow = new ResizeObserver(() => stick.current && (el.scrollTop = el.scrollHeight));\n    el.addEventListener(\"scroll\", onScroll, { passive: true });\n    Array.from(el.children).forEach((c) => follow.observe(c));\n    return () => {\n      el.removeEventListener(\"scroll\", onScroll);\n      follow.disconnect();\n    };\n  }, []);\n\n  return (\n    <Ctx.Provider value={{ atBottom, scrollToBottom }}>\n      <div data-slot=\"conversation\" className={cn(\"relative min-h-0\", className)}>\n        <div\n          ref={ref}\n          role=\"log\"\n          aria-live=\"polite\"\n          className=\"size-full overflow-y-auto [mask-image:linear-gradient(transparent,black_16px,black_calc(100%-16px),transparent)]\"\n          {...props}\n        >\n          {children}\n        </div>\n        <ConversationScrollButton />\n      </div>\n    </Ctx.Provider>\n  );\n}\n\nfunction ConversationContent({ className, ...props }: React.ComponentProps<\"div\">) {\n  return <div data-slot=\"conversation-content\" className={cn(\"grid gap-6 p-4\", className)} {...props} />;\n}\n\nfunction ConversationScrollButton() {\n  const { atBottom, scrollToBottom } = React.useContext(Ctx);\n  const ref = React.useRef<HTMLButtonElement>(null);\n  React.useEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n    const s = motionScale(el);\n    gsap.to(el, atBottom ? { opacity: 0, y: 8, scale: 0.95, duration: s ? 0.15 : 0, pointerEvents: \"none\" } : { opacity: 1, y: 0, scale: 1, duration: s ? 0.25 : 0, ease: \"cue\", pointerEvents: \"auto\" });\n  }, [atBottom]);\n  return (\n    <button\n      ref={ref}\n      type=\"button\"\n      onClick={scrollToBottom}\n      tabIndex={atBottom ? -1 : 0}\n      className=\"absolute bottom-3 left-1/2 -ml-11 flex h-7 w-22 items-center justify-center gap-1 rounded-full border border-border bg-background/90 text-xs font-medium opacity-0 shadow-raised backdrop-blur outline-none focus-visible:ring-3 focus-visible:ring-ring/50\"\n    >\n      <ArrowDownIcon className=\"size-3.5\" /> Latest\n    </button>\n  );\n}\n\nexport { Conversation, ConversationContent };\n",
      "type": "registry:ui"
    }
  ],
  "docs": "Docs and examples: https://ui.cuelark.com/components/conversation",
  "categories": [
    "agents"
  ],
  "type": "registry:ui"
}