/** * Shared Sidebar Navigation Components * * Reusable building blocks for main sidebar and settings sidebar navigation. */ import React from 'react'; import { Link, useLocation } from 'react-router-dom'; import { ChevronDown, Lock, LucideIcon } from 'lucide-react'; interface SidebarSectionProps { title?: string; children: React.ReactNode; isCollapsed?: boolean; className?: string; } /** * Section wrapper with optional header */ export const SidebarSection: React.FC = ({ title, children, isCollapsed = false, className = '', }) => { return (
{title && !isCollapsed && (

{title}

)} {title && isCollapsed && (
)} {children}
); }; interface SidebarItemProps { to: string; icon: LucideIcon; label: string; isCollapsed?: boolean; exact?: boolean; disabled?: boolean; badge?: string | number; variant?: 'default' | 'settings'; locked?: boolean; } /** * Navigation item with icon */ export const SidebarItem: React.FC = ({ to, icon: Icon, label, isCollapsed = false, exact = false, disabled = false, badge, variant = 'default', locked = false, }) => { const location = useLocation(); const isActive = exact ? location.pathname === to : location.pathname.startsWith(to); const baseClasses = 'flex items-center gap-3 py-2.5 text-sm font-medium rounded-lg transition-colors'; const collapsedClasses = isCollapsed ? 'px-3 justify-center' : 'px-4'; // Different color schemes for main nav vs settings nav const colorClasses = variant === 'settings' ? isActive ? 'bg-brand-50 text-brand-700 dark:bg-brand-900/30 dark:text-brand-400' : locked ? 'text-gray-400 hover:text-gray-500 hover:bg-gray-50 dark:text-gray-500 dark:hover:text-gray-400 dark:hover:bg-gray-800' : 'text-gray-600 hover:text-gray-900 hover:bg-gray-50 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-800' : isActive ? 'bg-white/10 text-white' : locked ? 'text-white/40 hover:text-white/60 hover:bg-white/5' : 'text-white/70 hover:text-white hover:bg-white/5'; const disabledClasses = variant === 'settings' ? 'text-gray-300 dark:text-gray-600 cursor-not-allowed' : 'text-white/30 cursor-not-allowed'; const className = `${baseClasses} ${collapsedClasses} ${disabled ? disabledClasses : colorClasses}`; if (disabled) { return (
{!isCollapsed && {label}} {badge && !isCollapsed && ( {badge} )}
); } return ( {!isCollapsed && ( {label} {locked && } )} {badge && !isCollapsed && ( {badge} )} ); }; interface SidebarDropdownProps { icon: LucideIcon; label: string; children: React.ReactNode; isCollapsed?: boolean; defaultOpen?: boolean; isActiveWhen?: string[]; } /** * Collapsible dropdown section */ export const SidebarDropdown: React.FC = ({ icon: Icon, label, children, isCollapsed = false, defaultOpen = false, isActiveWhen = [], }) => { const location = useLocation(); const [isOpen, setIsOpen] = React.useState( defaultOpen || isActiveWhen.some(path => location.pathname.startsWith(path)) ); const isActive = isActiveWhen.some(path => location.pathname.startsWith(path)); return (
{isOpen && !isCollapsed && (
{children}
)}
); }; interface SidebarSubItemProps { to: string; icon: LucideIcon; label: string; } /** * Sub-item for dropdown menus */ export const SidebarSubItem: React.FC = ({ to, icon: Icon, label, }) => { const location = useLocation(); const isActive = location.pathname === to; return ( {label} ); }; interface SidebarDividerProps { isCollapsed?: boolean; } /** * Visual divider between sections */ export const SidebarDivider: React.FC = ({ isCollapsed }) => { return (
); }; interface SettingsSidebarSectionProps { title: string; children: React.ReactNode; } /** * Section for settings sidebar (different styling) */ export const SettingsSidebarSection: React.FC = ({ title, children, }) => { return (

{title}

{children}
); }; interface SettingsSidebarItemProps { to: string; icon: LucideIcon; label: string; description?: string; locked?: boolean; } /** * Settings navigation item with optional description and lock indicator */ export const SettingsSidebarItem: React.FC = ({ to, icon: Icon, label, description, locked = false, }) => { const location = useLocation(); const isActive = location.pathname === to || location.pathname.startsWith(to + '/'); return (
{label} {locked && ( )}
{description && (

{description}

)}
); };