ps-helix 4.1.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/THEME.md +159 -156
- package/fesm2022/ps-helix.mjs +566 -325
- package/fesm2022/ps-helix.mjs.map +1 -1
- package/package.json +1 -1
- package/src/lib/styles/themes/dark.css +16 -16
- package/src/lib/styles/themes/light.css +16 -16
- package/src/lib/styles/tokens/spacing.tokens.css +4 -0
- package/types/ps-helix.d.ts +142 -24
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A comprehensive Angular component library built with Angular 21+ featuring modern design patterns, accessibility-first development, and optimal developer experience.
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/ps-helix)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
[](https://angular.dev/)
|
|
8
8
|
[](https://www.typescriptlang.org/)
|
|
@@ -106,7 +106,7 @@ After installation, verify that ps-helix is in your `package.json`:
|
|
|
106
106
|
```json
|
|
107
107
|
{
|
|
108
108
|
"dependencies": {
|
|
109
|
-
"ps-helix": "^
|
|
109
|
+
"ps-helix": "^5.0.0"
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
```
|
|
@@ -1182,7 +1182,7 @@ Copyright (c) 2025 PACK Solutions
|
|
|
1182
1182
|
|
|
1183
1183
|
---
|
|
1184
1184
|
|
|
1185
|
-
**Version**:
|
|
1185
|
+
**Version**: 5.0.0
|
|
1186
1186
|
**Built with**: Angular 21.0.3, TypeScript 5.9.0, Phosphor Icons 2.0.3
|
|
1187
1187
|
**Author**: Fabrice PEREZ | Product Designer at PACK Solutions
|
|
1188
1188
|
**Last Updated**: January 2026
|
package/THEME.md
CHANGED
|
@@ -8,19 +8,21 @@ This guide explains how to customize the colors and theme of the Helix Design Sy
|
|
|
8
8
|
- [Quick Start](#quick-start)
|
|
9
9
|
- [Theme Service](#theme-service)
|
|
10
10
|
- [Custom Colors with Injection Token](#custom-colors-with-injection-token)
|
|
11
|
+
- [Accessibility Safeguard (WCAG)](#accessibility-safeguard-wcag)
|
|
11
12
|
- [Available CSS Variables](#available-css-variables)
|
|
12
13
|
- [Dynamic Color Changes](#dynamic-color-changes)
|
|
13
14
|
- [Examples](#examples)
|
|
14
15
|
|
|
15
16
|
## Overview
|
|
16
17
|
|
|
17
|
-
The Helix Design System uses a
|
|
18
|
+
The Helix Design System uses a signal-based theming system that supports:
|
|
18
19
|
|
|
19
20
|
- Light and dark themes
|
|
20
|
-
- Custom primary and secondary colors
|
|
21
|
-
- Automatic
|
|
22
|
-
-
|
|
21
|
+
- Custom primary and secondary brand colors (customer context)
|
|
22
|
+
- Automatic variant generation in OKLCH (hue and chroma preserved, lightness adjusted per mode)
|
|
23
|
+
- WCAG contrast safeguard: a brand color that does not meet the target contrast ratio against the current theme background is silently adjusted before being applied to UI components
|
|
23
24
|
- Dynamic theme switching at runtime
|
|
25
|
+
- Theme preference persistence in `localStorage` (key: `helix-theme-preference`)
|
|
24
26
|
|
|
25
27
|
## Quick Start
|
|
26
28
|
|
|
@@ -34,7 +36,6 @@ import { ThemeService } from 'ps-helix';
|
|
|
34
36
|
|
|
35
37
|
@Component({
|
|
36
38
|
selector: 'app-root',
|
|
37
|
-
standalone: true,
|
|
38
39
|
template: `
|
|
39
40
|
<button (click)="toggleTheme()">
|
|
40
41
|
Current theme: {{ themeService.themeName() }}
|
|
@@ -58,59 +59,67 @@ The `ThemeService` provides the following API:
|
|
|
58
59
|
|
|
59
60
|
- `setDarkTheme(isDark: boolean)` - Set the theme to dark or light
|
|
60
61
|
- `toggleTheme()` - Toggle between light and dark themes
|
|
61
|
-
- `updateTheme(name: Theme)` - Update theme by name ('light' or 'dark')
|
|
62
|
-
- `
|
|
62
|
+
- `updateTheme(name: Theme)` - Update theme by name (`'light'` or `'dark'`) and persist it in localStorage
|
|
63
|
+
- `applyCustomerTheme()` - Re-apply brand colors from the customer context (usually called automatically after theme changes or when the context updates)
|
|
63
64
|
|
|
64
65
|
### Computed Signals
|
|
65
66
|
|
|
66
|
-
- `themeName()` - Returns current theme name ('light' or 'dark')
|
|
67
|
-
- `isDarkTheme()` - Returns boolean indicating
|
|
68
|
-
- `themeInfo()` - Returns complete theme information including last change date
|
|
67
|
+
- `themeName()` - Returns current theme name (`'light'` or `'dark'`)
|
|
68
|
+
- `isDarkTheme()` - Returns a boolean indicating whether dark theme is active
|
|
69
|
+
- `themeInfo()` - Returns complete theme information including the last change date
|
|
69
70
|
|
|
70
|
-
|
|
71
|
+
### Types
|
|
71
72
|
|
|
72
|
-
|
|
73
|
+
```typescript
|
|
74
|
+
export type Theme = 'light' | 'dark';
|
|
75
|
+
|
|
76
|
+
export interface ThemeConfig {
|
|
77
|
+
isDark: boolean;
|
|
78
|
+
name: Theme;
|
|
79
|
+
customerTheme?: {
|
|
80
|
+
primaryColor: string;
|
|
81
|
+
secondaryColor?: string;
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
```
|
|
73
85
|
|
|
74
|
-
|
|
75
|
-
- **Secondary Color**: `#7B3AEC` (Purple)
|
|
86
|
+
## Default Colors
|
|
76
87
|
|
|
77
|
-
|
|
88
|
+
If you don't provide custom colors, the design system falls back to the CSS defaults defined in the theme files:
|
|
78
89
|
|
|
79
|
-
|
|
90
|
+
- Light mode primary: `#0B0191`
|
|
91
|
+
- Dark mode primary: `#8178F7`
|
|
92
|
+
- Light mode secondary: `#5E5E5E`
|
|
93
|
+
- Dark mode secondary: `#5B5A5A`
|
|
80
94
|
|
|
81
|
-
|
|
95
|
+
These defaults are defined in `projects/ps-helix/src/lib/styles/themes/light.css` and `dark.css`.
|
|
82
96
|
|
|
83
|
-
|
|
97
|
+
## Custom Colors with Injection Token
|
|
84
98
|
|
|
85
|
-
|
|
99
|
+
To customize the primary and secondary colors of your design system to match your brand, provide a service that implements `CustomerContextService` through the `CUSTOMER_CONTEXT_SERVICE` injection token.
|
|
86
100
|
|
|
87
|
-
|
|
88
|
-
2. Text colors are automatically determined based on background luminance for accessibility
|
|
89
|
-
3. Changes can be reactive and propagate through the application
|
|
90
|
-
4. It provides type safety and better Angular integration
|
|
101
|
+
### Why an Injection Token and not just CSS variables?
|
|
91
102
|
|
|
92
|
-
|
|
103
|
+
1. Variants (light, lighter, dark, darker) are computed in OKLCH at runtime and depend on the current theme (light vs dark).
|
|
104
|
+
2. Text color on top of brand colors is computed from real WCAG contrast ratios.
|
|
105
|
+
3. Brand colors are verified against a minimum contrast ratio and silently adjusted for UI usage when needed; the original color is still exposed as `-source` for decorative contexts.
|
|
106
|
+
4. The token keeps everything type-safe and reactive via Angular DI.
|
|
93
107
|
|
|
94
|
-
|
|
108
|
+
### Step 1: Create a Customer Context Service
|
|
95
109
|
|
|
96
110
|
```typescript
|
|
97
111
|
// src/app/services/app-theme-context.service.ts
|
|
98
112
|
import { Injectable, signal } from '@angular/core';
|
|
99
|
-
import {
|
|
113
|
+
import { CustomerContextService } from 'ps-helix';
|
|
100
114
|
|
|
101
|
-
@Injectable({
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// Define your custom colors using signals for reactivity
|
|
106
|
-
private primaryColorSignal = signal('#FF0000'); // Your primary color
|
|
107
|
-
private secondaryColorSignal = signal('#00AA00'); // Your secondary color
|
|
115
|
+
@Injectable({ providedIn: 'root' })
|
|
116
|
+
export class AppThemeContextService implements CustomerContextService {
|
|
117
|
+
private primaryColorSignal = signal('#FF0000');
|
|
118
|
+
private secondaryColorSignal = signal('#00AA00');
|
|
108
119
|
|
|
109
|
-
// Expose as readonly signals - required by InsurerContextService interface
|
|
110
120
|
primaryColor = this.primaryColorSignal.asReadonly();
|
|
111
121
|
secondaryColor = this.secondaryColorSignal.asReadonly();
|
|
112
122
|
|
|
113
|
-
// Optional: Methods to change colors dynamically
|
|
114
123
|
setPrimaryColor(color: string) {
|
|
115
124
|
this.primaryColorSignal.set(color);
|
|
116
125
|
}
|
|
@@ -121,121 +130,143 @@ export class AppThemeContextService implements InsurerContextService {
|
|
|
121
130
|
}
|
|
122
131
|
```
|
|
123
132
|
|
|
124
|
-
The `
|
|
125
|
-
- `primaryColor(): string` - Returns the primary brand color
|
|
126
|
-
- `secondaryColor(): string` - Returns the secondary brand color
|
|
133
|
+
The `CustomerContextService` interface requires two methods:
|
|
127
134
|
|
|
128
|
-
|
|
135
|
+
- `primaryColor(): string` - Returns the primary brand color (hex)
|
|
136
|
+
- `secondaryColor(): string` - Returns the secondary brand color (hex)
|
|
129
137
|
|
|
130
|
-
|
|
138
|
+
### Step 2: Provide the Service with the Injection Token
|
|
131
139
|
|
|
132
140
|
```typescript
|
|
133
141
|
// src/app/app.config.ts
|
|
134
142
|
import { ApplicationConfig } from '@angular/core';
|
|
135
143
|
import { provideRouter } from '@angular/router';
|
|
136
|
-
import {
|
|
144
|
+
import { CUSTOMER_CONTEXT_SERVICE } from 'ps-helix';
|
|
137
145
|
import { AppThemeContextService } from './services/app-theme-context.service';
|
|
138
146
|
import { routes } from './app.routes';
|
|
139
147
|
|
|
140
148
|
export const appConfig: ApplicationConfig = {
|
|
141
149
|
providers: [
|
|
142
150
|
provideRouter(routes),
|
|
143
|
-
// Connect your service to the design system's injection token
|
|
144
151
|
{
|
|
145
|
-
provide:
|
|
152
|
+
provide: CUSTOMER_CONTEXT_SERVICE,
|
|
146
153
|
useExisting: AppThemeContextService
|
|
147
154
|
}
|
|
148
155
|
]
|
|
149
156
|
};
|
|
150
157
|
```
|
|
151
158
|
|
|
152
|
-
### Step 3:
|
|
153
|
-
|
|
154
|
-
In your root component, you can inject the `ThemeService` to ensure the theme is applied:
|
|
159
|
+
### Step 3: Re-apply the Theme (Optional)
|
|
155
160
|
|
|
156
161
|
```typescript
|
|
157
|
-
// src/app/app.component.ts
|
|
158
162
|
import { Component, inject, OnInit } from '@angular/core';
|
|
159
163
|
import { ThemeService } from 'ps-helix';
|
|
160
164
|
|
|
161
165
|
@Component({
|
|
162
166
|
selector: 'app-root',
|
|
163
|
-
standalone: true,
|
|
164
167
|
template: `<router-outlet />`
|
|
165
168
|
})
|
|
166
169
|
export class AppComponent implements OnInit {
|
|
167
170
|
private themeService = inject(ThemeService);
|
|
168
171
|
|
|
169
172
|
ngOnInit() {
|
|
170
|
-
|
|
171
|
-
this.themeService.applyInsurerTheme();
|
|
173
|
+
this.themeService.applyCustomerTheme();
|
|
172
174
|
}
|
|
173
175
|
}
|
|
174
176
|
```
|
|
175
177
|
|
|
176
178
|
### Alternative: Simple Non-Reactive Service
|
|
177
179
|
|
|
178
|
-
If you don't need dynamic color changes, you can use a simpler implementation without signals:
|
|
179
|
-
|
|
180
180
|
```typescript
|
|
181
181
|
import { Injectable } from '@angular/core';
|
|
182
|
-
import {
|
|
183
|
-
|
|
184
|
-
@Injectable({
|
|
185
|
-
providedIn: 'root'
|
|
186
|
-
})
|
|
187
|
-
export class AppThemeContextService implements InsurerContextService {
|
|
188
|
-
primaryColor() {
|
|
189
|
-
return '#FF0000';
|
|
190
|
-
}
|
|
182
|
+
import { CustomerContextService } from 'ps-helix';
|
|
191
183
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
184
|
+
@Injectable({ providedIn: 'root' })
|
|
185
|
+
export class AppThemeContextService implements CustomerContextService {
|
|
186
|
+
primaryColor() { return '#FF0000'; }
|
|
187
|
+
secondaryColor() { return '#00AA00'; }
|
|
195
188
|
}
|
|
196
189
|
```
|
|
197
190
|
|
|
198
|
-
|
|
191
|
+
## Accessibility Safeguard (WCAG)
|
|
192
|
+
|
|
193
|
+
When `applyCustomerTheme()` runs, each brand color is evaluated against the current theme background:
|
|
194
|
+
|
|
195
|
+
- Contrast is computed using the true WCAG 2.1 relative luminance formula.
|
|
196
|
+
- If the ratio is below the target (AA by default, AAA if configured), the color is adjusted in OKLCH: the luminance is shifted (darker on light backgrounds, lighter on dark backgrounds) while hue and chroma are preserved as much as possible.
|
|
197
|
+
- The adjusted color is written into `--customer-primary-color` / `--customer-secondary-color` and consumed by every UI component.
|
|
198
|
+
- The original, unmodified brand color is written into `--customer-primary-color-source` / `--customer-secondary-color-source`, available for decorative surfaces (logos, marketing images) where the contrast rule does not apply.
|
|
199
|
+
- The service never logs or warns: the adjustment is silent by design.
|
|
200
|
+
|
|
201
|
+
### Configuring the Target Ratio
|
|
202
|
+
|
|
203
|
+
Use the `PSH_THEME_OPTIONS` injection token to opt into AAA:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { PSH_THEME_OPTIONS, PshThemeOptions } from 'ps-helix';
|
|
207
|
+
|
|
208
|
+
export const appConfig: ApplicationConfig = {
|
|
209
|
+
providers: [
|
|
210
|
+
{
|
|
211
|
+
provide: PSH_THEME_OPTIONS,
|
|
212
|
+
useValue: { targetContrast: 'AAA' } satisfies PshThemeOptions
|
|
213
|
+
}
|
|
214
|
+
]
|
|
215
|
+
};
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Default is `'AA'` (ratio 4.5:1). `'AAA'` raises the requirement to 7:1.
|
|
219
|
+
|
|
220
|
+
### OKLCH Variant Derivation
|
|
221
|
+
|
|
222
|
+
The four tonal variants (`light`, `lighter`, `dark`, `darker`) are generated by shifting the OKLCH lightness of the accessible base color with mode-aware deltas:
|
|
223
|
+
|
|
224
|
+
| Mode | light | lighter | dark | darker |
|
|
225
|
+
|-------|-------|---------|-------|--------|
|
|
226
|
+
| Light | +0.08 | +0.18 | -0.08 | -0.16 |
|
|
227
|
+
| Dark | +0.06 | +0.14 | -0.06 | -0.14 |
|
|
228
|
+
|
|
229
|
+
Hue and chroma are preserved so the color palette stays perceptually consistent. Fixed percentage RGB lightening/darkening is no longer used.
|
|
199
230
|
|
|
200
231
|
## Available CSS Variables
|
|
201
232
|
|
|
202
|
-
Once configured,
|
|
233
|
+
Once configured, `ThemeService` writes the following CSS variables on `:root`:
|
|
203
234
|
|
|
204
235
|
### Primary Color Variables
|
|
205
236
|
|
|
206
|
-
- `--
|
|
207
|
-
- `--
|
|
208
|
-
- `--
|
|
209
|
-
- `--
|
|
210
|
-
- `--
|
|
211
|
-
- `--
|
|
212
|
-
- `--
|
|
237
|
+
- `--customer-primary-color` - Accessibility-adjusted primary color (used by components)
|
|
238
|
+
- `--customer-primary-color-source` - Original brand color, unmodified (for decorative use only)
|
|
239
|
+
- `--customer-primary-color-light` - OKLCH-derived lighter variant
|
|
240
|
+
- `--customer-primary-color-lighter` - OKLCH-derived extra light variant
|
|
241
|
+
- `--customer-primary-color-dark` - OKLCH-derived darker variant
|
|
242
|
+
- `--customer-primary-color-darker` - OKLCH-derived extra dark variant
|
|
243
|
+
- `--customer-primary-color-text` - Readable text color (black or white) picked via WCAG contrast against the adjusted primary
|
|
244
|
+
- `--customer-primary-color-rgb` - `r, g, b` triplet of the adjusted primary for `rgba()` usage
|
|
213
245
|
|
|
214
246
|
### Secondary Color Variables
|
|
215
247
|
|
|
216
|
-
- `--
|
|
217
|
-
- `--
|
|
218
|
-
- `--
|
|
219
|
-
- `--
|
|
220
|
-
- `--
|
|
221
|
-
- `--
|
|
222
|
-
- `--
|
|
248
|
+
- `--customer-secondary-color`
|
|
249
|
+
- `--customer-secondary-color-source`
|
|
250
|
+
- `--customer-secondary-color-light`
|
|
251
|
+
- `--customer-secondary-color-lighter`
|
|
252
|
+
- `--customer-secondary-color-dark`
|
|
253
|
+
- `--customer-secondary-color-darker`
|
|
254
|
+
- `--customer-secondary-color-text`
|
|
255
|
+
- `--customer-secondary-color-rgb`
|
|
223
256
|
|
|
224
257
|
### Component Usage
|
|
225
258
|
|
|
226
|
-
|
|
259
|
+
Design system components consume the stable abstract variables, which fall back to the defaults when no customer context is provided:
|
|
227
260
|
|
|
228
261
|
```css
|
|
229
|
-
/*
|
|
230
|
-
--primary-color: var(--
|
|
231
|
-
--primary-color-light: var(--
|
|
262
|
+
/* From projects/ps-helix/src/lib/styles/themes/light.css */
|
|
263
|
+
--primary-color: var(--customer-primary-color, #0B0191);
|
|
264
|
+
--primary-color-light: var(--customer-primary-color-light, #0F02C4);
|
|
232
265
|
```
|
|
233
266
|
|
|
234
|
-
If you provide a custom color through the injection token, it takes precedence. Otherwise, the default color is used.
|
|
235
|
-
|
|
236
267
|
### Using in Your Custom Styles
|
|
237
268
|
|
|
238
|
-
|
|
269
|
+
Use the abstract variables (`--primary-color`, `--secondary-color`, ...) in your own components so you automatically inherit the accessibility-adjusted palette:
|
|
239
270
|
|
|
240
271
|
```css
|
|
241
272
|
.my-custom-button {
|
|
@@ -248,9 +279,17 @@ You can also use these variables in your own components:
|
|
|
248
279
|
}
|
|
249
280
|
```
|
|
250
281
|
|
|
282
|
+
For decorative brand elements that must match the literal brand color (e.g. a logo background), use the `-source` variable:
|
|
283
|
+
|
|
284
|
+
```css
|
|
285
|
+
.brand-logo-surface {
|
|
286
|
+
background-color: var(--customer-primary-color-source);
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
251
290
|
## Dynamic Color Changes
|
|
252
291
|
|
|
253
|
-
Since the
|
|
292
|
+
Since the customer context service uses signals, you can change colors at runtime:
|
|
254
293
|
|
|
255
294
|
```typescript
|
|
256
295
|
import { Component, inject } from '@angular/core';
|
|
@@ -259,13 +298,10 @@ import { AppThemeContextService } from './services/app-theme-context.service';
|
|
|
259
298
|
|
|
260
299
|
@Component({
|
|
261
300
|
selector: 'app-theme-switcher',
|
|
262
|
-
standalone: true,
|
|
263
301
|
template: `
|
|
264
|
-
<
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
<button (click)="setGreenTheme()">Green Theme</button>
|
|
268
|
-
</div>
|
|
302
|
+
<button (click)="setRedTheme()">Red</button>
|
|
303
|
+
<button (click)="setBlueTheme()">Blue</button>
|
|
304
|
+
<button (click)="setGreenTheme()">Green</button>
|
|
269
305
|
`
|
|
270
306
|
})
|
|
271
307
|
export class ThemeSwitcherComponent {
|
|
@@ -275,19 +311,19 @@ export class ThemeSwitcherComponent {
|
|
|
275
311
|
setRedTheme() {
|
|
276
312
|
this.themeContext.setPrimaryColor('#DC2626');
|
|
277
313
|
this.themeContext.setSecondaryColor('#EF4444');
|
|
278
|
-
this.themeService.
|
|
314
|
+
this.themeService.applyCustomerTheme();
|
|
279
315
|
}
|
|
280
316
|
|
|
281
317
|
setBlueTheme() {
|
|
282
318
|
this.themeContext.setPrimaryColor('#2563EB');
|
|
283
319
|
this.themeContext.setSecondaryColor('#3B82F6');
|
|
284
|
-
this.themeService.
|
|
320
|
+
this.themeService.applyCustomerTheme();
|
|
285
321
|
}
|
|
286
322
|
|
|
287
323
|
setGreenTheme() {
|
|
288
324
|
this.themeContext.setPrimaryColor('#16A34A');
|
|
289
325
|
this.themeContext.setSecondaryColor('#22C55E');
|
|
290
|
-
this.themeService.
|
|
326
|
+
this.themeService.applyCustomerTheme();
|
|
291
327
|
}
|
|
292
328
|
}
|
|
293
329
|
```
|
|
@@ -296,15 +332,11 @@ export class ThemeSwitcherComponent {
|
|
|
296
332
|
|
|
297
333
|
### Example 1: Brand Colors from Configuration
|
|
298
334
|
|
|
299
|
-
Load colors from a configuration file or environment:
|
|
300
|
-
|
|
301
335
|
```typescript
|
|
302
336
|
import { Injectable, signal } from '@angular/core';
|
|
303
337
|
import { environment } from '../environments/environment';
|
|
304
338
|
|
|
305
|
-
@Injectable({
|
|
306
|
-
providedIn: 'root'
|
|
307
|
-
})
|
|
339
|
+
@Injectable({ providedIn: 'root' })
|
|
308
340
|
export class AppThemeContextService {
|
|
309
341
|
private primaryColorSignal = signal(environment.brandPrimaryColor);
|
|
310
342
|
private secondaryColorSignal = signal(environment.brandSecondaryColor);
|
|
@@ -316,51 +348,36 @@ export class AppThemeContextService {
|
|
|
316
348
|
|
|
317
349
|
### Example 2: User-Selected Theme
|
|
318
350
|
|
|
319
|
-
Allow users to customize their theme:
|
|
320
|
-
|
|
321
351
|
```typescript
|
|
322
352
|
import { Injectable, signal, effect } from '@angular/core';
|
|
323
353
|
|
|
324
|
-
@Injectable({
|
|
325
|
-
providedIn: 'root'
|
|
326
|
-
})
|
|
354
|
+
@Injectable({ providedIn: 'root' })
|
|
327
355
|
export class AppThemeContextService {
|
|
328
|
-
private primaryColorSignal = signal(this.loadFromStorage('primaryColor', '#
|
|
329
|
-
private secondaryColorSignal = signal(this.loadFromStorage('secondaryColor', '#
|
|
356
|
+
private primaryColorSignal = signal(this.loadFromStorage('primaryColor', '#0B0191'));
|
|
357
|
+
private secondaryColorSignal = signal(this.loadFromStorage('secondaryColor', '#5E5E5E'));
|
|
330
358
|
|
|
331
359
|
primaryColor = this.primaryColorSignal.asReadonly();
|
|
332
360
|
secondaryColor = this.secondaryColorSignal.asReadonly();
|
|
333
361
|
|
|
334
362
|
constructor() {
|
|
335
|
-
// Save to localStorage when colors change
|
|
336
363
|
effect(() => {
|
|
337
364
|
localStorage.setItem('primaryColor', this.primaryColorSignal());
|
|
338
365
|
localStorage.setItem('secondaryColor', this.secondaryColorSignal());
|
|
339
366
|
});
|
|
340
367
|
}
|
|
341
368
|
|
|
342
|
-
setPrimaryColor(color: string) {
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
setSecondaryColor(color: string) {
|
|
347
|
-
this.secondaryColorSignal.set(color);
|
|
348
|
-
}
|
|
369
|
+
setPrimaryColor(color: string) { this.primaryColorSignal.set(color); }
|
|
370
|
+
setSecondaryColor(color: string) { this.secondaryColorSignal.set(color); }
|
|
349
371
|
|
|
350
372
|
private loadFromStorage(key: string, defaultValue: string): string {
|
|
351
|
-
try {
|
|
352
|
-
|
|
353
|
-
} catch {
|
|
354
|
-
return defaultValue;
|
|
355
|
-
}
|
|
373
|
+
try { return localStorage.getItem(key) || defaultValue; }
|
|
374
|
+
catch { return defaultValue; }
|
|
356
375
|
}
|
|
357
376
|
}
|
|
358
377
|
```
|
|
359
378
|
|
|
360
379
|
### Example 3: Multi-Tenant Application
|
|
361
380
|
|
|
362
|
-
Different colors per tenant/client:
|
|
363
|
-
|
|
364
381
|
```typescript
|
|
365
382
|
import { Injectable, signal } from '@angular/core';
|
|
366
383
|
|
|
@@ -375,9 +392,7 @@ const TENANT_THEMES: Record<string, TenantTheme> = {
|
|
|
375
392
|
'tenant-c': { primary: '#16A34A', secondary: '#22C55E' }
|
|
376
393
|
};
|
|
377
394
|
|
|
378
|
-
@Injectable({
|
|
379
|
-
providedIn: 'root'
|
|
380
|
-
})
|
|
395
|
+
@Injectable({ providedIn: 'root' })
|
|
381
396
|
export class AppThemeContextService {
|
|
382
397
|
private currentTenantId = signal<string>('tenant-a');
|
|
383
398
|
private primaryColorSignal = signal(TENANT_THEMES['tenant-a'].primary);
|
|
@@ -399,45 +414,33 @@ export class AppThemeContextService {
|
|
|
399
414
|
|
|
400
415
|
## Troubleshooting
|
|
401
416
|
|
|
402
|
-
### Colors
|
|
403
|
-
|
|
404
|
-
1. **Check if the service is provided correctly**: Verify that you've provided your service with the `INSURER_CONTEXT_SERVICE` token in your app config.
|
|
405
|
-
|
|
406
|
-
2. **Check the console**: The `ThemeService` logs information about color application. Look for messages like:
|
|
407
|
-
```
|
|
408
|
-
Applying theme colors: { primaryColor: '#FF0000', ... }
|
|
409
|
-
Applied insurer theme with primary color: #FF0000
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
3. **Verify the service implementation**: Make sure your service has `primaryColor()` and `secondaryColor()` methods that return strings.
|
|
413
|
-
|
|
414
|
-
### Default Colors Showing Instead of Custom Colors
|
|
417
|
+
### Colors not applying
|
|
415
418
|
|
|
416
|
-
|
|
419
|
+
1. Verify that your service is provided with the `CUSTOMER_CONTEXT_SERVICE` token in your app config.
|
|
420
|
+
2. Confirm your service implements `primaryColor()` and `secondaryColor()` returning valid hex strings.
|
|
421
|
+
3. `ThemeService` is silent by design; if a brand color looks different from the one you provided, it is because the WCAG safeguard adjusted it. Read the adjusted color via `getComputedStyle(document.documentElement).getPropertyValue('--customer-primary-color')` and the original via `--customer-primary-color-source`.
|
|
417
422
|
|
|
418
|
-
|
|
419
|
-
2. The methods might not be returning valid hex color codes
|
|
420
|
-
3. Check the browser console for error messages
|
|
423
|
+
### Default colors showing instead of custom colors
|
|
421
424
|
|
|
422
|
-
|
|
425
|
+
- The context service is not provided, or
|
|
426
|
+
- `primaryColor()` / `secondaryColor()` return an invalid hex string.
|
|
423
427
|
|
|
424
|
-
|
|
428
|
+
### Colors not updating dynamically
|
|
425
429
|
|
|
426
|
-
1. Make sure you
|
|
427
|
-
2. Verify that your signals are updating
|
|
428
|
-
3.
|
|
430
|
+
1. Make sure you call `this.themeService.applyCustomerTheme()` after changing a color.
|
|
431
|
+
2. Verify that your signals are actually updating.
|
|
432
|
+
3. Confirm `ThemeService` is injected in the component driving the change.
|
|
429
433
|
|
|
430
434
|
## Best Practices
|
|
431
435
|
|
|
432
|
-
1.
|
|
433
|
-
2.
|
|
434
|
-
3.
|
|
435
|
-
4.
|
|
436
|
-
5.
|
|
437
|
-
6.
|
|
436
|
+
1. Use signals in your customer context service for reactivity.
|
|
437
|
+
2. Provide valid hex colors (`#RRGGBB`).
|
|
438
|
+
3. Trust the WCAG safeguard; do not hand-tune base colors for contrast.
|
|
439
|
+
4. For decorative brand surfaces, use `--customer-*-color-source`. For every other UI use case, use the abstract variables (`--primary-color`, ...).
|
|
440
|
+
5. Persist user preferences in `localStorage` when relevant.
|
|
441
|
+
6. Document brand color choices alongside your app's style guide.
|
|
438
442
|
|
|
439
443
|
## Related Documentation
|
|
440
444
|
|
|
441
445
|
- [Component Documentation](./README.md)
|
|
442
446
|
- [Translation Guide](./lib/services/translation/README.md)
|
|
443
|
-
- [Accessibility Guidelines](./ACCESSIBILITY.md)
|