← 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>
</>
);
}