"use client"; import React, { useState, useRef, useEffect, useCallback } from "react"; import { Button } from "../../ui/Button"; import SuggestedQuestions from "./suggested-questions"; import { useI18n, RTLText } from "./i18n-context"; import { useModalState, Message } from "./modal-state-context"; interface DesktopChatModalProps { onSendMessage: (message: string) => void; isTyping?: boolean; title?: string; placeholder?: string; className?: string; disabled?: boolean; showSuggestedQuestions?: boolean; suggestedQuestionsContext?: string; allowMinimize?: boolean; width?: string; height?: string; /** Whether to announce messages to screen readers */ announceMessages?: boolean; /** Custom breakpoints for responsive design */ breakpoints?: { mobile: string; tablet: string; desktop: string; }; } export function DesktopChatModal({ onSendMessage, isTyping = false, title, placeholder, className = "", disabled = false, showSuggestedQuestions = true, suggestedQuestionsContext = "welcome", allowMinimize = true, width = "800px", height = "600px", announceMessages = true, breakpoints = { mobile: "95vw", tablet: "600px", desktop: "800px", }, }: DesktopChatModalProps) { // Use modal state context const { modalState, closeModal, minimizeModal, maximizeModal, bringToFront, currentSession, addMessage, } = useModalState(); // Use i18n context const { t, isRTL, currentLanguage } = useI18n(); // Use default values from translations if not provided const displayTitle = title || t("chatbot"); const displayPlaceholder = placeholder || t("placeholder"); const [inputValue, setInputValue] = useState(""); const [screenSize, setScreenSize] = useState<"mobile" | "tablet" | "desktop">( "desktop" ); const messagesEndRef = useRef(null); const inputRef = useRef(null); const messagesContainerRef = useRef(null); const modalRef = useRef(null); const announcementRef = useRef(null); // Get messages from current session const messages = currentSession?.messages || []; // Responsive design detection useEffect(() => { const handleResize = () => { const width = window.innerWidth; if (width < 768) { setScreenSize("mobile"); } else if (width < 1024) { setScreenSize("tablet"); } else { setScreenSize("desktop"); } }; // Initial check handleResize(); window.addEventListener("resize", handleResize); return () => window.removeEventListener("resize", handleResize); }, []); // Auto-scroll to bottom when new messages arrive useEffect(() => { if (messagesEndRef.current && !modalState.isMinimized) { messagesEndRef.current.scrollIntoView({ behavior: "smooth" }); } }, [messages, modalState.isMinimized]); // Announce new messages to screen readers useEffect(() => { if (announceMessages && messages.length > 0 && announcementRef.current) { const lastMessage = messages[messages.length - 1]; if (lastMessage.sender === "ai") { announcementRef.current.textContent = `AI responded: ${lastMessage.content}`; // Clear after a delay to avoid cluttering screen readers setTimeout(() => { if (announcementRef.current) { announcementRef.current.textContent = ""; } }, 1000); } } }, [messages, announceMessages]); // Focus input when modal opens useEffect(() => { if (modalState.isOpen && !modalState.isMinimized && inputRef.current) { const timer = setTimeout(() => { inputRef.current?.focus(); }, 100); return () => clearTimeout(timer); } }, [modalState.isOpen, modalState.isMinimized]); // Handle escape key to close modal useEffect(() => { const handleEscape = (event: KeyboardEvent) => { if ( event.key === "Escape" && modalState.isOpen && !modalState.isMinimized ) { closeModal(); } }; if (modalState.isOpen) { document.addEventListener("keydown", handleEscape); // Prevent body scroll when modal is open document.body.style.overflow = "hidden"; } return () => { document.removeEventListener("keydown", handleEscape); document.body.style.overflow = "unset"; }; }, [modalState.isOpen, modalState.isMinimized, closeModal]); // Enhanced focus trap for accessibility useEffect(() => { if (modalState.isOpen && !modalState.isMinimized && modalRef.current) { const focusableElements = modalRef.current.querySelectorAll( 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])' ); const firstElement = focusableElements[0] as HTMLElement; const lastElement = focusableElements[ focusableElements.length - 1 ] as HTMLElement; const handleTabKey = (e: KeyboardEvent) => { if (e.key === "Tab") { if (e.shiftKey) { if (document.activeElement === firstElement) { lastElement?.focus(); e.preventDefault(); } } else { if (document.activeElement === lastElement) { firstElement?.focus(); e.preventDefault(); } } } }; document.addEventListener("keydown", handleTabKey); return () => document.removeEventListener("keydown", handleTabKey); } }, [modalState.isOpen, modalState.isMinimized]); const handleSendMessage = useCallback(() => { const trimmedValue = inputValue.trim(); if (trimmedValue && !disabled) { // Add user message to state addMessage({ content: trimmedValue, sender: "user", type: "text", }); // Call external handler onSendMessage(trimmedValue); setInputValue(""); } }, [inputValue, onSendMessage, disabled, addMessage]); const handleKeyPress = useCallback( (event: React.KeyboardEvent) => { if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); handleSendMessage(); } }, [handleSendMessage] ); const handleMinimize = useCallback(() => { if (modalState.isMinimized) { maximizeModal(); } else { minimizeModal(); } }, [modalState.isMinimized, minimizeModal, maximizeModal]); const handleBackdropClick = useCallback( (event: React.MouseEvent) => { if (event.target === event.currentTarget && !modalState.isMinimized) { closeModal(); } }, [closeModal, modalState.isMinimized] ); const handleModalClick = useCallback(() => { bringToFront(); }, [bringToFront]); const formatTimestamp = (timestamp: Date) => { return timestamp.toLocaleTimeString( currentLanguage === "ko" ? "ko-KR" : "en-US", { hour: "2-digit", minute: "2-digit", } ); }; // Get responsive modal dimensions const getModalDimensions = () => { if (modalState.isMinimized) { return { width: "320px", height: "64px" }; } switch (screenSize) { case "mobile": return { width: breakpoints.mobile, height: "85vh", maxWidth: "95vw", maxHeight: "85vh", }; case "tablet": return { width: breakpoints.tablet, height: "70vh", maxWidth: "90vw", maxHeight: "80vh", }; default: return { width: modalState.size.width || width, height: modalState.size.height || height, maxWidth: "90vw", maxHeight: "90vh", }; } }; const modalDimensions = getModalDimensions(); const TypingIndicator = () => (
{t("typing")}
); const MessageBubble = ({ message }: { message: Message }) => { const isUser = message.sender === "user"; const isError = message.type === "error"; const isSystem = message.type === "system"; return (
{message.content}
); }; if (!modalState.isOpen) return null; return (
{/* Backdrop */}
{/* Screen Reader Announcements */}
{/* Modal */}
{/* Hidden description for screen readers */}
AI chatbot conversation interface. Use Tab to navigate between elements, Escape to close.
{/* Header */}

{displayTitle}

{currentSession && screenSize !== "mobile" && ( ({messages.length} messages) )}
{allowMinimize && ( )}
{/* Body - Hidden when minimized */} {!modalState.isMinimized && (
{/* Messages Area */}
{messages.length === 0 ? (
💬
{t("welcomeMessage")} {/* Suggested Questions for first-time users */} {showSuggestedQuestions && (
)}
) : ( messages.map((message) => ( )) )} {isTyping && }
{/* Input Area */}
setInputValue(e.target.value)} onKeyPress={handleKeyPress} placeholder={displayPlaceholder} disabled={disabled} className={` flex-1 px-3 sm:px-4 py-2 sm:py-3 border border-gray-300 rounded-xl sm:rounded-2xl text-sm sm:text-base focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent disabled:bg-gray-100 disabled:cursor-not-allowed transition-colors ${isRTL ? "text-right" : "text-left"} `} dir={isRTL ? "rtl" : "ltr"} aria-label={displayPlaceholder} aria-describedby="input-instructions" />
{/* Hidden instructions for screen readers */}
Type your message and press Enter or click Send button to send. Press Escape to close the chat.
{/* Input hint for mobile users */} {screenSize === "mobile" && (

Press Enter to send • Escape to close

)}
)}
); } export default DesktopChatModal;