77 lines
2.5 KiB
TypeScript
77 lines
2.5 KiB
TypeScript
import { ReactNode, useEffect, useState } from 'react';
|
|
import { useLocation } from 'react-router';
|
|
|
|
interface PageTransitionProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
/**
|
|
* Smooth page transition wrapper with modern loading indicator
|
|
* Provides seamless transitions between pages without feeling like a page load
|
|
* Uses subtle fade effects and minimal loading indicator
|
|
*/
|
|
export default function PageTransition({ children }: PageTransitionProps) {
|
|
const location = useLocation();
|
|
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
const [displayChildren, setDisplayChildren] = useState(children);
|
|
const [currentPath, setCurrentPath] = useState(location.pathname);
|
|
|
|
useEffect(() => {
|
|
// Only show transition if pathname actually changed
|
|
if (location.pathname === currentPath) {
|
|
setDisplayChildren(children);
|
|
return;
|
|
}
|
|
|
|
// Start transition with minimal delay
|
|
setIsTransitioning(true);
|
|
setCurrentPath(location.pathname);
|
|
|
|
// Quick fade-out, then swap content
|
|
const fadeOutTimer = setTimeout(() => {
|
|
setDisplayChildren(children);
|
|
}, 100);
|
|
|
|
// Complete transition quickly for smooth feel
|
|
const fadeInTimer = setTimeout(() => {
|
|
setIsTransitioning(false);
|
|
}, 200);
|
|
|
|
return () => {
|
|
clearTimeout(fadeOutTimer);
|
|
clearTimeout(fadeInTimer);
|
|
};
|
|
}, [location.pathname, children, currentPath]);
|
|
|
|
return (
|
|
<div className="relative min-h-screen">
|
|
{/* Subtle fade overlay - very light */}
|
|
<div
|
|
className={`absolute inset-0 bg-white/50 dark:bg-gray-900/50 backdrop-blur-sm transition-opacity duration-200 z-40 pointer-events-none ${
|
|
isTransitioning ? 'opacity-100' : 'opacity-0'
|
|
}`}
|
|
/>
|
|
|
|
{/* Minimal loading indicator - only shows briefly */}
|
|
{isTransitioning && (
|
|
<div className="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 z-50 pointer-events-none">
|
|
<div className="relative w-10 h-10">
|
|
<div className="absolute inset-0 border-2 border-gray-200/50 dark:border-gray-700/50 rounded-full"></div>
|
|
<div className="absolute inset-0 border-2 border-transparent border-t-brand-500 rounded-full animate-spin"></div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Page content with smooth fade */}
|
|
<div
|
|
className={`transition-opacity duration-200 ease-in-out ${
|
|
isTransitioning ? 'opacity-0' : 'opacity-100'
|
|
}`}
|
|
>
|
|
{displayChildren}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|