All files / src/hooks useScrollDirection.ts

95.83% Statements 23/24
90.9% Branches 10/11
100% Functions 5/5
100% Lines 19/19

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42                65x 65x 65x       65x 25x 25x   25x 5x 5x   5x   4x 5x     5x 4x     25x 25x 25x 25x       65x    
import { useEffect, useRef, useState } from "react";
 
type ScrollDirection = "up" | "down" | null;
 
export function useScrollDirection(
	targetRef: React.RefObject<HTMLElement | null>,
	threshold = 10,
): ScrollDirection {
	const [direction, setDirection] = useState<ScrollDirection>(null);
	const lastScrollY = useRef(0);
	const idleTimer = useRef<ReturnType<typeof setTimeout> | undefined>(
		undefined,
	);
 
	useEffect(() => {
		const el = targetRef.current;
		Iif (!el) return;
 
		const onScroll = () => {
			const currentY = el.scrollTop;
			const diff = currentY - lastScrollY.current;
 
			if (Math.abs(diff) < threshold) return;
 
			setDirection(diff > 0 ? "down" : "up");
			lastScrollY.current = currentY;
 
			// Show bars again after scrolling stops
			if (idleTimer.current) clearTimeout(idleTimer.current);
			idleTimer.current = setTimeout(() => setDirection(null), 800);
		};
 
		el.addEventListener("scroll", onScroll, { passive: true });
		return () => {
			el.removeEventListener("scroll", onScroll);
			if (idleTimer.current) clearTimeout(idleTimer.current);
		};
	}, [targetRef, threshold]);
 
	return direction;
}