"use client" import * as React from "react" import { useState, useEffect, useRef } from "react"; import { Lightbulb, Mic, Globe, Paperclip, Send } from "lucide-react"; import { AnimatePresence, motion } from "framer-motion"; const PLACEHOLDERS = [ "Generate website with HextaUI", "Create a new project with Next.js", "What is the meaning of life?", "What is the best way to learn React?", "How to cook a delicious meal?", "Summarize this article", ]; interface AIChatInputProps { onSubmit?: (message: string) => void; onFocus?: () => void; autoFocus?: boolean; } const AIChatInput = ({ onSubmit, onFocus, autoFocus = false }: AIChatInputProps = {}) => { const [placeholderIndex, setPlaceholderIndex] = useState(0); const [showPlaceholder, setShowPlaceholder] = useState(true); const [isActive, setIsActive] = useState(false); const [thinkActive, setThinkActive] = useState(false); const [deepSearchActive, setDeepSearchActive] = useState(false); const [inputValue, setInputValue] = useState(""); const wrapperRef = useRef(null); const inputRef = useRef(null); // Cycle placeholder text when input is inactive useEffect(() => { if (isActive || inputValue) return; const interval = setInterval(() => { setShowPlaceholder(false); setTimeout(() => { setPlaceholderIndex((prev) => (prev + 1) % PLACEHOLDERS.length); setShowPlaceholder(true); }, 400); }, 3000); return () => clearInterval(interval); }, [isActive, inputValue]); // Close input when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( wrapperRef.current && !wrapperRef.current.contains(event.target as Node) ) { if (!inputValue) setIsActive(false); } }; document.addEventListener("mousedown", handleClickOutside as EventListener); return () => document.removeEventListener("mousedown", handleClickOutside as EventListener); }, [inputValue]); const handleActivate = () => setIsActive(true); // notify parent when input is focused/activated const handleActivateWithCallback = () => { setIsActive(true); if (onFocus) onFocus(); }; // auto-focus when requested useEffect(() => { if (autoFocus && inputRef.current) { try { inputRef.current.focus(); } catch (e) {} } }, [autoFocus]); const handleSubmit = () => { if (inputValue.trim() && onSubmit) { onSubmit(inputValue.trim()); setInputValue(""); setIsActive(false); } }; // small ripple effect for send button const createRipple = (e: React.MouseEvent) => { const button = e.currentTarget as HTMLButtonElement; const circle = document.createElement('span'); const diameter = Math.max(button.clientWidth, button.clientHeight); const radius = diameter / 2; circle.style.width = circle.style.height = `${diameter}px`; circle.style.left = `${e.nativeEvent.offsetX - radius}px`; circle.style.top = `${e.nativeEvent.offsetY - radius}px`; circle.className = 'ls-ripple'; const ripple = button.getElementsByClassName('ls-ripple')[0]; if (ripple) ripple.remove(); button.appendChild(circle); setTimeout(() => circle.remove(), 500); }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSubmit(); } }; const containerVariants: any = { collapsed: { height: 68, boxShadow: "0 2px 8px 0 rgba(0,0,0,0.08)", transition: { type: "spring", stiffness: 120, damping: 18 }, }, expanded: { height: 128, boxShadow: "0 8px 32px 0 rgba(0,0,0,0.16)", transition: { type: "spring", stiffness: 120, damping: 18 }, }, }; const placeholderContainerVariants: any = { initial: {}, animate: { transition: { staggerChildren: 0.025 } }, exit: { transition: { staggerChildren: 0.015, staggerDirection: -1 } }, }; const letterVariants: any = { initial: { opacity: 0, filter: "blur(12px)", y: 10, }, animate: { opacity: 1, filter: "blur(0px)", y: 0, transition: { opacity: { duration: 0.25 }, filter: { duration: 0.4 }, y: { type: "spring", stiffness: 80, damping: 20 }, }, }, exit: { opacity: 0, filter: "blur(12px)", y: -10, transition: { opacity: { duration: 0.2 }, filter: { duration: 0.3 }, y: { type: "spring", stiffness: 80, damping: 20 }, }, }, }; return (
{/* Input Row */}
{/* Text Input & Placeholder */}
setInputValue(e.target.value)} onKeyDown={handleKeyDown} className="flex-1 border-0 outline-0 rounded-md py-2 text-sm md:text-base bg-transparent w-full font-normal relative z-10" onFocus={handleActivateWithCallback} ref={inputRef} autoComplete="off" aria-label="Message input" aria-describedby="ls-chat-input-hint" />
{showPlaceholder && !isActive && !inputValue && ( {PLACEHOLDERS[placeholderIndex] .split("") .map((char, i) => ( {char === " " ? "\u00A0" : char} ))} )}
{/* Expanded Controls */}
{/* Think Toggle */} {/* Deep Search Toggle */} { e.stopPropagation(); setDeepSearchActive((a) => !a); }} initial={false} animate={{ width: deepSearchActive ? 125 : 36, paddingLeft: deepSearchActive ? 8 : 9, }} aria-pressed={deepSearchActive} >
Deep Search
{/* Keyboard hint (visually small, accessible) */}
Press Enter to send, Shift+Enter for newline.
); }; export { AIChatInput };