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']; +} diff --git a/projects/demo/src/modules/info/migration-guide/index.html b/projects/demo/src/modules/info/migration-guide/index.html index 57184aaa8ad2..51d109eeb67e 100644 --- a/projects/demo/src/modules/info/migration-guide/index.html +++ b/projects/demo/src/modules/info/migration-guide/index.html @@ -3,7 +3,14 @@

Prerequisites

  • - Update Angular version to + + Update Angular + + version to v16 or later.
  • @@ -51,13 +58,13 @@

    Prerequisites

  • - Update all Taiga packages to the + Update all Taiga UI packages to the latest - v3-lts + v3 version.
  • @@ -92,7 +99,15 @@

    Some final manual steps

  • You can find out that your codebase now contains some imports from - @taiga-ui/legacy + + + @taiga-ui/legacy + + . This package is a transitional state for many outdated entities before their full removal. Everything you find inside this package in the fourth major release will be removed in the fifth one. So, you can just continue to use them for a while. However, some of those components already have modern @@ -107,7 +122,7 @@

    Some final manual steps

    Troubleshooting

    - +