Skip to content

Commit b5644bb

Browse files
committed
Fix.
1 parent cee29e3 commit b5644bb

2 files changed

Lines changed: 89 additions & 174 deletions

File tree

components/docs/sidebar/doc-sidebar.tsx

Lines changed: 87 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@ import { AnimatePresence, motion } from "framer-motion";
44
import { BookOpen, ChevronLeft, Github, Menu, X } from "lucide-react";
55
import Link from "next/link";
66
import { usePathname } from "next/navigation";
7-
import { type FC, useCallback, useEffect, useMemo, useState } from "react";
7+
import { type FC, useEffect, useMemo, useState } from "react";
88
import { createPortal } from "react-dom";
99
import { Dropdown, type DropdownOption } from "@/components/ui/dropdown";
10+
import { useReducedMotion } from "@/hooks/use-reduced-motion";
1011
import { getDocProjectIcon } from "@/lib/docs-projects";
1112
import { cn } from "@/lib/utils";
1213
import { NetlifyHighlight } from "./netlify-highlight";
1314
import SidebarItem from "./sidebar-item";
1415
import type { DocSidebarProps } from "./types";
16+
import { useMobileSidebar } from "./use-mobile-sidebar";
1517

1618
const DocSidebar: FC<DocSidebarProps> = ({ className = "", onItemClick, sidebarStructure }) => {
1719
const pathname = usePathname();
18-
const [isMobile, setIsMobile] = useState(false);
19-
const [isOpen, setIsOpen] = useState(false);
20+
const { isOpen, isMobile, toggleSidebar, sidebarRef, toggleButtonRef, setIsOpen } =
21+
useMobileSidebar();
2022
const [isMounted, setIsMounted] = useState(false);
23+
const prefersReducedMotion = useReducedMotion();
2124

2225
const currentProject = useMemo(() => {
2326
return (
@@ -49,83 +52,41 @@ const DocSidebar: FC<DocSidebarProps> = ({ className = "", onItemClick, sidebarS
4952
}, [selectedProject, sidebarStructure]);
5053

5154
useEffect(() => {
52-
setIsMounted(true);
53-
}, []);
54-
55-
useEffect(() => {
56-
const check = () => {
57-
const mobile = window.innerWidth < 1024;
58-
setIsMobile(mobile);
59-
if (!mobile) {
60-
setIsOpen(false);
55+
const handleEscapeKey = (event: KeyboardEvent) => {
56+
if (event.key === "Escape" && isOpen && isMobile) {
57+
toggleSidebar();
6158
}
6259
};
63-
check();
64-
window.addEventListener("resize", check);
65-
return () => window.removeEventListener("resize", check);
66-
}, []);
67-
68-
useEffect(() => {
69-
if (isMobile && isOpen) {
70-
document.body.style.overflow = "hidden";
71-
} else {
72-
document.body.style.overflow = "";
73-
}
74-
return () => {
75-
document.body.style.overflow = "";
76-
};
77-
}, [isMobile, isOpen]);
78-
79-
// Close on navigation
80-
// biome-ignore lint/correctness/useExhaustiveDependencies: intentionally close on pathname change
81-
useEffect(() => {
82-
if (isMobile && isOpen) {
83-
setIsOpen(false);
84-
}
85-
}, [pathname]);
86-
87-
useEffect(() => {
88-
const handleEscape = (e: KeyboardEvent) => {
89-
if (e.key === "Escape" && isOpen && isMobile) {
90-
setIsOpen(false);
91-
}
92-
};
93-
document.addEventListener("keydown", handleEscape);
94-
return () => document.removeEventListener("keydown", handleEscape);
95-
}, [isOpen, isMobile]);
96-
97-
const closeSidebar = useCallback(() => setIsOpen(false), []);
98-
const toggleSidebar = useCallback(() => setIsOpen((v) => !v), []);
9960

100-
const handleItemClick = useCallback(
101-
(path: string) => {
102-
onItemClick?.(path);
103-
if (isMobile) {
104-
setIsOpen(false);
105-
}
106-
},
107-
[isMobile, onItemClick]
108-
);
61+
document.addEventListener("keydown", handleEscapeKey);
62+
return () => document.removeEventListener("keydown", handleEscapeKey);
63+
}, [isOpen, isMobile, toggleSidebar]);
10964

11065
const sidebarContent = (
11166
<>
112-
{/* Header */}
113-
<div className="shrink-0 border-gray-200/80 border-b bg-gray-50/80 px-4 py-4 backdrop-blur-sm dark:border-gray-800 dark:bg-gray-900/40">
67+
{/* Sidebar Header */}
68+
<div className="relative z-10 flex shrink-0 flex-col gap-3 border-gray-200 border-b bg-gray-50/50 px-4 py-4 backdrop-blur-sm dark:border-gray-800 dark:bg-gray-900/20">
11469
<div className="flex items-center gap-3">
115-
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-blue-600 dark:bg-blue-500">
116-
<BookOpen className="h-4 w-4 text-white" />
117-
</div>
118-
<div>
119-
<h2 className="font-semibold text-gray-900 text-sm leading-tight dark:text-white">
120-
Documentation
121-
</h2>
70+
<motion.div
71+
className="flex h-9 w-9 items-center justify-center rounded-lg bg-blue-600 shadow-xs dark:bg-blue-500"
72+
transition={{ duration: prefersReducedMotion ? 0 : 0.3 }}
73+
whileHover={{
74+
scale: prefersReducedMotion ? 1 : 1.1,
75+
rotate: prefersReducedMotion ? 0 : 5,
76+
}}
77+
>
78+
<BookOpen className="h-5 w-5 text-white" />
79+
</motion.div>
80+
<div className="flex flex-col">
81+
<h2 className="font-bold text-gray-900 text-sm dark:text-white">Documentation</h2>
12282
<p className="text-gray-500 text-xs dark:text-gray-400">Browse all topics</p>
12383
</div>
12484
</div>
12585

126-
<div className="relative z-[100] mt-3">
86+
{/* Project Filter Dropdown */}
87+
<div className="relative z-[100] mt-2">
12788
<Dropdown
128-
buttonClassName="h-9 text-xs"
89+
buttonClassName="h-10 text-xs"
12990
className="w-full"
13091
menuClassName="max-h-[300px] z-[100]"
13192
onChange={setSelectedProject}
@@ -137,9 +98,6 @@ const DocSidebar: FC<DocSidebarProps> = ({ className = "", onItemClick, sidebarS
13798

13899
<NetlifyHighlight />
139100

140-
{/* Items */}
141-
<div className="scrollbar-hide flex-1 overflow-y-auto px-3 py-3">
142-
<div className="space-y-0.5">
143101
{/* Sidebar Content - Scrollable with hidden scrollbar */}
144102
<div
145103
className="scrollbar-hide flex-1 px-3 py-4"
@@ -153,42 +111,58 @@ const DocSidebar: FC<DocSidebarProps> = ({ className = "", onItemClick, sidebarS
153111
item={item}
154112
key={item.path}
155113
level={0}
156-
onItemClick={handleItemClick}
114+
onItemClick={(path) => {
115+
onItemClick?.(path);
116+
if (isMobile) {
117+
setIsOpen(false);
118+
}
119+
}}
157120
/>
158121
))}
159122
</div>
160123
</div>
161124

162-
{/* Footer */}
163-
<div className="shrink-0 border-gray-200/80 border-t bg-gray-50/80 px-4 py-3 backdrop-blur-sm dark:border-gray-800 dark:bg-gray-900/40">
125+
{/* Sidebar Footer - Simple */}
126+
<div className="shrink-0 border-gray-200 border-t bg-gray-50/50 px-4 py-3 backdrop-blur-sm dark:border-gray-800 dark:bg-gray-900/20">
164127
<div className="flex items-center justify-between">
165-
<p className="text-gray-400 text-xs dark:text-gray-500">
166-
&copy; {new Date().getFullYear()} EternalCodeTeam
128+
<p className="text-gray-500 text-xs dark:text-gray-400">
129+
© {new Date().getFullYear()} EternalCodeTeam
167130
</p>
168131
<Link
169-
className="text-gray-400 transition-colors hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"
132+
className="group"
170133
href="https://github.com/eternalcodeteam"
171134
rel="noopener noreferrer"
172135
target="_blank"
173136
title="GitHub"
174137
>
175-
<Github className="h-4 w-4" />
138+
<motion.div
139+
whileHover={{ scale: prefersReducedMotion ? 1 : 1.1 }}
140+
whileTap={{ scale: prefersReducedMotion ? 1 : 0.95 }}
141+
>
142+
<Github className="h-4 w-4 text-gray-500 transition-colors group-hover:text-gray-900 dark:text-gray-400 dark:group-hover:text-white" />
143+
</motion.div>
176144
</Link>
177145
</div>
178146
</div>
179147
</>
180148
);
181149

150+
useEffect(() => {
151+
setIsMounted(true);
152+
}, []);
153+
182154
return (
183155
<>
184-
{/* Mobile toggle */}
156+
{/* Mobile toggle button */}
185157
{!!isMobile && (
186-
<button
158+
<motion.button
187159
aria-controls="doc-sidebar"
188160
aria-expanded={isOpen}
189-
className="group mb-4 flex w-full items-center justify-between gap-3 rounded-xl border border-gray-200 bg-white px-4 py-3 text-left font-medium text-gray-900 text-sm shadow-sm transition-all hover:border-blue-300 hover:shadow-md lg:hidden dark:border-gray-800 dark:bg-gray-900 dark:text-white dark:hover:border-blue-700"
161+
className="group mb-4 flex w-full items-center justify-between gap-3 rounded-lg border border-gray-200 bg-white px-4 py-3 font-medium text-gray-900 text-sm shadow-xs transition-all hover:border-blue-300 hover:shadow-md lg:hidden dark:border-gray-700 dark:bg-gray-800 dark:text-white dark:hover:border-blue-700"
190162
onClick={toggleSidebar}
191-
type="button"
163+
ref={toggleButtonRef}
164+
whileHover={{ scale: prefersReducedMotion ? 1 : 1.01 }}
165+
whileTap={{ scale: prefersReducedMotion ? 1 : 0.99 }}
192166
>
193167
<div className="flex items-center gap-2">
194168
{isOpen ? (
@@ -200,14 +174,14 @@ const DocSidebar: FC<DocSidebarProps> = ({ className = "", onItemClick, sidebarS
200174
</div>
201175
<ChevronLeft
202176
className={cn(
203-
"h-4 w-4 transition-transform duration-200",
204-
isOpen ? "rotate-90" : "-rotate-90"
177+
"h-4 w-4 transform-gpu transition-transform duration-300 will-change-transform",
178+
isOpen ? "rotate-180" : "rotate-0"
205179
)}
206180
/>
207-
</button>
181+
</motion.button>
208182
)}
209183

210-
{/* Desktop sidebar */}
184+
{/* Desktop Sidebar */}
211185
{!isMobile && (
212186
<nav
213187
aria-label="Documentation navigation"
@@ -218,101 +192,43 @@ const DocSidebar: FC<DocSidebarProps> = ({ className = "", onItemClick, sidebarS
218192
</nav>
219193
)}
220194

221-
{/* Mobile sidebar portal */}
222195
{isMounted &&
223196
createPortal(
224-
<AnimatePresence>
225-
{!!isMobile && !!isOpen && (
226-
<>
227-
<motion.div
228-
animate={{ opacity: 1 }}
229-
aria-hidden="true"
230-
className="fixed inset-0 z-[60] bg-black/50 backdrop-blur-sm"
231-
exit={{ opacity: 0 }}
232-
initial={{ opacity: 0 }}
233-
onClick={closeSidebar}
234-
/>
197+
<>
198+
<AnimatePresence mode="wait">
199+
{!!isMobile && !!isOpen && (
235200
<motion.nav
236201
animate={{ x: 0 }}
237202
aria-label="Documentation navigation"
238-
className="fixed inset-y-0 left-0 z-[70] flex w-80 max-w-[85vw] flex-col overflow-hidden bg-white shadow-2xl dark:bg-gray-950"
239-
exit={{ x: "-100%" }}
203+
className="fixed inset-y-0 left-0 z-[70] flex w-72 flex-col overflow-auto overscroll-contain border-gray-200 border-r bg-white shadow-2xl dark:border-gray-700 dark:bg-gray-900"
204+
exit={{ x: prefersReducedMotion ? 0 : -280 }}
240205
id="doc-sidebar-mobile"
241-
initial={{ x: "-100%" }}
206+
initial={{ x: prefersReducedMotion ? 0 : -280 }}
207+
ref={sidebarRef}
242208
role="navigation"
243-
transition={{ type: "spring", stiffness: 400, damping: 40 }}
209+
transition={{
210+
type: prefersReducedMotion ? "tween" : "spring",
211+
stiffness: 300,
212+
damping: 30,
213+
duration: prefersReducedMotion ? 0 : undefined,
214+
}}
244215
>
245-
{/* Mobile header with close */}
246-
<div className="flex items-center justify-between border-gray-100 border-b px-4 py-3 dark:border-gray-800">
247-
<div className="flex items-center gap-2">
248-
<BookOpen className="h-4 w-4 text-blue-600 dark:text-blue-400" />
249-
<span className="font-semibold text-gray-900 text-sm dark:text-white">
250-
Navigation
251-
</span>
252-
</div>
253-
<button
254-
aria-label="Close navigation"
255-
className="flex h-8 w-8 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-gray-800 dark:hover:text-gray-200"
256-
onClick={closeSidebar}
257-
type="button"
258-
>
259-
<X className="h-4 w-4" />
260-
</button>
261-
</div>
262-
263-
{/* Project dropdown */}
264-
<div className="border-gray-100 border-b px-4 py-3 dark:border-gray-800">
265-
<div className="relative z-[100]">
266-
<Dropdown
267-
buttonClassName="h-9 text-xs"
268-
className="w-full"
269-
menuClassName="max-h-[300px] z-[100]"
270-
onChange={setSelectedProject}
271-
options={projectOptions}
272-
value={selectedProject}
273-
/>
274-
</div>
275-
</div>
276-
277-
{/* Items */}
278-
<div className="scrollbar-hide flex-1 overflow-y-auto px-3 py-3">
279-
<div className="space-y-0.5">
280-
{filteredDocsStructure.map((item, index) => (
281-
<SidebarItem
282-
index={index}
283-
isActive={pathname === item.path}
284-
item={item}
285-
key={item.path}
286-
level={0}
287-
onItemClick={handleItemClick}
288-
/>
289-
))}
290-
</div>
291-
</div>
292-
293-
<NetlifyHighlight />
294-
295-
{/* Footer */}
296-
<div className="shrink-0 border-gray-100 border-t px-4 py-3 dark:border-gray-800">
297-
<div className="flex items-center justify-between">
298-
<p className="text-gray-400 text-xs dark:text-gray-500">
299-
&copy; {new Date().getFullYear()} EternalCodeTeam
300-
</p>
301-
<Link
302-
className="text-gray-400 transition-colors hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300"
303-
href="https://github.com/eternalcodeteam"
304-
rel="noopener noreferrer"
305-
target="_blank"
306-
title="GitHub"
307-
>
308-
<Github className="h-4 w-4" />
309-
</Link>
310-
</div>
311-
</div>
216+
{sidebarContent}
312217
</motion.nav>
313-
</>
218+
)}
219+
</AnimatePresence>
220+
221+
{!!isMobile && !!isOpen && (
222+
<motion.div
223+
animate={{ opacity: 1 }}
224+
aria-hidden="true"
225+
className="fixed inset-0 z-[60] bg-black/50 backdrop-blur-xs lg:hidden"
226+
exit={{ opacity: 0 }}
227+
initial={{ opacity: 0 }}
228+
onClick={toggleSidebar}
229+
/>
314230
)}
315-
</AnimatePresence>,
231+
</>,
316232
document.body
317233
)}
318234
</>

components/hero/announcement-banner.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,12 @@ export default function AnnouncementBanner() {
4242
</span>
4343

4444
<p className="text-sm text-white/90">
45-
We just launched the{" "}
46-
<span className="font-semibold text-white">Notification Generator</span>
45+
Explore the latest <span className="font-semibold text-white">stable</span> and <span className="font-semibold text-white">dev</span> builds using the <span className="font-semibold text-white">Build Explorer</span>.
4746
</p>
4847

4948
<Link
5049
className="group inline-flex items-center gap-1 font-medium text-sm text-white/95 underline decoration-white/30 underline-offset-2 transition-colors hover:decoration-white/60"
51-
href="/notification-generator"
50+
href="/builds"
5251
>
5352
Check it out
5453
<ArrowRight className="h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" />

0 commit comments

Comments
 (0)