Complete nav rewrite: native details/summary, zero JS state, zero transforms
Local Build and Deploy / deploy (push) Successful in 1m5s

- Mobile nav: uses <details>/<summary> for toggle, no useState, no useEffect
- Nav dropdown: uses <details>/<summary>, no useState, no event listeners
- Desktop: separate div with desktop nav items (hidden on mobile)
- Mobile: hamburger dropdown with absolute positioned panel
- CSS: removed ::after pseudo-element with transform, replaced hover
  with background-color only, added details[open] CSS rules
- No backdrop-filter, no will-change, no transition-all, no GPU hacks
- Works on every device without JS state management
This commit is contained in:
openhands committed 2026-07-14 16:25:36 +02:00
1 parent 5978b3c13d
commit 48a5394204
4 files changed
+194 -140

No files matched your search

+37 -75
View File
@@ -1,6 +1,6 @@
"use client";
import { type ReactNode, useEffect, useRef, useState } from "react";
import { type ReactNode } from "react";
interface MobileNavProps {
children: ReactNode;
@@ -13,44 +13,14 @@ export function MobileNav({
menuLabel = "Open menu",
closeLabel = "Close menu",
}: MobileNavProps) {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!open) return;
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") setOpen(false);
};
document.addEventListener("keydown", onKey);
return () => document.removeEventListener("keydown", onKey);
}, [open]);
useEffect(() => {
if (!open) return;
const onClick = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as Node)) {
setOpen(false);
}
};
document.addEventListener("mousedown", onClick);
return () => document.removeEventListener("mousedown", onClick);
}, [open]);
return (
<div ref={ref} className="relative flex md:hidden">
<button
type="button"
onClick={() => setOpen((v) => !v)}
className="flex items-center justify-center w-10 h-10 rounded-lg ml-auto shrink-0"
style={{
color: "var(--color-navbar-text-readable, var(--color-navbar-text))",
}}
aria-expanded={open}
aria-controls="mobile-menu"
aria-label={open ? closeLabel : menuLabel}
<details className="relative md:hidden ml-auto">
<summary
className="list-none cursor-pointer flex items-center justify-center w-10 h-10 rounded-lg shrink-0 [&::-webkit-details-marker]:hidden"
aria-label={"Menu"}
>
<svg
className="w-5 h-5"
className="w-5 h-5 details-open:hidden"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
@@ -58,47 +28,39 @@ export function MobileNav({
strokeLinecap="round"
strokeLinejoin="round"
>
{open ? (
<>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</>
) : (
<>
<line x1="3" y1="6" x2="21" y2="6" />
<line x1="3" y1="12" x2="21" y2="12" />
<line x1="3" y1="18" x2="21" y2="18" />
</>
)}
<line x1="3" y1="6" x2="21" y2="6" />
<line x1="3" y1="12" x2="21" y2="12" />
<line x1="3" y1="18" x2="21" y2="18" />
</svg>
</button>
{open && (
<div
className="fixed inset-0 z-30"
onClick={() => setOpen(false)}
style={{ backgroundColor: "rgba(0,0,0,0.6)" }}
/>
)}
{open && (
<div
id="mobile-menu"
role="menu"
className="absolute right-0 top-full mt-1 w-screen max-w-[360px] z-40 overflow-y-auto max-h-[70vh]"
style={{ backgroundColor: "var(--color-navbar)" }}
<svg
className="w-5 h-5 hidden details-open:block"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<div
className="flex flex-col gap-y-0.5 p-2 rounded-xl border shadow-lg mx-2 mb-2"
style={{
borderColor:
"color-mix(in srgb, var(--color-text-muted) 10%, transparent)",
}}
>
{children}
</div>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
</summary>
<div
className="absolute right-0 top-full mt-1 w-[280px] sm:w-[320px] z-50 overflow-y-auto max-h-[70vh]"
style={{ backgroundColor: "var(--color-navbar)" }}
>
<div
className="flex flex-col gap-y-0.5 p-2 rounded-xl border shadow-lg mx-2 mb-2"
style={{
backgroundColor: "var(--color-dropdown, var(--color-navbar))",
borderColor:
"color-mix(in srgb, var(--color-text-muted) 10%, transparent)",
}}
>
{children}
</div>
)}
</div>
</div>
</details>
);
}