feat: wire up dark mode selector with a real dark palette

theme.mode wrote data-theme-mode to the DOM but no CSS ever reacted to
it, and mapThemeConfigToCssVariables() never looked at mode at all -
selecting Dark had zero visible effect.

Added dark-mode neutral overrides (background/text/border axis only -
brand colors stay as configured) using the palette provided by the
user (colorhunt.co/palette/091413285a48408a71b0e4cc):
  --bg-primary: #091413   --bg-secondary: #285a48
  --text-primary: #b0e4cc --text-secondary: #408a71
  --border-color: #285a48

ThemeEngineService now resolves 'system' mode via
prefers-color-scheme and re-renders live on OS theme changes, and
sets data-theme-mode to the *effective* resolved mode instead of the
raw setting.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sdarbinyan
2026-08-13 09:35:09 +04:00
parent 0b08802996
commit b04e3a67f5
2 changed files with 54 additions and 4 deletions

View File

@@ -10,6 +10,9 @@ export class ThemeEngineService {
private readonly document = inject(DOCUMENT);
private readonly configService = inject(ConfigService);
private systemDarkQuery?: MediaQueryList;
private systemDarkListener?: (event: MediaQueryListEvent) => void;
initialize(): void {
this.configService.loadBootstrap().pipe(take(1)).subscribe({
next: (bootstrap) => this.applyTheme(bootstrap.theme),
@@ -20,7 +23,20 @@ export class ThemeEngineService {
}
applyTheme(theme: ThemeConfig): void {
const variables = mapThemeConfigToCssVariables(theme);
this.teardownSystemModeListener();
const effectiveMode = this.resolveEffectiveMode(theme.mode);
this.render(theme, effectiveMode);
if (theme.mode === 'system' && typeof window !== 'undefined' && window.matchMedia) {
this.systemDarkQuery = window.matchMedia('(prefers-color-scheme: dark)');
this.systemDarkListener = () => this.render(theme, this.resolveEffectiveMode('system'));
this.systemDarkQuery.addEventListener('change', this.systemDarkListener);
}
}
private render(theme: ThemeConfig, effectiveMode: 'light' | 'dark'): void {
const variables = mapThemeConfigToCssVariables(theme, effectiveMode);
const root = this.document.documentElement;
for (const [name, value] of Object.entries(variables)) {
@@ -28,7 +44,27 @@ export class ThemeEngineService {
}
root.setAttribute('data-theme-id', theme.themeId);
root.setAttribute('data-theme-mode', theme.mode);
root.setAttribute('data-theme-mode', effectiveMode);
root.setAttribute('data-icon-set', theme.iconSet);
}
private resolveEffectiveMode(mode: ThemeConfig['mode']): 'light' | 'dark' {
if (mode === 'dark') {
return 'dark';
}
if (mode === 'light') {
return 'light';
}
return typeof window !== 'undefined' && window.matchMedia?.('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
}
private teardownSystemModeListener(): void {
if (this.systemDarkQuery && this.systemDarkListener) {
this.systemDarkQuery.removeEventListener('change', this.systemDarkListener);
}
this.systemDarkQuery = undefined;
this.systemDarkListener = undefined;
}
}