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:
parent
1d1cbe4271
commit
c3c9c24516
@ -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>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user