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:
parent
208d3d9f76
commit
d4b1359b79
BIN
attached_assets/image_1756478620689.png
Normal file
BIN
attached_assets/image_1756478620689.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
@ -1,4 +1,4 @@
|
|||||||
import { useState, useRef } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import { type Video } from "@shared/schema";
|
import { type Video } from "@shared/schema";
|
||||||
import VideoCard from "./video-card";
|
import VideoCard from "./video-card";
|
||||||
import BunnyVideoModal from "./bunny-video-modal";
|
import BunnyVideoModal from "./bunny-video-modal";
|
||||||
@ -134,28 +134,30 @@ interface CategoryRowProps {
|
|||||||
function CategoryRow({ category, onVideoClick }: CategoryRowProps) {
|
function CategoryRow({ category, onVideoClick }: CategoryRowProps) {
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
const scrollIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
const scrollIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
const [isLeftButtonHovered, setIsLeftButtonHovered] = useState(false);
|
const [showLeftButton, setShowLeftButton] = useState(false);
|
||||||
const [isRightButtonHovered, setIsRightButtonHovered] = useState(false);
|
const [showRightButton, setShowRightButton] = useState(true);
|
||||||
|
|
||||||
const scroll = (direction: 'left' | 'right') => {
|
const scroll = (direction: 'left' | 'right') => {
|
||||||
if (!scrollRef.current) return;
|
if (!scrollRef.current) return;
|
||||||
|
|
||||||
const containerWidth = scrollRef.current.clientWidth;
|
const container = scrollRef.current;
|
||||||
const scrollAmount = containerWidth * 0.8; // 80% of container width
|
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') {
|
if (direction === 'left') {
|
||||||
scrollRef.current.scrollBy({
|
targetScroll = Math.max(0, currentScroll - scrollAmount);
|
||||||
left: -scrollAmount,
|
|
||||||
behavior: 'smooth'
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
scrollRef.current.scrollBy({
|
targetScroll = Math.min(maxScroll, currentScroll + scrollAmount);
|
||||||
left: scrollAmount,
|
|
||||||
behavior: 'smooth'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
container.scrollTo({
|
||||||
|
left: targetScroll,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const startAutoScroll = (direction: 'left' | 'right') => {
|
const startAutoScroll = (direction: 'left' | 'right') => {
|
||||||
@ -163,6 +165,22 @@ function CategoryRow({ category, onVideoClick }: CategoryRowProps) {
|
|||||||
scroll(direction);
|
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 = () => {
|
const stopAutoScroll = () => {
|
||||||
if (scrollIntervalRef.current) {
|
if (scrollIntervalRef.current) {
|
||||||
clearInterval(scrollIntervalRef.current);
|
clearInterval(scrollIntervalRef.current);
|
||||||
@ -186,15 +204,11 @@ function CategoryRow({ category, onVideoClick }: CategoryRowProps) {
|
|||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setIsLeftButtonHovered(true);
|
|
||||||
startAutoScroll('left');
|
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.stopPropagation();
|
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"
|
data-testid="button-scroll-left"
|
||||||
>
|
>
|
||||||
<ChevronLeft className="w-6 h-6 text-white" />
|
<ChevronLeft className="w-6 h-6 text-white" />
|
||||||
@ -209,15 +223,11 @@ function CategoryRow({ category, onVideoClick }: CategoryRowProps) {
|
|||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setIsRightButtonHovered(true);
|
|
||||||
startAutoScroll('right');
|
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.stopPropagation();
|
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"
|
data-testid="button-scroll-right"
|
||||||
>
|
>
|
||||||
<ChevronRight className="w-6 h-6 text-white" />
|
<ChevronRight className="w-6 h-6 text-white" />
|
||||||
@ -267,6 +277,7 @@ function CategoryRow({ category, onVideoClick }: CategoryRowProps) {
|
|||||||
<div
|
<div
|
||||||
ref={scrollRef}
|
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"
|
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={{
|
style={{
|
||||||
scrollbarWidth: 'none',
|
scrollbarWidth: 'none',
|
||||||
msOverflowStyle: 'none',
|
msOverflowStyle: 'none',
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user