refactor: enhance dropdown functionality for improved mobile responsiveness and accessibility

This commit is contained in:
fynks 2025-10-12 16:57:49 +05:00
parent 3defd1b68c
commit 92bb628a2b
2 changed files with 54 additions and 14 deletions

11
dist/css/styles.css vendored
View file

@ -865,6 +865,9 @@ button:focus-visible {
.nav-dropdown { .nav-dropdown {
position: relative; position: relative;
display: inline-block; display: inline-block;
/* Improve touch responsiveness */
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
} }
.nav-dropdown-trigger { .nav-dropdown-trigger {
@ -874,6 +877,11 @@ button:focus-visible {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: var(--space-xs); gap: var(--space-xs);
/* Improve touch responsiveness */
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
-webkit-user-select: none;
user-select: none;
} }
.dropdown-icon { .dropdown-icon {
@ -980,6 +988,9 @@ button:focus-visible {
max-height: 0; max-height: 0;
overflow: hidden; overflow: hidden;
transition: max-height 0.3s ease; transition: max-height 0.3s ease;
/* Prevent layout shift and improve performance */
will-change: max-height;
contain: layout;
} }
[data-theme="dark"] .nav-dropdown-menu { [data-theme="dark"] .nav-dropdown-menu {

57
dist/index.html vendored
View file

@ -1746,9 +1746,22 @@
// Navigation dropdowns // Navigation dropdowns
const dropdownTriggers = document.querySelectorAll('.nav-dropdown-trigger'); const dropdownTriggers = document.querySelectorAll('.nav-dropdown-trigger');
// Function to close all dropdowns
function closeAllDropdowns() {
dropdownTriggers.forEach(trigger => {
trigger.setAttribute('aria-expanded', 'false');
});
}
dropdownTriggers.forEach(trigger => { dropdownTriggers.forEach(trigger => {
trigger.addEventListener('click', (e) => { trigger.addEventListener('click', (e) => {
e.stopPropagation(); e.preventDefault();
// On mobile, only allow dropdowns when nav menu is open
if (window.innerWidth <= 900 && !bodyEl.classList.contains('nav-open')) {
return;
}
const isExpanded = trigger.getAttribute('aria-expanded') === 'true'; const isExpanded = trigger.getAttribute('aria-expanded') === 'true';
// Close all other dropdowns // Close all other dropdowns
@ -1759,25 +1772,25 @@
}); });
// Toggle current dropdown // Toggle current dropdown
trigger.setAttribute('aria-expanded', !isExpanded); trigger.setAttribute('aria-expanded', String(!isExpanded));
}); }, { passive: false });
}); });
// Close dropdowns when clicking outside // Close dropdowns when clicking outside (only on desktop)
document.addEventListener('click', () => { document.addEventListener('click', (e) => {
dropdownTriggers.forEach(trigger => { // Don't close if clicking on a dropdown trigger or menu
trigger.setAttribute('aria-expanded', 'false'); const isDropdownClick = e.target.closest('.nav-dropdown');
}); if (!isDropdownClick && window.innerWidth > 900) {
}); closeAllDropdowns();
}
}, { passive: true });
// Close dropdowns on escape key // Close dropdowns on escape key
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') { if (e.key === 'Escape') {
dropdownTriggers.forEach(trigger => { closeAllDropdowns();
trigger.setAttribute('aria-expanded', 'false');
});
} }
}); }, { passive: true });
// Mark JS ready to enable transitions after initial paint (prevents slide-in glitch) // Mark JS ready to enable transitions after initial paint (prevents slide-in glitch)
requestAnimationFrame(() => bodyEl.classList.add('js-ready')); requestAnimationFrame(() => bodyEl.classList.add('js-ready'));
@ -1804,6 +1817,8 @@
nav.setAttribute('aria-hidden', bodyEl.classList.contains('nav-open') ? 'false' : 'true'); nav.setAttribute('aria-hidden', bodyEl.classList.contains('nav-open') ? 'false' : 'true');
} else { } else {
nav.removeAttribute('aria-hidden'); nav.removeAttribute('aria-hidden');
// Restore scroll when switching to desktop
document.body.style.overflow = '';
if (bodyEl.classList.contains('nav-open')) { if (bodyEl.classList.contains('nav-open')) {
closeMenu({ restoreFocus: false }); closeMenu({ restoreFocus: false });
} else { } else {
@ -1821,6 +1836,12 @@
navToggle?.setAttribute('aria-expanded', 'true'); navToggle?.setAttribute('aria-expanded', 'true');
if (navOverlay) navOverlay.hidden = false; if (navOverlay) navOverlay.hidden = false;
nav.setAttribute('data-open', 'true'); nav.setAttribute('data-open', 'true');
// Prevent scroll on mobile when menu is open
if (window.innerWidth <= 900) {
document.body.style.overflow = 'hidden';
}
const firstLink = nav.querySelector('a'); const firstLink = nav.querySelector('a');
firstLink && firstLink.focus(); firstLink && firstLink.focus();
trapFocus(nav); trapFocus(nav);
@ -1833,6 +1854,13 @@
navToggle?.setAttribute('aria-expanded', 'false'); navToggle?.setAttribute('aria-expanded', 'false');
if (navOverlay) navOverlay.hidden = true; if (navOverlay) navOverlay.hidden = true;
nav.removeAttribute('data-open'); nav.removeAttribute('data-open');
// Restore scroll
document.body.style.overflow = '';
// Close all dropdowns when closing menu
closeAllDropdowns();
if (restoreFocus && lastFocusedBeforeMenu) { if (restoreFocus && lastFocusedBeforeMenu) {
lastFocusedBeforeMenu.focus(); lastFocusedBeforeMenu.focus();
} }
@ -1848,7 +1876,8 @@
} }
if (nav) { if (nav) {
nav.addEventListener('click', (event) => { nav.addEventListener('click', (event) => {
const target = event.target instanceof Element ? event.target.closest('.nav-link') : null; // Only close menu when clicking actual links, not dropdown triggers
const target = event.target instanceof Element ? event.target.closest('a.nav-link, a.nav-dropdown-item') : null;
if (!target) return; if (!target) return;
if (window.innerWidth <= 900) closeMenu(); if (window.innerWidth <= 900) closeMenu();
}); });