# Mobile-Friendly Sidebar & Responsive Layout Plan ## Problem Statement The current NextAV layout uses a **persistent left sidebar** (`w-64` expanded / `w-16` collapsed) alongside the main content area in a `flex h-screen` container. While this works well on desktop, it is **not mobile-friendly** for several reasons: 1. **The sidebar always occupies screen width** — on a 375px mobile screen, even the collapsed `w-16` (64px) sidebar consumes ~17% of the viewport, leaving only ~311px for content. 2. **No mobile navigation paradigm** — there is no hamburger menu, bottom navigation bar, or swipe-to-open drawer pattern. The sidebar is always rendered. 3. **The collapse toggle is not a mobile pattern** — the `ChevronLeft`/`ChevronRight` toggle on the sidebar is desktop-oriented; mobile users expect a hamburger icon in a top bar or a bottom tab bar. 4. **Content grids are too dense** — while `getColumnCount()` in the grids already supports 2 columns at `<640px`, the available width is further reduced by the always-visible sidebar. 5. **No `` consideration** — the layout doesn't adapt its core shell for touch devices. --- ## Current Architecture Analysis ### Root Layout ([layout.tsx](file:///Users/tigeren/Dev/xorbitlab/nextav/src/app/layout.tsx)) ```tsx
{children}
``` - The `` is always rendered as a direct flex child. - No conditional rendering based on screen size. - No shared state mechanism for mobile sidebar open/close. ### Sidebar ([sidebar.tsx](file:///Users/tigeren/Dev/xorbitlab/nextav/src/components/sidebar.tsx)) - **282 lines**, manages its own `isCollapsed` state locally. - Contains: nav links (Home, Videos, Photos, Bookmarks, Surprise Me, Settings), collapsible Clusters section, collapsible Libraries section, and a footer. - Width toggles between `w-64` and `w-16` via CSS transition. - **No `md:` or `lg:` responsive breakpoint classes** — purely desktop-oriented. ### Content Pages | Page | Component | Grid Behavior | |------|-----------|---------------| | `/videos` | `InfiniteVirtualGrid` | 2-7 columns based on `containerWidth` | | `/photos` | `InfiniteVirtualGrid` | Same as above | | `/bookmarks` | `InfiniteVirtualGrid` | Same as above | | `/folder-viewer` | `VirtualizedFolderGrid` | 2-7 columns based on `containerWidth` | | `/surprise-me` | CSS grid | `grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5` | | `/settings` | Manual layout | `grid-cols-1 lg:grid-cols-3` | | `/` (Home) | Manual layout | `grid-cols-1 md:grid-cols-2 lg:grid-cols-4` | > [!NOTE] > The virtualized grids (`InfiniteVirtualGrid`, `VirtualizedFolderGrid`) already calculate column count responsively via `containerWidth`. They would benefit automatically from having more width available once the sidebar is hidden on mobile. ### Tailwind Configuration ([tailwind.config.ts](file:///Users/tigeren/Dev/xorbitlab/nextav/tailwind.config.ts)) - TailwindCSS **v3.4.17** (confirmed in `package.json`) - Uses `darkMode: ["class"]` with `.dark` class on `` - Default breakpoints (`sm: 640px`, `md: 768px`, `lg: 1024px`, `xl: 1280px`, `2xl: 1536px`) - No custom mobile breakpoints defined --- ## Design Decisions > [!NOTE] > The following decisions were confirmed by the project owner: > 1. **YouTube-style bottom tab bar** for primary mobile navigation (not just a hamburger drawer) > 2. **`lg` breakpoint (1024px)** as the mobile/desktop cutoff — tablets in portrait get the mobile experience > 3. **Clusters & Libraries sections collapsed by default** in the mobile drawer --- ## Proposed Solution ### Approach: **Bottom Tab Bar + Hamburger Drawer + Persistent Desktop Sidebar** On **desktop** (`lg` and above, ≥1024px): keep the current sidebar as-is (persistent, collapsible). On **mobile/tablet** (`< lg`, <1024px): hide the sidebar, show a **YouTube-style bottom tab bar** for primary navigation, and provide a **hamburger menu in a slim top bar** that opens the full sidebar as a **slide-in drawer overlay** for secondary navigation (Clusters, Libraries, Settings). This mirrors the YouTube mobile app pattern: - **Bottom tab bar** = quick access to primary sections (Home, Videos, Photos, Bookmarks) - **Hamburger drawer** = full sidebar with all sections including Clusters, Libraries, Settings - **Top bar** = branding + hamburger trigger --- ## Detailed Implementation Plan ### Phase 1: Shared Sidebar State (Context) > [!IMPORTANT] > The sidebar's open/close state currently lives inside `sidebar.tsx` as a local `useState`. To control it from the top bar (hamburger button) and coordinate with the bottom tab bar, we need to lift this state into a React Context. #### [NEW] `src/contexts/sidebar-context.tsx` Create a `SidebarProvider` context that provides: ```typescript interface SidebarContextType { isCollapsed: boolean; // Desktop collapse state isMobileOpen: boolean; // Mobile drawer open state toggleCollapse: () => void; // Desktop toggle openMobile: () => void; // Open mobile drawer closeMobile: () => void; // Close mobile drawer isMobile: boolean; // Current screen is mobile (<1024px) } ``` - Use `window.matchMedia('(min-width: 1024px)')` to detect mobile vs desktop (using `lg` breakpoint). - Auto-close mobile drawer on route change (listen to `usePathname()`). - Auto-close mobile drawer when screen resizes above `lg` breakpoint. --- ### Phase 2: Mobile Top Bar Component #### [NEW] `src/components/mobile-top-bar.tsx` A slim fixed top bar visible only on `< lg` screens: ``` ┌──────────────────────────────────────────┐ │ ☰ │ NextAV │ [🔍] │ └──────────────────────────────────────────┘ ``` - **Left**: Hamburger icon (`Menu` from lucide) → triggers `openMobile()` to open the full drawer - **Center**: "NextAV" branding - **Right**: Optional search icon or current section indicator - CSS: `lg:hidden fixed top-0 left-0 right-0 z-40 h-14 bg-card border-b border-border` - Add `pt-14` to `
` on mobile to account for the fixed top bar height > [!NOTE] > The top bar provides access to the **drawer** (secondary nav). The **bottom tab bar** (Phase 2B) handles primary navigation. --- ### Phase 2B: Bottom Tab Bar Component (YouTube-style) #### [NEW] `src/components/mobile-bottom-tabs.tsx` A fixed bottom tab bar visible only on `< lg` screens, providing quick access to primary sections: ``` ┌──────────────────────────────────────────┐ │ 🏠 │ 🎬 │ 📷 │ 🔖 │ │ Home │ Videos │ Photos │ Bookmarks │ └──────────────────────────────────────────┘ ``` - **4 primary tabs**: Home, Videos, Photos, Bookmarks - Active tab highlighted with primary color (filled icon + colored label) - Inactive tabs use muted color - CSS: `lg:hidden fixed bottom-0 left-0 right-0 z-40 h-16 bg-card border-t border-border backdrop-blur-lg` - Uses `safe-area-inset-bottom` padding for devices with home indicators (iPhone notch) - Each tab is a `` using Next.js router, with active state derived from `usePathname()` - Add `pb-16` to `
` on mobile to account for the fixed bottom bar height **Why 4 tabs?** YouTube uses 4-5 tabs. The remaining sections (Surprise Me, Settings, Clusters, Libraries) live in the hamburger drawer — they're less frequently accessed. **Tab touch targets**: Each tab should be at minimum `48px × 48px` (WCAG 2.5.8), with the full tab area being tappable. --- ### Phase 3: Sidebar Refactoring #### [MODIFY] `src/components/sidebar.tsx` **Desktop behavior** (`lg:` and above): - Keep current behavior: persistent sidebar, `w-64`/`w-16` collapse toggle. - Add `hidden lg:flex` to the sidebar container. **Mobile behavior** (`< lg`): - Render as a **fixed overlay drawer** that slides in from the left (opened via hamburger in top bar). - Include a **backdrop** (`bg-black/50`) that closes the drawer on tap. - Use `translate-x` animation for the slide-in/out effect. - The drawer should be `w-72` (slightly wider than desktop's `w-64` for better touch targets). - Add a close button (`X` icon) in the drawer header. - Nav items should have larger touch targets (`min-h-[48px]` per Material Design guidelines). - **Clusters and Libraries sections collapsed by default** — `showClusters` and `showLibraries` should initialize to `false` when in mobile mode. **Structural approach** — two render paths in the same component: ```tsx // Desktop: persistent sidebar // Mobile: overlay drawer {isMobileOpen && (
)} ``` > [!TIP] > On mobile, the drawer acts as **secondary navigation** — users who need Clusters, Libraries, Surprise Me, or Settings will open it from the hamburger. The **bottom tab bar** handles the 4 most common destinations without opening the drawer. --- ### Phase 4: Layout Adjustments #### [MODIFY] `src/app/layout.tsx` ```tsx
{/* visible only < lg */} {/* persistent on lg+, drawer on mobile */}
{children}
{/* visible only < lg */}
``` > [!WARNING] > Both `pt-14` (top bar) and `pb-16` (bottom tab bar) must be applied on mobile only (`lg:pt-0 lg:pb-0`) to avoid wasting space on desktop. #### [MODIFY] `src/app/globals.css` Add animation keyframes for the drawer slide-in: ```css @keyframes slideInFromLeft { from { transform: translateX(-100%); } to { transform: translateX(0); } } @keyframes slideOutToLeft { from { transform: translateX(0); } to { transform: translateX(-100%); } } .animate-slide-in { animation: slideInFromLeft 0.3s ease-out forwards; } .animate-slide-out { animation: slideOutToLeft 0.3s ease-in forwards; } ``` --- ### Phase 5: Touch & Mobile UX Enhancements #### Touch Targets - Increase nav button heights from `h-10` to `min-h-[48px]` on mobile (WCAG 2.5.8). - Increase icon sizes from `h-4 w-4` to `h-5 w-5` on mobile. - Increase font sizes from `text-sm` to `text-base` on mobile for nav labels. #### Swipe Gesture (Optional, Phase 2) - Add a swipe-right gesture on the left edge of the screen to open the drawer. - Add a swipe-left gesture on the open drawer to close it. - Can be implemented with pointer events (`onTouchStart`, `onTouchMove`, `onTouchEnd`). #### Content Adjustments - The `InfiniteVirtualGrid` and `VirtualizedFolderGrid` will automatically gain more width since the sidebar no longer takes space on mobile. - Consider reducing padding from `p-6` to `p-3` on mobile for content pages. --- ## Files to Change | File | Action | Summary | |------|--------|---------| | `src/contexts/sidebar-context.tsx` | **NEW** | Shared sidebar state context with mobile detection at `lg` breakpoint | | `src/components/mobile-top-bar.tsx` | **NEW** | Mobile-only slim top bar with hamburger + branding | | `src/components/mobile-bottom-tabs.tsx` | **NEW** | YouTube-style bottom tab bar (Home, Videos, Photos, Bookmarks) | | `src/components/sidebar.tsx` | **MODIFY** | Dual-mode: persistent desktop (`lg:flex`) + overlay drawer mobile; Clusters/Libraries collapsed by default on mobile | | `src/app/layout.tsx` | **MODIFY** | Wrap with `SidebarProvider`, add `MobileTopBar` + `MobileBottomTabs`, adjust `
` padding (`pt-14 pb-16 lg:pt-0 lg:pb-0`) | | `src/app/globals.css` | **MODIFY** | Add slide-in/out animations, safe-area-inset-bottom support | | `tailwind.config.ts` | **MODIFY** (optional) | Add custom animation keyframes if preferred over raw CSS | --- ## Visual Mockup ### Desktop (≥1024px) — No Change ``` ┌─────────────┬────────────────────────────────────────┐ │ ▶ NextAV │ │ │─────────────│ Main Content Area │ │ 🏠 Home │ │ │ 🎬 Videos │ ┌──────┐ ┌──────┐ ┌──────┐ │ │ 📷 Photos │ │ Card │ │ Card │ │ Card │ │ │ 🔖 Bookmarks│ └──────┘ └──────┘ └──────┘ │ │ ✨ Surprise │ ┌──────┐ ┌──────┐ ┌──────┐ │ │ ⚙ Settings │ │ Card │ │ Card │ │ Card │ │ │─────────────│ └──────┘ └──────┘ └──────┘ │ │ Clusters │ │ │ Libraries │ │ │─────────────│ │ │ NextAV v1.0│ │ └─────────────┴────────────────────────────────────────┘ ``` ### Mobile/Tablet (<1024px) — Bottom Tab Bar + Top Bar ``` ┌──────────────────────────┐ │ ☰ │ NextAV │ 🔍 │ ← slim top bar (h-14) ├──────────────────────────┤ │ │ │ ┌──────────┐ ┌────────┐│ │ │ Card │ │ Card ││ │ └──────────┘ └────────┘│ │ ┌──────────┐ ┌────────┐│ │ │ Card │ │ Card ││ │ └──────────┘ └────────┘│ │ ┌──────────┐ ┌────────┐│ │ │ Card │ │ Card ││ │ └──────────┘ └────────┘│ │ │ ├──────────────────────────┤ │ 🏠 Home │🎬 Videos│📷 Photos│🔖 Marks│ ← bottom tab bar (h-16) └──────────────────────────┘ ``` ### Mobile/Tablet — Hamburger Drawer Open ``` ┌──────────────────────────┐ │ ▶ NextAV ✕ │▒▒▒▒▒│ │────────────────────│▒▒▒▒▒│ │ 🏠 Home │▒▒▒▒▒│ │ 🎬 Videos │▒▒▒▒▒│ │ 📷 Photos │▒▒▒▒▒│ │ 🔖 Bookmarks │▒▒▒▒▒│ │ ✨ Surprise Me │▒▒▒▒▒│ │ ⚙ Settings │▒▒▒▒▒│ │────────────────────│▒▒▒▒▒│ │ ▸ Clusters (collapsed) │ │ ▸ Libraries (collapsed) │ │────────────────────│▒▒▒▒▒│ │ NextAV v1.0 │▒▒▒▒▒│ └──────────────────────────┘ ▲ drawer (w-72) ▲ backdrop Note: Clusters & Libraries are collapsed by default on mobile. User can expand them. ``` --- ## Implementation Priority | Priority | Task | Effort | |----------|------|--------| | P0 | Create `SidebarProvider` context | Small | | P0 | Create `MobileTopBar` component | Small | | P0 | Create `MobileBottomTabs` component (YouTube-style) | Medium | | P0 | Refactor `sidebar.tsx` for desktop/mobile dual mode | Medium | | P0 | Update `layout.tsx` to integrate all pieces | Small | | P0 | Add CSS animations for drawer + safe-area support | Small | | P1 | Increase touch targets on mobile nav items & drawer | Small | | P1 | Reduce content padding on mobile (`p-6` → `p-3`) | Small | | P2 | Swipe gesture to open/close drawer | Medium | --- ## Resolved Decisions | # | Question | Decision | |---|----------|----------| | 1 | Bottom Tab Bar vs Hamburger Drawer? | **YouTube-style bottom tab bar** for primary nav (Home, Videos, Photos, Bookmarks). Hamburger drawer retained for secondary nav (Surprise Me, Settings, Clusters, Libraries). | | 2 | Breakpoint Selection | **`lg` (1024px)** — tablets in portrait mode get the mobile experience with bottom tab bar + drawer. | | 3 | Sidebar Sections on Mobile | **Collapsed by default** — Clusters and Libraries sections start collapsed in the mobile drawer to reduce scroll length. Users can expand them manually. |