From 32563adf1bd4ded70bea0cdad88191686f99ddd4 Mon Sep 17 00:00:00 2001 From: TheFlow Date: Mon, 9 Feb 2026 17:55:08 +1300 Subject: [PATCH] =?UTF-8?q?feat:=20Consolidate=20navbar=20=E2=80=94=20Fram?= =?UTF-8?q?ework=20dropdown=20+=20mobile=20accordion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge Architecture link and Implementation dropdown into single "Framework" dropdown on desktop (5 → 4 top-level items). Replace flat mobile drawer sections with collapsible accordion (chevron toggle, auto-expand active section). Remove Company section and Give Feedback button from mobile. Koha promoted to standalone teal link in mobile. Co-Authored-By: Claude Opus 4.6 --- public/js/components/navbar.js | 236 +++++++++++++++++++++------------ 1 file changed, 153 insertions(+), 83 deletions(-) diff --git a/public/js/components/navbar.js b/public/js/components/navbar.js index e57eb4f0..58d62444 100644 --- a/public/js/components/navbar.js +++ b/public/js/components/navbar.js @@ -72,20 +72,20 @@ class TractatusNavbar { - - - Architecture - - - -
+ + @@ -393,37 +411,62 @@ class TractatusNavbar { }); }); - // Feedback button - const navbarFeedbackBtn = document.getElementById('navbar-feedback-btn'); - if (navbarFeedbackBtn) { - navbarFeedbackBtn.addEventListener('click', () => { - if (this.mobileMenuOpen) { - toggleMobileMenu(); + // Mobile accordion sections + const mobileSections = document.querySelectorAll('.mobile-nav-section'); + mobileSections.forEach(section => { + const btn = section.querySelector('.mobile-nav-section-btn'); + const content = section.querySelector('.mobile-nav-section-content'); + const chevron = btn ? btn.querySelector('svg') : null; + if (!btn || !content) return; + + btn.addEventListener('click', () => { + const isCollapsed = content.classList.contains('max-h-0'); + if (isCollapsed) { + content.classList.remove('max-h-0'); + content.classList.add('max-h-96'); + if (chevron) chevron.style.transform = 'rotate(180deg)'; + } else { + content.classList.remove('max-h-96'); + content.classList.add('max-h-0'); + if (chevron) chevron.style.transform = ''; } - window.dispatchEvent(new CustomEvent('openFeedbackModal')); }); - } + }); + + // Auto-expand mobile section containing current page + this.autoExpandActiveSection(); + } + + normalizePath(path) { + if (!path || path.startsWith('http')) return path; + if (path === '/' || path === '/index.html') return '/'; + return path.replace('.html', '').replace(/\/$/, ''); } setActivePageIndicator() { const currentPath = window.location.pathname; + const normalizedCurrent = this.normalizePath(currentPath); - const normalizePath = (path) => { - if (!path || path.startsWith('http')) return path; - if (path === '/' || path === '/index.html') return '/'; - return path.replace('.html', '').replace(/\/$/, ''); - }; - - const normalizedCurrent = normalizePath(currentPath); - - // Desktop nav links + // Desktop nav links — highlight top-level links and dropdown trigger buttons const desktopLinks = document.querySelectorAll('#tractatus-navbar a[href]'); desktopLinks.forEach(link => { const linkPath = link.getAttribute('href'); - const normalizedLink = normalizePath(linkPath); - if (normalizedLink === normalizedCurrent && !link.closest('.nav-dropdown-panel')) { - link.classList.add('text-blue-700', 'bg-blue-50'); - link.classList.remove('text-gray-700', 'text-gray-600'); + const normalizedLink = this.normalizePath(linkPath); + if (normalizedLink === normalizedCurrent) { + // Highlight the link itself if it's a direct top-level link (not inside dropdown panel) + if (!link.closest('.nav-dropdown-panel')) { + link.classList.add('text-blue-700', 'bg-blue-50'); + link.classList.remove('text-gray-700', 'text-gray-600'); + } + // Also highlight the parent dropdown button if this link is inside a dropdown + const dropdownContainer = link.closest('[data-dropdown]'); + if (dropdownContainer) { + const dropdownBtn = dropdownContainer.querySelector('.nav-dropdown-btn'); + if (dropdownBtn) { + dropdownBtn.classList.add('text-blue-700'); + dropdownBtn.classList.remove('text-gray-700'); + } + } } }); @@ -431,7 +474,7 @@ class TractatusNavbar { const mobileLinks = document.querySelectorAll('#mobile-menu a'); mobileLinks.forEach(link => { const linkPath = link.getAttribute('href'); - const normalizedLink = normalizePath(linkPath); + const normalizedLink = this.normalizePath(linkPath); if (normalizedLink === normalizedCurrent) { link.classList.add('border-l-4', 'bg-sky-50', 'text-blue-700', 'font-medium'); link.style.borderLeftColor = 'var(--tractatus-core-end, #3b82f6)'; @@ -439,6 +482,33 @@ class TractatusNavbar { } }); } + + autoExpandActiveSection() { + const currentPath = window.location.pathname; + const normalizedCurrent = this.normalizePath(currentPath); + + const mobileSections = document.querySelectorAll('.mobile-nav-section'); + mobileSections.forEach(section => { + const content = section.querySelector('.mobile-nav-section-content'); + const chevron = section.querySelector('.mobile-nav-section-btn svg'); + if (!content) return; + + const links = content.querySelectorAll('a[href]'); + let hasActive = false; + links.forEach(link => { + const linkPath = link.getAttribute('href'); + if (this.normalizePath(linkPath) === normalizedCurrent) { + hasActive = true; + } + }); + + if (hasActive) { + content.classList.remove('max-h-0'); + content.classList.add('max-h-96'); + if (chevron) chevron.style.transform = 'rotate(180deg)'; + } + }); + } } // Auto-initialize when DOM is ready