256 lines
12 KiB
Markdown
256 lines
12 KiB
Markdown
---
|
|
phase: 27-top-nav-restructure-and-search-bar-rethink
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- src/client/components/TopNav.tsx
|
|
- src/client/components/BottomTabBar.tsx
|
|
autonomous: true
|
|
requirements:
|
|
- NAV-01
|
|
- NAV-02
|
|
- NAV-03
|
|
|
|
must_haves:
|
|
truths:
|
|
- "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"
|
|
artifacts:
|
|
- path: "src/client/components/TopNav.tsx"
|
|
provides: "Persistent top navigation bar replacing TotalsBar"
|
|
exports: ["TopNav"]
|
|
- path: "src/client/components/BottomTabBar.tsx"
|
|
provides: "Mobile bottom tab bar with 4 navigation items"
|
|
exports: ["BottomTabBar"]
|
|
key_links:
|
|
- from: "src/client/components/TopNav.tsx"
|
|
to: "src/client/stores/uiStore.ts"
|
|
via: "openCatalogSearch, openAuthPrompt"
|
|
pattern: "useUIStore.*openCatalogSearch|useUIStore.*openAuthPrompt"
|
|
- from: "src/client/components/BottomTabBar.tsx"
|
|
to: "src/client/stores/uiStore.ts"
|
|
via: "openCatalogSearch, openAuthPrompt"
|
|
pattern: "useUIStore.*openCatalogSearch|useUIStore.*openAuthPrompt"
|
|
---
|
|
|
|
<objective>
|
|
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.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<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
|
|
|
|
<interfaces>
|
|
<!-- Key types and contracts the executor needs. -->
|
|
|
|
From src/client/stores/uiStore.ts:
|
|
```typescript
|
|
openCatalogSearch: (mode: "collection" | "thread") => void;
|
|
closeCatalogSearch: () => void;
|
|
openAuthPrompt: () => void;
|
|
closeAuthPrompt: () => void;
|
|
```
|
|
|
|
From src/client/hooks/useAuth.ts:
|
|
```typescript
|
|
// Returns { data: { user: User | null }, isLoading: boolean }
|
|
export function useAuth(): UseQueryResult<{ user: User | null }>;
|
|
```
|
|
|
|
From src/client/lib/iconData.tsx:
|
|
```typescript
|
|
// 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:
|
|
```typescript
|
|
export function UserMenu(): JSX.Element;
|
|
```
|
|
|
|
From src/client/components/TotalsBar.tsx (pattern reference — being replaced):
|
|
```typescript
|
|
// 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"
|
|
```
|
|
</interfaces>
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Create TopNav component</name>
|
|
<files>src/client/components/TopNav.tsx</files>
|
|
<read_first>
|
|
src/client/components/TotalsBar.tsx
|
|
src/client/stores/uiStore.ts
|
|
src/client/components/UserMenu.tsx
|
|
src/client/hooks/useAuth.ts
|
|
</read_first>
|
|
<action>
|
|
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):**
|
|
```typescript
|
|
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()`
|
|
</action>
|
|
<verify>
|
|
<automated>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</automated>
|
|
</verify>
|
|
<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>
|
|
<done>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.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Create BottomTabBar component</name>
|
|
<files>src/client/components/BottomTabBar.tsx</files>
|
|
<read_first>
|
|
src/client/components/FabMenu.tsx
|
|
src/client/stores/uiStore.ts
|
|
src/client/hooks/useAuth.ts
|
|
src/client/lib/iconData.tsx
|
|
</read_first>
|
|
<action>
|
|
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):**
|
|
```typescript
|
|
<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()`
|
|
</action>
|
|
<verify>
|
|
<automated>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</automated>
|
|
</verify>
|
|
<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>
|
|
<done>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.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- 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
|
|
</verification>
|
|
|
|
<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>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/27-top-nav-restructure-and-search-bar-rethink/27-01-SUMMARY.md`
|
|
</output>
|