88 lines
3.4 KiB
TypeScript
88 lines
3.4 KiB
TypeScript
// 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>
|
||
);
|
||
}
|