← back to Freddy

components/Sidebar.tsx

304 lines

'use client';

import { useState } from 'react';
import {
  BarChart3,
  Flame,
  Building2,
  Landmark,
  Sparkles,
  Send,
  DollarSign,
  TrendingUp,
  Settings,
  ChevronLeft,
  ChevronRight,
} from 'lucide-react';

export type TabId =
  | 'pulse'
  | 'causes'
  | 'organizations'
  | 'granters'
  | 'matches'
  | 'solicitor'
  | 'revenue'
  | 'impact'
  | 'settings';

interface NavItem {
  id: TabId;
  label: string;
  Icon: React.ElementType;
  section: 'main' | 'tools';
}

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

const NAV_ITEMS: NavItem[] = [
  { id: 'pulse',         label: 'Pulse Board',    Icon: BarChart3,   section: 'main'  },
  { id: 'causes',        label: 'Causes',         Icon: Flame,       section: 'main'  },
  { id: 'organizations', label: 'Organizations',  Icon: Building2,   section: 'main'  },
  { id: 'granters',      label: 'Granters',       Icon: Landmark,    section: 'main'  },
  { id: 'matches',       label: 'Matches',        Icon: Sparkles,    section: 'main'  },
  { id: 'solicitor',     label: 'Solicitor',      Icon: Send,        section: 'main'  },
  { id: 'revenue',       label: 'Revenue',        Icon: DollarSign,  section: 'main'  },
  { id: 'impact',        label: 'Impact',         Icon: TrendingUp,  section: 'main'  },
  { id: 'settings',      label: 'Settings',       Icon: Settings,    section: 'tools' },
];

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

  const mainItems = NAV_ITEMS.filter((i) => i.section === 'main');
  const toolItems = NAV_ITEMS.filter((i) => i.section === 'tools');

  return (
    <>
    {isOpen && onToggle && (
      <div
        onClick={onToggle}
        className="sidebar-backdrop"
        style={{
          position: 'fixed',
          inset: 0,
          backgroundColor: 'rgba(0,0,0,0.5)',
          zIndex: 39,
        }}
      />
    )}
    <aside
      className={`sidebar-root${isOpen ? ' sidebar-open' : ''}`}
      style={{
        width: collapsed ? 64 : 220,
        minWidth: collapsed ? 64 : 220,
        transition: 'width 0.2s cubic-bezier(0.4, 0, 0.2, 1), min-width 0.2s cubic-bezier(0.4, 0, 0.2, 1)',
        height: '100vh',
        display: 'flex',
        flexDirection: 'column',
        backgroundColor: 'var(--color-surface)',
        borderRight: '1px solid var(--color-border)',
        position: 'relative',
        overflow: 'hidden',
        flexShrink: 0,
      }}
      aria-label="Main navigation"
    >
      {/* Brand */}
      <div
        style={{
          padding: collapsed ? '16px 0' : '16px',
          display: 'flex',
          alignItems: 'center',
          gap: 10,
          justifyContent: collapsed ? 'center' : 'flex-start',
          borderBottom: '1px solid var(--color-border)',
          height: 56,
          flexShrink: 0,
        }}
      >
        <div
          style={{
            width: 32,
            height: 32,
            borderRadius: 10,
            background: 'linear-gradient(135deg, #d97706, #b45309)',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            flexShrink: 0,
            boxShadow: '0 2px 8px rgba(217, 119, 6, 0.3)',
            fontWeight: 800,
            fontSize: 16,
            color: '#fff',
            fontFamily: 'Georgia, serif',
          }}
        >
          F
        </div>
        {!collapsed && (
          <div style={{ overflow: 'hidden', whiteSpace: 'nowrap' }}>
            <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--color-text)', lineHeight: 1.2 }}>
              Freddy
            </div>
            <div style={{ fontSize: 10, color: 'var(--color-text-muted)', lineHeight: 1.2, letterSpacing: '0.02em' }}>
              Fundraising Coordinator
            </div>
          </div>
        )}
      </div>

      {/* Main Nav */}
      <nav style={{ flex: 1, overflowY: 'auto', overflowX: 'hidden', padding: '8px 8px 0' }}>
        {!collapsed && (
          <div style={{ padding: '4px 8px 6px', fontSize: 10, fontWeight: 600, color: 'var(--color-text-muted)', letterSpacing: '0.06em', textTransform: 'uppercase' }}>
            Platform
          </div>
        )}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
          {mainItems.map(({ id, label, Icon }) => (
            <SidebarButton
              key={id}
              id={id}
              label={label}
              Icon={Icon}
              isActive={activeTab === id}
              collapsed={collapsed}
              onClick={() => onTabChange(id)}
            />
          ))}
        </div>

        <div
          style={{
            height: 1,
            backgroundColor: 'var(--color-border)',
            margin: collapsed ? '12px 8px' : '12px 4px',
            opacity: 0.6,
          }}
        />

        {!collapsed && (
          <div style={{ padding: '4px 8px 6px', fontSize: 10, fontWeight: 600, color: 'var(--color-text-muted)', letterSpacing: '0.06em', textTransform: 'uppercase' }}>
            System
          </div>
        )}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
          {toolItems.map(({ id, label, Icon }) => (
            <SidebarButton
              key={id}
              id={id}
              label={label}
              Icon={Icon}
              isActive={activeTab === id}
              collapsed={collapsed}
              onClick={() => onTabChange(id)}
            />
          ))}
        </div>
      </nav>

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

function SidebarButton({
  label,
  Icon,
  isActive,
  collapsed,
  onClick,
}: {
  id: string;
  label: string;
  Icon: React.ElementType;
  isActive: boolean;
  collapsed: boolean;
  onClick: () => void;
}) {
  return (
    <button
      onClick={onClick}
      aria-current={isActive ? 'page' : undefined}
      title={collapsed ? label : undefined}
      style={{
        display: 'flex',
        alignItems: 'center',
        gap: 10,
        padding: collapsed ? '10px 0' : '8px 12px',
        justifyContent: collapsed ? 'center' : 'flex-start',
        borderRadius: 8,
        border: 'none',
        cursor: 'pointer',
        position: 'relative',
        backgroundColor: isActive ? 'rgba(217, 119, 6, 0.12)' : 'transparent',
        color: isActive ? 'var(--color-text)' : 'var(--color-text-secondary)',
        fontSize: 13,
        fontWeight: isActive ? 600 : 500,
        transition: 'background-color 0.15s, color 0.15s',
        width: '100%',
        overflow: 'hidden',
        whiteSpace: 'nowrap',
      }}
      onMouseEnter={(e) => {
        if (!isActive) {
          e.currentTarget.style.backgroundColor = 'var(--color-surface-el)';
          e.currentTarget.style.color = 'var(--color-text)';
        }
      }}
      onMouseLeave={(e) => {
        if (!isActive) {
          e.currentTarget.style.backgroundColor = 'transparent';
          e.currentTarget.style.color = 'var(--color-text-secondary)';
        }
      }}
    >
      {/* Active indicator bar -- amber */}
      {isActive && (
        <div
          style={{
            position: 'absolute',
            left: 0,
            top: '20%',
            bottom: '20%',
            width: 3,
            borderRadius: '0 3px 3px 0',
            background: 'linear-gradient(180deg, #fbbf24, #d97706)',
          }}
        />
      )}
      <Icon
        size={18}
        aria-hidden="true"
        style={{
          color: isActive ? '#fbbf24' : 'currentColor',
          flexShrink: 0,
          transition: 'color 0.15s',
        }}
      />
      {!collapsed && <span>{label}</span>}
      {collapsed && <span className="sr-only">{label}</span>}
    </button>
  );
}