forked from
standard.site/standard.site
Standard.site landing page built in Next.js
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}