fix
This commit is contained in:
98
libs/ui/src/lib/util/theme/theme.service.ts
Normal file
98
libs/ui/src/lib/util/theme/theme.service.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
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<ThemeVariant, string> = {
|
||||
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__<tema>` en `<body>` — los overlays de Material (diálogo,
|
||||
* snackbar) cuelgan fuera de `<app-root>` y también necesitan heredarlo — y
|
||||
* `dark-mode` en `<html>`.
|
||||
*
|
||||
* `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<ThemeVariant>(this.readStoredTheme());
|
||||
readonly darkMode = signal<boolean>(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)
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user