From c3c9c2451642cbf22deaab7bee0cd47d6566af6b Mon Sep 17 00:00:00 2001 From: sebastjanartic <45803536-sebastjanartic@users.noreply.replit.com> Date: Wed, 3 Sep 2025 08:23:36 +0000 Subject: [PATCH] Add mobile swipe navigation with indicator dots to video carousels Implement touch event handlers for swiping between video cards on mobile devices, and display pagination dots to indicate the current video and total videos. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 2cd2c0bc-434c-4bc9-ad3f-b99d3897a0d1 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/2cd2c0bc-434c-4bc9-ad3f-b99d3897a0d1/OdlP8Wj --- client/src/components/netflix-grid.tsx | 84 ++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/client/src/components/netflix-grid.tsx b/client/src/components/netflix-grid.tsx index e7ae852..1fbb9be 100644 --- a/client/src/components/netflix-grid.tsx +++ b/client/src/components/netflix-grid.tsx @@ -188,6 +188,9 @@ function CategoryRow({ category, onVideoClick, hideScrollButtons = false }: Cate const [clickedVideoId, setClickedVideoId] = useState(null); const [canScrollLeft, setCanScrollLeft] = useState(false); const [canScrollRight, setCanScrollRight] = useState(true); + const [currentIndex, setCurrentIndex] = useState(0); + const [touchStart, setTouchStart] = useState(0); + const [touchEnd, setTouchEnd] = useState(0); const checkScrollButtons = () => { if (scrollRef.current) { @@ -252,6 +255,59 @@ function CategoryRow({ category, onVideoClick, hideScrollButtons = false }: Cate const handleScroll = () => { checkScrollButtons(); + + // Calculate current index for mobile dots + if (scrollRef.current) { + const containerWidth = scrollRef.current.clientWidth; + const scrollLeft = scrollRef.current.scrollLeft; + const cardWidth = containerWidth; // Full width cards on mobile + const newIndex = Math.round(scrollLeft / cardWidth); + setCurrentIndex(newIndex); + } + }; + + // Touch handlers for mobile swipe + const handleTouchStart = (e: React.TouchEvent) => { + setTouchEnd(0); + setTouchStart(e.targetTouches[0].clientX); + }; + + const handleTouchMove = (e: React.TouchEvent) => { + setTouchEnd(e.targetTouches[0].clientX); + }; + + const handleTouchEnd = () => { + if (!touchStart || !touchEnd) return; + + const distance = touchStart - touchEnd; + const isLeftSwipe = distance > 50; // Swipe left (next) + const isRightSwipe = distance < -50; // Swipe right (previous) + + if (isLeftSwipe && currentIndex < Math.min(10, category.videos.length) - 1) { + // Navigate to next card + const nextIndex = currentIndex + 1; + if (scrollRef.current) { + const containerWidth = scrollRef.current.clientWidth; + scrollRef.current.scrollTo({ + left: nextIndex * containerWidth, + behavior: 'smooth' + }); + setCurrentIndex(nextIndex); + } + } + + if (isRightSwipe && currentIndex > 0) { + // Navigate to previous card + const prevIndex = currentIndex - 1; + if (scrollRef.current) { + const containerWidth = scrollRef.current.clientWidth; + scrollRef.current.scrollTo({ + left: prevIndex * containerWidth, + behavior: 'smooth' + }); + setCurrentIndex(prevIndex); + } + } }; return ( @@ -295,6 +351,9 @@ function CategoryRow({ category, onVideoClick, hideScrollButtons = false }: Cate className="flex gap-3 overflow-x-auto scrollbar-hide py-4 px-2" style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }} onScroll={handleScroll} + onTouchStart={handleTouchStart} + onTouchMove={handleTouchMove} + onTouchEnd={handleTouchEnd} > {category.videos.map((video, index) => (
))}
+ + {/* Mobile navigation dots - only visible on mobile, under all video rows */} +
+ {Array.from({ length: Math.min(10, category.videos.length) }, (_, index) => ( +
);