nextav/docs/planning/MOBILE_RESPONSIVE_SIDEBAR_P...

17 KiB
Raw Blame History

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)

<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)

  • 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)

  • 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:

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 defaultshowClusters and showLibraries should initialize to false when in mobile mode.

Structural approach — two render paths in the same component:

// 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

<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:

@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-6p-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.