UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

686 lines (683 loc) 33.6 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { cn } from '../../lib/utilsComprehensive.js'; import { Hash, Search, Phone, Video, MoreVertical, Users, Image, File, Smile, X, Paperclip, MicOff, Mic, Send } from 'lucide-react'; import { useState, useRef, useEffect, useCallback } from 'react'; import '../../primitives/GlassCore.js'; import '../../primitives/glass/GlassAdvanced.js'; import '../../primitives/OptimizedGlassCore.js'; import '../../primitives/glass/OptimizedGlassAdvanced.js'; import '../../primitives/MotionNative.js'; import { MotionFramer } from '../../primitives/motion/MotionFramer.js'; import { GlassButton } from '../button/GlassButton.js'; import '../button/GlassFab.js'; import '../button/GlassMagneticButton.js'; import { CardHeader, CardTitle, CardContent } from '../card/index.js'; import { usePredictiveEngine, useInteractionRecorder } from '../advanced/GlassPredictiveEngine.js'; import { useAchievements } from '../advanced/GlassAchievementSystem.js'; import { useBiometricAdaptation } from '../advanced/GlassBiometricAdaptation.js'; import { useEyeTracking } from '../advanced/GlassEyeTracking.js'; import { useSpatialAudio } from '../advanced/GlassSpatialAudio.js'; import { GlassCard } from '../card/GlassCard.js'; /** * GlassChat component * A comprehensive chat interface with messages, participants, and real-time features */ const GlassChat = ({ messages, participants = [], currentUser, title = "Chat", subtitle, enableReactions = true, enableAttachments = true, enableVoice = false, enableTyping = true, showParticipants = false, showTimestamps = true, showAvatars = true, onSendMessage, onMessageReaction, onTyping, className, // Consciousness features consciousness = false, predictive = false, adaptive = false, eyeTracking = false, spatialAudio = false, trackAchievements = false, ...props }) => { const [newMessage, setNewMessage] = useState(""); const [isTyping, setIsTyping] = useState(false); const [attachments, setAttachments] = useState([]); const [showEmojiPicker, setShowEmojiPicker] = useState(false); const [isRecording, setIsRecording] = useState(false); // Consciousness state const [messageInteractionCounts, setMessageInteractionCounts] = useState({}); const [suggestedResponses, setSuggestedResponses] = useState([]); const [adaptiveInputHeight, setAdaptiveInputHeight] = useState(40); const [focusedMessage, setFocusedMessage] = useState(null); const [conversationInsights, setConversationInsights] = useState(null); const messagesEndRef = useRef(null); const inputRef = useRef(null); const fileInputRef = useRef(null); // Consciousness hooks const predictiveEngine = predictive ? usePredictiveEngine() : null; const eyeTracker = eyeTracking ? useEyeTracking() : null; const biometricAdapter = adaptive ? useBiometricAdaptation() : null; const spatialAudioEngine = spatialAudio ? useSpatialAudio() : null; const interactionRecorder = consciousness ? useInteractionRecorder() : null; const achievementTracker = trackAchievements ? useAchievements() : null; // Auto-scroll to bottom when new messages arrive useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); // Handle typing indicator useEffect(() => { if (!enableTyping) return; const timeout = setTimeout(() => { if (isTyping) { setIsTyping(false); onTyping?.(false); } }, 2000); return () => clearTimeout(timeout); }, [newMessage, isTyping, enableTyping, onTyping]); // Consciousness effects // Biometric adaptation for input height and response timing useEffect(() => { if (!adaptive || !biometricAdapter) return; const updateAdaptiveFeatures = () => { const stressLevel = biometricAdapter.currentStressLevel; // Adjust input height based on stress levels if (stressLevel > 0.7) { setAdaptiveInputHeight(60); // Larger input for stressed users } else if (stressLevel < 0.3) { setAdaptiveInputHeight(32); // Compact for calm users } else { setAdaptiveInputHeight(40); // Default } // Record biometric adaptation for achievements if (trackAchievements && achievementTracker) { achievementTracker.recordAction("chat_adaptive_ui", { stressLevel: stressLevel, inputHeight: adaptiveInputHeight }); } }; const interval = setInterval(updateAdaptiveFeatures, 2000); return () => clearInterval(interval); }, [adaptive, biometricAdapter, achievementTracker, trackAchievements, adaptiveInputHeight]); // Eye tracking for message focus useEffect(() => { if (!eyeTracking || !eyeTracker) return; // Eye tracking methods not available in current implementation // eyeTracker.startTracking(handleGazeData); // return () => eyeTracker.stopTracking(); }, [eyeTracking, eyeTracker, achievementTracker, trackAchievements]); // Predictive response suggestions useEffect(() => { if (!predictive || !predictiveEngine) return; const generateSuggestions = async () => { try { const conversationContext = { messages: messages.slice(-5).map(m => ({ content: m.content, sender: m.sender.name, timestamp: m.timestamp })), currentUser: currentUser?.name || "User" }; // Use available predictive data const predictions = predictiveEngine.predictions; const insights = predictiveEngine.insights; // Generate simple suggestions based on available data const suggestions = predictions.filter(p => p.type === "suggest").map(p => p.metadata?.text || "").filter(text => text.length > 0).slice(0, 3); setSuggestedResponses(suggestions); // Transform insights array to expected object format const transformedInsights = insights && insights.length > 0 ? { sentiment: insights.find(i => i.type === "sentiment")?.metadata?.sentiment || "neutral", urgency: insights.find(i => i.type === "urgency")?.metadata?.urgency || "medium", topics: insights.filter(i => i.type === "topic").map(i => i.metadata?.topic || "").filter(topic => topic.length > 0) } : null; setConversationInsights(transformedInsights); } catch (error) { console.warn("Predictive response generation failed:", error); } }; if (messages.length > 0) { const timeoutId = setTimeout(generateSuggestions, 500); return () => clearTimeout(timeoutId); } }, [messages, predictive, predictiveEngine, currentUser]); // Spatial audio for message notifications useEffect(() => { if (!spatialAudio || !spatialAudioEngine) return; const latestMessage = messages[messages.length - 1]; if (!latestMessage || latestMessage.sender.id === currentUser?.id) return; // Play spatial audio notification for new messages spatialAudioEngine.playGlassSound("message-notification", undefined, { volume: 0.6, reverb: 0.3 }); // Record achievement for audio engagement if (trackAchievements && achievementTracker) { achievementTracker.recordAction("spatial_audio_notification", { messageId: latestMessage.id, sender: latestMessage.sender.name }); } }, [messages, spatialAudio, spatialAudioEngine, currentUser, trackAchievements, achievementTracker]); // Message interaction tracking useEffect(() => { if (!consciousness || !interactionRecorder) return; const recordMessageInteraction = (messageId, interactionType) => { setMessageInteractionCounts(prev => ({ ...prev, [messageId]: (prev[messageId] || 0) + 1 })); // Create synthetic event for interaction recording const syntheticEvent = { currentTarget: { id: `message-${messageId}` }, type: interactionType === "click" ? "click" : interactionType === "hover" ? "mouseenter" : "focus" }; if (interactionType === "click") { interactionRecorder.recordClick(syntheticEvent); } else if (interactionType === "hover") { interactionRecorder.recordHover(syntheticEvent); } else { interactionRecorder.recordFocus(syntheticEvent); } }; // Set up global interaction listeners for message elements const handleMessageClick = event => { const messageElement = event.target.closest('[data-consciousness-message="true"]'); if (messageElement) { const messageId = messageElement.getAttribute("data-message-id"); if (messageId) { recordMessageInteraction(messageId, "message_click"); } } }; document.addEventListener("click", handleMessageClick); return () => document.removeEventListener("click", handleMessageClick); }, [consciousness, interactionRecorder, title]); // Handle input change const handleInputChange = useCallback(e => { setNewMessage(e.target.value); if (enableTyping && !isTyping && e.target.value.length > 0) { setIsTyping(true); onTyping?.(true); } }, [enableTyping, isTyping, onTyping]); // Enhanced send message handler with consciousness tracking const handleSendMessage = useCallback(() => { if (!newMessage.trim() && attachments.length === 0) return; // Record interaction for achievements and learning if (consciousness && interactionRecorder) { // Create synthetic event for message send interaction const syntheticEvent = { currentTarget: { id: "chat-input" }, type: "click" }; interactionRecorder.recordClick(syntheticEvent); } // Track achievement for message sending if (trackAchievements && achievementTracker) { achievementTracker.recordAction("chat_message_sent", { messageLength: newMessage.trim().length, hasAttachments: attachments.length > 0, timestamp: Date.now() }); } // Play spatial audio feedback for message sent if (spatialAudio && spatialAudioEngine) { spatialAudioEngine.playGlassSound("message-sent", undefined, { volume: 0.4, reverb: 0.2 }); } onSendMessage?.(newMessage.trim(), attachments); setNewMessage(""); setAttachments([]); setIsTyping(false); onTyping?.(false); setSuggestedResponses([]); // Clear suggestions after sending // Reset textarea height with adaptive sizing if (inputRef.current) { inputRef.current.style.height = "auto"; if (adaptive) { inputRef.current.style.minHeight = `${adaptiveInputHeight}px`; } } }, [newMessage, attachments, onSendMessage, onTyping, consciousness, interactionRecorder, title, trackAchievements, achievementTracker, spatialAudio, spatialAudioEngine, suggestedResponses, adaptive, adaptiveInputHeight]); // Handle key press const handleKeyPress = useCallback(e => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSendMessage(); } }, [handleSendMessage]); // Handle file attachment const handleFileAttachment = useCallback(() => { fileInputRef.current?.click(); }, []); const handleFileChange = useCallback(e => { const files = Array.from(e.target.files || []); setAttachments(prev => [...prev, ...files]); e.target.value = ""; }, []); // Remove attachment const removeAttachment = useCallback(index => { setAttachments(prev => prev.filter((_, i) => i !== index)); }, []); // Handle voice recording const handleVoiceToggle = useCallback(() => { setIsRecording(!isRecording); // Voice recording logic would go here }, [isRecording]); // Format timestamp const formatTimestamp = useCallback(date => { const now = new Date(); const diff = now.getTime() - date.getTime(); const minutes = Math.floor(diff / 60000); const hours = Math.floor(diff / 3600000); const days = Math.floor(diff / 86400000); if (minutes < 1) return "Just now"; if (minutes < 60) return `${minutes}m ago`; if (hours < 24) return `${hours}h ago`; if (days === 1) return "Yesterday"; if (days < 7) return `${days}d ago`; return date.toLocaleDateString(); }, []); // Group messages by date const groupedMessages = messages.reduce((groups, message) => { const date = message.timestamp.toDateString(); if (!groups[date]) { groups[date] = []; } groups[date].push(message); return groups; }, {}); // Auto-resize textarea useEffect(() => { if (inputRef.current) { inputRef.current.style.height = "auto"; inputRef.current.style.height = `${Math.min(inputRef.current.scrollHeight, 120)}px`; } }, [newMessage]); return jsx(MotionFramer, { "data-glass-component": true, preset: "fadeIn", className: "glass-w-full glass-h-full", children: jsxs(GlassCard, { className: cn("flex flex-col h-full overflow-hidden", className), ...props, children: [jsxs(CardHeader, { className: 'pb-3 glass-border-b glass-border-white/10', children: [jsxs("div", { className: "glass-flex glass-items-center glass-justify-between", children: [jsxs("div", { children: [jsxs(CardTitle, { className: 'text-primary glass-text-lg font-semibold glass-flex glass-items-center glass-gap-2', children: [jsx(Hash, { className: 'w-5 h-5' }), title] }), subtitle && jsx("p", { className: 'text-primary/60 glass-text-sm glass-mt-1', children: subtitle })] }), jsxs("div", { className: "glass-flex glass-items-center glass-gap-2", children: [jsx(GlassButton, { variant: "ghost", size: "sm", className: "glass-p-2", children: jsx(Search, { className: 'w-4 h-4' }) }), jsx(GlassButton, { variant: "ghost", size: "sm", className: "glass-p-2", children: jsx(Phone, { className: 'w-4 h-4' }) }), jsx(GlassButton, { variant: "ghost", size: "sm", className: "glass-p-2", children: jsx(Video, { className: 'w-4 h-4' }) }), jsx(GlassButton, { variant: "ghost", size: "sm", className: "glass-p-2", children: jsx(MoreVertical, { className: 'w-4 h-4' }) })] })] }), participants.length > 0 && jsxs("div", { className: "glass-flex glass-items-center glass-gap-2 glass-mt-2", children: [jsx(Users, { className: 'w-4 h-4 text-primary/60' }), jsxs("span", { className: 'text-primary/60 glass-text-sm', children: [participants.filter(p => p.status === "online").length, " ", "online \u2022 ", participants.length, " total"] })] })] }), jsxs("div", { className: 'glass-flex glass-flex-1 overflow-hidden', children: [jsxs("div", { className: 'glass-flex-1 glass-flex glass-flex-col overflow-hidden', children: [jsxs(CardContent, { className: 'glass-flex-1 overflow-y-auto glass-p-4 glass-gap-4', children: [Object.entries(groupedMessages).map(([date, dateMessages]) => jsxs("div", { children: [jsx("div", { className: "glass-flex glass-items-center glass-justify-center glass-my-6", children: jsx("div", { className: "glass-px-3 glass-py-1 glass-surface-subtle/10 glass-radius-full", children: jsx("span", { className: 'text-primary/60 glass-text-xs', children: new Date(date).toLocaleDateString(undefined, { weekday: "long", year: "numeric", month: "long", day: "numeric" }) }) }) }), jsx("div", { className: "glass-gap-3", children: dateMessages.map((message, index) => { const isCurrentUser = message.sender.id === currentUser?.id; const prevMessage = dateMessages[index - 1]; const showAvatar = showAvatars && (!prevMessage || prevMessage.sender.id !== message.sender.id); return jsxs("div", { className: cn("flex glass-gap-3 group animate-slide-in-up", isCurrentUser ? "flex-row-reverse" : "flex-row", eyeTracking && focusedMessage === message.id && "consciousness-message-focused", consciousness && messageInteractionCounts[message.id] > 0 && "consciousness-message-interacted", predictive && conversationInsights?.sentiment === "positive" && "consciousness-positive-context", predictive && conversationInsights?.sentiment === "negative" && "consciousness-negative-context", predictive && conversationInsights?.urgency === "high" && "consciousness-urgent-context"), style: { animationDelay: `${Math.min(index, 20) * 20}ms`, animationFillMode: "both" }, "data-consciousness-message": "true", "data-message-id": message.id, "data-message-sentiment": conversationInsights?.sentiment, "data-message-urgency": conversationInsights?.urgency, "data-interaction-count": messageInteractionCounts[message.id] || 0, "data-gaze-focused": focusedMessage === message.id, children: [showAvatar && showAvatars && jsx("div", { className: "glass-flex-shrink-0", children: jsx("div", { className: 'w-8 h-8 glass-radius-full glass-surface-subtle/20 glass-flex glass-items-center glass-justify-center', children: message.sender.avatar ? jsx("img", { src: message.sender.avatar, alt: message.sender.name, className: 'glass-w-full glass-h-full glass-radius-full object-cover' }) : jsx("span", { className: 'text-primary/80 glass-text-sm font-medium', children: message.sender.name.charAt(0).toUpperCase() }) }) }), jsxs("div", { className: cn("flex-1 max-w-[70%]", isCurrentUser ? "items-end" : "items-start"), children: [showAvatar && jsxs("div", { className: cn("flex items-center glass-gap-2 glass-mb-1", isCurrentUser ? "justify-end" : "justify-start"), children: [jsx("span", { className: 'text-primary/80 glass-text-sm font-medium', children: message.sender.name }), message.sender.status && jsx("div", { className: cn("w-2 h-2 glass-radius-full", message.sender.status === "online" ? "bg-green-400" : message.sender.status === "away" ? "bg-yellow-400" : message.sender.status === "busy" ? "bg-red-400" : "bg-gray-400") })] }), jsxs("div", { className: cn("relative glass-px-4 glass-py-2 rounded-2xl max-w-full break-words", isCurrentUser ? "bg-primary text-primary-foreground ml-auto" : "bg-white/10 glass-text-primary"), children: [message.replyTo && jsx("div", { className: 'glass-text-xs opacity-70 mb-2 pb-2 glass-border-b glass-border-current glass-border-opacity-20', children: "Replying to message" }), jsx("div", { className: "glass-text-sm", children: message.content }), message.attachments && message.attachments.length > 0 && jsx("div", { className: "glass-mt-2 glass-gap-2", children: message.attachments.map((attachment, attIndex) => jsxs("div", { className: "glass-flex glass-items-center glass-gap-2 glass-p-2 glass-surface-dark/20 glass-radius-md", children: [attachment.type === "image" && jsx(Image, { className: 'w-4 h-4' }), attachment.type === "file" && jsx(File, { className: 'w-4 h-4' }), jsx("span", { className: 'glass-text-xs truncate', children: attachment.name })] }, attIndex)) }), message.reactions && message.reactions.length > 0 && jsx("div", { className: "glass-flex glass-gap-1 glass-mt-2", children: message.reactions.map((reaction, reactionIndex) => jsxs(GlassButton, { variant: "ghost", size: "sm", onClick: e => onMessageReaction?.(message.id, reaction.emoji), className: 'h-6 glass-px-2 glass-text-xs', children: [reaction.emoji, " ", reaction.count] }, reactionIndex)) })] }), jsxs("div", { className: cn("flex items-center glass-gap-2 glass-mt-1 glass-text-xs glass-text-primary/50", isCurrentUser ? "justify-end" : "justify-start"), children: [showTimestamps && jsx("span", { children: formatTimestamp(message.timestamp) }), message.edited && jsx("span", { children: "(edited)" })] })] }), jsx("div", { className: cn("opacity-0 group-hover:opacity-100 transition-opacity flex glass-gap-1", isCurrentUser ? "flex-row-reverse" : "flex-row"), children: enableReactions && jsx(GlassButton, { variant: "ghost", size: "sm", onClick: e => setShowEmojiPicker(!showEmojiPicker), className: "glass-p-1", children: jsx(Smile, { className: 'w-3 h-3' }) }) })] }, message.id); }) })] }, date)), jsx("div", { ref: messagesEndRef })] }), jsxs("div", { className: cn("border-t border-white/10 glass-p-4", adaptive && "consciousness-adaptive-input", predictive && suggestedResponses.length > 0 && "consciousness-predictive-suggestions", consciousness && "consciousness-enhanced-input"), "data-consciousness-input-area": "true", "data-adaptive-height": adaptiveInputHeight, "data-has-suggestions": suggestedResponses.length > 0, "data-conversation-sentiment": conversationInsights?.sentiment, "data-conversation-urgency": conversationInsights?.urgency, children: [predictive && suggestedResponses.length > 0 && jsxs("div", { className: 'mb-3', "data-consciousness-suggestions": "true", children: [jsxs("div", { className: 'glass-text-xs text-primary/60 mb-2 glass-flex glass-items-center glass-gap-1', children: [jsx("span", { children: "Suggested responses" }), conversationInsights?.sentiment && jsx("span", { className: cn("glass-px-2 glass-py-0.5 glass-radius-md glass-text-xs", conversationInsights.sentiment === "positive" ? "bg-green-500/20 text-green-300" : conversationInsights.sentiment === "negative" ? "bg-red-500/20 text-red-300" : "bg-yellow-500/20 text-yellow-300"), children: conversationInsights.sentiment }), conversationInsights?.urgency === "high" && jsx("span", { className: "glass-px-2 glass-py-0.5 glass-radius-md glass-text-xs glass-surface-primary/20 glass-text-secondary", children: "urgent" })] }), jsx("div", { className: "glass-flex glass-gap-2 glass-flex-wrap", children: suggestedResponses.map((suggestion, index) => jsx(GlassButton, { variant: "ghost", size: "sm", onClick: () => setNewMessage(suggestion), className: 'glass-text-xs glass-px-3 glass-py-1 h-auto consciousness-suggestion-button', "data-suggestion-index": index, children: suggestion }, index)) })] }), predictive && conversationInsights?.topics && conversationInsights.topics.length > 0 && jsxs("div", { className: 'mb-3', "data-consciousness-insights": "true", children: [jsx("div", { className: 'glass-text-xs text-primary/60 mb-2', children: "Conversation topics:" }), jsx("div", { className: "glass-flex glass-gap-1 glass-flex-wrap", children: conversationInsights.topics.map((topic, index) => jsx("span", { className: 'glass-px-2 glass-py-0.5 glass-radius-md glass-text-xs glass-surface-primary/20 text-primary', children: topic }, index)) })] }), attachments.length > 0 && jsx("div", { className: 'glass-flex glass-gap-2 mb-3 overflow-x-auto', children: attachments.map((file, index) => jsxs("div", { className: "glass-flex glass-items-center glass-gap-2 glass-surface-subtle/10 glass-radius-md glass-px-3 glass-py-2", children: [jsx(File, { className: 'w-4 h-4' }), jsx("span", { className: 'glass-text-sm text-primary truncate max-w-32', children: file.name }), jsx(GlassButton, { variant: "ghost", size: "sm", onClick: e => removeAttachment(index), className: 'glass-p-1 h-auto', children: jsx(X, { className: 'w-3 h-3' }) })] }, index)) }), jsxs("div", { className: "glass-flex glass-items-end glass-gap-2", children: [enableAttachments && jsx(GlassButton, { variant: "ghost", size: "sm", onClick: handleFileAttachment, className: "glass-p-2", children: jsx(Paperclip, { className: 'w-4 h-4' }) }), enableVoice && jsx(GlassButton, { variant: isRecording ? "destructive" : "ghost", size: "sm", onClick: handleVoiceToggle, className: "glass-p-2", children: isRecording ? jsx(MicOff, { className: 'w-4 h-4' }) : jsx(Mic, { className: 'w-4 h-4' }) }), jsxs("div", { className: 'glass-flex-1 relative', children: [jsx("textarea", { ref: inputRef, value: newMessage, onChange: handleInputChange, onKeyPress: handleKeyPress, placeholder: predictive && conversationInsights?.urgency === "high" ? "Type urgent response..." : adaptive ? "Type a message (adaptive sizing enabled)..." : "Type a message...", className: cn("w-full bg-glass-fill ring-1 ring-white/10 glass-radius-lg glass-px-4 glass-py-2 glass-text-primary placeholder-white/50 focus:outline-none focus:ring-white/30 resize-none max-h-[120px]", adaptive && "consciousness-adaptive-textarea", predictive && conversationInsights?.urgency === "high" && "consciousness-urgent-input", eyeTracking && "consciousness-eye-trackable"), style: { minHeight: adaptive ? `${adaptiveInputHeight}px` : "40px" }, rows: 1, "data-consciousness-input": "true", "data-adaptive-height": adaptiveInputHeight, "data-urgency": conversationInsights?.urgency, "data-eye-tracking": String(!!eyeTracking) }), jsx(GlassButton, { variant: "ghost", size: "sm", onClick: e => setShowEmojiPicker(!showEmojiPicker), className: 'absolute right-2 glass-top-1/2 transform -translate-y-1/2 glass-p-1', children: jsx(Smile, { className: 'w-4 h-4' }) })] }), jsx(GlassButton, { variant: "primary", size: "sm", onClick: handleSendMessage, disabled: !newMessage.trim() && attachments.length === 0, className: cn("glass-p-2", consciousness && "consciousness-send-button", predictive && conversationInsights?.urgency === "high" && "consciousness-urgent-send", adaptive && "consciousness-adaptive-send"), consciousness: consciousness, predictive: predictive, adaptive: adaptive, spatialAudio: spatialAudio, trackAchievements: trackAchievements, "data-consciousness-send": "true", "data-urgency": conversationInsights?.urgency, "data-has-attachments": attachments.length > 0, "data-message-length": newMessage.trim().length, children: jsx(Send, { className: 'w-4 h-4' }) })] }), jsx("input", { ref: fileInputRef, type: "file", multiple: true, onChange: handleFileChange, className: 'hidden glass-touch-target glass-contrast-guard', accept: "image/*,video/*,audio/*,.pdf,.doc,.docx,.txt" })] })] }), showParticipants && participants.length > 0 && jsxs("div", { className: 'w-64 glass-border-l glass-border-white/10 glass-flex glass-flex-col', children: [jsx("div", { className: "glass-p-4 glass-border-b glass-border-white/10", children: jsxs("h3", { className: 'text-primary font-medium glass-flex glass-items-center glass-gap-2', children: [jsx(Users, { className: 'w-4 h-4' }), "Participants (", participants.length, ")"] }) }), jsx("div", { className: 'glass-flex-1 overflow-y-auto glass-p-2', children: participants.map(participant => jsxs("div", { className: 'glass-flex glass-items-center glass-gap-3 glass-p-2 glass-radius-lg hover:glass-surface-subtle/5', children: [jsxs("div", { className: 'relative', children: [jsx("div", { className: 'w-8 h-8 glass-radius-full glass-surface-subtle/20 glass-flex glass-items-center glass-justify-center', children: participant.avatar ? jsx("img", { src: participant.avatar, alt: participant.name, className: 'glass-w-full glass-h-full glass-radius-full object-cover' }) : jsx("span", { className: 'text-primary/80 glass-text-sm font-medium', children: participant.name.charAt(0).toUpperCase() }) }), jsx("div", { className: cn("absolute -bottom-1 -right-1 w-3 h-3 glass-radius-full border-2 border-black", participant.status === "online" ? "bg-green-400" : participant.status === "away" ? "bg-yellow-400" : participant.status === "busy" ? "bg-red-400" : "bg-gray-400") })] }), jsxs("div", { className: "glass-flex-1 glass-min-w-0", children: [jsx("p", { className: 'text-primary glass-text-sm font-medium truncate', children: participant.name }), jsx("p", { className: 'text-primary/60 glass-text-xs', children: participant.status === "online" ? "Online" : participant.status === "away" ? "Away" : participant.status === "busy" ? "Busy" : participant.lastSeen ? `Last seen ${formatTimestamp(participant.lastSeen)}` : "Offline" })] })] }, participant.id)) })] })] })] }) }); }; export { GlassChat, GlassChat as default }; //# sourceMappingURL=GlassChat.js.map