import { forwardRef, useMemo } from 'react'; import type { ImageSourcePropType } from 'react-native'; import Animated from 'react-native-reanimated'; import { HeroText } from '../../helpers/components'; import { AnimationSettingsProvider } from '../../helpers/contexts/animation-settings-context'; import { useThemeColor } from '../../helpers/theme'; import { childrenToString } from '../../helpers/utils'; import * as AvatarPrimitives from '../../primitives/avatar'; import { useAvatarFallbackAnimation, useAvatarImageAnimation, useAvatarRootAnimation, } from './avatar.animation'; import { AVATAR_DEFAULT_ICON_SIZE, AVATAR_DISPLAY_NAME, } from './avatar.constants'; import { AvatarProvider, useInnerAvatarContext } from './avatar.context'; import avatarStyles, { styleSheet } from './avatar.styles'; import type { AvatarColor, AvatarFallbackProps, AvatarFallbackRef, AvatarImageProps, AvatarImageRef, AvatarRootProps, AvatarRootRef, AvatarSize, } from './avatar.types'; import type { PersonIconProps } from './person-icon'; import { PersonIcon } from './person-icon'; const AnimatedFallback = Animated.createAnimatedComponent( AvatarPrimitives.Fallback ); /** * Hook to access Avatar primitive root context * Provides access to avatar status and other root-level state */ const useAvatar = AvatarPrimitives.useRootContext; // -------------------------------------------------- const AvatarRoot = forwardRef((props, ref) => { const { children, size = 'md', variant = 'default', color = 'accent', className, style, animation, ...restProps } = props; const tvStyles = avatarStyles.root({ variant, size, color, className, }); const { isAllAnimationsDisabled } = useAvatarRootAnimation({ animation, }); const contextValue = useMemo( () => ({ size, color, }), [size, color] ); const animationSettingsContextValue = useMemo( () => ({ isAllAnimationsDisabled, }), [isAllAnimationsDisabled] ); return ( {children} ); }); // -------------------------------------------------- const AvatarImage = forwardRef( (props, ref) => { const { className, style: styleProp, source, asChild, ...restProps } = props; const animation = asChild ? undefined : 'animation' in props ? props.animation : undefined; const isAnimatedStyleActive = asChild ? true : 'isAnimatedStyleActive' in props ? (props.isAnimatedStyleActive ?? true) : true; const { rImageStyle } = useAvatarImageAnimation({ animation, }); const imageClassName = avatarStyles.image({ className, }); const imageStyle = isAnimatedStyleActive ? [rImageStyle, styleProp] : styleProp; if (asChild) { return ( ); } return ( ); } ); // -------------------------------------------------- const DefaultFallbackIcon: React.FC<{ sizeVariant: AvatarSize; colorVariant: AvatarColor; iconProps?: PersonIconProps; }> = ({ sizeVariant, colorVariant, iconProps }) => { const [ themeColorDefaultForeground, themeColorAccent, themeColorSuccess, themeColorWarning, themeColorDanger, ] = useThemeColor([ 'default-foreground', 'accent', 'success', 'warning', 'danger', ]); const iconSize = iconProps?.size ?? AVATAR_DEFAULT_ICON_SIZE[sizeVariant]; const defaultIconColorMap: Record = { default: themeColorDefaultForeground, accent: themeColorAccent, success: themeColorSuccess, warning: themeColorWarning, danger: themeColorDanger, }; const iconColor = iconProps?.color ?? defaultIconColorMap[colorVariant]; return ; }; // -------------------------------------------------- const AvatarFallback = forwardRef( (props, ref) => { const { size, color: contextColor } = useInnerAvatarContext(); const { children, color: colorProp, className, classNames, textProps, iconProps, style, delayMs, animation, ...restProps } = props; const stringifiedChildren = childrenToString(children); const color = colorProp ?? contextColor; const { container, text } = avatarStyles.fallback({ size, color, }); const tvContainerStyles = container({ className: [className, classNames?.container], }); const tvTextStyles = text({ className: [classNames?.text, textProps?.className], }); const { entering } = useAvatarFallbackAnimation({ animation, delayMs, }); return ( {children ? ( stringifiedChildren ? ( {stringifiedChildren} ) : ( children ) ) : ( )} ); } ); // -------------------------------------------------- AvatarRoot.displayName = AVATAR_DISPLAY_NAME.ROOT; AvatarImage.displayName = AVATAR_DISPLAY_NAME.IMAGE; AvatarFallback.displayName = AVATAR_DISPLAY_NAME.FALLBACK; /** * Compound Avatar component with sub-components * * @component Avatar - Main container that manages avatar display state. * Provides color and size context to child components. * * @component Avatar.Image - Optional image component that displays the avatar image. * Handles loading states and errors automatically. * * @component Avatar.Fallback - Optional fallback component shown when image fails to load. * Supports text initials or custom content with optional delay. * * Props flow from Avatar to sub-components via context (size, color). * Fallback can override color with its own prop. * * @see Full documentation: https://heroui.com/components/avatar */ const Avatar = Object.assign(AvatarRoot, { /** @optional Displays the avatar image with loading state management */ Image: AvatarImage, /** @optional Shows fallback content when image is unavailable */ Fallback: AvatarFallback, }); export default Avatar; export { Avatar, useAvatar };