From 5f002f623a768d8aa5284b92e4242b7bceff20d9 Mon Sep 17 00:00:00 2001 From: Frank Loesche Date: Sun, 17 May 2026 00:17:26 -0400 Subject: [PATCH 1/2] hamburger update, remove whitespace from connectivity tables --- static/css/neuron-page.css | 205 +++++++++++++----- static/js/neuron-page.js | 86 +++++--- templates/base.html.jinja | 2 +- templates/sections/hamburger_menu.html.jinja | 27 ++- .../sections/neuron_page_scripts.html.jinja | 2 + templates/static/js/neuron-search.js.jinja | 38 +++- 6 files changed, 272 insertions(+), 88 deletions(-) diff --git a/static/css/neuron-page.css b/static/css/neuron-page.css index c9cc547c..54d63286 100644 --- a/static/css/neuron-page.css +++ b/static/css/neuron-page.css @@ -180,19 +180,91 @@ } } -/* Small screen styles - show hamburger button */ +/* Backdrop overlay for small-screen drawer. Hidden by default; shown only + when the drawer is open. Defined outside any media query so the JS class + toggle works the same way at all sizes — but on large screens it's never + visible since the drawer isn't a drawer. */ +.hamburger-backdrop { + display: none; + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.4); + z-index: 1000; + opacity: 0; + transition: opacity 0.25s ease; +} +.hamburger-backdrop.open { + display: block; + opacity: 1; +} + +/* Section structure inside the drawer / dropdown */ +.hamburger-section + .hamburger-section { + border-top: 1px solid #e9ecef; + margin-top: 8px; + padding-top: 8px; +} +.hamburger-section-title { + font-weight: 600; + font-size: 0.75rem; + color: #888; + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 4px 20px 8px; +} +.hamburger-link-row { + display: flex; + align-items: center; + gap: 12px; + padding: 8px 20px; + flex-wrap: wrap; +} +.hamburger-link-row .icon-link img { + display: block; +} +.hamburger-search { + padding: 4px 16px 8px; +} +.hamburger-search input { + width: 100%; + padding: 6px 8px; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 0.85rem; +} + +/* Wide screens: site-nav section duplicates the top header-bar, so hide it. */ +@media (min-width: 1700px) { + .hamburger-section-site { + display: none; + } +} + +/* Small screen styles - hamburger button + slide-in drawer */ @media (max-width: 1699px) { /* Hide all other header elements */ .header-flex { display: none; } + /* On neuron-type pages collapse the top header-bar to brand-only: + its other columns (Home/Types/Help, search, social) are now inside + the drawer. Index/help pages still need their inline nav, so we + scope this to .is-neuron-page. */ + body.is-neuron-page .neuron-header-bar > .row > div:nth-child(1), + body.is-neuron-page .neuron-header-bar > .row > div:nth-child(3) { + display: none; + } + body.is-neuron-page .neuron-header-bar > .row { + justify-content: center; + } + .hamburger-menu { position: fixed; top: 10px; - left: 30px; + left: 10px; width: auto; - z-index: 1001; + z-index: 1002; background: transparent; border: none; box-shadow: none; @@ -211,69 +283,46 @@ backdrop-filter: blur(10px); } - .hamburger-menu:hover .hamburger-btn, - .hamburger-menu.menu-open .hamburger-btn { - display: block; - border-bottom: none; - border-bottom-left-radius: 0; - border-bottom-right-radius: 0; - } - + /* Drawer: slides in from the left edge, full viewport height. */ .hamburger-dropdown { - display: none; - position: absolute; - top: 40px; - left: 50%; - transform: translateX(-50%); - background: rgba(255, 255, 255, 0.95); - border: 1px solid #ddd; - border-top: none; - border-radius: 0 0 8px 8px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - backdrop-filter: blur(10px); - padding: 8px 0; - min-width: 250px; - z-index: 1000; - opacity: 0; - visibility: hidden; - transition: - opacity 0.3s ease, - visibility 0.3s ease, - transform 0.3s ease; - transform: translateX(-50%) translateY(-10px); - } - - .hamburger-menu:hover .hamburger-dropdown, - .hamburger-dropdown.open { + position: fixed; + top: 0; + left: 0; + width: 280px; + max-width: 85vw; + height: 100vh; + background: #fff; + border: none; + border-right: 1px solid #ddd; + border-radius: 0; + box-shadow: 2px 0 12px rgba(0, 0, 0, 0.15); + padding: 56px 0 16px; /* leave room above for the hamburger button */ + overflow-y: auto; + z-index: 1001; + transform: translateX(-100%); + transition: transform 0.25s ease; display: block; opacity: 1; visibility: visible; - left: 0px; - transform: translateX(0) translateY(0); + } + .hamburger-dropdown.open { + transform: translateX(0); } - .hamburger-menu:hover, - .hamburger-menu.menu-open { - background: rgba(255, 255, 255, 0.95); - border: 1px solid #ddd; - border-radius: 8px; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - backdrop-filter: blur(10px); + /* When drawer is open, lift the hamburger button above it and shift it + so it appears inside the drawer area as a close affordance. */ + .hamburger-menu.menu-open .hamburger-btn { + background: #fff; } - /* Ensure dropdown is hidden by default on small screens */ + /* Override the static "display: none" rule from the base block so the + fixed positioning above takes effect. */ #hamburgerDropdown { - display: none; - opacity: 0; - visibility: hidden; + display: block; } - /* Show dropdown when open class is applied or on hover */ - .hamburger-menu:hover #hamburgerDropdown, - #hamburgerDropdown.open { - display: block; - opacity: 1; - visibility: visible; + .hamburger-section-title { + padding-left: 24px; } } @@ -1386,13 +1435,17 @@ abbr[data-original-title]:hover { max-width: 110px; } -/* Adjust specific columns for better fit */ +/* Below the md breakpoint each table is full-width and behaves fine with an + auto-sized first column. Above md, the tables sit side-by-side and the + first column was inflating because cols 2-6 only request 75% (5 * 15%) of + the table width, leaving the remaining 25% to col 1. Scoped fix at the + bottom of this file switches the connectivity tables to table-layout:fixed + with explicit column widths summing to 100% only on wide screens. */ #upstream-table th:first-child, #downstream-table th:first-child, #upstream-table td:first-child, #downstream-table td:first-child { - width: 40%; - min-width: 200px; + min-width: 5em; } #upstream-table th:nth-child(2), @@ -1455,6 +1508,30 @@ abbr[data-original-title]:hover { .row .col-xs-12.col-md-6:last-child { padding-left: 1rem; } + + /* Connectivity tables: use auto-layout so the partner-name column + sizes itself to the widest cell (across header *and* every body row), + which is what we need to "always show full names". With fixed-layout + the column only sees the first row (the th) and longer body names + overflow into the next column. */ + #upstream-table, + #downstream-table { + table-layout: auto !important; + } + #upstream-table th:first-child, + #downstream-table th:first-child, + #upstream-table td:first-child, + #downstream-table td:first-child { + /* Override the table-wide max-width:110px and overflow:hidden so the + partner-name column can grow to fit the longest entry without + ellipsis-truncating it. width:auto + white-space:nowrap lets the + browser take the column's max-content as the column width. */ + width: auto !important; + max-width: none !important; + overflow: visible !important; + text-overflow: clip !important; + white-space: nowrap !important; + } } /* Compact search field styling for all tables with sliders */ @@ -2795,7 +2872,8 @@ abbr[data-original-title]:hover { display: flex; align-items: center; gap: 8px; - width: 100%; + width: auto; + max-width: 18em; } .p-c-label { @@ -3071,6 +3149,15 @@ table.dataTable#roi-table tbody > tr > td:first-child { display: block; } +/* Floating variant used by the sidebar-drawer search instance: appended to + and positioned with viewport coordinates so the drawer's + overflow-y:auto can't clip the suggestion list. JS sets top/left/width. */ +.neuron-search-dropdown.neuron-search-dropdown-floating { + position: fixed; + right: auto; + z-index: 1100; +} + .neuron-search-no-results { padding: 8px 12px; color: #666; diff --git a/static/js/neuron-page.js b/static/js/neuron-page.js index 57dbf87e..ce07e0b1 100644 --- a/static/js/neuron-page.js +++ b/static/js/neuron-page.js @@ -403,43 +403,75 @@ function initializeResponsiveNavigation() { var btn = document.getElementById("hamburgerBtn"); var dropdown = document.getElementById("hamburgerDropdown"); var menu = document.getElementById("hamburgerMenu"); + var backdrop = document.getElementById("hamburgerBackdrop"); if (!dropdown || !menu) return; - // Handle button click and hover (only relevant on small screens) + // Drawer mode is only active under this breakpoint (matches the CSS). + var DRAWER_BREAKPOINT = 1700; + function isDrawerMode() { + return window.innerWidth < DRAWER_BREAKPOINT; + } + + function openDrawer() { + dropdown.classList.add("open"); + menu.classList.add("menu-open"); + if (backdrop) backdrop.classList.add("open"); + if (btn) btn.setAttribute("aria-expanded", "true"); + if (isDrawerMode()) document.body.style.overflow = "hidden"; + } + function closeDrawer() { + dropdown.classList.remove("open"); + menu.classList.remove("menu-open"); + if (backdrop) backdrop.classList.remove("open"); + if (btn) btn.setAttribute("aria-expanded", "false"); + document.body.style.overflow = ""; + } + function toggleDrawer() { + if (dropdown.classList.contains("open")) closeDrawer(); + else openDrawer(); + } + + // Button click toggles the drawer. if (btn) { btn.addEventListener("click", function (e) { e.stopPropagation(); - dropdown.classList.toggle("open"); - menu.classList.toggle("menu-open"); + toggleDrawer(); }); - // Handle hover to open menu on small screens - btn.addEventListener("mouseenter", function (e) { - dropdown.classList.add("open"); - menu.classList.add("menu-open"); + // On large screens (no drawer), the menu sits open inline — hover-open + // is a nicety for the floating widget but irrelevant once the drawer + // owns the small-screen experience. Gate hover behaviors accordingly. + btn.addEventListener("mouseenter", function () { + if (!isDrawerMode()) openDrawer(); }); } - // Handle hover over entire menu area if (menu) { - menu.addEventListener("mouseenter", function (e) { - dropdown.classList.add("open"); - menu.classList.add("menu-open"); + menu.addEventListener("mouseenter", function () { + if (!isDrawerMode()) openDrawer(); }); - - // Close menu when mouse leaves the entire menu area - menu.addEventListener("mouseleave", function (e) { - dropdown.classList.remove("open"); - menu.classList.remove("menu-open"); + menu.addEventListener("mouseleave", function () { + if (!isDrawerMode()) closeDrawer(); }); } - // Close when clicking outside (only on small screens) + // Backdrop click closes the drawer (small screens only). + if (backdrop) { + backdrop.addEventListener("click", closeDrawer); + } + + // ESC closes the drawer. + document.addEventListener("keydown", function (e) { + if (e.key === "Escape" && dropdown.classList.contains("open")) { + closeDrawer(); + } + }); + + // Click outside the menu (large screens) — keep the legacy behavior. document.body.addEventListener("click", function (e) { - if (!menu.contains(e.target)) { - dropdown.classList.remove("open"); - menu.classList.remove("menu-open"); + if (!menu.contains(e.target) && !isDrawerMode()) { + closeDrawer(); } }); @@ -449,13 +481,17 @@ function initializeResponsiveNavigation() { menuLinks.forEach(function (link) { link.addEventListener("click", function (e) { - // Close menu on small screens - dropdown.classList.remove("open"); - menu.classList.remove("menu-open"); + // Always close menu on click — applies to both in-page anchors and + // site-nav links. + closeDrawer(); + + // Only intercept same-page anchor links for smooth scrolling; let + // cross-page links (Home/Types/Help) navigate normally. + var href = this.getAttribute("href") || ""; + if (!href.startsWith("#")) return; - // Smooth scroll to target e.preventDefault(); - var targetId = this.getAttribute("href").substring(1); // Remove # + var targetId = href.substring(1); var target = document.getElementById(targetId); if (target) { target.scrollIntoView({ diff --git a/templates/base.html.jinja b/templates/base.html.jinja index 8cd5126d..6aa406f3 100644 --- a/templates/base.html.jinja +++ b/templates/base.html.jinja @@ -25,7 +25,7 @@ {% endif %} - + {% block header %}{% endblock %}
diff --git a/templates/sections/hamburger_menu.html.jinja b/templates/sections/hamburger_menu.html.jinja index 6d80f1e5..dcbb4545 100644 --- a/templates/sections/hamburger_menu.html.jinja +++ b/templates/sections/hamburger_menu.html.jinja @@ -58,11 +58,15 @@ JavaScript: {% set _ = section_links.append(('s-c', 'Connectivity')) %} {% endif %} +
-
diff --git a/templates/sections/neuron_page_scripts.html.jinja b/templates/sections/neuron_page_scripts.html.jinja index 0cd4808a..556b2648 100644 --- a/templates/sections/neuron_page_scripts.html.jinja +++ b/templates/sections/neuron_page_scripts.html.jinja @@ -120,6 +120,7 @@ $(document).ready(function() { "pageLength": -1, {#- Show all rows -#} "paging": false, {#- Disable pagination since we're using connections filter -#} "responsive": true, {#- Disable responsive layout -#} + "autoWidth": false, {#- Let CSS drive column widths instead of DataTables' content-measuring auto-width -#} "buttons": [{ extend: 'csv', text: '', @@ -178,6 +179,7 @@ $(document).ready(function() { "pageLength": -1, {#- Show all rows -#} "paging": false, {#- Disable pagination since we're using connections filter -#} "responsive": true, {#- Enable responsive layout -#} + "autoWidth": false, {#- Let CSS drive column widths instead of DataTables' content-measuring auto-width -#} "buttons": [{ extend: 'csv', text: '', diff --git a/templates/static/js/neuron-search.js.jinja b/templates/static/js/neuron-search.js.jinja index c00cf7bc..3dfba3f1 100644 --- a/templates/static/js/neuron-search.js.jinja +++ b/templates/static/js/neuron-search.js.jinja @@ -13,6 +13,11 @@ class NeuronSearch { constructor(inputId = "menulines", options = {}) { this.inputElement = document.getElementById(inputId); this.minQueryLength = options.minQueryLength ?? 1; + // floatingDropdown: re-parent the suggestion list to and position + // it with fixed coordinates derived from the input's bounding rect. Used + // by the sidebar-drawer copy on small screens, where the drawer's + // overflow-y:auto would otherwise clip an absolutely-positioned dropdown. + this.floatingDropdown = options.floatingDropdown ?? false; this.neuronTypes = []; this.neuronData = []; this.filteredTypes = []; @@ -27,6 +32,14 @@ class NeuronSearch { this.urlPrefix = this.isNeuronPage ? '' : 'types/'; this.dropdown = this.createDropdown(); + + if (this.floatingDropdown) { + this._positionDropdown = this._positionDropdown.bind(this); + // Capture-phase scroll listener catches scrolls on every ancestor + // (scroll events don't bubble) — needed when the drawer itself scrolls. + window.addEventListener("scroll", this._positionDropdown, { passive: true, capture: true }); + window.addEventListener("resize", this._positionDropdown, { passive: true }); + } } /** @@ -160,12 +173,25 @@ class NeuronSearch { const dropdown = document.createElement("div"); dropdown.className = "neuron-search-dropdown"; - const parent = this.inputElement.parentNode; - parent.appendChild(dropdown); + if (this.floatingDropdown) { + // Re-parent to so an overflow-clipping ancestor can't crop it. + dropdown.classList.add("neuron-search-dropdown-floating"); + document.body.appendChild(dropdown); + } else { + this.inputElement.parentNode.appendChild(dropdown); + } return dropdown; } + _positionDropdown() { + if (!this.floatingDropdown || !this.isDropdownVisible) return; + const rect = this.inputElement.getBoundingClientRect(); + this.dropdown.style.top = `${rect.bottom}px`; + this.dropdown.style.left = `${rect.left}px`; + this.dropdown.style.width = `${rect.width}px`; + } + setupEventListeners() { this.inputElement.addEventListener("input", (e) => { this.handleInput(e.target.value); @@ -521,6 +547,7 @@ class NeuronSearch { ) { this.dropdown.classList.add('is-open'); this.isDropdownVisible = true; + this._positionDropdown && this._positionDropdown(); } } @@ -546,6 +573,13 @@ document.addEventListener("DOMContentLoaded", async () => { window.neuronSearch = new NeuronSearch('menulines'); inits.push(window.neuronSearch.init()); } + if (document.getElementById('menulines-drawer')) { + // Sidebar-drawer copy of the header search. floatingDropdown re-parents + // the suggestion list to so the drawer's overflow-y:auto can't + // clip it. + window.neuronSearchDrawer = new NeuronSearch('menulines-drawer', { floatingDropdown: true }); + inits.push(window.neuronSearchDrawer.init()); + } if (document.getElementById('landing-search')) { window.landingSearch = new NeuronSearch('landing-search', { minQueryLength: 2 }); inits.push(window.landingSearch.init()); From b025beb3c515e5ada0708b84c1f5077d4eadb651 Mon Sep 17 00:00:00 2001 From: Frank Loesche Date: Sun, 17 May 2026 00:20:26 -0400 Subject: [PATCH 2/2] cards have same size --- static/css/neuron-page.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/static/css/neuron-page.css b/static/css/neuron-page.css index 54d63286..0d6bb61a 100644 --- a/static/css/neuron-page.css +++ b/static/css/neuron-page.css @@ -599,6 +599,11 @@ span.truman-hl { margin-bottom: 1rem; text-align: center; border: 1px solid #e5e7eb; + /* Stretch to fill the row height so cards align with the tallest sibling. + Flexboxgrid's .row is display:flex, so cols already stretch; this just + lets the card inside the col take that full height. */ + height: calc(100% - 1rem); + box-sizing: border-box; } .total-column {