All files / packages/components/src avatar.web.tsx

95% Statements 19/20
81.81% Branches 9/11
100% Functions 10/10
95% Lines 19/20

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108                        268x 36441x 6894x 34478x 13792x 6896x 856x 856x                                 38324x                   48638x 48638x 48638x 3015x     3015x       48638x                                                                     268x 19058x 19058x                         268x  
import React from "react";
 
import {
	AvatarGroup as AvatarGroupRaw,
	Avatar as AvatarRaw,
	useAvatarGroupContext,
} from "@heroui/avatar";
 
import { BeamAvatar } from "~components/beam-avatar";
import { cn } from "~components/utils";
 
const avatarComponents: React.ComponentProps<typeof BeamAvatar>["components"] =
	{
		Svg: (props) => <svg {...props} />,
		Mask: (props) => <mask {...props} />,
		Rect: (props) => <rect {...props} />,
		G: (props) => <g {...props} />,
		Path: (props) => <path {...props} />,
		Filter: (props) => <filter {...props} />,
		FeColorMatrix: (props) => <feColorMatrix {...props} />,
	};
 
export type Props = {
	size?: "sm" | "md" | "lg";
	className?: string;
	dimmed?: boolean;
	fallback?: React.ReactNode;
	image?: {
		url: string;
		alt: string;
	};
	hashId?: string;
	onPress?: () => void;
	testID?: string;
};
 
export const useAvatarProps = ({
	className,
	dimmed,
	fallback,
	image,
	hashId,
	onPress,
	testID = "user-avatar",
	...props
}: Props): React.ComponentProps<typeof AvatarRaw> => {
	const ref = React.useRef<HTMLSpanElement>(null);
	const [actualSize, setActualSize] = React.useState(0);
	React.useEffect(() => {
		Iif (!ref.current) {
			return;
		}
		setActualSize(Math.max(ref.current.offsetHeight, ref.current.offsetWidth));
		// We calculate actual size depending on the prop
		// oxlint-disable-next-line react/exhaustive-effect-dependencies
	}, [props.size]);
	return {
		...props,
		ref,
		// oxlint-disable-next-line typescript/strict-boolean-expressions
		fallback: fallback || (
			<BeamAvatar
				size={actualSize}
				name={hashId ?? "unknown"}
				components={avatarComponents}
				dimmed={dimmed}
			/>
		),
		imgProps: image
			? {
					alt: image.alt,
					width: actualSize,
					height: actualSize,
				}
			: undefined,
		src: image?.url,
		radius: "full",
		classNames: {
			fallback: "size-full",
			base: cn(
				// The hover=true is needed to remove default translation of avatars in avatar group
				"shrink-0 bg-transparent data-[hover=true]:translate-x-0",
				dimmed ? "grayscale" : undefined,
				className,
			),
		},
		onClick: onPress,
		"data-testid": testID,
	};
};
 
export const Avatar: React.FC<Props> = (props) => {
	const avatarGroupContext = useAvatarGroupContext();
	return <AvatarRaw {...useAvatarProps({ ...avatarGroupContext, ...props })} />;
};
 
export type GroupContext = {
	size?: Props["size"];
};
 
export type GroupProps = GroupContext &
	React.PropsWithChildren<{
		max?: number;
		className?: string;
	}>;
 
export const AvatarGroup: React.FC<GroupProps> = AvatarGroupRaw;