fix(bug-2): isCollapsed getter — sidebar auto-expands on mobile viewport

Add isMobile reactive property + resize listener to sidebar store.
isCollapsed getter returns collapsed && !isMobile, so:
- Desktop: collapse works as normal
- Mobile: isCollapsed always false → sidebar always shows expanded state
  regardless of desktop-set collapsed value

Replace all visual $store.sidebar.collapsed → isCollapsed in sidebar + layout.
toggle() / _syncDataset() remain on raw `collapsed` (persisted to localStorage).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
boban 2026-05-17 12:47:38 +02:00
parent e0624b5d7c
commit d3ad4b4740
3 changed files with 20 additions and 15 deletions

View File

@ -62,7 +62,12 @@ document.addEventListener('alpine:init', () => {
Alpine.store('sidebar', {
mobileOpen: false,
collapsed: localStorage.getItem('sidebarCollapsed') === 'true',
init() { this._syncDataset(); },
isMobile: window.innerWidth < 768,
get isCollapsed() { return this.collapsed && ! this.isMobile; },
init() {
this._syncDataset();
window.addEventListener('resize', () => { this.isMobile = window.innerWidth < 768; });
},
_syncDataset() {
if (this.collapsed) {
document.documentElement.dataset.sidebarCollapsed = '1';

View File

@ -98,21 +98,21 @@
<aside
x-data="{ wsOpen: false }"
:class="$store.sidebar.collapsed ? 'w-16' : 'w-60'"
:class="$store.sidebar.isCollapsed ? 'w-16' : 'w-60'"
class="fixed inset-y-0 left-0 h-screen flex flex-col bg-s1 border-r border-white/[.06] z-40 -translate-x-full md:translate-x-0 transition-[width,transform] duration-200 ease-in-out"
:style="$store.sidebar?.mobileOpen ? 'translate: 0' : null"
>
<!-- Header: Logo + Workspace Switcher -->
<div class="flex-shrink-0 relative" @click.outside="wsOpen = false">
<!-- Collapsed state: centered logo acts as expand toggle -->
<div x-show="$store.sidebar.collapsed" class="flex items-center justify-center py-4 border-b border-white/[.06]">
<div x-show="$store.sidebar.isCollapsed" class="flex items-center justify-center py-4 border-b border-white/[.06]">
<button @click="$store.sidebar.toggle()" title="Expand sidebar" class="flex items-center justify-center w-7 h-7 hover:opacity-75 transition-opacity">
<img src="/brand/logo-mark.svg" alt="Nimuli" class="w-7 h-7">
</button>
</div>
<!-- Expanded state: full header with workspace switcher -->
<div x-show="!$store.sidebar.collapsed">
<div x-show="!$store.sidebar.isCollapsed">
<div class="flex items-stretch border-b border-white/[.06]">
<button
@click="wsOpen = !wsOpen"
@ -192,8 +192,8 @@
wire:navigate
data-nav-link="{{ $item['route'] }}"
@if(in_array($item['route'], ['w.dashboard', 'dashboard'])) data-nav-exact="1" @endif
:title="$store.sidebar.collapsed ? '{{ $item['label'] }}' : ''"
:class="$store.sidebar.collapsed ? 'justify-center px-2 gap-0' : 'gap-3 px-3'"
:title="$store.sidebar.isCollapsed ? '{{ $item['label'] }}' : ''"
:class="$store.sidebar.isCollapsed ? 'justify-center px-2 gap-0' : 'gap-3 px-3'"
class="flex items-center py-2 rounded-lg text-sm transition-colors group
{{ $isActive
? 'bg-blue/10 text-blue'
@ -242,7 +242,7 @@
</svg>
@endif
</span>
<span x-show="!$store.sidebar.collapsed" class="truncate">{{ $item['label'] }}</span>
<span x-show="!$store.sidebar.isCollapsed" class="truncate">{{ $item['label'] }}</span>
</a>
@endforeach
</nav>
@ -251,24 +251,24 @@
<div class="max-md:hidden flex-shrink-0 border-t border-white/[.06] px-2 py-2">
<button
@click="$store.sidebar.toggle()"
:title="$store.sidebar.collapsed ? 'Expand sidebar' : 'Collapse sidebar'"
:title="$store.sidebar.isCollapsed ? 'Expand sidebar' : 'Collapse sidebar'"
class="flex items-center gap-3 w-full px-3 py-2 rounded-lg text-t3 hover:text-t1 hover:bg-s2 transition-colors"
>
<span class="w-4 h-4 flex-shrink-0">
<svg x-show="!$store.sidebar.collapsed" fill="none" viewBox="0 0 16 16" stroke="currentColor" stroke-width="1.5">
<svg x-show="!$store.sidebar.isCollapsed" fill="none" viewBox="0 0 16 16" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M10 3L5 8l5 5"/>
</svg>
<svg x-show="$store.sidebar.collapsed" x-cloak fill="none" viewBox="0 0 16 16" stroke="currentColor" stroke-width="1.5">
<svg x-show="$store.sidebar.isCollapsed" x-cloak fill="none" viewBox="0 0 16 16" stroke="currentColor" stroke-width="1.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 3l5 5-5 5"/>
</svg>
</span>
<span x-show="!$store.sidebar.collapsed" class="text-xs">Collapse</span>
<span x-show="!$store.sidebar.isCollapsed" class="text-xs">Collapse</span>
</button>
</div>
<!-- Plan Usage Box -->
@if($workspace && $plan)
<div x-show="!$store.sidebar.collapsed" class="mx-3 mb-3 p-3 bg-s2 border border-white/[.06] rounded-lg">
<div x-show="!$store.sidebar.isCollapsed" class="mx-3 mb-3 p-3 bg-s2 border border-white/[.06] rounded-lg">
<div class="flex items-center justify-between mb-1.5">
<span class="text-xs font-medium text-t2">{{ ucfirst($plan->name) }} Plan</span>
@if(str_contains(strtolower($plan->name), 'free'))
@ -291,11 +291,11 @@
<div class="w-8 h-8 rounded-full bg-blue flex items-center justify-center text-white text-xs font-semibold flex-shrink-0">
{{ $initials ?: strtoupper(substr(auth()->user()->email, 0, 2)) }}
</div>
<div x-show="!$store.sidebar.collapsed" class="flex-1 min-w-0">
<div x-show="!$store.sidebar.isCollapsed" class="flex-1 min-w-0">
<div class="text-xs font-medium text-t1 truncate">{{ auth()->user()->name }}</div>
<div class="text-xs text-t3 truncate">{{ auth()->user()->email }}</div>
</div>
<form x-show="!$store.sidebar.collapsed" method="POST" action="{{ route('logout') }}">
<form x-show="!$store.sidebar.isCollapsed" method="POST" action="{{ route('logout') }}">
@csrf
<button
type="submit"

View File

@ -132,7 +132,7 @@
<!-- Main content area: shifts right based on sidebar width -->
<div
class="nim-content flex flex-col min-h-screen transition-[margin-left] duration-200 ease-in-out"
:class="$store.sidebar?.collapsed ? 'md:ml-16' : 'md:ml-60'"
:class="$store.sidebar?.isCollapsed ? 'md:ml-16' : 'md:ml-60'"
>
@persist('topbar')
<x-topbar :title="$title ?? ''" />