fix(sidebar): remove collapse, fix mobile overlay and blur effect
- Remove desktop collapse feature — sidebar always open at 264px - Simplify Alpine sidebar store to mobileOpen only - Fix mobile overlay: add x-data to #nim-app so Alpine processes x-show/x-transition - Mobile overlay uses direct backdrop-filter: blur(4px) bypassing Tailwind v4 CSS variable chain - Fix close/workspace-trigger overlap: restructure header as flex row with close as sibling - Clicking overlay (z-30) closes sidebar; sidebar stays at z-40 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>main
parent
e36ecb1dba
commit
3eada67b9a
|
|
@ -61,34 +61,11 @@ document.addEventListener('alpine:init', () => {
|
|||
|
||||
Alpine.store('sidebar', {
|
||||
mobileOpen: false,
|
||||
collapsed: localStorage.getItem('sidebarCollapsed') === 'true',
|
||||
isMobile: window.innerWidth < 768,
|
||||
isCollapsed: false,
|
||||
init() {
|
||||
this.isCollapsed = this.collapsed && ! this.isMobile;
|
||||
this._syncDataset();
|
||||
window.addEventListener('resize', () => {
|
||||
const wasMobile = this.isMobile;
|
||||
this.isMobile = window.innerWidth < 768;
|
||||
this.isCollapsed = this.collapsed && ! this.isMobile;
|
||||
this._syncDataset();
|
||||
if (this.isMobile && ! wasMobile) { this.mobileOpen = false; }
|
||||
if (window.innerWidth >= 768) { this.mobileOpen = false; }
|
||||
});
|
||||
},
|
||||
_syncDataset() {
|
||||
if (this.isCollapsed) {
|
||||
document.documentElement.dataset.sidebarCollapsed = '1';
|
||||
} else {
|
||||
delete document.documentElement.dataset.sidebarCollapsed;
|
||||
}
|
||||
},
|
||||
toggle() {
|
||||
this.collapsed = ! this.collapsed;
|
||||
this.isCollapsed = this.collapsed && ! this.isMobile;
|
||||
localStorage.setItem('sidebarCollapsed', this.collapsed);
|
||||
this._syncDataset();
|
||||
window.dispatchEvent(new CustomEvent('sidebar-toggled', { detail: { collapsed: this.collapsed } }));
|
||||
},
|
||||
toggleMobile() { this.mobileOpen = ! this.mobileOpen; },
|
||||
closeMobile() { this.mobileOpen = false; },
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,33 +38,36 @@
|
|||
|
||||
{{-- ═══ Header ═══ --}}
|
||||
<header class="flex-shrink-0 px-3 py-3 border-b border-white/[.06]" @click.outside="wsOpen = false">
|
||||
<button
|
||||
@click="$store.sidebar?.isCollapsed && !$store.sidebar?.isMobile ? $store.sidebar.toggle() : (wsOpen = !wsOpen)"
|
||||
:aria-expanded="wsOpen.toString()"
|
||||
class="ws-trigger w-full flex items-center gap-3 px-2.5 py-2 rounded-xl hover:bg-s2 transition-colors duration-150"
|
||||
>
|
||||
<img src="/brand/logo-mark.svg" alt="Nimuli" class="ws-logo w-8 h-8 flex-shrink-0 rounded-lg">
|
||||
<span class="ws-text flex-1 min-w-0 text-left grid leading-snug">
|
||||
<span class="ws-brand text-accent-gradient font-bold text-sm tracking-tight">nimuli</span>
|
||||
<span class="ws-name text-xs text-t2 truncate">{{ $workspace?->name ?? 'Select workspace' }}</span>
|
||||
</span>
|
||||
<svg class="ws-chevron w-4 h-4 text-t3 flex-shrink-0 transition-transform duration-200"
|
||||
:class="wsOpen && 'rotate-180'"
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="6 9 12 15 18 9"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="flex items-center gap-1">
|
||||
<button
|
||||
@click="wsOpen = !wsOpen"
|
||||
:aria-expanded="wsOpen.toString()"
|
||||
class="ws-trigger flex-1 min-w-0 flex items-center gap-3 px-2.5 py-2 rounded-xl hover:bg-s2 transition-colors duration-150"
|
||||
>
|
||||
<img src="/brand/logo-mark.svg" alt="Nimuli" class="ws-logo w-8 h-8 flex-shrink-0 rounded-lg">
|
||||
<span class="ws-text flex-1 min-w-0 text-left grid leading-snug">
|
||||
<span class="ws-brand text-accent-gradient font-bold text-sm tracking-tight">nimuli</span>
|
||||
<span class="ws-name text-xs text-t2 truncate">{{ $workspace?->name ?? 'Select workspace' }}</span>
|
||||
</span>
|
||||
<svg class="ws-chevron w-4 h-4 text-t3 flex-shrink-0 transition-transform duration-200"
|
||||
:class="wsOpen && 'rotate-180'"
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="6 9 12 15 18 9"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{{-- Mobile close --}}
|
||||
<button
|
||||
@click="$store.sidebar.closeMobile()"
|
||||
class="md:hidden absolute right-3 top-3.5 p-2 rounded-lg text-t3 hover:text-t1 hover:bg-s2 transition-colors"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 20 20" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 6l8 8M6 14L14 6"/>
|
||||
</svg>
|
||||
</button>
|
||||
{{-- Mobile close --}}
|
||||
<button
|
||||
@click="$store.sidebar.closeMobile()"
|
||||
class="md:hidden flex-shrink-0 p-2 rounded-lg text-t3 hover:text-t1 hover:bg-s2 transition-colors"
|
||||
aria-label="Close sidebar"
|
||||
>
|
||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 20 20" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 6l8 8M6 14L14 6"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- Workspace dropdown --}}
|
||||
<div
|
||||
|
|
@ -83,7 +86,7 @@
|
|||
href="{{ route('w.links.index', $ws->ulid) }}"
|
||||
wire:navigate
|
||||
@click="wsOpen = false"
|
||||
class="flex items-center gap-2.5 px-3.5 py-2.5 text-sm border-b border-white/[.06] transition-colors hover:bg-s3
|
||||
class="ws-item flex items-center gap-2.5 text-sm border-b border-white/[.06] transition-colors hover:bg-s3
|
||||
{{ $workspace?->id === $ws->id ? 'text-t1 font-medium' : 'text-t2' }}"
|
||||
>
|
||||
<span class="w-6 h-6 rounded-md flex items-center justify-center text-white font-bold text-xs flex-shrink-0 bg-accent-gradient">
|
||||
|
|
@ -102,7 +105,7 @@
|
|||
@endforelse
|
||||
<button
|
||||
@click="$dispatch('openModal', {component: 'modals.create-workspace'}); wsOpen = false"
|
||||
class="w-full flex items-center gap-2.5 px-3.5 py-2.5 text-sm text-t2 hover:text-t1 hover:bg-s3 transition-colors text-left"
|
||||
class="ws-item w-full flex items-center gap-2.5 text-sm text-t2 hover:text-t1 hover:bg-s3 transition-colors text-left"
|
||||
>
|
||||
<svg class="w-4 h-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
|
|
@ -132,7 +135,6 @@
|
|||
wire:navigate
|
||||
data-nav-link="{{ $item['route'] }}"
|
||||
@isset($item['exact']) data-nav-exact="1" @endisset
|
||||
data-label="{{ $item['label'] }}"
|
||||
class="nav-item relative flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium w-full {{ $isActive ? 'nim-nav-active' : '' }}"
|
||||
>
|
||||
<span class="w-[18px] h-[18px] flex-shrink-0">
|
||||
|
|
@ -157,7 +159,6 @@
|
|||
href="{{ $url }}"
|
||||
wire:navigate
|
||||
data-nav-link="{{ $item['route'] }}"
|
||||
data-label="{{ $item['label'] }}"
|
||||
class="nav-item relative flex items-center gap-3 px-3 py-2 rounded-lg text-sm font-medium w-full {{ $isActive ? 'nim-nav-active' : '' }}"
|
||||
>
|
||||
<span class="w-[18px] h-[18px] flex-shrink-0">
|
||||
|
|
@ -197,19 +198,6 @@
|
|||
{{-- ═══ Footer ═══ --}}
|
||||
<footer class="flex-shrink-0 border-t border-white/[.06]">
|
||||
|
||||
{{-- Collapse toggle (desktop only) --}}
|
||||
<button
|
||||
@click="$store.sidebar.toggle()"
|
||||
class="collapse-row max-md:hidden w-full flex items-center gap-3 px-[18px] py-2.5 text-sm text-t3 hover:text-t1 border-b border-white/[.06] transition-colors duration-150"
|
||||
>
|
||||
<svg class="w-4 h-4 flex-shrink-0 transition-transform duration-200"
|
||||
fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="15 18 9 12 15 6"/>
|
||||
</svg>
|
||||
<span class="collapse-text">Collapse</span>
|
||||
</button>
|
||||
|
||||
{{-- User row --}}
|
||||
@auth
|
||||
<div class="user-row flex items-center gap-3 px-3.5 py-3 min-w-0">
|
||||
|
|
|
|||
|
|
@ -18,17 +18,6 @@
|
|||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- Sidebar-init: set collapsed attribute before first paint — eliminates width flash -->
|
||||
<script>
|
||||
(function () {
|
||||
try {
|
||||
if (localStorage.getItem('sidebarCollapsed') === 'true' && window.innerWidth >= 768) {
|
||||
document.documentElement.dataset.sidebarCollapsed = '1';
|
||||
}
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Theme-init: runs synchronously before any paint — eliminates FOUC -->
|
||||
<script>
|
||||
(function () {
|
||||
|
|
@ -57,7 +46,7 @@
|
|||
--nim-accent-bg: rgba(139,92,246,.14); --nim-accent-bg-strong: rgba(139,92,246,.22);
|
||||
--nim-accent-glow: rgba(139,92,246,.6);
|
||||
--nim-brand-grad: linear-gradient(135deg,#a855f7 0%,#ec4899 100%);
|
||||
--nim-sb-w: 264px; --nim-sb-c: 72px;
|
||||
--nim-sb-w: 264px;
|
||||
}
|
||||
[data-theme="light"] {
|
||||
--c-bg: #ffffff; --c-s1: #ffffff; --c-s2: #f9fafb;
|
||||
|
|
@ -92,8 +81,6 @@
|
|||
border-top-color: var(--nim-accent); border-radius: 50%;
|
||||
animation: nim-spin .55s linear infinite; z-index: 10;
|
||||
}
|
||||
html[data-sidebar-collapsed] .skel-sidebar { width: var(--nim-sb-c); }
|
||||
html[data-sidebar-collapsed] .skel-topbar { left: var(--nim-sb-c); }
|
||||
body.app-ready .app-spinner, body.app-ready .skel-sidebar, body.app-ready .skel-topbar { display:none; }
|
||||
@keyframes nim-spin { to { transform: translate(-50%,-50%) rotate(360deg); } }
|
||||
|
||||
|
|
@ -104,14 +91,12 @@
|
|||
border-right: 1px solid var(--color-b1);
|
||||
overflow-x: hidden;
|
||||
font-family: 'Geist', 'Inter', ui-sans-serif, system-ui, sans-serif;
|
||||
transition: width .28s cubic-bezier(.4,0,.2,1), translate .28s cubic-bezier(.4,0,.2,1);
|
||||
transition: translate .28s cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
/* Content margin */
|
||||
/* Content margin — desktop: always full sidebar width */
|
||||
@media (min-width: 768px) {
|
||||
.nim-content { margin-left: var(--nim-sb-w); }
|
||||
html[data-sidebar-collapsed] .nim-content { margin-left: var(--nim-sb-c); }
|
||||
html[data-sidebar-collapsed] .nim-sidebar { width: var(--nim-sb-c); }
|
||||
}
|
||||
|
||||
/* Nav items */
|
||||
|
|
@ -131,44 +116,21 @@
|
|||
box-shadow: 0 0 12px rgba(124,58,237,.5);
|
||||
}
|
||||
|
||||
/* Mobile overlay blur */
|
||||
.nim-mobile-overlay {
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
/* Nav scrollbar */
|
||||
.nim-sb-nav { scrollbar-width: thin; scrollbar-color: var(--nim-border-strong) transparent; }
|
||||
.nim-sb-nav::-webkit-scrollbar { width: 6px; }
|
||||
.nim-sb-nav::-webkit-scrollbar-thumb { background: var(--nim-border-strong); border-radius: 3px; }
|
||||
.nim-sb-nav::-webkit-scrollbar-track { background: transparent; }
|
||||
|
||||
/* Logout hover (Tailwind handles this via hover:text-red-400 hover:bg-red-400/10) */
|
||||
|
||||
/* Collapse state — desktop only */
|
||||
@media (min-width: 768px) {
|
||||
html[data-sidebar-collapsed] .ws-text,
|
||||
html[data-sidebar-collapsed] .ws-chevron,
|
||||
html[data-sidebar-collapsed] .nav-text,
|
||||
html[data-sidebar-collapsed] .nav-label,
|
||||
html[data-sidebar-collapsed] .nav-badge,
|
||||
html[data-sidebar-collapsed] .nav-plan-usage,
|
||||
html[data-sidebar-collapsed] .user-info,
|
||||
html[data-sidebar-collapsed] .user-logout-btn,
|
||||
html[data-sidebar-collapsed] .collapse-text { display: none !important; }
|
||||
|
||||
html[data-sidebar-collapsed] .ws-trigger { justify-content: center !important; padding: 8px !important; }
|
||||
html[data-sidebar-collapsed] .nav-item { justify-content: center !important; padding: 10px !important; gap: 0 !important; }
|
||||
html[data-sidebar-collapsed] .nav-item.nim-nav-active::before { display: none; }
|
||||
html[data-sidebar-collapsed] .collapse-row { justify-content: center !important; padding: 12px !important; }
|
||||
html[data-sidebar-collapsed] .collapse-row svg { transform: rotate(180deg); }
|
||||
html[data-sidebar-collapsed] .user-row { justify-content: center !important; padding: 12px !important; gap: 0 !important; }
|
||||
|
||||
/* Tooltip on hover when collapsed */
|
||||
html[data-sidebar-collapsed] .nav-item:hover::after {
|
||||
content: attr(data-label);
|
||||
position: absolute; left: calc(100% + 12px); top: 50%; transform: translateY(-50%);
|
||||
background: var(--color-s3); color: var(--color-t1);
|
||||
padding: 6px 10px; border-radius: 6px; font-size: 12.5px;
|
||||
white-space: nowrap; pointer-events: none;
|
||||
border: 1px solid var(--color-b2);
|
||||
box-shadow: 0 8px 24px -6px rgba(0,0,0,.5); z-index: 100;
|
||||
}
|
||||
}
|
||||
/* Sidebar element padding — shorthand overrides Tailwind v4 logical-property variants */
|
||||
.ws-item { padding: 12px 14px; }
|
||||
.user-row { padding: 14px; }
|
||||
</style>
|
||||
|
||||
@vite(['resources/js/app.js'])
|
||||
|
|
@ -180,10 +142,10 @@
|
|||
<div class="skel-topbar" aria-hidden="true"></div>
|
||||
<div class="app-spinner" aria-hidden="true"></div>
|
||||
|
||||
<div id="nim-app" class="bg-bg text-t1 font-sans antialiased min-h-screen">
|
||||
<div id="nim-app" x-data class="bg-bg text-t1 font-sans antialiased min-h-screen">
|
||||
<!-- Mobile Sidebar Overlay -->
|
||||
<div
|
||||
class="fixed inset-0 z-30 bg-black/60 md:hidden"
|
||||
class="nim-mobile-overlay fixed inset-0 z-30 bg-black/50 md:hidden"
|
||||
x-show="$store.sidebar?.mobileOpen"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0"
|
||||
|
|
|
|||
Loading…
Reference in New Issue