feat: implement video controls and pop-out functionality in Video component
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user