feat: enhance scrollbar customization and layout adjustments for media grid

- Updated global CSS to implement custom scrollbar styles, including hover effects and animations.
- Adjusted layout in the media grid component to improve responsiveness and user experience, including dynamic height calculations and loading states.
- Disabled automatic loading of more items, allowing users to manually trigger loading for better control.
This commit is contained in:
tigeren 2025-08-28 15:52:55 +00:00
parent c264fd551d
commit 6ce4e5a877
3 changed files with 152 additions and 74 deletions

View File

@ -82,15 +82,75 @@
}
::-webkit-scrollbar-track {
@apply bg-muted/50;
background: transparent;
}
::-webkit-scrollbar-thumb {
@apply bg-muted-foreground/30 rounded-full;
background: hsl(var(--muted-foreground) / 0.3);
border-radius: 4px;
transition: background 0.2s ease;
}
::-webkit-scrollbar-thumb:hover {
@apply bg-muted-foreground/50;
background: hsl(var(--muted-foreground) / 0.5);
}
::-webkit-scrollbar-corner {
background: transparent;
}
/* Custom scrollbar for react-window grids */
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: linear-gradient(180deg,
hsl(var(--primary) / 0.2) 0%,
hsl(var(--primary) / 0.4) 50%,
hsl(var(--primary) / 0.2) 100%);
border-radius: 3px;
transition: all 0.3s ease;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg,
hsl(var(--primary) / 0.4) 0%,
hsl(var(--primary) / 0.6) 50%,
hsl(var(--primary) / 0.4) 100%);
transform: scaleX(1.2);
}
.custom-scrollbar::-webkit-scrollbar-thumb:active {
background: linear-gradient(180deg,
hsl(var(--primary) / 0.6) 0%,
hsl(var(--primary) / 0.8) 50%,
hsl(var(--primary) / 0.6) 100%);
}
/* Hide scrollbar when not needed */
.custom-scrollbar::-webkit-scrollbar-thumb:vertical {
min-height: 40px;
}
/* Fancy scroll indicator animation */
@keyframes scrollPulse {
0%, 100% {
opacity: 0.3;
transform: scaleY(0.8);
}
50% {
opacity: 0.7;
transform: scaleY(1);
}
}
.scroll-indicator {
animation: scrollPulse 2s ease-in-out infinite;
}
/* Smooth transitions */

View File

@ -25,7 +25,7 @@ export default function RootLayout({
>
<div className="flex h-screen bg-gradient-to-br from-background via-background to-muted/20">
<Sidebar />
<main className="flex-1 overflow-y-auto bg-background/50 backdrop-blur-sm">
<main className="flex-1 bg-background/50 backdrop-blur-sm">
{children}
</main>
</div>

View File

@ -185,22 +185,8 @@ export default function VirtualizedMediaGrid({
fetchItems(0, searchTerm);
}, [type]);
useEffect(() => {
const observer = new IntersectionObserver(
entries => {
if (entries[0].isIntersecting && pagination.hasMore && !loadingRef.current) {
loadMoreItems();
}
},
{ threshold: 0.1 }
);
if (observerTarget.current) {
observer.observe(observerTarget.current);
}
return () => observer.disconnect();
}, [loadMoreItems, pagination.hasMore]);
// Disabled automatic loading to prevent premature batch loading
// Users will manually click "Load More" button when they want more content
const Cell = ({ columnIndex, rowIndex, style }: any) => {
const columnCount = getColumnCount();
@ -283,11 +269,23 @@ export default function VirtualizedMediaGrid({
const columnWidth = getColumnWidth();
const rowCount = Math.ceil(items.length / columnCount);
// Calculate available height for the grid more precisely
const getAvailableHeight = useCallback(() => {
if (typeof window === 'undefined') return 600;
// Calculate the actual header height and other UI elements
const headerHeight = 180; // Title, description, search bar
const bottomPadding = 120; // Load more button area
const availableHeight = window.innerHeight - headerHeight - bottomPadding;
// Ensure minimum height and maximum height
return Math.max(Math.min(availableHeight, window.innerHeight - 100), 400);
}, []);
if (loading && items.length === 0) {
return (
<div className="min-h-screen p-6">
<div className="max-w-7xl mx-auto">
<div className="flex items-center justify-center min-h-[60vh]">
<div className="h-screen flex flex-col overflow-hidden">
<div className="flex-1 flex items-center justify-center">
<div className="text-center">
<div className="w-16 h-16 bg-gradient-to-br from-primary to-primary/80 rounded-2xl flex items-center justify-center mx-auto mb-4 animate-pulse shadow-lg">
{type === 'video' ?
@ -299,15 +297,14 @@ export default function VirtualizedMediaGrid({
</div>
</div>
</div>
</div>
);
}
return (
<div className="min-h-screen p-6">
<div ref={containerRef} className="max-w-7xl mx-auto">
<div className="h-screen flex flex-col overflow-hidden">
<div ref={containerRef} className="flex-1 flex flex-col max-w-7xl mx-auto w-full overflow-hidden">
{/* Header */}
<div className="mb-6">
<div className="flex-shrink-0 p-6 pb-4">
<div className="flex items-center gap-4 mb-4">
<div className={`w-12 h-12 bg-gradient-to-br rounded-xl flex items-center justify-center shadow-lg ${
type === 'video' ? 'from-red-500 to-red-600' :
@ -343,30 +340,50 @@ export default function VirtualizedMediaGrid({
</div>
</div>
{/* Media Grid */}
{/* Media Grid Container */}
<div className="flex-1 relative overflow-hidden">
{items.length > 0 && containerWidth > 0 ? (
<div className="w-full">
<div className="h-full relative overflow-hidden">
<FixedSizeGrid
columnCount={columnCount}
columnWidth={columnWidth}
height={Math.min(window.innerHeight - 200, rowCount * 300)}
height={getAvailableHeight()}
rowCount={rowCount}
rowHeight={300}
width={containerWidth}
itemData={items}
className="custom-scrollbar"
>
{Cell}
</FixedSizeGrid>
{/* Fancy scroll indicator */}
<div className="absolute right-2 top-1/2 transform -translate-y-1/2 w-2 h-32 bg-gradient-to-b from-primary/20 via-primary/40 to-primary/20 rounded-full opacity-0 hover:opacity-100 transition-opacity duration-300 pointer-events-none">
<div className="w-full h-full bg-gradient-to-b from-primary/60 via-primary to-primary/60 rounded-full scroll-indicator"></div>
</div>
{pagination.hasMore && (
<div ref={observerTarget} className="flex justify-center py-8">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
<div className="flex justify-center py-8">
{isLoadingMore ? (
<div className="flex items-center gap-3">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-primary"></div>
<span className="text-sm text-muted-foreground">Loading more {type === 'bookmark' ? 'items' : `${type}s`}...</span>
</div>
) : (
<Button
onClick={loadMoreItems}
disabled={loadingRef.current}
className="px-6 py-2"
>
Load More {type === 'bookmark' ? 'Items' : `${type}s`}
</Button>
)}
</div>
)}
</div>
) : items.length === 0 ? (
<div className="text-center py-20">
<div className="max-w-sm mx-auto">
<div className="flex items-center justify-center h-full">
<div className="text-center max-w-sm">
<div className="w-16 h-16 bg-muted rounded-2xl flex items-center justify-center mx-auto mb-4">
<Search className="h-8 w-8 text-muted-foreground" />
</div>
@ -381,11 +398,12 @@ export default function VirtualizedMediaGrid({
</div>
</div>
) : (
<div className="flex items-center justify-center min-h-[60vh]">
<div className="flex items-center justify-center h-full">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
)}
</div>
</div>
</div>
);
}