import { DOCUMENT, isPlatformBrowser } from '@angular/common'; import { Injectable, PLATFORM_ID, computed, effect, inject, signal, } from '@angular/core'; import { ShellClass } from '../../styles'; import { LocalStorageService } from '../local-storage'; export type ThemeVariant = 'default' | 'alternative' | 'minimal' | 'gradient'; const THEME_STORAGE_KEY = 'pi-theme'; const DARK_MODE_STORAGE_KEY = 'pi-dark-mode'; const shellClassByVariant: Record = { default: ShellClass.Default, alternative: ShellClass.Alternative, minimal: ShellClass.Minimal, gradient: ShellClass.Gradient, }; /** * Centraliza lo que hasta ahora cada app reimplementaba a mano (ver el * `app.ts` del playground antes de esta extracción): fijar * `pi-shell pi-shell__` en `` — los overlays de Material (diálogo, * snackbar) cuelgan fuera de `` y también necesitan heredarlo — y * `dark-mode` en ``. * * `theme`/`darkMode` son señales normales: se pueden mutar tanto con * `setTheme()`/`toggleDarkMode()` como con un `[(checked)]`/`[(value)]` dos * vías directo sobre la señal. La aplicación de clases y la persistencia en * `localStorage` viven en `effect()`, no en los setters, para que cualquier * camino de escritura quede cubierto por igual. * * El modo oscuro en sí (qué tokens remapea `.dark-mode`) lo sigue definiendo * cada app consumidora — este servicio solo pone o quita la clase. */ @Injectable({ providedIn: 'root' }) export class PiThemeService { private readonly document = inject(DOCUMENT); private readonly platformId = inject(PLATFORM_ID); private readonly localStorage = inject(LocalStorageService); readonly theme = signal(this.readStoredTheme()); readonly darkMode = signal(this.readStoredDarkMode()); readonly shellClass = computed(() => shellClassByVariant[this.theme()]); constructor() { effect(() => { this.document.body.className = this.shellClass(); this.persist(THEME_STORAGE_KEY, this.theme()); }); effect(() => { this.document.documentElement.classList.toggle( 'dark-mode', this.darkMode(), ); this.persist(DARK_MODE_STORAGE_KEY, this.darkMode()); }); } setTheme(theme: ThemeVariant): void { this.theme.set(theme); } setDarkMode(darkMode: boolean): void { this.darkMode.set(darkMode); } toggleDarkMode(): void { this.darkMode.update((value) => !value); } private persist(key: string, value: unknown): void { if (isPlatformBrowser(this.platformId)) { this.localStorage.setItem(key, value); } } private readStoredTheme(): ThemeVariant { if (!isPlatformBrowser(this.platformId)) return 'default'; const stored = this.localStorage.getItem(THEME_STORAGE_KEY); return stored && stored in shellClassByVariant ? stored : 'default'; } private readStoredDarkMode(): boolean { return ( isPlatformBrowser(this.platformId) && !!this.localStorage.getItem(DARK_MODE_STORAGE_KEY) ); } }