canina/frontend/application/components/NavigationProgressBar.tsx
parsa aghaei 812bc2920c
Some checks failed
Deploy Canina / deploy (push) Successful in 1m46s
E2E Playwright Tests / Run Full E2E & Security Suites (push) Failing after 6s
feat(navigation): add custom glowing NavigationProgressBar, enable Swagger on localhost dev, set local store port to 3005, and remove hero-section-image.png
2026-08-29 13:53:21 +03:30

126 lines
3.6 KiB
TypeScript

"use client";
import React, { useEffect, useState, useRef, Suspense } from "react";
import { usePathname, useSearchParams } from "next/navigation";
function ProgressBarInner() {
const pathname = usePathname();
const searchParams = useSearchParams();
const [progress, setProgress] = useState(0);
const [visible, setVisible] = useState(false);
const timerRef = useRef<NodeJS.Timeout | null>(null);
const startProgress = () => {
if (timerRef.current) clearInterval(timerRef.current);
setVisible(true);
setProgress(20);
timerRef.current = setInterval(() => {
setProgress((prev) => {
if (prev < 60) return prev + Math.random() * 15;
if (prev < 85) return prev + Math.random() * 5;
return prev;
});
}, 150);
};
const completeProgress = () => {
if (timerRef.current) clearInterval(timerRef.current);
setProgress(100);
setTimeout(() => {
setVisible(false);
setTimeout(() => setProgress(0), 200);
}, 250);
};
// Complete progress on pathname or searchParams change
useEffect(() => {
completeProgress();
}, [pathname, searchParams]);
// Intercept click on internal links
useEffect(() => {
const handleDocumentClick = (e: MouseEvent) => {
const target = (e.target as HTMLElement)?.closest('a');
if (!target) return;
const href = target.getAttribute('href');
if (!href) return;
// Ignore external, anchor hash, tel, mailto, or new tab links
if (
href.startsWith('#') ||
href.startsWith('mailto:') ||
href.startsWith('tel:') ||
href.startsWith('javascript:') ||
target.getAttribute('target') === '_blank' ||
e.ctrlKey ||
e.metaKey ||
e.shiftKey ||
e.altKey
) {
return;
}
// Check if internal link
try {
const url = new URL(href, window.location.origin);
if (url.origin === window.location.origin) {
const currentUrl = new URL(window.location.href);
// If navigating to different path or search
if (url.pathname !== currentUrl.pathname || url.search !== currentUrl.search) {
startProgress();
}
}
} catch {
// Relative link
if (href.startsWith('/') && href !== window.location.pathname) {
startProgress();
}
}
};
document.addEventListener('click', handleDocumentClick, { capture: true });
return () => {
document.removeEventListener('click', handleDocumentClick, { capture: true });
if (timerRef.current) clearInterval(timerRef.current);
};
}, []);
if (!visible && progress === 0) return null;
return (
<div
className="fixed top-0 left-0 right-0 pointer-events-none transition-opacity duration-300"
style={{
zIndex: 9999999,
opacity: visible ? 1 : 0,
}}
>
{/* Glow Progress Bar */}
<div
className="h-[3.5px] bg-gradient-to-r from-sky-400 via-canina-blue to-emerald-400 transition-all duration-200 ease-out"
style={{
width: `${progress}%`,
boxShadow: '0 0 12px rgba(2, 132, 199, 0.9), 0 0 5px rgba(56, 189, 248, 0.8)',
}}
/>
{/* Trailing Shimmer Dot */}
<div
className="absolute top-0 right-0 -mt-[1px] h-[5px] w-24 bg-white/40 blur-[2px] transition-all duration-200"
style={{
transform: `translateX(${progress - 100}%)`,
}}
/>
</div>
);
}
export default function NavigationProgressBar() {
return (
<Suspense fallback={null}>
<ProgressBarInner />
</Suspense>
);
}