62 lines
2.2 KiB
TypeScript
62 lines
2.2 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect, useRef, useState } from 'react';
|
|
import { usePathname } from 'next/navigation';
|
|
|
|
export function NavigationProgress() {
|
|
const pathname = usePathname();
|
|
const [visible, setVisible] = useState(false);
|
|
const [width, setWidth] = useState(0);
|
|
const timer = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
|
|
useEffect(() => {
|
|
setVisible(false);
|
|
setWidth(100);
|
|
const hide = window.setTimeout(() => {
|
|
setWidth(0);
|
|
}, 180);
|
|
if (timer.current) clearInterval(timer.current);
|
|
return () => window.clearTimeout(hide);
|
|
}, [pathname]);
|
|
|
|
useEffect(() => {
|
|
const onClick = (event: MouseEvent) => {
|
|
if (event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
const anchor = (event.target as HTMLElement | null)?.closest('a');
|
|
if (!anchor) return;
|
|
const href = anchor.getAttribute('href');
|
|
if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:')) return;
|
|
if (anchor.target === '_blank' || anchor.hasAttribute('download')) return;
|
|
try {
|
|
const url = new URL(href, window.location.origin);
|
|
if (url.origin !== window.location.origin) return;
|
|
if (url.pathname === window.location.pathname && url.search === window.location.search) return;
|
|
} catch {
|
|
return;
|
|
}
|
|
setVisible(true);
|
|
setWidth(18);
|
|
if (timer.current) clearInterval(timer.current);
|
|
timer.current = setInterval(() => {
|
|
setWidth((current) => (current >= 88 ? current : current + 6 + Math.random() * 8));
|
|
}, 180);
|
|
};
|
|
|
|
document.addEventListener('click', onClick, true);
|
|
return () => {
|
|
document.removeEventListener('click', onClick, true);
|
|
if (timer.current) clearInterval(timer.current);
|
|
};
|
|
}, []);
|
|
|
|
if (!visible && width === 0) return null;
|
|
|
|
return (
|
|
<div className="fixed top-0 left-0 right-0 z-[80] h-[2px] pointer-events-none">
|
|
<div
|
|
className="h-full bg-primary transition-[width] duration-200 ease-out"
|
|
style={{ width: `${width}%` }}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|