fix(mobile): add functional mobile hamburger menu
- Add mobile menu button with hamburger/close icons - Add full-screen mobile navigation overlay - Add JavaScript for menu toggle functionality - Prevent body scroll when menu is open - Close menu on link click, escape key, or outside click - Add proper ARIA attributes for accessibility
This commit is contained in:
@@ -7,10 +7,25 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span data-testid="logo-text">Blog</span>
|
<span data-testid="logo-text">Blog</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
{% include "partials/nav.html" %}
|
{% include "partials/nav.html" %}
|
||||||
|
|
||||||
<div class="header-actions" data-testid="header-actions">
|
<div class="header-actions" data-testid="header-actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="mobile-menu-btn"
|
||||||
|
data-testid="mobile-menu-toggle"
|
||||||
|
aria-label="Toggle menu"
|
||||||
|
aria-expanded="false"
|
||||||
|
aria-controls="mobile-nav"
|
||||||
|
>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="menu-icon-open">
|
||||||
|
<path d="M3 12h18M3 6h18M3 18h18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="menu-icon-close" style="display: none;">
|
||||||
|
<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="theme-toggle"
|
class="theme-toggle"
|
||||||
@@ -170,5 +185,153 @@
|
|||||||
.user-name {
|
.user-name {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mobile-menu-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: var(--color-text);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-btn:hover {
|
||||||
|
background-color: var(--color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-btn[aria-expanded="true"] {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
color: var(--color-primary-contrast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 4rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: var(--color-body);
|
||||||
|
z-index: 99;
|
||||||
|
padding: 2rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav.is-open {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav .nav-link {
|
||||||
|
display: block;
|
||||||
|
padding: 1rem 0;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav .nav-link:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 769px) {
|
||||||
|
.mobile-menu-btn {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-nav {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<!-- Mobile Navigation Menu -->
|
||||||
|
<nav class="mobile-nav" id="mobile-nav" data-testid="mobile-nav" aria-label="Mobile navigation">
|
||||||
|
<a href="/" class="nav-link {% if active_page == 'home' %}active{% endif %}" data-testid="mobile-nav-link-home">
|
||||||
|
Home
|
||||||
|
</a>
|
||||||
|
<a href="/posts" class="nav-link {% if active_page == 'posts' %}active{% endif %}" data-testid="mobile-nav-link-posts">
|
||||||
|
Posts
|
||||||
|
</a>
|
||||||
|
<a href="/about" class="nav-link {% if active_page == 'about' %}active{% endif %}" data-testid="mobile-nav-link-about">
|
||||||
|
About
|
||||||
|
</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const menuBtn = document.querySelector('[data-testid="mobile-menu-toggle"]');
|
||||||
|
const mobileNav = document.getElementById('mobile-nav');
|
||||||
|
const menuIconOpen = menuBtn?.querySelector('.menu-icon-open');
|
||||||
|
const menuIconClose = menuBtn?.querySelector('.menu-icon-close');
|
||||||
|
|
||||||
|
function toggleMenu() {
|
||||||
|
if (!mobileNav || !menuBtn) return;
|
||||||
|
|
||||||
|
const isOpen = mobileNav.classList.toggle('is-open');
|
||||||
|
menuBtn.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
||||||
|
|
||||||
|
if (menuIconOpen && menuIconClose) {
|
||||||
|
menuIconOpen.style.display = isOpen ? 'none' : 'block';
|
||||||
|
menuIconClose.style.display = isOpen ? 'block' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent body scroll when menu is open
|
||||||
|
document.body.style.overflow = isOpen ? 'hidden' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMenu() {
|
||||||
|
if (!mobileNav || !menuBtn) return;
|
||||||
|
|
||||||
|
mobileNav.classList.remove('is-open');
|
||||||
|
menuBtn.setAttribute('aria-expanded', 'false');
|
||||||
|
|
||||||
|
if (menuIconOpen && menuIconClose) {
|
||||||
|
menuIconOpen.style.display = 'block';
|
||||||
|
menuIconClose.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (menuBtn) {
|
||||||
|
menuBtn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
toggleMenu();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close menu when clicking on a link
|
||||||
|
if (mobileNav) {
|
||||||
|
mobileNav.querySelectorAll('a').forEach(function(link) {
|
||||||
|
link.addEventListener('click', closeMenu);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close menu on escape key
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Escape' && mobileNav?.classList.contains('is-open')) {
|
||||||
|
closeMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close menu when clicking outside
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
if (mobileNav?.classList.contains('is-open') &&
|
||||||
|
!mobileNav.contains(e.target) &&
|
||||||
|
!menuBtn?.contains(e.target)) {
|
||||||
|
closeMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user