@cloud-ru/ds-theme 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/LICENSE +201 -0
- package/README.md +365 -0
- package/dist/cjs/components/ChildThemeProvider/ChildThemeProvider.d.ts +24 -0
- package/dist/cjs/components/ChildThemeProvider/ChildThemeProvider.js +25 -0
- package/dist/cjs/components/ChildThemeProvider/index.d.ts +1 -0
- package/dist/cjs/components/ChildThemeProvider/index.js +17 -0
- package/dist/cjs/components/RootThemeProvider/RootThemeProvider.d.ts +49 -0
- package/dist/cjs/components/RootThemeProvider/RootThemeProvider.js +30 -0
- package/dist/cjs/components/RootThemeProvider/index.d.ts +1 -0
- package/dist/cjs/components/RootThemeProvider/index.js +17 -0
- package/dist/cjs/components/ThemeProvider/ThemeProvider.d.ts +28 -0
- package/dist/cjs/components/ThemeProvider/ThemeProvider.js +16 -0
- package/dist/cjs/components/ThemeProvider/index.d.ts +1 -0
- package/dist/cjs/components/ThemeProvider/index.js +17 -0
- package/dist/cjs/components/ThemeScope/ThemeScope.d.ts +32 -0
- package/dist/cjs/components/ThemeScope/ThemeScope.js +65 -0
- package/dist/cjs/components/ThemeScope/index.d.ts +1 -0
- package/dist/cjs/components/ThemeScope/index.js +17 -0
- package/dist/cjs/components/index.d.ts +3 -0
- package/dist/cjs/components/index.js +19 -0
- package/dist/cjs/constants/appearance.d.ts +22 -0
- package/dist/cjs/constants/appearance.js +27 -0
- package/dist/cjs/constants/colorScheme.d.ts +17 -0
- package/dist/cjs/constants/colorScheme.js +23 -0
- package/dist/cjs/constants/index.d.ts +2 -0
- package/dist/cjs/constants/index.js +18 -0
- package/dist/cjs/context/appearanceContext.d.ts +20 -0
- package/dist/cjs/context/appearanceContext.js +22 -0
- package/dist/cjs/context/index.d.ts +4 -0
- package/dist/cjs/context/index.js +7 -0
- package/dist/cjs/context/themeContext.d.ts +12 -0
- package/dist/cjs/context/themeContext.js +15 -0
- package/dist/cjs/hooks/index.d.ts +4 -0
- package/dist/cjs/hooks/index.js +20 -0
- package/dist/cjs/hooks/useApplyCustomTheme.d.ts +27 -0
- package/dist/cjs/hooks/useApplyCustomTheme.js +39 -0
- package/dist/cjs/hooks/useColorScheme.d.ts +37 -0
- package/dist/cjs/hooks/useColorScheme.js +80 -0
- package/dist/cjs/hooks/useIsomorphicLayoutEffect.d.ts +7 -0
- package/dist/cjs/hooks/useIsomorphicLayoutEffect.js +16 -0
- package/dist/cjs/hooks/useThemeClassnames.d.ts +11 -0
- package/dist/cjs/hooks/useThemeClassnames.js +18 -0
- package/dist/cjs/hooks/useThemeConfig.d.ts +24 -0
- package/dist/cjs/hooks/useThemeConfig.js +15 -0
- package/dist/cjs/index.d.ts +7 -0
- package/dist/cjs/index.js +23 -0
- package/dist/cjs/keys.d.ts +4 -0
- package/dist/cjs/keys.js +19 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/ssr.d.ts +15 -0
- package/dist/cjs/ssr.js +38 -0
- package/dist/cjs/store/globalStore.d.ts +29 -0
- package/dist/cjs/store/globalStore.js +72 -0
- package/dist/cjs/store/index.d.ts +1 -0
- package/dist/cjs/store/index.js +17 -0
- package/dist/cjs/types/appearance.d.ts +23 -0
- package/dist/cjs/types/appearance.js +3 -0
- package/dist/cjs/types/colorScheme.d.ts +5 -0
- package/dist/cjs/types/colorScheme.js +2 -0
- package/dist/cjs/types/index.d.ts +2 -0
- package/dist/cjs/types/index.js +18 -0
- package/dist/cjs/utils/colorSchemeFromHeaders.d.ts +22 -0
- package/dist/cjs/utils/colorSchemeFromHeaders.js +44 -0
- package/dist/cjs/utils/colorSchemeStorage.d.ts +35 -0
- package/dist/cjs/utils/colorSchemeStorage.js +85 -0
- package/dist/cjs/utils/customTheme/buildBrandPaletteCss.d.ts +11 -0
- package/dist/cjs/utils/customTheme/buildBrandPaletteCss.js +35 -0
- package/dist/cjs/utils/customTheme/buildBrandPaletteVars.d.ts +11 -0
- package/dist/cjs/utils/customTheme/buildBrandPaletteVars.js +42 -0
- package/dist/cjs/utils/customTheme/color/apca.d.ts +4 -0
- package/dist/cjs/utils/customTheme/color/apca.js +62 -0
- package/dist/cjs/utils/customTheme/color/colorSpace.d.ts +5 -0
- package/dist/cjs/utils/customTheme/color/colorSpace.js +72 -0
- package/dist/cjs/utils/customTheme/color/conversions.d.ts +17 -0
- package/dist/cjs/utils/customTheme/color/conversions.js +101 -0
- package/dist/cjs/utils/customTheme/color/hex.d.ts +10 -0
- package/dist/cjs/utils/customTheme/color/hex.js +34 -0
- package/dist/cjs/utils/customTheme/color/index.d.ts +4 -0
- package/dist/cjs/utils/customTheme/color/index.js +20 -0
- package/dist/cjs/utils/customTheme/color/oklch.d.ts +3 -0
- package/dist/cjs/utils/customTheme/color/oklch.js +21 -0
- package/dist/cjs/utils/customTheme/color/types.d.ts +40 -0
- package/dist/cjs/utils/customTheme/color/types.js +2 -0
- package/dist/cjs/utils/customTheme/constants.d.ts +31 -0
- package/dist/cjs/utils/customTheme/constants.js +49 -0
- package/dist/cjs/utils/customTheme/generateBrandPalette.d.ts +10 -0
- package/dist/cjs/utils/customTheme/generateBrandPalette.js +25 -0
- package/dist/cjs/utils/customTheme/index.d.ts +4 -0
- package/dist/cjs/utils/customTheme/index.js +20 -0
- package/dist/cjs/utils/getThemeClassnames.d.ts +17 -0
- package/dist/cjs/utils/getThemeClassnames.js +38 -0
- package/dist/cjs/utils/index.d.ts +7 -0
- package/dist/cjs/utils/index.js +23 -0
- package/dist/cjs/utils/mergeAppearance.d.ts +7 -0
- package/dist/cjs/utils/mergeAppearance.js +17 -0
- package/dist/cjs/utils/resolveColorScheme.d.ts +7 -0
- package/dist/cjs/utils/resolveColorScheme.js +15 -0
- package/dist/cjs/utils/themeBootstrap.d.ts +12 -0
- package/dist/cjs/utils/themeBootstrap.js +19 -0
- package/dist/esm/components/ChildThemeProvider/ChildThemeProvider.d.ts +24 -0
- package/dist/esm/components/ChildThemeProvider/ChildThemeProvider.js +22 -0
- package/dist/esm/components/ChildThemeProvider/index.d.ts +1 -0
- package/dist/esm/components/ChildThemeProvider/index.js +1 -0
- package/dist/esm/components/RootThemeProvider/RootThemeProvider.d.ts +49 -0
- package/dist/esm/components/RootThemeProvider/RootThemeProvider.js +27 -0
- package/dist/esm/components/RootThemeProvider/index.d.ts +1 -0
- package/dist/esm/components/RootThemeProvider/index.js +1 -0
- package/dist/esm/components/ThemeProvider/ThemeProvider.d.ts +28 -0
- package/dist/esm/components/ThemeProvider/ThemeProvider.js +13 -0
- package/dist/esm/components/ThemeProvider/index.d.ts +1 -0
- package/dist/esm/components/ThemeProvider/index.js +1 -0
- package/dist/esm/components/ThemeScope/ThemeScope.d.ts +32 -0
- package/dist/esm/components/ThemeScope/ThemeScope.js +59 -0
- package/dist/esm/components/ThemeScope/index.d.ts +1 -0
- package/dist/esm/components/ThemeScope/index.js +1 -0
- package/dist/esm/components/index.d.ts +3 -0
- package/dist/esm/components/index.js +3 -0
- package/dist/esm/constants/appearance.d.ts +22 -0
- package/dist/esm/constants/appearance.js +24 -0
- package/dist/esm/constants/colorScheme.d.ts +17 -0
- package/dist/esm/constants/colorScheme.js +20 -0
- package/dist/esm/constants/index.d.ts +2 -0
- package/dist/esm/constants/index.js +2 -0
- package/dist/esm/context/appearanceContext.d.ts +20 -0
- package/dist/esm/context/appearanceContext.js +18 -0
- package/dist/esm/context/index.d.ts +4 -0
- package/dist/esm/context/index.js +2 -0
- package/dist/esm/context/themeContext.d.ts +12 -0
- package/dist/esm/context/themeContext.js +11 -0
- package/dist/esm/hooks/index.d.ts +4 -0
- package/dist/esm/hooks/index.js +4 -0
- package/dist/esm/hooks/useApplyCustomTheme.d.ts +27 -0
- package/dist/esm/hooks/useApplyCustomTheme.js +36 -0
- package/dist/esm/hooks/useColorScheme.d.ts +37 -0
- package/dist/esm/hooks/useColorScheme.js +76 -0
- package/dist/esm/hooks/useIsomorphicLayoutEffect.d.ts +7 -0
- package/dist/esm/hooks/useIsomorphicLayoutEffect.js +13 -0
- package/dist/esm/hooks/useThemeClassnames.d.ts +11 -0
- package/dist/esm/hooks/useThemeClassnames.js +15 -0
- package/dist/esm/hooks/useThemeConfig.d.ts +24 -0
- package/dist/esm/hooks/useThemeConfig.js +12 -0
- package/dist/esm/index.d.ts +7 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/keys.d.ts +4 -0
- package/dist/esm/keys.js +16 -0
- package/dist/esm/ssr.d.ts +15 -0
- package/dist/esm/ssr.js +15 -0
- package/dist/esm/store/globalStore.d.ts +29 -0
- package/dist/esm/store/globalStore.js +69 -0
- package/dist/esm/store/index.d.ts +1 -0
- package/dist/esm/store/index.js +1 -0
- package/dist/esm/types/appearance.d.ts +23 -0
- package/dist/esm/types/appearance.js +2 -0
- package/dist/esm/types/colorScheme.d.ts +5 -0
- package/dist/esm/types/colorScheme.js +1 -0
- package/dist/esm/types/index.d.ts +2 -0
- package/dist/esm/types/index.js +2 -0
- package/dist/esm/utils/colorSchemeFromHeaders.d.ts +22 -0
- package/dist/esm/utils/colorSchemeFromHeaders.js +40 -0
- package/dist/esm/utils/colorSchemeStorage.d.ts +35 -0
- package/dist/esm/utils/colorSchemeStorage.js +81 -0
- package/dist/esm/utils/customTheme/buildBrandPaletteCss.d.ts +11 -0
- package/dist/esm/utils/customTheme/buildBrandPaletteCss.js +32 -0
- package/dist/esm/utils/customTheme/buildBrandPaletteVars.d.ts +11 -0
- package/dist/esm/utils/customTheme/buildBrandPaletteVars.js +39 -0
- package/dist/esm/utils/customTheme/color/apca.d.ts +4 -0
- package/dist/esm/utils/customTheme/color/apca.js +59 -0
- package/dist/esm/utils/customTheme/color/colorSpace.d.ts +5 -0
- package/dist/esm/utils/customTheme/color/colorSpace.js +68 -0
- package/dist/esm/utils/customTheme/color/conversions.d.ts +17 -0
- package/dist/esm/utils/customTheme/color/conversions.js +91 -0
- package/dist/esm/utils/customTheme/color/hex.d.ts +10 -0
- package/dist/esm/utils/customTheme/color/hex.js +29 -0
- package/dist/esm/utils/customTheme/color/index.d.ts +4 -0
- package/dist/esm/utils/customTheme/color/index.js +4 -0
- package/dist/esm/utils/customTheme/color/oklch.d.ts +3 -0
- package/dist/esm/utils/customTheme/color/oklch.js +18 -0
- package/dist/esm/utils/customTheme/color/types.d.ts +40 -0
- package/dist/esm/utils/customTheme/color/types.js +1 -0
- package/dist/esm/utils/customTheme/constants.d.ts +31 -0
- package/dist/esm/utils/customTheme/constants.js +46 -0
- package/dist/esm/utils/customTheme/generateBrandPalette.d.ts +10 -0
- package/dist/esm/utils/customTheme/generateBrandPalette.js +22 -0
- package/dist/esm/utils/customTheme/index.d.ts +4 -0
- package/dist/esm/utils/customTheme/index.js +4 -0
- package/dist/esm/utils/getThemeClassnames.d.ts +17 -0
- package/dist/esm/utils/getThemeClassnames.js +34 -0
- package/dist/esm/utils/index.d.ts +7 -0
- package/dist/esm/utils/index.js +7 -0
- package/dist/esm/utils/mergeAppearance.d.ts +7 -0
- package/dist/esm/utils/mergeAppearance.js +14 -0
- package/dist/esm/utils/resolveColorScheme.d.ts +7 -0
- package/dist/esm/utils/resolveColorScheme.js +12 -0
- package/dist/esm/utils/themeBootstrap.d.ts +12 -0
- package/dist/esm/utils/themeBootstrap.js +16 -0
- package/dist/tsconfig.cjs.tsbuildinfo +1 -0
- package/dist/tsconfig.esm.tsbuildinfo +1 -0
- package/package.json +45 -0
- package/src/components/ChildThemeProvider/ChildThemeProvider.tsx +54 -0
- package/src/components/ChildThemeProvider/index.ts +1 -0
- package/src/components/RootThemeProvider/RootThemeProvider.tsx +87 -0
- package/src/components/RootThemeProvider/index.ts +1 -0
- package/src/components/ThemeProvider/ThemeProvider.tsx +41 -0
- package/src/components/ThemeProvider/index.ts +1 -0
- package/src/components/ThemeScope/ThemeScope.tsx +106 -0
- package/src/components/ThemeScope/index.ts +1 -0
- package/src/components/index.ts +3 -0
- package/src/constants/appearance.ts +27 -0
- package/src/constants/colorScheme.ts +23 -0
- package/src/constants/index.ts +2 -0
- package/src/context/appearanceContext.tsx +39 -0
- package/src/context/index.ts +4 -0
- package/src/context/themeContext.ts +18 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useApplyCustomTheme.ts +56 -0
- package/src/hooks/useColorScheme.ts +131 -0
- package/src/hooks/useIsomorphicLayoutEffect.ts +17 -0
- package/src/hooks/useThemeClassnames.ts +18 -0
- package/src/hooks/useThemeConfig.tsx +44 -0
- package/src/index.ts +7 -0
- package/src/keys.ts +18 -0
- package/src/ssr.ts +25 -0
- package/src/store/globalStore.ts +93 -0
- package/src/store/index.ts +1 -0
- package/src/types/appearance.ts +29 -0
- package/src/types/colorScheme.ts +7 -0
- package/src/types/index.ts +2 -0
- package/src/utils/colorSchemeFromHeaders.ts +57 -0
- package/src/utils/colorSchemeStorage.ts +117 -0
- package/src/utils/customTheme/buildBrandPaletteCss.ts +37 -0
- package/src/utils/customTheme/buildBrandPaletteVars.ts +56 -0
- package/src/utils/customTheme/color/apca.ts +74 -0
- package/src/utils/customTheme/color/colorSpace.ts +82 -0
- package/src/utils/customTheme/color/conversions.ts +113 -0
- package/src/utils/customTheme/color/hex.ts +39 -0
- package/src/utils/customTheme/color/index.ts +4 -0
- package/src/utils/customTheme/color/oklch.ts +22 -0
- package/src/utils/customTheme/color/types.ts +32 -0
- package/src/utils/customTheme/constants.ts +57 -0
- package/src/utils/customTheme/generateBrandPalette.ts +30 -0
- package/src/utils/customTheme/index.ts +4 -0
- package/src/utils/getThemeClassnames.ts +40 -0
- package/src/utils/index.ts +7 -0
- package/src/utils/mergeAppearance.ts +16 -0
- package/src/utils/resolveColorScheme.ts +15 -0
- package/src/utils/themeBootstrap.ts +18 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ColorScheme, ThemeOverride } from '../types/colorScheme';
|
|
2
|
+
import { ColorSchemeStorage } from '../utils/colorSchemeStorage';
|
|
3
|
+
export type UseColorSchemeOptions = {
|
|
4
|
+
/**
|
|
5
|
+
* Server-resolved начальный override (например, `getThemeOverrideFromHeaders(headers)` из cookie
|
|
6
|
+
* или из бэкенд-сессии). Делает первый рендер детерминированным и совпадающим с SSR — без
|
|
7
|
+
* hydration mismatch и без прыжка подсветки переключателя. Если не задан — старт с `system`.
|
|
8
|
+
*/
|
|
9
|
+
initialOverride?: ThemeOverride;
|
|
10
|
+
/**
|
|
11
|
+
* Адаптер персиста. По умолчанию — in-memory (без персиста между перезагрузками): DS не пишет в
|
|
12
|
+
* cookie/localStorage сам. Подключите явно: `createCookieColorSchemeStorage()` (no-flash SSR) или
|
|
13
|
+
* собственный адаптер (localStorage / бэкенд-сессия).
|
|
14
|
+
*/
|
|
15
|
+
storage?: ColorSchemeStorage;
|
|
16
|
+
/** Корень, на который вешается `sn-light`/`sn-dark`. По умолчанию `<html>`. */
|
|
17
|
+
target?: HTMLElement;
|
|
18
|
+
};
|
|
19
|
+
export type UseColorSchemeResult = {
|
|
20
|
+
/** Итоговая применённая схема (override ?? система). */
|
|
21
|
+
colorScheme: ColorScheme;
|
|
22
|
+
/** Текущий выбор пользователя: `light` / `dark` / `system`. */
|
|
23
|
+
override: ThemeOverride;
|
|
24
|
+
/** Системная тема (`prefers-color-scheme: dark`). */
|
|
25
|
+
systemPrefersDark: boolean;
|
|
26
|
+
/** Сменить выбор: персистит через `storage.write` (cookie / бэкенд). */
|
|
27
|
+
setOverride(next: ThemeOverride): void;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Управляет цветовой схемой DS: резолвит `override ?? prefers-color-scheme`, вешает `sn-light`/`sn-dark`
|
|
31
|
+
* на корень, реагирует на живое переключение темы ОС (`matchMedia`) и внешние изменения override
|
|
32
|
+
* (`storage.subscribe` — кросс-таб / бэкенд push). No-flash на reload обеспечивает inline
|
|
33
|
+
* `getThemeBootstrapScript` в `<head>` (а на SSR — корректный класс из `getColorSchemeFromHeaders`);
|
|
34
|
+
* хук синхронизирует состояние и переключатель override.
|
|
35
|
+
* @function React hook
|
|
36
|
+
*/
|
|
37
|
+
export declare function useColorScheme(options?: UseColorSchemeOptions): UseColorSchemeResult;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
'use client';
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.useColorScheme = useColorScheme;
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
const colorScheme_1 = require("../constants/colorScheme");
|
|
7
|
+
const colorSchemeStorage_1 = require("../utils/colorSchemeStorage");
|
|
8
|
+
const resolveColorScheme_1 = require("../utils/resolveColorScheme");
|
|
9
|
+
/* eslint-disable @cloud-ru/ssr-safe-react/domApi -- guarded by isBrowser(); хук помечен 'use client' */
|
|
10
|
+
function isBrowser() {
|
|
11
|
+
return Boolean(typeof window !== 'undefined' && window.document && window.document.createElement);
|
|
12
|
+
}
|
|
13
|
+
const SYSTEM_DARK_QUERY = '(prefers-color-scheme: dark)';
|
|
14
|
+
function getSystemPrefersDark() {
|
|
15
|
+
return isBrowser() && window.matchMedia(SYSTEM_DARK_QUERY).matches;
|
|
16
|
+
}
|
|
17
|
+
function applyColorScheme(target, colorScheme) {
|
|
18
|
+
target.classList.toggle('sn-dark', colorScheme === colorScheme_1.COLOR_SCHEME.Dark);
|
|
19
|
+
target.classList.toggle('sn-light', colorScheme === colorScheme_1.COLOR_SCHEME.Light);
|
|
20
|
+
}
|
|
21
|
+
// Общий синглтон in-memory-адаптера: несколько `useColorScheme` без своего `storage` смотрят в один
|
|
22
|
+
// источник, поэтому переключатели в разных поддеревьях синхронны в пределах сессии.
|
|
23
|
+
const defaultColorSchemeStorage = (0, colorSchemeStorage_1.createInMemoryColorSchemeStorage)();
|
|
24
|
+
/**
|
|
25
|
+
* Управляет цветовой схемой DS: резолвит `override ?? prefers-color-scheme`, вешает `sn-light`/`sn-dark`
|
|
26
|
+
* на корень, реагирует на живое переключение темы ОС (`matchMedia`) и внешние изменения override
|
|
27
|
+
* (`storage.subscribe` — кросс-таб / бэкенд push). No-flash на reload обеспечивает inline
|
|
28
|
+
* `getThemeBootstrapScript` в `<head>` (а на SSR — корректный класс из `getColorSchemeFromHeaders`);
|
|
29
|
+
* хук синхронизирует состояние и переключатель override.
|
|
30
|
+
* @function React hook
|
|
31
|
+
*/
|
|
32
|
+
function useColorScheme(options) {
|
|
33
|
+
const { initialOverride, target } = options ?? {};
|
|
34
|
+
const providedStorage = options?.storage;
|
|
35
|
+
// Дефолт — общий in-memory-адаптер: DS не пишет в cookie/localStorage без явного `storage`.
|
|
36
|
+
const storage = (0, react_1.useMemo)(() => providedStorage ?? defaultColorSchemeStorage, [providedStorage]);
|
|
37
|
+
// override инициализируется СИНХРОННО из источника правды, чтобы SegmentControl не «прыгал» с
|
|
38
|
+
// System на реальный выбор после mount: `initialOverride` (server-resolved — бэкенд-сессия / SSR
|
|
39
|
+
// cookie, authoritative) → синхронное чтение store (`storage.read`) → `system`.
|
|
40
|
+
// SSR-консистентность: на сервере `storage.read()` обязан вернуть undefined (in-memory- и cookie-
|
|
41
|
+
// адаптеры так и делают), поэтому и сервер, и первый клиентский рендер берут одно значение — мисматча
|
|
42
|
+
// нет. В чистом клиенте с персист-адаптером (cookie/localStorage) `read()` отдаёт сохранённый выбор
|
|
43
|
+
// сразу при первом рендере, без прыжка; дефолтный in-memory стартует с `system`.
|
|
44
|
+
const [override, setOverrideState] = (0, react_1.useState)(() => initialOverride ?? storage.read() ?? colorScheme_1.THEME_OVERRIDE.System);
|
|
45
|
+
const [systemPrefersDark, setSystemPrefersDark] = (0, react_1.useState)(false);
|
|
46
|
+
const [mounted, setMounted] = (0, react_1.useState)(false);
|
|
47
|
+
// matchMedia нельзя читать в initializer (на SSR-хосте рассинхронит colorScheme-readout) — системную
|
|
48
|
+
// тему добираем после mount. override уже корректен из initializer, его здесь перечитывать не нужно.
|
|
49
|
+
(0, react_1.useEffect)(() => {
|
|
50
|
+
setSystemPrefersDark(getSystemPrefersDark());
|
|
51
|
+
setMounted(true);
|
|
52
|
+
}, []);
|
|
53
|
+
const colorScheme = (0, resolveColorScheme_1.resolveColorScheme)(override, systemPrefersDark);
|
|
54
|
+
// Применяем класс к корню ТОЛЬКО после mount: до этого корректный класс уже стоит из SSR/bootstrap,
|
|
55
|
+
// и детерминированный первый рендер не должен его перетереть (иначе моргание).
|
|
56
|
+
(0, react_1.useEffect)(() => {
|
|
57
|
+
if (!mounted || !isBrowser()) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
applyColorScheme(target ?? document.documentElement, colorScheme);
|
|
61
|
+
}, [mounted, colorScheme, target]);
|
|
62
|
+
// Живое переключение темы ОС.
|
|
63
|
+
(0, react_1.useEffect)(() => {
|
|
64
|
+
if (!isBrowser()) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
const mql = window.matchMedia(SYSTEM_DARK_QUERY);
|
|
68
|
+
const onChange = (event) => setSystemPrefersDark(event.matches);
|
|
69
|
+
mql.addEventListener('change', onChange);
|
|
70
|
+
return () => mql.removeEventListener('change', onChange);
|
|
71
|
+
}, []);
|
|
72
|
+
// Внешняя синхронизация override (кросс-таб через BroadcastChannel / бэкенд push).
|
|
73
|
+
(0, react_1.useEffect)(() => storage.subscribe(() => setOverrideState(storage.read() ?? colorScheme_1.THEME_OVERRIDE.System)), [storage]);
|
|
74
|
+
const setOverride = (0, react_1.useCallback)((next) => {
|
|
75
|
+
setOverrideState(next);
|
|
76
|
+
storage.write(next);
|
|
77
|
+
}, [storage]);
|
|
78
|
+
return { colorScheme, override, systemPrefersDark, setOverride };
|
|
79
|
+
}
|
|
80
|
+
/* eslint-enable @cloud-ru/ssr-safe-react/domApi */
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { useLayoutEffect } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* `useLayoutEffect`, безопасный для SSR: на клиенте — настоящий `useLayoutEffect` (применяет эффект
|
|
4
|
+
* синхронно до пейнта, поэтому инъекция бренд-`<style>` не даёт кадра с дефолтным акцентом — «моргания»),
|
|
5
|
+
* на сервере — `useEffect` (сырой `useLayoutEffect` на сервере варнит и всё равно не выполняется).
|
|
6
|
+
*/
|
|
7
|
+
export declare const useIsomorphicLayoutEffect: typeof useLayoutEffect;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useIsomorphicLayoutEffect = void 0;
|
|
4
|
+
// eslint-disable-next-line no-restricted-imports -- единственная точка, где берём сырой useLayoutEffect
|
|
5
|
+
const react_1 = require("react");
|
|
6
|
+
/* eslint-disable @cloud-ru/ssr-safe-react/domApi -- это и есть SSR-guard, DOM читается только для детекта браузера */
|
|
7
|
+
function isBrowser() {
|
|
8
|
+
return Boolean(typeof window !== 'undefined' && window.document && window.document.createElement);
|
|
9
|
+
}
|
|
10
|
+
/* eslint-enable @cloud-ru/ssr-safe-react/domApi */
|
|
11
|
+
/**
|
|
12
|
+
* `useLayoutEffect`, безопасный для SSR: на клиенте — настоящий `useLayoutEffect` (применяет эффект
|
|
13
|
+
* синхронно до пейнта, поэтому инъекция бренд-`<style>` не даёт кадра с дефолтным акцентом — «моргания»),
|
|
14
|
+
* на сервере — `useEffect` (сырой `useLayoutEffect` на сервере варнит и всё равно не выполняется).
|
|
15
|
+
*/
|
|
16
|
+
exports.useIsomorphicLayoutEffect = isBrowser() ? react_1.useLayoutEffect : react_1.useEffect;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ThemeAppearance } from '../types/appearance';
|
|
2
|
+
/**
|
|
3
|
+
* Возвращает полный набор `sn-*` классов = ближайшее оформление из контекста (`RootThemeProvider` +
|
|
4
|
+
* цепочка `ChildThemeProvider`) ⊕ переданные `overrides`. Навешивается на DOM-границу компонента,
|
|
5
|
+
* который хочет переопределить ось (например, мобильная обёртка с `density: 'comfort'`).
|
|
6
|
+
*
|
|
7
|
+
* Поскольку набор включает текущий `colorScheme`/`brand`/`brandRole` из контекста, такая обёртка —
|
|
8
|
+
* самосогласованный scope и трекает переключение темы. Без провайдера в дереве оформление пустое,
|
|
9
|
+
* и поведение совпадает со старым `getThemeClassnames({ density })`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function useThemeClassnames(overrides?: Partial<ThemeAppearance>): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useThemeClassnames = useThemeClassnames;
|
|
4
|
+
const appearanceContext_1 = require("../context/appearanceContext");
|
|
5
|
+
const getThemeClassnames_1 = require("../utils/getThemeClassnames");
|
|
6
|
+
/**
|
|
7
|
+
* Возвращает полный набор `sn-*` классов = ближайшее оформление из контекста (`RootThemeProvider` +
|
|
8
|
+
* цепочка `ChildThemeProvider`) ⊕ переданные `overrides`. Навешивается на DOM-границу компонента,
|
|
9
|
+
* который хочет переопределить ось (например, мобильная обёртка с `density: 'comfort'`).
|
|
10
|
+
*
|
|
11
|
+
* Поскольку набор включает текущий `colorScheme`/`brand`/`brandRole` из контекста, такая обёртка —
|
|
12
|
+
* самосогласованный scope и трекает переключение темы. Без провайдера в дереве оформление пустое,
|
|
13
|
+
* и поведение совпадает со старым `getThemeClassnames({ density })`.
|
|
14
|
+
*/
|
|
15
|
+
function useThemeClassnames(overrides) {
|
|
16
|
+
const { appearance } = (0, appearanceContext_1.useThemeAppearance)();
|
|
17
|
+
return (0, getThemeClassnames_1.getThemeClassnames)({ ...appearance, ...overrides });
|
|
18
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
type UseThemePropsWithDefaultTheme<T extends string> = {
|
|
2
|
+
themeMap: Record<T, string>;
|
|
3
|
+
defaultTheme: T;
|
|
4
|
+
};
|
|
5
|
+
type UseThemeProps<T extends string> = {
|
|
6
|
+
/**
|
|
7
|
+
* Объект с указанием соответсвия темы и css-класса
|
|
8
|
+
*/
|
|
9
|
+
themeMap: Record<T, string>;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Хук для работы с темами
|
|
13
|
+
*/
|
|
14
|
+
export declare function useThemeConfig<T extends string>(props: UseThemeProps<T>): {
|
|
15
|
+
theme: T | undefined;
|
|
16
|
+
themeClassName: string | undefined;
|
|
17
|
+
changeTheme(theme: T): void;
|
|
18
|
+
};
|
|
19
|
+
export declare function useThemeConfig<T extends string>(props: UseThemePropsWithDefaultTheme<T>): {
|
|
20
|
+
theme: T;
|
|
21
|
+
themeClassName: string;
|
|
22
|
+
changeTheme(theme: T): void;
|
|
23
|
+
};
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useThemeConfig = useThemeConfig;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
function hasDefaultTheme(props) {
|
|
6
|
+
return Boolean(props.defaultTheme);
|
|
7
|
+
}
|
|
8
|
+
function useThemeConfig(props) {
|
|
9
|
+
const [theme, setTheme] = (0, react_1.useState)(hasDefaultTheme(props) ? props.defaultTheme : undefined);
|
|
10
|
+
return {
|
|
11
|
+
theme,
|
|
12
|
+
themeClassName: theme ? props.themeMap[theme] : undefined,
|
|
13
|
+
changeTheme: setTheme,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./components"), exports);
|
|
18
|
+
__exportStar(require("./hooks"), exports);
|
|
19
|
+
__exportStar(require("./context"), exports);
|
|
20
|
+
__exportStar(require("./constants"), exports);
|
|
21
|
+
__exportStar(require("./types"), exports);
|
|
22
|
+
__exportStar(require("./utils"), exports);
|
|
23
|
+
__exportStar(require("./store"), exports);
|
package/dist/cjs/keys.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.THEME_KEYS = void 0;
|
|
4
|
+
const context_kit_1 = require("@cloud-ru/ds-context-kit");
|
|
5
|
+
/**
|
|
6
|
+
* `Symbol.for`-ключи домена theme-appearance — обе системы синглтонов в одном месте:
|
|
7
|
+
* - `context` — объект React-контекста оформления (через `providerKey` → dev-warn о смешанных версиях);
|
|
8
|
+
* - `store` — глобальный мутабельный стор оформления (кросс-root мост хост→MFE).
|
|
9
|
+
*
|
|
10
|
+
* Одна контракт-версия на домен бампается ТОЛЬКО при несовместимом сломе формы значения и двигает
|
|
11
|
+
* ОБА ключа вместе. Ортогонально мажору ПАКЕТА. Строки менять НЕЛЬЗЯ (форк синглтона = случайный
|
|
12
|
+
* `:v2`). НЕ путать с доменом `theme` старого `ThemeContext` (themeMap/changeTheme). См.
|
|
13
|
+
* providers-standard.md.
|
|
14
|
+
*/
|
|
15
|
+
const CONTRACT_VERSION = 1;
|
|
16
|
+
exports.THEME_KEYS = {
|
|
17
|
+
context: (0, context_kit_1.providerKey)('theme-appearance', CONTRACT_VERSION),
|
|
18
|
+
store: `@cloud-ru/ds:global-theme-store:v${CONTRACT_VERSION}`,
|
|
19
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export { BRAND, BRAND_ROLE, DENSITY } from './constants/appearance';
|
|
2
|
+
export { COLOR_SCHEME, THEME_OVERRIDE, THEME_OVERRIDE_STORAGE_KEY } from './constants/colorScheme';
|
|
3
|
+
export type { Brand, BrandRole, Density, ThemeAppearance } from './types/appearance';
|
|
4
|
+
export type { ColorScheme, ThemeOverride } from './types/colorScheme';
|
|
5
|
+
export { mergeAppearance } from './utils/mergeAppearance';
|
|
6
|
+
export { getThemeClassnameList, getThemeClassnames } from './utils/getThemeClassnames';
|
|
7
|
+
export { resolveColorScheme } from './utils/resolveColorScheme';
|
|
8
|
+
export { getColorSchemeFromHeaders, getThemeOverrideFromHeaders } from './utils/colorSchemeFromHeaders';
|
|
9
|
+
export { getThemeBootstrapScript } from './utils/themeBootstrap';
|
|
10
|
+
export { generateBrandPalette } from './utils/customTheme/generateBrandPalette';
|
|
11
|
+
export type { BrandPalette } from './utils/customTheme/generateBrandPalette';
|
|
12
|
+
export { buildBrandPaletteVars } from './utils/customTheme/buildBrandPaletteVars';
|
|
13
|
+
export { buildBrandPaletteCss } from './utils/customTheme/buildBrandPaletteCss';
|
|
14
|
+
export { BASE_BRAND_PALETTE, BRAND_PRIMARY_TONES, BRAND_PRIMARY_VAR_PREFIX, PRIMARY_ACCENT_TONE, } from './utils/customTheme/constants';
|
|
15
|
+
export type { BrandPrimaryTone } from './utils/customTheme/constants';
|
package/dist/cjs/ssr.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SSR-безопасный субпуть `@cloud-ru/ds-theme/ssr`: только чистые символы (без React-контекста/хуков),
|
|
3
|
+
// чтобы импорт в React Server Components не тянул `createContext`. Зеркало `@cloud-ru/ds-adaptive/ssr`.
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.PRIMARY_ACCENT_TONE = exports.BRAND_PRIMARY_VAR_PREFIX = exports.BRAND_PRIMARY_TONES = exports.BASE_BRAND_PALETTE = exports.buildBrandPaletteCss = exports.buildBrandPaletteVars = exports.generateBrandPalette = exports.getThemeBootstrapScript = exports.getThemeOverrideFromHeaders = exports.getColorSchemeFromHeaders = exports.resolveColorScheme = exports.getThemeClassnames = exports.getThemeClassnameList = exports.mergeAppearance = exports.THEME_OVERRIDE_STORAGE_KEY = exports.THEME_OVERRIDE = exports.COLOR_SCHEME = exports.DENSITY = exports.BRAND_ROLE = exports.BRAND = void 0;
|
|
6
|
+
var appearance_1 = require("./constants/appearance");
|
|
7
|
+
Object.defineProperty(exports, "BRAND", { enumerable: true, get: function () { return appearance_1.BRAND; } });
|
|
8
|
+
Object.defineProperty(exports, "BRAND_ROLE", { enumerable: true, get: function () { return appearance_1.BRAND_ROLE; } });
|
|
9
|
+
Object.defineProperty(exports, "DENSITY", { enumerable: true, get: function () { return appearance_1.DENSITY; } });
|
|
10
|
+
var colorScheme_1 = require("./constants/colorScheme");
|
|
11
|
+
Object.defineProperty(exports, "COLOR_SCHEME", { enumerable: true, get: function () { return colorScheme_1.COLOR_SCHEME; } });
|
|
12
|
+
Object.defineProperty(exports, "THEME_OVERRIDE", { enumerable: true, get: function () { return colorScheme_1.THEME_OVERRIDE; } });
|
|
13
|
+
Object.defineProperty(exports, "THEME_OVERRIDE_STORAGE_KEY", { enumerable: true, get: function () { return colorScheme_1.THEME_OVERRIDE_STORAGE_KEY; } });
|
|
14
|
+
var mergeAppearance_1 = require("./utils/mergeAppearance");
|
|
15
|
+
Object.defineProperty(exports, "mergeAppearance", { enumerable: true, get: function () { return mergeAppearance_1.mergeAppearance; } });
|
|
16
|
+
var getThemeClassnames_1 = require("./utils/getThemeClassnames");
|
|
17
|
+
Object.defineProperty(exports, "getThemeClassnameList", { enumerable: true, get: function () { return getThemeClassnames_1.getThemeClassnameList; } });
|
|
18
|
+
Object.defineProperty(exports, "getThemeClassnames", { enumerable: true, get: function () { return getThemeClassnames_1.getThemeClassnames; } });
|
|
19
|
+
var resolveColorScheme_1 = require("./utils/resolveColorScheme");
|
|
20
|
+
Object.defineProperty(exports, "resolveColorScheme", { enumerable: true, get: function () { return resolveColorScheme_1.resolveColorScheme; } });
|
|
21
|
+
var colorSchemeFromHeaders_1 = require("./utils/colorSchemeFromHeaders");
|
|
22
|
+
Object.defineProperty(exports, "getColorSchemeFromHeaders", { enumerable: true, get: function () { return colorSchemeFromHeaders_1.getColorSchemeFromHeaders; } });
|
|
23
|
+
Object.defineProperty(exports, "getThemeOverrideFromHeaders", { enumerable: true, get: function () { return colorSchemeFromHeaders_1.getThemeOverrideFromHeaders; } });
|
|
24
|
+
var themeBootstrap_1 = require("./utils/themeBootstrap");
|
|
25
|
+
Object.defineProperty(exports, "getThemeBootstrapScript", { enumerable: true, get: function () { return themeBootstrap_1.getThemeBootstrapScript; } });
|
|
26
|
+
// Кастомный бренд-цвет — чистые (без React/DOM) генератор палитры и сборщик CSS-переменных, чтобы
|
|
27
|
+
// SSR/RSC мог инжектить `--sn-brand-color-primary-*` строкой и убрать мигание до гидрации.
|
|
28
|
+
var generateBrandPalette_1 = require("./utils/customTheme/generateBrandPalette");
|
|
29
|
+
Object.defineProperty(exports, "generateBrandPalette", { enumerable: true, get: function () { return generateBrandPalette_1.generateBrandPalette; } });
|
|
30
|
+
var buildBrandPaletteVars_1 = require("./utils/customTheme/buildBrandPaletteVars");
|
|
31
|
+
Object.defineProperty(exports, "buildBrandPaletteVars", { enumerable: true, get: function () { return buildBrandPaletteVars_1.buildBrandPaletteVars; } });
|
|
32
|
+
var buildBrandPaletteCss_1 = require("./utils/customTheme/buildBrandPaletteCss");
|
|
33
|
+
Object.defineProperty(exports, "buildBrandPaletteCss", { enumerable: true, get: function () { return buildBrandPaletteCss_1.buildBrandPaletteCss; } });
|
|
34
|
+
var constants_1 = require("./utils/customTheme/constants");
|
|
35
|
+
Object.defineProperty(exports, "BASE_BRAND_PALETTE", { enumerable: true, get: function () { return constants_1.BASE_BRAND_PALETTE; } });
|
|
36
|
+
Object.defineProperty(exports, "BRAND_PRIMARY_TONES", { enumerable: true, get: function () { return constants_1.BRAND_PRIMARY_TONES; } });
|
|
37
|
+
Object.defineProperty(exports, "BRAND_PRIMARY_VAR_PREFIX", { enumerable: true, get: function () { return constants_1.BRAND_PRIMARY_VAR_PREFIX; } });
|
|
38
|
+
Object.defineProperty(exports, "PRIMARY_ACCENT_TONE", { enumerable: true, get: function () { return constants_1.PRIMARY_ACCENT_TONE; } });
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ThemeAppearanceStore } from '../context/appearanceContext';
|
|
2
|
+
import { ThemeAppearance } from '../types/appearance';
|
|
3
|
+
export type GlobalThemeStore = {
|
|
4
|
+
/** Стор для `<RootThemeProvider store={...} />`. */
|
|
5
|
+
store: ThemeAppearanceStore;
|
|
6
|
+
/** Текущее оформление. */
|
|
7
|
+
getAppearance(): ThemeAppearance;
|
|
8
|
+
/** Слить патч в оформление и уведомить все подписанные провайдеры. */
|
|
9
|
+
setAppearance(patch: Partial<ThemeAppearance>): void;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Глобальный singleton-стор оформления, общий для всех React-корней процесса: микрофронтов
|
|
13
|
+
* single-spa, островков Astro и любых сценариев, где один `RootThemeProvider` нельзя поставить
|
|
14
|
+
* общим предком в одном дереве. Хранится в `globalThis` через `Symbol.for`, поэтому любой модуль
|
|
15
|
+
* получает один экземпляр без проброса по импортам и переживает несколько копий пакета.
|
|
16
|
+
*
|
|
17
|
+
* Shell контейнера меняет оформление через `setAppearance({ colorScheme, density, ... })` — все
|
|
18
|
+
* подписанные `RootThemeProvider` реэмитят полный набор `sn-*` классов. Когда у приложения один
|
|
19
|
+
* React-корень — используйте `<RootThemeProvider value={…} />`, этот хелпер нужен только для multi-root.
|
|
20
|
+
*
|
|
21
|
+
* ```tsx
|
|
22
|
+
* // там, где меняется тема (один раз):
|
|
23
|
+
* getGlobalThemeStore().setAppearance({ colorScheme });
|
|
24
|
+
*
|
|
25
|
+
* // в каждом независимом корне:
|
|
26
|
+
* <RootThemeProvider store={getGlobalThemeStore().store} rootRef={htmlRef}>{root}</RootThemeProvider>;
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function getGlobalThemeStore(): GlobalThemeStore;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getGlobalThemeStore = getGlobalThemeStore;
|
|
4
|
+
const keys_1 = require("../keys");
|
|
5
|
+
const mergeAppearance_1 = require("../utils/mergeAppearance");
|
|
6
|
+
// Ключ — из локального keys.ts (одна точка бампа версии для контекста и стора домена).
|
|
7
|
+
const REGISTRY_KEY = Symbol.for(keys_1.THEME_KEYS.store);
|
|
8
|
+
// SSR-snapshot — пустое оформление без сеттера. Мутабельный глобал на сервере запрещён (утечёт
|
|
9
|
+
// между запросами), поэтому серверный рендер всегда отдаёт дефолт; реальные классы на SSR ставит
|
|
10
|
+
// строка на `<html>` (см. providers-standard.md).
|
|
11
|
+
const SERVER_VALUE = { appearance: {}, setAppearance: undefined };
|
|
12
|
+
const APPEARANCE_KEYS = ['colorScheme', 'brand', 'brandRole', 'density', 'acrylic'];
|
|
13
|
+
function isSameAppearance(a, b) {
|
|
14
|
+
return APPEARANCE_KEYS.every(key => a[key] === b[key]);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Глобальный singleton-стор оформления, общий для всех React-корней процесса: микрофронтов
|
|
18
|
+
* single-spa, островков Astro и любых сценариев, где один `RootThemeProvider` нельзя поставить
|
|
19
|
+
* общим предком в одном дереве. Хранится в `globalThis` через `Symbol.for`, поэтому любой модуль
|
|
20
|
+
* получает один экземпляр без проброса по импортам и переживает несколько копий пакета.
|
|
21
|
+
*
|
|
22
|
+
* Shell контейнера меняет оформление через `setAppearance({ colorScheme, density, ... })` — все
|
|
23
|
+
* подписанные `RootThemeProvider` реэмитят полный набор `sn-*` классов. Когда у приложения один
|
|
24
|
+
* React-корень — используйте `<RootThemeProvider value={…} />`, этот хелпер нужен только для multi-root.
|
|
25
|
+
*
|
|
26
|
+
* ```tsx
|
|
27
|
+
* // там, где меняется тема (один раз):
|
|
28
|
+
* getGlobalThemeStore().setAppearance({ colorScheme });
|
|
29
|
+
*
|
|
30
|
+
* // в каждом независимом корне:
|
|
31
|
+
* <RootThemeProvider store={getGlobalThemeStore().store} rootRef={htmlRef}>{root}</RootThemeProvider>;
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
function getGlobalThemeStore() {
|
|
35
|
+
const registry = globalThis;
|
|
36
|
+
if (!registry[REGISTRY_KEY]) {
|
|
37
|
+
let appearance = {};
|
|
38
|
+
// Стабильный snapshot: новый объект только после смены оформления, иначе `useSyncExternalStore`
|
|
39
|
+
// зациклится на «getSnapshot вернул новую ссылку».
|
|
40
|
+
let cache;
|
|
41
|
+
const listeners = new Set();
|
|
42
|
+
const setAppearance = (patch) => {
|
|
43
|
+
const next = (0, mergeAppearance_1.mergeAppearance)(appearance, patch);
|
|
44
|
+
if (isSameAppearance(appearance, next))
|
|
45
|
+
return;
|
|
46
|
+
appearance = next;
|
|
47
|
+
cache = undefined;
|
|
48
|
+
listeners.forEach(listener => listener());
|
|
49
|
+
};
|
|
50
|
+
const read = () => {
|
|
51
|
+
if (!cache) {
|
|
52
|
+
cache = { appearance, setAppearance };
|
|
53
|
+
}
|
|
54
|
+
return cache;
|
|
55
|
+
};
|
|
56
|
+
registry[REGISTRY_KEY] = {
|
|
57
|
+
store: {
|
|
58
|
+
subscribe: onChange => {
|
|
59
|
+
listeners.add(onChange);
|
|
60
|
+
return () => {
|
|
61
|
+
listeners.delete(onChange);
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
getSnapshot: read,
|
|
65
|
+
getServerSnapshot: () => SERVER_VALUE,
|
|
66
|
+
},
|
|
67
|
+
getAppearance: () => appearance,
|
|
68
|
+
setAppearance,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return registry[REGISTRY_KEY];
|
|
72
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './globalStore';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./globalStore"), exports);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BRAND, BRAND_ROLE, DENSITY } from '../constants/appearance';
|
|
2
|
+
import { ColorScheme } from './colorScheme';
|
|
3
|
+
export type Density = (typeof DENSITY)[keyof typeof DENSITY];
|
|
4
|
+
export type Brand = (typeof BRAND)[keyof typeof BRAND];
|
|
5
|
+
export type BrandRole = (typeof BRAND_ROLE)[keyof typeof BRAND_ROLE];
|
|
6
|
+
/**
|
|
7
|
+
* Набор осей оформления, которые определяют полный набор `sn-*` классов на DOM-границе. Любая
|
|
8
|
+
* незаданная ось наследуется от вышестоящего провайдера (слияние в `ChildThemeProvider`) — но на сам
|
|
9
|
+
* элемент всё равно эмитится **полный** набор классов, потому что токены `@cloud-ru/figma-variables`
|
|
10
|
+
* не переопределяются по одной оси через CSS-каскад (см. providers-standard.md).
|
|
11
|
+
*/
|
|
12
|
+
export type ThemeAppearance = {
|
|
13
|
+
/** Цветовая схема — `sn-light` / `sn-dark`. */
|
|
14
|
+
colorScheme?: ColorScheme;
|
|
15
|
+
/** Бренд — `sn-brandA` … */
|
|
16
|
+
brand?: Brand;
|
|
17
|
+
/** Роль бренда (палитра) — `sn-main` … */
|
|
18
|
+
brandRole?: BrandRole;
|
|
19
|
+
/** Плотность — `sn-comfort` / `sn-compact` / `sn-spacious`. */
|
|
20
|
+
density?: Density;
|
|
21
|
+
/** Акрил (blur-материал) — `sn-yes` при `true`, иначе `sn-no`. */
|
|
22
|
+
acrylic?: boolean;
|
|
23
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { COLOR_SCHEME, THEME_OVERRIDE } from '../constants/colorScheme';
|
|
2
|
+
/** Разрешённая цветовая схема (то, что реально применяется на корень). */
|
|
3
|
+
export type ColorScheme = (typeof COLOR_SCHEME)[keyof typeof COLOR_SCHEME];
|
|
4
|
+
/** Пользовательский выбор: фиксированная схема или `system`. */
|
|
5
|
+
export type ThemeOverride = (typeof THEME_OVERRIDE)[keyof typeof THEME_OVERRIDE];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./appearance"), exports);
|
|
18
|
+
__exportStar(require("./colorScheme"), exports);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ColorScheme, ThemeOverride } from '../types/colorScheme';
|
|
2
|
+
type HeadersLike = {
|
|
3
|
+
get(name: string): string | null;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* SSR-резолв override из cookie заголовка запроса (стенд-ин бэкенд-сессии: в сервисе тут читается
|
|
7
|
+
* сохранённая тема пользователя). Прокидывается в `useColorScheme({ initialOverride })` —
|
|
8
|
+
* детерминированный первый рендер без hydration mismatch и без прыжка подсветки переключателя.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getThemeOverrideFromHeaders(headers: HeadersLike, options?: {
|
|
11
|
+
storageKey?: string;
|
|
12
|
+
}): ThemeOverride | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* SSR-резолв итоговой схемы из заголовков (зеркало `getAdaptive(userAgent)` для adaptive):
|
|
15
|
+
* cookie `snack-uikit-theme` даёт override, `Sec-CH-Prefers-Color-Scheme` (client hint) — системную тему.
|
|
16
|
+
* Включите client hint ответными заголовками `Accept-CH`/`Critical-CH`, иначе на первом визите
|
|
17
|
+
* хинта нет (системная вернётся `light`), а корректную тему доставит inline-bootstrap до отрисовки.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getColorSchemeFromHeaders(headers: HeadersLike, options?: {
|
|
20
|
+
storageKey?: string;
|
|
21
|
+
}): ColorScheme;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getThemeOverrideFromHeaders = getThemeOverrideFromHeaders;
|
|
4
|
+
exports.getColorSchemeFromHeaders = getColorSchemeFromHeaders;
|
|
5
|
+
const colorScheme_1 = require("../constants/colorScheme");
|
|
6
|
+
const resolveColorScheme_1 = require("./resolveColorScheme");
|
|
7
|
+
function isOverride(value) {
|
|
8
|
+
return value === colorScheme_1.THEME_OVERRIDE.Light || value === colorScheme_1.THEME_OVERRIDE.Dark || value === colorScheme_1.THEME_OVERRIDE.System;
|
|
9
|
+
}
|
|
10
|
+
function parseOverrideFromCookie(cookieHeader, storageKey) {
|
|
11
|
+
if (!cookieHeader) {
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
for (const part of cookieHeader.split(';')) {
|
|
15
|
+
const [name, ...rest] = part.trim().split('=');
|
|
16
|
+
if (name === storageKey) {
|
|
17
|
+
const value = decodeURIComponent(rest.join('='));
|
|
18
|
+
if (isOverride(value)) {
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* SSR-резолв override из cookie заголовка запроса (стенд-ин бэкенд-сессии: в сервисе тут читается
|
|
27
|
+
* сохранённая тема пользователя). Прокидывается в `useColorScheme({ initialOverride })` —
|
|
28
|
+
* детерминированный первый рендер без hydration mismatch и без прыжка подсветки переключателя.
|
|
29
|
+
*/
|
|
30
|
+
function getThemeOverrideFromHeaders(headers, options) {
|
|
31
|
+
const storageKey = options?.storageKey ?? colorScheme_1.THEME_OVERRIDE_STORAGE_KEY;
|
|
32
|
+
return parseOverrideFromCookie(headers.get('cookie'), storageKey);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* SSR-резолв итоговой схемы из заголовков (зеркало `getAdaptive(userAgent)` для adaptive):
|
|
36
|
+
* cookie `snack-uikit-theme` даёт override, `Sec-CH-Prefers-Color-Scheme` (client hint) — системную тему.
|
|
37
|
+
* Включите client hint ответными заголовками `Accept-CH`/`Critical-CH`, иначе на первом визите
|
|
38
|
+
* хинта нет (системная вернётся `light`), а корректную тему доставит inline-bootstrap до отрисовки.
|
|
39
|
+
*/
|
|
40
|
+
function getColorSchemeFromHeaders(headers, options) {
|
|
41
|
+
const override = getThemeOverrideFromHeaders(headers, options);
|
|
42
|
+
const systemPrefersDark = headers.get('sec-ch-prefers-color-scheme') === colorScheme_1.COLOR_SCHEME.Dark;
|
|
43
|
+
return (0, resolveColorScheme_1.resolveColorScheme)(override, systemPrefersDark);
|
|
44
|
+
}
|