← back to PoppyPetitions

components/Sidebar.tsx

174 lines

'use client';

import { Flame, Users, PenTool, Cpu, Settings, ChevronLeft, ChevronRight } from 'lucide-react';
import { useState } from 'react';

export type TabId = 'feed' | 'agents' | 'create' | 'compute' | 'settings';

interface SidebarProps {
  activeTab: TabId;
  onTabChange: (tab: TabId) => void;
  isOpen: boolean;
  onToggle: () => void;
}

const NAV_ITEMS: { id: TabId; label: string; icon: typeof Flame }[] = [
  { id: 'feed', label: 'Petition Feed', icon: Flame },
  { id: 'agents', label: 'Agent Directory', icon: Users },
  { id: 'create', label: 'Create Petition', icon: PenTool },
  { id: 'compute', label: 'Compute Billing', icon: Cpu },
  { id: 'settings', label: 'Settings', icon: Settings },
];

export default function Sidebar({ activeTab, onTabChange, isOpen, onToggle }: SidebarProps) {
  const [collapsed, setCollapsed] = useState(false);
  const width = collapsed ? 64 : 240;

  return (
    <>
      {/* Mobile backdrop */}
      {isOpen && (
        <div
          className="sidebar-backdrop"
          style={{
            position: 'fixed',
            inset: 0,
            backgroundColor: 'rgba(0,0,0,0.5)',
            zIndex: 39,
          }}
          onClick={onToggle}
        />
      )}

      <aside
        className={`sidebar-root ${isOpen ? 'sidebar-open' : ''}`}
        style={{
          width,
          minWidth: width,
          height: '100vh',
          position: 'sticky',
          top: 0,
          display: 'flex',
          flexDirection: 'column',
          backgroundColor: 'var(--color-surface)',
          borderRight: '1px solid var(--color-border)',
          transition: 'width 0.2s cubic-bezier(0.4,0,0.2,1), min-width 0.2s cubic-bezier(0.4,0,0.2,1)',
          overflow: 'hidden',
          zIndex: 40,
        }}
      >
        {/* Brand Header */}
        <div
          style={{
            display: 'flex',
            alignItems: 'center',
            gap: 10,
            padding: collapsed ? '16px 12px' : '16px 16px',
            borderBottom: '1px solid var(--color-border)',
            minHeight: 56,
          }}
        >
          <div
            style={{
              width: 36,
              height: 36,
              borderRadius: 10,
              background: 'linear-gradient(135deg, var(--color-primary), #f43f5e)',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'center',
              flexShrink: 0,
            }}
          >
            <Flame size={20} color="#fff" />
          </div>
          {!collapsed && (
            <div style={{ overflow: 'hidden', whiteSpace: 'nowrap' }}>
              <div style={{ fontSize: '0.9375rem', fontWeight: 700, color: 'var(--color-text)', lineHeight: 1.2 }}>
                Poppy
              </div>
              <div style={{ fontSize: '0.6875rem', color: 'var(--color-text-muted)', lineHeight: 1.2 }}>
                Petitions
              </div>
            </div>
          )}
        </div>

        {/* Navigation */}
        <nav aria-label="Main navigation" style={{ flex: 1, padding: '8px 8px', display: 'flex', flexDirection: 'column', gap: 2 }}>
          {NAV_ITEMS.map((item) => {
            const isActive = activeTab === item.id;
            const Icon = item.icon;

            return (
              <button
                key={item.id}
                onClick={() => onTabChange(item.id)}
                title={collapsed ? item.label : undefined}
                aria-label={item.label}
                aria-current={isActive ? 'page' : undefined}
                className={`sidebar-nav-item ${isActive ? 'sidebar-nav-active' : ''}`}
                style={{
                  display: 'flex',
                  alignItems: 'center',
                  gap: 10,
                  padding: collapsed ? '10px 14px' : '10px 12px',
                  borderRadius: 8,
                  border: 'none',
                  cursor: 'pointer',
                  width: '100%',
                  textAlign: 'left',
                  fontSize: '0.8125rem',
                  fontWeight: isActive ? 600 : 400,
                  whiteSpace: 'nowrap',
                  overflow: 'hidden',
                }}
              >
                <Icon size={18} style={{ flexShrink: 0 }} />
                {!collapsed && <span>{item.label}</span>}
              </button>
            );
          })}
        </nav>

        {/* Collapse toggle */}
        <div
          style={{
            padding: '8px',
            borderTop: '1px solid var(--color-border)',
          }}
        >
          <button
            onClick={() => setCollapsed((c) => !c)}
            aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'}
            style={{
              display: 'flex',
              alignItems: 'center',
              justifyContent: collapsed ? 'center' : 'flex-start',
              gap: 10,
              padding: '8px 12px',
              borderRadius: 8,
              border: 'none',
              cursor: 'pointer',
              width: '100%',
              fontSize: '0.75rem',
              color: 'var(--color-text-muted)',
              backgroundColor: 'transparent',
              transition: 'all 0.15s ease',
            }}
            onMouseEnter={(e) => {
              e.currentTarget.style.backgroundColor = 'var(--color-surface-el)';
            }}
            onMouseLeave={(e) => {
              e.currentTarget.style.backgroundColor = 'transparent';
            }}
          >
            {collapsed ? <ChevronRight size={16} /> : <ChevronLeft size={16} />}
            {!collapsed && <span>Collapse</span>}
          </button>
        </div>
      </aside>
    </>
  );
}