From d4aad5c93eadcb92863ff05937f0ed7183c8995e Mon Sep 17 00:00:00 2001 From: GianRomani Date: Mon, 17 Aug 2026 12:38:40 +0200 Subject: [PATCH 1/3] feat(mobile): implement off-canvas navigation drawer and layout fixes (GIA-51) --- custom-site-assets/sass/custom.scss | 176 ++++++++++++++++++ custom-site-assets/static/js/custom.js | 108 +++++++++++ custom-site-assets/templates/docs/page.html | 1 + .../templates/macros/docs-sidebar.html | 8 +- .../templates/macros/header.html | 7 +- 5 files changed, 297 insertions(+), 3 deletions(-) diff --git a/custom-site-assets/sass/custom.scss b/custom-site-assets/sass/custom.scss index 6c46389..2c53dc0 100644 --- a/custom-site-assets/sass/custom.scss +++ b/custom-site-assets/sass/custom.scss @@ -749,8 +749,183 @@ body.dark .form-control.is-search:focus { background: var(--accent-teal) !important; } +/* Mobile Sidebar Toggle Button */ +.sidebar-toggle-btn { + background: transparent !important; + border: 1px solid var(--border-color) !important; + border-radius: 6px !important; + padding: 0.45rem 0.55rem !important; + margin-right: 0.75rem !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + cursor: pointer !important; + color: var(--text-main) !important; + transition: all 0.2s ease !important; +} + +.sidebar-toggle-btn:hover, +.sidebar-toggle-btn.active { + background-color: var(--bg-hover) !important; + border-color: var(--accent-teal) !important; +} + +.sidebar-toggle-btn .navicon { + position: relative; + display: block; + width: 18px; + height: 2px; + background-color: var(--text-main); + transition: all 0.2s ease-out; +} + +.sidebar-toggle-btn .navicon::before, +.sidebar-toggle-btn .navicon::after { + content: ""; + position: absolute; + left: 0; + width: 18px; + height: 2px; + background-color: var(--text-main); + transition: all 0.2s ease-out; +} + +.sidebar-toggle-btn .navicon::before { + top: -6px; +} + +.sidebar-toggle-btn .navicon::after { + bottom: -6px; +} + +.sidebar-toggle-btn.active .navicon { + background-color: transparent !important; +} + +.sidebar-toggle-btn.active .navicon::before { + transform: rotate(-45deg); + top: 0; +} + +.sidebar-toggle-btn.active .navicon::after { + transform: rotate(45deg); + bottom: 0; +} + +/* Off-Canvas Mobile Navigation Drawer (< 992px) */ +@media (max-width: 991.98px) { + .docs-sidebar { + position: fixed !important; + top: 0 !important; + left: 0 !important; + bottom: 0 !important; + width: min(85vw, 340px) !important; + max-width: 340px !important; + height: 100vh !important; + height: 100dvh !important; + padding: 1.25rem 1.15rem 4rem 1.15rem !important; + background-color: var(--bg-base) !important; + border-right: 1px solid var(--border-color) !important; + z-index: 1100 !important; + overflow-y: auto !important; + -webkit-overflow-scrolling: touch !important; + transform: translateX(-100%) !important; + transition: transform 0.28s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.28s ease !important; + order: unset !important; + display: block !important; + } + + .docs-sidebar.drawer-open { + transform: translateX(0) !important; + box-shadow: 14px 0 36px rgba(0, 0, 0, 0.35) !important; + } + + .sidebar-header { + display: flex !important; + align-items: center !important; + justify-content: space-between !important; + padding-bottom: 0.85rem !important; + margin-bottom: 1.25rem !important; + border-bottom: 1px solid var(--border-color) !important; + } + + .sidebar-title { + font-family: var(--font-ui) !important; + font-size: 1.05rem !important; + font-weight: 700 !important; + color: var(--text-headings) !important; + letter-spacing: -0.01em !important; + } + + .sidebar-close-btn { + background: var(--bg-panel) !important; + border: 1px solid var(--border-color) !important; + border-radius: 6px !important; + color: var(--text-muted) !important; + width: 32px !important; + height: 32px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + cursor: pointer !important; + transition: all 0.2s ease !important; + } + + .sidebar-close-btn:hover { + color: var(--text-headings) !important; + border-color: var(--accent-teal) !important; + background-color: var(--bg-hover) !important; + } + + .sidebar-backdrop { + position: fixed !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + width: 100% !important; + height: 100% !important; + background-color: rgba(0, 0, 0, 0.5) !important; + backdrop-filter: blur(4px) !important; + -webkit-backdrop-filter: blur(4px) !important; + z-index: 1080 !important; + opacity: 0 !important; + pointer-events: none !important; + transition: opacity 0.28s ease !important; + } + + .sidebar-backdrop.active { + opacity: 1 !important; + pointer-events: auto !important; + } + + body.drawer-locked { + overflow: hidden !important; + touch-action: none !important; + } + + .docs-content { + margin-left: 0 !important; + margin-right: 0 !important; + width: 100% !important; + max-width: 100% !important; + padding: 1.5rem 1rem 4rem 1rem !important; + flex: 1 1 100% !important; + } + + .docs-toc { + display: none !important; + } +} + /* Sidebar Fixed Panel Layout on Desktop */ @media (min-width: 992px) { + .sidebar-header, + .sidebar-backdrop, + .sidebar-toggle-btn { + display: none !important; + } + .docs-sidebar { position: fixed !important; top: var(--navbar-height) !important; /* locked below header */ @@ -762,6 +937,7 @@ body.dark .form-control.is-search:focus { border-right: 1px solid var(--border-color) !important; background-color: var(--bg-base) !important; z-index: 900 !important; + transform: none !important; } } diff --git a/custom-site-assets/static/js/custom.js b/custom-site-assets/static/js/custom.js index 0b680c4..3c8f982 100644 --- a/custom-site-assets/static/js/custom.js +++ b/custom-site-assets/static/js/custom.js @@ -24,6 +24,9 @@ document.addEventListener("DOMContentLoaded", () => { // Fix and initialize collapsible sidebar folders initCollapsibleSidebar(); + + // Initialize mobile off-canvas drawer navigation + initMobileDrawer(); }); // Helper to escape HTML characters @@ -317,3 +320,108 @@ function initCollapsibleSidebar() { }); }); } + +// Initialize Mobile Navigation Off-Canvas Drawer +function initMobileDrawer() { + const sidebar = + document.getElementById("docs-sidebar") || + document.querySelector(".docs-sidebar"); + const backdrop = document.getElementById("sidebar-backdrop"); + const toggleBtn = document.getElementById("mobile-sidebar-toggle"); + const closeBtn = document.getElementById("sidebar-close-btn"); + const menuCheckbox = document.getElementById("menu-btn"); + + if (!sidebar) return; + + function openDrawer() { + sidebar.classList.add("drawer-open"); + if (backdrop) backdrop.classList.add("active"); + if (toggleBtn) { + toggleBtn.setAttribute("aria-expanded", "true"); + toggleBtn.classList.add("active"); + } + document.body.classList.add("drawer-locked"); + } + + function closeDrawer() { + sidebar.classList.remove("drawer-open"); + if (backdrop) backdrop.classList.remove("active"); + if (toggleBtn) { + toggleBtn.setAttribute("aria-expanded", "false"); + toggleBtn.classList.remove("active"); + } + document.body.classList.remove("drawer-locked"); + if (menuCheckbox) menuCheckbox.checked = false; + } + + if (toggleBtn) { + toggleBtn.addEventListener("click", (e) => { + e.preventDefault(); + e.stopPropagation(); + if (sidebar.classList.contains("drawer-open")) { + closeDrawer(); + } else { + openDrawer(); + } + }); + } + + // Support clicking fallback menu icon if present + const menuIcon = document.querySelector(".menu-icon"); + if (menuIcon) { + menuIcon.addEventListener("click", (e) => { + e.preventDefault(); + e.stopPropagation(); + if (sidebar.classList.contains("drawer-open")) { + closeDrawer(); + } else { + openDrawer(); + } + }); + } + + if (closeBtn) { + closeBtn.addEventListener("click", (e) => { + e.preventDefault(); + closeDrawer(); + }); + } + + if (backdrop) { + backdrop.addEventListener("click", (e) => { + e.preventDefault(); + closeDrawer(); + }); + } + + // Close drawer when a note link is clicked on mobile + const sidebarLinks = sidebar.querySelectorAll("a.docs-link"); + sidebarLinks.forEach((link) => { + link.addEventListener("click", () => { + if (window.innerWidth < 992) { + closeDrawer(); + } + }); + }); + + // ESC key closes drawer + document.addEventListener("keydown", (e) => { + if (e.key === "Escape" && sidebar.classList.contains("drawer-open")) { + closeDrawer(); + } + }); + + // Close drawer if viewport resizes above desktop threshold + window.addEventListener( + "resize", + () => { + if ( + window.innerWidth >= 992 && + sidebar.classList.contains("drawer-open") + ) { + closeDrawer(); + } + }, + { passive: true }, + ); +} diff --git a/custom-site-assets/templates/docs/page.html b/custom-site-assets/templates/docs/page.html index 2f2d37f..01c73da 100644 --- a/custom-site-assets/templates/docs/page.html +++ b/custom-site-assets/templates/docs/page.html @@ -11,6 +11,7 @@ {% endblock header %} {% block content %} +
diff --git a/custom-site-assets/templates/macros/docs-sidebar.html b/custom-site-assets/templates/macros/docs-sidebar.html index b49a6ad..e13f676 100644 --- a/custom-site-assets/templates/macros/docs-sidebar.html +++ b/custom-site-assets/templates/macros/docs-sidebar.html @@ -1,5 +1,11 @@ {% macro docs_sidebar(current_section) %} -
+
+