// Desktop / tablet right-side profile pane. Renders the same // `UserRoomProfile` content the mobile top horseshoe shows, but as a // flex sibling next to the chat column instead of a slide-down rail. // // Mounted in `Room.tsx` only when the screen size is not Mobile — // the mobile branch keeps using the top horseshoe via // `RoomViewProfilePanel`. import React, { useEffect, useRef, useState } from 'react'; import { useAtomValue } from 'jotai'; import { Box, Icon, IconButton, Icons, Text, config } from 'folds'; import FocusTrap from 'focus-trap-react'; import { useTranslation } from 'react-i18next'; import { userRoomProfileAtom } from '../../state/userRoomProfile'; import { useCloseUserRoomProfile } from '../../state/hooks/userRoomProfile'; import { useOpenRoomMembersSheet } from '../../state/hooks/roomMembersSheet'; import { useIsOneOnOne, useRoom } from '../../hooks/useRoom'; import { UserRoomProfile } from '../../components/user-profile/UserRoomProfile'; import { stopPropagation } from '../../utils/keyboard'; import { PageHeader } from '../../components/page'; import { ContainerColor } from '../../styles/ContainerColor.css'; import * as css from './RoomViewProfileSidePanel.css'; export function RoomViewProfileSidePanel() { const { t } = useTranslation(); const profileState = useAtomValue(userRoomProfileAtom); const close = useCloseUserRoomProfile(); const room = useRoom(); const isOneOnOne = useIsOneOnOne(); const openMembersSheet = useOpenRoomMembersSheet(); const open = !!profileState; // 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 // where chrome would render against the wrong member / power // levels. useEffect(() => () => close(), [room.roomId, close]); // Reset avatar-zoom mode whenever the rendered user changes. useEffect(() => { setAvatarExpanded(false); }, [profileState?.userId]); const profileStateRef = useRef(profileState); profileStateRef.current = profileState; const renderUserId = profileState?.userId; if (!open || !renderUserId) return null; return ( true, escapeDeactivates: stopPropagation, onDeactivate: () => { if (profileStateRef.current) close(); }, checkCanFocusTrap: () => Promise.resolve(), }} active={open} >
{/* Use the same `PageHeader` (folds `Header size="600"`) the chat header uses, so the bottom rule lines up with the adjacent `RoomViewHeaderDm` row to the pixel. */} {/* Group rooms: leading back arrow returns to the members sheet (the room card) the profile replaced in this pane — the open hook clears the profile atom, so the panes swap in place (redesign item 15). 1:1 has no members sheet → no arrow. Call rooms neither: Room.tsx mounts the members side pane only when `!callView`, so opening the sheet atom from inside a call would close the profile into nothing AND leak a stale atom that auto-opens the pane in the next group room. */} {!isOneOnOne && !room.isCallRoom() && ( openMembersSheet(room.roomId)} aria-label={t('User.back')} > )} {t('User.profile_title')}
setAvatarExpanded((prev) => !prev)} avatarExpanded={avatarExpanded} />
); }