Move sidebar-collapsed class to <html> element

LiveView DOM patching was stripping the client-side sidebar-collapsed
class from #sidebar-wrapper on every navigation. Fix: put the class
on document.documentElement which LiveView never touches.

- Sync script in <head> applies state before first paint (no flash)
- Hook click handler toggles on <html> + saves to localStorage
- CSS selectors updated to html.sidebar-collapsed
- Removed phx-click JS.toggle_class (hook handles toggle directly)
This commit is contained in:
Graham McIntire 2026-03-13 14:28:38 -05:00
parent 7149ab71cd
commit 8e25034e69
4 changed files with 18 additions and 36 deletions

View file

@ -109,25 +109,25 @@
#app-sidebar {
@apply w-56 transition-all duration-200;
}
.sidebar-collapsed #app-sidebar {
html.sidebar-collapsed #app-sidebar {
@apply w-16;
}
.sidebar-collapsed [data-sidebar-text] {
html.sidebar-collapsed [data-sidebar-text] {
display: none;
}
.sidebar-collapsed [data-sidebar-section] {
html.sidebar-collapsed [data-sidebar-section] {
display: none;
}
.sidebar-collapsed .sidebar-collapse-icon {
html.sidebar-collapsed .sidebar-collapse-icon {
transform: rotate(180deg);
}
.sidebar-collapse-icon {
transition: transform 0.2s ease;
}
.sidebar-collapsed [data-sidebar-tooltip] {
html.sidebar-collapsed [data-sidebar-tooltip] {
position: relative;
}
.sidebar-collapsed [data-sidebar-tooltip]:hover::after {
html.sidebar-collapsed [data-sidebar-tooltip]:hover::after {
content: attr(data-sidebar-tooltip);
position: absolute;
left: 100%;
@ -150,7 +150,7 @@
#main-wrapper {
margin-left: 14rem; /* w-56 = 224px */
}
.sidebar-collapsed #main-wrapper {
html.sidebar-collapsed #main-wrapper {
margin-left: 4rem; /* w-16 = 64px */
}
}

View file

@ -1375,29 +1375,15 @@ const StatusTitle = {
}
}
// Sidebar collapse - restore state from localStorage and persist toggle.
// Uses both mounted() and updated() because LiveView DOM patching can
// strip the client-side "sidebar-collapsed" class on live navigations.
// Sidebar collapse - toggle class on <html> (which LiveView never patches)
// so the state persists across all navigations without flicker.
const SidebarCollapse = {
mounted(this: any) {
this._apply()
this._listen()
},
updated(this: any) {
this._apply()
},
_apply(this: any) {
if (localStorage.getItem('sidebarCollapsed') === 'true') {
this.el.classList.add('sidebar-collapsed')
}
},
_listen(this: any) {
this.el.addEventListener('click', (e: Event) => {
if ((e.target as HTMLElement).closest('[data-sidebar-toggle]')) {
requestAnimationFrame(() => {
const collapsed = this.el.classList.contains('sidebar-collapsed')
localStorage.setItem('sidebarCollapsed', String(collapsed))
})
const html = document.documentElement
html.classList.toggle('sidebar-collapsed')
localStorage.setItem('sidebarCollapsed', String(html.classList.contains('sidebar-collapsed')))
}
})
}

View file

@ -149,15 +149,6 @@ defmodule ToweropsWeb.Layouts do
~H"""
<div id="sidebar-wrapper" phx-hook="SidebarCollapse" class="min-h-screen bg-gray-50 dark:bg-gray-950">
<script>
// Apply sidebar state synchronously to prevent flash on navigation
(function() {
var w = document.getElementById('sidebar-wrapper');
if (w && localStorage.getItem('sidebarCollapsed') === 'true') {
w.classList.add('sidebar-collapsed');
}
})();
</script>
<!-- Impersonation Banner -->
<%= if @current_scope && @current_scope.impersonating? do %>
<div class="bg-yellow-400 border-b-2 border-yellow-600 relative z-50">
@ -487,7 +478,6 @@ defmodule ToweropsWeb.Layouts do
<button
type="button"
data-sidebar-toggle
phx-click={JS.toggle_class("sidebar-collapsed", to: "#sidebar-wrapper")}
class="flex items-center justify-center w-full rounded-md px-3 py-2 text-gray-400 hover:bg-gray-50 hover:text-gray-600 dark:hover:bg-gray-800 dark:hover:text-gray-300 transition-colors"
title={t("Toggle sidebar")}
>

View file

@ -68,6 +68,12 @@
window.addEventListener("phx:set-theme", (e) => setTheme(e.target.dataset.phxTheme));
})();
</script>
<script>
// Apply sidebar collapsed state before paint (prevents flash)
if (localStorage.getItem('sidebarCollapsed') === 'true') {
document.documentElement.classList.add('sidebar-collapsed');
}
</script>
</head>
<body>
<a