Files
GearBox/.planning/phases/27-top-nav-restructure-and-search-bar-rethink/27-01-PLAN.md

12 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
27-top-nav-restructure-and-search-bar-rethink 01 execute 1
src/client/components/TopNav.tsx
src/client/components/BottomTabBar.tsx
true
NAV-01
NAV-02
NAV-03
truths artifacts key_links
TopNav renders logo, Home/Collection/Setups links, search bar, and user avatar on desktop
Clicking Collection or Setups while anonymous calls openAuthPrompt instead of navigating
Active section is visually highlighted in the nav
BottomTabBar renders 4 tabs (Home, Collection, Setups, Search) with Lucide icons on mobile
Tapping Search tab calls openCatalogSearch
path provides exports
src/client/components/TopNav.tsx Persistent top navigation bar replacing TotalsBar
TopNav
path provides exports
src/client/components/BottomTabBar.tsx Mobile bottom tab bar with 4 navigation items
BottomTabBar
from to via pattern
src/client/components/TopNav.tsx src/client/stores/uiStore.ts openCatalogSearch, openAuthPrompt useUIStore.*openCatalogSearch|useUIStore.*openAuthPrompt
from to via pattern
src/client/components/BottomTabBar.tsx src/client/stores/uiStore.ts openCatalogSearch, openAuthPrompt useUIStore.*openCatalogSearch|useUIStore.*openAuthPrompt
Create the two new navigation components: TopNav (desktop persistent nav bar) and BottomTabBar (mobile fixed bottom tab bar).

Purpose: These components are the core UI deliverables of Phase 27, replacing the minimal TotalsBar with full navigation. They implement D-01 through D-03 (top nav structure), D-07 (nav search bar), D-12 through D-14 (mobile bottom tab bar), and D-17 (search trigger from nav).

Output: Two new component files ready to be wired into __root.tsx in Plan 03.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/27-top-nav-restructure-and-search-bar-rethink/27-CONTEXT.md @.planning/phases/27-top-nav-restructure-and-search-bar-rethink/27-RESEARCH.md

From src/client/stores/uiStore.ts:

openCatalogSearch: (mode: "collection" | "thread") => void;
closeCatalogSearch: () => void;
openAuthPrompt: () => void;
closeAuthPrompt: () => void;

From src/client/hooks/useAuth.ts:

// Returns { data: { user: User | null }, isLoading: boolean }
export function useAuth(): UseQueryResult<{ user: User | null }>;

From src/client/lib/iconData.tsx:

// Renders a Lucide icon by name string. Available icons include: package, home, layers, search
export function LucideIcon({ name, size, className }: { name: string; size?: number; className?: string }): JSX.Element;

From src/client/components/UserMenu.tsx:

export function UserMenu(): JSX.Element;

From src/client/components/TotalsBar.tsx (pattern reference — being replaced):

// Sticky positioning pattern: "sticky top-0 z-10 bg-white border-b border-gray-100"
// Container: "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8"
// Height: "h-14"
Task 1: Create TopNav component src/client/components/TopNav.tsx src/client/components/TotalsBar.tsx src/client/stores/uiStore.ts src/client/components/UserMenu.tsx src/client/hooks/useAuth.ts Create `src/client/components/TopNav.tsx` that replaces TotalsBar with a full navigation bar.

Structure (per D-01):

  • Sticky top bar: sticky top-0 z-10 bg-white border-b border-gray-100 (same as TotalsBar)
  • Container: mx-auto max-w-7xl px-4 sm:px-6 lg:px-8, flex row, h-14
  • Left: Logo — <Link to="/"> with <LucideIcon name="package" size={20} /> and "GearBox" text. Same styling as TotalsBar logo.
  • Center: Desktop nav links (hidden on mobile with hidden md:flex) — Home, Collection, Setups
  • Right: Search bar (desktop only) + UserMenu or "Sign in" link

Active route detection (per D-03):

const matchRoute = useMatchRoute();
const isHome = !!matchRoute({ to: "/" });
const isCollection = !!matchRoute({ to: "/collection", fuzzy: true });
const isSetups = !!matchRoute({ to: "/setups", fuzzy: true });

Active link gets text-gray-900, inactive gets text-gray-500 hover:text-gray-700.

Auth-gated nav links (per D-02): Create a NavLinkOrButton internal component. When isAuthenticated is false and the link is protected (Collection, Setups), render a <button type="button" onClick={openAuthPrompt}> styled identically to the link. When authenticated, render <Link to={to}>. Home is always a <Link> (not protected).

Do NOT use <Link> with e.preventDefault() — TanStack Router fires navigation before React event handlers can intercept reliably.

Search bar (per D-07, D-17): Desktop only (hidden md:flex). Clickable div that calls openCatalogSearch("collection"). Include keyboard handler for Enter. Style: bg-gray-50 border border-gray-200 rounded-lg cursor-pointer hover:border-gray-300. Contains <LucideIcon name="search" size={16} /> and placeholder text "Search catalog..." (text hidden below lg: hidden lg:inline).

User section: Same as TotalsBar: isAuthenticated ? <UserMenu /> : <Link to="/login">Sign in</Link>. Avatar always visible (both mobile and desktop).

Imports:

  • Link, useMatchRoute from @tanstack/react-router
  • useAuth from ../hooks/useAuth
  • LucideIcon from ../lib/iconData
  • useUIStore from ../stores/uiStore
  • UserMenu from ./UserMenu

Export: export function TopNav() cd /home/jean-luc-makiola/Development/projects/GearBox && grep -c "export function TopNav" src/client/components/TopNav.tsx && grep -c "useMatchRoute" src/client/components/TopNav.tsx && grep -c "openAuthPrompt" src/client/components/TopNav.tsx && grep -c "openCatalogSearch" src/client/components/TopNav.tsx <acceptance_criteria> - src/client/components/TopNav.tsx exists and exports export function TopNav() - File imports useMatchRoute from @tanstack/react-router - File contains openAuthPrompt call for anonymous nav interception - File contains openCatalogSearch("collection") for search bar click - File contains hidden md:flex for desktop-only nav links - File contains <UserMenu /> for authenticated users - File contains <Link to="/login" for anonymous "Sign in" - File uses LucideIcon (not direct lucide-react imports) - File does NOT contain e.preventDefault on Link elements </acceptance_criteria> TopNav component renders logo, nav links (Home/Collection/Setups), search bar, and user section. Anonymous clicks on Collection/Setups fire AuthPromptModal. Active route is highlighted. Desktop nav links hidden on mobile.

Task 2: Create BottomTabBar component src/client/components/BottomTabBar.tsx src/client/components/FabMenu.tsx src/client/stores/uiStore.ts src/client/hooks/useAuth.ts src/client/lib/iconData.tsx Create `src/client/components/BottomTabBar.tsx` for mobile navigation.

Structure (per D-13):

  • Fixed bottom: fixed bottom-0 left-0 right-0 md:hidden z-20 bg-white border-t border-gray-100
  • z-20 so CatalogSearchOverlay (which uses higher z-index) renders above it
  • 4 tab items in a flex row with justify-around
  • Each tab: icon (LucideIcon, size 20) + label (text-xs) stacked vertically

Tab items (per D-13, D-14):

  1. Home — icon: home, label: "Home", always <Link to="/">
  2. Collection — icon: package, label: "Collection", <Link to="/collection"> if authenticated, <button onClick={openAuthPrompt}> if anonymous (per D-02)
  3. Setups — icon: layers, label: "Setups", <Link to="/setups"> if authenticated, <button onClick={openAuthPrompt}> if anonymous (per D-02)
  4. Search — icon: search, label: "Search", always <button onClick={() => openCatalogSearch("collection")}> (per D-14, D-17)

Icon availability: The layers icon is confirmed available in the curated icon set (src/client/lib/iconData.tsx). If for any reason it's not found at implementation time, use briefcase or grid-2x2 as fallback.

Active state: Use same useMatchRoute pattern as TopNav. Active tab: text-gray-900, inactive: text-gray-400. Search tab is never "active" (it opens an overlay, not a route).

Entry animation (Framer Motion):

<motion.div
  initial={{ y: 20, opacity: 0 }}
  animate={{ y: 0, opacity: 1 }}
  transition={{ duration: 0.2, ease: "easeOut" }}
  className="fixed bottom-0 ..."
>

Safe area: Add pb-[env(safe-area-inset-bottom)] to the container for iOS devices with home indicator.

Imports:

  • Link, useMatchRoute from @tanstack/react-router
  • motion from framer-motion
  • useAuth from ../hooks/useAuth
  • LucideIcon from ../lib/iconData
  • useUIStore from ../stores/uiStore

Export: export function BottomTabBar() cd /home/jean-luc-makiola/Development/projects/GearBox && grep -c "export function BottomTabBar" src/client/components/BottomTabBar.tsx && grep -c "md:hidden" src/client/components/BottomTabBar.tsx && grep -c "openCatalogSearch" src/client/components/BottomTabBar.tsx && grep -c "openAuthPrompt" src/client/components/BottomTabBar.tsx && grep -c "layers" src/client/lib/iconData.tsx <acceptance_criteria> - src/client/components/BottomTabBar.tsx exists and exports export function BottomTabBar() - File contains md:hidden to only show on mobile - File contains fixed bottom-0 for fixed positioning - File contains z-20 for z-index - File contains 4 tab items: Home, Collection, Setups, Search (grep for all 4 labels) - File contains openCatalogSearch("collection") for Search tab - File contains openAuthPrompt for anonymous Collection/Setups taps - File uses LucideIcon with names: home, package, layers (or fallback briefcase/grid-2x2), search - File imports motion from framer-motion for entry animation - The layers icon exists in src/client/lib/iconData.tsx (verified: present) </acceptance_criteria> BottomTabBar renders 4 tabs with Lucide icons on mobile viewports. Search tab opens CatalogSearchOverlay. Collection/Setups tabs fire AuthPromptModal for anonymous users. Active tab is highlighted. Component hidden on md+ screens.

- Both component files exist and export their named functions - Both use `useMatchRoute` for active route detection - Both use `openAuthPrompt` for anonymous user interception on protected links - Both use `openCatalogSearch("collection")` for search trigger - TopNav uses `hidden md:flex` for desktop nav; BottomTabBar uses `md:hidden` for mobile only - Neither imports directly from `lucide-react` — both use `LucideIcon` wrapper - The `layers` icon is available in the curated icon set

<success_criteria>

  • TopNav.tsx is a complete, self-contained component ready to replace TotalsBar in __root.tsx
  • BottomTabBar.tsx is a complete, self-contained component ready to add to __root.tsx
  • Both handle authenticated and anonymous states correctly
  • Both follow existing codebase patterns (Tailwind, LucideIcon, uiStore, useAuth) </success_criteria>
After completion, create `.planning/phases/27-top-nav-restructure-and-search-bar-rethink/27-01-SUMMARY.md`