import { ReactNode, useContext, useEffect, useRef, useState } from 'react'; import { RouteGlobalContext, RouteLevelContext } from './context.js'; export interface TransitionIndicatorProps { children: ReactNode; /** * Delay showing the indicator until the transition has been pending for this * long. This is useful for avoiding flickering when the transition is * fast. */ delay?: number; } export function TransitionIndicator({ children, delay, }: TransitionIndicatorProps) { const show = useIsRouteTransitioning(delay); if (show) return <>{children}; return null; } export function useIsRouteTransitioning(delay?: number) { const { transitioning } = useContext(RouteGlobalContext); const delayedTransitioning = useDelayedValue(transitioning, delay ?? 0); // if transitioning is false, show false immediately regardless. // otherwise wait (delay) before showing true. return delay ? !transitioning ? false : delayedTransitioning : transitioning; } /** * Reacts to changes inputValue, but waits for delay before updating the * returned value. If the inputValue changes again before the delay has * elapsed, the timer is reset. */ function useDelayedValue(inputValue: boolean, delay: number) { const [value, setValue] = useState(inputValue); const timeoutRef = useRef(null); useEffect(() => { if (inputValue === value) return; if (timeoutRef.current) { clearTimeout(timeoutRef.current); } timeoutRef.current = setTimeout(() => { setValue(inputValue); }, delay); return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } }; }, [inputValue, delay]); return value; }