use-scroll-active.ts1.3 KBView on GitHub
'use client';

import { useEffect, useRef, useState } from 'react';

/**
 * True while the element is actively being scrolled, false ~`idleMs` after it stops.
 *
 * Used to show a scrollbar only during scrolling. `scrollbar-width: none` can't do this on its
 * own — it hides the bar permanently — and the OS "always show scrollbars" setting overrides the
 * usual overlay behaviour, so the visibility has to be driven explicitly.
 *
 * Returns a ref to attach to the scroll container plus the current state:
 *
 *   const { ref, scrolling } = useScrollActive();
 *   <div ref={ref} className={cn('overflow-x-auto', scrolling ? 'scrollbar-thin' : 'scrollbar-none')} />
 */
export function useScrollActive<T extends HTMLElement = HTMLDivElement>(idleMs = 800) {
  const ref = useRef<T | null>(null);
  const [scrolling, setScrolling] = useState(false);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;

    let timer: ReturnType<typeof setTimeout> | undefined;
    const onScroll = () => {
      setScrolling(true);
      if (timer) clearTimeout(timer);
      timer = setTimeout(() => setScrolling(false), idleMs);
    };

    el.addEventListener('scroll', onScroll, { passive: true });
    return () => {
      el.removeEventListener('scroll', onScroll);
      if (timer) clearTimeout(timer);
    };
  }, [idleMs]);

  return { ref, scrolling };
}