Initial commit

This commit is contained in:
2025-10-04 18:22:24 +03:00
commit 2852c2c054
291 changed files with 38109 additions and 0 deletions

View File

@@ -0,0 +1,157 @@
import { Image, ImageProps, Skeleton } from "antd";
import { CSSProperties, useCallback, useEffect, useRef, useState } from "react";
import styles from "./ImageWithFallback.module.css";
interface ImageWithFallbackProps extends ImageProps {
fallbackSrc: string;
borderRadius?: number | string;
priority?: boolean;
lazy?: boolean;
placeholder?: "blur" | "empty";
blurDataURL?: string;
width?: number | string;
height?: number | string;
loadingContainerStyle?: CSSProperties;
}
const ImageWithFallback = ({
fallbackSrc,
src,
borderRadius,
style,
alt,
priority = false,
lazy = true,
width,
height,
loadingContainerStyle,
...rest
}: ImageWithFallbackProps) => {
const [imgSrc, setImgSrc] = useState(src || fallbackSrc);
const [isLoading, setIsLoading] = useState(true);
const [isInView, setIsInView] = useState(false);
const [hasError, setHasError] = useState(false);
const imageRef = useRef<HTMLDivElement>(null);
// Intersection Observer for lazy loading
useEffect(() => {
if (lazy && !priority) {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsInView(true);
observer.disconnect();
}
},
{
rootMargin: "50px", // Start loading 50px before the image comes into view
threshold: 0.1,
}
);
if (imageRef.current) {
observer.observe(imageRef.current);
}
return () => observer.disconnect();
} else {
setIsInView(true);
}
}, [lazy, priority]);
const handleError = useCallback(() => {
setImgSrc(fallbackSrc);
setHasError(true);
setIsLoading(false);
}, [fallbackSrc]);
const handleLoad = useCallback(() => {
setIsLoading(false);
setHasError(false);
}, []);
// Combine custom style with borderRadius if provided
const combinedStyle = {
...style,
...(borderRadius && { borderRadius }),
};
// Don't render the image until it's in view (for lazy loading)
if (lazy && !priority && !isInView) {
return (
<div
ref={imageRef}
className={styles.loadingContainer}
style={{
...combinedStyle,
height,
width: width || 90,
}}
>
<Skeleton.Image
active
style={{
border: "none",
height,
width: width || 90,
...loadingContainerStyle,
}}
/>
</div>
);
}
return (
<div ref={imageRef} style={{ position: "relative", height }}>
{isLoading && !hasError && (
<div
className={`${styles.loadingContainer} ${styles.loadingState}`}
style={{
zIndex: 1,
borderRadius: borderRadius || "8px",
height,
width: width || 90,
textAlign: "center",
...loadingContainerStyle,
}}
>
<Skeleton.Image
active
style={{
height,
width: width || 90,
display: "flex",
alignItems: "center",
justifyContent: "center",
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
...loadingContainerStyle,
}}
/>
</div>
)}
<Image
src={imgSrc}
onError={handleError}
onLoad={handleLoad}
style={{
opacity: isLoading ? 0 : 1,
transition: "opacity 0.3s ease-in-out",
objectFit: "cover",
...combinedStyle,
}}
alt={alt || ""}
width={width}
height={height}
preview={false}
{...rest}
/>
</div>
);
};
export default ImageWithFallback;