140 lines
4.3 KiB
TypeScript
140 lines
4.3 KiB
TypeScript
import { useComputed, useSignal } from "@preact/signals";
|
|
import { useEffect } from "preact/hooks";
|
|
import { Home, NotebookPen, FileText, CodeXml } from 'lucide-preact';
|
|
|
|
interface NavigationBarProps {
|
|
currentPath: string;
|
|
}
|
|
|
|
export default function NavigationBar({ currentPath }: NavigationBarProps) {
|
|
const isScrolling = useSignal(false);
|
|
const prevScrollPos = useSignal(0);
|
|
const currentClientPath = useSignal(currentPath);
|
|
|
|
const isVisible = useComputed(() => {
|
|
if (prevScrollPos.value < 50) return true;
|
|
|
|
const currentPos = typeof window !== "undefined" ? globalThis.scrollY : 0;
|
|
return prevScrollPos.value > currentPos;
|
|
});
|
|
|
|
// Update client path when location changes
|
|
useEffect(() => {
|
|
const updatePath = () => {
|
|
if (typeof window !== "undefined") {
|
|
currentClientPath.value = window.location.pathname;
|
|
}
|
|
};
|
|
|
|
// Set initial path
|
|
updatePath();
|
|
|
|
// Listen for Astro's view transition events
|
|
const handleAstroNavigation = () => {
|
|
updatePath();
|
|
};
|
|
|
|
// Listen for astro:page-load event which fires after navigation completes
|
|
document.addEventListener('astro:page-load', handleAstroNavigation);
|
|
|
|
// Also listen for astro:after-swap as a backup
|
|
document.addEventListener('astro:after-swap', handleAstroNavigation);
|
|
|
|
// Listen for regular navigation events as fallback
|
|
window.addEventListener('popstate', updatePath);
|
|
|
|
return () => {
|
|
document.removeEventListener('astro:page-load', handleAstroNavigation);
|
|
document.removeEventListener('astro:after-swap', handleAstroNavigation);
|
|
window.removeEventListener('popstate', updatePath);
|
|
};
|
|
}, []);
|
|
|
|
// Use the client path
|
|
const activePath = currentClientPath.value;
|
|
|
|
// Normalize path by removing trailing slashes for consistent comparison
|
|
const normalizedPath = activePath.endsWith('/') && activePath.length > 1
|
|
? activePath.slice(0, -1)
|
|
: activePath;
|
|
|
|
const isPostsPath = (path: string) => {
|
|
return path.startsWith("/posts") || path.startsWith("/post/");
|
|
};
|
|
|
|
useEffect(() => {
|
|
let scrollTimer: ReturnType<typeof setTimeout> | undefined;
|
|
|
|
const handleScroll = () => {
|
|
isScrolling.value = true;
|
|
prevScrollPos.value = globalThis.scrollY;
|
|
|
|
if (scrollTimer) clearTimeout(scrollTimer);
|
|
|
|
scrollTimer = setTimeout(() => {
|
|
isScrolling.value = false;
|
|
}, 200);
|
|
};
|
|
|
|
globalThis.addEventListener("scroll", handleScroll);
|
|
|
|
return () => {
|
|
globalThis.removeEventListener("scroll", handleScroll);
|
|
if (scrollTimer) clearTimeout(scrollTimer);
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<div
|
|
class={`fixed bottom-3 sm:bottom-4 left-1/2 transform -translate-x-1/2 z-20 transition-all duration-300 ${
|
|
isScrolling.value ? "opacity-30" : "opacity-100"
|
|
} ${isVisible.value ? "translate-y-0" : "translate-y-20"}`}
|
|
>
|
|
<div class="overflow-visible">
|
|
<ul class="menu menu-horizontal bg-base-200 rounded-box p-1.5 sm:p-2 shadow-lg flex flex-nowrap whitespace-nowrap">
|
|
<li class="mx-0.5 sm:mx-1">
|
|
<a href="/" class={normalizedPath === "/" ? "menu-active" : ""}>
|
|
<div class="tooltip" data-tip="Home">
|
|
<Home size={18} class="sm:w-5 sm:h-5" />
|
|
</div>
|
|
</a>
|
|
</li>
|
|
|
|
<li class="mx-0.5 sm:mx-1">
|
|
<a
|
|
href="/posts"
|
|
class={isPostsPath(normalizedPath) ? "menu-active" : ""}
|
|
>
|
|
<div class="tooltip" data-tip="Posts">
|
|
<NotebookPen size={18} class="sm:w-5 sm:h-5" />
|
|
</div>
|
|
</a>
|
|
</li>
|
|
|
|
<li class="mx-0.5 sm:mx-1">
|
|
<a
|
|
href="/resume"
|
|
class={normalizedPath === "/resume" ? "menu-active" : ""}
|
|
>
|
|
<div class="tooltip" data-tip="Resume">
|
|
<FileText size={18} class="sm:w-5 sm:h-5" />
|
|
</div>
|
|
</a>
|
|
</li>
|
|
|
|
<li class="mx-0.5 sm:mx-1">
|
|
<a
|
|
href="/projects"
|
|
class={normalizedPath.startsWith("/projects") ? "menu-active" : ""}
|
|
>
|
|
<div class="tooltip" data-tip="Projects">
|
|
<CodeXml size={18} class="sm:w-5 sm:h-5" />
|
|
</div>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|