|
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