// ============================================================================ // OpenClaw Control Center — Theme Service // ============================================================================ // Angular service providing programmatic access to design tokens, theme // mode switching (dark/light), and runtime CSS custom property manipulation. // // Usage: // constructor(private theme: CcThemeService) {} // // // Read a token // const primary = this.theme.getToken('--cc-color-primary'); // // // Set a token at runtime // this.theme.setToken('--cc-color-primary', '#00ff00'); // // // Toggle theme // this.theme.setMode('light'); // ============================================================================ import { Injectable, signal, computed, effect } from '@angular/core'; import { CcCssProps, getStatusColor, setCssToken, getCssToken } from './tokens'; // --------------------------------------------------------------------------- // Theme Mode Types // --------------------------------------------------------------------------- export type ThemeMode = 'dark' | 'light'; // --------------------------------------------------------------------------- // Light theme overrides (future use) // --------------------------------------------------------------------------- const LIGHT_THEME_OVERRIDES: Record = { // Surface tokens '--cc-surface-darkest': '#F8FAFC', '--cc-surface-dark': '#FFFFFF', '--cc-surface-medium': '#F1F5F9', '--cc-surface-light': '#E2E8F0', '--cc-surface-lighter': '#CBD5E1', // On-surface tokens '--cc-on-surface': '#0F172A', '--cc-on-surface-variant': '#475569', '--cc-on-surface-muted': '#94A3B8', // Border '--cc-surface-lighter-alt': '#E2E8F0', // M3 system overrides for light '--mat-sys-surface': '#FFFFFF', '--mat-sys-surface-container': '#F1F5F9', '--mat-sys-surface-container-high': '#E2E8F0', '--mat-sys-on-surface': '#0F172A', '--mat-sys-on-surface-variant': '#475569', '--mat-sys-outline': '#CBD5E1', '--mat-sys-background': '#F8FAFC', }; // --------------------------------------------------------------------------- // Dark theme (matches the SCSS defaults) // --------------------------------------------------------------------------- const DARK_THEME_OVERRIDES: Record = { '--cc-surface-darkest': '#0D0F12', '--cc-surface-dark': '#13161A', '--cc-surface-medium': '#1C2027', '--cc-surface-light': '#252B33', '--cc-surface-lighter': '#2D3748', '--cc-on-surface': '#E2E8F0', '--cc-on-surface-variant': '#8A9BB0', '--cc-on-surface-muted': '#64748B', '--mat-sys-surface': '#13161A', '--mat-sys-surface-container': '#1C2027', '--mat-sys-surface-container-high': '#252B33', '--mat-sys-on-surface': '#E2E8F0', '--mat-sys-on-surface-variant': '#8A9BB0', '--mat-sys-outline': '#2D3748', '--mat-sys-background': '#0D0F12', }; @Injectable({ providedIn: 'root' }) export class CcThemeService { // --------------------------------------------------------------------------- // Signals for reactive theme state // --------------------------------------------------------------------------- private readonly _mode = signal( (localStorage.getItem('cc-theme') as ThemeMode) ?? 'dark' ); /** Current theme mode */ readonly mode = this._mode.asReadonly(); /** Computed: is the current mode dark? */ readonly isDark = computed(() => this._mode() === 'dark'); /** Computed: is the current mode light? */ readonly isLight = computed(() => this._mode() === 'light'); constructor() { // Apply theme on init and whenever mode changes effect(() => { this.applyTheme(this._mode()); }); } // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- /** Set the theme mode and persist to localStorage */ setMode(mode: ThemeMode): void { this._mode.set(mode); localStorage.setItem('cc-theme', mode); } /** Toggle between dark and light mode */ toggle(): void { this.setMode(this._mode() === 'dark' ? 'light' : 'dark'); } /** Read a CSS custom property from the document root */ getToken(property: string): string { return getCssToken(property); } /** Set a CSS custom property on the document root */ setToken(property: string, value: string): void { setCssToken(property, value); } /** Get status color set by agent status */ getStatusColors(status: string): { fg: string; bg: string; border: string } { return getStatusColor(status); } // --------------------------------------------------------------------------- // Internal // --------------------------------------------------------------------------- /** Apply a theme mode by setting all CSS custom properties */ private applyTheme(mode: ThemeMode): void { const overrides = mode === 'dark' ? DARK_THEME_OVERRIDES : LIGHT_THEME_OVERRIDES; // Set color-scheme for native form controls document.documentElement.style.setProperty('color-scheme', mode); // Apply all overrides for (const [prop, value] of Object.entries(overrides)) { document.documentElement.style.setProperty(prop, value); } } }