Skip to content

Commit e97641f

Browse files
marcin-hoaDamianBrzezinskiHoA
authored andcommitted
feat: added theme switcher - wip
1 parent 38ca2d3 commit e97641f

File tree

7 files changed

+65
-26
lines changed

7 files changed

+65
-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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
AdImageBanner,
2424
AlBannerCarouselComponent,
2525
} from '@angular-love/blog/shared/ad-banner';
26+
import { AppThemeStore } from '@angular-love/data-access-app-theme';
2627

2728
@Component({
2829
selector: 'al-root-shell',
@@ -32,7 +33,9 @@ import {
3233
<al-header
3334
class="block w-full"
3435
[language]="language()"
36+
[theme]="theme()"
3537
(languageChange)="onLanguageChange($event)"
38+
(themeToggle)="onThemeToggle()"
3639
>
3740
<al-search />
3841
</al-header>
@@ -86,6 +89,14 @@ export class RootShellComponent {
8689
() => this.sliderStore.slider()?.slideDisplayTimeMs,
8790
);
8891
readonly translocoService = inject(TranslocoService);
92+
<<<<<<< HEAD
93+
=======
94+
95+
private readonly _appThemeStore = inject(AppThemeStore);
96+
97+
protected readonly theme = computed(() => this._appThemeStore.theme());
98+
99+
>>>>>>> 31a555a8 (feat: added theme switcher - wip)
89100
// todo: temporary solution to keep in mind how banner influence the layout
90101
protected readonly adBannerVisible = computed(() => false);
91102
readonly language = toSignal(
@@ -113,6 +124,10 @@ export class RootShellComponent {
113124
);
114125
}
115126

127+
onThemeToggle() {
128+
this._appThemeStore.toggleTheme();
129+
}
130+
116131
constructor(viewport: ViewportScroller) {
117132
// todo: temporary solution to keep in mind how banner influence the layout
118133
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;
@@ -23,17 +23,15 @@
2323
--grey: 46 47 59;
2424
}
2525

26-
@media (prefers-color-scheme: light) {
27-
:root {
28-
--primary: 213 1 89;
29-
--foreground: 20 21 27;
30-
--primary-foreground: 0 0 0;
31-
--muted: 25 25 25;
32-
--border: 200 200 200;
33-
--card: 255 255 255;
34-
--background: 255 255 255;
35-
--grey: 241 241 241;
36-
}
26+
:root[data-theme='light'] {
27+
--primary: 213 1 89;
28+
--foreground: 20 21 27;
29+
--primary-foreground: 0 0 0;
30+
--muted: 25 25 25;
31+
--border: 200 200 200;
32+
--card: 255 255 255;
33+
--background: 255 255 255;
34+
--grey: 241 241 241;
3735
}
3836
}
3937

0 commit comments

Comments
 (0)