Skip to content

Commit 3bba4ca

Browse files
simonhampclaude
andauthored
Persist sidebar menu group open/closed state in localStorage (#337)
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 <noreply@anthropic.com>
1 parent 19c4c7c commit 3bba4ca

File tree

4 files changed

+34
-4
lines changed

4 files changed

+34
-4
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
export default (key) => ({
3+
init() {
4+
const storageKey = `sidebar_${key}`
5+
const saved = localStorage.getItem(storageKey)
6+
const hasCurrent = !!this.$el.querySelector('[data-current]')
7+
let skipNext = false
8+
9+
if ((hasCurrent || saved === 'open') && !this.$el.hasAttribute('data-open')) {
10+
if (hasCurrent) {
11+
skipNext = true
12+
}
13+
this.$el.querySelector('button')?.click()
14+
}
15+
16+
this._observer = new MutationObserver(() => {
17+
if (skipNext) {
18+
skipNext = false
19+
return
20+
}
21+
localStorage.setItem(storageKey, this.$el.hasAttribute('data-open') ? 'open' : 'closed')
22+
})
23+
this._observer.observe(this.$el, { attributes: true, attributeFilter: ['data-open'] })
24+
},
25+
destroy() {
26+
this._observer?.disconnect()
27+
},
28+
})

resources/js/app.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from '../../vendor/livewire/livewire/dist/livewire.esm.js'
99
import codeBlock from './alpine/codeBlock.js'
1010
import copyMarkdown from './alpine/copyMarkdown.js'
11+
import sidebarGroup from './alpine/sidebarGroup.js'
1112
import docsearch from '@docsearch/js'
1213
import Atropos from 'atropos'
1314
import '@docsearch/css'
@@ -62,6 +63,7 @@ window.gsap = gsap
6263
// Alpine
6364
Alpine.data('codeBlock', codeBlock)
6465
Alpine.data('copyMarkdown', copyMarkdown)
66+
Alpine.data('sidebarGroup', sidebarGroup)
6567
Alpine.magic('refAll', (el) => {
6668
return (refName) => {
6769
return Array.from(el.querySelectorAll(`[x-ref="${refName}"]`))

resources/views/components/layouts/dashboard.blade.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ class="min-h-screen bg-white font-poppins antialiased dark:bg-zinc-900 dark:text
125125
</flux:sidebar.item>
126126
@endif
127127

128-
<flux:sidebar.group expandable :expanded="false" heading="Community" class="mt-4 grid">
128+
<flux:sidebar.group expandable :expanded="false" heading="Community" class="mt-4 grid" x-data="sidebarGroup('community')">
129129
<flux:sidebar.item href="{{ route('customer.showcase.index') }}" :current="request()->routeIs('customer.showcase.*')">
130130
Showcase
131131
</flux:sidebar.item>
@@ -150,7 +150,7 @@ class="min-h-screen bg-white font-poppins antialiased dark:bg-zinc-900 dark:text
150150
$ownedTeam = auth()->user()->ownedTeam;
151151
$teamMemberships = auth()->user()->activeTeamMemberships();
152152
@endphp
153-
<flux:sidebar.group expandable :expanded="false" heading="Teams" class="mt-4 grid">
153+
<flux:sidebar.group expandable :expanded="false" heading="Teams" class="mt-4 grid" x-data="sidebarGroup('teams')">
154154
@if($ownedTeam)
155155
<flux:sidebar.item href="{{ route('customer.team.index') }}" :current="request()->routeIs('customer.team.index')">
156156
{{ $ownedTeam->name }}
@@ -172,7 +172,7 @@ class="min-h-screen bg-white font-poppins antialiased dark:bg-zinc-900 dark:text
172172
@endif
173173

174174
@feature(App\Features\ShowPlugins::class)
175-
<flux:sidebar.group expandable :expanded="false" heading="Developer" class="mt-4 grid">
175+
<flux:sidebar.group expandable :expanded="false" heading="Developer" class="mt-4 grid" x-data="sidebarGroup('developer')">
176176
<flux:sidebar.item href="{{ route('customer.developer.onboarding') }}" :current="request()->routeIs('customer.developer.onboarding', 'customer.developer.dashboard')">
177177
Hub
178178
</flux:sidebar.item>

0 commit comments

Comments
 (0)