Avatar.tsx
1 import { 2 Avatar as MaterialAvatar, 3 AvatarProps as MaterialAvatarProps, 4 Badge, 5 Skeleton, 6 } from '@mui/material'; 7 import { blo } from 'blo'; 8 import { ReactNode, useEffect, useState } from 'react'; 9 10 export enum AvatarSize { 11 XS = 20, 12 SM = 22, 13 MD = 24, 14 LG = 32, 15 XL = 40, 16 } 17 18 export interface AvatarProps extends Omit<MaterialAvatarProps, 'src'> { 19 image?: string; 20 fallbackImage?: string; 21 size?: AvatarSize | number; 22 loading?: boolean; 23 badge?: ReactNode; 24 invisibleBadge?: boolean; 25 } 26 27 export const Avatar: React.FC<AvatarProps> = ({ 28 image, 29 fallbackImage = blo('0x'), 30 size = AvatarSize.MD, 31 sx, 32 loading = false, 33 invisibleBadge = false, 34 badge, 35 ...rest 36 }) => { 37 const [useFallbackImage, setUseFallbackImage] = useState(false); 38 39 useEffect(() => { 40 setUseFallbackImage(false); 41 }, [image]); 42 43 return loading ? ( 44 <Skeleton variant="circular" width={size} height={size} /> 45 ) : ( 46 <Badge 47 overlap="circular" 48 anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }} 49 badgeContent={badge} 50 invisible={invisibleBadge || loading} 51 > 52 <MaterialAvatar 53 src={useFallbackImage || !image ? fallbackImage : image} 54 sx={{ width: size, height: size, border: '1px solid #FAFBFC1F', ...sx }} 55 alt="avatar" 56 imgProps={{ 57 onError: () => setUseFallbackImage(true), 58 }} 59 {...rest} 60 /> 61 </Badge> 62 ); 63 };