feat: implement video controls and pop-out functionality in Video component

This commit is contained in:
2026-02-27 22:12:47 +11:00
parent 58966aec19
commit f8af51b75c
7 changed files with 811 additions and 33 deletions

View File

@@ -1,10 +1,168 @@
import React, { VideoHTMLAttributes, forwardRef } from 'react';
import React, { VideoHTMLAttributes, forwardRef, useRef, useState, useEffect } from 'react';
import classNames from 'classnames';
import { Box, IconButton, Icon, Icons, Tooltip, TooltipProvider, Text } from 'folds';
import * as css from './media.css';
import * as videoCss from './videoControls.css';
export const Video = forwardRef<HTMLVideoElement, VideoHTMLAttributes<HTMLVideoElement>>(
({ className, ...props }, ref) => (
// eslint-disable-next-line jsx-a11y/media-has-caption
<video className={classNames(css.Video, className)} {...props} ref={ref} />
)
type VideoProps = VideoHTMLAttributes<HTMLVideoElement> & {
disableControls?: boolean;
};
export const Video = forwardRef<HTMLVideoElement, VideoProps>(
({ className, disableControls, ...props }, ref) => {
const containerRef = useRef<HTMLDivElement>(null);
const videoRef = useRef<HTMLVideoElement>(null);
const [showControls, setShowControls] = useState(false);
const [isFullscreen, setIsFullscreen] = useState(false);
// Combine refs
useEffect(() => {
if (typeof ref === 'function') {
ref(videoRef.current);
} else if (ref) {
ref.current = videoRef.current;
}
}, [ref]);
// Track fullscreen state
useEffect(() => {
const handleFullscreenChange = () => {
setIsFullscreen(!!document.fullscreenElement);
};
document.addEventListener('fullscreenchange', handleFullscreenChange);
return () => document.removeEventListener('fullscreenchange', handleFullscreenChange);
}, []);
const handleFullscreen = async () => {
try {
if (!document.fullscreenElement && containerRef.current) {
await containerRef.current.requestFullscreen();
} else if (document.fullscreenElement) {
await document.exitFullscreen();
}
} catch (error) {
console.error('Error toggling fullscreen:', error);
}
};
const handlePopOut = () => {
const videoSrc = videoRef.current?.src;
const videoTitle = props.title || 'Video';
if (videoSrc) {
const popoutWindow = window.open(
'',
'_blank',
'width=800,height=600,menubar=no,toolbar=no,location=no,status=no'
);
if (popoutWindow) {
popoutWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<title>${videoTitle}</title>
<style>
body {
margin: 0;
padding: 0;
background: #000;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
video {
max-width: 100%;
max-height: 100%;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<video controls autoplay src="${videoSrc}">
Your browser does not support the video tag.
</video>
</body>
</html>
`);
popoutWindow.document.close();
}
}
};
if (disableControls) {
return (
// eslint-disable-next-line jsx-a11y/media-has-caption
<video className={classNames(css.Video, className)} {...props} ref={videoRef} />
);
}
return (
<Box
ref={containerRef}
className={videoCss.VideoContainer}
onMouseEnter={() => setShowControls(true)}
onMouseLeave={() => setShowControls(false)}
>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<video className={classNames(css.Video, className)} {...props} ref={videoRef} />
{showControls && (
<Box className={videoCss.VideoControlsOverlay} gap="200">
<TooltipProvider
tooltip={
<Tooltip variant="Secondary">
<Text size="T200">{isFullscreen ? 'Exit Fullscreen' : 'Fullscreen'}</Text>
</Tooltip>
}
position="Bottom"
align="Center"
>
{(triggerRef) => (
<IconButton
ref={triggerRef}
variant="Secondary"
size="300"
radii="300"
onClick={handleFullscreen}
aria-label={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}
>
<Icon
size="200"
src={isFullscreen ? Icons.Minus : Icons.Plus}
/>
</IconButton>
)}
</TooltipProvider>
<TooltipProvider
tooltip={
<Tooltip variant="Secondary">
<Text size="T200">Pop Out</Text>
</Tooltip>
}
position="Bottom"
align="Center"
>
{(triggerRef) => (
<IconButton
ref={triggerRef}
variant="Secondary"
size="300"
radii="300"
onClick={handlePopOut}
aria-label="Open in new window"
>
<Icon size="200" src={Icons.External} />
</IconButton>
)}
</TooltipProvider>
</Box>
)}
</Box>
);
}
);