import { Image, Text, View } from 'react-native'; import type { Human } from '@/api/types'; import { useAvatarUrl } from '@/hooks/use-avatar-url'; import { resolveHumanDisplay } from '@/lib/humans'; import { cn } from '@/lib/utils'; type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl'; interface AvatarProps { humanId: string | null | undefined; humans: Human[] | undefined; size?: Size; /** True for online presence — adds a green ring (matches desktop). */ online?: boolean; /** Background ring used to separate stacked avatars from the chrome. */ stackBg?: string; /** Initials shown when the human can't be resolved (e.g. a group stream). */ fallbackInitials?: string; className?: string; } const sizeMap: Record = { xs: { box: 'h-6 w-6', text: 'text-[9px]', ring: 1.5 }, sm: { box: 'h-9 w-9', text: 'text-xs', ring: 2 }, md: { box: 'h-10 w-10', text: 'text-sm', ring: 2 }, lg: { box: 'h-16 w-16', text: 'text-xl', ring: 2.5 }, xl: { box: 'h-24 w-24', text: 'text-3xl', ring: 3 }, }; /** * Human avatar: renders the profile picture when one is set (resolved to a * signed URL via React Query), otherwise initials. Supports an optional online * ring (green) and an outer stack separator ring used to keep overlapping * avatars distinct on busy chrome. Matches desktop's avatar + presence pattern. */ export function Avatar({ humanId, humans, size = 'sm', online = false, stackBg, fallbackInitials, className, }: AvatarProps) { const display = resolveHumanDisplay(humanId, humans); const human = humanId ? humans?.find((h) => h.id === humanId) : undefined; const avatarUrl = useAvatarUrl(human?.avatar_object_id); const dims = sizeMap[size]; const initials = display.exists || fallbackInitials === undefined ? display.initials : fallbackInitials; return ( {avatarUrl ? ( ) : ( {initials} )} ); }