|
1 |
| -import { Component, Inject, OnInit } from '@angular/core'; |
2 |
| -import { DOCUMENT } from '@angular/common'; |
3 | 1 | import { MediaMatcher } from '@angular/cdk/layout';
|
| 2 | +import { DOCUMENT } from '@angular/common'; |
| 3 | +import { ChangeDetectorRef, Component, Inject, OnInit } from '@angular/core'; |
4 | 4 | import { StorageService } from '../../services/storage.service';
|
5 | 5 |
|
| 6 | +type Theme = 'light' | 'dark'; |
| 7 | + |
6 | 8 | @Component({
|
7 | 9 | selector: 'app-theme-mode-toggle',
|
8 | 10 | templateUrl: './theme-mode-toggle.component.html',
|
9 | 11 | styleUrls: ['./theme-mode-toggle.component.scss'],
|
10 | 12 | })
|
11 | 13 | export class ThemeModeToggleComponent implements OnInit {
|
12 |
| - isDarkMode: boolean; |
| 14 | + theme: Theme; |
13 | 15 |
|
14 | 16 | constructor(
|
15 | 17 | @Inject(DOCUMENT)
|
16 | 18 | private readonly document: Document,
|
17 | 19 | private readonly mediaMatcher: MediaMatcher,
|
18 | 20 | private readonly storageService: StorageService,
|
| 21 | + private readonly changeDetector: ChangeDetectorRef, |
19 | 22 | ) {}
|
20 | 23 |
|
21 | 24 | ngOnInit() {
|
22 |
| - // This is commented out because by default the theme mode is set to light (at least for now) |
23 |
| - // const userPrefersTheme = |
24 |
| - // this.mediaMatcher.matchMedia && |
25 |
| - // this.mediaMatcher.matchMedia('(prefers-color-scheme: light)').matches; |
26 |
| - // this.setThemeMode(this.getUserSettingsIsDarkMode() || userPrefersTheme); |
27 |
| - |
28 |
| - const isDarkMode = this.getUserSettingsIsDarkMode(); |
29 |
| - this.setThemeMode(isDarkMode); |
| 25 | + const darkSchemeMatcher = this.mediaMatcher.matchMedia( |
| 26 | + '(prefers-color-scheme: dark)', |
| 27 | + ); |
| 28 | + |
| 29 | + darkSchemeMatcher.onchange = ({ matches }) => { |
| 30 | + if (!this.getStoredTheme()) this.setTheme(matches ? 'dark' : 'light'); |
| 31 | + }; |
| 32 | + |
| 33 | + const preferredScheme = darkSchemeMatcher.matches ? 'dark' : 'light'; |
| 34 | + const storedTheme = this.getStoredTheme(); |
| 35 | + |
| 36 | + this.setTheme(storedTheme ?? preferredScheme); |
30 | 37 | }
|
31 | 38 |
|
32 |
| - toggleThemeMode() { |
33 |
| - const isDarkMode = !this.isDarkMode; |
34 |
| - this.storageService.set('theme-mode', isDarkMode.toString()); |
35 |
| - this.setThemeMode(isDarkMode); |
| 39 | + toggleTheme(skipStorage = false) { |
| 40 | + const newTheme = this.theme === 'dark' ? 'light' : 'dark'; |
| 41 | + // NOTE: We should skip saving theme in storage when toggle is caused by matchMedia change event |
| 42 | + // Otherwise, once saved, it'll no longer correspond to the system preferences, |
| 43 | + // despite the user not touching the toggle button themselves |
| 44 | + if (!skipStorage) this.storageService.set('theme', newTheme); |
| 45 | + this.setTheme(newTheme); |
36 | 46 | }
|
37 | 47 |
|
38 |
| - private getUserSettingsIsDarkMode(): boolean { |
39 |
| - return this.storageService.get('theme-mode') === 'true'; |
| 48 | + private getStoredTheme() { |
| 49 | + return this.storageService.get('theme') as Theme | null; |
40 | 50 | }
|
41 | 51 |
|
42 |
| - private setThemeMode(isDarkMode: boolean) { |
43 |
| - this.isDarkMode = isDarkMode; |
44 |
| - this.document.documentElement.setAttribute( |
45 |
| - 'mode', |
46 |
| - isDarkMode ? 'dark' : 'light', |
47 |
| - ); |
| 52 | + private setTheme(theme: Theme) { |
| 53 | + this.theme = theme; |
| 54 | + this.document.documentElement.setAttribute('mode', theme); |
| 55 | + this.changeDetector.detectChanges(); |
48 | 56 | }
|
49 | 57 | }
|
0 commit comments