Show avatar decoration in user profile modal
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:
@@ -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,27 +48,29 @@ export function UserHero({ userId, avatarUrl, presence }: UserHeroProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={css.UserHeroAvatarContainer}>
|
<div className={css.UserHeroAvatarContainer}>
|
||||||
<AvatarPresence
|
<AvatarDecoration userId={userId} inset={20}>
|
||||||
className={css.UserAvatarContainer}
|
<AvatarPresence
|
||||||
badge={
|
className={css.UserAvatarContainer}
|
||||||
presence && <PresenceBadge presence={presence.presence} status={presence.status} />
|
badge={
|
||||||
}
|
presence && <PresenceBadge presence={presence.presence} status={presence.status} />
|
||||||
>
|
}
|
||||||
<Avatar
|
|
||||||
as={avatarUrl ? 'button' : 'div'}
|
|
||||||
onClick={avatarUrl ? () => setViewAvatar(avatarUrl) : undefined}
|
|
||||||
className={css.UserHeroAvatar}
|
|
||||||
size="500"
|
|
||||||
>
|
>
|
||||||
<UserAvatar
|
<Avatar
|
||||||
className={css.UserHeroAvatarImg}
|
as={avatarUrl ? 'button' : 'div'}
|
||||||
userId={userId}
|
onClick={avatarUrl ? () => setViewAvatar(avatarUrl) : undefined}
|
||||||
src={avatarUrl}
|
className={css.UserHeroAvatar}
|
||||||
alt={userId}
|
size="500"
|
||||||
renderFallback={() => <Icon size="500" src={Icons.User} filled />}
|
>
|
||||||
/>
|
<UserAvatar
|
||||||
</Avatar>
|
className={css.UserHeroAvatarImg}
|
||||||
</AvatarPresence>
|
userId={userId}
|
||||||
|
src={avatarUrl}
|
||||||
|
alt={userId}
|
||||||
|
renderFallback={() => <Icon size="500" src={Icons.User} filled />}
|
||||||
|
/>
|
||||||
|
</Avatar>
|
||||||
|
</AvatarPresence>
|
||||||
|
</AvatarDecoration>
|
||||||
{viewAvatar && (
|
{viewAvatar && (
|
||||||
<Overlay open backdrop={<OverlayBackdrop />}>
|
<Overlay open backdrop={<OverlayBackdrop />}>
|
||||||
<OverlayCenter>
|
<OverlayCenter>
|
||||||
|
|||||||
Reference in New Issue
Block a user