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 |
|
true |
|
|
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.mdFrom 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"
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, useMatchRoutefrom@tanstack/react-routeruseAuthfrom../hooks/useAuthLucideIconfrom../lib/iconDatauseUIStorefrom../stores/uiStoreUserMenufrom./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.
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):
- Home — icon:
home, label: "Home", always<Link to="/"> - Collection — icon:
package, label: "Collection",<Link to="/collection">if authenticated,<button onClick={openAuthPrompt}>if anonymous (per D-02) - Setups — icon:
layers, label: "Setups",<Link to="/setups">if authenticated,<button onClick={openAuthPrompt}>if anonymous (per D-02) - 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, useMatchRoutefrom@tanstack/react-routermotionfromframer-motionuseAuthfrom../hooks/useAuthLucideIconfrom../lib/iconDatauseUIStorefrom../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.
<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>