1import { useEffect, useRef } from 'react';
2
3const UPPER_THRESHOLD = 1 / 4;
4const LOWER_THRESHOLD = 3 / 4;
5
6/**
7 * Automatically scroll to a child element, by selector.
8 * Whenever the selector changes, it tries to detect if the selector is out of view.
9 * If it is, it will scroll to the closest upper or lower bound within the ref's scroll area.
10 */
11export function useAutoScrollTo<T extends HTMLElement = HTMLDivElement>(selector: string) {
12  const ref = useRef<T>(null);
13
14  useEffect(
15    function onMaybeScroll() {
16      if (!selector || !ref.current) return;
17      const target = ref.current.querySelector<HTMLElement>(selector);
18      if (target) {
19        const bounds = ref.current.getBoundingClientRect();
20        const position = ref.current.scrollTop;
21        const targetPosition = target.offsetTop;
22        const upperThreshold = bounds.height * UPPER_THRESHOLD;
23        const lowerThreshold = bounds.height * LOWER_THRESHOLD;
24
25        if (targetPosition < position + upperThreshold) {
26          ref.current.scrollTo({ top: Math.max(0, targetPosition - upperThreshold) });
27        } else if (targetPosition > position + lowerThreshold) {
28          ref.current.scrollTo({ top: targetPosition - lowerThreshold });
29        }
30      }
31    },
32    [ref.current, selector]
33  );
34
35  return { ref };
36}
37