Add option to view user avatar (#2462)

This commit is contained in:
Ajay Bura 2025-08-24 18:06:45 +05:30 committed by GitHub
parent 4056cbb11c
commit 23aa5c6f94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 66 additions and 7 deletions

View file

@ -1,15 +1,17 @@
import { AvatarFallback, AvatarImage, color } from 'folds'; import { AvatarFallback, AvatarImage, color } from 'folds';
import React, { ReactEventHandler, ReactNode, useState } from 'react'; import React, { ReactEventHandler, ReactNode, useState } from 'react';
import classNames from 'classnames';
import * as css from './UserAvatar.css'; import * as css from './UserAvatar.css';
import colorMXID from '../../../util/colorMXID'; import colorMXID from '../../../util/colorMXID';
type UserAvatarProps = { type UserAvatarProps = {
className?: string;
userId: string; userId: string;
src?: string; src?: string;
alt?: string; alt?: string;
renderFallback: () => ReactNode; renderFallback: () => ReactNode;
}; };
export function UserAvatar({ userId, src, alt, renderFallback }: UserAvatarProps) { export function UserAvatar({ className, userId, src, alt, renderFallback }: UserAvatarProps) {
const [error, setError] = useState(false); const [error, setError] = useState(false);
const handleLoad: ReactEventHandler<HTMLImageElement> = (evt) => { const handleLoad: ReactEventHandler<HTMLImageElement> = (evt) => {
@ -20,7 +22,7 @@ export function UserAvatar({ userId, src, alt, renderFallback }: UserAvatarProps
return ( return (
<AvatarFallback <AvatarFallback
style={{ backgroundColor: colorMXID(userId), color: color.Surface.Container }} style={{ backgroundColor: colorMXID(userId), color: color.Surface.Container }}
className={css.UserAvatar} className={classNames(css.UserAvatar, className)}
> >
{renderFallback()} {renderFallback()}
</AvatarFallback> </AvatarFallback>
@ -29,7 +31,7 @@ export function UserAvatar({ userId, src, alt, renderFallback }: UserAvatarProps
return ( return (
<AvatarImage <AvatarImage
className={css.UserAvatar} className={classNames(css.UserAvatar, className)}
src={src} src={src}
alt={alt} alt={alt}
onError={() => setError(true)} onError={() => setError(true)}

View file

@ -1,6 +1,17 @@
import React from 'react'; import React, { useState } from 'react';
import { Avatar, Box, Icon, Icons, Text } from 'folds'; import {
Avatar,
Box,
Icon,
Icons,
Modal,
Overlay,
OverlayBackdrop,
OverlayCenter,
Text,
} from 'folds';
import classNames from 'classnames'; import classNames from 'classnames';
import FocusTrap from 'focus-trap-react';
import * as css from './styles.css'; import * as css from './styles.css';
import { UserAvatar } from '../user-avatar'; import { UserAvatar } from '../user-avatar';
import colorMXID from '../../../util/colorMXID'; import colorMXID from '../../../util/colorMXID';
@ -8,6 +19,8 @@ import { getMxIdLocalPart } from '../../utils/matrix';
import { BreakWord, LineClamp3 } from '../../styles/Text.css'; import { BreakWord, 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 { ImageViewer } from '../image-viewer';
import { stopPropagation } from '../../utils/keyboard';
type UserHeroProps = { type UserHeroProps = {
userId: string; userId: string;
@ -15,6 +28,8 @@ type UserHeroProps = {
presence?: UserPresence; presence?: UserPresence;
}; };
export function UserHero({ userId, avatarUrl, presence }: UserHeroProps) { export function UserHero({ userId, avatarUrl, presence }: UserHeroProps) {
const [viewAvatar, setViewAvatar] = useState<string>();
return ( return (
<Box direction="Column" className={css.UserHero}> <Box direction="Column" className={css.UserHero}>
<div <div
@ -24,7 +39,9 @@ export function UserHero({ userId, avatarUrl, presence }: UserHeroProps) {
filter: avatarUrl ? undefined : 'brightness(50%)', filter: avatarUrl ? undefined : 'brightness(50%)',
}} }}
> >
{avatarUrl && <img className={css.UserHeroCover} src={avatarUrl} alt={userId} />} {avatarUrl && (
<img className={css.UserHeroCover} src={avatarUrl} alt={userId} draggable="false" />
)}
</div> </div>
<div className={css.UserHeroAvatarContainer}> <div className={css.UserHeroAvatarContainer}>
<AvatarPresence <AvatarPresence
@ -33,8 +50,14 @@ export function UserHero({ userId, avatarUrl, presence }: UserHeroProps) {
presence && <PresenceBadge presence={presence.presence} status={presence.status} /> presence && <PresenceBadge presence={presence.presence} status={presence.status} />
} }
> >
<Avatar className={css.UserHeroAvatar} size="500"> <Avatar
as={avatarUrl ? 'button' : 'div'}
onClick={avatarUrl ? () => setViewAvatar(avatarUrl) : undefined}
className={css.UserHeroAvatar}
size="500"
>
<UserAvatar <UserAvatar
className={css.UserHeroAvatarImg}
userId={userId} userId={userId}
src={avatarUrl} src={avatarUrl}
alt={userId} alt={userId}
@ -42,6 +65,28 @@ export function UserHero({ userId, avatarUrl, presence }: UserHeroProps) {
/> />
</Avatar> </Avatar>
</AvatarPresence> </AvatarPresence>
{viewAvatar && (
<Overlay open backdrop={<OverlayBackdrop />}>
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
onDeactivate: () => setViewAvatar(undefined),
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Modal size="500" onContextMenu={(evt: any) => evt.stopPropagation()}>
<ImageViewer
src={viewAvatar}
alt={userId}
requestClose={() => setViewAvatar(undefined)}
/>
</Modal>
</FocusTrap>
</OverlayCenter>
</Overlay>
)}
</div> </div>
</Box> </Box>
); );

View file

@ -39,4 +39,16 @@ export const UserAvatarContainer = style({
}); });
export const UserHeroAvatar = style({ export const UserHeroAvatar = style({
outline: `${config.borderWidth.B600} solid ${color.Surface.Container}`, outline: `${config.borderWidth.B600} solid ${color.Surface.Container}`,
selectors: {
'button&': {
cursor: 'pointer',
},
},
});
export const UserHeroAvatarImg = style({
selectors: {
[`button${UserHeroAvatar}:hover &`]: {
filter: 'brightness(0.5)',
},
},
}); });