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
This commit is contained in:
sebastjanartic 2025-09-03 08:23:36 +00:00
parent 1d1cbe4271
commit c3c9c24516

View File

@ -188,6 +188,9 @@ function CategoryRow({ category, onVideoClick, hideScrollButtons = false }: Cate
const [clickedVideoId, setClickedVideoId] = useState<string | null>(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) => (
<div
@ -323,6 +382,31 @@ function CategoryRow({ category, onVideoClick, hideScrollButtons = false }: Cate
</div>
))}
</div>
{/* Mobile navigation dots - only visible on mobile, under all video rows */}
<div className="md:hidden flex justify-center mt-4 space-x-2">
{Array.from({ length: Math.min(10, category.videos.length) }, (_, index) => (
<button
key={index}
onClick={() => {
if (scrollRef.current) {
const containerWidth = scrollRef.current.clientWidth;
scrollRef.current.scrollTo({
left: index * containerWidth,
behavior: 'smooth'
});
setCurrentIndex(index);
}
}}
className={`w-2 h-2 rounded-full transition-all duration-300 ${
index === currentIndex
? 'bg-gradient-to-r from-purple-500 to-blue-500 scale-125'
: 'bg-white/30 hover:bg-white/50'
}`}
aria-label={`Go to card ${index + 1}`}
/>
))}
</div>
</div>
</div>
);