From e3271f6e0fb67b273a11fd0be060a59051c2c3a5 Mon Sep 17 00:00:00 2001 From: Cyril Date: Thu, 9 Jul 2026 16:07:55 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(frontend)=20add=20ScreenShareZoomCont?= =?UTF-8?q?rols=20toolbar=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bottom-right toolbar with zoom, fit-to-window and fullscreen buttons. --- .../components/ScreenShareZoomControls.tsx | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 src/frontend/src/features/rooms/livekit/components/ScreenShareZoomControls.tsx diff --git a/src/frontend/src/features/rooms/livekit/components/ScreenShareZoomControls.tsx b/src/frontend/src/features/rooms/livekit/components/ScreenShareZoomControls.tsx new file mode 100644 index 00000000..7cf62229 --- /dev/null +++ b/src/frontend/src/features/rooms/livekit/components/ScreenShareZoomControls.tsx @@ -0,0 +1,168 @@ +import { css } from '@/styled-system/css' +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, useState } from 'react' +import { useScreenReaderAnnounce } from '@/hooks/useScreenReaderAnnounce' + +interface ScreenShareZoomControlsProps { + containerRef: React.RefObject + isZoomed: boolean + zoomPercentage: number + canZoomIn: boolean + canZoomOut: boolean + onZoomIn: () => void + onZoomOut: () => void + onResetZoom: () => void +} + +export const ScreenShareZoomControls = ({ + containerRef, + isZoomed, + zoomPercentage, + canZoomIn, + canZoomOut, + onZoomIn, + onZoomOut, + onResetZoom, +}: ScreenShareZoomControlsProps) => { + const { t } = useTranslation('rooms', { keyPrefix: 'screenShareZoom' }) + const announce = useScreenReaderAnnounce() + + const [isFullscreen, setIsFullscreen] = useState(false) + const [isFullscreenAvailable] = useState( + () => typeof document !== 'undefined' && document.fullscreenEnabled + ) + + // Covers Esc and browser UI exits, not just the toolbar button. + useEffect(() => { + const onChange = () => { + const entered = !!document.fullscreenElement + setIsFullscreen(entered) + announce( + entered ? t('fullScreenEntered') : t('fullScreenExited'), + 'assertive' + ) + } + document.addEventListener('fullscreenchange', onChange) + return () => document.removeEventListener('fullscreenchange', onChange) + }, [announce, t]) + + const toggleFullScreen = useCallback(async () => { + try { + if (document.fullscreenElement) { + 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 ( +
+ + {isZoomed && ( + <> + + + {/* Visual only - zoom level is announced via useScreenReaderAnnounce. */} + + + )} + + {isFullscreenAvailable && ( + + )} + +
+ ) +}