Skip to content

Commit e84e4c3

Browse files
committed
feat: docs mobile
1 parent 5923342 commit e84e4c3

File tree

4 files changed

+341
-23
lines changed

4 files changed

+341
-23
lines changed

apps/docs/app/docs/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { DocsLayout } from 'fumadocs-ui/layouts/docs';
22
import type { ReactNode } from 'react';
33
import { baseOptions } from '@/app/layout.config';
44
import CustomSidebar from '@/components/custom-sidebar';
5-
import { Navbar } from '@/components/navbar';
5+
import { DocsNavbar } from '@/components/docs-navbar';
66
import { source } from '@/lib/source';
77

88
async function getGithubStars(): Promise<number | null> {
@@ -39,7 +39,7 @@ export default async function Layout({ children }: { children: ReactNode }) {
3939
{...baseOptions}
4040
nav={{
4141
enabled: true,
42-
component: <Navbar stars={stars} />,
42+
component: <DocsNavbar stars={stars} />,
4343
}}
4444
sidebar={{
4545
enabled: true,

apps/docs/components/custom-sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default function CustomSidebar() {
3131
};
3232

3333
return (
34-
<div className="fixed top-16 left-0 z-30 h-[calc(100vh-4rem)]">
34+
<div className="fixed top-16 left-0 z-30 hidden h-[calc(100vh-4rem)] md:block">
3535
<aside className="flex h-full w-[268px] flex-col overflow-y-auto border-border border-t border-r bg-background lg:w-[286px]">
3636
<div className="flex h-full flex-col">
3737
<button
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
'use client';
2+
3+
import { CaretDownIcon, ListIcon, XIcon } from '@phosphor-icons/react';
4+
import { AnimatePresence, motion } from 'motion/react';
5+
import Link from 'next/link';
6+
import { useState } from 'react';
7+
import { ThemeToggle } from '@/components/theme-toggle';
8+
import { Logo } from './logo';
9+
import { NavLink } from './nav-link';
10+
import { contents } from './sidebar-content';
11+
12+
export type DocsNavbarProps = {
13+
stars?: number | null;
14+
};
15+
16+
export const DocsNavbar = ({ stars }: DocsNavbarProps) => {
17+
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
18+
const [openSection, setOpenSection] = useState<number>(0); // Default first section open
19+
20+
const toggleMobileMenu = () => {
21+
setIsMobileMenuOpen(!isMobileMenuOpen);
22+
};
23+
24+
const toggleSection = (index: number) => {
25+
setOpenSection(openSection === index ? -1 : index);
26+
};
27+
28+
return (
29+
<div className="sticky top-0 z-30 flex flex-col border-b backdrop-blur-md">
30+
<nav>
31+
<div className="mx-auto w-full px-2 sm:px-2 md:px-6 lg:px-8">
32+
<div className="flex h-16 items-center justify-between">
33+
{/* Logo Section */}
34+
<div className="flex-shrink-0">
35+
<Logo />
36+
</div>
37+
38+
{/* Desktop Navigation */}
39+
<div className="hidden md:block">
40+
<ul className="flex items-center">
41+
{navMenu.map((menu) => (
42+
<NavLink
43+
external={menu.external}
44+
href={menu.path}
45+
key={menu.name}
46+
>
47+
{menu.name}
48+
</NavLink>
49+
))}
50+
<NavLink
51+
external
52+
href="https://github.com/databuddy-analytics/Databuddy"
53+
>
54+
<span className="inline-flex items-center gap-2">
55+
<svg
56+
className="transition-transform duration-200 hover:scale-110"
57+
height="1.4em"
58+
viewBox="0 0 496 512"
59+
width="1.4em"
60+
xmlns="http://www.w3.org/2000/svg"
61+
>
62+
<title>GitHub</title>
63+
<path
64+
d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6c-3.3.3-5.6-1.3-5.6-3.6c0-2 2.3-3.6 5.2-3.6c3-.3 5.6 1.3 5.6 3.6m-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9c2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3m44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9c.3 2 2.9 3.3 5.9 2.6c2.9-.7 4.9-2.6 4.6-4.6c-.3-1.9-3-3.2-5.9-2.9M244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2c12.8 2.3 17.3-5.6 17.3-12.1c0-6.2-.3-40.4-.3-61.4c0 0-70 15-84.7-29.8c0 0-11.4-29.1-27.8-36.6c0 0-22.9-15.7 1.6-15.4c0 0 24.9 2 38.6 25.8c21.9 38.6 58.6 27.5 72.9 20.9c2.3-16 8.8-27.1 16-33.7c-55.9-6.2-112.3-14.3-112.3-110.5c0-27.5 7.6-41.3 23.6-58.9c-2.6-6.5-11.1-33.3 2.6-67.9c20.9-6.5 69 27 69 27c20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27c13.7 34.7 5.2 61.4 2.6 67.9c16 17.7 25.8 31.5 25.8 58.9c0 96.5-58.9 104.2-114.8 110.5c9.2 7.9 17 22.9 17 46.4c0 33.7-.3 75.4-.3 83.6c0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252C496 113.3 383.5 8 244.8 8M97.2 352.9c-1.3 1-1 3.3.7 5.2c1.6 1.6 3.9 2.3 5.2 1c1.3-1 1-3.3-.7-5.2c-1.6-1.6-3.9-2.3-5.2-1m-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9c1.6 1 3.6.7 4.3-.7c.7-1.3-.3-2.9-2.3-3.9c-2-.6-3.6-.3-4.3.7m32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2c2.3 2.3 5.2 2.6 6.5 1c1.3-1.3.7-4.3-1.3-6.2c-2.2-2.3-5.2-2.6-6.5-1m-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2c-1.4-2.3-4-3.3-5.6-2"
65+
fill="currentColor"
66+
/>
67+
</svg>
68+
{typeof stars === 'number' && (
69+
<span
70+
className="rounded border border-border/40 bg-muted/40 px-2 py-0.5 text-foreground/80 text-xs"
71+
title="GitHub stars"
72+
>
73+
{stars.toLocaleString()}
74+
</span>
75+
)}
76+
</span>
77+
</NavLink>
78+
<li className="ml-2">
79+
<ThemeToggle />
80+
</li>
81+
</ul>
82+
</div>
83+
84+
{/* Mobile Menu Button */}
85+
<button
86+
aria-label="Toggle mobile menu"
87+
className="group relative rounded-lg border border-transparent p-2.5 transition-all duration-200 hover:border-border/30 hover:bg-muted/50 active:bg-muted/70 md:hidden"
88+
onClick={toggleMobileMenu}
89+
type="button"
90+
>
91+
<div className="relative h-6 w-6">
92+
<ListIcon
93+
className={`absolute inset-0 h-6 w-6 transition-all duration-300 ease-out ${
94+
isMobileMenuOpen
95+
? 'rotate-90 scale-90 opacity-0'
96+
: 'rotate-0 scale-100 opacity-100'
97+
}`}
98+
weight="duotone"
99+
/>
100+
<XIcon
101+
className={`absolute inset-0 h-6 w-6 transition-all duration-300 ease-out ${
102+
isMobileMenuOpen
103+
? 'rotate-0 scale-100 opacity-100'
104+
: '-rotate-90 scale-90 opacity-0'
105+
}`}
106+
/>
107+
</div>
108+
</button>
109+
</div>
110+
</div>
111+
</nav>
112+
113+
{/* Mobile Documentation Menu */}
114+
<div
115+
className={`overflow-hidden transition-all duration-300 ease-out md:hidden ${
116+
isMobileMenuOpen
117+
? 'max-h-[80vh] border-border/50 border-b opacity-100'
118+
: 'max-h-0 opacity-0'
119+
}`}
120+
>
121+
<div className="bg-background/95 backdrop-blur-sm">
122+
<div
123+
className="mx-auto max-w-7xl overflow-y-auto px-4 py-4 sm:px-6 lg:px-8"
124+
style={{ maxHeight: '70vh' }}
125+
>
126+
{/* Documentation sections */}
127+
<div className="space-y-2">
128+
{contents.map((section, sectionIndex) => (
129+
<div key={section.title}>
130+
<button
131+
className="flex w-full items-center justify-between rounded-lg px-2 py-2 text-left transition-colors hover:bg-muted/50 active:bg-muted/70"
132+
onClick={() => toggleSection(sectionIndex)}
133+
type="button"
134+
>
135+
<div className="flex items-center gap-2">
136+
<section.Icon
137+
className="h-4 w-4 text-muted-foreground"
138+
weight="duotone"
139+
/>
140+
<h3 className="font-medium text-foreground text-sm">
141+
{section.title}
142+
</h3>
143+
</div>
144+
<motion.div
145+
animate={{
146+
rotate: openSection === sectionIndex ? 180 : 0,
147+
}}
148+
transition={{ duration: 0.2 }}
149+
>
150+
<CaretDownIcon
151+
className="h-4 w-4 text-muted-foreground"
152+
weight="duotone"
153+
/>
154+
</motion.div>
155+
</button>
156+
<AnimatePresence initial={false}>
157+
{openSection === sectionIndex && (
158+
<motion.div
159+
animate={{ opacity: 1, height: 'auto' }}
160+
className="relative overflow-hidden"
161+
exit={{ opacity: 0, height: 0 }}
162+
initial={{ opacity: 0, height: 0 }}
163+
transition={{ duration: 0.3, ease: 'easeInOut' }}
164+
>
165+
<div className="ml-6 space-y-1 pb-2">
166+
{section.list.map((item, itemIndex) => (
167+
<div key={item.title}>
168+
{item.group ? (
169+
<div className="px-2 py-1">
170+
<p className="font-medium text-muted-foreground text-xs uppercase tracking-wider">
171+
{item.title}
172+
</p>
173+
</div>
174+
) : (
175+
<Link
176+
className={`block transform rounded-lg px-3 py-2 text-muted-foreground text-sm transition-all duration-200 hover:translate-x-1 hover:bg-muted/50 hover:text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20 active:bg-muted/70 ${
177+
isMobileMenuOpen
178+
? 'translate-x-0 opacity-100'
179+
: '-translate-x-4 opacity-0'
180+
}`}
181+
href={item.href || '#'}
182+
onClick={() => setIsMobileMenuOpen(false)}
183+
style={{
184+
transitionDelay: isMobileMenuOpen
185+
? `${(sectionIndex * section.list.length + itemIndex) * 30}ms`
186+
: '0ms',
187+
}}
188+
>
189+
<div className="flex items-center gap-2">
190+
<item.icon
191+
className="h-4 w-4 flex-shrink-0"
192+
weight="duotone"
193+
/>
194+
<span>{item.title}</span>
195+
{item.isNew && (
196+
<span className="rounded border border-border/40 bg-muted/40 px-1.5 py-0.5 text-foreground/80 text-xs">
197+
New
198+
</span>
199+
)}
200+
</div>
201+
</Link>
202+
)}
203+
</div>
204+
))}
205+
</div>
206+
</motion.div>
207+
)}
208+
</AnimatePresence>
209+
</div>
210+
))}
211+
</div>
212+
213+
{/* Separator */}
214+
<div className="my-4 h-px bg-border" />
215+
216+
{/* Regular nav items at bottom */}
217+
<div className="space-y-1">
218+
{navMenu
219+
.filter((menu) => menu.name !== 'Docs')
220+
.map((menu, index) => (
221+
<Link
222+
className={`block transform rounded-lg px-3 py-2 font-medium text-sm transition-all duration-200 hover:translate-x-1 hover:bg-muted/50 focus:outline-none focus:ring-2 focus:ring-primary/20 active:bg-muted/70 ${
223+
isMobileMenuOpen
224+
? 'translate-x-0 opacity-100'
225+
: '-translate-x-4 opacity-0'
226+
}`}
227+
href={menu.path}
228+
key={menu.name}
229+
onClick={() => setIsMobileMenuOpen(false)}
230+
style={{
231+
transitionDelay: isMobileMenuOpen
232+
? `${(contents.length * 5 + index) * 30}ms`
233+
: '0ms',
234+
}}
235+
{...(menu.external && {
236+
target: '_blank',
237+
rel: 'noopener noreferrer',
238+
})}
239+
>
240+
{menu.name}
241+
</Link>
242+
))}
243+
<Link
244+
className={`flex transform items-center gap-3 rounded-lg border border-border/30 px-3 py-2 font-medium text-sm transition-all duration-200 hover:translate-x-1 hover:border-border/50 hover:bg-muted/50 focus:outline-none focus:ring-2 focus:ring-primary/20 active:bg-muted/70 ${
245+
isMobileMenuOpen
246+
? 'translate-x-0 opacity-100'
247+
: '-translate-x-4 opacity-0'
248+
}`}
249+
href="https://github.com/databuddy-analytics"
250+
onClick={() => setIsMobileMenuOpen(false)}
251+
rel="noopener noreferrer"
252+
style={{
253+
transitionDelay: isMobileMenuOpen
254+
? `${(contents.length * 5 + navMenu.length) * 30}ms`
255+
: '0ms',
256+
}}
257+
target="_blank"
258+
>
259+
<svg
260+
className="flex-shrink-0 transition-transform duration-200 group-hover:scale-110"
261+
height="1.2em"
262+
viewBox="0 0 496 512"
263+
width="1.2em"
264+
xmlns="http://www.w3.org/2000/svg"
265+
>
266+
<title>GitHub</title>
267+
<path
268+
d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6c-3.3.3-5.6-1.3-5.6-3.6c0-2 2.3-3.6 5.2-3.6c3-.3 5.6 1.3 5.6 3.6m-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9c2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3m44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9c.3 2 2.9 3.3 5.9 2.6c2.9-.7 4.9-2.6 4.6-4.6c-.3-1.9-3-3.2-5.9-2.9M244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2c12.8 2.3 17.3-5.6 17.3-12.1c0-6.2-.3-40.4-.3-61.4c0 0-70 15-84.7-29.8c0 0-11.4-29.1-27.8-36.6c0 0-22.9-15.7 1.6-15.4c0 0 24.9 2 38.6 25.8c21.9 38.6 58.6 27.5 72.9 20.9c2.3-16 8.8-27.1 16-33.7c-55.9-6.2-112.3-14.3-112.3-110.5c0-27.5 7.6-41.3 23.6-58.9c-2.6-6.5-11.1-33.3 2.6-67.9c20.9-6.5 69 27 69 27c20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27c13.7 34.7 5.2 61.4 2.6 67.9c16 17.7 25.8 31.5 25.8 58.9c0 96.5-58.9 104.2-114.8 110.5c9.2 7.9 17 22.9 17 46.4c0 33.7-.3 75.4-.3 83.6c0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252C496 113.3 383.5 8 244.8 8M97.2 352.9c-1.3 1-1 3.3.7 5.2c1.6 1.6 3.9 2.3 5.2 1c1.3-1 1-3.3-.7-5.2c-1.6-1.6-3.9-2.3-5.2-1m-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9c1.6 1 3.6.7 4.3-.7c.7-1.3-.3-2.9-2.3-3.9c-2-.6-3.6-.3-4.3.7m32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2c2.3 2.3 5.2 2.6 6.5 1c1.3-1.3.7-4.3-1.3-6.2c-2.2-2.3-5.2-2.6-6.5-1m-11.4-14.7c-1.6 1-1.6 3.6 0 5.9s4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2c-1.4-2.3-4-3.3-5.6-2"
269+
fill="currentColor"
270+
/>
271+
</svg>
272+
<span className="flex items-center gap-2">
273+
GitHub
274+
{typeof stars === 'number' && (
275+
<span
276+
className="rounded border border-border/40 bg-muted/40 px-2 py-0.5 text-foreground/80 text-xs"
277+
title="GitHub stars"
278+
>
279+
{stars.toLocaleString()}
280+
</span>
281+
)}
282+
</span>
283+
</Link>
284+
</div>
285+
</div>
286+
</div>
287+
</div>
288+
</div>
289+
);
290+
};
291+
292+
export const navMenu = [
293+
{
294+
name: 'Docs',
295+
path: '/docs',
296+
},
297+
{
298+
name: 'Blog',
299+
path: '/blog',
300+
},
301+
{
302+
name: 'Pricing',
303+
path: '/pricing',
304+
},
305+
{
306+
name: 'Dashboard',
307+
path: 'https://app.databuddy.cc',
308+
external: true,
309+
},
310+
];

0 commit comments

Comments
 (0)