diff --git a/src/app/globals.css b/src/app/globals.css index eb03c44..2ffdb3f 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -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 */ diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3a997dd..b8a959f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -25,7 +25,7 @@ export default function RootLayout({ >
-
+
{children}
diff --git a/src/components/virtualized-media-grid.tsx b/src/components/virtualized-media-grid.tsx index 904a057..91b887a 100644 --- a/src/components/virtualized-media-grid.tsx +++ b/src/components/virtualized-media-grid.tsx @@ -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,20 +269,31 @@ 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 ( -
-
-
-
-
- {type === 'video' ? - : - - } -
-

Loading {type}s...

+
+
+
+
+ {type === 'video' ? + : + + }
+

Loading {type}s...

@@ -304,10 +301,10 @@ export default function VirtualizedMediaGrid({ } return ( -
-
+
+
{/* Header */} -
+
- {/* Media Grid */} - {items.length > 0 && containerWidth > 0 ? ( -
- - {Cell} - - - {pagination.hasMore && ( -
-
+ {/* Media Grid Container */} +
+ {items.length > 0 && containerWidth > 0 ? ( +
+ + {Cell} + + + {/* Fancy scroll indicator */} +
+
- )} -
- ) : items.length === 0 ? ( -
-
-
- -
-

- No {type === 'bookmark' ? 'bookmarked items' : `${type}s`} found -

-

- {searchTerm ? 'Try adjusting your search terms' : - type === 'bookmark' ? 'Start bookmarking videos and photos to see them here' : - 'Add media libraries and scan for content to get started'} -

+ + {pagination.hasMore && ( +
+ {isLoadingMore ? ( +
+
+ Loading more {type === 'bookmark' ? 'items' : `${type}s`}... +
+ ) : ( + + )} +
+ )}
-
- ) : ( -
-
-
- )} + ) : items.length === 0 ? ( +
+
+
+ +
+

+ No {type === 'bookmark' ? 'bookmarked items' : `${type}s`} found +

+

+ {searchTerm ? 'Try adjusting your search terms' : + type === 'bookmark' ? 'Start bookmarking videos and photos to see them here' : + 'Add media libraries and scan for content to get started'} +

+
+
+ ) : ( +
+
+
+ )} +
);