Files
boc/web-v2/src/components/layout/Sidebar.tsx
T

154 lines
5.3 KiB
TypeScript
Raw Normal View History

import { NavLink, useLocation } from 'react-router-dom'
import { motion, AnimatePresence } from 'framer-motion'
import { useUIStore } from '@/stores/uiStore'
import {
LayoutDashboard,
Users,
TrendingUp,
Wallet,
Briefcase,
FileText,
Megaphone,
HeadphonesIcon,
Zap,
ChevronLeft,
ChevronRight,
Newspaper,
2026-08-12 07:50:12 +00:00
Bot,
Inbox,
Target,
MessageCircle,
} from 'lucide-react'
import { cn } from '@/lib/utils'
const navItems = [
{ path: '/', label: 'Briefing', icon: Newspaper },
{ path: '/dashboard', label: 'Dashboard', icon: LayoutDashboard },
2026-08-12 07:50:12 +00:00
{ path: '/agents', label: 'Agents', icon: Bot },
{ path: '/comm/inbox', label: 'Inbox', icon: Inbox },
{ path: '/comm/leads', label: 'Leads', icon: Target },
{ path: '/comm/contacts', label: 'Contacts', icon: MessageCircle },
{ path: '/crm', label: 'CRM', icon: Users },
{ path: '/sales', label: 'Sales', icon: TrendingUp },
{ path: '/finance', label: 'Finance', icon: Wallet },
{ path: '/hr', label: 'HR', icon: Briefcase },
{ path: '/legal', label: 'Legal', icon: FileText },
{ path: '/marketing', label: 'Marketing', icon: Megaphone },
{ path: '/support', label: 'Support', icon: HeadphonesIcon },
{ path: '/automation', label: 'Automation', icon: Zap },
]
export function Sidebar() {
const { sidebarOpen, toggleSidebar } = useUIStore()
const location = useLocation()
return (
<>
{/* Mobile overlay */}
<AnimatePresence>
{sidebarOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
className="fixed inset-0 bg-black/20 z-40 lg:hidden"
onClick={toggleSidebar}
/>
)}
</AnimatePresence>
<motion.aside
initial={false}
animate={{ width: sidebarOpen ? 240 : 72 }}
transition={{ duration: 0.2, ease: 'easeOut' }}
className={cn(
'fixed left-0 top-0 h-full bg-sidebar z-50 flex flex-col',
'border-r border-border/60',
sidebarOpen ? 'px-4' : 'px-3'
)}
>
{/* Logo */}
<div className="h-16 flex items-center justify-between shrink-0">
<div className="flex items-center gap-2.5 overflow-hidden">
<div className="w-8 h-8 rounded-lg bg-primary flex items-center justify-center shrink-0">
<svg width="16" height="16" viewBox="0 0 32 32" fill="none">
<path d="M10 22L16 10L22 22H10Z" stroke="white" strokeWidth="2.5" strokeLinejoin="round" />
</svg>
</div>
<AnimatePresence>
{sidebarOpen && (
<motion.span
initial={{ opacity: 0, x: -8 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -8 }}
transition={{ duration: 0.15 }}
className="text-sm font-semibold text-text-primary whitespace-nowrap"
>
AMOS
</motion.span>
)}
</AnimatePresence>
</div>
<button
onClick={toggleSidebar}
className="hidden lg:flex w-7 h-7 items-center justify-center rounded-lg hover:bg-bg text-text-secondary transition-colors"
>
{sidebarOpen ? <ChevronLeft size={14} /> : <ChevronRight size={14} />}
</button>
</div>
{/* Navigation */}
<nav className="flex-1 py-4 space-y-0.5 overflow-y-auto">
{navItems.map((item) => {
const isActive = location.pathname === item.path ||
(item.path !== '/' && location.pathname.startsWith(item.path))
const Icon = item.icon
return (
<NavLink
key={item.path}
to={item.path}
onClick={() => {
if (window.innerWidth < 1024) toggleSidebar()
}}
className={cn(
'flex items-center gap-3 h-10 rounded-xl px-3 transition-colors duration-150 relative',
'hover:bg-primary-light',
isActive && 'bg-primary-light text-primary',
!isActive && 'text-text-secondary'
)}
>
{isActive && (
<motion.div
layoutId="sidebar-active"
className="absolute left-0 top-1/2 -translate-y-1/2 w-0.5 h-5 bg-primary rounded-full"
transition={{ duration: 0.18, ease: 'easeOut' }}
/>
)}
<Icon size={18} strokeWidth={isActive ? 2 : 1.5} />
<AnimatePresence>
{sidebarOpen && (
<motion.span
initial={{ opacity: 0, x: -8 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -8 }}
transition={{ duration: 0.15 }}
className={cn(
'text-sm font-medium whitespace-nowrap',
isActive ? 'text-primary' : 'text-text-secondary'
)}
>
{item.label}
</motion.span>
)}
</AnimatePresence>
</NavLink>
)
})}
</nav>
</motion.aside>
</>
)
}