2026-10-18 15:08:34 -06:00
|
|
|
import { useEffect, useState, type ReactNode } from "react";
|
2026-10-15 23:08:47 -06:00
|
|
|
import { Link } from "react-router";
|
|
|
|
|
import logo from "../assets/logo.png";
|
2026-10-18 15:08:34 -06:00
|
|
|
import { setMotion, useMotion } from "../lib/motion";
|
|
|
|
|
|
|
|
|
|
/** The Motion on / Motion off switch (the old motionBtn), with a short toast on toggle. */
|
|
|
|
|
export function MotionButton() {
|
|
|
|
|
const on = useMotion();
|
|
|
|
|
const [toast, setToast] = useState<string | null>(null);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!toast) return;
|
|
|
|
|
const t = window.setTimeout(() => setToast(null), 2500);
|
|
|
|
|
return () => window.clearTimeout(t);
|
|
|
|
|
}, [toast]);
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<button
|
|
|
|
|
className="btn sm ghost mbtn"
|
|
|
|
|
type="button"
|
|
|
|
|
aria-pressed={on}
|
|
|
|
|
title={on ? "Turn animations off" : "Turn animations on"}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
setMotion(!on);
|
|
|
|
|
setToast(on ? "Motion off" : "Motion on");
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<span className="mdot" aria-hidden="true" />
|
|
|
|
|
{on ? "Motion on" : "Motion off"}
|
|
|
|
|
</button>
|
|
|
|
|
{toast ? (
|
|
|
|
|
<div className="toast" role="status" data-testid="motion-toast">
|
|
|
|
|
{toast}
|
|
|
|
|
</div>
|
|
|
|
|
) : null}
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-10-15 23:08:47 -06:00
|
|
|
|
|
|
|
|
interface TopBarProps {
|
2026-10-16 07:40:29 -06:00
|
|
|
/** The role switcher, drawn between the brand and the spacer. */
|
|
|
|
|
nav?: ReactNode;
|
|
|
|
|
/** Right-hand side: the signed-in user, the role chip, Account and Sign out. */
|
2026-10-15 23:08:47 -06:00
|
|
|
children?: ReactNode;
|
2026-10-30 17:50:05 -06:00
|
|
|
/** A strip above the bar, inside the sticky header so it stays in view (the build-version banner). */
|
|
|
|
|
banner?: ReactNode;
|
2026-10-15 23:08:47 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Sticky shop header. Same markup classes as the old page's header.top. */
|
2026-10-30 17:50:05 -06:00
|
|
|
export function TopBar({ nav, children, banner }: TopBarProps) {
|
2026-10-15 23:08:47 -06:00
|
|
|
return (
|
|
|
|
|
<header className="top">
|
2026-10-30 17:50:05 -06:00
|
|
|
{banner}
|
2026-10-15 23:08:47 -06:00
|
|
|
<div className="top-in">
|
|
|
|
|
<Link className="brand" to="/" aria-label="Packet Station home">
|
|
|
|
|
<img alt="PESCO" src={logo} width={119} height={34} />
|
|
|
|
|
<div className="nm">
|
|
|
|
|
Packet <span>Station</span>
|
|
|
|
|
</div>
|
|
|
|
|
</Link>
|
2026-10-16 07:40:29 -06:00
|
|
|
{nav}
|
2026-10-18 15:08:34 -06:00
|
|
|
<MotionButton />
|
2026-10-15 23:08:47 -06:00
|
|
|
<div className="sp" />
|
|
|
|
|
{children ? <div className="who">{children}</div> : null}
|
|
|
|
|
</div>
|
|
|
|
|
</header>
|
|
|
|
|
);
|
|
|
|
|
}
|