UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

301 lines (298 loc) 11.4 kB
'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