@@ -5,6 +5,9 @@ import type { LanguageKey } from "@/i18n/types";
55import HeaderLogo from " ./HeaderLogo.astro" ;
66import HeaderNavLinks from " ./HeaderNavLinks.astro" ;
77import HeaderUtilities from " ./HeaderUtilities.astro" ;
8+ import MobileMenu from " ./MobileMenu.astro" ;
9+ import SearchToggle from " ./SearchToggle.astro" ;
10+
811const lang = getLangFromUrl (Astro .url ) as LanguageKey ;
912const currentPath = Astro .url .pathname ;
1013---
@@ -23,151 +26,10 @@ const currentPath = Astro.url.pathname;
2326 />
2427 </div >
2528 <div class =" flex items-center" >
26- <button id =" open-search-overlay" class =" text-gray-600 mr-2 dark:text-gray-300 hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200" aria-label =" Open search" aria-controls =" search-overlay" aria-haspopup =" dialog" >
27- <svg class =" h-5 w-5" fill =" none" stroke =" currentColor" viewBox =" 0 0 24 24" aria-hidden =" true" >
28- <path stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" ></path >
29- </svg >
30- </button >
29+ <SearchToggle client:idle />
3130 <HeaderUtilities />
3231 </div >
3332 </div >
3433 </nav >
3534</header >
36- <div id =" mobile-nav-overlay" class =" mobile-nav-overlay hidden md:hidden" aria-hidden =" true" role =" presentation" ></div >
37- <div
38- id =" mobile-menu"
39- class =" fixed top-0 right-0 h-full w-80 bg-white dark:bg-slate-900 z-50 shadow-lg p-6 hidden md:hidden"
40- aria-modal =" true"
41- aria-label =" Mobile menu"
42- role =" dialog"
43- aria-hidden =" true"
44- >
45- <div class =" flex justify-end mb-4" >
46- <button id =" close-mobile-menu" class =" text-gray-600 dark:text-gray-300 hover:text-primary-600 dark:hover:text-primary-400 transition-colors duration-200" aria-label =" Close mobile menu" >
47- <svg class =" h-6 w-6" fill =" none" stroke =" currentColor" viewBox =" 0 0 24 24" aria-hidden =" true" >
48- <path stroke-linecap =" round" stroke-linejoin =" round" stroke-width =" 2" d =" M6 18L18 6M6 6l12 12" ></path >
49- </svg >
50- </button >
51- </div >
52- <div class =" flex flex-col gap-4" >
53- <HeaderNavLinks
54- lang ={ lang }
55- currentPath ={ currentPath }
56- variant =" mobile"
57- />
58- </div >
59- </div >
60-
61- <script is:inline >
62- // Make toggleSearchOverlay globally accessible
63- function toggleSearchOverlay(show) {
64- const searchOverlay = document.getElementById("search-overlay");
65- if (!searchOverlay) return;
66- if (show) {
67- searchOverlay.classList.remove("hidden");
68- searchOverlay.setAttribute("aria-hidden", "false");
69- const searchInput = searchOverlay.querySelector("#search-input");
70- if (searchInput) {
71- searchInput.focus();
72- }
73- } else {
74- searchOverlay.classList.add("hidden");
75- searchOverlay.setAttribute("aria-hidden", "true");
76- const searchInput = searchOverlay.querySelector("#search-input");
77- if (searchInput) {
78- searchInput.value = ""; // Clear search input on close
79- }
80- const searchResultsContainer = searchOverlay.querySelector("#search-results-container");
81- if (searchResultsContainer) {
82- searchResultsContainer.innerHTML = ""; // Clear results on close
83- }
84- }
85- }
86-
87- document.addEventListener("astro:page-load", () => {
88- const mobileMenuButton = document.getElementById("mobile-menu-button");
89- const mobileMenu = document.getElementById("mobile-menu");
90- const closeMobileMenuButton = document.getElementById("close-mobile-menu");
91- const mobileNavOverlay = document.getElementById("mobile-nav-overlay");
92-
93- const openSearchOverlayButton = document.getElementById("open-search-overlay");
94- const closeSearchOverlayButtonFromOverlay = document.getElementById("close-search-overlay");
95-
96- let lastActiveElement;
97-
98- const openMobileMenu = () => {
99- if (!mobileMenu || !mobileNavOverlay) return;
100- lastActiveElement = document.activeElement;
101- mobileMenu.classList.remove("hidden");
102- mobileNavOverlay.classList.remove("hidden");
103- mobileNavOverlay.setAttribute("aria-hidden", "false");
104- document.body.classList.add("mobile-nav-open");
105- mobileMenu.setAttribute("aria-hidden", "false");
106- mobileMenuButton.setAttribute("aria-expanded", "true");
107-
108- const focusableElements = mobileMenu.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');
109- const firstFocusableElement = focusableElements[0];
110- const lastFocusableElement = focusableElements[focusableElements.length - 1];
111-
112- firstFocusableElement.focus();
113-
114- mobileMenu.addEventListener('keydown', (e) => {
115- if (e.key !== 'Tab') return;
116-
117- if (e.shiftKey) {
118- if (document.activeElement === firstFocusableElement) {
119- lastFocusableElement.focus();
120- e.preventDefault();
121- }
122- } else {
123- if (document.activeElement === lastFocusableElement) {
124- firstFocusableElement.focus();
125- e.preventDefault();
126- }
127- }
128- });
129- };
130-
131- const closeMobileMenu = () => {
132- if (!mobileMenu || !mobileNavOverlay) return;
133- document.body.classList.remove("mobile-nav-open");
134- mobileMenu.classList.add("hidden");
135- mobileNavOverlay.classList.add("hidden");
136- mobileMenu.setAttribute("aria-hidden", "true");
137- mobileNavOverlay.setAttribute("aria-hidden", "true");
138- mobileMenuButton.setAttribute("aria-expanded", "false");
139- if (lastActiveElement) {
140- lastActiveElement.focus();
141- }
142- };
143-
144- if (mobileMenuButton) {
145- mobileMenuButton.addEventListener("click", openMobileMenu);
146- }
147-
148- if (closeMobileMenuButton) {
149- closeMobileMenuButton.addEventListener("click", closeMobileMenu);
150- }
151-
152- if (mobileNavOverlay) {
153- mobileNavOverlay.addEventListener("click", closeMobileMenu);
154- }
155-
156- if (openSearchOverlayButton) {
157- openSearchOverlayButton.addEventListener("click", () => toggleSearchOverlay(true));
158- }
159-
160- if (closeSearchOverlayButtonFromOverlay) {
161- closeSearchOverlayButtonFromOverlay.addEventListener("click", () => toggleSearchOverlay(false));
162- }
163-
164- document.addEventListener("keydown", (event) => {
165- if (event.key === "Escape" && !mobileMenu.classList.contains("hidden")) {
166- closeMobileMenu();
167- }
168- if (event.key === "Escape" && !document.getElementById("search-overlay").classList.contains("hidden")) {
169- toggleSearchOverlay(false);
170- }
171- });
172- });
173- </script >
35+ <MobileMenu client:idle />
0 commit comments