feat: change color
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
"use client";
|
||||
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
const TOP_BAR_HEIGHT = 3;
|
||||
const MAX_PERCENT = 90;
|
||||
const COMPLETE_PERCENT = 100;
|
||||
const TICK_MS = 50;
|
||||
const INCREMENT = 2;
|
||||
const COMPLETE_DURATION = 200;
|
||||
|
||||
export function TopProgressBar() {
|
||||
const pathname = usePathname();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [percent, setPercent] = useState(0);
|
||||
const timerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
||||
const completeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const prevPathnameRef = useRef(pathname);
|
||||
|
||||
const clearTimers = useCallback(() => {
|
||||
if (timerRef.current) {
|
||||
clearInterval(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
if (completeTimeoutRef.current) {
|
||||
clearTimeout(completeTimeoutRef.current);
|
||||
completeTimeoutRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
const startProgress = useCallback(() => {
|
||||
clearTimers();
|
||||
setVisible(true);
|
||||
setPercent(0);
|
||||
timerRef.current = setInterval(() => {
|
||||
setPercent((p) => {
|
||||
if (p >= MAX_PERCENT) {
|
||||
if (timerRef.current) {
|
||||
clearInterval(timerRef.current);
|
||||
timerRef.current = null;
|
||||
}
|
||||
return MAX_PERCENT;
|
||||
}
|
||||
return Math.min(p + INCREMENT, MAX_PERCENT);
|
||||
});
|
||||
}, TICK_MS);
|
||||
}, [clearTimers]);
|
||||
|
||||
const completeProgress = useCallback(() => {
|
||||
clearTimers();
|
||||
setPercent(COMPLETE_PERCENT);
|
||||
completeTimeoutRef.current = setTimeout(() => {
|
||||
setVisible(false);
|
||||
setPercent(0);
|
||||
completeTimeoutRef.current = null;
|
||||
}, COMPLETE_DURATION);
|
||||
}, [clearTimers]);
|
||||
|
||||
// Start bar when user clicks an internal link
|
||||
useEffect(() => {
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
const target = (e.target as Element).closest("a");
|
||||
if (!target?.href) return;
|
||||
try {
|
||||
const url = new URL(target.href);
|
||||
if (url.origin === window.location.origin && url.pathname !== pathname) {
|
||||
startProgress();
|
||||
}
|
||||
} catch {
|
||||
// ignore invalid URLs
|
||||
}
|
||||
};
|
||||
document.addEventListener("click", handleClick, true);
|
||||
return () => document.removeEventListener("click", handleClick, true);
|
||||
}, [pathname, startProgress]);
|
||||
|
||||
// Complete bar when pathname changes (route loaded)
|
||||
useEffect(() => {
|
||||
if (prevPathnameRef.current !== pathname) {
|
||||
prevPathnameRef.current = pathname;
|
||||
if (visible) completeProgress();
|
||||
}
|
||||
}, [pathname, visible, completeProgress]);
|
||||
|
||||
useEffect(() => {
|
||||
return clearTimers;
|
||||
}, [clearTimers]);
|
||||
|
||||
if (!visible) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed left-0 right-0 z-[9999] overflow-hidden bg-transparent"
|
||||
style={{ height: TOP_BAR_HEIGHT }}
|
||||
aria-hidden
|
||||
>
|
||||
<div
|
||||
className="h-full bg-primary transition-[width] duration-150 ease-out"
|
||||
style={{ width: `${percent}%` }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user