All files / components/ui avatar.tsx

91.66% Statements 11/12
90.9% Branches 10/11
50% Functions 1/2
91.66% Lines 11/12

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 8912x 12x       12x   12x                 12x                         52x 34x   34x 18x                                   16x 16x                                                                        
import { useState } from "react";
import { cn } from "@/utils/class-name";
import {
  APP_ASSISTANT_AVATAR_ALT,
  APP_ASSISTANT_AVATAR_SRC,
} from "@/constants/app";
 
const SIZE = {
  sm: { box: "h-[30px] w-[30px]", text: "text-[10px] tracking-wide" },
  md: { box: "h-9 w-9",           text: "text-[11px] tracking-wide" },
  lg: { box: "h-14 w-14",         text: "text-sm tracking-wider"    },
} as const;
 
export type AvatarSize = keyof typeof SIZE;
 
const BASE =
  "relative shrink-0 select-none overflow-hidden rounded-full bg-cover bg-center";
 
type AvatarProps =
  | { variant: "assistant"; size?: AvatarSize; className?: string }
  | {
      variant: "user";
      src?: string;
      alt: string;
      initials: string;
      size?: AvatarSize;
      className?: string;
    };
 
export function Avatar(props: AvatarProps) {
  const [imgError, setImgError] = useState(false);
 
  if (props.variant === "assistant") {
    const { size = "sm", className } = props;
    return (
      <div
        role="img"
        aria-label={APP_ASSISTANT_AVATAR_ALT}
        className={cn(
          BASE,
          SIZE[size].box,
          "border border-white/20 shadow-avatar-assistant light:border-slate-200",
          className,
        )}
        style={{
          backgroundImage: `url(${APP_ASSISTANT_AVATAR_SRC}), linear-gradient(135deg,#7c3aed 0%,#06b6d4 100%)`,
        }}
      />
    );
  }
 
  const { src, alt, initials, size = "sm", className } = props;
  const hasImage = Boolean(src?.trim()) && !imgError;
 
  return (
    <div
      role="img"
      aria-label={alt}
      className={cn(
        BASE,
        SIZE[size].box,
        "bg-avatar-user",
        "ring-[1.5px] ring-indigo-400/35 light:ring-indigo-400/50",
        !hasImage && "shadow-avatar-user",
        hasImage && "shadow-avatar-user-image",
        className,
      )}
    >
      {hasImage ? (
        <img
          src={src}
          alt={alt}
          onError={() => setImgError(true)}
          className="absolute inset-0 h-full w-full object-cover"
        />
      ) : (
        <span
          className={cn(
            "absolute inset-0 flex items-center justify-center font-dm-sans font-semibold text-white/90",
            SIZE[size].text,
          )}
        >
          {initials}
        </span>
      )}
    </div>
  );
}