Skip to main content

Overview

The Sidebar component provides the main navigation interface for StreamVault. It features view selection, Google Drive status, storage usage indicators, and responsive collapsing behavior. Sidebar showing navigation items, active view highlighting, and cloud storage status

Features

  • View Navigation: Home, Google Drive, Discover, AI Chat, Social, History
  • Active View Highlighting: Animated pill indicator with glow effect
  • Responsive Collapsing: Auto-collapse on small screens, manual toggle on larger screens
  • Cloud Storage Status: Google Drive account info and storage usage bar
  • Sync Library Button: Manual cloud library indexing with loading state
  • Tooltips: Hover tooltips in collapsed mode
  • Beta Feature Badges: “NEW” badges for experimental features
  • Settings Button: Quick access to settings modal
  • Smooth Animations: Framer Motion for width transitions and active states

Component Interface

interface SidebarProps {
  className?: string
  currentView: string
  setView: (view: string) => void
  onOpenSettings: () => void
  onCloudScan?: () => void
  theme?: 'dark' | 'light'
  toggleTheme?: () => void
  isScanning?: boolean
  isCloudIndexing?: boolean
  scanProgress?: {
    current: number
    total: number
  } | null
  showCloudTab?: boolean
  showStreamTab?: boolean
  betaEnabled?: boolean
}

Props

currentView
string
required
The currently active view ID (e.g., “home”, “cloud”, “history”)
setView
(view: string) => void
required
Callback when a navigation item is clicked to change views
onOpenSettings
() => void
required
Callback to open the settings modal
onCloudScan
() => void
Optional callback to trigger manual cloud library sync
isScanning
boolean
default:"false"
Whether local library scan is in progress
isCloudIndexing
boolean
default:"false"
Whether cloud library indexing is in progress (shows spinner)
scanProgress
{ current: number; total: number } | null
Progress information for local library scanning
showCloudTab
boolean
default:"true"
Whether to show the Google Drive tab
showStreamTab
boolean
default:"false"
Whether to show the Discover/Stream tab (beta feature)
betaEnabled
boolean
default:"false"
Whether beta features (AI Chat, Social) should be visible
className
string
Additional CSS classes for the sidebar container

Usage Example

import { Sidebar } from '@/components/Sidebar'
import { useState } from 'react'

function App() {
  const [currentView, setCurrentView] = useState('home')
  const [settingsOpen, setSettingsOpen] = useState(false)
  const [isCloudIndexing, setIsCloudIndexing] = useState(false)
  const [betaEnabled, setBetaEnabled] = useState(false)
  const [streamTabEnabled, setStreamTabEnabled] = useState(false)

  const handleCloudScan = async () => {
    setIsCloudIndexing(true)
    await indexGoogleDrive()
    setIsCloudIndexing(false)
  }

  return (
    <div className="flex h-screen">
      <Sidebar
        currentView={currentView}
        setView={setCurrentView}
        onOpenSettings={() => setSettingsOpen(true)}
        onCloudScan={handleCloudScan}
        isCloudIndexing={isCloudIndexing}
        betaEnabled={betaEnabled}
        showStreamTab={streamTabEnabled}
      />
      
      <main className="flex-1">
        {/* View content */}
      </main>
    </div>
  )
}
Menu items are configured dynamically:
const menuItems = [
  { id: "home", label: "Home", icon: Home },
  { id: "cloud", label: "Google Drive", icon: Cloud, hidden: !showCloudTab },
  { id: "stream", label: "Discover", icon: Globe, hidden: !showStreamTab },
  { id: "ai", label: "AI Chat", icon: Bot, hidden: !betaEnabled, isNew: true },
  { id: "social", label: "Social", icon: Users, hidden: !betaEnabled },
  { id: "history", label: "History", icon: History },
].filter(item => !item.hidden)

View IDs

  • home - Main library view (Movies, TV Shows)
  • cloud - Google Drive library
  • stream - Discover/Stream online content (beta)
  • ai - AI Chat interface (beta)
  • social - Social features (beta)
  • history - Watch history

Responsive Behavior

Auto-Collapse

The sidebar automatically collapses on screens smaller than 800px:
const [windowWidth, setWindowWidth] = useState(() => window.innerWidth)
const isForcedCollapsed = windowWidth < 800

Manual Collapse

Users can manually toggle collapse on larger screens:
const [isManualCollapsed, setIsManualCollapsed] = useState(() => {
  return window.localStorage.getItem("sidebar-collapsed") === "1"
})

const isCollapsed = isForcedCollapsed || isManualCollapsed
Collapse state is persisted in localStorage.

Width Calculation

const sidebarWidth = isCollapsed 
  ? (isForcedCollapsed ? 68 : 72)  // Icon-only mode
  : (windowWidth < 1100 ? 240 : 280)  // Expanded mode
Smooth animated transitions:
<motion.aside
  animate={{ width: sidebarWidth }}
  transition={{ duration: 0.3, ease: [0.22, 1, 0.36, 1] }}
>

Active View Highlighting

Active navigation items have:
  1. Background glow with blur effect
  2. Left pill indicator with shadow
  3. White text color
  4. Border highlight
{isActive && (
  <>
    <motion.div
      layoutId="active-glow"
      className="absolute inset-0 rounded-xl bg-white/5 blur-md"
      transition={{ type: "spring", stiffness: 300, damping: 30 }}
    />
    <motion.div
      layoutId="active-pill"
      className="absolute left-0 inset-y-0 my-auto w-1 h-6 bg-white rounded-r-full shadow-[0_0_15px_rgba(255,255,255,0.5)]"
      transition={{ type: "spring", stiffness: 300, damping: 30 }}
    />
  </>
)}
The layoutId prop enables smooth animations when switching between views.

Google Drive Integration

Account Status

Fetches and displays Google Drive connection status:
useEffect(() => {
  const fetchGdriveInfo = async () => {
    const connected = await isGDriveConnected()
    setGdriveConnected(connected)
    
    if (connected) {
      const info = await getGDriveAccountInfo()
      setGdriveInfo(info)  // { email, storage_used, storage_limit }
    }
  }
  
  fetchGdriveInfo()
  const interval = setInterval(fetchGdriveInfo, 60000)  // Refresh every minute
  return () => clearInterval(interval)
}, [])

Sync Library Button

Only shown when Google Drive is connected and sidebar is expanded:
{gdriveConnected && onCloudScan && !isCollapsed && (
  <button
    onClick={onCloudScan}
    disabled={isCloudIndexing || isScanning}
    className="w-full flex items-center justify-between px-4 py-2.5 rounded-xl bg-white/[0.04] border border-white/[0.06] hover:bg-white/[0.08]"
  >
    <div className="flex items-center gap-3">
      <RotateCw className={cn("w-4 h-4 text-white", isCloudIndexing && "animate-spin")} />
      <span className="text-xs font-bold text-neutral-300">Sync Library</span>
    </div>
    <div className="w-1.5 h-1.5 rounded-full bg-white animate-pulse" />
  </button>
)}

Storage Usage Bar

Displays storage quota with animated progress bar:
{gdriveInfo && gdriveInfo.storage_used !== undefined && !isCollapsed && (
  <div className="px-1 space-y-2">
    <div className="flex justify-between items-end">
      <span className="text-[10px] font-bold text-neutral-500 uppercase">
        Cloud Storage
      </span>
      <span className="text-[10px] font-bold text-white bg-white/10 px-1.5 py-0.5 rounded">
        {Math.round((gdriveInfo.storage_used / gdriveInfo.storage_limit) * 100)}%
      </span>
    </div>
    
    <div className="h-1.5 w-full bg-white/5 rounded-full overflow-hidden border border-white/[0.03]">
      <motion.div
        className="h-full bg-gradient-to-r from-neutral-400 to-white rounded-full"
        initial={{ width: 0 }}
        animate={{ width: `${Math.min((gdriveInfo.storage_used / gdriveInfo.storage_limit) * 100, 100)}%` }}
        transition={{ duration: 1, ease: "easeOut" }}
      />
    </div>
    
    <div className="flex justify-between text-[10px] text-neutral-400">
      <span>{formatStorageSize(gdriveInfo.storage_used)} used</span>
      <span>{formatStorageSize(gdriveInfo.storage_limit - gdriveInfo.storage_used)} left</span>
    </div>
  </div>
)}

Beta Feature Badges

New beta features display a “NEW” badge:
{ id: "ai", label: "AI Chat", icon: Bot, isNew: true }
Badge rendering:
{!isCollapsed && item.isNew && (
  <span className="ml-auto rounded-full px-2 py-0.5 text-[9px] font-bold tracking-[0.14em] uppercase border border-amber-400/45 bg-amber-400/15 text-amber-300">
    New
  </span>
)}
In collapsed mode, the badge appears in the tooltip.

Tooltips (Collapsed Mode)

When collapsed, each button shows a tooltip on hover:
{isCollapsed && (
  <div className="absolute left-full ml-4 z-[60] whitespace-nowrap rounded-lg border border-white/10 bg-[#141414] px-3 py-2 shadow-2xl pointer-events-none opacity-0 translate-x-1 transition-all duration-200 group-hover:opacity-100 group-hover:translate-x-0">
    <span className="text-xs font-semibold text-white">Open {item.label}</span>
    {item.isNew && (
      <span className="text-xs font-bold text-amber-300 tracking-wider">{" • NEW"}</span>
    )}
  </div>
)}
Tooltips use CSS transitions with a 100ms delay on hover.

Expanded Mode

Two-button grid layout:
<div className="grid grid-cols-2 gap-2">
  {/* Settings Button */}
  <button onClick={onOpenSettings} className="group h-11 rounded-xl">
    <Settings className="h-4.5 w-4.5 transition-transform group-hover:rotate-45" />
    <span className="text-xs font-semibold">Settings</span>
  </button>

  {/* Collapse Button */}
  <button
    onClick={() => setIsManualCollapsed(true)}
    disabled={!canToggleCollapse}
  >
    <ChevronLeft className="h-4.5 w-4.5" />
    <span className="text-xs font-semibold">Collapse</span>
  </button>
</div>

Collapsed Mode

Stacked icon-only buttons with tooltips:
<div className="space-y-1.5">
  {/* Settings */}
  <button onClick={onOpenSettings} title="Open settings">
    <Settings className="h-4.5 w-4.5" />
  </button>

  {/* Expand */}
  <button onClick={() => setIsManualCollapsed(false)}>
    <ChevronRight className="h-4.5 w-4.5" />
  </button>
</div>

Styling & Theme

Background

bg-[#0D0D0D]/80 backdrop-blur-2xl
border-r border-white/[0.05]
shadow-2xl

Glossy Overlay

<div className="absolute inset-0 bg-gradient-to-b from-white/[0.02] to-transparent pointer-events-none" />
// Default
text-neutral-500 hover:text-neutral-200 hover:bg-white/[0.03]

// Active
bg-white/[0.08] text-white shadow-[0_0_20px_rgba(255,255,255,0.05)] border border-white/10

Accessibility

  • Semantic <nav> element for navigation items
  • <aside> element for sidebar container
  • title attributes on buttons in collapsed mode
  • Keyboard navigation support
  • Focus states on interactive elements
  • aria-label on icon-only buttons (recommended enhancement)

Performance Optimization

Window Resize Listener

useEffect(() => {
  const handleResize = () => {
    setWindowWidth(window.innerWidth)
  }

  handleResize()  // Initial call
  window.addEventListener("resize", handleResize)
  return () => window.removeEventListener("resize", handleResize)
}, [])

LocalStorage Persistence

useEffect(() => {
  window.localStorage.setItem("sidebar-collapsed", isManualCollapsed ? "1" : "0")
}, [isManualCollapsed])

Animations

Width Transition

<motion.aside
  animate={{ width: sidebarWidth }}
  transition={{ 
    duration: 0.3, 
    ease: [0.22, 1, 0.36, 1]  // Custom cubic-bezier easing
  }}
>

Active Indicator

Spring-based animation for smooth, natural movement:
transition={{ 
  type: "spring", 
  stiffness: 300, 
  damping: 30 
}}

Storage Bar Fill

initial={{ width: 0 }}
animate={{ width: `${percentage}%` }}
transition={{ duration: 1, ease: "easeOut" }}

SettingsModal

Comprehensive settings interface opened from sidebar footer

GoogleDriveSettings

Google Drive OAuth and storage configuration

Source Code

Location: ~/workspace/source/src/components/Sidebar.tsx The component is approximately 323 lines including responsive logic and Google Drive integration.

Build docs developers (and LLMs) love