Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | 1x 1x 46x 46x 46x 46x 46x 46x 46x 1x 46x 46x 46x 46x 46x 46x 46x 46x 46x 40x 46x 46x 46x 46x 46x 46x 46x 46x 46x 46x 46x | import { useState, useEffect } from 'react';
import './UserAvatar.css';
export type UserAvatarVariant = 'accent' | 'muted';
export interface UserAvatarProps {
username: string;
displayName?: string | null;
avatarUrl?: string | null;
className?: string;
size?: 'sm' | 'md';
variant?: UserAvatarVariant;
}
function initialLetter(username: string, displayName?: string | null): string {
const from = displayName?.trim() || username;
return from.charAt(0).toUpperCase() || '?';
}
function hasUsableSrc(src: string | null | undefined): boolean {
return typeof src === 'string' && src.trim().length > 0;
}
export function UserAvatar({
username,
displayName,
avatarUrl,
className = '',
size = 'sm',
variant = 'muted',
}: UserAvatarProps) {
const [failed, setFailed] = useState(false);
useEffect(() => {
setFailed(false);
}, [avatarUrl]);
const showImg = hasUsableSrc(avatarUrl) && !failed;
const letter = initialLetter(username, displayName);
const sizeClass = size === 'md' ? 'user-avatar--md' : 'user-avatar--sm';
const variantClass = variant === 'accent' ? 'user-avatar--accent' : 'user-avatar--muted';
return (
<div className={`user-avatar ${sizeClass} ${variantClass} ${className}`.trim()}>
{showImg ? (
<img
src={avatarUrl!.trim()}
alt=""
className="user-avatar-img"
onError={() => setFailed(true)}
/>
) : (
<span className="user-avatar-fallback">{letter}</span>
)}
</div>
);
}
|