39948-vm/frontend/src/components/CanvasLoadingSpinner.tsx

91 lines
2.4 KiB
TypeScript

/**
* CanvasLoadingSpinner Component
*
* Loading spinner overlay for canvas contexts.
* Shows during video preparation/buffering.
* Includes delay to avoid flashing for quick operations.
*/
import React, { useState, useEffect } from 'react';
import { PRELOAD_CONFIG } from '../config/preload.config';
interface CanvasLoadingSpinnerProps {
/** Whether the spinner is visible */
isVisible: boolean;
/** Loading message to display */
message?: string;
/** Spinner size */
size?: 'sm' | 'md' | 'lg';
/** Loading progress (0-100) */
progress?: number;
/** Z-index for stacking (default: 100) */
zIndex?: number;
}
const CanvasLoadingSpinner: React.FC<CanvasLoadingSpinnerProps> = ({
isVisible,
message,
size = 'md',
progress,
zIndex = 100,
}) => {
// Delayed visibility - only show after SPINNER_DELAY_MS
const [showSpinner, setShowSpinner] = useState(false);
useEffect(() => {
if (isVisible) {
// Start timer to show spinner after delay
const timer = setTimeout(() => {
setShowSpinner(true);
}, PRELOAD_CONFIG.ui.spinnerDelayMs);
return () => clearTimeout(timer);
} else {
// Hide immediately when loading completes
setShowSpinner(false);
}
}, [isVisible]);
if (!showSpinner) return null;
const sizeClasses = {
sm: 'w-8 h-8 border-2',
md: 'w-12 h-12 border-[3px]',
lg: 'w-16 h-16 border-4',
};
return (
<div
className='absolute inset-0 flex flex-col items-center justify-center pointer-events-none'
style={{ zIndex }}
>
{/* Spinner with subtle shadow for visibility on any background */}
<div
className='relative'
style={{ filter: 'drop-shadow(0 2px 4px rgba(0,0,0,0.5))' }}
>
{/* Spinner ring */}
<div
className={`${sizeClasses[size]} rounded-full border-white/30 border-t-white animate-spin`}
style={{ borderStyle: 'solid' }}
/>
{/* Progress indicator (optional) */}
{progress !== undefined && (
<div className='absolute inset-0 flex items-center justify-center'>
<span className='text-white text-xs font-medium drop-shadow-md'>
{Math.round(progress)}%
</span>
</div>
)}
</div>
{message && (
<p className='mt-3 text-white/90 text-sm font-medium drop-shadow-md'>
{message}
</p>
)}
</div>
);
};
export default CanvasLoadingSpinner;