import clamp from 'lodash/clamp'; import throttle from 'lodash/throttle'; import React from 'react'; const ScrollDirectionContext = React.createContext<'up' | 'down' | null>(null); import isBrowser from 'lib/isBrowser'; const SCROLL_DIFF_THRESHOLD = 20; type Directions = 'up' | 'down'; interface Props { children: React.ReactNode; } export function ScrollDirectionProvider({ children }: Props) { const prevScrollPosition = React.useRef(isBrowser() ? window.pageYOffset : 0); const [ scrollDirection, setDirection ] = React.useState(null); const handleScroll = React.useCallback(() => { const currentScrollPosition = clamp(window.pageYOffset, 0, window.document.body.scrollHeight - window.innerHeight); const scrollDiff = currentScrollPosition - prevScrollPosition.current; if (window.pageYOffset === 0) { setDirection(null); } else if (Math.abs(scrollDiff) > SCROLL_DIFF_THRESHOLD) { setDirection(scrollDiff < 0 ? 'up' : 'down'); } prevScrollPosition.current = currentScrollPosition; }, [ ]); React.useEffect(() => { const throttledHandleScroll = throttle(handleScroll, 300); window.addEventListener('scroll', throttledHandleScroll); return () => { window.removeEventListener('scroll', throttledHandleScroll); }; // replicate componentDidMount // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( { children } ); } export function useScrollDirection() { const context = React.useContext(ScrollDirectionContext); if (context === undefined) { throw new Error('useScrollDirection must be used within a ScrollDirectionProvider'); } return context; }