kawkab-frontend
Version:
Kawkab frontend is a frontend library for the Kawkab framework
78 lines (77 loc) • 4.19 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useState, useRef, useEffect } from 'react';
const defaultLoader = (src, width, quality) => {
const url = new URL(src, 'http://localhost');
url.searchParams.set('w', width.toString());
url.searchParams.set('q', quality.toString());
return url.toString();
};
const defaultWidths = [320, 480, 768, 1024, 1280, 1600, 1920];
const generateSrcSetString = (src, quality, loader) => defaultWidths.map((w) => `${loader(src, w, quality)} ${w}w`).join(', ');
export const Image = ({ src, alt, width, height, layout = 'intrinsic', objectFit = 'cover', objectPosition = 'center', className = '', style = {}, placeholder = 'empty', blurDataURL, priority = false, loading = 'lazy', onLoad, onError, quality = 75, sizes = '100vw', generateSrcSet = true, loader = defaultLoader, unoptimized = false, }) => {
const [isLoaded, setIsLoaded] = useState(false);
const [isInView, setIsInView] = useState(priority || loading === 'eager');
const imageRef = useRef(null);
const isFill = layout === 'fill';
useEffect(() => {
if (priority || loading === 'eager')
return;
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsInView(true);
observer.unobserve(entry.target);
}
});
}, { rootMargin: '100px', threshold: 0.1 });
const current = imageRef.current;
if (current)
observer.observe(current);
return () => {
if (current)
observer.unobserve(current);
};
}, [src, priority, loading]);
const aspectRatioStyle = layout === 'intrinsic' && width && height
? { aspectRatio: `${width} / ${height}` }
: {};
const containerStyle = {
position: isFill ? 'relative' : 'initial',
width: isFill ? '100%' : width,
height: isFill ? '100%' : height,
overflow: 'hidden',
display: layout === 'intrinsic' ? 'inline-block' : 'block',
...aspectRatioStyle,
...style,
};
const imageStyle = {
objectFit,
objectPosition,
width: layout === 'intrinsic' || layout === 'fixed' ? width : '100%',
height: layout === 'intrinsic' || layout === 'fixed' ? height : '100%',
position: isFill ? 'absolute' : 'relative',
top: isFill ? 0 : undefined,
left: isFill ? 0 : undefined,
transition: 'filter 0.3s ease, transform 0.3s ease',
filter: placeholder === 'blur' && !isLoaded && blurDataURL
? 'blur(20px)'
: 'none',
transform: placeholder === 'blur' && !isLoaded && blurDataURL
? 'scale(1.02)'
: 'none',
};
const shouldUseSrcSet = !unoptimized && generateSrcSet && isInView;
const finalSrc = !unoptimized && isInView ? loader(src, width || 800, quality) : undefined;
const srcSet = shouldUseSrcSet ? generateSrcSetString(src, quality, loader) : undefined;
return (_jsxs(_Fragment, { children: [priority && _jsx("link", { rel: "preload", as: "image", href: finalSrc }), _jsxs("div", { style: containerStyle, className: className, children: [_jsx("img", { ref: imageRef, src: finalSrc, srcSet: srcSet, sizes: srcSet ? sizes : undefined, alt: alt, width: layout === 'fixed' || layout === 'intrinsic' ? width : undefined, height: layout === 'fixed' || layout === 'intrinsic' ? height : undefined, style: imageStyle, loading: loading, onLoad: () => {
setIsLoaded(true);
onLoad?.();
}, onError: onError }), placeholder === 'blur' && blurDataURL && !isLoaded && (_jsx("img", { src: blurDataURL, alt: "", "aria-hidden": "true", style: {
...imageStyle,
position: 'absolute',
top: 0,
left: 0,
filter: 'blur(20px)',
zIndex: -1,
} }))] })] }));
};