import React, { useRef } from "react"; import { ImageProps } from "../image/image"; import Image from "../image/image"; import { ImageSourcePropType, ViewStyle } from "react-native"; import Box from "../../atoms/box/box"; import Text from "../../atoms/text/text"; import { useAccessibleColor } from "../../../hooks/useAccessibleColor"; import { MoleculeComponentProps, PaletteColors, } from "../../../theme/src/types"; import { pearl } from "../../../pearl"; import { useAvatarGroup } from "./useAvatarGroup"; import { AvatarAtoms } from "./avatar.config"; /** * A function that generates initials from a name * @param name The name to generate initials from * @returns The initials generated from the name */ const defaultGetInitials = (name: string) => name .split(" ") .map((el) => el[0]) .join("") .toUpperCase(); const DEFAULT_AVATAR_BACKGROUND = [ "#c8e6d0ff", "#e0ecbaff", "#fbb991ff", "#f27e81ff", "#8bc7d3ff", "#bcdfd8ff", "#fae3d9ff", "#f8b4b9ff", "#c2b0d7ff", "#eeef99ff", "#a5d7c4ff", "#fef5aeff", "#f7bdd6ff", "#d6a5cbff", "#c38788ff", "#FA8072", "#FFD700", "#FFA500", ]; export type BaseAvatarProps = Omit< ImageProps, "source" | "size" | "variant" > & { /** The name of the person in the avatar. If 'src' is not loaded or is missing, the 'name' will be used to create the initials */ name?: string; /** The source of the Avatar image. This can be a url, or a local image */ src?: string | number; /** A method to specify how initials are generated from a user's name */ getInitials?(name: string): string; /** A custom list of background colors to choose the avatar background from. * * @default * [ "#c8e6d0ff", "#e0ecbaff", "#fbb991ff", "#f27e81ff", "#8bc7d3ff", "#bcdfd8ff", "#fae3d9ff", "#f8b4b9ff", "#c2b0d7ff", "#eeef99ff", "#a5d7c4ff", "#fef5aeff", "#f7bdd6ff", "#d6a5cbff", "#c38788ff", "#FA8072", "#FFD700", "#FFA500", ] */ customBackgroundColors?: string[]; }; /** * CustomAvatar is a component that renders an avatar image, initials or a fallback component. * It uses the name, src, getInitials, atoms, and fallbackComponent props from the parent Avatar component. * If an image source is provided, it will render the image. * If no image source is provided but a name is provided, it will render the initials of the name. * If neither image source nor name is provided, it will render the fallbackComponent. * If no fallbackComponent is provided, it will render nothing. */ const CustomAvatar = React.memo( React.forwardRef( ( { atoms }: MoleculeComponentProps<"Avatar", BaseAvatarProps, AvatarAtoms>, ref: any ) => { const { name, src, getInitials, fallbackComponent, ...otherBoxProps } = atoms.box; // Function to pick a random color from the namedColors object const pickRandomColor = () => DEFAULT_AVATAR_BACKGROUND[ Math.floor(Math.random() * DEFAULT_AVATAR_BACKGROUND.length) ]; // Store the random color in a ref to prevent it from changing on re-renders const randomColor = useRef(pickRandomColor()).current; // Determine the text color based on the background color to ensure accessibility const accessibleTextColor = useAccessibleColor( typeof otherBoxProps.backgroundColor === "string" ? (otherBoxProps.backgroundColor as PaletteColors) : typeof otherBoxProps.bgColor === "string" ? (otherBoxProps.bgColor as PaletteColors) : typeof (otherBoxProps.style as ViewStyle)?.backgroundColor === "string" ? ((otherBoxProps.style as ViewStyle) ?.backgroundColor as PaletteColors) : randomColor, { light: "neutral.50", dark: "neutral.900", } ); // Function to render the fallback component (initials or fallbackComponent prop) const renderFallBack = () => { if (name) { const initialComputeFunction = getInitials ?? defaultGetInitials; const nameInitials = initialComputeFunction(name); return ( {nameInitials} ); } if (fallbackComponent) return React.cloneElement(fallbackComponent); return null; }; // Determine the final source of the image const finalSource = src && typeof src === "string" ? ({ uri: src } as ImageSourcePropType) : (src as ImageSourcePropType); // If a source is provided, render the image if (finalSource) { return ( ); } // If no source is provided, render the fallback return ( {renderFallBack()} ); } ) ); const StyledAvatar = pearl( CustomAvatar, { componentName: "Avatar", type: "molecule", animatable: true, }, undefined, { partForOverridenStyleProps: "box", partForOverridenNativeProps: "box", partForOverridenAnimationProps: "box", } ); /** The Avatar component is used to represent a user, and displays the profile picture, initials or fallback icon. */ const Avatar = React.forwardRef( ( { children, ...rest }: Omit< MoleculeComponentProps<"Avatar", BaseAvatarProps, AvatarAtoms>, "atoms" > & { atoms?: Partial; }, ref: any ) => { let { size, variant } = useAvatarGroup(); // Overwrite props from avatar group rest.size = size ?? rest.size; rest.variant = variant ?? rest.variant; // Render a fallback return ( {children} ); } ); export type AvatarProps = React.ComponentProps; Avatar.displayName = "Avatar"; export default Avatar;