Skip to content

Commit 3ab14cc

Browse files
committed
feat: implement responsive mobile navigation
- Added `mobile-nav.js` to handle mobile navigation toggle, focus trapping, and keyboard accessibility. - Created `nav-links.html` partial for reusable navigation link structure. - Refactored `nav.html` to embed `nav-links.html` for both desktop and mobile views. - Enhanced `header.html` with a mobile navigation toggle button and associated styles. - Updated `baseof.html` to include the mobile navigation script.
1 parent f1e6f23 commit 3ab14cc

6 files changed

Lines changed: 140 additions & 21 deletions

File tree

static/js/mobile-nav.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
(function () {
2+
var toggle = document.querySelector("[data-mobile-nav-toggle]");
3+
var panel = document.querySelector("[data-mobile-nav-panel]");
4+
var lastFocused = null;
5+
var focusableSelectors = [
6+
"a[href]",
7+
"button:not([disabled])",
8+
"textarea:not([disabled])",
9+
"input:not([type=\"hidden\"]):not([disabled])",
10+
"select:not([disabled])",
11+
"[tabindex]:not([tabindex=\"-1\"])",
12+
];
13+
14+
if (!toggle || !panel) {
15+
return;
16+
}
17+
18+
function getFocusableElements() {
19+
return Array.prototype.slice.call(
20+
panel.querySelectorAll(focusableSelectors.join(","))
21+
).filter(function (el) {
22+
return !el.hasAttribute("disabled") && el.offsetParent !== null;
23+
});
24+
}
25+
26+
var content = panel.querySelector("[data-mobile-nav-content]");
27+
28+
function setOpen(open) {
29+
toggle.setAttribute("aria-expanded", open ? "true" : "false");
30+
toggle.setAttribute("aria-label", open ? "Sluit navigatie" : "Open navigatie");
31+
panel.setAttribute("aria-hidden", open ? "false" : "true");
32+
panel.classList.toggle("hidden", !open);
33+
34+
if (open) {
35+
lastFocused = document.activeElement;
36+
var focusables = getFocusableElements();
37+
if (focusables.length) {
38+
focusables[0].focus();
39+
}
40+
if (content) {
41+
content.classList.remove("opacity-0", "-translate-y-2");
42+
content.classList.add("opacity-100", "translate-y-0");
43+
}
44+
} else if (lastFocused && typeof lastFocused.focus === "function") {
45+
lastFocused.focus();
46+
lastFocused = null;
47+
if (content) {
48+
content.classList.add("opacity-0", "-translate-y-2");
49+
content.classList.remove("opacity-100", "translate-y-0");
50+
}
51+
}
52+
}
53+
54+
setOpen(false);
55+
56+
toggle.addEventListener("click", function () {
57+
var isOpen = toggle.getAttribute("aria-expanded") === "true";
58+
setOpen(!isOpen);
59+
});
60+
61+
panel.addEventListener("keydown", function (event) {
62+
if (event.key !== "Tab") {
63+
return;
64+
}
65+
66+
var focusables = getFocusableElements();
67+
if (!focusables.length) {
68+
return;
69+
}
70+
71+
var first = focusables[0];
72+
var last = focusables[focusables.length - 1];
73+
var active = document.activeElement;
74+
75+
if (event.shiftKey && active === first) {
76+
event.preventDefault();
77+
last.focus();
78+
} else if (!event.shiftKey && active === last) {
79+
event.preventDefault();
80+
first.focus();
81+
}
82+
});
83+
84+
panel.addEventListener("click", function (event) {
85+
var target = event.target;
86+
if (target && target.tagName && target.tagName.toLowerCase() === "a") {
87+
setOpen(false);
88+
}
89+
});
90+
91+
document.addEventListener("keydown", function (event) {
92+
if (event.key === "Escape") {
93+
setOpen(false);
94+
}
95+
});
96+
})();

themes/custom/layouts/_default/baseof.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44
{{ partial "head.html" . }}
55
{{ block "head" . }}{{ end }}
66
</head>
7-
<body class="bg-slate-50 text-slate-900 antialiased dark:bg-slate-950 dark:text-slate-100" data-search-url="{{ "/zoeken/" | relURL }}"{{ if not (and .IsPage (eq .Section "posts")) }} data-pagefind-ignore{{ end }}>
7+
{{- $isPostPage := and .IsPage (eq .Section "posts") -}}
8+
<body class="bg-slate-50 text-slate-900 antialiased dark:bg-slate-950 dark:text-slate-100" data-search-url="{{ "/zoeken/" | relURL }}"{{ if not $isPostPage }} data-pagefind-ignore{{ end }}>
89
<a class="sr-only focus-visible:not-sr-only focus-visible:absolute focus-visible:left-6 focus-visible:top-4 focus-visible:z-50 focus-visible:rounded focus-visible:bg-white focus-visible:px-3 focus-visible:py-2 focus-visible:text-sm focus-visible:font-semibold focus-visible:text-slate-900 focus-visible:shadow focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-slate-900 dark:focus-visible:bg-slate-900 dark:focus-visible:text-slate-100 dark:focus-visible:outline-slate-100" href="#main">Ga naar inhoud</a>
910
{{ partial "header.html" . }}
10-
<main id="main" class="mx-auto max-w-3xl px-6 py-8"{{ if and .IsPage (eq .Section "posts") }} data-pagefind-body{{ end }}>
11+
<main id="main" class="mx-auto max-w-3xl px-6 py-8"{{ if $isPostPage }} data-pagefind-body{{ end }}>
1112
{{ block "main" . }}{{ end }}
1213
</main>
1314
{{ partial "footer.html" . }}
1415
{{ partial "cookie-banner.html" . }}
1516
{{ partial "cookie-preferences.html" . }}
1617
<script src="/js/cookie-consent.js" defer></script>
1718
<script src="/js/search-shortcut.js" defer></script>
19+
<script src="/js/mobile-nav.js" defer></script>
1820
</body>
1921
</html>

themes/custom/layouts/_default/search.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{{ define "main" }}
2-
<section class="space-y-6">
2+
<section class="space-y-6" data-pagefind-ignore>
33
<header class="space-y-2">
44
<h1 class="text-2xl font-semibold text-slate-900 dark:text-slate-100">{{ .Title }}</h1>
55
<p class="text-sm text-slate-600 dark:text-slate-300">Zoek in artikelen, tags en pagina's. Resultaten verschijnen zodra je typt.</p>

themes/custom/layouts/partials/header.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,20 @@
1717
</a>
1818
<div class="ml-auto flex items-center gap-3 md:gap-4">
1919
{{ partial "nav.html" . }}
20+
<button
21+
class="inline-flex items-center justify-center rounded-full border border-slate-200 bg-white p-2 text-slate-700 transition hover:border-slate-300 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-moss/70 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 motion-reduce:transition-none dark:border-slate-800 dark:bg-slate-900 dark:text-slate-200 dark:hover:border-slate-700 dark:hover:text-white dark:focus-visible:ring-moss/70 dark:focus-visible:ring-offset-slate-950 md:hidden"
22+
type="button"
23+
aria-expanded="false"
24+
aria-controls="mobile-nav"
25+
aria-label="Open navigatie"
26+
data-mobile-nav-toggle
27+
>
28+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
29+
<path d="M4 7h16" />
30+
<path d="M4 12h16" />
31+
<path d="M4 17h16" />
32+
</svg>
33+
</button>
2034
<button
2135
class="inline-flex items-center justify-center rounded-full border border-slate-200 bg-white p-2 text-slate-700 transition hover:border-slate-300 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-moss/70 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 motion-reduce:transition-none dark:border-slate-800 dark:bg-slate-900 dark:text-slate-200 dark:hover:border-slate-700 dark:hover:text-white dark:focus-visible:ring-moss/70 dark:focus-visible:ring-offset-slate-950"
2236
type="button"
@@ -39,4 +53,9 @@
3953
</button>
4054
</div>
4155
</div>
56+
<div id="mobile-nav" class="mt-4 hidden md:hidden" data-mobile-nav-panel aria-hidden="true">
57+
<nav aria-label="Hoofdnavigatie mobiel" class="rounded-2xl border border-slate-200 bg-white p-4 shadow-sm dark:border-slate-800 dark:bg-slate-900 transition-transform transition-opacity duration-200 ease-out motion-reduce:transition-none opacity-0 -translate-y-2" data-mobile-nav-content>
58+
{{ partial "nav-links.html" (dict "page" . "class" "flex flex-col gap-2 text-sm font-semibold") }}
59+
</nav>
60+
</div>
4261
</header>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{{- $page := .page -}}
2+
{{- $isHome := $page.IsHome -}}
3+
{{- $isTags := eq $page.RelPermalink "/tags/" -}}
4+
{{- $isAbout := eq $page.RelPermalink "/over-mij/" -}}
5+
{{- $isPrivacy := eq $page.RelPermalink "/privacy/" -}}
6+
<ul class="{{ .class }}">
7+
<li>
8+
<a class="rounded px-2 py-1 text-slate-700 hover:text-moss hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-moss dark:text-slate-300 dark:hover:text-mustard dark:focus-visible:outline-moss {{ if $isHome }}text-moss dark:text-mustard{{ end }}" href="/"{{ if $isHome }} aria-current="page"{{ end }}>Start</a>
9+
</li>
10+
<li>
11+
<a class="rounded px-2 py-1 text-slate-700 hover:text-moss hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-moss dark:text-slate-300 dark:hover:text-mustard dark:focus-visible:outline-moss {{ if $isTags }}text-moss dark:text-mustard{{ end }}" href="/tags/"{{ if $isTags }} aria-current="page"{{ end }}>Tags</a>
12+
</li>
13+
<li>
14+
<a class="rounded px-2 py-1 text-slate-700 hover:text-moss hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-moss dark:text-slate-300 dark:hover:text-mustard dark:focus-visible:outline-moss {{ if $isAbout }}text-moss dark:text-mustard{{ end }}" href="/over-mij/"{{ if $isAbout }} aria-current="page"{{ end }}>Over mij</a>
15+
</li>
16+
<li>
17+
<a class="rounded px-2 py-1 text-slate-700 hover:text-moss hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-moss dark:text-slate-300 dark:hover:text-mustard dark:focus-visible:outline-moss {{ if $isPrivacy }}text-moss dark:text-mustard{{ end }}" href="/privacy/"{{ if $isPrivacy }} aria-current="page"{{ end }}>Privacy</a>
18+
</li>
19+
</ul>
Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,3 @@
1-
{{- $isHome := .IsHome -}}
2-
{{- $isTags := eq .RelPermalink "/tags/" -}}
3-
{{- $isAbout := eq .RelPermalink "/over-mij/" -}}
4-
{{- $isPrivacy := eq .RelPermalink "/privacy/" -}}
51
<nav aria-label="Hoofdnavigatie" class="hidden items-center md:flex">
6-
<ul class="flex items-center gap-3 text-sm font-semibold">
7-
<li>
8-
<a class="rounded px-2 py-1 text-slate-700 hover:text-moss hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-moss dark:text-slate-300 dark:hover:text-mustard dark:focus-visible:outline-moss {{ if $isHome }}text-moss dark:text-mustard{{ end }}" href="/"{{ if $isHome }} aria-current="page"{{ end }}>Start</a>
9-
</li>
10-
<li>
11-
<a class="rounded px-2 py-1 text-slate-700 hover:text-moss hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-moss dark:text-slate-300 dark:hover:text-mustard dark:focus-visible:outline-moss {{ if $isTags }}text-moss dark:text-mustard{{ end }}" href="/tags/"{{ if $isTags }} aria-current="page"{{ end }}>Tags</a>
12-
</li>
13-
<li>
14-
<a class="rounded px-2 py-1 text-slate-700 hover:text-moss hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-moss dark:text-slate-300 dark:hover:text-mustard dark:focus-visible:outline-moss {{ if $isAbout }}text-moss dark:text-mustard{{ end }}" href="/over-mij/"{{ if $isAbout }} aria-current="page"{{ end }}>Over mij</a>
15-
</li>
16-
<li>
17-
<a class="rounded px-2 py-1 text-slate-700 hover:text-moss hover:underline focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-moss dark:text-slate-300 dark:hover:text-mustard dark:focus-visible:outline-moss {{ if $isPrivacy }}text-moss dark:text-mustard{{ end }}" href="/privacy/"{{ if $isPrivacy }} aria-current="page"{{ end }}>Privacy</a>
18-
</li>
19-
</ul>
2+
{{ partial "nav-links.html" (dict "page" . "class" "flex items-center gap-3 text-sm font-semibold") }}
203
</nav>

0 commit comments

Comments
 (0)