{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-ai-chat-container",
  "title": "Chat Container",
  "description": "A composable chat container with auto-scroll, empty state, animated messages, scroll-to-bottom button, and divider support.",
  "dependencies": [
    "motion",
    "@hugeicons/react",
    "@hugeicons/core-free-icons"
  ],
  "registryDependencies": [
    "scroll-area",
    "button"
  ],
  "files": [
    {
      "path": "registry/components/ai/chat-container/index.tsx",
      "content": "'use client';\n\nimport { Button } from '@/components/ui/button';\nimport { cn } from '@/lib/utils';\nimport { AnimatePresence, motion } from 'motion/react';\nimport { HTMLMotionProps } from 'motion/react';\nimport { HugeiconsIcon } from '@hugeicons/react';\nimport { ArrowDown01Icon, BubbleChatIcon } from '@hugeicons/core-free-icons';\nimport React, {\n  createContext,\n  useCallback,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from 'react';\n\ntype ChatContainerContextValue = {\n  scrollToBottom: (behavior?: ScrollBehavior) => void;\n  isAtBottom: boolean;\n  isEmpty: boolean;\n  messageCount: number;\n  registerMessage: () => () => void;\n};\n\nconst ChatContainerContext = createContext<ChatContainerContextValue | null>(\n  null,\n);\n\nconst useChatContainer = () => {\n  const ctx = useContext(ChatContainerContext);\n  if (!ctx)\n    throw new Error('useChatContainer must be used inside <ChatContainer />');\n  return ctx;\n};\n\nexport type ChatContainerProps = React.ComponentProps<'div'> & {\n  bottomThreshold?: number;\n  autoScroll?: boolean;\n};\n\nexport function ChatContainer({\n  children,\n  className,\n  bottomThreshold = 64,\n  autoScroll = true,\n  ...props\n}: ChatContainerProps) {\n  const viewportRef = useRef<HTMLDivElement>(null);\n  const [isAtBottom, setIsAtBottom] = useState(true);\n  const [messageCount, setMessageCount] = useState(0);\n\n  const scrollToBottom = useCallback((behavior: ScrollBehavior = 'smooth') => {\n    const viewport = viewportRef.current;\n    if (!viewport) return;\n    viewport.scrollTo({ top: viewport.scrollHeight, behavior });\n  }, []);\n\n  useEffect(() => {\n    const viewport = viewportRef.current;\n    if (!viewport) return;\n    const onScroll = () => {\n      const distFromBottom =\n        viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;\n      setIsAtBottom(distFromBottom <= bottomThreshold);\n    };\n    viewport.addEventListener('scroll', onScroll, { passive: true });\n    return () => viewport.removeEventListener('scroll', onScroll);\n  }, [bottomThreshold]);\n\n  useEffect(() => {\n    if (autoScroll && isAtBottom) scrollToBottom('smooth');\n  }, [messageCount, autoScroll, isAtBottom, scrollToBottom]);\n\n  const registerMessage = useCallback(() => {\n    setMessageCount((c) => c + 1);\n    return () => setMessageCount((c) => c - 1);\n  }, []);\n\n  const isEmpty = messageCount === 0;\n\n  return (\n    <ChatContainerContext.Provider\n      value={{\n        scrollToBottom,\n        isAtBottom,\n        isEmpty,\n        messageCount,\n        registerMessage,\n      }}\n    >\n      <div\n        className={cn(\n          'p-2 ring-1 ring-ring/30 rounded-4xl backdrop-blur-lg',\n          className,\n        )}\n        {...props}\n      >\n        <div\n          className={cn(\n            'relative flex h-full w-full flex-col',\n            'bg-background ring-1 ring-ring/20 rounded-3xl shadow-lg overflow-hidden',\n          )}\n        >\n          <div\n            ref={viewportRef}\n            className=\"min-h-0 flex-1 w-full overflow-y-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]\"\n          >\n            {children}\n          </div>\n          <ChatContainerScrollButton />\n        </div>\n      </div>\n    </ChatContainerContext.Provider>\n  );\n}\n\nexport type ChatContainerContentProps = React.ComponentProps<'div'>;\n\nexport const ChatContainerContent = ({\n  children,\n  className,\n  ...props\n}: ChatContainerContentProps) => (\n  <div\n    className={cn(\n      'mx-auto flex w-full max-w-3xl flex-col gap-6 px-4 py-6',\n      className,\n    )}\n    {...props}\n  >\n    {children}\n  </div>\n);\n\nexport type ChatContainerEmptyStateProps = Omit<\n  HTMLMotionProps<'div'>,\n  'children'\n> & {\n  children?: React.ReactNode;\n  icon?: React.ReactNode;\n  title?: string;\n  description?: string;\n};\n\nexport const ChatContainerEmptyState = ({\n  className,\n  icon,\n  title = 'No messages yet',\n  description = 'Start a conversation to see messages here.',\n  children,\n  ...props\n}: ChatContainerEmptyStateProps) => {\n  const { isEmpty } = useChatContainer();\n  if (!isEmpty) return null;\n\n  return (\n    <motion.div\n      initial={{ opacity: 0, y: 8 }}\n      animate={{ opacity: 1, y: 0 }}\n      exit={{ opacity: 0, y: -8 }}\n      transition={{ duration: 0.3, ease: 'easeOut' }}\n      className={cn(\n        'flex flex-col items-center justify-center gap-3 py-20 text-center',\n        className,\n      )}\n      {...props}\n    >\n      <div className=\"text-muted-foreground/40\">\n        {icon ?? (\n          <HugeiconsIcon\n            icon={BubbleChatIcon}\n            className=\"size-10\"\n            strokeWidth={1.25}\n          />\n        )}\n      </div>\n      <div className=\"space-y-1\">\n        <p className=\"text-foreground/80 text-sm font-medium\">{title}</p>\n        <p className=\"text-muted-foreground max-w-xs text-xs leading-relaxed\">\n          {description}\n        </p>\n      </div>\n      {children}\n    </motion.div>\n  );\n};\n\nexport type ChatContainerScrollButtonProps = React.ComponentProps<\n  typeof Button\n> & {\n  icon?: React.ReactNode;\n};\n\nexport const ChatContainerScrollButton = ({\n  className,\n  icon,\n  onClick,\n  ...props\n}: ChatContainerScrollButtonProps) => {\n  const { isAtBottom, scrollToBottom } = useChatContainer();\n\n  return (\n    <AnimatePresence>\n      {!isAtBottom && (\n        <motion.div\n          key=\"scroll-btn\"\n          initial={{ opacity: 0, scale: 0.85, y: 8 }}\n          animate={{ opacity: 1, scale: 1, y: 0 }}\n          exit={{ opacity: 0, scale: 0.85, y: 8 }}\n          transition={{ duration: 0.18, ease: 'easeOut' }}\n          className=\"absolute bottom-4 left-1/2 z-10 -translate-x-1/2\"\n        >\n          <Button\n            size=\"icon\"\n            variant=\"secondary\"\n            className={cn(\n              'hover:bg-background size-8 rounded-full border shadow-md backdrop-blur-sm',\n              className,\n            )}\n            onClick={(e) => {\n              scrollToBottom('smooth');\n              onClick?.(e);\n            }}\n            aria-label=\"Scroll to bottom\"\n            {...props}\n          >\n            {icon ?? (\n              <HugeiconsIcon\n                icon={ArrowDown01Icon}\n                className=\"size-4\"\n                strokeWidth={2}\n              />\n            )}\n          </Button>\n        </motion.div>\n      )}\n    </AnimatePresence>\n  );\n};\n\nexport type ChatContainerMessageProps = Omit<\n  HTMLMotionProps<'div'>,\n  'children'\n> & { children?: React.ReactNode };\n\nexport const ChatContainerMessage = ({\n  children,\n  className,\n  ...props\n}: ChatContainerMessageProps) => {\n  const { registerMessage } = useChatContainer();\n\n  useEffect(() => {\n    return registerMessage();\n  }, [registerMessage]);\n\n  return (\n    <motion.div\n      initial={{ opacity: 0, y: 6 }}\n      animate={{ opacity: 1, y: 0 }}\n      transition={{ duration: 0.22, ease: 'easeOut' }}\n      className={cn('w-full', className)}\n      {...props}\n    >\n      {children}\n    </motion.div>\n  );\n};\n\nexport type ChatContainerDividerProps = React.ComponentProps<'div'> & {\n  label?: string;\n};\n\nexport const ChatContainerDivider = ({\n  className,\n  label,\n  children,\n  ...props\n}: ChatContainerDividerProps) => (\n  <div\n    className={cn('relative flex items-center gap-3 py-2', className)}\n    role=\"separator\"\n    aria-label={label}\n    {...props}\n  >\n    <div className=\"bg-border h-px flex-1\" />\n    {(label ?? children) && (\n      <span className=\"text-muted-foreground shrink-0 text-xs\">\n        {label ?? children}\n      </span>\n    )}\n    <div className=\"bg-border h-px flex-1\" />\n  </div>\n);\n\nexport { useChatContainer };\n",
      "type": "registry:ui",
      "target": "components/odysseyui/chat-container.tsx"
    }
  ],
  "type": "registry:ui"
}