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

66 lines
2.3 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 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>
);
}