Show avatar decoration in user profile modal
CI / Build & Quality Checks (push) Successful in 10m29s
Trigger Desktop Build / trigger (push) Successful in 11s

Wraps the profile hero avatar with AvatarDecoration so a user's chosen
decoration ring is visible when viewing their profile panel. Added an
optional `inset` prop to AvatarDecoration so the profile hero can use
a larger bleed (20px) proportional to the bigger avatar size.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-14 16:19:47 -04:00
parent 99e6a456a7
commit 388a934665
2 changed files with 32 additions and 31 deletions
@@ -2,17 +2,15 @@ import React from 'react';
import { useAvatarDecoration } from '../../hooks/useAvatarDecoration'; import { useAvatarDecoration } from '../../hooks/useAvatarDecoration';
import { decorationUrl } from '../../features/lotus/avatarDecorations'; import { decorationUrl } from '../../features/lotus/avatarDecorations';
// How far the decoration image extends beyond the avatar on each side (px). const DEFAULT_INSET = 8;
// The APNG files are 256×256 with a transparent center. At this extension
// the center hole lines up naturally with the avatar beneath it.
const INSET = 8;
type AvatarDecorationProps = { type AvatarDecorationProps = {
userId: string; userId: string;
children: React.ReactNode; children: React.ReactNode;
inset?: number;
}; };
export function AvatarDecoration({ userId, children }: AvatarDecorationProps) { export function AvatarDecoration({ userId, children, inset = DEFAULT_INSET }: AvatarDecorationProps) {
const slug = useAvatarDecoration(userId); const slug = useAvatarDecoration(userId);
if (!slug) { if (!slug) {
@@ -32,12 +30,12 @@ export function AvatarDecoration({ userId, children }: AvatarDecorationProps) {
src={decorationUrl(slug)} src={decorationUrl(slug)}
style={{ style={{
position: 'absolute', position: 'absolute',
top: -INSET, top: -inset,
left: -INSET, left: -inset,
right: -INSET, right: -inset,
bottom: -INSET, bottom: -inset,
width: `calc(100% + ${INSET * 2}px)`, width: `calc(100% + ${inset * 2}px)`,
height: `calc(100% + ${INSET * 2}px)`, height: `calc(100% + ${inset * 2}px)`,
pointerEvents: 'none', pointerEvents: 'none',
zIndex: 10, zIndex: 10,
objectFit: 'contain', objectFit: 'contain',
@@ -19,6 +19,7 @@ import { getMxIdLocalPart } from '../../utils/matrix';
import { BreakWord, LineClamp2, LineClamp3 } from '../../styles/Text.css'; import { BreakWord, LineClamp2, LineClamp3 } from '../../styles/Text.css';
import { UserPresence } from '../../hooks/useUserPresence'; import { UserPresence } from '../../hooks/useUserPresence';
import { AvatarPresence, PresenceBadge } from '../presence'; import { AvatarPresence, PresenceBadge } from '../presence';
import { AvatarDecoration } from '../avatar-decoration/AvatarDecoration';
import { ImageViewer } from '../image-viewer'; import { ImageViewer } from '../image-viewer';
import { stopPropagation } from '../../utils/keyboard'; import { stopPropagation } from '../../utils/keyboard';
import { useSetting } from '../../state/hooks/settings'; import { useSetting } from '../../state/hooks/settings';
@@ -47,6 +48,7 @@ export function UserHero({ userId, avatarUrl, presence }: UserHeroProps) {
)} )}
</div> </div>
<div className={css.UserHeroAvatarContainer}> <div className={css.UserHeroAvatarContainer}>
<AvatarDecoration userId={userId} inset={20}>
<AvatarPresence <AvatarPresence
className={css.UserAvatarContainer} className={css.UserAvatarContainer}
badge={ badge={
@@ -68,6 +70,7 @@ export function UserHero({ userId, avatarUrl, presence }: UserHeroProps) {
/> />
</Avatar> </Avatar>
</AvatarPresence> </AvatarPresence>
</AvatarDecoration>
{viewAvatar && ( {viewAvatar && (
<Overlay open backdrop={<OverlayBackdrop />}> <Overlay open backdrop={<OverlayBackdrop />}>
<OverlayCenter> <OverlayCenter>