"use client"; import { cn } from "@mdxui/primitives/lib/utils"; import { AnimatePresence, motion, type PanInfo } from "motion/react"; import { useEffect, useRef, useState } from "react"; import { ChatHeader } from "./chat-header"; import { ChatInput } from "./chat-input"; import { ChatMessages } from "./chat-messages"; interface ChatPanelProps { isOpen: boolean; onClose: () => void; position?: "bottom-right" | "bottom-left"; title?: string; avatar?: string; } export function ChatPanel({ isOpen, onClose, position = "bottom-right", title, avatar, }: ChatPanelProps) { const panelRef = useRef(null); const [isMobile, setIsMobile] = useState(false); const [hasMounted, setHasMounted] = useState(false); // Detect mobile and mark as mounted useEffect(() => { const checkMobile = () => setIsMobile(window.innerWidth < 640); checkMobile(); setHasMounted(true); window.addEventListener("resize", checkMobile); return () => window.removeEventListener("resize", checkMobile); }, []); // Close on escape useEffect(() => { const handleEscape = (e: KeyboardEvent) => { if (e.key === "Escape" && isOpen) { onClose(); } }; document.addEventListener("keydown", handleEscape); return () => document.removeEventListener("keydown", handleEscape); }, [isOpen, onClose]); // Focus management useEffect(() => { if (isOpen && panelRef.current) { const input = panelRef.current.querySelector("textarea"); input?.focus(); } }, [isOpen]); const handleDragEnd = ( _: MouseEvent | TouchEvent | PointerEvent, info: PanInfo, ) => { // Close if dragged down more than 100px or with enough velocity if (info.offset.y > 100 || info.velocity.y > 500) { onClose(); } }; // Don't render until mounted to avoid hydration mismatch with isMobile if (!hasMounted) { return null; } return ( {isOpen && ( {/* Drag handle indicator for mobile */} {isMobile && (
)} )} ); }