66 lines
2.3 KiB
TypeScript
66 lines
2.3 KiB
TypeScript
// Desktop / tablet right-side media pane. Renders the same
|
||
// `MediaViewerBody` the mobile bottom-up horseshoe shows, but as a
|
||
// flex sibling next to the chat column instead of a slide-up rail.
|
||
//
|
||
// Mounted in `Room.tsx` only on non-mobile screens; mobile uses
|
||
// `MobileMediaViewerHorseshoe` instead.
|
||
|
||
import React, { useRef } from 'react';
|
||
import { useAtomValue } from 'jotai';
|
||
import { useTranslation } from 'react-i18next';
|
||
import FocusTrap from 'focus-trap-react';
|
||
import { mediaViewerAtom } from '../../state/mediaViewer';
|
||
import { useCloseMediaViewer } from '../../state/hooks/mediaViewer';
|
||
import { stopPropagation } from '../../utils/keyboard';
|
||
import { MediaViewerBody } from './MediaViewerBody';
|
||
import * as css from './RoomViewMediaSidePanel.css';
|
||
|
||
export function RoomViewMediaSidePanel() {
|
||
const { t } = useTranslation();
|
||
const entry = useAtomValue(mediaViewerAtom);
|
||
const close = useCloseMediaViewer();
|
||
|
||
const open = !!entry;
|
||
|
||
const entryRef = useRef(entry);
|
||
entryRef.current = entry;
|
||
|
||
if (!open || !entry) return null;
|
||
|
||
return (
|
||
<FocusTrap
|
||
focusTrapOptions={{
|
||
// Move focus into the trap on open so screen readers
|
||
// announce the dialog and keyboard arrow keys / Esc reach
|
||
// the viewer body. Falls back to first focusable element
|
||
// (the close button in the body header) without us pinning
|
||
// a specific ref — simpler than threading one down.
|
||
initialFocus: undefined,
|
||
// Outside clicks pass through to chat (`allowOutsideClick`)
|
||
// but don't close — clicking around in chat to scroll /
|
||
// compose shouldn't dismiss the viewer. Explicit close via
|
||
// the body's × button or `Esc`. Mirror of the profile side
|
||
// pane's stance.
|
||
clickOutsideDeactivates: false,
|
||
allowOutsideClick: () => true,
|
||
escapeDeactivates: stopPropagation,
|
||
onDeactivate: () => {
|
||
if (entryRef.current) close();
|
||
},
|
||
checkCanFocusTrap: () => Promise.resolve(),
|
||
}}
|
||
active={open}
|
||
>
|
||
<div
|
||
className={css.panel}
|
||
role="dialog"
|
||
aria-modal="true"
|
||
aria-label={entry.body || t('MediaViewer.title', 'Media viewer')}
|
||
>
|
||
<div className={css.body}>
|
||
<MediaViewerBody entry={entry} requestClose={close} />
|
||
</div>
|
||
</div>
|
||
</FocusTrap>
|
||
);
|
||
}
|