import { useState } from "react";
import { useReducedMotion } from "~/hooks/use-reduced-motion";
import { useKnownAvatarKey } from "~/lib/avatar-version";
import { lavaLampDataUrl } from "~/lib/lava-lamp";
import { cn } from "~/utils/cn";
const AVATAR_PIXELS = { sm: 40, md: 64, lg: 160 } as const;
const AVATAR_SIZES = { sm: "size-5", md: "size-8", lg: "size-20" } as const;
type Props = { name: string; size?: keyof typeof AVATAR_PIXELS; src?: string | null };
/**
* Shows an account's uploaded picture (looked up by name unless `src` is given) and falls back to
* a lava lamp generated from the name when there is none or it fails to load.
*/
export function Avatar({ name, size = "md", src }: Props) {
const [failedUrl, setFailedUrl] = useState<string | null>(null);
const [loadedUrl, setLoadedUrl] = useState<string | null>(null);
const knownKey = useKnownAvatarKey(name);
const sizeClass = AVATAR_SIZES[size];
const byName = knownKey
? `/avatars/${knownKey}?s=${AVATAR_PIXELS[size]}`
: `/avatars/u/${encodeURIComponent(name)}?s=${AVATAR_PIXELS[size]}`;
const url = src === undefined ? byName : src;
if (url && url !== failedUrl) {
// A blank tile holds the spot until the picture loads, so a 404 never flashes a broken image before the lava lamp.
const loaded = loadedUrl === url;
return (
<span className={cn("relative inline-block shrink-0 rounded-md bg-kumo-base", sizeClass)}>
<img
src={url}
alt=""
loading="lazy"
onLoad={() => setLoadedUrl(url)}
onError={() => setFailedUrl(url)}
ref={(node) => {
if (!node?.complete) return;
if (node.naturalWidth === 0) setFailedUrl(url);
else setLoadedUrl(url);
}}