39948-vm/frontend/src/components/Constructor/TransitionPreviewOverlay.tsx
2026-05-06 10:04:42 +02:00

100 lines
3.4 KiB
TypeScript

/**
* TransitionPreviewOverlay Component
*
* Full-screen overlay for transition video preview.
* Designed to work with useTransitionPlayback hook which manages
* video src and playback externally via the videoRef.
*
* Supports letterbox mode to constrain transitions within canvas bounds,
* matching the behavior of background images and UI elements.
*/
import React from 'react';
import CanvasLoadingSpinner from '../CanvasLoadingSpinner';
interface TransitionPreviewOverlayProps {
/** Reference to the video element - useTransitionPlayback manages src and playback */
videoRef: React.RefObject<HTMLVideoElement | null>;
/** Whether the overlay is visible */
isActive: boolean;
/** Whether the video is currently buffering (used to hide video during load) */
isBuffering?: boolean;
/** Show loading spinner during buffering (default: false for backward compat) */
showSpinner?: boolean;
/** Loading message for spinner */
spinnerMessage?: string;
/** Letterbox styles from useCanvasScale - positions overlay within canvas bounds */
letterboxStyles?: React.CSSProperties;
/** Video object-fit mode (default: 'contain' to match backgrounds) */
videoFit?: 'contain' | 'cover';
/** Additional opacity value for fade-out effects (0-1) */
opacity?: number;
/** Forces video element remount when changed - prevents decoder state issues with pre-created blob URLs */
videoKey?: string;
}
const TransitionPreviewOverlay: React.FC<TransitionPreviewOverlayProps> = ({
videoRef,
isActive,
isBuffering = false,
showSpinner = false,
spinnerMessage = 'Preparing transition...',
letterboxStyles,
videoFit = 'contain',
opacity,
videoKey,
}) => {
if (!isActive) return null;
// Container opacity: 0 while buffering to prevent black flash
// Video first frame = old page background, so we hide everything until ready
const containerOpacity = isBuffering ? 0 : (opacity ?? 1);
return (
// Outer: full viewport, transparent background
// Transparent ensures if Safari clears video frame when paused,
// the new page background shows through instead of black flash
<div className='fixed inset-0 z-50 overflow-hidden pointer-events-none'>
{/* Loading spinner during buffering - provides user feedback */}
{isBuffering && showSpinner && (
<CanvasLoadingSpinner
isVisible={true}
message={spinnerMessage}
size='lg'
zIndex={60}
/>
)}
{/* Video container - hidden while buffering */}
<div
style={{
opacity: containerOpacity,
transition: 'opacity 150ms ease-out',
}}
>
{/* Inner: respects letterbox dimensions when provided */}
<div
className='overflow-hidden'
style={letterboxStyles || { position: 'absolute', inset: 0 }}
>
{/* Video element - container handles visibility, video is always opaque */}
{/* key forces React to remount the video element when URL changes, clearing decoder state */}
<video
key={videoKey}
ref={videoRef}
className={`absolute inset-0 h-full w-full ${
videoFit === 'cover' ? 'object-cover' : 'object-contain'
}`}
muted
playsInline
preload='auto'
disablePictureInPicture
/>
</div>
</div>
</div>
);
};
export default TransitionPreviewOverlay;