Phase 3: Mobile hamburger navigation menu

- Added responsive hamburger menu button visible on mobile (sm:hidden)
- Hidden desktop navigation on mobile (hidden sm:block)
- Added mobile menu dropdown with all navigation links
- Added mobile language switcher and theme toggle in menu
- JavaScript toggle functionality for menu open/close
- Auto-close menu when clicking navigation links
- Updated theme toggle to work with both desktop and mobile toggles
- Proper ARIA attributes for accessibility (aria-expanded, aria-label)
This commit is contained in:
ijuanes 2026-06-24 19:46:19 +01:00
parent 06da5bdf2f
commit c7ad302334

View file

@ -291,8 +291,8 @@
<h1 class="text-xl font-bold">{{ _('Tablet Management System') }}</h1>
</div>
<!-- Language Switcher -->
<div class="flex items-center gap-2">
<!-- Desktop Controls: Language + Theme Toggle -->
<div class="hidden sm:flex items-center gap-2">
<span class="text-sm opacity-80">{{ _('Language') }}:</span>
{% for lang_code, lang_name in config['LANGUAGES'].items() %}
<a
@ -310,6 +310,69 @@
<span class="icon-sun" style="display:none;">☀️</span>
</a>
</div>
<!-- Mobile Menu Button -->
<button id="mobile-menu-btn" class="sm:hidden text-white hover:text-cobalt-200 focus:outline-none"
aria-label="Toggle navigation menu" aria-expanded="false">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path id="menu-icon-hamburger" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"></path>
<path id="menu-icon-close" class="hidden" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
</div>
<!-- Mobile Navigation Menu (hidden by default) -->
<div id="mobile-menu" class="sm:hidden hidden">
<div class="px-4 py-3 space-y-2 bg-cobalt-700">
<!-- Mobile Language Switcher -->
<div class="flex items-center gap-2 pb-2 border-b border-cobalt-600 mb-2">
<span class="text-sm opacity-80">{{ _('Language') }}:</span>
{% for lang_code, lang_name in config['LANGUAGES'].items() %}
<a
href="{{ url_for('set_language', lang=lang_code) }}"
class="px-2 py-1 text-sm rounded hover:bg-white/20 transition-colors
{% if language == lang_code %}bg-white/30 font-medium{% endif %}"
>
{{ lang_name }}
</a>
{% endfor %}
<!-- Mobile Theme Toggle -->
<a href="#" id="theme-toggle-mobile" class="text-white hover:text-cobalt-200 ml-2" title="{{ _('Toggle dark mode') }}">
<span class="icon-moon">🌙</span>
<span class="icon-sun" style="display:none;">☀️</span>
</a>
</div>
<!-- Mobile Navigation Links -->
<a href="/" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('Home') }}
</a>
<a href="/add_tablet" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('Add Tablet') }}
</a>
<a href="/add_user" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('Add User') }}
</a>
<a href="/loan_tablet" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('Loan Tablet') }}
</a>
<a href="/history" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('History') }}
</a>
<a href="/user_loans" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('User Loans') }}
</a>
<a href="/students" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('Students') }}
</a>
<a href="/non_loanable_devices" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('Non-Loanable Devices') }}
</a>
<a href="/project_management" class="block px-3 py-2 text-white rounded hover:bg-cobalt-600 transition-colors">
{{ _('Project Management') }}
</a>
</div>
</div>
</header>
@ -346,8 +409,8 @@
{% endif %}
{% endwith %}
<!-- Navigation -->
<nav class="bg-cobalt-600 rounded-lg shadow-md mb-6" aria-label="Main navigation">
<!-- Navigation (Desktop only) -->
<nav class="hidden sm:block bg-cobalt-600 rounded-lg shadow-md mb-6" aria-label="Main navigation">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex flex-wrap justify-center sm:justify-start gap-1 sm:gap-2 py-3">
<a href="/"
@ -404,6 +467,33 @@
<!-- Theme Toggle Script -->
<script>
// Mobile menu toggle
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
const mobileMenu = document.getElementById('mobile-menu');
const menuIconHamburger = document.getElementById('menu-icon-hamburger');
const menuIconClose = document.getElementById('menu-icon-close');
if (mobileMenuBtn && mobileMenu) {
mobileMenuBtn.addEventListener('click', function() {
const isOpen = mobileMenu.classList.toggle('hidden');
mobileMenuBtn.setAttribute('aria-expanded', !isOpen);
// Toggle icons
menuIconHamburger.classList.toggle('hidden');
menuIconClose.classList.toggle('hidden');
});
// Close menu when clicking a link
mobileMenu.querySelectorAll('a').forEach(link => {
link.addEventListener('click', function() {
mobileMenu.classList.add('hidden');
mobileMenuBtn.setAttribute('aria-expanded', false);
menuIconHamburger.classList.remove('hidden');
menuIconClose.classList.add('hidden');
});
});
}
// Check for saved theme preference or default to light mode
const savedTheme = localStorage.getItem('theme');
if (savedTheme === 'dark') {
@ -414,23 +504,36 @@
localStorage.removeItem('theme');
}
document.getElementById('theme-toggle').addEventListener('click', function(e) {
// Theme toggle for desktop
const themeToggle = document.getElementById('theme-toggle');
if (themeToggle) {
themeToggle.addEventListener('click', function(e) {
e.preventDefault();
const isDark = document.body.classList.toggle('dark-mode');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
updateToggleIcon(isDark);
});
}
// Theme toggle for mobile
const themeToggleMobile = document.getElementById('theme-toggle-mobile');
if (themeToggleMobile) {
themeToggleMobile.addEventListener('click', function(e) {
e.preventDefault();
const isDark = document.body.classList.toggle('dark-mode');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
updateToggleIcon(isDark);
});
}
function updateToggleIcon(isDark) {
const iconMoon = document.querySelector('.icon-moon');
const iconSun = document.querySelector('.icon-sun');
if (isDark) {
iconMoon.style.display = 'none';
iconSun.style.display = 'inline';
} else {
iconMoon.style.display = 'inline';
iconSun.style.display = 'none';
}
// Update all theme toggle icons (desktop and mobile)
document.querySelectorAll('.icon-moon').forEach(icon => {
icon.style.display = isDark ? 'none' : 'inline';
});
document.querySelectorAll('.icon-sun').forEach(icon => {
icon.style.display = isDark ? 'inline' : 'none';
});
}
</script>
</body>