Skip to content

Instantly share code, notes, and snippets.

@zenkiet
Last active December 9, 2024 04:13
Show Gist options
  • Save zenkiet/9ebf03af0fc6cae1ef6f255cd49e363f to your computer and use it in GitHub Desktop.
Save zenkiet/9ebf03af0fc6cae1ef6f255cd49e363f to your computer and use it in GitHub Desktop.
Implement theme color support change primary and secondary schema color in Angular

Implement theme color support change primary and secondary schema color in Angular

//... imported library
import { provideThemeColors } from '@core/theme';
/** Application configuration */
export const appConfig: ApplicationConfig = {
providers: [
//...
provideThemeColors(),
//...
]
};
/** @type {import('tailwindcss').Config} */
module.exports = {
theme: {
//...
extend: {
colors: {
primary: 'var(--primary)',
secondary: 'var(--secondary)',
},
//...
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,
];
}
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,
};
}
}
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