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`}...
+
+ ) : (
+
+ Load 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'}
+
+
+
+ ) : (
+
+ )}
+
);