Skip to content

Commit 31a555a

Browse files
committed
feat: added theme switcher - wip
1 parent 4852439 commit 31a555a

File tree

7 files changed

+61
-26
lines changed

7 files changed

+61
-26
lines changed

apps/blog/src/assets/icons/moon.svg

Lines changed: 1 addition & 0 deletions
Loading

apps/blog/src/assets/icons/sun.svg

Lines changed: 1 addition & 0 deletions
Loading

libs/blog/app-theme/data-access-app-theme/src/app-theme.store.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { isPlatformBrowser } from '@angular/common';
22
import { inject, Injectable, PLATFORM_ID } from '@angular/core';
3-
import { signalStore, withMethods, withState } from '@ngrx/signals';
3+
import { patchState, signalStore, withMethods, withState } from '@ngrx/signals';
44

5-
type Theme = 'dark' | 'light';
5+
export type Theme = 'dark' | 'light';
66

77
interface AppThemeStore {
88
theme: Theme;
@@ -19,9 +19,16 @@ export const AppThemeStore = signalStore(
1919
) => ({
2020
syncWithSystemTheme: () => {
2121
if (isPlatformBrowser(platformId)) {
22-
ccConsumer.setThemeClass(getSystemTheme());
22+
const theme = getSystemTheme();
23+
ccConsumer.setThemeAttribute(theme);
24+
patchState(store, { theme: theme });
2325
}
2426
},
27+
toggleTheme: () => {
28+
const theme = store.theme() === 'dark' ? 'light' : 'dark';
29+
ccConsumer.setThemeAttribute(theme);
30+
patchState(store, { theme: theme });
31+
},
2532
}),
2633
),
2734
);
@@ -35,15 +42,7 @@ function getSystemTheme(): Theme {
3542
/* todo: create consumer interface and decouple AppThemeStore from CCAppThemeConsumer*/
3643
@Injectable({ providedIn: 'root' })
3744
export class CCAppThemeConsumer {
38-
setThemeClass(theme: Theme): void {
39-
const htmlElement = document.documentElement;
40-
switch (theme) {
41-
case 'dark':
42-
htmlElement.classList.add('cc--darkmode');
43-
break;
44-
case 'light':
45-
htmlElement.classList.remove('cc--darkmode');
46-
break;
47-
}
45+
setThemeAttribute(theme: Theme): void {
46+
document.documentElement.setAttribute('data-theme', theme);
4847
}
4948
}

libs/blog/layouts/ui-layouts/src/lib/header/header.component.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import {
22
ChangeDetectionStrategy,
33
Component,
4+
computed,
45
input,
56
output,
67
signal,
78
} from '@angular/core';
9+
import { FastSvgComponent } from '@push-based/ngx-fast-svg';
810

911
import {
1012
LanguagePickerComponent,
@@ -36,6 +38,19 @@ import {
3638
(languageChange)="languageChange.emit($event)"
3739
/>
3840
41+
<button
42+
aria-label="Toggle theme"
43+
class="flex items-center bg-transparent p-1"
44+
(click)="themeToggle.emit()"
45+
date-testid="header-theme-switch"
46+
>
47+
<fast-svg
48+
class="text-al-pink"
49+
[name]="themeSwitchIcon()"
50+
size="24"
51+
/>
52+
</button>
53+
3954
<ng-content />
4055
4156
<al-header-hamburger
@@ -60,15 +75,23 @@ import {
6075
HeaderHamburgerComponent,
6176
HeaderMobileMenuComponent,
6277
LanguagePickerComponent,
78+
FastSvgComponent,
6379
],
6480
})
6581
export class HeaderComponent {
6682
readonly language = input.required<string>();
83+
readonly theme = input.required<'light' | 'dark'>();
6784

6885
protected languageChange = output<string>();
6986

87+
protected themeToggle = output<void>();
88+
7089
protected showNav = signal<boolean>(false);
7190

91+
protected readonly themeSwitchIcon = computed(() =>
92+
this.theme() === 'light' ? 'moon' : 'sun',
93+
);
94+
7295
protected toggleNav(): void {
7396
this.showNav.set(!this.showNav());
7497
}

libs/blog/shared/ui-icon/src/lib/icon/icon.component.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ export type IconType =
1818
| 'send'
1919
| 'tick'
2020
| 'twitter-x'
21-
| 'youtube';
21+
| 'youtube'
22+
| 'sun'
23+
| 'moon';
2224

2325
@Component({
2426
selector: 'al-icon',

libs/blog/shell/feature-shell-web/src/lib/root-shell.component.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
AlBannerCarouselComponent,
1919
TopBannerComponent,
2020
} from '@angular-love/blog/shared/ad-banner';
21+
import { AppThemeStore } from '@angular-love/data-access-app-theme';
2122

2223
@Component({
2324
selector: 'al-root-shell',
@@ -27,7 +28,9 @@ import {
2728
<al-header
2829
class="block w-full"
2930
[language]="language()"
31+
[theme]="theme()"
3032
(languageChange)="onLanguageChange($event)"
33+
(themeToggle)="onThemeToggle()"
3134
>
3235
<al-search />
3336
</al-header>
@@ -73,6 +76,10 @@ export class RootShellComponent {
7376

7477
readonly translocoService = inject(TranslocoService);
7578

79+
private readonly _appThemeStore = inject(AppThemeStore);
80+
81+
protected readonly theme = computed(() => this._appThemeStore.theme());
82+
7683
// todo: temporary solution to keep in mind how banner influence the layout
7784
protected readonly adBannerVisible = computed(() => false);
7885

@@ -94,6 +101,10 @@ export class RootShellComponent {
94101
);
95102
}
96103

104+
onThemeToggle() {
105+
this._appThemeStore.toggleTheme();
106+
}
107+
97108
constructor(viewport: ViewportScroller) {
98109
// todo: temporary solution to keep in mind how banner influence the layout
99110
effect(() => {

libs/shared/assets/src/lib/styles/main.scss

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
@tailwind utilities;
66

77
@layer base {
8-
:root {
8+
:root[data-theme='dark'] {
99
--primary: 255 0 106;
1010
--foreground: 255 255 255;
1111
--primary-foreground: 255 255 255;
@@ -16,17 +16,15 @@
1616
--grey: 46 47 59;
1717
}
1818

19-
@media (prefers-color-scheme: light) {
20-
:root {
21-
--primary: 213 1 89;
22-
--foreground: 20 21 27;
23-
--primary-foreground: 0 0 0;
24-
--muted: 25 25 25;
25-
--border: 200 200 200;
26-
--card: 255 255 255;
27-
--background: 255 255 255;
28-
--grey: 241 241 241;
29-
}
19+
:root[data-theme='light'] {
20+
--primary: 213 1 89;
21+
--foreground: 20 21 27;
22+
--primary-foreground: 0 0 0;
23+
--muted: 25 25 25;
24+
--border: 200 200 200;
25+
--card: 255 255 255;
26+
--background: 255 255 255;
27+
--grey: 241 241 241;
3028
}
3129
}
3230

0 commit comments

Comments
 (0)