Skip to content

Commit f6f9e3c

Browse files
ambicuityrohitg00
andauthored
fix(website): portal mobile nav sheet to body and close breakpoint gap (#670)
* fix(website): portal mobile nav sheet to body so it covers the viewport The mobile nav sheet was rendered inside the <header> element, which uses backdrop-filter: blur(10px). Per CSS spec, backdrop-filter establishes a containing block for fixed-positioned descendants, so the sheet's `position: fixed; inset: 0` was clipped to the header strip instead of covering the viewport. The dark overlay only painted across the top bar, leaving the hero fully visible behind the menu and the panel items squeezed into a strip too short to render. Render the sheet through createPortal to document.body so it escapes the nav's containing block. Drop backdrop-filter on the sheet (no longer needed once the bg is fully opaque) and switch to var(--abyss) so hero content can't bleed through even if a browser ignores 0.94 opacity. Closes #669 * fix(website): close mobile nav gaps on top of the portal fix Show the hamburger at the same 860px breakpoint that hides the section links; previously the links disappeared at 860px but the hamburger only appeared at 720px, leaving tablet widths with no navigation at all. Make the closed sheet inert so keyboard focus cannot tab into hidden links, return focus to the toggle when the sheet closes, and add noreferrer to the external links. --------- Co-authored-by: Rohit Ghumare <ghumare64@gmail.com>
1 parent f3dc7f8 commit f6f9e3c

2 files changed

Lines changed: 70 additions & 49 deletions

File tree

website/components/MobileNavToggle.module.css

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@
3131
.sheet {
3232
position: fixed;
3333
inset: 0;
34-
background: rgba(0, 0, 0, 0.94);
35-
backdrop-filter: blur(8px);
34+
/* Portaled to <body>, so this sits in the root stacking context. Kept
35+
below the nav (z-index: 100) so the brand + close button stay on top.
36+
Fully opaque so hero content can't bleed through. */
37+
background: var(--abyss);
3638
z-index: 95;
3739
opacity: 0;
3840
pointer-events: none;
@@ -90,7 +92,9 @@
9092
color: var(--gold);
9193
}
9294

93-
@media (max-width: 720px) {
95+
/* Must match the breakpoint that hides .links in Nav.module.css, or the
96+
721-860px range has no navigation at all. */
97+
@media (max-width: 860px) {
9498
.hamburger {
9599
display: inline-flex;
96100
}
Lines changed: 63 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use client";
22

3-
import { useEffect, useState } from "react";
3+
import { useEffect, useRef, useState } from "react";
4+
import { createPortal } from "react-dom";
45
import { formatCompact } from "@/lib/format";
56
import styles from "./MobileNavToggle.module.css";
67

@@ -17,6 +18,12 @@ export function MobileNavToggle({
1718
stars: number;
1819
}) {
1920
const [open, setOpen] = useState(false);
21+
const [mounted, setMounted] = useState(false);
22+
const toggleRef = useRef<HTMLButtonElement>(null);
23+
24+
useEffect(() => {
25+
setMounted(true);
26+
}, []);
2027

2128
useEffect(() => {
2229
if (!open) return;
@@ -28,9 +35,63 @@ export function MobileNavToggle({
2835
return () => {
2936
document.removeEventListener("keydown", onKey);
3037
document.body.style.overflow = "";
38+
toggleRef.current?.focus();
3139
};
3240
}, [open]);
3341

42+
// The parent <header> uses `backdrop-filter`, which establishes a containing
43+
// block for fixed descendants — that would clip the sheet to the header
44+
// strip. Portal it to <body> so `position: fixed; inset: 0` covers the
45+
// viewport instead.
46+
const sheet = (
47+
<div
48+
className={`${styles.sheet} ${open ? styles.sheetOpen : ""}`}
49+
aria-hidden={!open}
50+
inert={!open}
51+
onClick={(e) => {
52+
if (e.target === e.currentTarget) setOpen(false);
53+
}}
54+
>
55+
<nav className={styles.panel} aria-label="Site navigation">
56+
<ul className={styles.list}>
57+
{sections.map((s) => (
58+
<li key={s.href}>
59+
<a href={s.href} onClick={() => setOpen(false)}>
60+
{s.label}
61+
</a>
62+
</li>
63+
))}
64+
</ul>
65+
<div className={styles.foot}>
66+
<a
67+
href="https://github.com/rohitg00/agentmemory"
68+
target="_blank"
69+
rel="noopener noreferrer"
70+
onClick={() => setOpen(false)}
71+
>
72+
GITHUB · {formatCompact(stars)}
73+
</a>
74+
<a
75+
href="https://www.npmjs.com/package/@agentmemory/agentmemory"
76+
target="_blank"
77+
rel="noopener noreferrer"
78+
onClick={() => setOpen(false)}
79+
>
80+
NPM
81+
</a>
82+
<a
83+
href="https://github.com/rohitg00/agentmemory/blob/main/CHANGELOG.md"
84+
target="_blank"
85+
rel="noopener noreferrer"
86+
onClick={() => setOpen(false)}
87+
>
88+
CHANGELOG
89+
</a>
90+
</div>
91+
</nav>
92+
</div>
93+
);
94+
3495
return (
3596
<>
3697
<button
@@ -44,51 +105,7 @@ export function MobileNavToggle({
44105
<span className={`${styles.bar} ${open ? styles.bar3 : ""}`} />
45106
</button>
46107

47-
<div
48-
className={`${styles.sheet} ${open ? styles.sheetOpen : ""}`}
49-
aria-hidden={!open}
50-
onClick={(e) => {
51-
if (e.target === e.currentTarget) setOpen(false);
52-
}}
53-
>
54-
<nav className={styles.panel} aria-label="Site navigation">
55-
<ul className={styles.list}>
56-
{sections.map((s) => (
57-
<li key={s.href}>
58-
<a href={s.href} onClick={() => setOpen(false)}>
59-
{s.label}
60-
</a>
61-
</li>
62-
))}
63-
</ul>
64-
<div className={styles.foot}>
65-
<a
66-
href="https://github.com/rohitg00/agentmemory"
67-
target="_blank"
68-
rel="noopener"
69-
onClick={() => setOpen(false)}
70-
>
71-
GITHUB · {formatCompact(stars)}
72-
</a>
73-
<a
74-
href="https://www.npmjs.com/package/@agentmemory/agentmemory"
75-
target="_blank"
76-
rel="noopener"
77-
onClick={() => setOpen(false)}
78-
>
79-
NPM
80-
</a>
81-
<a
82-
href="https://github.com/rohitg00/agentmemory/blob/main/CHANGELOG.md"
83-
target="_blank"
84-
rel="noopener"
85-
onClick={() => setOpen(false)}
86-
>
87-
CHANGELOG
88-
</a>
89-
</div>
90-
</nav>
91-
</div>
108+
{mounted ? createPortal(sheet, document.body) : null}
92109
</>
93110
);
94111
}

0 commit comments

Comments
 (0)