@oxyhq/bloom 0.41.0 → 0.42.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/lib/commonjs/theme/BloomThemeProvider.js +18 -7
- package/lib/commonjs/theme/BloomThemeProvider.js.map +1 -1
- package/lib/commonjs/theme/apply-dark-class.js +2 -2
- package/lib/commonjs/theme/apply-dark-class.js.map +1 -1
- package/lib/commonjs/theme/build-theme-from-seed.js +17 -5
- package/lib/commonjs/theme/build-theme-from-seed.js.map +1 -1
- package/lib/commonjs/theme/build-theme.js +9 -6
- package/lib/commonjs/theme/build-theme.js.map +1 -1
- package/lib/commonjs/theme/color-engine/index.js +8 -1
- package/lib/commonjs/theme/color-engine/index.js.map +1 -1
- package/lib/commonjs/theme/color-engine/scheme-variants.js +42 -18
- package/lib/commonjs/theme/color-engine/scheme-variants.js.map +1 -1
- package/lib/commonjs/theme/color-engine/seed-from-image.js +11 -1
- package/lib/commonjs/theme/color-engine/seed-from-image.js.map +1 -1
- package/lib/commonjs/theme/color-presets.js.map +1 -1
- package/lib/commonjs/theme/color-scope/seed-scope.js +3 -1
- package/lib/commonjs/theme/color-scope/seed-scope.js.map +1 -1
- package/lib/commonjs/theme/color-scope/style-builder.js +2 -2
- package/lib/commonjs/theme/color-scope/style-builder.js.map +1 -1
- package/lib/commonjs/theme/index.js.map +1 -1
- package/lib/commonjs/theme/native-root-vars.js +1 -1
- package/lib/commonjs/theme/native-root-vars.js.map +1 -1
- package/lib/commonjs/theme/native-root-vars.native.js +2 -2
- package/lib/commonjs/theme/native-root-vars.native.js.map +1 -1
- package/lib/commonjs/theme/native-root-vars.web.js +1 -1
- package/lib/commonjs/theme/native-root-vars.web.js.map +1 -1
- package/lib/commonjs/theme/preset-vars.js +16 -2
- package/lib/commonjs/theme/preset-vars.js.map +1 -1
- package/lib/commonjs/theme/token-registry.js +2 -2
- package/lib/commonjs/theme/token-registry.js.map +1 -1
- package/lib/module/theme/BloomThemeProvider.js +18 -7
- package/lib/module/theme/BloomThemeProvider.js.map +1 -1
- package/lib/module/theme/apply-dark-class.js +2 -2
- package/lib/module/theme/apply-dark-class.js.map +1 -1
- package/lib/module/theme/build-theme-from-seed.js +18 -5
- package/lib/module/theme/build-theme-from-seed.js.map +1 -1
- package/lib/module/theme/build-theme.js +9 -6
- package/lib/module/theme/build-theme.js.map +1 -1
- package/lib/module/theme/color-engine/index.js +8 -1
- package/lib/module/theme/color-engine/index.js.map +1 -1
- package/lib/module/theme/color-engine/scheme-variants.js +43 -18
- package/lib/module/theme/color-engine/scheme-variants.js.map +1 -1
- package/lib/module/theme/color-engine/seed-from-image.js +11 -1
- package/lib/module/theme/color-engine/seed-from-image.js.map +1 -1
- package/lib/module/theme/color-presets.js.map +1 -1
- package/lib/module/theme/color-scope/seed-scope.js +3 -1
- package/lib/module/theme/color-scope/seed-scope.js.map +1 -1
- package/lib/module/theme/color-scope/style-builder.js +2 -2
- package/lib/module/theme/color-scope/style-builder.js.map +1 -1
- package/lib/module/theme/index.js.map +1 -1
- package/lib/module/theme/native-root-vars.js +1 -1
- package/lib/module/theme/native-root-vars.js.map +1 -1
- package/lib/module/theme/native-root-vars.native.js +2 -2
- package/lib/module/theme/native-root-vars.native.js.map +1 -1
- package/lib/module/theme/native-root-vars.web.js +1 -1
- package/lib/module/theme/native-root-vars.web.js.map +1 -1
- package/lib/module/theme/preset-vars.js +17 -2
- package/lib/module/theme/preset-vars.js.map +1 -1
- package/lib/module/theme/token-registry.js +2 -2
- package/lib/module/theme/token-registry.js.map +1 -1
- package/lib/typescript/commonjs/theme/BloomThemeProvider.d.ts +15 -1
- package/lib/typescript/commonjs/theme/BloomThemeProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/apply-dark-class.d.ts +2 -1
- package/lib/typescript/commonjs/theme/apply-dark-class.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/build-theme-from-seed.d.ts +13 -2
- package/lib/typescript/commonjs/theme/build-theme-from-seed.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/build-theme.d.ts +2 -1
- package/lib/typescript/commonjs/theme/build-theme.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-engine/index.d.ts +17 -1
- package/lib/typescript/commonjs/theme/color-engine/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-engine/scheme-variants.d.ts +25 -7
- package/lib/typescript/commonjs/theme/color-engine/scheme-variants.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-engine/seed-from-image.d.ts +11 -1
- package/lib/typescript/commonjs/theme/color-engine/seed-from-image.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-presets.d.ts +14 -0
- package/lib/typescript/commonjs/theme/color-presets.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-scope/seed-scope.d.ts +12 -0
- package/lib/typescript/commonjs/theme/color-scope/seed-scope.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/color-scope/style-builder.d.ts +2 -1
- package/lib/typescript/commonjs/theme/color-scope/style-builder.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/index.d.ts +3 -1
- package/lib/typescript/commonjs/theme/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/native-root-vars.d.ts +2 -1
- package/lib/typescript/commonjs/theme/native-root-vars.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/native-root-vars.native.d.ts +2 -1
- package/lib/typescript/commonjs/theme/native-root-vars.native.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/native-root-vars.web.d.ts +2 -1
- package/lib/typescript/commonjs/theme/native-root-vars.web.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/preset-vars.d.ts +12 -1
- package/lib/typescript/commonjs/theme/preset-vars.d.ts.map +1 -1
- package/lib/typescript/commonjs/theme/token-registry.d.ts +2 -1
- package/lib/typescript/commonjs/theme/token-registry.d.ts.map +1 -1
- package/lib/typescript/module/theme/BloomThemeProvider.d.ts +15 -1
- package/lib/typescript/module/theme/BloomThemeProvider.d.ts.map +1 -1
- package/lib/typescript/module/theme/apply-dark-class.d.ts +2 -1
- package/lib/typescript/module/theme/apply-dark-class.d.ts.map +1 -1
- package/lib/typescript/module/theme/build-theme-from-seed.d.ts +13 -2
- package/lib/typescript/module/theme/build-theme-from-seed.d.ts.map +1 -1
- package/lib/typescript/module/theme/build-theme.d.ts +2 -1
- package/lib/typescript/module/theme/build-theme.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-engine/index.d.ts +17 -1
- package/lib/typescript/module/theme/color-engine/index.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-engine/scheme-variants.d.ts +25 -7
- package/lib/typescript/module/theme/color-engine/scheme-variants.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-engine/seed-from-image.d.ts +11 -1
- package/lib/typescript/module/theme/color-engine/seed-from-image.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-presets.d.ts +14 -0
- package/lib/typescript/module/theme/color-presets.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-scope/seed-scope.d.ts +12 -0
- package/lib/typescript/module/theme/color-scope/seed-scope.d.ts.map +1 -1
- package/lib/typescript/module/theme/color-scope/style-builder.d.ts +2 -1
- package/lib/typescript/module/theme/color-scope/style-builder.d.ts.map +1 -1
- package/lib/typescript/module/theme/index.d.ts +3 -1
- package/lib/typescript/module/theme/index.d.ts.map +1 -1
- package/lib/typescript/module/theme/native-root-vars.d.ts +2 -1
- package/lib/typescript/module/theme/native-root-vars.d.ts.map +1 -1
- package/lib/typescript/module/theme/native-root-vars.native.d.ts +2 -1
- package/lib/typescript/module/theme/native-root-vars.native.d.ts.map +1 -1
- package/lib/typescript/module/theme/native-root-vars.web.d.ts +2 -1
- package/lib/typescript/module/theme/native-root-vars.web.d.ts.map +1 -1
- package/lib/typescript/module/theme/preset-vars.d.ts +12 -1
- package/lib/typescript/module/theme/preset-vars.d.ts.map +1 -1
- package/lib/typescript/module/theme/token-registry.d.ts +2 -1
- package/lib/typescript/module/theme/token-registry.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/theme.test.ts +3 -0
- package/src/theme/BloomThemeProvider.tsx +38 -8
- package/src/theme/__tests__/explicit-accents.test.ts +129 -0
- package/src/theme/apply-dark-class.ts +7 -2
- package/src/theme/build-theme-from-seed.ts +25 -2
- package/src/theme/build-theme.ts +9 -3
- package/src/theme/color-engine/index.ts +27 -1
- package/src/theme/color-engine/scheme-variants.ts +108 -30
- package/src/theme/color-engine/seed-from-image.ts +11 -1
- package/src/theme/color-presets.ts +14 -0
- package/src/theme/color-scope/seed-scope.ts +14 -0
- package/src/theme/color-scope/style-builder.ts +3 -1
- package/src/theme/index.ts +3 -1
- package/src/theme/native-root-vars.native.ts +7 -2
- package/src/theme/native-root-vars.ts +6 -1
- package/src/theme/native-root-vars.web.ts +6 -1
- package/src/theme/preset-vars.ts +19 -0
- package/src/theme/token-registry.ts +3 -2
|
@@ -27,6 +27,18 @@ export interface SeedScopeOptions {
|
|
|
27
27
|
variant?: SchemeVariant;
|
|
28
28
|
/** −1 (low) … 0 (normal) … 1 (high). Defaults to 0. */
|
|
29
29
|
contrastLevel?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Optional explicit secondary-accent seed, `#rrggbb`. Pins the secondary palette
|
|
32
|
+
* to this brand colour instead of the derived rotation (M3 role tones still
|
|
33
|
+
* apply). Handy for an artwork-extracted theme: pass the second-ranked seed from
|
|
34
|
+
* `seedsFromImagePixels` (`seeds[1]`) as a distinct secondary accent.
|
|
35
|
+
*/
|
|
36
|
+
secondarySeed?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Optional explicit tertiary-accent seed, `#rrggbb`. Same semantics; pass
|
|
39
|
+
* `seedsFromImagePixels`' `seeds[2]` for a third artwork-derived accent.
|
|
40
|
+
*/
|
|
41
|
+
tertiarySeed?: string;
|
|
30
42
|
}
|
|
31
43
|
/**
|
|
32
44
|
* Build the CSS custom-property map for an ARBITRARY seed colour — the dynamic
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seed-scope.d.ts","sourceRoot":"","sources":["../../../../../src/theme/color-scope/seed-scope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAG1F;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA0C9E;AAED,mEAAmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IACvB;;;;OAIG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"seed-scope.d.ts","sourceRoot":"","sources":["../../../../../src/theme/color-scope/seed-scope.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAG1F;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA0C9E;AAED,mEAAmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IACvB;;;;OAIG;IACH,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoBpF"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
import { type StyleProp, type ViewStyle } from 'react-native';
|
|
3
3
|
import type { AppColorName } from '../color-presets';
|
|
4
|
+
import type { ExplicitAccents } from '../preset-vars';
|
|
4
5
|
/**
|
|
5
6
|
* Component that provides inline CSS variables to a subtree via NativeWind's
|
|
6
7
|
* (react-native-css's) real `VariableContext`. The runtime strips the leading
|
|
@@ -21,7 +22,7 @@ export type VariableContextProviderComponent = React.ComponentType<{
|
|
|
21
22
|
* inherited custom properties like `--color-primary: var(--primary)` compute at
|
|
22
23
|
* the document root and would otherwise keep pointing at the app-wide preset.
|
|
23
24
|
*/
|
|
24
|
-
export declare function buildScopeVars(colorPreset: AppColorName, mode: 'light' | 'dark'): Record<string, string>;
|
|
25
|
+
export declare function buildScopeVars(colorPreset: AppColorName, mode: 'light' | 'dark', accents?: ExplicitAccents): Record<string, string>;
|
|
25
26
|
/**
|
|
26
27
|
* Resolve NativeWind's `VariableContextProvider` — the canonical, non-deprecated
|
|
27
28
|
* react-native-css v3 API for providing inline CSS variables to a subtree. It
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"style-builder.d.ts","sourceRoot":"","sources":["../../../../../src/theme/color-scope/style-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAY,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAGxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"style-builder.d.ts","sourceRoot":"","sources":["../../../../../src/theme/color-scope/style-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAY,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAGxE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAGtD;;;;;GAKG;AACH,MAAM,MAAM,gCAAgC,GAAG,KAAK,CAAC,aAAa,CAAC;IACjE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B,CAAC,CAAC;AASH;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,WAAW,EAAE,YAAY,EACzB,IAAI,EAAE,OAAO,GAAG,MAAM,EACtB,OAAO,CAAC,EAAE,eAAe,GACxB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAYxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,IAAI,gCAAgC,GAAG,IAAI,CAKpF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,WAAW,EAAE,YAAY,EACzB,IAAI,EAAE,OAAO,GAAG,MAAM,GACrB,SAAS,CAAC,SAAS,CAAC,CAKtB"}
|
|
@@ -7,9 +7,10 @@ export type { BloomSeedScopeProps } from './seed-scope';
|
|
|
7
7
|
export { buildSeedScopeVars, roleColorsToPresetTokens } from './color-scope/seed-scope';
|
|
8
8
|
export type { SeedScopeOptions } from './color-scope/seed-scope';
|
|
9
9
|
export { generateRoleColors, seedHexFromImagePixels, seedsFromImagePixels, quantizeImage, argbFromHex, hexFromArgb, argbFromRgb, redFromArgb, greenFromArgb, blueFromArgb, } from './color-engine';
|
|
10
|
-
export type { RoleColors, GenerateOptions, RoleName, SchemeVariant } from './color-engine';
|
|
10
|
+
export type { RoleColors, GenerateOptions, RoleName, SchemeVariant, AccentSources } from './color-engine';
|
|
11
11
|
export { buildTheme, STATUS_COLORS } from './build-theme';
|
|
12
12
|
export { buildThemeFromSeed, buildColorsFromSeed } from './build-theme-from-seed';
|
|
13
|
+
export type { SeedAccents } from './build-theme-from-seed';
|
|
13
14
|
export { THEME_GRADIENTS } from './gradients';
|
|
14
15
|
export { useTheme, useThemeColor, useBloomTheme } from './use-theme';
|
|
15
16
|
export { useNavigationTheme } from './use-navigation-theme';
|
|
@@ -20,6 +21,7 @@ export { APP_COLOR_NAMES, PREMIUM_COLOR_NAMES, APP_COLOR_PRESETS, HEX_TO_APP_COL
|
|
|
20
21
|
export { parseRgb, withAlpha } from './color-utils';
|
|
21
22
|
export type { RgbChannels } from './color-utils';
|
|
22
23
|
export { getPresetVars, applyPresetVarsToDocument } from './preset-vars';
|
|
24
|
+
export type { ExplicitAccents } from './preset-vars';
|
|
23
25
|
export { applyDarkClass } from './apply-dark-class';
|
|
24
26
|
export { setColorSchemeSafe } from './set-color-scheme-safe';
|
|
25
27
|
export { initCssInteropDarkMode } from './init-css-interop';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/theme/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,YAAY,EACV,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACpE,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACxF,YAAY,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAIjE,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,EACpB,aAAa,EACb,WAAW,EACX,WAAW,EACX,WAAW,EACX,WAAW,EACX,aAAa,EACb,YAAY,GACb,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/theme/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,YAAY,EACV,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACpE,YAAY,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACxF,YAAY,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAIjE,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,EACpB,aAAa,EACb,WAAW,EACX,WAAW,EACX,WAAW,EACX,WAAW,EACX,aAAa,EACb,YAAY,GACb,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC1G,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAClF,YAAY,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AACnF,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAC5F,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAClF,OAAO,EACL,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACpD,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AACzE,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,YAAY,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AppColorName } from './color-presets';
|
|
2
|
+
import type { ExplicitAccents } from './preset-vars';
|
|
2
3
|
/**
|
|
3
4
|
* Platform-neutral DEFAULT variant of `applyNativeRootVars` — a no-op.
|
|
4
5
|
*
|
|
@@ -29,5 +30,5 @@ import { type AppColorName } from './color-presets';
|
|
|
29
30
|
* The signature is intentionally identical to those variants so the import site in
|
|
30
31
|
* `BloomThemeProvider` is platform-agnostic.
|
|
31
32
|
*/
|
|
32
|
-
export declare function applyNativeRootVars(_colorPreset: AppColorName, _mode: 'light' | 'dark'): void;
|
|
33
|
+
export declare function applyNativeRootVars(_colorPreset: AppColorName, _mode: 'light' | 'dark', _accents?: ExplicitAccents): void;
|
|
33
34
|
//# sourceMappingURL=native-root-vars.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-root-vars.d.ts","sourceRoot":"","sources":["../../../../src/theme/native-root-vars.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"native-root-vars.d.ts","sourceRoot":"","sources":["../../../../src/theme/native-root-vars.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,YAAY,EAC1B,KAAK,EAAE,OAAO,GAAG,MAAM,EACvB,QAAQ,CAAC,EAAE,eAAe,GACzB,IAAI,CAGN"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AppColorName } from './color-presets';
|
|
2
|
+
import type { ExplicitAccents } from './preset-vars';
|
|
2
3
|
/**
|
|
3
4
|
* Publish the active color preset's CSS custom properties into react-native-css's
|
|
4
5
|
* GLOBAL root-variable family so they reach the ENTIRE native app tree — including
|
|
@@ -100,5 +101,5 @@ import { type AppColorName } from './color-presets';
|
|
|
100
101
|
* Defensive guard: if a host ships a react-native-css build that doesn't expose
|
|
101
102
|
* `rootVariables` as a callable (it always does on @3), we bail rather than throw.
|
|
102
103
|
*/
|
|
103
|
-
export declare function applyNativeRootVars(colorPreset: AppColorName, mode: 'light' | 'dark'): void;
|
|
104
|
+
export declare function applyNativeRootVars(colorPreset: AppColorName, mode: 'light' | 'dark', accents?: ExplicitAccents): void;
|
|
104
105
|
//# sourceMappingURL=native-root-vars.native.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-root-vars.native.d.ts","sourceRoot":"","sources":["../../../../src/theme/native-root-vars.native.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"native-root-vars.native.d.ts","sourceRoot":"","sources":["../../../../src/theme/native-root-vars.native.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuFG;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,YAAY,EACzB,IAAI,EAAE,OAAO,GAAG,MAAM,EACtB,OAAO,CAAC,EAAE,eAAe,GACxB,IAAI,CASN"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AppColorName } from './color-presets';
|
|
2
|
+
import type { ExplicitAccents } from './preset-vars';
|
|
2
3
|
/**
|
|
3
4
|
* Web fork of `applyNativeRootVars` — a no-op.
|
|
4
5
|
*
|
|
@@ -16,5 +17,5 @@ import { type AppColorName } from './color-presets';
|
|
|
16
17
|
* The signature is intentionally identical to the plain variant so the import
|
|
17
18
|
* site in `BloomThemeProvider` is platform-agnostic.
|
|
18
19
|
*/
|
|
19
|
-
export declare function applyNativeRootVars(_colorPreset: AppColorName, _mode: 'light' | 'dark'): void;
|
|
20
|
+
export declare function applyNativeRootVars(_colorPreset: AppColorName, _mode: 'light' | 'dark', _accents?: ExplicitAccents): void;
|
|
20
21
|
//# sourceMappingURL=native-root-vars.web.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-root-vars.web.d.ts","sourceRoot":"","sources":["../../../../src/theme/native-root-vars.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"native-root-vars.web.d.ts","sourceRoot":"","sources":["../../../../src/theme/native-root-vars.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAErD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,YAAY,EAC1B,KAAK,EAAE,OAAO,GAAG,MAAM,EACvB,QAAQ,CAAC,EAAE,eAAe,GACzB,IAAI,CAEN"}
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { type AppColorName, type PresetTokens } from './color-presets';
|
|
2
|
+
/**
|
|
3
|
+
* Optional explicit accent-colour overrides (`#rrggbb`) that PIN the secondary
|
|
4
|
+
* and/or tertiary palette app-wide, regardless of the active preset's own seed.
|
|
5
|
+
* Threaded from `BloomThemeProvider`'s `secondaryColor` / `tertiaryColor` props
|
|
6
|
+
* down through the whole token pipeline. When both are omitted, the preset's own
|
|
7
|
+
* (usually derived) accents are used — output is unchanged.
|
|
8
|
+
*/
|
|
9
|
+
export interface ExplicitAccents {
|
|
10
|
+
secondaryHex?: string;
|
|
11
|
+
tertiaryHex?: string;
|
|
12
|
+
}
|
|
2
13
|
/**
|
|
3
14
|
* Map a preset (seed + variant) and mode onto Bloom's canonical CSS-var token
|
|
4
15
|
* set, sourcing EVERY colour from the colour engine's Material-3 role output.
|
|
@@ -16,7 +27,7 @@ import { type AppColorName, type PresetTokens } from './color-presets';
|
|
|
16
27
|
* This is the single source of truth for extended theming vars — consumer apps
|
|
17
28
|
* must not redefine these per-app.
|
|
18
29
|
*/
|
|
19
|
-
export declare function getPresetVars(colorName: AppColorName, mode: 'light' | 'dark'): PresetTokens;
|
|
30
|
+
export declare function getPresetVars(colorName: AppColorName, mode: 'light' | 'dark', accents?: ExplicitAccents): PresetTokens;
|
|
20
31
|
/**
|
|
21
32
|
* Apply a preset's extended vars to `document.documentElement`. Web-only no-op
|
|
22
33
|
* on native. `BloomThemeProvider` already writes the base preset vars on web;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preset-vars.d.ts","sourceRoot":"","sources":["../../../../src/theme/preset-vars.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"preset-vars.d.ts","sourceRoot":"","sources":["../../../../src/theme/preset-vars.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAG1F;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAOD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,YAAY,EACvB,IAAI,EAAE,OAAO,GAAG,MAAM,EACtB,OAAO,CAAC,EAAE,eAAe,GACxB,YAAY,CA8Dd;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,YAAY,EACvB,IAAI,EAAE,OAAO,GAAG,MAAM,GACrB,IAAI,CAYN"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { APP_COLOR_NAMES, type AppColorName } from './color-presets';
|
|
2
|
+
import { type ExplicitAccents } from './preset-vars';
|
|
2
3
|
/**
|
|
3
4
|
* The canonical set of Bloom theme token names — WITHOUT the leading `--`.
|
|
4
5
|
*
|
|
@@ -48,6 +49,6 @@ export declare function hslToSrgb(triple: string): string;
|
|
|
48
49
|
* (`__fixtures__/golden-resolved-tokens.json`), guaranteeing the visible palette
|
|
49
50
|
* matches the previous resolved-color pipeline byte-for-byte.
|
|
50
51
|
*/
|
|
51
|
-
export declare function getResolvedTokens(preset: AppColorName, mode: 'light' | 'dark'): Record<string, string>;
|
|
52
|
+
export declare function getResolvedTokens(preset: AppColorName, mode: 'light' | 'dark', accents?: ExplicitAccents): Record<string, string>;
|
|
52
53
|
export { APP_COLOR_NAMES };
|
|
53
54
|
//# sourceMappingURL=token-registry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-registry.d.ts","sourceRoot":"","sources":["../../../../src/theme/token-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"token-registry.d.ts","sourceRoot":"","sources":["../../../../src/theme/token-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAEpE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,6jBAqCnB,CAAC;AAEX,gDAAgD;AAChD,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAW/D;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CA+ChD;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,IAAI,EAAE,OAAO,GAAG,MAAM,EACtB,OAAO,CAAC,EAAE,eAAe,GACxB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOxB;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -158,6 +158,9 @@ function createMockColors(): ThemeColors {
|
|
|
158
158
|
primaryLight: '#e0f7fa',
|
|
159
159
|
primaryDark: '#003d44',
|
|
160
160
|
secondary: '#005c67',
|
|
161
|
+
secondaryForeground: '#FFFFFF',
|
|
162
|
+
tertiary: '#7a4b3a',
|
|
163
|
+
tertiaryForeground: '#FFFFFF',
|
|
161
164
|
tint: '#005c67',
|
|
162
165
|
icon: '#666',
|
|
163
166
|
iconActive: '#005c67',
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
type BloomThemeStorage,
|
|
38
38
|
type SyncReadResult,
|
|
39
39
|
} from './persistence';
|
|
40
|
+
import type { ExplicitAccents } from './preset-vars';
|
|
40
41
|
import { setColorSchemeSafe } from './set-color-scheme-safe';
|
|
41
42
|
import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';
|
|
42
43
|
import type { Theme, ThemeMode } from './types';
|
|
@@ -135,6 +136,21 @@ export interface BloomThemeProviderProps {
|
|
|
135
136
|
*/
|
|
136
137
|
nativeWrapperStyle?: StyleProp<ViewStyle>;
|
|
137
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Optional app-wide secondary-accent colour, `#rrggbb`. When set, the whole
|
|
141
|
+
* theme's secondary family (`--secondary`, `bg-secondary`, `colors.secondary`,
|
|
142
|
+
* plus the container / on-colour roles) is PINNED to this brand colour instead
|
|
143
|
+
* of the derived hue-rotation of the active preset's seed — for a brand with a
|
|
144
|
+
* distinct accent (e.g. blue primary + yellow secondary). M3 role tones still
|
|
145
|
+
* apply. Omit to keep the preset's derived accent.
|
|
146
|
+
*/
|
|
147
|
+
secondaryColor?: string;
|
|
148
|
+
/**
|
|
149
|
+
* Optional app-wide tertiary-accent colour, `#rrggbb`. Same semantics as
|
|
150
|
+
* {@link BloomThemeProviderProps.secondaryColor} for the tertiary family.
|
|
151
|
+
*/
|
|
152
|
+
tertiaryColor?: string;
|
|
153
|
+
|
|
138
154
|
children: React.ReactNode;
|
|
139
155
|
}
|
|
140
156
|
|
|
@@ -296,10 +312,24 @@ export function BloomThemeProvider({
|
|
|
296
312
|
fonts = true,
|
|
297
313
|
onFontsLoading,
|
|
298
314
|
nativeWrapperStyle,
|
|
315
|
+
secondaryColor,
|
|
316
|
+
tertiaryColor,
|
|
299
317
|
children,
|
|
300
318
|
}: BloomThemeProviderProps) {
|
|
301
319
|
const rnScheme = useRNColorScheme();
|
|
302
320
|
|
|
321
|
+
// The app-wide pinned accents, if any. Memoized so its identity only changes
|
|
322
|
+
// when a prop changes (keeps the effect/memo deps below stable). Left
|
|
323
|
+
// `undefined` when neither is set so every downstream call is byte-identical
|
|
324
|
+
// to the no-accent path.
|
|
325
|
+
const explicitAccents = useMemo<ExplicitAccents | undefined>(
|
|
326
|
+
() =>
|
|
327
|
+
secondaryColor === undefined && tertiaryColor === undefined
|
|
328
|
+
? undefined
|
|
329
|
+
: { secondaryHex: secondaryColor, tertiaryHex: tertiaryColor },
|
|
330
|
+
[secondaryColor, tertiaryColor],
|
|
331
|
+
);
|
|
332
|
+
|
|
303
333
|
const { mode, colorPreset, setMode, setColorPreset, resetTheme, hydrated } = useThemeState({
|
|
304
334
|
controlledMode,
|
|
305
335
|
controlledPreset,
|
|
@@ -322,8 +352,8 @@ export function BloomThemeProvider({
|
|
|
322
352
|
useIsomorphicLayoutEffect(() => {
|
|
323
353
|
setColorSchemeSafe(effectiveMode);
|
|
324
354
|
applyDarkClass(resolved);
|
|
325
|
-
applyColorPresetVars(colorPreset, resolved);
|
|
326
|
-
}, [effectiveMode, resolved, colorPreset]);
|
|
355
|
+
applyColorPresetVars(colorPreset, resolved, explicitAccents);
|
|
356
|
+
}, [effectiveMode, resolved, colorPreset, explicitAccents]);
|
|
327
357
|
|
|
328
358
|
// Native only: publish the preset vars into react-native-css's GLOBAL root
|
|
329
359
|
// variable family so they reach the ENTIRE app tree — navigator screens and
|
|
@@ -333,19 +363,19 @@ export function BloomThemeProvider({
|
|
|
333
363
|
// there. Runs before paint via `useIsomorphicLayoutEffect`, matching the
|
|
334
364
|
// sibling `applyColorPresetVars` side effect.
|
|
335
365
|
useIsomorphicLayoutEffect(() => {
|
|
336
|
-
applyNativeRootVars(colorPreset, resolved);
|
|
337
|
-
}, [colorPreset, resolved]);
|
|
366
|
+
applyNativeRootVars(colorPreset, resolved, explicitAccents);
|
|
367
|
+
}, [colorPreset, resolved, explicitAccents]);
|
|
338
368
|
|
|
339
369
|
const contextValue = useMemo<BloomThemeContextValue>(
|
|
340
370
|
() => ({
|
|
341
|
-
theme: buildTheme(colorPreset, resolved, isAdaptive),
|
|
371
|
+
theme: buildTheme(colorPreset, resolved, isAdaptive, explicitAccents),
|
|
342
372
|
mode,
|
|
343
373
|
colorPreset,
|
|
344
374
|
setMode,
|
|
345
375
|
setColorPreset,
|
|
346
376
|
resetTheme,
|
|
347
377
|
}),
|
|
348
|
-
[colorPreset, resolved, isAdaptive, mode, setMode, setColorPreset, resetTheme],
|
|
378
|
+
[colorPreset, resolved, isAdaptive, explicitAccents, mode, setMode, setColorPreset, resetTheme],
|
|
349
379
|
);
|
|
350
380
|
|
|
351
381
|
const shouldAwait = awaitHydration ?? Boolean(persistKey && storage);
|
|
@@ -356,8 +386,8 @@ export function BloomThemeProvider({
|
|
|
356
386
|
// etc. through react-native-css's real VariableContext. Web writes these to
|
|
357
387
|
// `document.documentElement` via `applyColorPresetVars`, so it's unused there.
|
|
358
388
|
const nativeVars = useMemo(
|
|
359
|
-
() => (Platform.OS === 'web' ? null : buildScopeVars(colorPreset, resolved)),
|
|
360
|
-
[colorPreset, resolved],
|
|
389
|
+
() => (Platform.OS === 'web' ? null : buildScopeVars(colorPreset, resolved, explicitAccents)),
|
|
390
|
+
[colorPreset, resolved, explicitAccents],
|
|
361
391
|
);
|
|
362
392
|
const VariableProvider = getVariableContextProvider();
|
|
363
393
|
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explicit secondary/tertiary accent pinning.
|
|
3
|
+
*
|
|
4
|
+
* A brand with a distinct accent (e.g. blue primary + yellow secondary) can PIN
|
|
5
|
+
* its real accent colours instead of the engine deriving secondary/tertiary as
|
|
6
|
+
* hue-rotations of the primary seed. These tests assert:
|
|
7
|
+
* 1. pinning shifts the secondary/tertiary family HUE toward the pinned colour
|
|
8
|
+
* (and away from the derived one), and
|
|
9
|
+
* 2. omitting the seeds is byte-identical to the pre-feature derived output.
|
|
10
|
+
*/
|
|
11
|
+
import {
|
|
12
|
+
argbFromHex,
|
|
13
|
+
generateRoleColors,
|
|
14
|
+
type RoleColors,
|
|
15
|
+
} from '../color-engine';
|
|
16
|
+
import { Hct } from '../color-engine/hct';
|
|
17
|
+
import { buildSeedScopeVars } from '../color-scope/seed-scope';
|
|
18
|
+
|
|
19
|
+
/** Hue (deg) of an `rgb(r g b)` engine output string. */
|
|
20
|
+
function hueOf(rgb: string): number {
|
|
21
|
+
const m = rgb.match(/rgb\((\d+) (\d+) (\d+)\)/);
|
|
22
|
+
if (!m) throw new Error(`expected "rgb(r g b)", got: ${rgb}`);
|
|
23
|
+
const argb = (0xff << 24) | (Number(m[1]) << 16) | (Number(m[2]) << 8) | Number(m[3]);
|
|
24
|
+
return Hct.fromInt(argb >>> 0).hue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Smallest absolute angular distance between two hues, in degrees (0–180). */
|
|
28
|
+
function hueDistance(a: number, b: number): number {
|
|
29
|
+
const d = Math.abs(a - b) % 360;
|
|
30
|
+
return d > 180 ? 360 - d : d;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const BLUE = '#1d9bf0';
|
|
34
|
+
const YELLOW = '#ffc300';
|
|
35
|
+
const PINK = '#ec4899';
|
|
36
|
+
|
|
37
|
+
describe('explicit accent pinning — generateRoleColors', () => {
|
|
38
|
+
const derived: RoleColors = generateRoleColors({ seed: BLUE, variant: 'vivid', isDark: false });
|
|
39
|
+
|
|
40
|
+
it('pins the secondary family to the supplied colour hue', () => {
|
|
41
|
+
const pinned = generateRoleColors({
|
|
42
|
+
seed: BLUE,
|
|
43
|
+
variant: 'vivid',
|
|
44
|
+
isDark: false,
|
|
45
|
+
secondarySeed: YELLOW,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const yellowHue = Hct.fromInt(argbFromHex(YELLOW)).hue;
|
|
49
|
+
// The pinned secondary family reads at the yellow hue…
|
|
50
|
+
expect(hueDistance(hueOf(pinned.secondary), yellowHue)).toBeLessThan(20);
|
|
51
|
+
// …and its container / on-colour roles share that hue family too.
|
|
52
|
+
expect(hueDistance(hueOf(pinned.secondaryContainer), yellowHue)).toBeLessThan(20);
|
|
53
|
+
expect(hueDistance(hueOf(pinned.onSecondaryContainer), yellowHue)).toBeLessThan(20);
|
|
54
|
+
// …which differs materially from the derived (blue-rotation) secondary.
|
|
55
|
+
expect(hueDistance(hueOf(pinned.secondary), hueOf(derived.secondary))).toBeGreaterThan(30);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('pins the tertiary family independently of the secondary', () => {
|
|
59
|
+
const pinned = generateRoleColors({
|
|
60
|
+
seed: BLUE,
|
|
61
|
+
variant: 'vivid',
|
|
62
|
+
isDark: false,
|
|
63
|
+
tertiarySeed: PINK,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const pinkHue = Hct.fromInt(argbFromHex(PINK)).hue;
|
|
67
|
+
expect(hueDistance(hueOf(pinned.tertiary), pinkHue)).toBeLessThan(20);
|
|
68
|
+
expect(hueDistance(hueOf(pinned.tertiary), hueOf(derived.tertiary))).toBeGreaterThan(20);
|
|
69
|
+
// Secondary is untouched when only tertiary is pinned.
|
|
70
|
+
expect(pinned.secondary).toBe(derived.secondary);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('pins both accents at once, leaving primary + neutrals unchanged', () => {
|
|
74
|
+
const pinned = generateRoleColors({
|
|
75
|
+
seed: BLUE,
|
|
76
|
+
variant: 'vivid',
|
|
77
|
+
isDark: false,
|
|
78
|
+
secondarySeed: YELLOW,
|
|
79
|
+
tertiarySeed: PINK,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
expect(hueDistance(hueOf(pinned.secondary), Hct.fromInt(argbFromHex(YELLOW)).hue)).toBeLessThan(20);
|
|
83
|
+
expect(hueDistance(hueOf(pinned.tertiary), Hct.fromInt(argbFromHex(PINK)).hue)).toBeLessThan(20);
|
|
84
|
+
// Primary + surfaces are seed-driven and must not move.
|
|
85
|
+
expect(pinned.primary).toBe(derived.primary);
|
|
86
|
+
expect(pinned.background).toBe(derived.background);
|
|
87
|
+
expect(pinned.surfaceContainerLowest).toBe(derived.surfaceContainerLowest);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('is byte-identical to the derived output when no accents are supplied', () => {
|
|
91
|
+
const again = generateRoleColors({ seed: BLUE, variant: 'vivid', isDark: false });
|
|
92
|
+
expect(again).toEqual(derived);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('applies pinned accents in dark mode too', () => {
|
|
96
|
+
const pinnedDark = generateRoleColors({
|
|
97
|
+
seed: BLUE,
|
|
98
|
+
variant: 'vivid',
|
|
99
|
+
isDark: true,
|
|
100
|
+
secondarySeed: YELLOW,
|
|
101
|
+
});
|
|
102
|
+
expect(hueDistance(hueOf(pinnedDark.secondary), Hct.fromInt(argbFromHex(YELLOW)).hue)).toBeLessThan(20);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('explicit accent pinning — arbitrary-seed scope path', () => {
|
|
107
|
+
it('threads pinned accents through buildSeedScopeVars', () => {
|
|
108
|
+
const derivedVars = buildSeedScopeVars({ seed: BLUE, mode: 'light' });
|
|
109
|
+
const pinnedVars = buildSeedScopeVars({
|
|
110
|
+
seed: BLUE,
|
|
111
|
+
mode: 'light',
|
|
112
|
+
secondarySeed: YELLOW,
|
|
113
|
+
tertiarySeed: PINK,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const yellowHue = Hct.fromInt(argbFromHex(YELLOW)).hue;
|
|
117
|
+
const pinkHue = Hct.fromInt(argbFromHex(PINK)).hue;
|
|
118
|
+
|
|
119
|
+
expect(pinnedVars['--secondary']).toBeDefined();
|
|
120
|
+
expect(hueDistance(hueOf(pinnedVars['--secondary'] ?? ''), yellowHue)).toBeLessThan(20);
|
|
121
|
+
expect(hueDistance(hueOf(pinnedVars['--tertiary'] ?? ''), pinkHue)).toBeLessThan(20);
|
|
122
|
+
// The scoped `--color-*` alias tracks the pinned value too (web utility contract).
|
|
123
|
+
expect(pinnedVars['--color-secondary']).toBe(pinnedVars['--secondary']);
|
|
124
|
+
// Primary token is unchanged vs the derived scope.
|
|
125
|
+
expect(pinnedVars['--primary']).toBe(derivedVars['--primary']);
|
|
126
|
+
// Derived secondary differs from the pinned one.
|
|
127
|
+
expect(pinnedVars['--secondary']).not.toBe(derivedVars['--secondary']);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Platform } from 'react-native';
|
|
2
2
|
import { APP_COLOR_PRESETS, type AppColorName } from './color-presets';
|
|
3
|
+
import type { ExplicitAccents } from './preset-vars';
|
|
3
4
|
import { getResolvedTokens } from './token-registry';
|
|
4
5
|
|
|
5
6
|
export function applyDarkClass(resolved: 'light' | 'dark') {
|
|
@@ -25,11 +26,15 @@ export function applyDarkClass(resolved: 'light' | 'dark') {
|
|
|
25
26
|
* Includes extended tokens (card, chart-*, content-area, sidebar-*) so consumer
|
|
26
27
|
* apps don't need to synthesize them.
|
|
27
28
|
*/
|
|
28
|
-
export function applyColorPresetVars(
|
|
29
|
+
export function applyColorPresetVars(
|
|
30
|
+
preset: AppColorName,
|
|
31
|
+
resolved: 'light' | 'dark',
|
|
32
|
+
accents?: ExplicitAccents,
|
|
33
|
+
) {
|
|
29
34
|
if (Platform.OS !== 'web' || typeof document === 'undefined') return;
|
|
30
35
|
if (!APP_COLOR_PRESETS[preset]) return;
|
|
31
36
|
|
|
32
|
-
const vars = getResolvedTokens(preset, resolved);
|
|
37
|
+
const vars = getResolvedTokens(preset, resolved, accents);
|
|
33
38
|
const root = document.documentElement.style;
|
|
34
39
|
|
|
35
40
|
for (const [key, value] of Object.entries(vars)) {
|
|
@@ -4,6 +4,18 @@ import { STATUS_COLORS } from './build-theme';
|
|
|
4
4
|
import { THEME_GRADIENTS } from './gradients';
|
|
5
5
|
import type { Theme, ThemeColors } from './types';
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Optional explicit accent seeds for the arbitrary-seed theme path. Pinning a
|
|
9
|
+
* `secondarySeed` / `tertiarySeed` (`#rrggbb`) forces that accent family to the
|
|
10
|
+
* given brand colour instead of the derived hue-rotation — mirroring
|
|
11
|
+
* {@link GenerateOptions.secondarySeed}. For an artwork-derived theme, pass the
|
|
12
|
+
* ranked `seedsFromImagePixels` results (`seeds[1]` / `seeds[2]`).
|
|
13
|
+
*/
|
|
14
|
+
export interface SeedAccents {
|
|
15
|
+
secondarySeed?: string;
|
|
16
|
+
tertiarySeed?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
7
19
|
/**
|
|
8
20
|
* Build the JS `theme.colors` object from an ARBITRARY seed colour — the dynamic
|
|
9
21
|
* counterpart of `buildTheme(preset, …)`.
|
|
@@ -22,8 +34,16 @@ export function buildColorsFromSeed(
|
|
|
22
34
|
resolved: 'light' | 'dark',
|
|
23
35
|
variant?: SchemeVariant,
|
|
24
36
|
contrastLevel?: number,
|
|
37
|
+
accents?: SeedAccents,
|
|
25
38
|
): ThemeColors {
|
|
26
|
-
const t = buildSeedScopeVars({
|
|
39
|
+
const t = buildSeedScopeVars({
|
|
40
|
+
seed,
|
|
41
|
+
mode: resolved,
|
|
42
|
+
variant,
|
|
43
|
+
contrastLevel,
|
|
44
|
+
secondarySeed: accents?.secondarySeed,
|
|
45
|
+
tertiarySeed: accents?.tertiarySeed,
|
|
46
|
+
});
|
|
27
47
|
const isDark = resolved === 'dark';
|
|
28
48
|
|
|
29
49
|
// Read a resolved `rgb(...)` token by its bare name (no leading `--`).
|
|
@@ -34,6 +54,8 @@ export function buildColorsFromSeed(
|
|
|
34
54
|
variant,
|
|
35
55
|
isDark,
|
|
36
56
|
contrastLevel,
|
|
57
|
+
secondarySeed: accents?.secondarySeed,
|
|
58
|
+
tertiarySeed: accents?.tertiarySeed,
|
|
37
59
|
});
|
|
38
60
|
|
|
39
61
|
return {
|
|
@@ -88,10 +110,11 @@ export function buildThemeFromSeed(
|
|
|
88
110
|
resolved: 'light' | 'dark',
|
|
89
111
|
variant?: SchemeVariant,
|
|
90
112
|
contrastLevel?: number,
|
|
113
|
+
accents?: SeedAccents,
|
|
91
114
|
): Theme {
|
|
92
115
|
return {
|
|
93
116
|
mode: resolved,
|
|
94
|
-
colors: buildColorsFromSeed(seed, resolved, variant, contrastLevel),
|
|
117
|
+
colors: buildColorsFromSeed(seed, resolved, variant, contrastLevel, accents),
|
|
95
118
|
gradients: THEME_GRADIENTS,
|
|
96
119
|
isDark: resolved === 'dark',
|
|
97
120
|
isLight: resolved === 'light',
|
package/src/theme/build-theme.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Platform } from 'react-native';
|
|
2
2
|
import { APP_COLOR_PRESETS, type AppColorName } from './color-presets';
|
|
3
3
|
import { generateRoleColors, type RoleColors } from './color-engine';
|
|
4
|
+
import type { ExplicitAccents } from './preset-vars';
|
|
4
5
|
import { getAdaptiveColors } from './adaptive-colors';
|
|
5
6
|
import { getResolvedTokens } from './token-registry';
|
|
6
7
|
import { THEME_GRADIENTS } from './gradients';
|
|
@@ -32,20 +33,24 @@ export const STATUS_COLORS = {
|
|
|
32
33
|
function buildColorsFromPreset(
|
|
33
34
|
preset: AppColorName,
|
|
34
35
|
resolved: 'light' | 'dark',
|
|
36
|
+
accents?: ExplicitAccents,
|
|
35
37
|
): ThemeColors {
|
|
36
|
-
const t = getResolvedTokens(preset, resolved);
|
|
38
|
+
const t = getResolvedTokens(preset, resolved, accents);
|
|
37
39
|
const isDark = resolved === 'dark';
|
|
38
40
|
|
|
39
41
|
// Read a resolved `rgb(...)` token by its bare name (no leading `--`).
|
|
40
42
|
const g = (k: string): string => t[`--${k}`] ?? 'rgb(0 0 0)';
|
|
41
43
|
|
|
42
44
|
// The full engine role set — for fields that map to roles Bloom's canonical
|
|
43
|
-
// token set does not surface (containers, error family).
|
|
45
|
+
// token set does not surface (containers, error family). An app-wide accent
|
|
46
|
+
// override wins over the preset's own declared accent (else derived).
|
|
44
47
|
const config = APP_COLOR_PRESETS[preset];
|
|
45
48
|
const r: RoleColors = generateRoleColors({
|
|
46
49
|
seed: config.hex,
|
|
47
50
|
variant: config.variant,
|
|
48
51
|
isDark,
|
|
52
|
+
secondarySeed: accents?.secondaryHex ?? config.secondaryHex,
|
|
53
|
+
tertiarySeed: accents?.tertiaryHex ?? config.tertiaryHex,
|
|
49
54
|
});
|
|
50
55
|
|
|
51
56
|
return {
|
|
@@ -105,9 +110,10 @@ export function buildTheme(
|
|
|
105
110
|
preset: AppColorName,
|
|
106
111
|
resolved: 'light' | 'dark',
|
|
107
112
|
isAdaptive: boolean = false,
|
|
113
|
+
accents?: ExplicitAccents,
|
|
108
114
|
): Theme {
|
|
109
115
|
const adaptive = isAdaptive && Platform.OS !== 'web' ? getAdaptiveColors() : undefined;
|
|
110
|
-
const colors = adaptive ?? buildColorsFromPreset(preset, resolved);
|
|
116
|
+
const colors = adaptive ?? buildColorsFromPreset(preset, resolved, accents);
|
|
111
117
|
|
|
112
118
|
return {
|
|
113
119
|
mode: resolved,
|
|
@@ -11,7 +11,7 @@ export { Hct } from './hct';
|
|
|
11
11
|
export { TonalPalette } from './tonal-palette';
|
|
12
12
|
export { DynamicScheme } from './dynamic-scheme';
|
|
13
13
|
export { buildScheme, schemeVibrant, schemeExpressive, schemeTonalSpot, schemeNeutral } from './scheme-variants';
|
|
14
|
-
export type { SchemeVariant } from './scheme-variants';
|
|
14
|
+
export type { SchemeVariant, AccentSources } from './scheme-variants';
|
|
15
15
|
export { Roles } from './color-roles';
|
|
16
16
|
export type { RoleName } from './color-roles';
|
|
17
17
|
export { ColorRole } from './color-role';
|
|
@@ -51,11 +51,27 @@ export interface GenerateOptions {
|
|
|
51
51
|
isDark?: boolean;
|
|
52
52
|
/** −1 (low) … 0 (normal) … 1 (high). */
|
|
53
53
|
contrastLevel?: number;
|
|
54
|
+
/**
|
|
55
|
+
* Optional explicit secondary-accent seed, `#rrggbb`. When set, the secondary
|
|
56
|
+
* palette is pinned to THIS colour's hue + chroma (M3 role tones still apply)
|
|
57
|
+
* instead of the variant's derived hue-rotation of the primary seed. Omit for
|
|
58
|
+
* the current derived behaviour.
|
|
59
|
+
*/
|
|
60
|
+
secondarySeed?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Optional explicit tertiary-accent seed, `#rrggbb`. Same semantics as
|
|
63
|
+
* {@link GenerateOptions.secondarySeed} for the tertiary palette.
|
|
64
|
+
*/
|
|
65
|
+
tertiarySeed?: string;
|
|
54
66
|
}
|
|
55
67
|
|
|
56
68
|
/**
|
|
57
69
|
* Generate the full role set from ANY seed colour — the dynamic-theming entry
|
|
58
70
|
* point. Presets are just fixed seeds; a user-picked colour works identically.
|
|
71
|
+
*
|
|
72
|
+
* `secondarySeed` / `tertiarySeed` optionally PIN those accent families to real
|
|
73
|
+
* brand colours (e.g. a blue-primary brand with a yellow secondary); omitted, the
|
|
74
|
+
* accents are derived as the variant's hue-rotations — byte-identical to before.
|
|
59
75
|
*/
|
|
60
76
|
export function generateRoleColors(opts: GenerateOptions): RoleColors {
|
|
61
77
|
const scheme = buildScheme(
|
|
@@ -63,6 +79,16 @@ export function generateRoleColors(opts: GenerateOptions): RoleColors {
|
|
|
63
79
|
Hct.fromInt(argbFromHex(opts.seed)),
|
|
64
80
|
opts.isDark ?? false,
|
|
65
81
|
opts.contrastLevel ?? 0,
|
|
82
|
+
{
|
|
83
|
+
secondarySource:
|
|
84
|
+
opts.secondarySeed !== undefined
|
|
85
|
+
? Hct.fromInt(argbFromHex(opts.secondarySeed))
|
|
86
|
+
: undefined,
|
|
87
|
+
tertiarySource:
|
|
88
|
+
opts.tertiarySeed !== undefined
|
|
89
|
+
? Hct.fromInt(argbFromHex(opts.tertiarySeed))
|
|
90
|
+
: undefined,
|
|
91
|
+
},
|
|
66
92
|
);
|
|
67
93
|
const out = {} as RoleColors;
|
|
68
94
|
for (const name of Object.keys(Roles) as RoleName[]) {
|