diff --git a/Workflow/components/NavBar.tsx b/Workflow/components/NavBar.tsx new file mode 100644 index 0000000..218f451 --- /dev/null +++ b/Workflow/components/NavBar.tsx @@ -0,0 +1,454 @@ +// components/NavBar.tsx +// Top-of-page navigation. Logo lockup on the left, primary nav +// center, secondary status on the right. Sticky on scroll with the +// signature hairline border-bottom. +// +// Mobile ( +
+
+ + + + Workflow + + + + + + {/* Right-side desktop cluster: label + theme toggle. Hidden + on mobile — the bottom tab bar carries the brand context. */} +
+ Registry Browser + +
+ + {/* Spacer for mobile so the brand lockup stays left-aligned + and the bottom-bar height is the only vertical change. */} + +
+ + {/* Mobile bottom tab bar */} + + + ); +} + +function isActiveLink(pathname: string, href: string): boolean { + if (href === "/") return pathname === "/"; + return pathname === href || pathname.startsWith(href + "/"); +} + +/* ─── Icon set (inline SVG, stroke-currentColor, 1.6px stroke) ──── */ + +interface IconProps { + className?: string; + /** Active state hints — used to swap fill behavior on the home icon + * and to thicken the stroke on outlined icons. Always defaults to + * the brand "muted" rendering since the active state is owned by + * the parent (text color + scale already encodes it). */ + active?: boolean; +} + +function IconHome({ className, active }: IconProps): ReactNode { + return ( + + ); +} + +function IconUsers({ className }: IconProps): ReactNode { + return ( + + ); +} + +function IconBuilding({ className }: IconProps): ReactNode { + return ( + + ); +} + +function IconDocument({ className }: IconProps): ReactNode { + return ( + + ); +} + +function IconClipboard({ className }: IconProps): ReactNode { + return ( + + ); +} + +function IconSearch({ className }: IconProps): ReactNode { + return ( + + ); +} + +function IconTag({ className }: IconProps): ReactNode { + return ( + + ); +} + +function IconCheckSquare({ className }: IconProps): ReactNode { + return ( + + ); +} + +/** + * Tax-receipt glyph for the SARS Returns tab. Deliberately a torn- + * edge receipt (zig-zag bottom) to distinguish it from IconDocument + * (which is a sealed page) and IconClipboard (which has a clip). + */ +function IconReceipt({ className }: IconProps): ReactNode { + return ( + + ); +} + +/** + * Notepad glyph for the Notes tab. Rounded rectangle with a horizontal + * ruled line and a folded top-right corner — distinguishes it from + * IconClipboard (task list with a clip) and IconDocument (sealed page). + */ +function IconNotepad({ className }: IconProps): ReactNode { + return ( + + ); +} + +/* ─── Theme toggle ────────────────────────────────────────────────── */ + +/** + * Sun/moon button. Uses next-themes' `resolvedTheme` so system + * preference is honoured. The icon shown represents the mode you'd + * switch TO (moon in light, sun in dark). + * + * `mounted` guard avoids SSR/CSR mismatch — `useTheme` returns + * undefined on the first render. Until mounted, we render a neutral + * placeholder so the button's box doesn't pop in late. + */ +function ThemeToggle() { + const [mounted, setMounted] = useState(false); + const { resolvedTheme, setTheme } = useTheme(); + useEffect(() => setMounted(true), []); + + const isDark = mounted && resolvedTheme === "dark"; + const next = isDark ? "light" : "dark"; + + return ( + + ); +} + +function IconSun({ className }: IconProps): ReactNode { + return ( + + ); +} + +function IconMoon({ className }: IconProps): ReactNode { + return ( + + ); +}