nextav/docs/planning/MOBILE_RESPONSIVE_SIDEBAR_P...

379 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 `<meta name="viewport">` 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
<div className="flex h-screen bg-gradient-to-br from-background via-background to-muted/20">
<Sidebar />
<main className="flex-1 bg-background/50 backdrop-blur-sm overflow-y-auto">
{children}
</main>
</div>
```
- The `<Sidebar>` 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 `<html>`
- 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 `<main>` 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 `<Link>` using Next.js router, with active state derived from `usePathname()`
- Add `pb-16` to `<main>` 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
<aside className="hidden lg:flex flex-col ...">
{/* existing sidebar content */}
</aside>
// Mobile: overlay drawer
{isMobileOpen && (
<div className="lg:hidden fixed inset-0 z-50">
<div className="absolute inset-0 bg-black/50" onClick={closeMobile} />
<aside className="relative w-72 h-full bg-card animate-slide-in ...">
{/* same sidebar content with larger touch targets */}
{/* Clusters: showClusters defaults to false */}
{/* Libraries: showLibraries defaults to false */}
</aside>
</div>
)}
```
> [!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
<SidebarProvider>
<div className="flex h-screen">
<MobileTopBar /> {/* visible only < lg */}
<Sidebar /> {/* persistent on lg+, drawer on mobile */}
<main className="flex-1 overflow-y-auto pt-14 lg:pt-0 pb-16 lg:pb-0">
{children}
</main>
<MobileBottomTabs /> {/* visible only < lg */}
</div>
</SidebarProvider>
```
> [!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 `<main>` 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. |