41 lines
1.2 KiB
TypeScript
41 lines
1.2 KiB
TypeScript
"use client";
|
|
|
|
import { motion, useIsPresent } from "motion/react";
|
|
import { usePathname, useSearchParams } from "next/navigation";
|
|
import { useEffect, useState } from "react";
|
|
|
|
/**
|
|
* Global smooth progress indicator for page transitions.
|
|
* Uses motion/react for high-performance 60fps animations.
|
|
*/
|
|
export function GlobalProgressBar() {
|
|
const pathname = usePathname();
|
|
const searchParams = useSearchParams();
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const isPresent = useIsPresent();
|
|
|
|
// biome-ignore lint/correctness/useExhaustiveDependencies: Route changes intentionally restart the indicator timer.
|
|
useEffect(() => {
|
|
setIsLoading(true);
|
|
const timer = setTimeout(() => {
|
|
setIsLoading(false);
|
|
}, 400);
|
|
|
|
return () => clearTimeout(timer);
|
|
}, [pathname, searchParams]);
|
|
|
|
if (!isLoading && isPresent) return null;
|
|
|
|
return (
|
|
<div className="fixed top-0 left-0 right-0 z-[99999] h-1 bg-transparent pointer-events-none overflow-hidden">
|
|
<motion.div
|
|
className="h-full bg-[var(--color-primary)] shadow-[0_0_10px_var(--color-primary)]"
|
|
initial={{ width: "0%", opacity: 1 }}
|
|
animate={{ width: "85%", opacity: 1 }}
|
|
exit={{ width: "100%", opacity: 0 }}
|
|
transition={{ duration: 0.3, ease: "easeInOut" }}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|