import React, {
forwardRef,
useEffect,
useState,
type ReactElement,
} from 'react'
import {
AvatarV2Props,
AvatarV2Size,
AvatarV2Shape,
AvatarV2Status,
AvatarV2StatusPosition,
AvatarV2Variant,
} from './avatarV2.types'
import { useResponsiveTokens } from '../../hooks/useResponsiveTokens'
import { AvatarV2TokensType } from './avatarV2.tokens'
import Block from '../Primitives/Block/Block'
import Text from '../Text/Text'
import { Skeleton } from '../Skeleton'
import { filterBlockedProps } from '../../utils/prop-helpers'
import { VisuallyHidden } from '../../utils/accessibility'
import {
getColorFromText,
getAccessibleLabel,
getAriaLiveValue,
getStatusPositionStyles,
renderFallbackContent,
createAvatarKeyboardHandler,
isInteractive,
} from './avatarV2.utils'
const AvatarImage = ({
src,
alt,
onError,
onLoad,
}: {
src: string
alt: string
onError: () => void
onLoad?: () => void
}) => {
return (
)
}
const AvatarFallback = ({
children,
backgroundColor,
textColor,
}: {
children: React.ReactNode
backgroundColor?: string
textColor?: string
}) => {
return (
{children}
)
}
const StatusIndicator = ({
status,
position,
size,
shape,
tokens,
}: {
status: AvatarV2Status
position: AvatarV2StatusPosition
size: AvatarV2Size
shape: AvatarV2Shape
tokens: AvatarV2TokensType
}) => {
if (status === AvatarV2Status.NONE) {
return null
}
const positionStyles = getStatusPositionStyles(
position,
size,
shape,
tokens
)
return (
)
}
const SlotContainer = ({
slot,
position,
tokens,
}: {
slot?: ReactElement
position: 'leading' | 'trailing'
tokens: AvatarV2TokensType
}) => {
if (!slot) return null
return (
{slot}
)
}
const AvatarV2 = forwardRef(
(
{
src,
alt = '',
fallbackText,
size = AvatarV2Size.MD,
shape = AvatarV2Shape.CIRCULAR,
status = { type: AvatarV2Status.NONE },
leftSlot,
rightSlot,
skeleton,
backgroundColor,
disabled,
onImageError,
onImageLoad,
width,
height,
...rest
},
ref
) => {
const [imageError, setImageError] = useState(false)
useEffect(() => {
setImageError(false)
}, [src])
const tokens = useResponsiveTokens('AVATARV2')
const hasImage = src && !imageError
const shouldShowSkeleton = skeleton?.show
const statusType = status?.type || AvatarV2Status.NONE
const statusPosition =
status?.position || AvatarV2StatusPosition.TOP_RIGHT
const isInteractiveMode = isInteractive(rest.onClick)
const accessibleLabel = getAccessibleLabel(
alt,
fallbackText,
statusType
)
const ariaLiveValue = getAriaLiveValue(statusType)
const fallbackContent = renderFallbackContent(fallbackText, alt)
const fallbackColor = backgroundColor || getColorFromText(alt)
const avatarVariant = hasImage
? AvatarV2Variant.IMAGE
: AvatarV2Variant.TEXT
const containerWidth = width || tokens.container.width[size]
const containerHeight = height || tokens.container.height[size]
const handleImageError = () => {
setImageError(true)
onImageError?.(new Error('Failed to load avatar image'))
}
const handleImageLoad = () => {
onImageLoad?.()
}
const keyboardHandler = createAvatarKeyboardHandler(rest.onClick)
const filteredProps = filterBlockedProps(rest)
const renderAvatarContent = () => {
return (
{!shouldShowSkeleton && (
)}
{shouldShowSkeleton ? (
) : hasImage ? (
) : (
{typeof fallbackContent === 'string' ? (
{fallbackContent}
) : (
fallbackContent
)}
)}
{accessibleLabel}
)
}
if (leftSlot || rightSlot) {
return (
{renderAvatarContent()}
)
}
return renderAvatarContent()
}
)
AvatarV2.displayName = 'AvatarV2'
export default AvatarV2