From e25558e4d85e8f78247b7ef0c95daa1a8b6ee3ee Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 8 Mar 2026 12:32:14 -0700 Subject: [PATCH] admin: fix mobile sidebar menu inaccessible in portrait mode (#8556) * admin: fix mobile sidebar menu inaccessible in portrait mode The hamburger button only toggled the user dropdown, leaving the sidebar navigation inaccessible on mobile devices in portrait mode. Add a dedicated sidebar toggle button (visible only on mobile), give the sidebar an id so Bootstrap collapse can target it, add a backdrop overlay for the open state, and auto-close the sidebar when a nav link is clicked. Fixes #8550 * admin: address review feedback on mobile sidebar - Remove redundant JS show/hide.bs.collapse listeners; CSS sibling selector already handles backdrop visibility - Use const instead of var for non-reassigned variables - Move inline style on user icon to CSS class * admin: add aria attributes to user-menu toggler, use CSS variable for navbar height - Add aria-controls, aria-expanded, and aria-label to the user-menu toggle button for assistive technology - Extract hard-coded 56px navbar height into --navbar-height CSS custom property used by sidebar and backdrop positioning * admin: extract hideSidebar helper, use toggler visibility for breakpoint check - Extract duplicated collapse-hide logic into a hideSidebar helper - Replace hardcoded window.innerWidth < 768 with a check on the sidebar toggler's computed display, decoupling JS from CSS breakpoints - Add aria-expanded="false" to sidebar toggle button --------- Co-authored-by: Copilot --- weed/admin/static/css/admin.css | 32 ++++++++++++++++++++++-- weed/admin/static/js/admin.js | 30 +++++++++++++++++++++++ weed/admin/view/layout/layout.templ | 10 ++++++-- weed/admin/view/layout/layout_templ.go | 34 +++++++++++++------------- 4 files changed, 85 insertions(+), 21 deletions(-) diff --git a/weed/admin/static/css/admin.css b/weed/admin/static/css/admin.css index 8f387b1df..cc6b46cfb 100644 --- a/weed/admin/static/css/admin.css +++ b/weed/admin/static/css/admin.css @@ -1,5 +1,9 @@ /* SeaweedFS Dashboard Custom Styles */ +:root { + --navbar-height: 56px; +} + /* Link colors - muted */ a { color: #5b7c99; @@ -12,7 +16,7 @@ a:hover { /* Sidebar Styles */ .sidebar { position: fixed; - top: 56px; + top: var(--navbar-height); bottom: 0; left: 0; z-index: 100; @@ -51,13 +55,32 @@ main { @media (max-width: 767.98px) { .sidebar { - top: 5rem; + top: var(--navbar-height); + padding-top: 0; + width: 240px; + background-color: #f8f9fa !important; + z-index: 1050; + } + .sidebar.show ~ .sidebar-backdrop { + display: block; } main { margin-left: 0; } } +/* Sidebar backdrop for mobile overlay */ +.sidebar-backdrop { + display: none; + position: fixed; + top: var(--navbar-height); + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1040; +} + /* Custom card styles */ .border-left-primary { border-left: 0.25rem solid #6b8caf !important; @@ -262,6 +285,11 @@ main { box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15) !important; } +/* Navbar user icon color */ +.navbar-toggler .fa-user { + color: rgba(255, 255, 255, 0.75); +} + /* Collapsible menu styles */ .nav-link[data-bs-toggle="collapse"] { position: relative; diff --git a/weed/admin/static/js/admin.js b/weed/admin/static/js/admin.js index 27e1646c5..7891645c7 100644 --- a/weed/admin/static/js/admin.js +++ b/weed/admin/static/js/admin.js @@ -31,6 +31,9 @@ function initializeDashboard() { // Set up submenu behavior setupSubmenuBehavior(); + + // Set up mobile sidebar behavior + setupMobileSidebar(); } // HTMX event listeners @@ -194,6 +197,33 @@ function setupSubmenuBehavior() { } +// Mobile sidebar toggle and backdrop behavior +function setupMobileSidebar() { + const sidebar = document.getElementById('sidebarMenu'); + const backdrop = document.getElementById('sidebarBackdrop'); + if (!sidebar || !backdrop) return; + + const hideSidebar = () => { + const bsCollapse = bootstrap.Collapse.getInstance(sidebar); + if (bsCollapse) { + bsCollapse.hide(); + } + }; + + // Close sidebar when backdrop is clicked + backdrop.addEventListener('click', hideSidebar); + + // Close sidebar when a nav link is clicked (on mobile) + const sidebarToggler = document.querySelector("button[data-bs-target='#sidebarMenu']"); + sidebar.querySelectorAll('a.nav-link:not([data-bs-toggle="collapse"])').forEach(function (link) { + link.addEventListener('click', function () { + if (sidebarToggler && getComputedStyle(sidebarToggler).display !== 'none') { + hideSidebar(); + } + }); + }); +} + // Loading indicator functions function showLoadingIndicator() { const indicator = document.getElementById('loading-indicator'); diff --git a/weed/admin/view/layout/layout.templ b/weed/admin/view/layout/layout.templ index 5bf83dc14..6e87b462a 100644 --- a/weed/admin/view/layout/layout.templ +++ b/weed/admin/view/layout/layout.templ @@ -60,9 +60,12 @@ templ Layout(view ViewContext, content templ.Component) { SeaweedFS Admin - +
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -344,7 +344,7 @@ func Layout(view ViewContext, content templ.Component) templ.Component { var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", time.Now().Year())) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 325, Col: 60} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/layout/layout.templ`, Line: 331, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -357,7 +357,7 @@ func Layout(view ViewContext, content templ.Component) templ.Component { var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(version.VERSION_NUMBER) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 325, Col: 102} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/layout/layout.templ`, Line: 331, Col: 102} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -409,7 +409,7 @@ func LoginForm(title string, errorMessage string, csrfToken string) templ.Compon var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 353, Col: 17} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/layout/layout.templ`, Line: 359, Col: 17} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -422,7 +422,7 @@ func LoginForm(title string, errorMessage string, csrfToken string) templ.Compon var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(title) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 367, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/layout/layout.templ`, Line: 373, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -440,7 +440,7 @@ func LoginForm(title string, errorMessage string, csrfToken string) templ.Compon var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(errorMessage) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 374, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/layout/layout.templ`, Line: 380, Col: 45} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { @@ -458,7 +458,7 @@ func LoginForm(title string, errorMessage string, csrfToken string) templ.Compon var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(csrfToken) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 379, Col: 84} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/layout/layout.templ`, Line: 385, Col: 84} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil {