Share one user avatar between the rail and the profile page

UserAvatar (src/components/general/user-avatar.tsx) replaces the
profile page's ProfileAvatar and the rail's initials chip, so the rail
shows the user's photo too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-19 05:35:42 +01:00
co-authored by Claude Opus 5.5
parent 02ed47dd86
commit b1023ba8ff
4 changed files with 79 additions and 39 deletions
+62
View File
@@ -0,0 +1,62 @@
import Img from "@moduletrace/twui/layout/Img";
import Span from "@moduletrace/twui/layout/Span";
import { twMerge } from "tailwind-merge";
/**
* Round avatar sizes, each with the type scale for its initials.
*/
const UserAvatarSizeClasses = {
sm: "size-6 text-[11px]",
md: "size-8 text-xs",
lg: "size-16 text-lg",
} as const;
type Props = {
src?: string;
/**
* Tried when `src` fails to load, e.g. a full image behind a thumbnail.
*/
img_err_src?: string;
initials: string;
alt?: string;
size?: keyof typeof UserAvatarSizeClasses;
className?: string;
};
/**
* A user's round photo, or their initials on sunken paper when there is none.
*/
export default function UserAvatar({
src,
img_err_src,
initials,
alt = "",
size = "md",
className,
}: Props) {
const base_class = twMerge(
"shrink-0 rounded-full bg-sunken",
UserAvatarSizeClasses[size],
src
? "object-cover"
: "flex items-center justify-center font-bold leading-none text-ink",
className,
);
if (src) {
return (
<Img
src={src}
imgErrSrc={img_err_src}
alt={alt}
className={base_class}
/>
);
}
return (
<Span className={base_class} aria-hidden>
{initials || "?"}
</Span>
);
}