()
| 28 | // ─── TypingIndicator ────────────────────────────────────────────────────────── |
| 29 | |
| 30 | export function TypingIndicator() { |
| 31 | const ctx = useCollaborationContextOptional(); |
| 32 | if (!ctx) return null; |
| 33 | |
| 34 | const { typingUsers } = ctx; |
| 35 | if (typingUsers.length === 0) return null; |
| 36 | |
| 37 | let label: string; |
| 38 | if (typingUsers.length === 1) { |
| 39 | label = `${typingUsers[0].name} is typing`; |
| 40 | } else if (typingUsers.length === 2) { |
| 41 | label = `${typingUsers[0].name} and ${typingUsers[1].name} are typing`; |
| 42 | } else { |
| 43 | label = `${typingUsers.length} people are typing`; |
| 44 | } |
| 45 | |
| 46 | return ( |
| 47 | <AnimatePresence> |
| 48 | <motion.div |
| 49 | key="typing-indicator" |
| 50 | initial={{ opacity: 0, y: 4 }} |
| 51 | animate={{ opacity: 1, y: 0 }} |
| 52 | exit={{ opacity: 0, y: 4 }} |
| 53 | transition={{ duration: 0.15 }} |
| 54 | className="flex items-center gap-1.5 px-4 pb-1 text-xs text-surface-400" |
| 55 | > |
| 56 | {/* Colored dots for each typing user */} |
| 57 | <span className="flex -space-x-1"> |
| 58 | {typingUsers.slice(0, 3).map((u) => ( |
| 59 | <span |
| 60 | key={u.id} |
| 61 | className="w-4 h-4 rounded-full border border-surface-900 flex items-center justify-center text-[8px] font-bold text-white" |
| 62 | style={{ backgroundColor: u.color }} |
| 63 | > |
| 64 | {u.name[0].toUpperCase()} |
| 65 | </span> |
| 66 | ))} |
| 67 | </span> |
| 68 | <span>{label}</span> |
| 69 | <Dots /> |
| 70 | </motion.div> |
| 71 | </AnimatePresence> |
| 72 | ); |
| 73 | } |
nothing calls this directly
no test coverage detected