From 5a8e3885a92de6e96d09e31341ae6cfa39f5a1eb Mon Sep 17 00:00:00 2001 From: Graham McIntie Date: Fri, 13 Mar 2026 13:28:19 -0500 Subject: [PATCH] =?UTF-8?q?Refactor=20nav:=20horizontal=20top=20bar=20?= =?UTF-8?q?=E2=86=92=20sidebar=20layout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace cramped horizontal nav with grouped sidebar navigation - Sections: Monitor, Respond, Analyze with logical grouping - Collapsible sidebar with localStorage persistence - Tooltips on hover when collapsed (icon-only mode) - Slim top bar: search, alerts bell, help, theme toggle, user menu - Org switcher + settings pinned to sidebar bottom - Mobile: grouped slide-out panel matches sidebar sections - Smooth CSS transitions on collapse/expand --- .pre-commit-config.yaml | 2 +- assets/css/app.css | 50 ++ assets/js/app.ts | 18 +- lib/towerops_web/components/layouts.ex | 1035 ++++++++++++------------ 4 files changed, 576 insertions(+), 529 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 173daa95..3498c83d 120000 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1 +1 @@ -/nix/store/zx636v118rzzqzwafgrw2aybh7l0szrl-pre-commit-config.json \ No newline at end of file +/nix/store/4xlkmdd947h5qlhj2rmbyavq08grkz27-pre-commit-config.json \ No newline at end of file diff --git a/assets/css/app.css b/assets/css/app.css index b044c0a9..aaa61e4b 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -105,6 +105,56 @@ /* Hide 1Password live region */ [id="1p-live-region"] { display: none !important; } +/* Sidebar layout */ +#app-sidebar { + @apply w-56 transition-all duration-200; +} +.sidebar-collapsed #app-sidebar { + @apply w-16; +} +.sidebar-collapsed [data-sidebar-text] { + display: none; +} +.sidebar-collapsed [data-sidebar-section] { + display: none; +} +.sidebar-collapsed .sidebar-collapse-icon { + transform: rotate(180deg); +} +.sidebar-collapse-icon { + transition: transform 0.2s ease; +} +.sidebar-collapsed [data-sidebar-tooltip] { + position: relative; +} +.sidebar-collapsed [data-sidebar-tooltip]:hover::after { + content: attr(data-sidebar-tooltip); + position: absolute; + left: 100%; + top: 50%; + transform: translateY(-50%); + margin-left: 0.5rem; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + font-size: 0.75rem; + white-space: nowrap; + z-index: 50; + background-color: #1f2937; + color: #fff; + pointer-events: none; +} +#main-wrapper { + @apply transition-all duration-200; +} +@media (min-width: 1024px) { + #main-wrapper { + margin-left: 14rem; /* w-56 = 224px */ + } + .sidebar-collapsed #main-wrapper { + margin-left: 4rem; /* w-16 = 64px */ + } +} + /* This file is for your main application CSS */ /* Page transitions */ diff --git a/assets/js/app.ts b/assets/js/app.ts index 63a883ab..69e90563 100644 --- a/assets/js/app.ts +++ b/assets/js/app.ts @@ -1375,6 +1375,22 @@ const StatusTitle = { } } +// Sidebar collapse - restore state from localStorage and persist toggle +const SidebarCollapse = { + mounted(this: any) { + if (localStorage.getItem('sidebarCollapsed') === 'true') { + this.el.classList.add('sidebar-collapsed') + } + this.el.querySelector('[data-sidebar-toggle]')?.addEventListener('click', () => { + // The JS.toggle_class will fire, so we check after a tick + requestAnimationFrame(() => { + const collapsed = this.el.classList.contains('sidebar-collapsed') + localStorage.setItem('sidebarCollapsed', String(collapsed)) + }) + }) + } +} + const ThemeSelector = { mounted() { this.updateActive() @@ -1398,7 +1414,7 @@ const ThemeSelector = { const liveSocket = new LiveSocket("/live", Socket, { longPollFallbackMs: 5000, params: { _csrf_token: csrfToken, timezone: userTimezone }, - hooks: { ...colocatedHooks, SensorChart, CopyToClipboard, ScrollToTop, AutoDismissFlash, BetaBannerDismiss, NetworkMap, SitesMap, LeafletMap, DeviceListReorder, MikrotikPortSync, GlobalSearch, GlobalSearchTrigger, DynamicFavicon, StatusTitle, ThemeSelector }, + hooks: { ...colocatedHooks, SensorChart, CopyToClipboard, ScrollToTop, AutoDismissFlash, BetaBannerDismiss, NetworkMap, SitesMap, LeafletMap, DeviceListReorder, MikrotikPortSync, GlobalSearch, GlobalSearchTrigger, DynamicFavicon, StatusTitle, ThemeSelector, SidebarCollapse }, }) // Show progress bar on live navigation and form submits diff --git a/lib/towerops_web/components/layouts.ex b/lib/towerops_web/components/layouts.ex index de965b46..7fd586b7 100644 --- a/lib/towerops_web/components/layouts.ex +++ b/lib/towerops_web/components/layouts.ex @@ -148,10 +148,10 @@ defmodule ToweropsWeb.Layouts do |> Map.put(:always_show_banner, always_show_banner) ~H""" -
+