Skip to main content
Glama
Sidebar.tsx5.92 kB
'use client'; import { useState, useMemo } from 'react'; import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { getAllNavigation, NavSection } from '@/lib/design-system'; function ChevronIcon({ expanded }: { expanded: boolean }) { return ( <svg className={`w-4 h-4 transition-transform duration-200 ${expanded ? 'rotate-90' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor" > <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" /> </svg> ); } interface SidebarProps { onLinkClick?: () => void; isMobile?: boolean; } export function Sidebar({ onLinkClick, isMobile = false }: SidebarProps) { const pathname = usePathname(); const navSections = useMemo(() => getAllNavigation(), []); // Both main sections (Docs and Components) expanded by default const [expandedSections, setExpandedSections] = useState<Set<string>>(() => { return new Set(navSections.map(s => s.id)); }); const toggleSection = (sectionId: string) => { setExpandedSections(prev => { const next = new Set(prev); if (next.has(sectionId)) { next.delete(sectionId); } else { next.add(sectionId); } return next; }); }; // Check if a path is active const isActivePath = (href: string) => pathname === href; // Check if section has active item const sectionHasActiveItem = (section: NavSection) => { if (section.items) { return section.items.some(item => pathname.startsWith(item.href)); } if (section.categories) { return section.categories.some(cat => cat.items.some(item => pathname.startsWith(item.href)) ); } return false; }; return ( <aside className={`${isMobile ? 'w-full' : 'w-64 shrink-0 border-r border-default h-[calc(100vh-64px)] sticky top-16 overflow-y-auto hidden md:block'} bg-surface`}> <nav className="p-4 space-y-2"> {/* Home link (no accordion) */} <div className="mb-2"> <Link href="/" onClick={onLinkClick} className={`block px-3 py-2 text-sm rounded-lg transition-colors ${ pathname === '/' ? 'text-default font-medium bg-surface-hover' : 'text-muted hover:bg-surface-hover hover:text-default' }`} > Home </Link> </div> {/* Accordion sections (skip home) */} {navSections.filter(s => s.id !== 'home').map((section) => { const isExpanded = expandedSections.has(section.id); const hasActiveItem = sectionHasActiveItem(section); return ( <div key={section.id}> {/* Accordion header */} <button type="button" onClick={() => toggleSection(section.id)} className={`w-full flex items-center justify-between px-3 py-2 text-xs font-semibold uppercase tracking-wider transition-colors ${ hasActiveItem ? 'text-default' : 'text-subtle hover:text-muted' }`} > {section.title} <ChevronIcon expanded={isExpanded} /> </button> {/* Collapsible content with smooth animation */} <div className={`overflow-hidden transition-all duration-200 ${ isExpanded ? 'max-h-[2000px] opacity-100' : 'max-h-0 opacity-0' }`} > <div className="mt-1 space-y-1"> {/* Direct items (for Docs section) */} {section.items?.map((item) => { const isActive = isActivePath(item.href); return ( <Link key={item.href} href={item.href} onClick={onLinkClick} className={`block px-3 py-2 text-sm rounded-lg transition-colors ${ isActive ? 'text-default font-medium bg-surface-hover border-l-2 border-primary -ml-0.5 pl-[10px]' : 'text-muted hover:bg-surface-hover hover:text-default' }`} > {item.title} </Link> ); })} {/* Nested categories (for Components section) */} {section.categories?.map((category) => ( <div key={category.id} className="mt-3"> <div className="px-3 py-1 text-xs font-medium text-subtle"> {category.title} </div> <div className="mt-1 space-y-0.5"> {category.items.map((item) => { const isActive = isActivePath(item.href); return ( <Link key={item.href} href={item.href} onClick={onLinkClick} className={`block px-3 py-1.5 text-sm rounded-lg transition-colors ${ isActive ? 'text-default font-medium bg-surface-hover border-l-2 border-primary -ml-0.5 pl-[10px]' : 'text-muted hover:bg-surface-hover hover:text-default' }`} > {item.title} </Link> ); })} </div> </div> ))} </div> </div> </div> ); })} </nav> </aside> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/heyadam/mcpsystemdesign'

If you have feedback or need assistance with the MCP directory API, please join our Discord server