vojo/src/app/features/room/RoomViewMembersSidePanel.tsx

88 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Desktop / tablet right-side members pane. Same horseshoe void seam
// idiom as `RoomViewProfileSidePanel` (TL + BL carved across the 12 px
// gap painted by the parent flex row in `Room.tsx`). Mobile uses the
// top horseshoe `RoomViewMembersPanel` instead.
//
// Renders the same `MembersList` body the mobile horseshoe shows, so
// the two surfaces stay visually identical content-wise — only the
// chrome differs.
import React, { useEffect, useRef } from 'react';
import { useAtomValue } from 'jotai';
import { Box, Icon, IconButton, Icons, Text } from 'folds';
import FocusTrap from 'focus-trap-react';
import { useTranslation } from 'react-i18next';
import { roomMembersSheetAtom } from '../../state/roomMembersSheet';
import { useCloseRoomMembersSheet } from '../../state/hooks/roomMembersSheet';
import { useRoom } from '../../hooks/useRoom';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useRoomMembers } from '../../hooks/useRoomMembers';
import { usePowerLevels } from '../../hooks/usePowerLevels';
import { useRoomCreators } from '../../hooks/useRoomCreators';
import { useGetMemberPowerTag } from '../../hooks/useMemberPowerTag';
import { MembersList } from '../../components/members-list/MembersList';
import { stopPropagation } from '../../utils/keyboard';
import { PageHeader } from '../../components/page';
import { ContainerColor } from '../../styles/ContainerColor.css';
import * as css from './RoomViewMembersSidePanel.css';
export function RoomViewMembersSidePanel() {
const { t } = useTranslation();
const sheetState = useAtomValue(roomMembersSheetAtom);
const close = useCloseRoomMembersSheet();
const room = useRoom();
const mx = useMatrixClient();
const members = useRoomMembers(mx, room.roomId);
const powerLevels = usePowerLevels(room);
const creators = useRoomCreators(room);
const getPowerTag = useGetMemberPowerTag(room, creators, powerLevels);
const open = !!sheetState;
// Close sheet when the room changes — atom is global state.
useEffect(() => () => close(), [room.roomId, close]);
const sheetStateRef = useRef(sheetState);
sheetStateRef.current = sheetState;
if (!open) return null;
return (
<FocusTrap
focusTrapOptions={{
initialFocus: false,
// Outside clicks pass through to the chat but don't close the
// pane — mirrors `RoomViewProfileSidePanel`. Explicit close is
// via `×` or `Esc`.
clickOutsideDeactivates: false,
allowOutsideClick: () => true,
escapeDeactivates: stopPropagation,
onDeactivate: () => {
if (sheetStateRef.current) close();
},
checkCanFocusTrap: () => Promise.resolve(),
}}
active={open}
>
<div className={css.panel}>
<PageHeader className={`${ContainerColor({ variant: 'Surface' })} ${css.header}`}>
<Box grow="Yes" alignItems="Center">
<Text size="H4" truncate>
{t('Room.members_pane_title')}
</Text>
</Box>
<Box shrink="No" alignItems="Center">
<IconButton fill="None" onClick={close} aria-label={t('Room.close')}>
<Icon size="400" src={Icons.Cross} />
</IconButton>
</Box>
</PageHeader>
<div className={css.body}>
<MembersList room={room} members={members} getPowerTag={getPowerTag} />
</div>
</div>
</FocusTrap>
);
}