feat(web): add persistent beam avatars
Staging Backend Quality Gate / validate (push) Successful in 13m34s
Staging Backend Quality Gate / publish (push) Has been cancelled

This commit is contained in:
Jesse
2026-08-07 12:07:57 +08:00
parent e7482c49c6
commit 49f6edd7fb
11 changed files with 420 additions and 2 deletions
+9 -2
View File
@@ -30,12 +30,15 @@ import {
type SidebarSession,
type SidebarSessionControls,
} from "@/components/sidebar-session-row";
import { UserAvatar } from "@/components/user-avatar";
import type { BeamAvatar } from "@/lib/beam-avatar";
export type SidebarAccount = {
name: string;
email: string;
credits: number;
initial: string;
avatar: BeamAvatar | null;
};
export type AppSidebarProps = {
@@ -183,7 +186,9 @@ export function AppSidebar({
ref={accountTriggerRef}
type="button"
>
<span className="profile-initial" aria-hidden="true">{account.initial}</span>
{account.avatar
? <UserAvatar avatar={account.avatar} size={32} className="profile-avatar" />
: <span className="profile-initial" aria-hidden="true">{account.initial}</span>}
{showExpandedContent ? <span><b>{account.name}</b></span> : null}
{showExpandedContent ? <ChevronRight className={accountMenuOpen ? "chevron is-open" : "chevron"} aria-hidden="true" /> : null}
</Menu.Trigger>
@@ -196,7 +201,9 @@ export function AppSidebar({
>
<Menu.Popup className="account-menu-popup" aria-label="账户菜单">
<div className="account-menu-identity">
<span className="account-menu-avatar" aria-hidden="true">{account.initial}</span>
{account.avatar
? <UserAvatar avatar={account.avatar} size={40} className="account-menu-avatar" />
: <span className="account-menu-avatar" aria-hidden="true">{account.initial}</span>}
<span><b>{account.name}</b><small>{account.email}</small></span>
</div>
<Menu.Item className="account-menu-item" onClick={onOpenProfile}>
+32
View File
@@ -0,0 +1,32 @@
"use client";
import Avatar from "boring-avatars";
import { beamAvatarPalettes, type BeamAvatar } from "@/lib/beam-avatar";
export type UserAvatarProps = {
avatar: BeamAvatar;
size: number;
className?: string;
label?: string;
};
export function UserAvatar({ avatar, size, className, label }: UserAvatarProps) {
const palette = beamAvatarPalettes[avatar.palette] ?? beamAvatarPalettes[0];
return (
<span
className={className ? `user-avatar ${className}` : "user-avatar"}
style={{ width: size, height: size }}
role={label ? "img" : undefined}
aria-label={label}
aria-hidden={label ? undefined : true}
>
<Avatar
name={avatar.seed}
variant="beam"
colors={[...palette.colors]}
size={size}
/>
</span>
);
}