feat: Improve dropdown menu handling and visibility for OTS-specific menus

This commit is contained in:
Matt Batchelder
2026-02-07 00:48:41 -05:00
parent edd112fec3
commit 1c5c23f100
5 changed files with 197 additions and 132 deletions

View File

@@ -315,79 +315,63 @@
group.classList.toggle('is-open', !isOpen);
target.setAttribute('aria-expanded', (!isOpen).toString());
submenu.style.display = isOpen ? 'none' : 'block';
}, true);
});
}
/**
* Initialize dropdown menus
*/
function initDropdowns() {
const dropdowns = document.querySelectorAll('.dropdown');
dropdowns.forEach(dropdown => {
const button = dropdown.querySelector('.dropdown-menu');
if (!button) return;
const menu = dropdown.querySelector('.dropdown-menu');
// Toggle menu on button click
dropdown.addEventListener('click', function(e) {
if (e.target.closest('.user-btn') || e.target.closest('[aria-label="User menu"]') || e.target.closest('#navbarUserMenu')) {
e.preventDefault();
const nowActive = !dropdown.classList.contains('active');
dropdown.classList.toggle('active');
// Only handle the user-menu dropdown.
// Let Bootstrap handle topbar nav dropdowns (Schedule, Design, etc.) natively
// so that links like Dayparting navigate normally.
const userDropdown = document.querySelector('#navbarUserMenu') && document.querySelector('#navbarUserMenu').closest('.dropdown');
if (!userDropdown) return;
// If the dropdown has a menu, float it out of any overflowed container
try {
const ddMenu = dropdown.querySelector('.dropdown-menu');
if (ddMenu) {
if (nowActive) {
floatMenu(ddMenu, dropdown);
} else {
unfloatMenu(ddMenu);
}
}
} catch (err) { /* ignore */ }
const userMenu = userDropdown.querySelector('.dropdown-menu');
if (!userMenu) return;
// If this dropdown contains the user menu, compute placement to avoid going off-screen
const menu = dropdown.querySelector('.dropdown-menu.ots-user-menu');
const trigger = dropdown.querySelector('#navbarUserMenu');
if (menu && trigger) {
// Reset any previous placement classes
menu.classList.remove('dropdown-menu-left');
menu.classList.remove('dropdown-menu-right');
userDropdown.addEventListener('click', function(e) {
if (e.target.closest('.user-btn') || e.target.closest('[aria-label="User menu"]') || e.target.closest('#navbarUserMenu')) {
e.preventDefault();
const nowActive = !userDropdown.classList.contains('active');
userDropdown.classList.toggle('active');
// Use getBoundingClientRect for accurate placement
const trigRect = trigger.getBoundingClientRect();
// Ensure menu is in DOM and has an offsetWidth
const menuWidth = menu.offsetWidth || 220; // fallback estimate
// Float / unfloat the user menu
try {
if (nowActive) {
floatMenu(userMenu, userDropdown);
} else {
unfloatMenu(userMenu);
}
} catch (err) { /* ignore */ }
const spaceRight = window.innerWidth - trigRect.right;
const spaceLeft = trigRect.left;
// Prefer opening to the right where possible, otherwise open to the left
if (spaceRight < menuWidth && spaceLeft > menuWidth) {
// not enough space on the right, open to left
menu.classList.add('dropdown-menu-left');
} else {
// default to right-aligned
menu.classList.add('dropdown-menu-right');
}
// Compute placement to avoid going off-screen
const trigger = userDropdown.querySelector('#navbarUserMenu');
if (trigger) {
userMenu.classList.remove('dropdown-menu-left', 'dropdown-menu-right');
const trigRect = trigger.getBoundingClientRect();
const menuWidth = userMenu.offsetWidth || 220;
const spaceRight = window.innerWidth - trigRect.right;
const spaceLeft = trigRect.left;
if (spaceRight < menuWidth && spaceLeft > menuWidth) {
userMenu.classList.add('dropdown-menu-left');
} else {
userMenu.classList.add('dropdown-menu-right');
}
}
});
// Close menu when clicking outside
document.addEventListener('click', function(e) {
if (!dropdown.contains(e.target)) {
const hasActive = dropdown.classList.contains('active');
dropdown.classList.remove('active');
if (hasActive) {
try { const ddMenu = dropdown.querySelector('.dropdown-menu'); if (ddMenu) unfloatMenu(ddMenu); } catch (err) {}
}
}
});
// Close user menu when clicking outside
document.addEventListener('click', function(e) {
if (!userDropdown.contains(e.target) && !userMenu.contains(e.target)) {
const hadActive = userDropdown.classList.contains('active');
userDropdown.classList.remove('active');
if (hadActive) {
try { unfloatMenu(userMenu); } catch (err) {}
}
});
}
});
}