import React, { useEffect, useRef, useState } from 'react'; import { useLocation } from 'react-router-dom'; import { style } from '@vanilla-extract/css'; const transitionContainer = style({ position: 'relative', width: '100%', height: '100%', overflow: 'hidden', }); const transitionContent = style({ width: '100%', height: '100%', }); const animateIn = style({ animation: 'routeFadeSlideIn 0.25s cubic-bezier(0.4, 0, 0.2, 1)', '@keyframes': { routeFadeSlideIn: { from: { opacity: 0, transform: 'translateX(12px)', }, to: { opacity: 1, transform: 'translateX(0)', }, }, }, }); const animateOut = style({ animation: 'routeFadeSlideOut 0.2s cubic-bezier(0.4, 0, 0.2, 1) forwards', '@keyframes': { routeFadeSlideOut: { from: { opacity: 1, transform: 'translateX(0)', }, to: { opacity: 0, transform: 'translateX(-12px)', }, }, }, }); interface RouteTransitionProps { children: React.ReactNode; } /** * Wraps content in a transition animation that triggers on route change */ export function RouteTransition({ children }: RouteTransitionProps) { const location = useLocation(); const [displayLocation, setDisplayLocation] = useState(location); const [transitionStage, setTransitionStage] = useState<'entering' | 'exiting' | 'idle'>('idle'); const timeoutRef = useRef>(); useEffect(() => { if (location.pathname !== displayLocation.pathname) { // Start exit animation setTransitionStage('exiting'); // Clear any existing timeout if (timeoutRef.current) { clearTimeout(timeoutRef.current); } // Wait for exit animation, then update location and enter timeoutRef.current = setTimeout(() => { setDisplayLocation(location); setTransitionStage('entering'); // Reset to idle after enter animation timeoutRef.current = setTimeout(() => { setTransitionStage('idle'); }, 250); }, 200); } return () => { if (timeoutRef.current) { clearTimeout(timeoutRef.current); } }; }, [location, displayLocation]); const className = transitionStage === 'entering' ? `${transitionContent} ${animateIn}` : transitionStage === 'exiting' ? `${transitionContent} ${animateOut}` : transitionContent; return (
{children}
); }