import clsx from 'clsx'; import type { CSSProperties } from 'react'; import styles from './Avatar.module.css'; export interface AvatarProps { src?: string | null; alt?: string; size?: number; fallback?: string; status?: 'online' | 'idle' | 'dnd' | 'offline'; className?: string; style?: CSSProperties; onClick?: () => void; } function getInitials(name: string): string { return name .split(/\s+/) .map((w) => w[0]) .join('') .toUpperCase() .slice(0, 2); } function stringToColor(str: string): string { let hash = 0; for (let i = 0; i < str.length; i++) { hash = str.charCodeAt(i) + ((hash << 5) - hash); } const hue = Math.abs(hash % 360); return `hsl(${hue}, 60%, 50%)`; } function getStatusGeometry(avatarSize: number): { dotSize: number; borderWidth: number; offset: number } { if (avatarSize <= 16) return { dotSize: 8, borderWidth: 0, offset: -1 }; if (avatarSize <= 20) return { dotSize: 8, borderWidth: 0, offset: -1 }; if (avatarSize <= 24) return { dotSize: 10, borderWidth: 2, offset: -2 }; if (avatarSize <= 32) return { dotSize: 10, borderWidth: 3, offset: -3 }; if (avatarSize <= 36) return { dotSize: 10, borderWidth: 3, offset: -3 }; if (avatarSize <= 40) return { dotSize: 12, borderWidth: 3, offset: -3 }; if (avatarSize <= 48) return { dotSize: 14, borderWidth: 3, offset: -3 }; if (avatarSize <= 56) return { dotSize: 16, borderWidth: 3, offset: -3 }; if (avatarSize <= 80) return { dotSize: 16, borderWidth: 6, offset: -4 }; return { dotSize: 24, borderWidth: 8, offset: -6 }; } export function Avatar({ src, alt = '', size = 40, fallback, status, className, style, onClick }: AvatarProps) { const initials = fallback ? getInitials(fallback) : alt ? getInitials(alt) : '?'; const bgColor = stringToColor(fallback || alt || '?'); const geo = getStatusGeometry(size); return (
{src ? ( {alt} ) : (
{initials}
)} {status && (
)}
); }