diff --git a/public/locales/en.json b/public/locales/en.json index d4920808..3f5a2d9a 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -426,6 +426,7 @@ "Room": { "drag_to_close": "Drag up to close", "collapse_avatar": "Collapse avatar", + "expand_avatar": "Open avatar", "new_messages": "New Messages", "jump_to_unread": "Jump to Unread", "mark_as_read": "Mark as Read", diff --git a/public/locales/ru.json b/public/locales/ru.json index d626802a..3939bd0b 100644 --- a/public/locales/ru.json +++ b/public/locales/ru.json @@ -428,6 +428,7 @@ "Room": { "drag_to_close": "Потянуть вверх чтобы закрыть", "collapse_avatar": "Свернуть аватар", + "expand_avatar": "Развернуть аватар", "new_messages": "Новые сообщения", "jump_to_unread": "К непрочитанным", "mark_as_read": "Отметить прочитанным", diff --git a/src/app/components/user-profile/UserHero.tsx b/src/app/components/user-profile/UserHero.tsx index f10d1f73..899ecd1e 100644 --- a/src/app/components/user-profile/UserHero.tsx +++ b/src/app/components/user-profile/UserHero.tsx @@ -75,6 +75,12 @@ type UserHeroProps = { presence?: UserPresence; encrypted?: boolean; onAvatarClick?: () => void; + // Desktop side-pane only. When true the avatar stretches to fill + // the card width as a square; the rest of the hero (name / presence + // / e2ee chip) stays in flow below. The online dot is suppressed in + // this mode — it makes no sense as a tiny corner glyph on a + // ~340px avatar tile. + avatarExpanded?: boolean; }; export function UserHero({ @@ -84,6 +90,7 @@ export function UserHero({ presence, encrypted, onAvatarClick, + avatarExpanded, }: UserHeroProps) { const { t } = useTranslation(); const username = getMxIdLocalPart(userId); @@ -124,7 +131,10 @@ export function UserHero({ const fallbackBg = `linear-gradient(135deg, ${fallbackColor}, ${shadeHex(fallbackColor, -22)})`; const avatarNode = ( - + )} /> - {online && } + {online && !avatarExpanded && } ); @@ -145,8 +155,14 @@ export function UserHero({ diff --git a/src/app/components/user-profile/UserRoomProfile.tsx b/src/app/components/user-profile/UserRoomProfile.tsx index 1fff848d..02a95ca0 100644 --- a/src/app/components/user-profile/UserRoomProfile.tsx +++ b/src/app/components/user-profile/UserRoomProfile.tsx @@ -45,9 +45,19 @@ import * as css from './styles.css'; type UserRoomProfileProps = { userId: string; onAvatarClick?: () => void; + // Desktop side-pane only — forwarded straight to `UserHero` so the + // hero avatar can render in its inline-expanded mode while the + // rest of the card stays in flow. Mobile leaves this `undefined` + // and keeps using the full-rail avatar swap inside + // `RoomViewProfilePanel`. + avatarExpanded?: boolean; }; -export function UserRoomProfile({ userId, onAvatarClick }: UserRoomProfileProps) { +export function UserRoomProfile({ + userId, + onAvatarClick, + avatarExpanded, +}: UserRoomProfileProps) { const mx = useMatrixClient(); const useAuthentication = useMediaAuthentication(); const navigate = useNavigate(); @@ -137,6 +147,7 @@ export function UserRoomProfile({ userId, onAvatarClick }: UserRoomProfileProps) presence={presence} encrypted={encrypted} onAvatarClick={onAvatarClick} + avatarExpanded={avatarExpanded} />
diff --git a/src/app/components/user-profile/styles.css.ts b/src/app/components/user-profile/styles.css.ts index 1b74ccc2..3d74b481 100644 --- a/src/app/components/user-profile/styles.css.ts +++ b/src/app/components/user-profile/styles.css.ts @@ -86,6 +86,29 @@ export const HeroAvatarButton = style({ cursor: 'pointer', }); +// Desktop side-pane expanded avatar. Clicking the small hero avatar +// in the right-side profile pane flips this on: the wrapper +// stretches to the card width, squares up via `aspect-ratio` so the +// server-side 1:1 avatar shows full-bleed without cropping, and the +// rest of the card stays in flow below. Replaces the legacy full-pane +// avatar swap which hid the name / presence / info rows entirely. +// `border-radius: !important` is mandatory: the global override in +// `components/user-avatar/UserAvatar.css.ts` uses `!important` to +// force every avatar parent to 50%, so without matching specificity +// the expanded square would render as a huge circle. +export const HeroAvatarExpanded = style({ + width: '100%', + height: 'auto', + aspectRatio: '1 / 1', + borderRadius: `${toRem(16)} !important`, + boxShadow: 'none', +}); + +export const HeroAvatarButtonExpanded = style({ + display: 'flex', + width: '100%', +}); + export const HeroIdentity = style({ width: '100%', }); diff --git a/src/app/features/room/RoomViewProfileSidePanel.css.ts b/src/app/features/room/RoomViewProfileSidePanel.css.ts index 33a79bfc..96c6578f 100644 --- a/src/app/features/room/RoomViewProfileSidePanel.css.ts +++ b/src/app/features/room/RoomViewProfileSidePanel.css.ts @@ -39,32 +39,3 @@ export const scrollWrap = style({ }, }); -export const avatarFullView = style({ - flex: 1, - minHeight: 0, - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - cursor: 'pointer', - background: 'transparent', - border: 'none', - padding: 0, -}); - -export const avatarFullImage = style({ - width: '100%', - height: '100%', - objectFit: 'cover', -}); - -export const avatarFullFallback = style({ - width: '100%', - height: '100%', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - fontSize: toRem(96), - fontWeight: 600, - color: color.Surface.Container, - textTransform: 'uppercase', -}); diff --git a/src/app/features/room/RoomViewProfileSidePanel.tsx b/src/app/features/room/RoomViewProfileSidePanel.tsx index ea8adca8..59e0c536 100644 --- a/src/app/features/room/RoomViewProfileSidePanel.tsx +++ b/src/app/features/room/RoomViewProfileSidePanel.tsx @@ -14,13 +14,8 @@ import { useTranslation } from 'react-i18next'; import { userRoomProfileAtom } from '../../state/userRoomProfile'; import { useCloseUserRoomProfile } from '../../state/hooks/userRoomProfile'; import { useRoom } from '../../hooks/useRoom'; -import { useMatrixClient } from '../../hooks/useMatrixClient'; -import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; -import { getMemberAvatarMxc } from '../../utils/room'; -import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix'; import { UserRoomProfile } from '../../components/user-profile/UserRoomProfile'; import { stopPropagation } from '../../utils/keyboard'; -import colorMXID from '../../../util/colorMXID'; import { PageHeader } from '../../components/page'; import { ContainerColor } from '../../styles/ContainerColor.css'; import * as css from './RoomViewProfileSidePanel.css'; @@ -30,11 +25,16 @@ export function RoomViewProfileSidePanel() { const profileState = useAtomValue(userRoomProfileAtom); const close = useCloseUserRoomProfile(); const room = useRoom(); - const mx = useMatrixClient(); - const useAuthentication = useMediaAuthentication(); const open = !!profileState; - const [avatarMode, setAvatarMode] = useState(false); + // Inline avatar-expanded mode: the hero avatar stretches to fill + // the card width as a square, pushing the rest of the card down in + // the scroll container. Replaces the legacy full-pane image swap + // which hid the name / presence / info rows. Mobile keeps the + // full-rail swap inside `RoomViewProfilePanel` — the rail there is + // short enough that pushing content down would just create a + // scroll well. + const [avatarExpanded, setAvatarExpanded] = useState(false); // Close profile when the room changes — atom is global state and // would otherwise carry the previous room's userId into this room @@ -44,20 +44,13 @@ export function RoomViewProfileSidePanel() { // Reset avatar-zoom mode whenever the rendered user changes. useEffect(() => { - setAvatarMode(false); + setAvatarExpanded(false); }, [profileState?.userId]); const profileStateRef = useRef(profileState); profileStateRef.current = profileState; const renderUserId = profileState?.userId; - const renderUserAvatarMxc = renderUserId - ? getMemberAvatarMxc(room, renderUserId) - : undefined; - const renderUserAvatarUrl = - (renderUserAvatarMxc && - mxcUrlToHttp(mx, renderUserAvatarMxc, useAuthentication, 720, 720, 'scale')) ?? - undefined; if (!open || !renderUserId) return null; @@ -96,39 +89,15 @@ export function RoomViewProfileSidePanel() { - {avatarMode ? ( - - ) : ( -
-
- setAvatarMode(true)} - /> -
+
+
+ setAvatarExpanded((prev) => !prev)} + avatarExpanded={avatarExpanded} + />
- )} +
);