add horizontal scroll to feature carousel (#1123)

This commit is contained in:
jeffvli
2025-12-27 18:29:10 -08:00
parent aae68853ef
commit e821397e6c
@@ -1,7 +1,7 @@
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
import { AnimatePresence, motion } from 'motion/react'; import { AnimatePresence, motion } from 'motion/react';
import { useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { generatePath, Link } from 'react-router'; import { generatePath, Link } from 'react-router';
import styles from './feature-carousel.module.css'; import styles from './feature-carousel.module.css';
@@ -209,28 +209,70 @@ export const FeatureCarousel = ({ data, onNearEnd }: FeatureCarouselProps) => {
} }
}, [data, startIndex, itemsPerRow, onNearEnd]); }, [data, startIndex, itemsPerRow, onNearEnd]);
const handleNext = (e?: MouseEvent<HTMLButtonElement>) => { const handleNext = useCallback(
(e?: MouseEvent<HTMLButtonElement>) => {
e?.preventDefault(); e?.preventDefault();
e?.stopPropagation(); e?.stopPropagation();
if (!data) return; if (!data) return;
directionRef.current = { isNext: true }; directionRef.current = { isNext: true };
setStartIndex((prev) => (prev + itemsPerRow) % data.length); setStartIndex((prev) => (prev + itemsPerRow) % data.length);
}; },
[data, itemsPerRow],
);
const handlePrevious = (e?: MouseEvent<HTMLButtonElement>) => { const handlePrevious = useCallback(
(e?: MouseEvent<HTMLButtonElement>) => {
e?.preventDefault(); e?.preventDefault();
e?.stopPropagation(); e?.stopPropagation();
if (!data) return; if (!data) return;
directionRef.current = { isNext: false }; directionRef.current = { isNext: false };
setStartIndex((prev) => (prev - itemsPerRow + data.length) % data.length); setStartIndex((prev) => (prev - itemsPerRow + data.length) % data.length);
}; },
[data, itemsPerRow],
);
const canNavigate = data && data.length > itemsPerRow;
const wheelCooldownRef = useRef(0);
const wheelThreshold = 10;
const wheelCooldownMs = 250;
const handleWheel = useCallback(
(event: React.WheelEvent<HTMLDivElement>) => {
if (!canNavigate || !data) {
return;
}
if (!event.shiftKey) {
return;
}
const now = Date.now();
const elapsed = now - wheelCooldownRef.current;
const horizontalDelta = Math.abs(event.deltaY);
if (horizontalDelta < wheelThreshold || elapsed < wheelCooldownMs) {
return;
}
if (event.deltaY > 0) {
wheelCooldownRef.current = now;
handleNext();
} else if (event.deltaY < 0) {
wheelCooldownRef.current = now;
handlePrevious();
}
},
[canNavigate, data, handleNext, handlePrevious, wheelCooldownMs, wheelThreshold],
);
if (!data || data.length === 0) { if (!data || data.length === 0) {
return null; return null;
} }
return ( return (
<div className={styles.carouselContainer} ref={containerRef}> <div className={styles.carouselContainer} onWheel={handleWheel} ref={containerRef}>
<AnimatePresence initial={false} mode="popLayout"> <AnimatePresence initial={false} mode="popLayout">
<motion.div <motion.div
animate="animate" animate="animate"