mirror of
https://github.com/suitenumerique/meet.git
synced 2026-08-11 03:07:43 +00:00
✨(frontend) add ScreenShareZoomControls toolbar component
Bottom-right toolbar with zoom, fit-to-window and fullscreen buttons.
This commit is contained in:
@@ -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<HTMLDivElement | null>
|
||||
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 (
|
||||
<div
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
bottom: '12px',
|
||||
right: '12px',
|
||||
zIndex: 2,
|
||||
pointerEvents: 'auto',
|
||||
})}
|
||||
>
|
||||
<HStack
|
||||
gap={0}
|
||||
role="toolbar"
|
||||
aria-label={t('toolbarLabel')}
|
||||
className={css({
|
||||
backgroundColor: 'primaryDark.50',
|
||||
borderRadius: '2rem',
|
||||
padding: '0.25rem',
|
||||
opacity: 0.7,
|
||||
transition: 'opacity 200ms linear',
|
||||
_hover: {
|
||||
opacity: 0.95,
|
||||
},
|
||||
})}
|
||||
>
|
||||
{isZoomed && (
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="primaryTextDark"
|
||||
square
|
||||
tooltip={t('fitToWindow')}
|
||||
aria-label={t('fitToWindow')}
|
||||
onPress={onResetZoom}
|
||||
>
|
||||
<RiFullscreenExitLine size={18} />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="primaryTextDark"
|
||||
square
|
||||
tooltip={t('zoomOut')}
|
||||
aria-label={t('zoomOut')}
|
||||
isDisabled={!canZoomOut}
|
||||
onPress={onZoomOut}
|
||||
>
|
||||
<RiZoomOutLine size={18} />
|
||||
</Button>
|
||||
{/* Visual only - zoom level is announced via useScreenReaderAnnounce. */}
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={css({
|
||||
color: 'white',
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 500,
|
||||
minWidth: '3rem',
|
||||
textAlign: 'center',
|
||||
userSelect: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '0 0.25rem',
|
||||
})}
|
||||
>
|
||||
{zoomPercentage} %
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="primaryTextDark"
|
||||
square
|
||||
tooltip={t('zoomIn')}
|
||||
aria-label={t('zoomIn')}
|
||||
isDisabled={!canZoomIn}
|
||||
onPress={onZoomIn}
|
||||
>
|
||||
<RiZoomInLine size={18} />
|
||||
</Button>
|
||||
{isFullscreenAvailable && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="primaryTextDark"
|
||||
square
|
||||
tooltip={isFullscreen ? t('exitFullScreen') : t('fullScreen')}
|
||||
aria-label={isFullscreen ? t('exitFullScreen') : t('fullScreen')}
|
||||
onPress={toggleFullScreen}
|
||||
>
|
||||
{isFullscreen ? (
|
||||
<RiFullscreenExitLine size={18} />
|
||||
) : (
|
||||
<RiFullscreenLine size={18} />
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</HStack>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user