Skip to content

Commit 4061e57

Browse files
Implementing a burger menu for small viewports
1 parent 0b7b12e commit 4061e57

3 files changed

Lines changed: 196 additions & 0 deletions

File tree

production/css/header.css

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,131 @@
349349
display: none;
350350
}
351351

352+
/* ─── Mobile hamburger button ───────────────────────────────────────────────── */
353+
354+
#mobile-nav-toggle {
355+
display: none; /* revealed by mobile media query */
356+
flex-direction: column;
357+
justify-content: center;
358+
gap: 5px;
359+
width: 36px;
360+
height: 36px;
361+
padding: var(--spacing-0);
362+
border: none;
363+
background: transparent;
364+
cursor: pointer;
365+
366+
span {
367+
display: block;
368+
width: 22px;
369+
height: 2px;
370+
border-radius: 2px;
371+
background: var(--ink);
372+
transition: transform 0.2s ease, opacity 0.2s ease;
373+
transform-origin: center;
374+
}
375+
376+
@media (prefers-reduced-motion: reduce) {
377+
span {
378+
transition: none;
379+
}
380+
}
381+
}
382+
383+
body.mobile-nav-open #mobile-nav-toggle {
384+
span:nth-child(1) {
385+
transform: translateY(7px) rotate(45deg);
386+
}
387+
388+
span:nth-child(2) {
389+
opacity: 0;
390+
}
391+
392+
span:nth-child(3) {
393+
transform: translateY(-7px) rotate(-45deg);
394+
}
395+
}
396+
397+
/* ─── Mobile nav drawer ─────────────────────────────────────────────────────── */
398+
399+
#mobile-nav-drawer {
400+
position: fixed;
401+
top: 90px; /* 45px brand bar + 45px mobile-header */
402+
left: 0;
403+
z-index: 3;
404+
display: none; /* revealed by mobile media query */
405+
flex-direction: column;
406+
width: 100%;
407+
max-height: 0;
408+
overflow: hidden;
409+
background: var(--white);
410+
transition: max-height 0.25s ease;
411+
border-bottom: 1px solid var(--color-border-default);
412+
box-shadow: var(--shadow-elevation-sm);
413+
414+
.account-nav {
415+
display: flex;
416+
align-items: center;
417+
padding: var(--spacing-3) var(--spacing-5);
418+
border-bottom: 1px solid var(--color-border-default);
419+
420+
.login-link {
421+
display: inline-flex;
422+
align-items: center;
423+
width: auto;
424+
height: auto;
425+
padding: var(--spacing-2) var(--spacing-6);
426+
border-radius: var(--radius-lg);
427+
background-color: var(--blurple);
428+
color: var(--primary-white-hex) !important;
429+
font-family: Gellix, sans-serif;
430+
font-size: var(--font-size-sm);
431+
font-weight: var(--font-weight-semibold);
432+
text-decoration: none;
433+
}
434+
435+
.info-circle-wrapper {
436+
border-left: none;
437+
padding: var(--spacing-0);
438+
cursor: pointer;
439+
440+
.info-circle {
441+
display: inline-flex;
442+
justify-content: center;
443+
align-items: center;
444+
width: 36px;
445+
height: 36px;
446+
border-radius: var(--radius-circle);
447+
background: var(--solar);
448+
color: var(--color-surface-base);
449+
font-size: var(--font-size-base);
450+
font-weight: var(--font-weight-bold);
451+
line-height: var(--line-height-none);
452+
}
453+
}
454+
}
455+
456+
.mobile-nav-link {
457+
display: block;
458+
padding: var(--spacing-4) var(--spacing-5);
459+
border-top: 1px solid var(--color-border-default);
460+
color: var(--ink);
461+
font-size: var(--font-size-sm);
462+
font-weight: var(--font-weight-semibold);
463+
text-decoration: none;
464+
465+
&:hover {
466+
background: var(--color-surface-soft);
467+
}
468+
}
469+
470+
@media (prefers-reduced-motion: reduce) {
471+
transition: none;
472+
}
473+
}
474+
475+
/* ─── Mobile layout ─────────────────────────────────────────────────────────── */
476+
352477
@media screen and (max-width: 690px) {
353478
#mobile-header {
354479
z-index: 1;
@@ -371,6 +496,18 @@
371496
}
372497
}
373498

499+
#mobile-nav-toggle {
500+
display: flex;
501+
}
502+
503+
#mobile-nav-drawer {
504+
display: flex;
505+
}
506+
507+
body.mobile-nav-open #mobile-nav-drawer {
508+
max-height: 300px;
509+
}
510+
374511
#header #header-left > a,
375512
#header #header-right #to-chainguard {
376513
display: none;
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Q, el, sanitizeUrl } from "./utils.mjs";
2+
import { CG } from "./CG.mjs";
3+
4+
/**
5+
* Adds a hamburger button to #mobile-header-right and a slide-down drawer
6+
* below it. The drawer holds a clone of .account-nav (so Skilljar's JS on
7+
* the original is undisturbed) and the "Go to Chainguard" link.
8+
*
9+
* Must be called after CG.dom.mobileHeader is set and before setupCoursesNav()
10+
* so the .account-nav clone is taken while the element is still in #header-right.
11+
*/
12+
export function setupMobileNav() {
13+
const btn = el("button", {
14+
id: "mobile-nav-toggle",
15+
type: "button",
16+
aria: { label: "Open menu", expanded: "false" },
17+
}, [el("span"), el("span"), el("span")]);
18+
19+
const accountNavClone = Q(".account-nav")?.cloneNode(true) ?? null;
20+
21+
const drawer = el("nav", {
22+
id: "mobile-nav-drawer",
23+
aria: { label: "Mobile navigation", hidden: "true" },
24+
}, [
25+
...(accountNavClone ? [accountNavClone] : []),
26+
el("a", {
27+
className: "mobile-nav-link",
28+
href: sanitizeUrl("https://www.chainguard.dev"),
29+
target: "_blank",
30+
rel: "noopener noreferrer",
31+
text: "Go to Chainguard →",
32+
}),
33+
]);
34+
35+
CG.dom.mobileHeader.right.replaceChildren(btn);
36+
37+
Q("header#mobile-header").insertAdjacentElement("afterend", drawer);
38+
39+
function setOpen(open) {
40+
CG.dom.body.classList.toggle("mobile-nav-open", open);
41+
btn.setAttribute("aria-expanded", String(open));
42+
btn.setAttribute("aria-label", open ? "Close menu" : "Open menu");
43+
drawer.setAttribute("aria-hidden", String(!open));
44+
}
45+
46+
btn.addEventListener("click", () =>
47+
setOpen(!CG.dom.body.classList.contains("mobile-nav-open"))
48+
);
49+
50+
document.addEventListener("click", (e) => {
51+
if (!CG.dom.body.classList.contains("mobile-nav-open")) return;
52+
if (btn.contains(e.target) || drawer.contains(e.target)) return;
53+
setOpen(false);
54+
});
55+
}

production/skilljar-theme-v3.0/router.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import { generateFooter } from "./footer.mjs";
1515
import { hide } from "./styling.mjs";
1616
import { setupCoursesNav } from "./courses-nav.mjs";
17+
import { setupMobileNav } from "./mobile-nav.mjs";
1718

1819
/**
1920
* An array of page handlers that map specific page conditions to their corresponding view functions.
@@ -119,6 +120,9 @@ export function preRoute() {
119120
right: Q("#mobile-header-right"),
120121
};
121122

123+
// Mobile hamburger (clone account-nav before setupCoursesNav moves it)
124+
setupMobileNav();
125+
122126
// Two-tier header: brand bar + sticky courses nav
123127
if (CG.page.isLanding || CG.page.isCatalog) {
124128
setupCoursesNav();

0 commit comments

Comments
 (0)