Improve carousel navigation and video scrolling behavior

Refactor the `CategoryRow` component to use `scrollTo` for smoother horizontal scrolling and dynamically show/hide navigation buttons based on scroll position.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 2eb1084e-b728-4449-9231-f1665924c8d5
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/2eb1084e-b728-4449-9231-f1665924c8d5/QCN70f2
This commit is contained in:
sebastjanartic 2025-08-29 14:45:30 +00:00
parent 208d3d9f76
commit d4b1359b79
2 changed files with 35 additions and 24 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

View File

@ -1,4 +1,4 @@
import { useState, useRef } from "react";
import { useState, useRef, useEffect } from "react";
import { type Video } from "@shared/schema";
import VideoCard from "./video-card";
import BunnyVideoModal from "./bunny-video-modal";
@ -134,28 +134,30 @@ interface CategoryRowProps {
function CategoryRow({ category, onVideoClick }: CategoryRowProps) {
const scrollRef = useRef<HTMLDivElement>(null);
const scrollIntervalRef = useRef<NodeJS.Timeout | null>(null);
const [isLeftButtonHovered, setIsLeftButtonHovered] = useState(false);
const [isRightButtonHovered, setIsRightButtonHovered] = useState(false);
const [showLeftButton, setShowLeftButton] = useState(false);
const [showRightButton, setShowRightButton] = useState(true);
const scroll = (direction: 'left' | 'right') => {
if (!scrollRef.current) return;
const containerWidth = scrollRef.current.clientWidth;
const scrollAmount = containerWidth * 0.8; // 80% of container width
const container = scrollRef.current;
const currentScroll = container.scrollLeft;
const maxScroll = container.scrollWidth - container.clientWidth;
const videoWidth = 224; // approx width of one video card + spacing
const scrollAmount = videoWidth * 3; // Show 3 videos at a time
console.log(`Scrolling ${direction}, container width: ${containerWidth}, scroll amount: ${scrollAmount}`);
let targetScroll;
if (direction === 'left') {
scrollRef.current.scrollBy({
left: -scrollAmount,
behavior: 'smooth'
});
targetScroll = Math.max(0, currentScroll - scrollAmount);
} else {
scrollRef.current.scrollBy({
left: scrollAmount,
behavior: 'smooth'
});
targetScroll = Math.min(maxScroll, currentScroll + scrollAmount);
}
container.scrollTo({
left: targetScroll,
behavior: 'smooth'
});
};
const startAutoScroll = (direction: 'left' | 'right') => {
@ -163,6 +165,22 @@ function CategoryRow({ category, onVideoClick }: CategoryRowProps) {
scroll(direction);
};
const checkScrollPosition = () => {
if (!scrollRef.current) return;
const container = scrollRef.current;
const currentScroll = container.scrollLeft;
const maxScroll = container.scrollWidth - container.clientWidth;
setShowLeftButton(currentScroll > 10); // Show left if not at start
setShowRightButton(currentScroll < maxScroll - 10); // Show right if not at end
};
// Check scroll position on mount and when videos change
useEffect(() => {
checkScrollPosition();
}, [category.videos]);
const stopAutoScroll = () => {
if (scrollIntervalRef.current) {
clearInterval(scrollIntervalRef.current);
@ -186,15 +204,11 @@ function CategoryRow({ category, onVideoClick }: CategoryRowProps) {
}}
onMouseEnter={(e) => {
e.stopPropagation();
setIsLeftButtonHovered(true);
startAutoScroll('left');
}}
onMouseLeave={(e) => {
e.stopPropagation();
setIsLeftButtonHovered(false);
stopAutoScroll();
}}
className="flex absolute left-2 top-[45%] -translate-y-1/2 w-12 h-12 z-30 bg-black/80 hover:bg-black/95 rounded-full items-center justify-center transition-all duration-300 cursor-pointer border border-white/30 shadow-lg opacity-0 group-hover:opacity-100 hover:!opacity-100"
className={`${showLeftButton ? 'opacity-0 group-hover:opacity-100 hover:!opacity-100' : 'hidden'} flex absolute left-2 top-[45%] -translate-y-1/2 w-12 h-12 z-30 bg-black/80 hover:bg-black/95 rounded-full items-center justify-center transition-all duration-300 cursor-pointer border border-white/30 shadow-lg`}
data-testid="button-scroll-left"
>
<ChevronLeft className="w-6 h-6 text-white" />
@ -209,15 +223,11 @@ function CategoryRow({ category, onVideoClick }: CategoryRowProps) {
}}
onMouseEnter={(e) => {
e.stopPropagation();
setIsRightButtonHovered(true);
startAutoScroll('right');
}}
onMouseLeave={(e) => {
e.stopPropagation();
setIsRightButtonHovered(false);
stopAutoScroll();
}}
className="flex absolute right-2 top-[45%] -translate-y-1/2 w-12 h-12 z-30 bg-black/80 hover:bg-black/95 rounded-full items-center justify-center transition-all duration-300 cursor-pointer border border-white/30 shadow-lg opacity-0 group-hover:opacity-100 hover:!opacity-100"
className={`${showRightButton ? 'opacity-0 group-hover:opacity-100 hover:!opacity-100' : 'hidden'} flex absolute right-2 top-[45%] -translate-y-1/2 w-12 h-12 z-30 bg-black/80 hover:bg-black/95 rounded-full items-center justify-center transition-all duration-300 cursor-pointer border border-white/30 shadow-lg`}
data-testid="button-scroll-right"
>
<ChevronRight className="w-6 h-6 text-white" />
@ -267,6 +277,7 @@ function CategoryRow({ category, onVideoClick }: CategoryRowProps) {
<div
ref={scrollRef}
className="flex space-x-2 md:space-x-3 overflow-x-auto scrollbar-hide pb-4 scroll-smooth px-4 md:px-0"
onScroll={checkScrollPosition}
style={{
scrollbarWidth: 'none',
msOverflowStyle: 'none',