Skip to content

feat(core): theme service refactor (#DS-3003) - #1856

Open
NikGurev wants to merge 3 commits into
mainfrom
feat/DS-3003
Open

feat(core): theme service refactor (#DS-3003)#1856
NikGurev wants to merge 3 commits into
mainfrom
feat/DS-3003

Conversation

@NikGurev

@NikGurev NikGurev commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Refactors ThemeService (DS-3003): moves it to signals, adds a built-in auto mode that follows the OS color scheme, and persists the selected mode to localStorage out of the box. ThemeService is kept as a deprecated alias so existing consumers don't break, and the new KbqThemeService is fully DI-configurable via kbqThemeProvider().

List of notable changes

  • added KbqThemeService — signal-based (mode, resolvedMode, currentTheme, themes) replacement for ThemeService, with setAuto()/toggle(), internal matchMedia handling for auto mode, and localStorage persistence via a new swappable KBQ_THEME_STORE/KbqThemeLocalStorageStore (same pattern as KBQ_ACCORDION_STATE_STORE)
  • added kbqThemeProvider(config) / KBQ_THEME_CONFIG for DI-based setup (themes, mode, storageKey, autoLight, autoDark) instead of imperative setThemes()/setTheme() calls
  • added autoLight/autoDark config so auto mode resolves correctly against fully custom theme sets, not just the built-in light/dark names
  • kept ThemeService (deprecated alias of KbqThemeService), KbqTheme.selected (deprecated, still synced), and current/setTheme()/getTheme() (deprecated, still functional) for backward compatibility — no ng update schematic needed for the rename
  • removed the docs app's hand-rolled matchMedia listener and localStorage wiring in navbar.component.ts/navbar-property.ts — now just calls setAuto()/setMode(); configured with kbqThemeProvider({ storageKey: 'docs_theme' }) in apps/docs/src/app/config.ts to preserve existing users' saved preference under the old key
  • updated all other ThemeService consumers (theme-toggle.ts, welcome.component.ts, tokens-overview.ts, docsearch.directive.ts, 7 docs-examples) to the signal API
  • added theme.service.spec.ts — unit tests for auto resolution, mode selection, custom themes, persistence, SSR-safety, and the deprecated shims
  • updated docs/guides/migration.en.md / migration.ru.md with a new "13. Theme service review (20.3.0)" section, and approved the core.api.md public API snapshot

What should reviewers focus on?

  • Whether the deprecated back-compat surface (ThemeService, current, selected, setTheme/getTheme) is worth keeping vs. a cleaner break
  • autoLight/autoDark as the mechanism for custom themes to opt into auto mode — reasonable default, or should it be required when custom themes are registered?

@NikGurev
NikGurev requested a review from artembelik August 7, 2026 10:18
@NikGurev NikGurev self-assigned this Aug 7, 2026
@NikGurev NikGurev added the enhancement New feature or request label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown

Visit the preview URL for this PR (updated for commit 7211a09):

https://koobiq-next--prs-1856-jmotwjcq.web.app

(expires Mon, 10 Aug 2026 10:38:40 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: c9e37e518febda70d0317d07e8ceb35ac43c534c

@NikGurev
NikGurev marked this pull request as ready for review August 7, 2026 10:42
@NikGurev
NikGurev requested a review from lskramarov as a code owner August 7, 2026 10:42
@artembelik
artembelik requested a balanced review from Copilot August 10, 2026 08:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the core ThemeService (DS-3003) into a signal-based KbqThemeService. It introduces a built-in auto mode that follows the OS color scheme via matchMedia, adds out-of-the-box mode persistence through a swappable KBQ_THEME_STORE (default KbqThemeLocalStorageStore), and DI-based configuration via kbqThemeProvider()/KBQ_THEME_CONFIG. Backward compatibility is preserved: ThemeService remains as a deprecated alias, and current, KbqTheme.selected, setTheme()/getTheme() still work. Consumers across the docs app and docs-examples are migrated to the signal API, and the docs navbar's hand-rolled matchMedia/localStorage wiring is removed.

Changes:

  • New signal-based KbqThemeService (mode, resolvedMode, currentTheme, themes) with setAuto()/toggle()/setMode(), internal OS-scheme handling, and DI config (kbqThemeProvider, KBQ_THEME_CONFIG, KBQ_THEME_STORE, KbqThemeLocalStorageStore).
  • Deprecated back-compat surface kept (ThemeService alias, current, selected, setTheme/getTheme); public API snapshot approved.
  • Migrated all consumers (navbar, welcome, docsearch, tokens-overview, theme-toggle, 7 docs-examples) to the signal API; added theme.service.spec.ts and migration docs.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/components/core/services/theme.service.ts New KbqThemeService, config/store tokens, deprecated ThemeService alias.
packages/components/core/services/theme.service.spec.ts New unit tests for auto/mode/custom themes/persistence/SSR/deprecated shims.
tools/public_api_guard/components/core.api.md Approved public API changes for the new/renamed symbols.
apps/docs/src/app/config.ts Wires kbqThemeProvider({ storageKey: 'docs_theme' }) (see comment — format mismatch).
apps/docs/src/app/components/navbar/navbar.component.ts / navbar.template.html Removes hand-rolled matchMedia/storage; drives dropdown off mode()/setMode().
apps/docs/src/app/components/welcome/welcome.component.ts Uses resolvedMode() computed instead of current observable.
apps/docs/src/app/components/docsearch/docsearch.directive.ts Uses toObservable(resolvedMode) for the search theme.
apps/docs/src/app/components/design-tokens-viewers/tokens-overview.ts / .spec.ts Recalculates via effect on resolvedMode(); test mocks matchMedia.
packages/components-dev/theme-toggle.ts Dev toggle rewired to resolvedMode()/setMode() via effect.
packages/docs-examples/** (7 files) Switched currentTheme to resolvedMode() computed; dropped unused rxjs imports.
docs/guides/migration.en.md / migration.ru.md Adds a "13. Theme service review" migration section.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread apps/docs/src/app/config.ts
private readonly storageKey = inject(KBQ_THEME_CONFIG).storageKey ?? KBQ_THEME_DEFAULT_CONFIG.storageKey;

getMode(): KbqThemeMode | string | null {
if (!this.isBrowser) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

зачем добавлять проверку isBrowser если ниже вызывается KBQ_WINDOW?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Убрал, действительно не нужно

});

/** Configures `KbqThemeService` — registers custom themes, sets the initial mode, and how it's applied to the DOM. */
export const kbqThemeProvider = (config: KbqThemeConfig): Provider => ({

@artembelik artembelik Aug 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

можно дать возможность пользователю переопределять только часть свойств

export const kbqThemeProvider = (config: Partial<KbqThemeConfig>): Provider => ({
    provide: KBQ_THEME_CONFIG,
    useValue: { ...KBQ_THEME_DEFAULT_CONFIG, ...config }
});

в таком случае в коде ниже можно упростить проверки:

private readonly storageKey = inject(KBQ_THEME_CONFIG).storageKey;

и здесь:

private readonly config: KbqThemeConfig<T> = inject(KBQ_THEME_CONFIG);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сделал

* (see `kbqThemeProvider()`).
*/
@Injectable({ providedIn: 'root' })
export class KbqThemeLocalStorageStore implements KbqThemeStore {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

для поддержки ssr можно было бы добавить сохранение текущей темы в cookie

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Добавил

@@ -26,67 +50,221 @@ export enum KbqThemeSelector {
}

export const KbqDefaultThemes: KbqTheme[] = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

давай отметим как @docs-private, не совсем из кода понятно для чего эта константа

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Сделано


current: BehaviorSubject<T> = new BehaviorSubject(null as T);
/** Currently selected mode. `'auto'` resolves to `light`/`dark` based on the OS color scheme. */
readonly mode = signal<KbqThemeMode | string>(this.store.getMode() ?? this.config.mode);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

почему mode в какой-то момент становится строкой? зачем в таком случае тип?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

я понимаю это так, что тема может быть кастомной, но мод может быть только 3х вариантов

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Отписал в задаче

/** A theme registered with `KbqThemeService`. */
export interface KbqTheme {
/** Unique name used to select the theme via `setMode()`. */
name: string;

@artembelik artembelik Aug 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

кажется что это не очень идея смешивать mode и name для темы, убиваем типизацию таким образом

mode будет полезен для настройки https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Values/color_value/light-dark например, если у него будет строгий тип

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Добавил colorScheme - отдельный сигнал для этого

/** Registers a custom set of themes. */
setThemes(items: T[]) {
this.themes = items;
this.themes.set(items);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

было бы удобно настраивать список тем при помощи provider, чтобы при инициализации приложения они уже были доступны

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Добавил

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants