import Image from "next/image"; import { cn } from "@/lib/utils"; /** * UserAvatar — the one avatar component: shows the user's image, or their * initials on a tinted disc when there's no image. Used everywhere a user * thumbnail appears (notification feed, comments, follower lists, and the * profile header's large zoomable hero). * * Sizing is class-driven (`className`) rather than a fixed px, so callers get * Tailwind's responsive sizes — e.g. `size-10` for a list row or * `size-24 sm:size-28` for the profile header. `textClassName` scales the * initials to match. When `onClick` is set, an image avatar becomes a * focusable button (used by the profile header to open its zoom preview); * `interactive` adds a hover-ring affordance. */ export function UserAvatar({ src, name, className, textClassName = "text-sm", onClick, ariaLabel, interactive = false, }: { src?: string | null; name: string; /** Box size + extras. Defaults to a 40px (size-10) circle. */ className?: string; /** Initials font size — match it to the box. Defaults to text-sm. */ textClassName?: string; /** When set, an image avatar renders as a focusable button (e.g. to open a * zoom preview or navigate). Initials stay non-interactive. */ onClick?: () => void; ariaLabel?: string; /** Add a hover-ring affordance to an image avatar. */ interactive?: boolean; }) { const size = className ?? "size-10"; if (src) { const imgRing = cn( "rounded-full object-cover ring-1 ring-foreground/10", interactive && "hover:ring-foreground/30 transition", ); // width/height satisfy next/image; the rendered size comes from the CSS // size class (which overrides the intrinsic attributes). if (onClick) { return ( ); } return ( {name} ); } const initials = name .replace(/^@/, "") .split(/\s+/) .map((p) => p[0]) .slice(0, 2) .join("") .toUpperCase() || "?"; return (
{initials}
); }