105 lines
4.3 KiB
TypeScript
105 lines
4.3 KiB
TypeScript
import { NavLink, useLocation, useNavigate } from "react-router-dom";
|
|
import { useEffect, useState } from "react";
|
|
import ThemeToggle from "./ThemeToggle";
|
|
|
|
export default function NavBar({
|
|
hideOn = ["/onboarding", "/login", "/register"],
|
|
}: {
|
|
hideOn?: string[];
|
|
}) {
|
|
const navigate = useNavigate();
|
|
const { pathname } = useLocation();
|
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
const linkClass = ({ isActive }: { isActive: boolean }) =>
|
|
"nav-link " + (isActive ? "nav-link-active" : "");
|
|
const mobileLinkClass = ({ isActive }: { isActive: boolean }) =>
|
|
"nav-link text-[--color-text] " +
|
|
(isActive ? "nav-link-active" : "hover:bg-[--color-ink]/20");
|
|
|
|
useEffect(() => {
|
|
setMenuOpen(false);
|
|
}, [pathname]);
|
|
|
|
if (hideOn.some((p) => pathname.startsWith(p))) return null;
|
|
|
|
return (
|
|
<header
|
|
className="topnav sticky top-0 z-40 border-b"
|
|
style={{ backdropFilter: "blur(8px)" }}
|
|
aria-label="Primary"
|
|
>
|
|
<div className="container h-14 min-h-14 flex items-center gap-2 flex-nowrap">
|
|
{/* Brand */}
|
|
<button
|
|
onClick={() => navigate("/")}
|
|
className="brand row items-center shrink-0 px-2 py-1 rounded-lg hover:bg-[--color-panel] transition-all"
|
|
aria-label="Go to dashboard"
|
|
>
|
|
<span className="font-bold text-xl tracking-wide">SkyMoney</span>
|
|
</button>
|
|
|
|
{/* Links */}
|
|
<nav className="ml-2 topnav-links items-center gap-1 flex-1 overflow-x-auto whitespace-nowrap hide-scrollbar">
|
|
<NavLink to="/" className={linkClass} end>Dashboard</NavLink>
|
|
<NavLink to="/spend" className={linkClass}>Transactions</NavLink>
|
|
<NavLink to="/income" className={linkClass}>Income</NavLink>
|
|
<NavLink to="/transactions" className={linkClass}>Records</NavLink>
|
|
<NavLink to="/rebalance" className={linkClass}>Rebalance</NavLink>
|
|
<NavLink to="/settings" className={linkClass}>Settings</NavLink>
|
|
</nav>
|
|
|
|
{/* Actions */}
|
|
<div className="ml-auto row gap-2 items-center shrink-0">
|
|
<div className="topnav-theme items-center gap-2">
|
|
<ThemeToggle size="sm" />
|
|
</div>
|
|
|
|
{/* Mobile menu */}
|
|
<div className="topnav-mobile relative">
|
|
<button
|
|
type="button"
|
|
className={
|
|
"rounded-xl border border-[--color-border] bg-[--color-panel] px-3 py-2 text-[--color-text] transition " +
|
|
(menuOpen ? "shadow-md" : "hover:bg-[--color-ink]/10")
|
|
}
|
|
aria-expanded={menuOpen}
|
|
aria-controls="mobile-menu"
|
|
onClick={() => setMenuOpen((open) => !open)}
|
|
>
|
|
<span className="sr-only">Open menu</span>
|
|
<span className="grid gap-1">
|
|
<span className="h-0.5 w-5 bg-current transition-all" />
|
|
<span className="h-0.5 w-5 bg-current transition-all" />
|
|
<span className="h-0.5 w-5 bg-current transition-all" />
|
|
</span>
|
|
</button>
|
|
<div
|
|
id="mobile-menu"
|
|
className={
|
|
"absolute right-0 top-full mt-2 w-64 origin-top-right rounded-2xl border bg-[--color-surface] p-3 shadow-lg transition-all duration-200 ease-out " +
|
|
(menuOpen
|
|
? "opacity-100 translate-y-0"
|
|
: "pointer-events-none opacity-0 -translate-y-2")
|
|
}
|
|
role="menu"
|
|
aria-hidden={!menuOpen}
|
|
>
|
|
<div className="flex flex-col gap-1">
|
|
<NavLink to="/" className={mobileLinkClass} end>Dashboard</NavLink>
|
|
<NavLink to="/spend" className={mobileLinkClass}>Transactions</NavLink>
|
|
<NavLink to="/income" className={mobileLinkClass}>Income</NavLink>
|
|
<NavLink to="/transactions" className={mobileLinkClass}>Records</NavLink>
|
|
<NavLink to="/rebalance" className={mobileLinkClass}>Rebalance</NavLink>
|
|
<NavLink to="/settings" className={mobileLinkClass}>Settings</NavLink>
|
|
</div>
|
|
<div className="mt-3 border-t border-[--color-border] pt-3 flex items-center justify-between">
|
|
<ThemeToggle size="sm" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|