Spaces:
Sleeping
Sleeping
| 'use client'; | |
| import { useState, useEffect, useRef } from 'react'; | |
| import { Send, X, MessageSquare, ShieldAlert } from 'lucide-react'; | |
| import { motion, AnimatePresence } from 'framer-motion'; | |
| interface Message { | |
| sender: string; | |
| text: string; | |
| type?: 'chat' | 'system'; | |
| avatar?: string; | |
| } | |
| interface ChatDrawerProps { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| messages: Message[]; | |
| onSendMessage: (text: string) => void; | |
| me: any; | |
| isDead?: boolean; | |
| } | |
| export default function ChatDrawer({ isOpen, onClose, messages, onSendMessage, me, isDead }: ChatDrawerProps) { | |
| const [inputValue, setInputValue] = useState(''); | |
| const messagesEndRef = useRef<HTMLDivElement>(null); | |
| useEffect(() => { | |
| if (isOpen) { | |
| messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); | |
| } | |
| }, [messages, isOpen]); | |
| const handleSubmit = (e: React.FormEvent) => { | |
| e.preventDefault(); | |
| if (!inputValue.trim()) return; | |
| onSendMessage(inputValue.trim()); | |
| setInputValue(''); | |
| }; | |
| return ( | |
| <AnimatePresence> | |
| {isOpen && ( | |
| <> | |
| {/* Backdrop */} | |
| <motion.div | |
| initial={{ opacity: 0 }} | |
| animate={{ opacity: 1 }} | |
| exit={{ opacity: 0 }} | |
| onClick={onClose} | |
| className="fixed inset-0 bg-black/60 backdrop-blur-sm z-[100]" | |
| /> | |
| {/* Drawer */} | |
| <motion.div | |
| initial={{ x: '100%' }} | |
| animate={{ x: 0 }} | |
| exit={{ x: '100%' }} | |
| transition={{ type: 'spring', damping: 25, stiffness: 200 }} | |
| className="fixed right-0 top-0 bottom-0 w-full md:max-w-md bg-space-950 border-l border-white/10 shadow-2xl z-[101] flex flex-col" | |
| > | |
| {/* Header */} | |
| <div className="p-4 md:p-6 border-b border-white/5 flex justify-between items-center bg-space-900/50"> | |
| <div className="flex items-center gap-3"> | |
| <div className="p-2 bg-neon-cyan/10 rounded-xl"> | |
| <MessageSquare className="text-neon-cyan" size={20} /> | |
| </div> | |
| <div> | |
| <h2 className="text-white font-black text-lg tracking-tight uppercase">Mission Comms</h2> | |
| <p className="text-[10px] font-bold text-white/30 uppercase tracking-widest">Secure Channel Active</p> | |
| </div> | |
| </div> | |
| <button | |
| onClick={onClose} | |
| className="p-2 hover:bg-white/5 rounded-full text-white/30 hover:text-white transition-colors" | |
| > | |
| <X size={24} /> | |
| </button> | |
| </div> | |
| {/* Messages Area */} | |
| <div className="flex-1 overflow-y-auto p-6 space-y-6 custom-scrollbar bg-[url('/images/grid.png')] bg-repeat opacity-95"> | |
| {messages.map((msg, i) => { | |
| const isMe = msg.sender === me?.username; | |
| const isSystem = msg.type === 'system'; | |
| if (isSystem) { | |
| return ( | |
| <div key={i} className="flex justify-center"> | |
| <div className="bg-white/5 border border-white/10 px-4 py-1.5 rounded-full flex items-center gap-2"> | |
| <ShieldAlert size={12} className="text-neon-cyan" /> | |
| <span className="text-[10px] font-black text-white/40 uppercase tracking-wider">{msg.text}</span> | |
| </div> | |
| </div> | |
| ); | |
| } | |
| return ( | |
| <motion.div | |
| key={i} | |
| initial={{ opacity: 0, x: isMe ? 20 : -20 }} | |
| animate={{ opacity: 1, x: 0 }} | |
| className={`flex ${isMe ? 'justify-end' : 'justify-start'}`} | |
| > | |
| <div className={`flex gap-3 max-w-[85%] ${isMe ? 'flex-row-reverse' : 'flex-row'}`}> | |
| {/* Avatar */} | |
| <div className="w-8 h-8 rounded-full border border-white/10 overflow-hidden shrink-0 mt-1"> | |
| <img src={`/images/${msg.avatar || 'warga.jpeg'}`} className="w-full h-full object-cover" /> | |
| </div> | |
| <div className={`flex flex-col ${isMe ? 'items-end' : 'items-start'}`}> | |
| <span className="text-[10px] font-black text-white/30 uppercase tracking-tighter mb-1 ml-1"> | |
| {msg.sender} | |
| </span> | |
| <div className={`px-4 py-3 rounded-2xl text-sm font-bold shadow-xl ${ | |
| isMe | |
| ? 'bg-neon-cyan text-space-950 rounded-tr-none' | |
| : 'bg-space-800 text-white border border-white/5 rounded-tl-none' | |
| }`}> | |
| {msg.text} | |
| </div> | |
| </div> | |
| </div> | |
| </motion.div> | |
| ); | |
| })} | |
| <div ref={messagesEndRef} /> | |
| </div> | |
| {/* Input Area */} | |
| <div className="p-4 md:p-6 bg-space-900/80 backdrop-blur-xl border-t border-white/10"> | |
| <form onSubmit={handleSubmit} className="relative group"> | |
| <div className="relative flex items-center bg-space-950/80 border-2 border-white/5 rounded-2xl md:rounded-3xl p-1 focus-within:border-neon-cyan/50 focus-within:shadow-[0_0_20px_rgba(34,211,238,0.15)] transition-all duration-300"> | |
| <input | |
| value={inputValue} | |
| onChange={(e) => setInputValue(e.target.value)} | |
| placeholder={isDead ? "CHANNEL TERKUNCI" : "KETIK PESAN..."} | |
| disabled={isDead} | |
| className="w-full bg-transparent border-none outline-none focus:outline-none focus:ring-0 py-4 md:py-5 px-6 text-white placeholder-white/10 text-base md:text-lg font-display font-bold tracking-wide uppercase" | |
| /> | |
| <button | |
| type="submit" | |
| disabled={!inputValue.trim() || isDead} | |
| className="mr-1 md:mr-2 p-4 md:p-5 bg-neon-cyan text-space-950 rounded-xl md:rounded-2xl shadow-xl disabled:opacity-20 disabled:grayscale transition-all hover:scale-105 active:scale-95 group-focus-within:shadow-neon-cyan/20" | |
| > | |
| <Send size={24} strokeWidth={3} /> | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </motion.div> | |
| </> | |
| )} | |
| </AnimatePresence> | |
| ); | |
| } | |