From bf55002d74c2714f2ff5afc85c5f925bcca91eef Mon Sep 17 00:00:00 2001 From: bamiebot Date: Thu, 26 Mar 2026 18:50:43 +0100 Subject: [PATCH 1/2] feat: add sticky marketplace filter bar --- src/components/common/StickyFilterBar.tsx | 126 ++++++++++++++++++++++ src/pages/LandingPage.tsx | 25 +++-- 2 files changed, 141 insertions(+), 10 deletions(-) create mode 100644 src/components/common/StickyFilterBar.tsx diff --git a/src/components/common/StickyFilterBar.tsx b/src/components/common/StickyFilterBar.tsx new file mode 100644 index 00000000..fbf45f6c --- /dev/null +++ b/src/components/common/StickyFilterBar.tsx @@ -0,0 +1,126 @@ +import { useEffect, useState, type ReactNode } from 'react'; +import { cn } from '@/lib/utils'; + +interface StickyFilterBarProps { + eyebrow?: string; + title: string; + description?: string; + resultCount?: number; + children: ReactNode; + className?: string; +} + +const COMPACT_SCROLL_Y = 96; + +const StickyFilterBar: React.FC = ({ + eyebrow = 'Filters', + title, + description, + resultCount, + children, + className, +}) => { + const [isCompact, setIsCompact] = useState(() => + typeof window !== 'undefined' ? window.scrollY > COMPACT_SCROLL_Y : false + ); + + useEffect(() => { + if (typeof window === 'undefined') { + return; + } + + let ticking = false; + + const updateCompactState = () => { + ticking = false; + const nextIsCompact = window.scrollY > COMPACT_SCROLL_Y; + setIsCompact(prev => (prev === nextIsCompact ? prev : nextIsCompact)); + }; + + const onScroll = () => { + if (ticking) { + return; + } + + ticking = true; + window.requestAnimationFrame(updateCompactState); + }; + + window.addEventListener('scroll', onScroll, { passive: true }); + + return () => { + window.removeEventListener('scroll', onScroll); + }; + }, []); + + return ( +
+
+
+ +
+
+
+

+ {eyebrow} +

+
+

+ {title} +

+ {typeof resultCount === 'number' && ( + + {resultCount} {resultCount === 1 ? 'result' : 'results'} + + )} +
+ {description && ( +

+ {description} +

+ )} +
+
+ +
+
{children}
+ + {isCompact ? 'Filters stay pinned while you browse' : 'Scroll to keep filters in reach'} + +
+
+
+
+ ); +}; + +export default StickyFilterBar; diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx index 7e72e9d5..3261c362 100644 --- a/src/pages/LandingPage.tsx +++ b/src/pages/LandingPage.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useMemo } from 'react'; import { courseService, type Course } from '@/services/course.service'; import SearchBar from '@/components/common/SearchBar'; +import StickyFilterBar from '@/components/common/StickyFilterBar'; import CreatorCard from '@/components/common/CreatorCard'; import { CreatorGridSkeleton } from '@/components/common/CreatorSkeleton'; import EmptyState from '@/components/common/EmptyState'; @@ -125,19 +126,23 @@ function LandingPage() {

Access Layer

- - {/* Search Bar Integration */} -
- -
+ + + + {/* Marketplace Grid Section */} -
+
{isLoading ? ( ) : filteredCreators.length > 0 ? ( From 4811eb064257d6fe6ec53701cb01cfd8370a9595 Mon Sep 17 00:00:00 2001 From: bamiebot Date: Thu, 26 Mar 2026 20:12:20 +0100 Subject: [PATCH 2/2] fix: simplify sticky marketplace filter bar --- src/components/common/StickyFilterBar.tsx | 69 +++-------------------- src/pages/LandingPage.tsx | 2 +- 2 files changed, 8 insertions(+), 63 deletions(-) diff --git a/src/components/common/StickyFilterBar.tsx b/src/components/common/StickyFilterBar.tsx index fbf45f6c..2d70edec 100644 --- a/src/components/common/StickyFilterBar.tsx +++ b/src/components/common/StickyFilterBar.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, type ReactNode } from 'react'; +import { type ReactNode } from 'react'; import { cn } from '@/lib/utils'; interface StickyFilterBarProps { @@ -10,8 +10,6 @@ interface StickyFilterBarProps { className?: string; } -const COMPACT_SCROLL_Y = 96; - const StickyFilterBar: React.FC = ({ eyebrow = 'Filters', title, @@ -20,53 +18,15 @@ const StickyFilterBar: React.FC = ({ children, className, }) => { - const [isCompact, setIsCompact] = useState(() => - typeof window !== 'undefined' ? window.scrollY > COMPACT_SCROLL_Y : false - ); - - useEffect(() => { - if (typeof window === 'undefined') { - return; - } - - let ticking = false; - - const updateCompactState = () => { - ticking = false; - const nextIsCompact = window.scrollY > COMPACT_SCROLL_Y; - setIsCompact(prev => (prev === nextIsCompact ? prev : nextIsCompact)); - }; - - const onScroll = () => { - if (ticking) { - return; - } - - ticking = true; - window.requestAnimationFrame(updateCompactState); - }; - - window.addEventListener('scroll', onScroll, { passive: true }); - - return () => { - window.removeEventListener('scroll', onScroll); - }; - }, []); - return (
@@ -78,10 +38,7 @@ const StickyFilterBar: React.FC = ({

{title}

@@ -92,29 +49,17 @@ const StickyFilterBar: React.FC = ({ )}
{description && ( -

+

{description}

)}
-
+
{children}
- {isCompact ? 'Filters stay pinned while you browse' : 'Scroll to keep filters in reach'} + Filters stay pinned while you browse
diff --git a/src/pages/LandingPage.tsx b/src/pages/LandingPage.tsx index 3261c362..31388031 100644 --- a/src/pages/LandingPage.tsx +++ b/src/pages/LandingPage.tsx @@ -157,7 +157,7 @@ function LandingPage() { image="/images/no-results.png" title="No creators found" description={`We couldn't find any creators matching "${searchQuery}". Try a different name or handle.`} - onReset={handleResetSearch} + onReset={handleResetSearch} />
)}