AI Image Studio Progress Review
- Added new router for content assets - Added new service for content assets - Added new model for content assets - Added new utils for content assets - Added new docs for content assets - Added new tests for content assets - Added new examples for content assets - Added new guides for content assets
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Box, Skeleton } from '@mui/material';
|
||||
|
||||
interface OptimizedImageProps {
|
||||
src: string;
|
||||
alt: string;
|
||||
sx?: any;
|
||||
loading?: 'lazy' | 'eager';
|
||||
placeholder?: 'blur' | 'empty';
|
||||
sizes?: string;
|
||||
width?: number | string;
|
||||
height?: number | string;
|
||||
}
|
||||
|
||||
export const OptimizedImage: React.FC<OptimizedImageProps> = ({
|
||||
src,
|
||||
alt,
|
||||
sx = {},
|
||||
loading = 'lazy',
|
||||
placeholder = 'blur',
|
||||
sizes,
|
||||
width,
|
||||
height,
|
||||
}) => {
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
const [isInView, setIsInView] = useState(loading === 'eager');
|
||||
const [hasError, setHasError] = useState(false);
|
||||
const imgRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading === 'eager') {
|
||||
setIsInView(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
setIsInView(true);
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
rootMargin: '50px',
|
||||
threshold: 0.01,
|
||||
}
|
||||
);
|
||||
|
||||
if (imgRef.current) {
|
||||
observer.observe(imgRef.current);
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [loading]);
|
||||
|
||||
const handleLoad = () => {
|
||||
setIsLoaded(true);
|
||||
};
|
||||
|
||||
const handleError = () => {
|
||||
setHasError(true);
|
||||
setIsLoaded(true);
|
||||
};
|
||||
|
||||
// Extract clip-path and other advanced CSS from sx to apply to wrapper
|
||||
const {
|
||||
clipPath,
|
||||
gridArea,
|
||||
'--progress': progress,
|
||||
...imgSx
|
||||
} = sx || {};
|
||||
|
||||
return (
|
||||
<Box
|
||||
ref={imgRef}
|
||||
sx={{
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
overflow: 'hidden',
|
||||
clipPath,
|
||||
gridArea,
|
||||
'--progress': progress,
|
||||
...(clipPath ? {} : sx),
|
||||
}}
|
||||
>
|
||||
{!isLoaded && !hasError && (
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
width="100%"
|
||||
height="100%"
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
bgcolor: 'rgba(15,23,42,0.5)',
|
||||
borderRadius: imgSx.borderRadius || 0,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isInView && (
|
||||
<Box
|
||||
component="img"
|
||||
src={src}
|
||||
alt={alt}
|
||||
onLoad={handleLoad}
|
||||
onError={handleError}
|
||||
loading={loading}
|
||||
sizes={sizes}
|
||||
width={width}
|
||||
height={height}
|
||||
sx={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover',
|
||||
opacity: isLoaded ? 1 : 0,
|
||||
transition: 'opacity 0.3s ease-in-out',
|
||||
...imgSx,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{hasError && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
bgcolor: 'rgba(15,23,42,0.8)',
|
||||
color: 'rgba(255,255,255,0.5)',
|
||||
fontSize: '0.875rem',
|
||||
}}
|
||||
>
|
||||
Failed to load image
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user