Skip to main content
Glama
MobileDrawer.tsx3.74 kB
'use client'; import { useEffect, useState } from 'react'; import { usePathname } from 'next/navigation'; import { Sidebar } from './Sidebar'; interface MobileDrawerProps { isOpen: boolean; onClose: () => void; } export function MobileDrawer({ isOpen, onClose }: MobileDrawerProps) { const pathname = usePathname(); const [isVisible, setIsVisible] = useState(false); const [isAnimating, setIsAnimating] = useState(false); // Handle visibility and animation states useEffect(() => { if (isOpen) { setIsVisible(true); // Reset animation state first to ensure it starts from closed position setIsAnimating(false); // Trigger animation after DOM is ready - small delay ensures initial state is painted const timer = setTimeout(() => { setIsAnimating(true); }, 10); // Small delay to ensure browser paints the initial closed state return () => clearTimeout(timer); } else { setIsAnimating(false); // Wait for animation to complete before removing from DOM const timer = setTimeout(() => { setIsVisible(false); }, 300); // Match transition duration return () => clearTimeout(timer); } }, [isOpen]); // Close drawer on route change useEffect(() => { if (isOpen) { onClose(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [pathname]); // Lock body scroll when open useEffect(() => { if (isOpen) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = ''; } return () => { document.body.style.overflow = ''; }; }, [isOpen]); // Close on escape key useEffect(() => { if (!isOpen) return; const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', handleEscape); return () => document.removeEventListener('keydown', handleEscape); // eslint-disable-next-line react-hooks/exhaustive-deps }, [isOpen]); if (!isVisible) return null; return ( <div className="fixed inset-0 z-[60] md:hidden"> {/* Backdrop */} <div className={`fixed inset-0 bg-surface-overlay backdrop-blur-sm transition-opacity duration-300 ${ isAnimating ? 'opacity-100' : 'opacity-0' }`} onClick={onClose} aria-hidden="true" /> {/* Drawer */} <div className={`fixed inset-y-0 left-0 w-72 bg-surface shadow-xl overflow-y-auto z-10 transition-transform duration-300 ease-out ${ isAnimating ? 'translate-x-0' : '-translate-x-full' }`} > {/* Header */} <div className="flex items-center justify-between p-4 border-b border-default"> <div className="flex items-center gap-2"> <div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center"> <span className="text-primary-foreground font-bold text-sm">M</span> </div> <span className="font-semibold text-default">mcpsystem.design</span> </div> <button type="button" onClick={onClose} className="w-8 h-8 flex items-center justify-center rounded-lg hover:bg-surface-hover transition-colors" aria-label="Close menu" > <svg className="w-5 h-5 text-muted" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> </svg> </button> </div> {/* Unified Navigation - reuses Sidebar component */} <Sidebar isMobile onLinkClick={onClose} /> </div> </div> ); }

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