import { forwardRef, useEffect, useState } from 'react' import { type AvatarProps, AvatarSize, AvatarShape, AvatarOnlinePosition, } from './types' import { StyledAvatarImage } from './StyledAvatar' import { getInitialsFromText, getColorFromText } from './avatarUtils' import Block from '../Primitives/Block/Block' import { AvatarTokensType } from './avatar.tokens' import { useResponsiveTokens } from '../../hooks/useResponsiveTokens' import { useBreakpoints } from '../../hooks/useBreakPoints' import { BREAKPOINTS } from '../../breakpoints/breakPoints' import { Skeleton } from '../Skeleton' const Avatar = forwardRef( ( { src, alt = '', fallback, size = AvatarSize.MD, shape = AvatarShape.CIRCULAR, online = false, onlinePosition = AvatarOnlinePosition.TOP, leadingSlot, trailingSlot, skeleton, onClick, ...props }, ref ) => { const [imageError, setImageError] = useState(false) useEffect(() => { setImageError(false) }, [src]) const hasImage = src && !imageError const shouldShowSkeleton = skeleton?.show const variant = hasImage ? 'withImage' : 'withoutImage' const tokens = useResponsiveTokens('AVATAR') const { breakPointLabel } = useBreakpoints(BREAKPOINTS) const isSmallScreen = breakPointLabel === 'sm' const textForColor = typeof fallback === 'string' ? fallback : alt const initialsColor = getColorFromText(textForColor) const renderFallback = () => { if (fallback) { return typeof fallback === 'string' ? fallback.substring(0, 2) : fallback } return getInitialsFromText(alt) } const getAccessibleLabel = () => { const name = alt || (typeof fallback === 'string' ? fallback : 'Avatar') const statusText = online ? ', online' : '' return `${name}${statusText}` } const isInteractive = onClick !== undefined const accessibleLabel = getAccessibleLabel() const INDICATOR_POSITIONS = { sm: { [AvatarShape.CIRCULAR]: { [AvatarSize.SM]: { top: '-2px', right: '-2px', bottom: '0px', }, [AvatarSize.REGULAR]: { top: '-3px', right: '-3px', bottom: '0px', }, [AvatarSize.MD]: { top: '-3px', right: '-4px', bottom: '0px', }, [AvatarSize.LG]: { top: '0.2px', right: '-3px', bottom: '0px', }, [AvatarSize.XL]: { top: '1.167px', right: '-3px', bottom: '0px', }, }, [AvatarShape.ROUNDED]: { [AvatarSize.SM]: { top: '-2px', right: '-3px', bottom: '-1px', }, [AvatarSize.REGULAR]: { top: '-2px', right: '-3px', bottom: '-1px', }, [AvatarSize.MD]: { top: '-3.667px', right: '-5px', bottom: '-3px', }, [AvatarSize.LG]: { top: '-3.111px', right: '-8.222px', bottom: '-3px', }, [AvatarSize.XL]: { top: '-3.111px', right: '-10.222px', bottom: '-4px', }, }, }, lg: { [AvatarShape.CIRCULAR]: { [AvatarSize.SM]: { top: '-2px', right: '-2px', bottom: '0px', }, [AvatarSize.REGULAR]: { top: '-3px', right: '-3px', bottom: '0px', }, [AvatarSize.MD]: { top: '-3px', right: '-3px', bottom: '0px', }, [AvatarSize.LG]: { top: '0px', right: '2px', bottom: '0px', }, [AvatarSize.XL]: { top: '8px', right: '3px', bottom: '0px', }, }, [AvatarShape.ROUNDED]: { [AvatarSize.SM]: { top: '-3px', right: '-3px', bottom: '-3px', }, [AvatarSize.REGULAR]: { top: '-3px', right: '-3px', bottom: '-3px', }, [AvatarSize.MD]: { top: '-3px', right: '-3px', bottom: '-3px', }, [AvatarSize.LG]: { top: '-5px', right: '-5px', bottom: '-5px', }, [AvatarSize.XL]: { top: '-8px', right: '-8px', bottom: '-8px', }, }, }, } const screenSize = isSmallScreen ? 'sm' : 'lg' const dynamicTopPosition = INDICATOR_POSITIONS[screenSize][shape as AvatarShape][ size as AvatarSize ]['top'] || '0' const dynamicRightPosition = INDICATOR_POSITIONS[screenSize][shape as AvatarShape][ size as AvatarSize ]['right'] || '0' const dynamicBottomPosition = INDICATOR_POSITIONS[screenSize][shape as AvatarShape][ size as AvatarSize ]['bottom'] || '0' const renderContent = () => ( ) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault() e.stopPropagation() // Create a synthetic mouse event for onClick handler const syntheticEvent = { ...e, currentTarget: e.currentTarget, target: e.target, button: 0, buttons: 0, clientX: 0, clientY: 0, pageX: 0, pageY: 0, screenX: 0, screenY: 0, relatedTarget: null, movementX: 0, movementY: 0, nativeEvent: e.nativeEvent, bubbles: true, cancelable: true, defaultPrevented: false, eventPhase: 0, isTrusted: false, timeStamp: Date.now(), type: 'click', } as unknown as React.MouseEvent onClick(syntheticEvent) } } : undefined } {...props} data-status={online ? 'online' : 'offline'} > {online && !shouldShowSkeleton && ( ) if (leadingSlot || trailingSlot) { return ( {leadingSlot && ( {leadingSlot} )} {renderContent()} {trailingSlot && ( {trailingSlot} )} ) } // Otherwise just return the avatar return renderContent() } ) Avatar.displayName = 'Avatar' export default Avatar