UNPKG

aura-glass

Version:

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

189 lines (186 loc) 6.68 kB
'use client'; import { jsx, jsxs } from 'react/jsx-runtime'; import { cn } from '../../lib/utilsComprehensive.js'; import { Loader2, AlertCircle, RefreshCw } from 'lucide-react'; import { useState, useRef, useCallback, useEffect } 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'; /** * GlassInfiniteScroll component * Infinite scrolling container with loading states and error handling */ const GlassInfiniteScroll = ({ children, onLoadMore, hasMore, loading = false, error = null, threshold = 100, loadingIndicator, errorIndicator, endMessage, className, reverse = false, autoScroll = false, ...props }) => { const [isLoading, setIsLoading] = useState(loading); const [loadError, setLoadError] = useState(error); const [hasReachedEnd, setHasReachedEnd] = useState(false); const containerRef = useRef(null); const sentinelRef = useRef(null); const observerRef = useRef(null); const isLoadingMoreRef = useRef(false); // Handle intersection observer const handleIntersection = useCallback(async entries => { const [entry] = entries; if (entry.isIntersecting && hasMore && !isLoadingMoreRef.current && !loadError) { isLoadingMoreRef.current = true; setIsLoading(true); setLoadError(null); try { await onLoadMore(); } catch (err) { console.error("Error loading more items:", err); setLoadError("Failed to load more items"); } finally { setIsLoading(false); isLoadingMoreRef.current = false; } } if (!hasMore && entry.isIntersecting) { setHasReachedEnd(true); } }, [hasMore, onLoadMore, loadError]); // Setup intersection observer useEffect(() => { if (!sentinelRef.current) return; observerRef.current = new IntersectionObserver(handleIntersection, { root: containerRef.current, rootMargin: `${threshold}px`, threshold: 0.1 }); observerRef.current.observe(sentinelRef.current); return () => { if (observerRef.current) { observerRef.current.disconnect(); } }; }, [handleIntersection, threshold]); // Update loading state when prop changes useEffect(() => { setIsLoading(loading); }, [loading]); // Update error state when prop changes useEffect(() => { setLoadError(error); }, [error]); // Auto-scroll to bottom useEffect(() => { if (autoScroll && containerRef.current && reverse) { containerRef.current.scrollTop = containerRef.current.scrollHeight; } }, [children, autoScroll, reverse]); // Handle retry const handleRetry = useCallback(async () => { setLoadError(null); setIsLoading(true); try { await onLoadMore(); } catch (err) { console.error("Error retrying load:", err); setLoadError("Failed to load more items"); } finally { setIsLoading(false); } }, [onLoadMore]); // Default loading indicator const defaultLoadingIndicator = jsx(MotionFramer, { preset: "fadeIn", className: "glass-flex glass-items-center glass-justify-center glass-py-8", children: jsxs("div", { className: "glass-flex glass-items-center glass-gap-3 glass-px-4 glass-py-2 glass-surface-subtle/10 glass-glass-glass-backdrop-blur-md glass-contrast-guard glass-radius-full glass-contrast-guard", children: [jsx(Loader2, { className: 'w-5 h-5 animate-spin text-primary' }), jsx("span", { className: 'text-primary/80 glass-text-sm', children: "Loading more..." })] }) }); // Default error indicator const defaultErrorIndicator = jsxs(MotionFramer, { preset: "fadeIn", className: "glass-flex glass-flex-col glass-items-center glass-justify-center glass-py-8 glass-px-4", children: [jsxs("div", { className: 'glass-flex glass-items-center glass-gap-2 text-primary mb-3', children: [jsx(AlertCircle, { className: 'w-5 h-5' }), jsx("span", { className: 'glass-text-sm font-medium', children: "Error loading more items" })] }), jsxs("button", { onClick: handleRetry, className: 'glass-flex glass-items-center glass-gap-2 glass-px-4 glass-py-2 glass-surface-dark/30 hover:glass-surface-dark/40 glass-radius-lg transition-colors glass-border glass-border-white/20 hover:border-white/30 glass-focus glass-touch-target glass-contrast-guard glass-focus glass-touch-target glass-contrast-guard', children: [jsx(RefreshCw, { className: 'w-4 h-4' }), jsx("span", { className: "glass-text-sm", children: "Try again" })] })] }); // Default end message const defaultEndMessage = jsx(MotionFramer, { preset: "fadeIn", className: 'text-center glass-py-8', children: jsx("div", { className: 'text-primary/60 glass-text-sm', children: "You've reached the end" }) }); return jsxs("div", { "data-glass-component": true, ref: containerRef, className: cn("overflow-y-auto", reverse && "flex flex-col-reverse", className), ...props, children: [jsx("div", { className: cn(reverse && "flex flex-col-reverse"), children: children }), !reverse && jsx("div", { ref: sentinelRef, className: 'h-4' }), isLoading && !reverse && jsx("div", { className: "glass-py-4", children: loadingIndicator || defaultLoadingIndicator }), loadError && !reverse && jsx("div", { className: "glass-py-4", children: errorIndicator || defaultErrorIndicator }), hasReachedEnd && !hasMore && !isLoading && !loadError && !reverse && jsx("div", { className: "glass-py-4", children: endMessage || defaultEndMessage }), reverse && jsxs("div", { className: "glass-flex glass-flex-col-reverse", children: [jsx("div", { ref: sentinelRef, className: 'h-4' }), isLoading && jsx("div", { className: "glass-py-4", children: loadingIndicator || defaultLoadingIndicator }), loadError && jsx("div", { className: "glass-py-4", children: errorIndicator || defaultErrorIndicator }), hasReachedEnd && !hasMore && !isLoading && !loadError && jsx("div", { className: "glass-py-4", children: endMessage || defaultEndMessage })] })] }); }; export { GlassInfiniteScroll, GlassInfiniteScroll as default }; //# sourceMappingURL=GlassInfiniteScroll.js.map