78 lines
1.9 KiB
TypeScript
78 lines
1.9 KiB
TypeScript
import { useState } from 'react';
|
|
import Image, { ImageProps } from 'next/image';
|
|
import { Box, Skeleton } from '@mui/material';
|
|
|
|
interface OptimizedImageProps extends Omit<ImageProps, 'onLoadingComplete'> {
|
|
onLoadComplete?: () => void;
|
|
}
|
|
|
|
/**
|
|
* OptimizedImage Component
|
|
*
|
|
* Wraps Next.js Image component with:
|
|
* - Loading states with MUI Skeleton
|
|
* - Blur placeholder support
|
|
* - Loading completion events
|
|
* - Automatic optimization
|
|
*/
|
|
export const OptimizedImage: React.FC<OptimizedImageProps> = ({
|
|
src,
|
|
alt,
|
|
width,
|
|
height,
|
|
onLoadComplete,
|
|
style,
|
|
...props
|
|
}) => {
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
const handleLoadingComplete = () => {
|
|
setIsLoading(false);
|
|
onLoadComplete?.();
|
|
};
|
|
|
|
// Generate a simple blur data URL for placeholder
|
|
const blurDataURL = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iI2YwZjBmMCIvPjwvc3ZnPg==';
|
|
|
|
return (
|
|
<Box
|
|
sx={{
|
|
position: 'relative',
|
|
width: typeof width === 'number' ? `${width}px` : width,
|
|
height: typeof height === 'number' ? `${height}px` : height,
|
|
...style,
|
|
}}
|
|
>
|
|
{isLoading && (
|
|
<Skeleton
|
|
variant="rectangular"
|
|
width={width}
|
|
height={height}
|
|
sx={{
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
borderRadius: 'inherit',
|
|
}}
|
|
animation="wave"
|
|
/>
|
|
)}
|
|
<Image
|
|
src={src}
|
|
alt={alt}
|
|
width={width}
|
|
height={height}
|
|
onLoadingComplete={handleLoadingComplete}
|
|
placeholder="blur"
|
|
blurDataURL={blurDataURL}
|
|
style={{
|
|
opacity: isLoading ? 0 : 1,
|
|
transition: 'opacity 0.3s ease-in-out',
|
|
...style,
|
|
}}
|
|
{...props}
|
|
/>
|
|
</Box>
|
|
);
|
|
};
|