{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chat-prompt-layout",
  "title": "Chat Prompt Layout",
  "description": "A full-height chat layout with a scrollable messages area, a bottom-anchored prompt slot, and contextual scroll-to-top/bottom buttons.",
  "registryDependencies": [],
  "files": [
    {
      "path": "packages/prompt-area/src/chat-prompt-layout/chat-prompt-layout.tsx",
      "content": "'use client'\n\nimport { cn } from '@/lib/utils'\nimport type { ChatPromptLayoutProps } from './types'\nimport { useScrollObserver } from './use-scroll-observer'\n\nconst NAV_BUTTON_CLASS =\n  'pointer-events-auto rounded-full border bg-background p-2 shadow-sm text-muted-foreground hover:bg-accent hover:text-foreground transition-colors animate-in fade-in-0 zoom-in-95 duration-150'\n\ntype IconProps = { className?: string }\n\n/** Shared SVG wrapper matching the lucide icon defaults (no dependency). */\nfunction Svg({ className, children }: IconProps & { children: React.ReactNode }) {\n  return (\n    <svg\n      xmlns=\"http://www.w3.org/2000/svg\"\n      width=\"24\"\n      height=\"24\"\n      viewBox=\"0 0 24 24\"\n      fill=\"none\"\n      stroke=\"currentColor\"\n      strokeWidth=\"2\"\n      strokeLinecap=\"round\"\n      strokeLinejoin=\"round\"\n      aria-hidden=\"true\"\n      className={className}>\n      {children}\n    </svg>\n  )\n}\n\nconst ArrowUp = ({ className }: IconProps) => (\n  <Svg className={className}>\n    <path d=\"m5 12 7-7 7 7\" />\n    <path d=\"M12 19V5\" />\n  </Svg>\n)\nconst ArrowDown = ({ className }: IconProps) => (\n  <Svg className={className}>\n    <path d=\"M12 5v14\" />\n    <path d=\"m19 12-7 7-7-7\" />\n  </Svg>\n)\n\n/**\n * ChatPromptLayout - A full-height chat layout with scrollable messages\n * and a bottom-anchored prompt slot.\n *\n * Pass chat messages as `children` and the prompt area via the `prompt`\n * prop. Contextual scroll buttons appear when the user scrolls away\n * from the top or bottom of the messages area.\n *\n * @example\n * ```tsx\n * <ChatPromptLayout\n *   className=\"h-[600px]\"\n *   prompt={\n *     <div className=\"border-t p-4\">\n *       <PromptArea ... />\n *       <ActionBar ... />\n *     </div>\n *   }\n * >\n *   {messages.map(msg => <ChatBubble key={msg.id} {...msg} />)}\n * </ChatPromptLayout>\n * ```\n */\nexport function ChatPromptLayout({\n  children,\n  prompt,\n  className,\n  'aria-label': ariaLabel,\n  'data-test-id': dataTestId,\n  ref,\n}: ChatPromptLayoutProps & { ref?: React.Ref<HTMLDivElement> }) {\n  const { scrollRef, showGoToTop, showGoToBottom, scrollToTop, scrollToBottom } =\n    useScrollObserver()\n\n  return (\n    <div\n      ref={ref}\n      role=\"region\"\n      aria-label={ariaLabel ?? 'Chat layout'}\n      data-test-id={dataTestId}\n      className={cn('chat-prompt-layout', 'flex h-full flex-col', className)}>\n      <div ref={scrollRef} className=\"relative flex-1 overflow-y-auto\">\n        {children}\n\n        <div className=\"pointer-events-none sticky bottom-4 flex justify-end gap-2 px-4 pb-2\">\n          {showGoToTop && (\n            <button\n              type=\"button\"\n              onClick={scrollToTop}\n              className={NAV_BUTTON_CLASS}\n              aria-label=\"Scroll to top\">\n              <ArrowUp className=\"size-4\" />\n            </button>\n          )}\n          {showGoToBottom && (\n            <button\n              type=\"button\"\n              onClick={scrollToBottom}\n              className={NAV_BUTTON_CLASS}\n              aria-label=\"Scroll to bottom\">\n              <ArrowDown className=\"size-4\" />\n            </button>\n          )}\n        </div>\n      </div>\n\n      <div className=\"shrink-0\">{prompt}</div>\n    </div>\n  )\n}\n",
      "type": "registry:component"
    },
    {
      "path": "packages/prompt-area/src/chat-prompt-layout/use-scroll-observer.ts",
      "content": "'use client'\n\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\n/** Distance from edge before showing a button */\nconst SHOW_THRESHOLD = 300\n/** Distance from edge before hiding a button (larger = less flicker) */\nconst HIDE_THRESHOLD = 100\n\nexport function useScrollObserver() {\n  const scrollRef = useRef<HTMLDivElement>(null)\n  const [showGoToTop, setShowGoToTop] = useState(false)\n  const [showGoToBottom, setShowGoToBottom] = useState(false)\n  const rafId = useRef(0)\n\n  const update = useCallback(() => {\n    // Coalesce rapid scroll events into a single rAF\n    cancelAnimationFrame(rafId.current)\n    rafId.current = requestAnimationFrame(() => {\n      const el = scrollRef.current\n      if (!el) return\n\n      const scrollTop = el.scrollTop\n      const distanceFromBottom = el.scrollHeight - scrollTop - el.clientHeight\n\n      // Hysteresis: use a higher threshold to show, lower to hide\n      setShowGoToTop((prev) => (prev ? scrollTop > HIDE_THRESHOLD : scrollTop > SHOW_THRESHOLD))\n      setShowGoToBottom((prev) =>\n        prev ? distanceFromBottom > HIDE_THRESHOLD : distanceFromBottom > SHOW_THRESHOLD,\n      )\n    })\n  }, [])\n\n  useEffect(() => {\n    const el = scrollRef.current\n    if (!el) return\n\n    el.addEventListener('scroll', update, { passive: true })\n    update()\n\n    return () => {\n      el.removeEventListener('scroll', update)\n      cancelAnimationFrame(rafId.current)\n    }\n  }, [update])\n\n  const scrollToTop = useCallback(() => {\n    scrollRef.current?.scrollTo({ top: 0, behavior: 'smooth' })\n  }, [])\n\n  const scrollToBottom = useCallback(() => {\n    const el = scrollRef.current\n    if (!el) return\n    el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' })\n  }, [])\n\n  return { scrollRef, showGoToTop, showGoToBottom, scrollToTop, scrollToBottom }\n}\n",
      "type": "registry:component"
    },
    {
      "path": "packages/prompt-area/src/chat-prompt-layout/types.ts",
      "content": "/**\n * ChatPromptLayout component types\n *\n * A full-height chat layout with a scrollable messages area and\n * a bottom-anchored prompt slot. Includes contextual scroll\n * navigation buttons.\n */\n\n/**\n * Props for the ChatPromptLayout component.\n */\nexport type ChatPromptLayoutProps = {\n  /** Chat messages rendered in the scrollable area */\n  children: React.ReactNode\n  /** Prompt area rendered at the bottom of the layout (slot) */\n  prompt: React.ReactNode\n  /** Additional CSS class for the root container */\n  className?: string\n  /** Accessible label for the layout region */\n  'aria-label'?: string\n  /** data-test-id for e2e testing */\n  'data-test-id'?: string\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}