Files
shadow-gunbuilder-ai-proto/components/BuilderNav.tsx
T

191 lines
6.8 KiB
TypeScript

"use client";
/**
* BuilderNav.tsx
* -----------------------------------------------------------------------------
* Top navigation for the Builder experience.
*
* MAJOR LAYOUT IDEA:
* - Keep all primary nav items on the LEFT
* - Put *everything else* (search, viewing state, theme toggle later, user menu later)
* into a RIGHT "actions cluster" using a single `ml-auto` wrapper.
*
* WHY:
* - Prevents multiple `ml-auto` elements fighting each other
* - Makes the right side easy to extend without breaking alignment
*/
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { CATEGORIES } from "@/data/gunbuilderParts";
import type { Category } from "@/types/gunbuilder";
type BuilderNavProps = {
// Optional override if a parent wants to force the active category
activeCategoryId?: string | null;
};
/**
* Helper: group categories for dropdowns.
* This assumes your Category has a `group` field like "lower" | "upper" | "accessories".
*/
function groupCategories(group: "lower" | "upper" | "accessories"): Category[] {
return CATEGORIES.filter((c) => c.group === group);
}
export function BuilderNav({ activeCategoryId }: BuilderNavProps) {
const searchParams = useSearchParams();
// ---- Data / derived state --------------------------------------------------
const lower = groupCategories("lower");
const upper = groupCategories("upper");
const accessories = groupCategories("accessories");
// Determine which category is "active" so we can highlight dropdown items
// and show the "Viewing" indicator on the right.
const currentCategory =
activeCategoryId ?? searchParams.get("category") ?? undefined;
// Base route for parts browsing (used for dropdown links + "Clear")
const baseHref = "/parts";
/**
* Dropdown renderer (hover-based).
* - Uses Tailwind `group` + `group-hover` to show/hide the menu.
* - Highlights the active item.
*/
const renderDropdown = (label: string, items: Category[]) => {
if (!items.length) return null;
return (
<div className="relative group">
{/* Dropdown trigger */}
<button
type="button"
className={`inline-flex items-center gap-1 font-medium tracking-wide uppercase text-[11px] transition-colors ${
items.some((cat) => cat.id === currentCategory)
? "text-white"
: "text-neutral-300 hover:text-white"
}`}
>
{label}
<span className="text-[9px]"></span>
</button>
{/* Dropdown menu */}
<div className="invisible opacity-0 group-hover:visible group-hover:opacity-100 transition-all absolute left-0 mt-2 rounded-md border border-neutral-800 bg-black/95 shadow-xl z-40 min-w-[220px]">
<ul className="py-2 text-sm">
{items.map((cat) => {
const isActive = currentCategory === cat.id;
return (
<li key={cat.id}>
<Link
href={`${baseHref}/${cat.id}`}
className={`block w-full px-3 py-1.5 transition-colors ${
isActive
? "bg-neutral-800 text-white"
: "text-neutral-300 hover:bg-neutral-800 hover:text-white"
}`}
>
{cat.name}
</Link>
</li>
);
})}
</ul>
</div>
</div>
);
};
return (
/**
* Outer container matches TopNav width:
* - max-w-6xl mx-auto w-full ensures consistent alignment across app
*/
<div className="max-w-6xl mx-auto w-full">
{/**
* FLEX STRATEGY:
* - Everything before the RIGHT cluster is "left side"
* - Then a single `ml-auto` cluster pushes everything else to the right
*/}
<nav className="flex items-center gap-6 border-b border-neutral-900 px-6 py-3 text-sm backdrop-blur-sm">
{/* ------------------------------------------------------------------ */}
{/* LEFT SIDE: primary nav */}
{/* ------------------------------------------------------------------ */}
<Link
href="/builder"
className="font-semibold text-neutral-100 hover:text-white tracking-[0.25em] uppercase text-[11px]"
>
Builder
</Link>
{renderDropdown("Lower Parts", lower)}
{renderDropdown("Upper Parts", upper)}
{renderDropdown("Accessories", accessories)}
<Link
href="/builds"
className="font-medium text-neutral-300 hover:text-white tracking-[0.25em] uppercase text-[11px]"
>
Builds
</Link>
{/* ------------------------------------------------------------------ */}
{/* RIGHT SIDE: actions + state */}
{/* IMPORTANT: this is the ONLY `ml-auto` in the nav to avoid conflicts */}
{/* ------------------------------------------------------------------ */}
<div className="ml-auto flex items-center gap-3">
{/* Search icon (placeholder action) */}
<button
type="button"
className="inline-flex h-8 w-8 items-center justify-center rounded-full border border-zinc-700 bg-zinc-900/60 text-zinc-400 hover:text-zinc-200 hover:bg-zinc-800 hover:border-zinc-500 transition-colors"
aria-label="Search (coming soon)"
title="Search (coming soon)"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4"
>
<circle cx="11" cy="11" r="6" />
<line x1="16.5" y1="16.5" x2="21" y2="21" />
</svg>
</button>
{/* "Viewing" indicator (only shows when a category is active) */}
{currentCategory && (
<div className="flex items-center gap-2 text-neutral-400 text-xs">
<span className="uppercase tracking-wide text-[10px]">Viewing</span>
<span className="font-medium text-neutral-100">
{CATEGORIES.find((c) => c.id === currentCategory)?.name ??
currentCategory}
</span>
{/* Clears category filter -> go back to /parts */}
<Link
href={baseHref}
className="rounded border border-neutral-700 px-2 py-0.5 text-[10px] uppercase tracking-wide hover:bg-neutral-800 hover:text-white"
>
Clear
</Link>
</div>
)}
{/* FUTURE: drop in ThemeToggle, UserMenu, Notifications here */}
{/* <ThemeToggle /> */}
{/* <UserMenu /> */}
</div>
</nav>
</div>
);
}
export default BuilderNav;