Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions app/docs.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,21 @@
.docs-shell { grid-template-columns: 240px minmax(0, 1fr) !important; }
.docs-shell > .docs-toc { display: none !important; }
}
@media (max-width: 720px) {
/* ≤880px the sidebar becomes a slide-in drawer (body.docnav-open contract,
opened from the SiteNav burger) — same pattern as the API reference. */
@media (max-width: 880px) {
.docs-shell { grid-template-columns: 1fr !important; }
.docs-shell > .docs-side { display: none !important; }
}
/* aside.docs-side out-specifies the base `.docs-shell .docs-side` sticky
rule below (the grid rules above use !important for the same reason). */
.docs-shell > aside.docs-side {
position: fixed; top: 0; left: 0; bottom: 0; width: 296px; z-index: 60;
height: 100vh; background: var(--bg); border-right: 1px solid var(--line);
transform: translateX(-100%); transition: transform .25s; padding-top: 70px;
}
body.docnav-open .docs-shell > aside.docs-side { transform: none; }
body.docnav-open .docs-shell .docnav-scrim { display: block; position: fixed; inset: 0; z-index: 55; background: rgba(0,0,0,.5); }
}
.docs-shell .docnav-scrim { display: none; }

.docs-shell {
/* Design var aliases → framework tokens. Local to the docs shell so they
Expand Down
32 changes: 32 additions & 0 deletions app/markline-tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,39 @@ body {
/* nav wrap (constrains content width, matches the design) */
.nav .wrap { width: 100%; max-width: 1240px; margin-inline: auto; padding-inline: 32px; }

/* mobile menu button — hidden on desktop, replaces the nav links ≤880px */
.nav-burger { display: none; width: 36px; height: 36px; place-items: center; border-radius: 999px; border: 1px solid var(--line); background: transparent; color: var(--ink-2); transition: border-color .15s, color .15s; }
.nav-burger:hover { color: var(--ink); border-color: var(--ink-4); }
.nav-burger svg { width: 18px; height: 18px; }

/* fallback dropdown for pages without a sidebar drawer (home, 404) */
.nav-mobile-panel { display: none; }

@media (max-width: 880px) {
.nav .wrap { padding-inline: 20px; }
.nav-links, .ghbadge .lbl { display: none; }
.nav-burger { display: grid; flex: none; }
/* Fit brand + actions on narrow screens: tighter gaps/padding, smaller logo,
and a shrinkable brand so nothing (the burger) gets pushed off-viewport. */
.nav-in { gap: 14px; }
.nav-in--full, .nav-in--contained { padding-inline: 16px; }
.nav-in .brand { min-width: 0; overflow: hidden; }
.brand .brand-logo { height: 22px; max-width: 100%; object-position: left center; }
.nav-right { gap: 8px; flex: none; }
.nav-right .theme-btn, .nav-right .ghbadge { flex: none; }
.nav-mobile-panel { display: flex; flex-direction: column; gap: 2px; padding: 10px 14px 14px; border-top: 1px solid var(--line); background: var(--bg); }
.nav-mobile-panel a { font-size: 15px; color: var(--ink-2); padding: 11px 10px; border-radius: 9px; text-decoration: none; }
.nav-mobile-panel a:hover { color: var(--ink); background: var(--panel-hi); }
.nav-mobile-panel a.active { color: var(--ink); font-weight: 500; }
.nav-mobile-panel .btn { justify-content: center; margin-top: 8px; }
}

/* tab links pinned at the top of the mobile sidebar drawer (docs shell) so the
top-level sections stay reachable on mobile */
.drawer-tabs { display: none; }
@media (max-width: 880px) {
.drawer-tabs { display: flex; flex-direction: column; gap: 2px; margin: 0 0 14px; padding-bottom: 12px; border-bottom: 1px solid var(--line); }
.drawer-tabs a { display: block; font-size: 13.5px; font-weight: 500; color: var(--ink-2); padding: 8px 10px; border-radius: 8px; text-decoration: none; }
.drawer-tabs a:hover { color: var(--ink); background: var(--panel-hi); }
.drawer-tabs a.active { color: var(--accent-ink, rgb(var(--c-brand))); background: var(--accent-dim, rgb(var(--c-brand) / 0.10)); }
}
20 changes: 19 additions & 1 deletion components/docs/nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,14 +143,32 @@ export function DocsSidebar({
return () => cancelAnimationFrame(id);
}, [pathname]);

const closeDrawer = () => document.body.classList.remove("docnav-open");

return (
<>
{/* Mobile drawer scrim (body.docnav-open contract — see app/docs.css). */}
<div className="docnav-scrim" onClick={closeDrawer} />
<aside className="docs-side" ref={sideRef}>
{tabs.length > 1 && (
<nav className="drawer-tabs">
{tabs.map((t) => (
<Link
key={t.id}
href={t.href}
className={t.id === activeId ? "active" : undefined}
onClick={closeDrawer}
>
{t.label}
</Link>
))}
</nav>
)}
<div className="docs-tools">
<SidebarSearchTrigger />
{ai && <SidebarAskButton />}
</div>
<SidebarSections sections={sections} pathname={pathname} />
<SidebarSections sections={sections} pathname={pathname} onNavigate={closeDrawer} />
</aside>
{/* Page-level AI affordances (the doc-ai row + View-as-Markdown modal) live
in the docs shell so they're available on every docs page. */}
Expand Down
56 changes: 56 additions & 0 deletions components/site-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,26 @@ export function SiteNav({
const pathname = usePathname();
// The homepage ("/") uses its own width knob; every other route uses `width`.
const layout = pathname === "/" ? homeWidth : width;
const [menuOpen, setMenuOpen] = useState(false);

// Close the mobile menu / page drawer whenever the route changes.
useEffect(() => {
setMenuOpen(false);
document.body.classList.remove("docnav-open");
}, [pathname]);

// Mobile menu button. Pages that ship a sidebar drawer (docs shell, API
// reference) use the shared body.docnav-open contract; pages without one
// (home, 404) fall back to the SiteNav's own dropdown panel with the links.
const onBurger = () => {
if (document.querySelector(".docs-side, .api-side")) {
setMenuOpen(false);
document.body.classList.toggle("docnav-open");
return;
}
document.body.classList.remove("docnav-open");
setMenuOpen((v) => !v);
};

const isActive = (href: string) => {
const path = href.split(/[#?]/)[0] || "/";
Expand Down Expand Up @@ -105,8 +125,44 @@ export function SiteNav({
{cta.label}
</Link>
)}
<button
className="nav-burger"
type="button"
onClick={onBurger}
aria-label={menuOpen ? "Close menu" : "Open menu"}
aria-expanded={menuOpen}
>
{menuOpen ? (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" aria-hidden="true">
<path d="M6 6l12 12M18 6L6 18" />
</svg>
) : (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" aria-hidden="true">
<path d="M4 7h16M4 12h16M4 17h16" />
</svg>
)}
</button>
</div>
</div>
{menuOpen && (
<div className="nav-mobile-panel">
{links.map((l) => (
<Link
key={l.href + l.label}
href={l.href}
className={isActive(l.href) ? "active" : undefined}
onClick={() => setMenuOpen(false)}
>
{l.label}
</Link>
))}
{cta && (
<Link className="btn btn-primary btn-sm" href={cta.href} onClick={() => setMenuOpen(false)}>
{cta.label}
</Link>
)}
</div>
)}
</header>
);
}
Expand Down