80 lines
3.9 KiB
TypeScript
80 lines
3.9 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { usePathname } from "next/navigation";
|
|
import { AccountMenu } from "@/components/AccountMenu";
|
|
import { EnvBadge } from "@/components/EnvBadge";
|
|
|
|
export type NavLink = { href: string; label: string };
|
|
|
|
/*
|
|
* One masthead in three zones: who you are acting as, where you can go, and
|
|
* which environment you are in.
|
|
*
|
|
* It replaces a brand bar and a separate nav strip. The nav's active state is
|
|
* derived from the pathname rather than hardcoded the previous customer nav
|
|
* marked Overview as current on every page, including the ones that weren't it.
|
|
*
|
|
* Staff sit on --panel-2 with a chip where the account name goes. web/ is locked
|
|
* to dark so this app defaults to light for the same reason CLAUDE.md gives:
|
|
* telling two consoles apart before you click Reissue. Staff and customer need
|
|
* that distinction from each other too, and one shade plus one chip buys it
|
|
* without a second palette.
|
|
*/
|
|
export function AppBar({ links, context, staff = false }: { links: NavLink[]; context?: React.ReactNode; staff?: boolean }) {
|
|
const pathname = usePathname();
|
|
|
|
const isCurrent = (href: string) =>
|
|
// The section root matches only exactly; deeper routes match by prefix,
|
|
// so /staff/accounts/:id still lights Accounts while /staff/accounts
|
|
// does not light Operations.
|
|
href === "/" || href === "/staff" ? pathname === href : pathname === href || pathname.startsWith(`${href}/`);
|
|
|
|
return (
|
|
<header className={`border-b border-rule ${staff ? "bg-panel-2" : "bg-panel"}`}>
|
|
<div className="mx-auto grid max-w-rail grid-cols-[auto_1fr_auto] items-center gap-4 px-5 md:gap-7">
|
|
<div className="col-start-1 row-start-1 flex min-w-0 items-center gap-3 py-2.5">
|
|
<span className="flex items-baseline gap-2 text-[1.16rem] font-extrabold tracking-[-0.02em]">
|
|
Vantage
|
|
<span className="font-mono text-[0.72rem] font-normal uppercase tracking-[0.14em] text-ink-3">HQ</span>
|
|
</span>
|
|
{context && (
|
|
<>
|
|
<span aria-hidden className="hidden h-[22px] w-px bg-rule sm:block" />
|
|
<span className="hidden min-w-0 sm:block">{context}</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
<nav
|
|
aria-label={staff ? "Staff" : "Account"}
|
|
className="col-span-3 col-start-1 row-start-2 flex items-stretch gap-1 overflow-x-auto border-t border-rule-soft md:col-span-1 md:col-start-2 md:row-start-1 md:border-t-0"
|
|
>
|
|
{links.map((l) => {
|
|
const on = isCurrent(l.href);
|
|
return (
|
|
<Link
|
|
key={l.href}
|
|
href={l.href}
|
|
aria-current={on ? "page" : undefined}
|
|
className={`relative inline-flex shrink-0 items-center px-3 py-2.5 font-mono text-[0.72rem] uppercase tracking-[0.08em] md:py-0 ${
|
|
on ? "font-bold text-accent after:absolute after:inset-x-3 after:bottom-0 after:h-0.5 after:bg-accent after:content-['']" : "text-ink-3 hover:text-ink-2"
|
|
}`}
|
|
>
|
|
{l.label}
|
|
</Link>
|
|
);
|
|
})}
|
|
</nav>
|
|
|
|
<div className="col-start-3 row-start-1 flex items-center justify-end gap-2.5 py-2.5">
|
|
<span className="hidden sm:block">
|
|
<EnvBadge />
|
|
</span>
|
|
<AccountMenu staff={staff} />
|
|
</div>
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|