feat: enhance user profile customization with avatar border color and gradient options

- Added support for setting avatar border color and gradient in user profile settings.
- Introduced new hooks for managing user profile styles and fetching metadata from avatars.
- Implemented an AngleSelector component for selecting gradient direction.
- Updated RoomNavItem to display parent space information for direct messages.
- Improved caching mechanism for user banners and profile styles.
- Refactored image metadata handling to include new profile style attributes.
This commit is contained in:
2026-03-12 20:50:00 +11:00
parent fc30d81f8f
commit 834de012b4
13 changed files with 911 additions and 90 deletions

View File

@@ -17,7 +17,7 @@ import FocusTrap from 'focus-trap-react';
import * as css from './styles.css';
import { UserAvatar } from '../user-avatar';
import colorMXID from '../../../util/colorMXID';
import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
import { getMxIdLocalPart } from '../../utils/matrix';
import { BreakWord, LineClamp3 } from '../../styles/Text.css';
import { UserPresence } from '../../hooks/useUserPresence';
import { AvatarPresence, PresenceBadge } from '../presence';
@@ -25,8 +25,7 @@ import { ImageViewer } from '../image-viewer';
import { stopPropagation } from '../../utils/keyboard';
import { useOtherUserColor } from '../../hooks/useUserColor';
import { useOtherUserBanner } from '../../hooks/useUserBanner';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { useOtherUserProfileStyle } from '../../hooks/useUserProfileStyle';
type UserHeroProps = {
userId: string;
@@ -35,13 +34,20 @@ type UserHeroProps = {
presence?: UserPresence;
};
export function UserHero({ userId, avatarUrl, avatarMxc, presence }: UserHeroProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const [viewAvatar, setViewAvatar] = useState<string>();
const bannerUrl = useOtherUserBanner(userId, avatarMxc); // Now returns blob URL directly
const profileStyle = useOtherUserProfileStyle(userId, avatarMxc);
// Build gradient CSS if configured
const gradientStyle = profileStyle.gradient
? `linear-gradient(${profileStyle.gradient.direction}, ${profileStyle.gradient.startColor}, ${profileStyle.gradient.stopColor})`
: undefined;
return (
<Box direction="Column" className={css.UserHero}>
<Box
direction="Column"
className={css.UserHero}
>
<div
className={css.UserHeroCoverContainer}
style={{
@@ -57,9 +63,10 @@ export function UserHero({ userId, avatarUrl, avatarMxc, presence }: UserHeroPro
)
)}
</div>
<div className={css.UserHeroAvatarContainer}>
<div className={css.UserHeroAvatarContainer} style={gradientStyle ? { background: gradientStyle } : undefined}>
<AvatarPresence
className={css.UserAvatarContainer}
style={gradientStyle ? { background: gradientStyle } : undefined}
badge={
presence && <PresenceBadge presence={presence.presence} status={presence.status} />
}
@@ -69,6 +76,10 @@ export function UserHero({ userId, avatarUrl, avatarMxc, presence }: UserHeroPro
onClick={avatarUrl ? () => setViewAvatar(avatarUrl) : undefined}
className={css.UserHeroAvatar}
size="500"
style={profileStyle.avatarBorderColor ? {
outline: `${toRem(4)} solid ${profileStyle.avatarBorderColor}`,
outlineOffset: toRem(-1),
} : undefined}
>
<UserAvatar
className={css.UserHeroAvatarImg}