Last active
December 9, 2024 04:13
-
-
Save zenkiet/9ebf03af0fc6cae1ef6f255cd49e363f to your computer and use it in GitHub Desktop.
Implement theme color support change primary and secondary schema color in Angular
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//... imported library | |
import { provideThemeColors } from '@core/theme'; | |
/** Application configuration */ | |
export const appConfig: ApplicationConfig = { | |
providers: [ | |
//... | |
provideThemeColors(), | |
//... | |
] | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** @type {import('tailwindcss').Config} */ | |
module.exports = { | |
theme: { | |
//... | |
extend: { | |
colors: { | |
primary: 'var(--primary)', | |
secondary: 'var(--secondary)', | |
}, | |
//... |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { APP_INITIALIZER, Provider } from '@angular/core'; | |
import { ThemeColor } from '@shared/types'; | |
import { ThemeColorService } from './theme-color.service'; | |
export function provideThemeColors(config?: Partial<ThemeColor>): Provider[] { | |
return [ | |
{ | |
provide: APP_INITIALIZER, | |
useFactory: (themeColorService: ThemeColorService) => { | |
return () => { | |
if (config) { | |
themeColorService.updateThemeColors(config); | |
} | |
return Promise.resolve(); | |
}; | |
}, | |
deps: [ThemeColorService], | |
multi: true, | |
}, | |
ThemeColorService, | |
]; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { DOCUMENT } from '@angular/common'; | |
import { DestroyRef, effect, inject, Injectable, signal } from '@angular/core'; | |
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; | |
import { STORAGE_KEY } from '@configs/storage.config'; | |
import { ThemeColor } from '@shared/types'; | |
import chroma from 'chroma-js'; | |
import { distinctUntilChanged, filter, map, tap } from 'rxjs'; | |
import { PreferencesService } from '../../services/native/preferences.service'; | |
import { PosService } from '../../services/pos.service'; | |
@Injectable({ providedIn: 'root' }) | |
export class ThemeColorService { | |
private readonly _destroyRef = inject(DestroyRef); | |
private readonly _preferences = inject(PreferencesService); | |
private readonly _document = inject(DOCUMENT); | |
private readonly _posConfig$ = inject(PosService).posConfig$; | |
private readonly _currentThemeColor = signal<ThemeColor>({ primary: '', secondary: '' }); | |
readonly currentThemeColor = this._currentThemeColor.asReadonly(); | |
constructor() { | |
this._init(); | |
this._posConfig$ | |
.pipe( | |
filter(Boolean), | |
map((res) => ({ primary: res.primaryColor, secondary: res.secondaryColor })), | |
distinctUntilChanged((prev, curr) => JSON.stringify(prev) === JSON.stringify(curr)), | |
tap((colors) => this.updateThemeColors(colors)), | |
takeUntilDestroyed(this._destroyRef) | |
) | |
.subscribe(); | |
effect(() => this._applyThemeColors(this._currentThemeColor()), { allowSignalWrites: false }); | |
} | |
updateThemeColors(colors: Partial<ThemeColor>): void { | |
const newColors = { ...this._currentThemeColor(), ...colors }; | |
if (this._isValidColors(newColors)) { | |
this._preferences.set(STORAGE_KEY.THEME_COLOR, JSON.stringify(newColors)); | |
this._currentThemeColor.set(newColors); | |
} | |
} | |
private async _init(): Promise<void> { | |
const savedThemeColor: ThemeColor | null = await this._preferences.get(STORAGE_KEY.THEME_COLOR); | |
if (savedThemeColor) { | |
this._currentThemeColor.set(savedThemeColor); | |
} | |
} | |
private _isValidColors(colors: ThemeColor): boolean { | |
return chroma.valid(colors.primary) && chroma.valid(colors.secondary); | |
} | |
private _applyThemeColors(colors: ThemeColor): void { | |
const primaryVariants = this._generateColorVariants(colors.primary); | |
const secondaryVariants = this._generateColorVariants(colors.secondary); | |
const style = this._document.documentElement.style; | |
// Set primary color variants | |
for (const [variant, value] of Object.entries(primaryVariants)) { | |
style.setProperty(`--primary-${variant}`, value); | |
} | |
// Set secondary color variants | |
for (const [variant, value] of Object.entries(secondaryVariants)) { | |
style.setProperty(`--secondary-${variant}`, value); | |
} | |
// Set base primary and secondary colors | |
style.setProperty('--primary', colors.primary); | |
style.setProperty('--secondary', colors.secondary); | |
style.setProperty('--primary-rgb', chroma(colors.primary).rgb().join(', ')); | |
style.setProperty('--secondary-rgb', chroma(colors.secondary).rgb().join(', ')); | |
} | |
private _generateColorVariants(baseColor: string): Record<string, string> { | |
const color = chroma(baseColor); | |
const generateVariant = ( | |
luminance: number, | |
darkenFactor?: number | |
): { hex: string; rgb: string } => { | |
const processedColor = darkenFactor ? color.darken(darkenFactor) : color.luminance(luminance); | |
return { | |
hex: processedColor.hex(), | |
rgb: processedColor.rgb().join(', '), | |
}; | |
}; | |
return { | |
50: generateVariant(0.8).hex, | |
'50-rgb': generateVariant(0.8).rgb, | |
100: generateVariant(0.9).hex, | |
'100-rgb': generateVariant(0.9).rgb, | |
200: generateVariant(0.8).hex, | |
'200-rgb': generateVariant(0.8).rgb, | |
300: generateVariant(0.7).hex, | |
'300-rgb': generateVariant(0.7).rgb, | |
400: generateVariant(0.6).hex, | |
'400-rgb': generateVariant(0.6).rgb, | |
500: baseColor, | |
'500-rgb': color.rgb().join(', '), | |
600: generateVariant(0, 0.5).hex, | |
'600-rgb': generateVariant(0, 0.5).rgb, | |
700: generateVariant(0, 1).hex, | |
'700-rgb': generateVariant(0, 1).rgb, | |
800: generateVariant(0, 1.5).hex, | |
'800-rgb': generateVariant(0, 1.5).rgb, | |
900: generateVariant(0, 2).hex, | |
'900-rgb': generateVariant(0, 2).rgb, | |
}; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export interface ThemeColor { | |
primary: string; | |
secondary: string; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment