diff --git a/projects/addon-doc/components/demo/index.ts b/projects/addon-doc/components/demo/index.ts index 4d37bf890231..e042a3c962a3 100644 --- a/projects/addon-doc/components/demo/index.ts +++ b/projects/addon-doc/components/demo/index.ts @@ -13,7 +13,6 @@ import type {AbstractControl} from '@angular/forms'; import {FormGroup, FormsModule, ReactiveFormsModule} from '@angular/forms'; import type {Params, UrlTree} from '@angular/router'; import {UrlSerializer} from '@angular/router'; -import {TuiDocThemeDarkService} from '@taiga-ui/addon-doc/services'; import {TUI_DOC_DEMO_TEXTS, TUI_DOC_URL_STATE_HANDLER} from '@taiga-ui/addon-doc/tokens'; import type {TuiDemoParams} from '@taiga-ui/addon-doc/types'; import {tuiCleanObject, tuiCoerceValueIsTrue} from '@taiga-ui/addon-doc/utils'; @@ -24,6 +23,7 @@ import {tuiPure, tuiPx} from '@taiga-ui/cdk/utils/miscellaneous'; import {TuiButton} from '@taiga-ui/core/components/button'; import {TuiExpand} from '@taiga-ui/core/components/expand'; import {TuiGroup} from '@taiga-ui/core/directives/group'; +import {TUI_DARK_MODE} from '@taiga-ui/core/tokens'; import {TuiDataListWrapper} from '@taiga-ui/kit/components/data-list-wrapper'; import {TuiSwitch} from '@taiga-ui/kit/components/switch'; import {TuiChevron} from '@taiga-ui/kit/directives/chevron'; @@ -80,7 +80,7 @@ export class TuiDocDemo implements OnInit { protected readonly template: TemplateRef> | null = null; protected dark = tuiCoerceValueIsTrue( - this.params.darkMode ?? inject(TuiDocThemeDarkService).value, + this.params.darkMode ?? inject(TUI_DARK_MODE)(), ); protected testForm?: FormGroup; diff --git a/projects/addon-doc/components/main/main.component.ts b/projects/addon-doc/components/main/main.component.ts index 5ac2edd9fc43..4809c0edcc35 100644 --- a/projects/addon-doc/components/main/main.component.ts +++ b/projects/addon-doc/components/main/main.component.ts @@ -1,4 +1,3 @@ -import {AsyncPipe} from '@angular/common'; import { ChangeDetectionStrategy, Component, @@ -6,10 +5,10 @@ import { ViewEncapsulation, } from '@angular/core'; import {RouterOutlet} from '@angular/router'; -import {TuiDocThemeDarkService} from '@taiga-ui/addon-doc/services'; import {TUI_DOC_ICONS} from '@taiga-ui/addon-doc/tokens'; import {TuiButton} from '@taiga-ui/core/components/button'; import {TuiRoot} from '@taiga-ui/core/components/root'; +import {TUI_DARK_MODE} from '@taiga-ui/core/tokens'; import {TuiDocHeader} from '../internal/header'; import {TuiDocNavigation} from '../navigation/navigation.component'; @@ -17,14 +16,7 @@ import {TuiDocNavigation} from '../navigation/navigation.component'; @Component({ standalone: true, selector: 'tui-doc-main', - imports: [ - TuiRoot, - AsyncPipe, - RouterOutlet, - TuiButton, - TuiDocHeader, - TuiDocNavigation, - ], + imports: [TuiRoot, RouterOutlet, TuiButton, TuiDocHeader, TuiDocNavigation], templateUrl: './main.template.html', styleUrls: ['./main.style.less'], encapsulation: ViewEncapsulation.None, @@ -35,9 +27,9 @@ import {TuiDocNavigation} from '../navigation/navigation.component'; export class TuiDocMain { private readonly icons = inject(TUI_DOC_ICONS); - protected readonly dark$ = inject(TuiDocThemeDarkService); + protected readonly darkMode = inject(TUI_DARK_MODE); protected get icon(): string { - return this.dark$.value ? this.icons.light : this.icons.dark; + return this.darkMode() ? this.icons.light : this.icons.dark; } } diff --git a/projects/addon-doc/components/main/main.template.html b/projects/addon-doc/components/main/main.template.html index 276f72ea91c0..4614b3904ee7 100644 --- a/projects/addon-doc/components/main/main.template.html +++ b/projects/addon-doc/components/main/main.template.html @@ -1,4 +1,4 @@ - +
@@ -17,7 +17,7 @@ class="tui-doc-dark-mode-switch" [iconStart]="icon" [style.border-radius.%]="100" - (click)="dark$.toggle()" + (click)="darkMode.set(!darkMode())" > diff --git a/projects/addon-doc/services/theme-dark.service.ts b/projects/addon-doc/services/theme-dark.service.ts index 24f4f1e9d15f..090145921448 100644 --- a/projects/addon-doc/services/theme-dark.service.ts +++ b/projects/addon-doc/services/theme-dark.service.ts @@ -1,12 +1,27 @@ import {inject, Injectable} from '@angular/core'; import {WA_LOCAL_STORAGE, WA_WINDOW} from '@ng-web-apis/common'; import {tuiCreateToken} from '@taiga-ui/cdk/utils/miscellaneous'; +import {TUI_DARK_MODE_DEFAULT_KEY, TUI_DARK_MODE_KEY} from '@taiga-ui/core/tokens'; import {BehaviorSubject} from 'rxjs'; -export const TUI_DARK_THEME_DEFAULT_KEY = 'tuiDark'; -export const TUI_DARK_THEME_KEY = tuiCreateToken(TUI_DARK_THEME_DEFAULT_KEY); +/** + * @deprecated use {@link TUI_DARK_THEME} instead + */ +export const TUI_DARK_THEME_DEFAULT_KEY = TUI_DARK_MODE_DEFAULT_KEY; + +/** + * @deprecated use {@link TUI_DARK_THEME} instead + */ +export const TUI_DARK_THEME_KEY = TUI_DARK_MODE_KEY; + +/** + * @deprecated use {@link TUI_DARK_THEME} instead + */ export const TUI_DARK_THEME = tuiCreateToken(false); +/** + * @deprecated use {@link TUI_DARK_THEME} instead + */ @Injectable({ providedIn: 'root', }) diff --git a/projects/core/services/dark-theme.service.ts b/projects/core/services/dark-theme.service.ts index 0e27ec0be5e3..5e3f1b92dad3 100644 --- a/projects/core/services/dark-theme.service.ts +++ b/projects/core/services/dark-theme.service.ts @@ -2,6 +2,9 @@ import {inject, Injectable} from '@angular/core'; import {WA_WINDOW} from '@ng-web-apis/common'; import {fromEvent, map, Observable, shareReplay, startWith} from 'rxjs'; +/** + * @deprecated use {@link TUI_DARK_MODE} instead + */ @Injectable({ providedIn: 'root', }) diff --git a/projects/core/tokens/dark-mode.ts b/projects/core/tokens/dark-mode.ts new file mode 100644 index 000000000000..128884211b8e --- /dev/null +++ b/projects/core/tokens/dark-mode.ts @@ -0,0 +1,49 @@ +import {effect, inject, InjectionToken, signal, type WritableSignal} from '@angular/core'; +import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; +import {WA_LOCAL_STORAGE, WA_WINDOW} from '@ng-web-apis/common'; +import {tuiCreateToken} from '@taiga-ui/cdk/utils/miscellaneous'; +import {filter, fromEvent} from 'rxjs'; + +export const TUI_DARK_MODE_DEFAULT_KEY = 'tuiDark'; +export const TUI_DARK_MODE_KEY = tuiCreateToken(TUI_DARK_MODE_DEFAULT_KEY); +export const TUI_DARK_MODE = new InjectionToken< + WritableSignal & {reset(): void} +>('', { + factory: () => { + let automatic = true; + + const storage = inject(WA_LOCAL_STORAGE); + const key = inject(TUI_DARK_MODE_KEY); + const saved = storage.getItem(key); + const media = inject(WA_WINDOW).matchMedia('(prefers-color-scheme: dark)'); + const result = signal(Boolean((saved && JSON.parse(saved)) ?? media.matches)); + + fromEvent(media, 'change') + .pipe( + filter(() => !storage.getItem(key)), + takeUntilDestroyed(), + ) + .subscribe(() => { + automatic = true; + result.set(media.matches); + }); + + effect(() => { + const value = String(result()); + + if (automatic) { + automatic = false; + } else { + storage.setItem(key, value); + } + }); + + return Object.assign(result, { + reset: () => { + storage.removeItem(key); + automatic = true; + result.set(media.matches); + }, + }); + }, +}); diff --git a/projects/core/tokens/index.ts b/projects/core/tokens/index.ts index d2b3be4fba3b..0cfec6c5e022 100644 --- a/projects/core/tokens/index.ts +++ b/projects/core/tokens/index.ts @@ -1,6 +1,7 @@ export * from './animations-speed'; export * from './assets-path'; export * from './common-icons'; +export * from './dark-mode'; export * from './date-format'; export * from './day-type-handler'; export * from './first-day-of-week'; diff --git a/projects/demo/src/modules/directives/theme/examples/2/index.html b/projects/demo/src/modules/directives/theme/examples/2/index.html new file mode 100644 index 000000000000..7f911f4214f5 --- /dev/null +++ b/projects/demo/src/modules/directives/theme/examples/2/index.html @@ -0,0 +1,17 @@ +Dark mode enabled: {{ darkMode() }} +

+ + +

+

Add to Root to enable:

+<tui-root [attr.tuiTheme]="darkMode() ? 'dark' : null'"> diff --git a/projects/demo/src/modules/directives/theme/examples/2/index.less b/projects/demo/src/modules/directives/theme/examples/2/index.less new file mode 100644 index 000000000000..b6b395e49bb8 --- /dev/null +++ b/projects/demo/src/modules/directives/theme/examples/2/index.less @@ -0,0 +1,8 @@ +p { + display: flex; + gap: 1rem; +} + +code { + white-space: nowrap !important; +} diff --git a/projects/demo/src/modules/directives/theme/examples/2/index.ts b/projects/demo/src/modules/directives/theme/examples/2/index.ts new file mode 100644 index 000000000000..7a3a78d64ede --- /dev/null +++ b/projects/demo/src/modules/directives/theme/examples/2/index.ts @@ -0,0 +1,26 @@ +import {Component, inject} from '@angular/core'; +import {changeDetection} from '@demo/emulate/change-detection'; +import {encapsulation} from '@demo/emulate/encapsulation'; +import {WA_LOCAL_STORAGE, WA_WINDOW} from '@ng-web-apis/common'; +import {TUI_DARK_MODE, TUI_DARK_MODE_KEY, TuiButton} from '@taiga-ui/core'; + +@Component({ + standalone: true, + imports: [TuiButton], + templateUrl: './index.html', + styleUrls: ['./index.less'], + encapsulation, + changeDetection, +}) +export default class Example { + private readonly key = inject(TUI_DARK_MODE_KEY); + private readonly storage = inject(WA_LOCAL_STORAGE); + private readonly media = inject(WA_WINDOW).matchMedia('(prefers-color-scheme: dark)'); + + protected readonly darkMode = inject(TUI_DARK_MODE); + + protected reset(): void { + this.darkMode.set(this.media.matches); + this.storage.removeItem(this.key); + } +} diff --git a/projects/demo/src/modules/directives/theme/index.html b/projects/demo/src/modules/directives/theme/index.html index 4194383d073d..c973fb81a2a8 100644 --- a/projects/demo/src/modules/directives/theme/index.html +++ b/projects/demo/src/modules/directives/theme/index.html @@ -11,10 +11,11 @@ are included. Importing is not required. diff --git a/projects/demo/src/modules/directives/theme/index.ts b/projects/demo/src/modules/directives/theme/index.ts index a6af1afa2f68..4f8a6d8d7508 100644 --- a/projects/demo/src/modules/directives/theme/index.ts +++ b/projects/demo/src/modules/directives/theme/index.ts @@ -8,4 +8,6 @@ import {TuiDemo} from '@demo/utils'; templateUrl: './index.html', changeDetection, }) -export default class Page {} +export default class Page { + protected readonly examples = ['Themes', 'Toggling']; +}