aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
301 lines (298 loc) • 11.4 kB
JavaScript
'use client';
import { jsx, jsxs } from 'react/jsx-runtime';
import { cn } from '../../lib/utilsComprehensive.js';
import { Loader2, AlertCircle, Image, Eye, Heart, Download, ZoomIn, Share2 } from 'lucide-react';
import { useState, useRef, useEffect, useCallback } from 'react';
import '../../primitives/GlassCore.js';
import '../../primitives/glass/GlassAdvanced.js';
import '../../primitives/OptimizedGlassCore.js';
import '../../primitives/glass/OptimizedGlassAdvanced.js';
import '../../primitives/MotionNative.js';
import { MotionFramer } from '../../primitives/motion/MotionFramer.js';
/**
* GlassLazyImage component
* Lazy loading image with blur placeholder and performance optimizations
*/
const GlassLazyImage = ({
src,
placeholder,
alt = "",
title,
width,
height,
objectFit = "cover",
blur = true,
blurIntensity = 5,
rootMargin = "50px",
threshold = 0.1,
loadingComponent,
errorComponent,
enableZoom = false,
enableActions = false,
showStats = false,
stats,
onLoad,
onError,
onClick,
className,
...props
}) => {
const [isLoaded, setIsLoaded] = useState(false);
const [isError, setIsError] = useState(false);
const [isInView, setIsInView] = useState(false);
const [showActionsMenu, setShowActionsMenu] = useState(false);
const [imageDimensions, setImageDimensions] = useState({
width: 0,
height: 0
});
const imgRef = useRef(null);
const containerRef = useRef(null);
const observerRef = useRef(null);
// Handle intersection observer
useEffect(() => {
if (!containerRef.current) return;
observerRef.current = new IntersectionObserver(entries => {
const [entry] = entries;
if (entry.isIntersecting) {
setIsInView(true);
if (observerRef.current) {
observerRef.current.disconnect();
}
}
}, {
rootMargin,
threshold
});
observerRef.current.observe(containerRef.current);
return () => {
if (observerRef.current) {
observerRef.current.disconnect();
}
};
}, [rootMargin, threshold]);
// Handle image load
const handleLoad = useCallback(() => {
setIsLoaded(true);
setIsError(false);
// Get natural dimensions
if (imgRef.current) {
setImageDimensions({
width: imgRef.current.naturalWidth,
height: imgRef.current.naturalHeight
});
}
onLoad?.();
}, [onLoad]);
// Handle image error
const handleError = useCallback(() => {
setIsLoaded(false);
setIsError(true);
const errorMsg = "Failed to load image";
onError?.(errorMsg);
}, [onError]);
// Handle image click
const handleClick = useCallback(event => {
onClick?.(event);
}, [onClick]);
// Handle zoom
const handleZoom = useCallback(() => {
if (!enableZoom) return;
// Zoom functionality would be implemented here
// Could open a modal with zoom controls
}, [enableZoom]);
// Handle download
const handleDownload = useCallback(() => {
if (!src) return;
const link = document.createElement("a");
link.href = src;
link.download = title || "image";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}, [src, title]);
// Handle share
const handleShare = useCallback(async () => {
if (!src) return;
if (navigator.share) {
try {
await navigator.share({
title: title || "Image",
url: src
});
} catch (err) {
console.error("Error sharing:", err);
}
} else {
// Fallback: copy to clipboard
navigator.clipboard.writeText(src);
}
}, [src, title]);
// Default loading component
const defaultLoadingComponent = jsx(MotionFramer, {
preset: "fadeIn",
className: 'absolute inset-0 glass-flex glass-items-center glass-justify-center',
children: jsxs("div", {
className: "glass-flex glass-items-center glass-gap-2 glass-px-3 glass-py-2 glass-surface-dark/50 glass-glass-glass-backdrop-blur-md glass-contrast-guard glass-radius-full glass-contrast-guard",
children: [jsx(Loader2, {
className: 'w-4 h-4 animate-spin text-primary/80'
}), jsx("span", {
className: 'text-primary/80 glass-text-sm',
children: "Loading..."
})]
})
});
// Default error component
const defaultErrorComponent = jsxs(MotionFramer, {
preset: "fadeIn",
className: 'absolute inset-0 glass-flex glass-flex-col glass-items-center glass-justify-center glass-surface-dark/20',
children: [jsx(AlertCircle, {
className: 'w-8 h-8 text-primary mb-2'
}), jsx("span", {
className: 'text-primary glass-text-sm',
children: "Failed to load image"
})]
});
return jsxs(MotionFramer, {
"data-glass-component": true,
preset: "fadeIn",
className: cn("relative overflow-hidden group cursor-pointer", className),
style: {
width: width || "auto",
height: height || "auto"
},
onClick: handleClick,
ref: containerRef,
...props,
children: [(!isLoaded || !isInView) && jsx("div", {
className: 'absolute inset-0 glass-surface-subtle/10 glass-flex glass-items-center glass-justify-center',
style: {
filter: blur ? `blur(${blurIntensity}px)` : "none"
},
children: placeholder ? jsx("img", {
src: placeholder,
alt: "Loading...",
className: 'glass-w-full glass-h-full object-cover'
}) : jsx("div", {
className: "glass-flex glass-items-center glass-justify-center glass-w-full glass-h-full",
children: jsx(Image, {
className: 'w-8 h-8 text-primary/40'
})
})
}), isInView && jsx(MotionFramer, {
preset: "fadeIn",
className: cn("w-full h-full transition-opacity duration-300", isLoaded ? "opacity-100" : "opacity-0"),
children: jsx("img", {
ref: imgRef,
src: src,
alt: alt,
className: cn("w-full h-full transition-all duration-300", objectFit === "contain" && "object-contain", objectFit === "cover" && "object-cover", objectFit === "fill" && "object-fill", objectFit === "none" && "object-none", objectFit === "scale-down" && "object-scale-down"),
onLoad: handleLoad,
onError: handleError,
loading: "lazy"
})
}), !isLoaded && !isError && isInView && jsx("div", {
className: 'absolute inset-0',
children: loadingComponent || defaultLoadingComponent
}), isError && jsx("div", {
className: 'absolute inset-0',
children: errorComponent || defaultErrorComponent
}), (title || showStats) && jsxs("div", {
className: 'absolute bottom-0 left-0 right-0 glass-p-3 glass-gradient-primary glass-gradient-primary glass-gradient-primary opacity-0 group-hover:opacity-100 transition-opacity',
children: [title && jsx("h3", {
className: 'text-primary font-medium glass-text-sm mb-1 truncate',
children: title
}), showStats && stats && jsxs("div", {
className: 'glass-flex glass-items-center glass-gap-3 text-primary/80 glass-text-xs',
children: [stats.views && jsxs("div", {
className: "glass-flex glass-items-center glass-gap-1",
children: [jsx(Eye, {
className: 'w-3 h-3'
}), jsx("span", {
children: stats.views
})]
}), stats.likes && jsxs("div", {
className: "glass-flex glass-items-center glass-gap-1",
children: [jsx(Heart, {
className: 'w-3 h-3'
}), jsx("span", {
children: stats.likes
})]
}), stats.downloads && jsxs("div", {
className: "glass-flex glass-items-center glass-gap-1",
children: [jsx(Download, {
className: 'w-3 h-3'
}), jsx("span", {
children: stats.downloads
})]
})]
})]
}), enableActions && jsx("div", {
className: 'absolute glass-top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity',
children: jsxs("div", {
className: 'relative',
children: [jsx("button", {
onClick: e => {
e.stopPropagation();
setShowActionsMenu(!showActionsMenu);
},
className: 'glass-p-2 glass-surface-dark/50 glass-glass-glass-backdrop-blur-md glass-contrast-guard glass-radius-full hover:glass-surface-dark/70 transition-colors glass-focus glass-touch-target',
children: jsx("span", {
className: 'text-primary glass-text-lg',
children: "\u22EF"
})
}), showActionsMenu && jsx(MotionFramer, {
preset: "slideDown",
className: 'absolute top-full right-0 glass-mt-2',
children: jsxs("div", {
className: "glass-surface-dark/80 glass-glass-glass-backdrop-blur-md glass-contrast-guard glass-radius-lg glass-shadow-xl glass-border glass-border-white/20 glass-min-w-32 glass-contrast-guard",
children: [enableZoom && jsxs("button", {
onClick: e => {
e.stopPropagation();
handleZoom();
setShowActionsMenu(false);
},
className: 'glass-w-full text-left glass-px-4 glass-py-3 text-primary/90 hover:glass-surface-subtle/10 transition-colors glass-flex glass-items-center glass-gap-3 first:glass-radius-t-lg glass-focus glass-touch-target glass-contrast-guard',
children: [jsx(ZoomIn, {
className: 'w-4 h-4'
}), jsx("span", {
className: "glass-text-sm",
children: "Zoom"
})]
}), jsxs("button", {
onClick: e => {
e.stopPropagation();
handleDownload();
setShowActionsMenu(false);
},
className: 'glass-w-full text-left glass-px-4 glass-py-3 text-primary/90 hover:glass-surface-subtle/10 transition-colors glass-flex glass-items-center glass-gap-3 glass-focus glass-touch-target glass-contrast-guard',
children: [jsx(Download, {
className: 'w-4 h-4'
}), jsx("span", {
className: "glass-text-sm",
children: "Download"
})]
}), jsxs("button", {
onClick: e => {
e.stopPropagation();
handleShare();
setShowActionsMenu(false);
},
className: 'glass-w-full text-left glass-px-4 glass-py-3 text-primary/90 hover:glass-surface-subtle/10 transition-colors glass-flex glass-items-center glass-gap-3 last:glass-radius-b-lg glass-focus glass-touch-target glass-contrast-guard',
children: [jsx(Share2, {
className: 'w-4 h-4'
}), jsx("span", {
className: "glass-text-sm",
children: "Share"
})]
})]
})
})]
})
}), process.env.NODE_ENV === "development" && imageDimensions.width > 0 && jsxs("div", {
className: 'absolute glass-top-2 left-2 glass-px-2 glass-py-1 glass-surface-dark/50 glass-glass-glass-backdrop-blur-md glass-contrast-guard glass-radius-md text-primary/60 glass-text-xs glass-contrast-guard',
children: [imageDimensions.width, " \u00D7 ", imageDimensions.height]
})]
});
};
export { GlassLazyImage, GlassLazyImage as default };
//# sourceMappingURL=GlassLazyImage.js.map