import { Injectable, effect, signal } from '@angular/core'; /** * Shell-level layout state shared by the header and the sidebar. * * Currently a single piece of state: whether the sidebar is collapsed * to its icon-only rail. The collapsed state is persisted in * `localStorage` so it survives reloads without a backend round-trip. * * Lives outside of `` so that `` can also * read it — that's what lets the header's logo zone size itself to * match the sidebar width, and stay perfectly aligned with the rail * below it. As more cross-cutting shell state lands (density, theme, * panel pinning, …), it joins here rather than spreading across * sibling components. */ const STORAGE_KEY = 'portal-shell:sidebar-collapsed'; @Injectable({ providedIn: 'root' }) export class LayoutStateService { private readonly _sidebarCollapsed = signal(readPersistedCollapsed()); readonly sidebarCollapsed = this._sidebarCollapsed.asReadonly(); constructor() { effect(() => { const value = this._sidebarCollapsed(); try { localStorage.setItem(STORAGE_KEY, value ? '1' : '0'); } catch { // localStorage can throw in private-mode contexts; the UI // still works, the preference just doesn't persist. } }); } toggleSidebar(): void { this._sidebarCollapsed.update((v) => !v); } setSidebarCollapsed(value: boolean): void { this._sidebarCollapsed.set(value); } } function readPersistedCollapsed(): boolean { try { return localStorage.getItem(STORAGE_KEY) === '1'; } catch { return false; } }