From 24ed71975faadca9eb7f810f7de2185e991b919f Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Fri, 10 Apr 2026 23:44:56 +0200 Subject: [PATCH] feat(27-01): create BottomTabBar component - Fixed bottom tab bar for mobile (md:hidden) with z-20 stacking - 4 tabs: Home, Collection, Setups, Search with Lucide icons - Collection and Setups fire openAuthPrompt for anonymous users - Search tab calls openCatalogSearch('collection') to open overlay - Active route highlighting via useMatchRoute - Framer Motion entry animation (y slide + fade) - iOS safe area padding with env(safe-area-inset-bottom) [Rule 1 - Bug] Used 'house' icon instead of 'home': lucide-react has no 'Home' icon (only 'House') --- src/client/components/BottomTabBar.tsx | 95 ++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 src/client/components/BottomTabBar.tsx diff --git a/src/client/components/BottomTabBar.tsx b/src/client/components/BottomTabBar.tsx new file mode 100644 index 0000000..8497f39 --- /dev/null +++ b/src/client/components/BottomTabBar.tsx @@ -0,0 +1,95 @@ +import { Link, useMatchRoute } from "@tanstack/react-router"; +import { motion } from "framer-motion"; +import { useAuth } from "../hooks/useAuth"; +import { LucideIcon } from "../lib/iconData"; +import { useUIStore } from "../stores/uiStore"; + +interface TabItemProps { + icon: string; + label: string; + isActive: boolean; +} + +function TabItemWrapper({ + icon, + label, + isActive, + children, +}: TabItemProps & { children?: React.ReactNode }) { + const activeClass = "text-gray-900"; + const inactiveClass = "text-gray-400"; + const colorClass = isActive ? activeClass : inactiveClass; + + return ( + + + {label} + + ); +} + +export function BottomTabBar() { + const { data: auth } = useAuth(); + const isAuthenticated = !!auth?.user; + const openCatalogSearch = useUIStore((s) => s.openCatalogSearch); + const openAuthPrompt = useUIStore((s) => s.openAuthPrompt); + const matchRoute = useMatchRoute(); + + const isHome = !!matchRoute({ to: "/" }); + const isCollection = !!matchRoute({ to: "/collection", fuzzy: true }); + const isSetups = !!matchRoute({ to: "/setups", fuzzy: true }); + + return ( + +
+ {/* Home tab — always a Link */} + + + + + {/* Collection tab — Link if authenticated, button if anonymous */} + {isAuthenticated ? ( + + + + ) : ( + + )} + + {/* Setups tab — Link if authenticated, button if anonymous */} + {isAuthenticated ? ( + + + + ) : ( + + )} + + {/* Search tab — always a button, opens CatalogSearchOverlay */} + +
+
+ ); +}