Skip to content

Commit d3c2634

Browse files
committed
refactor(search): extract search and mobile menu components from Header
Move search overlay logic to Search.astro component and mobile menu to MobileMenu.astro Delete unused client-side scripts and consolidate search functionality
1 parent 61f22ba commit d3c2634

8 files changed

Lines changed: 271 additions & 422 deletions

File tree

public/js/blogIndexClient.js

Lines changed: 0 additions & 107 deletions
This file was deleted.

src/components/blog/loadMoreButtonClient.js

Lines changed: 0 additions & 23 deletions
This file was deleted.

src/components/navigation/Header.astro

Lines changed: 5 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import type { LanguageKey } from "@/i18n/types";
55
import HeaderLogo from "./HeaderLogo.astro";
66
import HeaderNavLinks from "./HeaderNavLinks.astro";
77
import HeaderUtilities from "./HeaderUtilities.astro";
8+
import MobileMenu from "./MobileMenu.astro";
9+
import SearchToggle from "./SearchToggle.astro";
10+
811
const lang = getLangFromUrl(Astro.url) as LanguageKey;
912
const 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

Comments
 (0)