Bump Vite 8, matrix-js-sdk, and related deps; fix immer/vanilla-extract imports for new majors. Replace blocking sync error dialog with background recovery on wake/offline, and disable Vite auto-open in dev. Add feature handoff documentation index.
105 lines
2.5 KiB
TypeScript
105 lines
2.5 KiB
TypeScript
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<ReturnType<typeof setTimeout>>();
|
|
|
|
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 (
|
|
<div className={transitionContainer}>
|
|
<div className={className} key={displayLocation.pathname}>
|
|
{children}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|