From 79d981916625413395b823c7f57770b315c0e22b Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Thu, 9 Apr 2026 17:34:59 +0100 Subject: [PATCH] Persist sidebar menu group open/closed state in localStorage Add an Alpine.js sidebarGroup component that remembers whether each expandable sidebar group (Community, Teams, Developer) is open or closed across page navigations using localStorage. When the current page belongs to a group, that group is forced open regardless of saved state, without overwriting the user's preference. Co-Authored-By: Claude Opus 4.6 --- package-lock.json | 2 +- resources/js/alpine/sidebarGroup.js | 28 +++++++++++++++++++ resources/js/app.js | 2 ++ .../components/layouts/dashboard.blade.php | 6 ++-- 4 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 resources/js/alpine/sidebarGroup.js diff --git a/package-lock.json b/package-lock.json index 1064607a..5da89bcd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "ancient-ox", + "name": "zappy-ox", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/resources/js/alpine/sidebarGroup.js b/resources/js/alpine/sidebarGroup.js new file mode 100644 index 00000000..e22ce619 --- /dev/null +++ b/resources/js/alpine/sidebarGroup.js @@ -0,0 +1,28 @@ + +export default (key) => ({ + init() { + const storageKey = `sidebar_${key}` + const saved = localStorage.getItem(storageKey) + const hasCurrent = !!this.$el.querySelector('[data-current]') + let skipNext = false + + if ((hasCurrent || saved === 'open') && !this.$el.hasAttribute('data-open')) { + if (hasCurrent) { + skipNext = true + } + this.$el.querySelector('button')?.click() + } + + this._observer = new MutationObserver(() => { + if (skipNext) { + skipNext = false + return + } + localStorage.setItem(storageKey, this.$el.hasAttribute('data-open') ? 'open' : 'closed') + }) + this._observer.observe(this.$el, { attributes: true, attributeFilter: ['data-open'] }) + }, + destroy() { + this._observer?.disconnect() + }, +}) diff --git a/resources/js/app.js b/resources/js/app.js index a1a7363b..dfb5cda0 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -8,6 +8,7 @@ import { } from '../../vendor/livewire/livewire/dist/livewire.esm.js' import codeBlock from './alpine/codeBlock.js' import copyMarkdown from './alpine/copyMarkdown.js' +import sidebarGroup from './alpine/sidebarGroup.js' import docsearch from '@docsearch/js' import Atropos from 'atropos' import '@docsearch/css' @@ -62,6 +63,7 @@ window.gsap = gsap // Alpine Alpine.data('codeBlock', codeBlock) Alpine.data('copyMarkdown', copyMarkdown) +Alpine.data('sidebarGroup', sidebarGroup) Alpine.magic('refAll', (el) => { return (refName) => { return Array.from(el.querySelectorAll(`[x-ref="${refName}"]`)) diff --git a/resources/views/components/layouts/dashboard.blade.php b/resources/views/components/layouts/dashboard.blade.php index 12c7fd5b..ccf29c31 100644 --- a/resources/views/components/layouts/dashboard.blade.php +++ b/resources/views/components/layouts/dashboard.blade.php @@ -125,7 +125,7 @@ class="min-h-screen bg-white font-poppins antialiased dark:bg-zinc-900 dark:text @endif - + Showcase @@ -150,7 +150,7 @@ class="min-h-screen bg-white font-poppins antialiased dark:bg-zinc-900 dark:text $ownedTeam = auth()->user()->ownedTeam; $teamMemberships = auth()->user()->activeTeamMemberships(); @endphp - + @if($ownedTeam) {{ $ownedTeam->name }} @@ -172,7 +172,7 @@ class="min-h-screen bg-white font-poppins antialiased dark:bg-zinc-900 dark:text @endif @feature(App\Features\ShowPlugins::class) - + Hub