fix
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
justify-content: space-between;
|
||||
gap: var(--pi-spacing-medium);
|
||||
padding: var(--pi-spacing-large) var(--pi-spacing-x-large);
|
||||
border-bottom: 0.0625rem solid var(--pi-color-grey-100);
|
||||
border-bottom: 0.0625rem solid var(--pi-theme-overlay-divider-color, var(--pi-color-grey-100));
|
||||
}
|
||||
|
||||
main {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
height: 0.25rem;
|
||||
margin: var(--pi-spacing-small) auto 0;
|
||||
border-radius: var(--pi-border-radius-x-small);
|
||||
background-color: var(--pi-color-grey-100);
|
||||
background-color: var(--pi-theme-hub-grabber-color, var(--pi-color-grey-100));
|
||||
}
|
||||
|
||||
header {
|
||||
@@ -19,7 +19,7 @@
|
||||
justify-content: space-between;
|
||||
gap: var(--pi-spacing-medium);
|
||||
padding: var(--pi-spacing-medium) var(--pi-spacing-x-large) var(--pi-spacing-large);
|
||||
border-bottom: 0.0625rem solid var(--pi-color-grey-100);
|
||||
border-bottom: 0.0625rem solid var(--pi-theme-overlay-divider-color, var(--pi-color-grey-100));
|
||||
}
|
||||
|
||||
main {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export const datoCms = 'https://graphql.datocms.com/'; // our GraphQL API
|
||||
@@ -1 +0,0 @@
|
||||
export * from './util-dato-cms.module';
|
||||
@@ -1,36 +0,0 @@
|
||||
import { EnvironmentProviders, makeEnvironmentProviders } from '@angular/core';
|
||||
|
||||
// Apollo
|
||||
import { InMemoryCache } from '@apollo/client/core';
|
||||
import { APOLLO_OPTIONS } from 'apollo-angular';
|
||||
import { HttpLink } from 'apollo-angular/http';
|
||||
|
||||
// Shared
|
||||
import { environment } from '../environments';
|
||||
|
||||
// datoCMS
|
||||
import { datoCms } from './graphql.module';
|
||||
|
||||
/**
|
||||
* Sustituye a `PiSharedUtilDatoCmsModule`: apollo-angular ya no expone
|
||||
* `ApolloModule`, la configuración se hace con providers.
|
||||
*/
|
||||
export function providePiDatoCms(): EnvironmentProviders {
|
||||
return makeEnvironmentProviders([
|
||||
{
|
||||
provide: APOLLO_OPTIONS,
|
||||
useFactory: (httpLink: HttpLink) => {
|
||||
return {
|
||||
cache: new InMemoryCache(),
|
||||
link: httpLink.create({
|
||||
uri: datoCms,
|
||||
headers: {
|
||||
Authorization: environment.DATO_KEY,
|
||||
} as any,
|
||||
}),
|
||||
};
|
||||
},
|
||||
deps: [HttpLink],
|
||||
},
|
||||
]);
|
||||
}
|
||||
1
libs/ui/src/lib/util/theme/index.ts
Normal file
1
libs/ui/src/lib/util/theme/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './theme.service';
|
||||
60
libs/ui/src/lib/util/theme/theme.service.spec.ts
Normal file
60
libs/ui/src/lib/util/theme/theme.service.spec.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PiThemeService } from './theme.service';
|
||||
|
||||
describe('PiThemeService', () => {
|
||||
afterEach(() => {
|
||||
localStorage.clear();
|
||||
document.body.className = '';
|
||||
document.documentElement.classList.remove('dark-mode');
|
||||
});
|
||||
|
||||
it('applies `pi-shell pi-shell__default` to <body> by default', () => {
|
||||
TestBed.inject(PiThemeService);
|
||||
TestBed.flushEffects();
|
||||
|
||||
expect(document.body.className).toBe('pi-shell pi-shell__default');
|
||||
});
|
||||
|
||||
it('updates <body> when the theme changes', () => {
|
||||
const service = TestBed.inject(PiThemeService);
|
||||
|
||||
service.setTheme('alternative');
|
||||
TestBed.flushEffects();
|
||||
|
||||
expect(document.body.className).toBe('pi-shell pi-shell__alternative');
|
||||
});
|
||||
|
||||
it('toggles `dark-mode` on <html>', () => {
|
||||
const service = TestBed.inject(PiThemeService);
|
||||
|
||||
service.setDarkMode(true);
|
||||
TestBed.flushEffects();
|
||||
expect(document.documentElement.classList.contains('dark-mode')).toBe(true);
|
||||
|
||||
service.toggleDarkMode();
|
||||
TestBed.flushEffects();
|
||||
expect(document.documentElement.classList.contains('dark-mode')).toBe(false);
|
||||
});
|
||||
|
||||
it('persists the theme and dark mode across a fresh instance (reload)', () => {
|
||||
const service = TestBed.inject(PiThemeService);
|
||||
service.setTheme('minimal');
|
||||
service.setDarkMode(true);
|
||||
TestBed.flushEffects();
|
||||
|
||||
TestBed.resetTestingModule();
|
||||
const reloaded = TestBed.inject(PiThemeService);
|
||||
|
||||
expect(reloaded.theme()).toBe('minimal');
|
||||
expect(reloaded.darkMode()).toBe(true);
|
||||
});
|
||||
|
||||
it('falls back to the default theme when nothing valid is stored', () => {
|
||||
localStorage.setItem('pi-theme', JSON.stringify('not-a-real-theme'));
|
||||
|
||||
const service = TestBed.inject(PiThemeService);
|
||||
|
||||
expect(service.theme()).toBe('default');
|
||||
});
|
||||
});
|
||||
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