diff --git a/src/frontend/src/features/rooms/livekit/components/ScreenShareFullscreenButton.tsx b/src/frontend/src/features/rooms/livekit/components/ScreenShareFullscreenButton.tsx new file mode 100644 index 00000000..6718eada --- /dev/null +++ b/src/frontend/src/features/rooms/livekit/components/ScreenShareFullscreenButton.tsx @@ -0,0 +1,76 @@ +import { Button } from '@/primitives' +import { RiCollapseDiagonalLine, RiExpandDiagonalLine } from '@remixicon/react' +import { memo, useCallback, useEffect, useRef, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useScreenReaderAnnounce } from '@/hooks/useScreenReaderAnnounce' + +// Keeps the fullscreen state here rather than on the toolbar, so entering or +// leaving fullscreen does not re-render the zoom controls. +export const ScreenShareFullscreenButton = memo( + ({ + containerRef, + }: { + containerRef: React.RefObject + }) => { + const { t } = useTranslation('rooms', { keyPrefix: 'screenShareZoom' }) + const announce = useScreenReaderAnnounce() + + const [isFullscreen, setIsFullscreen] = useState(false) + // Tracks whether this tile's container triggered fullscreen (vs another share's). + const wasThisTileFullscreen = useRef(false) + + // Covers Esc and browser UI exits, not just this button. + // Only this tile's instance announces to avoid duplicates with multiple shares. + useEffect(() => { + const onChange = () => { + const isThisTileFullscreen = + document.fullscreenElement === containerRef.current + setIsFullscreen(isThisTileFullscreen) + + if (isThisTileFullscreen) { + wasThisTileFullscreen.current = true + announce(t('fullScreenEntered'), 'assertive') + } else if (wasThisTileFullscreen.current) { + wasThisTileFullscreen.current = false + announce(t('fullScreenExited'), 'assertive') + } + } + document.addEventListener('fullscreenchange', onChange) + return () => document.removeEventListener('fullscreenchange', onChange) + }, [announce, t, containerRef]) + + const toggleFullScreen = useCallback(async () => { + try { + if (document.fullscreenElement === containerRef.current) { + await document.exitFullscreen() + } else { + // Tile container so zoom controls stay visible in fullscreen. + await containerRef.current?.requestFullscreen() + } + } catch (error) { + console.error('Error toggling fullscreen:', error) + } + }, [containerRef]) + + if (!document.fullscreenEnabled) return null + + return ( + + ) + } +) + +ScreenShareFullscreenButton.displayName = 'ScreenShareFullscreenButton' diff --git a/src/frontend/src/features/rooms/livekit/components/ScreenShareZoomControls.tsx b/src/frontend/src/features/rooms/livekit/components/ScreenShareZoomControls.tsx index b0dcfa41..53c14213 100644 --- a/src/frontend/src/features/rooms/livekit/components/ScreenShareZoomControls.tsx +++ b/src/frontend/src/features/rooms/livekit/components/ScreenShareZoomControls.tsx @@ -3,13 +3,11 @@ import { HStack } from '@/styled-system/jsx' import { Button } from '@/primitives' import { RiFullscreenExitLine, - RiFullscreenLine, RiZoomInLine, RiZoomOutLine, } from '@remixicon/react' import { useTranslation } from 'react-i18next' -import { useCallback, useEffect, useRef, useState } from 'react' -import { useScreenReaderAnnounce } from '@/hooks/useScreenReaderAnnounce' +import { ScreenShareFullscreenButton } from './ScreenShareFullscreenButton' interface ScreenShareZoomControlsProps { containerRef: React.RefObject @@ -33,46 +31,6 @@ export const ScreenShareZoomControls = ({ onResetZoom, }: ScreenShareZoomControlsProps) => { const { t } = useTranslation('rooms', { keyPrefix: 'screenShareZoom' }) - const announce = useScreenReaderAnnounce() - - const [isFullscreen, setIsFullscreen] = useState(false) - const wasOwnFullscreen = useRef(false) - const [isFullscreenAvailable] = useState( - () => typeof document !== 'undefined' && document.fullscreenEnabled - ) - - // Covers Esc and browser UI exits, not just the toolbar button. - // Only this tile's instance announces to avoid duplicates with multiple shares. - useEffect(() => { - const onChange = () => { - const isThisTileFullscreen = - document.fullscreenElement === containerRef.current - setIsFullscreen(isThisTileFullscreen) - - if (isThisTileFullscreen) { - wasOwnFullscreen.current = true - announce(t('fullScreenEntered'), 'assertive') - } else if (wasOwnFullscreen.current) { - wasOwnFullscreen.current = false - announce(t('fullScreenExited'), 'assertive') - } - } - document.addEventListener('fullscreenchange', onChange) - return () => document.removeEventListener('fullscreenchange', onChange) - }, [announce, t, containerRef]) - - const toggleFullScreen = useCallback(async () => { - try { - if (document.fullscreenElement === containerRef.current) { - await document.exitFullscreen() - } else { - // Tile container so zoom controls stay visible in fullscreen. - await containerRef.current?.requestFullscreen() - } - } catch (error) { - console.error('Error toggling fullscreen:', error) - } - }, [containerRef]) return (
- {isFullscreenAvailable && ( - - )} +
)