From c4a45ef8fdae397ee9eca0cd6cbbcd48f1c15ad4 Mon Sep 17 00:00:00 2001 From: colin Date: Sun, 6 Jul 2025 11:20:32 -0400 Subject: [PATCH] Fix navigation menu closing too quickly with transition delay and keyboard support --- docker/resume/includes.js | 91 ++++++++++++++++++++++++++++++++++++++- docker/resume/styles.css | 6 +++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/docker/resume/includes.js b/docker/resume/includes.js index 910555e..d47161c 100644 --- a/docker/resume/includes.js +++ b/docker/resume/includes.js @@ -79,10 +79,99 @@ document.addEventListener('DOMContentLoaded', function() { const footerElement = document.getElementById('footer-include'); if (headerElement) { - includeHTML('header-include', '/includes/header.html', setActiveNavItem); + includeHTML('header-include', '/includes/header.html', () => { + setActiveNavItem(); + setupNavDropdowns(); + }); } if (footerElement) { includeHTML('footer-include', '/includes/footer.html'); } + + // Setup dropdown behavior with delay + function setupNavDropdowns() { + const dropdowns = document.querySelectorAll('.main-nav .dropdown'); + let timeoutId; + + dropdowns.forEach(dropdown => { + // Mouse interactions + dropdown.addEventListener('mouseenter', () => { + clearTimeout(timeoutId); + dropdowns.forEach(d => { + if (d !== dropdown) { + d.querySelector('.dropdown-content').style.display = 'none'; + d.querySelector('.dropdown-content').style.opacity = '0'; + d.querySelector('.dropdown-content').style.visibility = 'hidden'; + } + }); + + const dropdownContent = dropdown.querySelector('.dropdown-content'); + dropdownContent.style.display = 'block'; + // Small delay to allow CSS transition to work properly + setTimeout(() => { + dropdownContent.style.opacity = '1'; + dropdownContent.style.visibility = 'visible'; + }, 10); + }); + + dropdown.addEventListener('mouseleave', () => { + const dropdownContent = dropdown.querySelector('.dropdown-content'); + // Add delay before hiding the dropdown + timeoutId = setTimeout(() => { + dropdownContent.style.opacity = '0'; + dropdownContent.style.visibility = 'hidden'; + // Wait for transition to complete before changing display + setTimeout(() => { + if (dropdownContent.style.opacity === '0') { + dropdownContent.style.display = 'none'; + } + }, 200); + }, 300); // 300ms delay before starting to close + }); + + // Keyboard interactions + const dropdownLink = dropdown.querySelector('a'); + dropdownLink.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') { + e.preventDefault(); + const dropdownContent = dropdown.querySelector('.dropdown-content'); + dropdownContent.style.display = 'block'; + setTimeout(() => { + dropdownContent.style.opacity = '1'; + dropdownContent.style.visibility = 'visible'; + + // Focus the first link in the dropdown + const firstLink = dropdownContent.querySelector('a'); + if (firstLink) firstLink.focus(); + }, 10); + } + }); + + // Add keyboard navigation for dropdown items + const dropdownLinks = dropdown.querySelectorAll('.dropdown-content a'); + dropdownLinks.forEach((link, index) => { + link.addEventListener('keydown', (e) => { + if (e.key === 'ArrowDown') { + e.preventDefault(); + const nextLink = dropdownLinks[index + 1] || dropdownLinks[0]; + nextLink.focus(); + } else if (e.key === 'ArrowUp') { + e.preventDefault(); + const prevLink = dropdownLinks[index - 1] || dropdownLinks[dropdownLinks.length - 1]; + prevLink.focus(); + } else if (e.key === 'Escape') { + e.preventDefault(); + dropdown.querySelector('a').focus(); + const dropdownContent = dropdown.querySelector('.dropdown-content'); + dropdownContent.style.opacity = '0'; + dropdownContent.style.visibility = 'hidden'; + setTimeout(() => { + dropdownContent.style.display = 'none'; + }, 200); + } + }); + }); + }); + } }); \ No newline at end of file diff --git a/docker/resume/styles.css b/docker/resume/styles.css index fabdf58..9c288ee 100644 --- a/docker/resume/styles.css +++ b/docker/resume/styles.css @@ -302,11 +302,17 @@ hr { border-radius: 4px; padding: 0.5rem 0; margin-top: 0.5rem; + opacity: 0; + visibility: hidden; + transition: opacity 0.2s ease, visibility 0s linear 0.2s; } .main-nav .dropdown:hover .dropdown-content, .main-nav .dropdown:focus-within .dropdown-content { display: block; + opacity: 1; + visibility: visible; + transition: opacity 0.2s ease, visibility 0s linear 0s; } .main-nav .dropdown-content a {