Standard.site landing page built in Next.js
at main 135 lines 6.0 kB view raw
1'use client' 2 3import { useEffect, useState } from 'react' 4import Link from 'next/link' 5import { usePathname } from 'next/navigation' 6import { ArrowLeftIcon, MenuIcon, XIcon } from 'lucide-react' 7import BlurEffect from 'react-progressive-blur' 8import { AnimateIn, StandardSiteLogo } from '@/app/components' 9import { DOCS_NAV } from '@/app/data/docs-nav' 10 11export function DocsNav() { 12 const [isOpen, setIsOpen] = useState(false) 13 const pathname = usePathname() 14 const [prevPathname, setPrevPathname] = useState(pathname) 15 16 // Close menu when route changes (adjusting state during render) 17 if (pathname !== prevPathname) { 18 setPrevPathname(pathname) 19 setIsOpen(false) 20 } 21 22 useEffect(() => { 23 if (isOpen) { 24 document.body.style.overflow = 'hidden' 25 } else { 26 document.body.style.overflow = '' 27 } 28 29 return () => { 30 document.body.style.overflow = '' 31 } 32 }, [isOpen]) 33 34 return ( 35 <> 36 <AnimateIn 37 as="header" 38 direction="down" 39 delay={0.5} 40 onScroll={false} 41 className="fixed left-0 right-0 top-0 z-30 px-4 py-2 md:hidden" 42 > 43 <div 44 className={`p-4 relative flex flex-col gap-6 z-40 mx-auto max-w-[38rem] w-full min-h-0 overflow-hidden rounded-2xl transition-all duration-300 ease-in-out ${ 45 isOpen 46 ? 'bg-zinc-950 dark:bg-zinc-50 text-zinc-50 dark:text-zinc-950 max-h-[80vh] overflow-y-auto' 47 : 'text-base-content h-15' 48 }`} 49 > 50 <div className="flex justify-between items-center"> 51 <Link href="/" className="flex items-center gap-2"> 52 <StandardSiteLogo className="size-7" /> 53 <span className="font-medium text-sm tracking-tight">Docs</span> 54 </Link> 55 {!isOpen && ( 56 <button 57 onClick={() => setIsOpen(true)} 58 aria-label="Open menu" 59 > 60 <MenuIcon className="size-6" /> 61 </button> 62 )} 63 {isOpen && ( 64 <button 65 onClick={() => setIsOpen(false)} 66 aria-label="Close menu" 67 > 68 <XIcon className="size-6" /> 69 </button> 70 )} 71 </div> 72 73 {isOpen && ( 74 <nav className="flex flex-col gap-6"> 75 {DOCS_NAV.map((section) => ( 76 <div key={section.title} className="flex flex-col gap-2"> 77 <span className="font-mono text-xs font-medium tracking-tight text-muted-content uppercase"> 78 {section.title} 79 </span> 80 <div className="flex flex-col gap-2"> 81 {section.items.map((item) => { 82 const isDocPage = item.href.startsWith('/docs/') 83 const className = `font-medium text-lg tracking-tight ${ 84 pathname === item.href 85 ? 'text-zinc-50 dark:text-zinc-950' 86 : 'text-muted-content' 87 } hover:text-zinc-50 dark:hover:text-zinc-950 transition-colors` 88 89 if (!isDocPage) { 90 return ( 91 <a key={item.href} href={item.href} className={className}> 92 {item.label} 93 </a> 94 ) 95 } 96 97 return ( 98 <Link key={item.href} href={item.href} className={className}> 99 {item.label} 100 </Link> 101 ) 102 })} 103 </div> 104 </div> 105 ))} 106 107 <div className="h-px w-full bg-border/10" /> 108 109 <Link 110 href="/" 111 className="flex items-center gap-2 font-medium text-lg tracking-tight text-muted-content hover:text-zinc-50 dark:hover:text-zinc-950 transition-colors" 112 > 113 <ArrowLeftIcon className="size-5" /> 114 Back home 115 </Link> 116 </nav> 117 )} 118 </div> 119 <BlurEffect 120 className="bg-gradient-to-b from-base-100 to-transparent absolute inset-0 w-full h-full -z-10" 121 position="top" 122 intensity={75} 123 /> 124 </AnimateIn> 125 126 {/* Overlay */} 127 <div 128 className={`fixed inset-0 z-10 bg-base-100/50 backdrop-blur-sm transition-opacity duration-300 md:hidden ${ 129 isOpen ? 'opacity-100' : 'opacity-0 pointer-events-none' 130 }`} 131 onClick={() => setIsOpen(false)} 132 /> 133 </> 134 ) 135}