import * as React from 'react'; import { useAvatarContext } from './avatar'; import { Facehash, type FacehashProps } from './facehash'; const WHITESPACE_REGEX = /\s+/; export type AvatarFallbackProps = Omit< React.HTMLAttributes, 'children' > & { /** * The name to derive initials and Facehash from. */ name?: string; /** * Delay in milliseconds before showing the fallback. * Useful to prevent flashing when images load quickly. * @default 0 */ delayMs?: number; /** * Custom children to render instead of initials or Facehash. */ children?: React.ReactNode; /** * Use the Facehash component as fallback instead of initials. * @default true */ facehash?: boolean; /** * Use Tailwind group-hover for hover detection. * When true, hover effect triggers when a parent with "group" class is hovered. * @default false */ groupHover?: boolean; /** * Props to pass to the Facehash component. */ facehashProps?: Omit; }; /** * Extracts initials from a name string. */ function getInitials(name: string): string { const parts = name.trim().split(WHITESPACE_REGEX); if (parts.length === 0) { return ''; } if (parts.length === 1) { return parts[0]?.charAt(0).toUpperCase() || ''; } const firstInitial = parts[0]?.charAt(0) || ''; const lastInitial = parts.at(-1)?.charAt(0) || ''; return (firstInitial + lastInitial).toUpperCase(); } /** * Fallback component that displays when the image fails to load. * Uses Facehash by default, can show initials or custom content. */ export const AvatarFallback = React.forwardRef< HTMLSpanElement, AvatarFallbackProps >( ( { name = '', delayMs = 0, children, facehash = true, groupHover = false, facehashProps, className, style, ...props }, ref, ) => { const { imageLoadingStatus } = useAvatarContext(); const [canRender, setCanRender] = React.useState(delayMs === 0); React.useEffect(() => { if (delayMs > 0) { const timerId = window.setTimeout(() => setCanRender(true), delayMs); return () => window.clearTimeout(timerId); } }, [delayMs]); const initials = React.useMemo(() => getInitials(name), [name]); const shouldRender = canRender && imageLoadingStatus !== 'loaded' && imageLoadingStatus !== 'loading'; if (!shouldRender) { return null; } // Custom children take precedence if (children) { return ( {children} ); } // Facehash mode (default) if (facehash) { return ( } name={name || '?'} size="100%" groupHover={groupHover} {...facehashProps} style={{ ...style, }} {...props} /> ); } // Initials mode return ( {initials} ); }, ); AvatarFallback.displayName = 'AvatarFallback';