File
Blob: src/client/components/presence/avatar-stack.tsx
| 1 | import type { Awareness } from "y-protocols/awareness"; |
| 2 | import { awarenessColor, useAwareness, type AwarenessState } from "@/client/hooks/use-sync"; |
| 3 | import { useCollabIdentity } from "@/client/lib/collab-identity"; |
| 4 | |
| 5 | interface AvatarStackProps { |
| 6 | awareness: Awareness | null; |
| 7 | localClientId: number | null; |
| 8 | } |
| 9 | |
| 10 | interface RemoteUser { |
| 11 | clientId: number; |
| 12 | color: string; |
| 13 | name: string; |
| 14 | avatar_url: string | null; |
| 15 | } |
| 16 | |
| 17 | export function AvatarStack({ awareness, localClientId }: AvatarStackProps) { |
| 18 | const { resolveIdentity } = useCollabIdentity(); |
| 19 | const states = useAwareness(awareness); |
| 20 | |
| 21 | const remoteUsers: RemoteUser[] = []; |
| 22 | states.forEach((state: AwarenessState, clientId: number) => { |
| 23 | if (clientId === localClientId || !state.user) return; |
| 24 | const userId = state.user.userId ?? null; |
| 25 | const identity = resolveIdentity(userId, clientId); |
| 26 | remoteUsers.push({ |
| 27 | clientId, |
| 28 | color: awarenessColor(userId, clientId), |
| 29 | name: identity.name, |
| 30 | avatar_url: identity.avatar_url, |
| 31 | }); |
| 32 | }); |
| 33 | |
| 34 | if (remoteUsers.length === 0) return null; |
| 35 | |
| 36 | return ( |
| 37 | <div className="flex -space-x-1.5"> |
| 38 | {remoteUsers.slice(0, 5).map((u) => ( |
| 39 | <div |
| 40 | key={u.clientId} |
| 41 | className="flex h-6 w-6 items-center justify-center overflow-hidden rounded-full border-2 border-canvas text-[10px] font-medium text-white" |
| 42 | style={{ backgroundColor: u.color }} |
| 43 | title={u.name} |
| 44 | > |
| 45 | {u.avatar_url ? ( |
| 46 | <img src={u.avatar_url} alt={u.name} className="h-full w-full object-cover" /> |
| 47 | ) : ( |
| 48 | <span style={{ textShadow: "0 0 2px rgba(0,0,0,0.5)" }}>{u.name.charAt(0).toUpperCase()}</span> |
| 49 | )} |
| 50 | </div> |
| 51 | ))} |
| 52 | {remoteUsers.length > 5 && ( |
| 53 | <div className="flex h-6 w-6 items-center justify-center rounded-full border-2 border-canvas bg-zinc-700 text-[10px] font-medium text-zinc-300"> |
| 54 | +{remoteUsers.length - 5} |
| 55 | </div> |
| 56 | )} |
| 57 | </div> |
| 58 | ); |
| 59 | } |