import { useState, useEffect, useRef } from 'react' import { useReducedMotion, useDidUpdate } from '~/hooks' export type TransitionStatus = | 'entered' | 'exited' | 'entering' | 'exiting' | 'pre-exiting' | 'pre-entering' interface UseTransition { duration: number exitDuration: number timingFunction: string mounted: boolean onEnter?(): void onExit?(): void onEntered?(): void onExited?(): void } export function useTransition({ duration, exitDuration, timingFunction, mounted, onEnter, onExit, onEntered, onExited, }: UseTransition) { const reduceMotion = useReducedMotion() const [transitionStatus, setStatus] = useState( mounted ? 'entered' : 'exited' ) const [transitionDuration, setTransitionDuration] = useState( reduceMotion ? 0 : duration ) const timeoutRef = useRef(-1) const handleStateChange = (shouldMount: boolean) => { const preHandler = shouldMount ? onEnter : onExit const handler = shouldMount ? onEntered : onExited setStatus(shouldMount ? 'pre-entering' : 'pre-exiting') window.clearTimeout(timeoutRef.current) const _duration = reduceMotion ? 0 : shouldMount ? duration : exitDuration setTransitionDuration(_duration) if (_duration === 0) { typeof preHandler === 'function' && preHandler() typeof handler === 'function' && handler() setStatus(shouldMount ? 'entered' : 'exited') } else { const preStateTimeout = window.setTimeout(() => { typeof preHandler === 'function' && preHandler() setStatus(shouldMount ? 'entering' : 'exiting') }, 10) timeoutRef.current = window.setTimeout(() => { window.clearTimeout(preStateTimeout) typeof handler === 'function' && handler() setStatus(shouldMount ? 'entered' : 'exited') }, _duration) } } useDidUpdate(() => { handleStateChange(mounted) }, [mounted]) useEffect(() => () => window.clearTimeout(timeoutRef.current), []) return { transitionDuration, transitionStatus, transitionTimingFunction: timingFunction, } }