aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
146 lines (143 loc) • 5.03 kB
JavaScript
'use client';
import { jsx, jsxs } from 'react/jsx-runtime';
import { cn } from '../../lib/utilsComprehensive.js';
import { Loader2 } from 'lucide-react';
import { useState, useRef, useMemo, 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';
/**
* GlassVirtualList component
* High-performance virtualized list that only renders visible items
*/
const GlassVirtualList = ({
items,
height,
itemHeight,
estimatedItemHeight = 50,
overscan = 5,
smoothScroll = true,
loading = false,
onEndReached,
endThreshold = 100,
onScrollChange,
className,
...props
}) => {
const [scrollTop, setScrollTop] = useState(0);
const [containerHeight, setContainerHeight] = useState(height);
const containerRef = useRef(null);
const contentRef = useRef(null);
// Calculate total height
const totalHeight = useMemo(() => {
if (itemHeight) {
return (items?.length || 0) * itemHeight;
}
return items.reduce((sum, item) => sum + (item?.height || estimatedItemHeight), 0);
}, [items, itemHeight, estimatedItemHeight]);
// Get visible range
const visibleRange = useMemo(() => {
const itemSize = itemHeight || estimatedItemHeight;
const start = Math.floor(scrollTop / itemSize);
const end = Math.min((items?.length || 1) - 1, Math.ceil((scrollTop + containerHeight) / itemSize));
return {
start: Math.max(0, start - overscan),
end: Math.min(items.length - 1, end + overscan)
};
}, [scrollTop, containerHeight, itemHeight, estimatedItemHeight, items.length, overscan]);
// Get visible items
const visibleItems = useMemo(() => {
return items.slice(visibleRange.start, visibleRange.end + 1);
}, [items, visibleRange]);
// Calculate offset
const offsetY = useMemo(() => {
if (itemHeight) {
return visibleRange.start * itemHeight;
}
let offset = 0;
for (let i = 0; i < visibleRange.start; i++) {
offset += items[i]?.height || estimatedItemHeight;
}
return offset;
}, [visibleRange.start, itemHeight, items, estimatedItemHeight]);
// Handle scroll
const handleScroll = useCallback(e => {
const scrollTop = e.currentTarget.scrollTop;
setScrollTop(scrollTop);
onScrollChange?.(scrollTop);
// Check if near end for infinite scroll
if (onEndReached && scrollTop + containerHeight >= totalHeight - endThreshold) {
onEndReached();
}
}, [onScrollChange, onEndReached, containerHeight, totalHeight, endThreshold]);
// Handle resize
useEffect(() => {
const handleResize = () => {
if (containerRef.current) {
setContainerHeight(containerRef.current.clientHeight);
}
};
handleResize();
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []);
// Update container height when prop changes
useEffect(() => {
setContainerHeight(height);
}, [height]);
return jsx(MotionFramer, {
"data-glass-component": true,
preset: "fadeIn",
className: "glass-w-full",
children: jsxs("div", {
ref: containerRef,
className: cn("relative overflow-auto", smoothScroll && "scroll-smooth", className),
style: {
height: containerHeight
},
onScroll: handleScroll,
...props,
children: [jsx("div", {
ref: contentRef,
className: 'relative',
style: {
height: totalHeight
},
children: jsx("div", {
className: 'absolute top-0 left-0 right-0',
style: {
transform: `translateY(${offsetY}px)`
},
children: visibleItems.map((item, index) => {
const ItemComponent = item?.component;
visibleRange.start + index;
return jsx("div", {
style: {
height: itemHeight || item?.height || estimatedItemHeight
},
children: jsx(ItemComponent, {
...(item?.props || {})
})
}, item?.id);
})
})
}), loading && jsx("div", {
className: 'absolute bottom-4 left-1/2 transform -translate-x-1/2',
children: jsxs("div", {
className: "glass-flex glass-items-center glass-gap-2 glass-px-4 glass-py-2 glass-surface-dark/80 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'
}), jsx("span", {
className: 'text-primary glass-text-sm',
children: "Loading..."
})]
})
})]
})
});
};
export { GlassVirtualList, GlassVirtualList as default };
//# sourceMappingURL=GlassVirtualList.js.map