@multiplatform.one/theme 7.40.0 → 7.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiplatform.one/theme",
3
- "version": "7.40.0",
3
+ "version": "7.42.0",
4
4
  "description": "Tamagui theme system for multiplatform.one",
5
5
  "keywords": [
6
6
  "multiplatform",
@@ -59,8 +59,8 @@
59
59
  "access": "public"
60
60
  },
61
61
  "dependencies": {
62
- "@multiplatform.one/platform": "^7.40.0",
63
- "@multiplatform.one/store": "^7.40.0",
62
+ "@multiplatform.one/platform": "^7.42.0",
63
+ "@multiplatform.one/store": "^7.42.0",
64
64
  "@tamagui/animations-react-native": "2.7.6",
65
65
  "@tamagui/colors": "2.7.6",
66
66
  "@tamagui/config": "2.7.6",
@@ -73,8 +73,8 @@
73
73
  "react-cookie": "^8.1.2"
74
74
  },
75
75
  "devDependencies": {
76
- "@multiplatform.one/config": "7.40.0",
77
- "@multiplatform.one/test-utils": "7.40.0",
76
+ "@multiplatform.one/config": "7.42.0",
77
+ "@multiplatform.one/test-utils": "7.42.0",
78
78
  "@tamagui/animations-css": "2.7.6",
79
79
  "@tamagui/animations-reanimated": "2.7.6",
80
80
  "@tamagui/font-inter": "2.7.6",
@@ -1,5 +1,9 @@
1
1
  import { describe, expect, it } from "vitest";
2
- import { resolveAccentTintedSurface, resolveLegibleInkOn } from "./readableColor";
2
+ import {
3
+ resolveAccentTintedSurface,
4
+ resolveIdentityInk,
5
+ resolveLegibleInkOn,
6
+ } from "./readableColor";
3
7
  import { contrastRatio, normalizeToHex, relativeLuminance } from "./colorRules";
4
8
 
5
9
  const lightPaper = "#ffffff";
@@ -123,3 +127,40 @@ describe("resolveLegibleInkOn (MPO-448)", () => {
123
127
  ).toBe("#a09ba6");
124
128
  });
125
129
  });
130
+
131
+ describe("resolveIdentityInk", () => {
132
+ const orangeLight = ["#f76b15", "#ef5f00", "#b54500", "#582d1d"];
133
+ const orangeDark = ["#f76b15", "#ff801f", "#ffa057", "#ffe0c2"];
134
+
135
+ it("picks the most chromatic orange step that clears AA on light surfaces", () => {
136
+ const ink = resolveIdentityInk({
137
+ candidates: orangeLight,
138
+ surfaces: [lightPaper, "#fefcfb"],
139
+ ink: lightInk,
140
+ });
141
+ expect(ink).toBe("#b54500");
142
+ expect(contrastRatio(lum(ink), lum(lightPaper))).toBeGreaterThanOrEqual(4.5);
143
+ });
144
+
145
+ it("picks a saturated orange on dark surfaces", () => {
146
+ const ink = resolveIdentityInk({
147
+ candidates: orangeDark,
148
+ surfaces: [darkPaper, "#1f160f"],
149
+ ink: darkInk,
150
+ });
151
+ expect(["#f76b15", "#ff801f"]).toContain(ink);
152
+ });
153
+
154
+ it("falls back to the primary ink when the ramp has no hue", () => {
155
+ const greys = ["#8d8d8d", "#6f6f6f", "#b4b4b4", "#eeeeee"];
156
+ expect(resolveIdentityInk({ candidates: greys, surfaces: [darkPaper], ink: darkInk })).toBe(
157
+ darkInk,
158
+ );
159
+ });
160
+
161
+ it("falls back to the primary ink when no hue step clears AA", () => {
162
+ expect(
163
+ resolveIdentityInk({ candidates: ["#f76b15"], surfaces: [lightPaper], ink: lightInk }),
164
+ ).toBe(lightInk);
165
+ });
166
+ });
@@ -1,5 +1,5 @@
1
1
  import { useMemo } from "react";
2
- import { useTheme as useTamaguiTheme } from "tamagui";
2
+ import { useTheme as useTamaguiTheme, useThemeName } from "tamagui";
3
3
  import {
4
4
  aaTextContrastRatio,
5
5
  contrastRatio,
@@ -8,6 +8,7 @@ import {
8
8
  pickReadableForeground,
9
9
  relativeLuminance,
10
10
  } from "./colorRules";
11
+ import { tintHueNames } from "./createThemes";
11
12
 
12
13
  /**
13
14
  * Live-theme contrast helpers for text on solids and accent ink on surfaces.
@@ -135,6 +136,79 @@ export function useAccentOnSurface(): string {
135
136
  }, [c1, c2, ink, steps.join("|")]);
136
137
  }
137
138
 
139
+ /** Below this sRGB chroma a step reads as grey, not as a hue. */
140
+ const IDENTITY_CHROMA_FLOOR = 0.1;
141
+
142
+ function chromaOf(hex: string): number {
143
+ const rgb = [1, 3, 5].map((offset) => parseInt(hex.slice(offset, offset + 2), 16) / 255);
144
+ return Math.max(...rgb) - Math.min(...rgb);
145
+ }
146
+
147
+ /**
148
+ * Pure resolver behind {@link useIdentityInkOnSurface}: the most chromatic
149
+ * candidate that clears AA text contrast on every surface, else `ink`.
150
+ */
151
+ export function resolveIdentityInk({
152
+ candidates,
153
+ surfaces,
154
+ ink,
155
+ }: {
156
+ candidates: readonly (string | undefined)[];
157
+ surfaces: readonly (string | undefined)[];
158
+ ink: string;
159
+ }): string {
160
+ const surfaceLums: number[] = [];
161
+ for (const surface of surfaces) {
162
+ const hex = surface ? normalizeToHex(surface) : null;
163
+ if (!hex) return ink;
164
+ surfaceLums.push(relativeLuminance(hex));
165
+ }
166
+ let best: string | undefined;
167
+ let bestChroma = IDENTITY_CHROMA_FLOOR;
168
+ for (const candidate of candidates) {
169
+ const hex = candidate ? normalizeToHex(candidate) : null;
170
+ if (!candidate || !hex) continue;
171
+ const lum = relativeLuminance(hex);
172
+ if (surfaceLums.some((surface) => contrastRatio(lum, surface) < aaTextContrastRatio)) continue;
173
+ const chroma = chromaOf(hex);
174
+ if (chroma > bestChroma) {
175
+ bestChroma = chroma;
176
+ best = candidate;
177
+ }
178
+ }
179
+ return best ?? ink;
180
+ }
181
+
182
+ /**
183
+ * The theme's identity hue as ink on the page surfaces (color1/color2): the
184
+ * most chromatic step that clears AA on both. Under a tint (`dark_orange`)
185
+ * the hue is the tint's own ramp, whose text tiers the build hands back to
186
+ * neutral, so the candidates are its `color8`..`color10` plus the raw hue's
187
+ * steps 11 and 12; otherwise the accent ramp. A theme with no hue (a neutral
188
+ * accent) returns the primary ink (`color12`): a mark that means "current"
189
+ * never reads quieter than the secondary ink beside it, which
190
+ * {@link useAccentOnSurface} does there.
191
+ */
192
+ export function useIdentityInkOnSurface(): string {
193
+ const themeName = (useThemeName() as string | undefined) ?? "";
194
+ const theme = useTamaguiTheme() as unknown as Record<string, { val?: unknown } | undefined>;
195
+ const hue = themeName.split("_")[1];
196
+ const isTint = hue !== undefined && tintHueNames.has(hue as never);
197
+ const keys = isTint
198
+ ? ["color8", "color9", "color10", `${hue}11`, `${hue}12`]
199
+ : Array.from({ length: 12 }, (_, index) => `accent${index + 1}`);
200
+ const candidates = keys.map((key) => resolveTokenValue(theme, key));
201
+ const c1 = resolveTokenValue(theme, "color1");
202
+ const c2 = resolveTokenValue(theme, "color2");
203
+ const ink = resolveTokenValue(theme, "color12") ?? "$color12";
204
+ const candidateKey = candidates.join("|");
205
+ return useMemo(
206
+ () => resolveIdentityInk({ candidates, surfaces: [c1, c2], ink }),
207
+ // eslint-disable-next-line react-hooks/exhaustive-deps
208
+ [candidateKey, c1, c2, ink],
209
+ );
210
+ }
211
+
138
212
  const ACCENT_WASH_RATIO = 1.2;
139
213
 
140
214
  export interface ResolveAccentTintedSurfaceInput {
@@ -35,6 +35,26 @@ export declare function useLegibleInkOn(fill_: string | undefined, preferred: st
35
35
  * tint sub-themes, where neutral text is the house default anyway).
36
36
  */
37
37
  export declare function useAccentOnSurface(): string;
38
+ /**
39
+ * Pure resolver behind {@link useIdentityInkOnSurface}: the most chromatic
40
+ * candidate that clears AA text contrast on every surface, else `ink`.
41
+ */
42
+ export declare function resolveIdentityInk({ candidates, surfaces, ink, }: {
43
+ candidates: readonly (string | undefined)[];
44
+ surfaces: readonly (string | undefined)[];
45
+ ink: string;
46
+ }): string;
47
+ /**
48
+ * The theme's identity hue as ink on the page surfaces (color1/color2): the
49
+ * most chromatic step that clears AA on both. Under a tint (`dark_orange`)
50
+ * the hue is the tint's own ramp, whose text tiers the build hands back to
51
+ * neutral, so the candidates are its `color8`..`color10` plus the raw hue's
52
+ * steps 11 and 12; otherwise the accent ramp. A theme with no hue (a neutral
53
+ * accent) returns the primary ink (`color12`): a mark that means "current"
54
+ * never reads quieter than the secondary ink beside it, which
55
+ * {@link useAccentOnSurface} does there.
56
+ */
57
+ export declare function useIdentityInkOnSurface(): string;
38
58
  export interface ResolveAccentTintedSurfaceInput {
39
59
  solid?: string;
40
60
  paper?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"readableColor.d.ts","sourceRoot":"","sources":["../../src/theme/readableColor.ts"],"names":[],"mappings":"AA0BA;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAU/E;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,SAAS,EACT,KAAK,EACL,GAAG,GACJ,EAAE;IACD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,GAAG,SAAS,CAarB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAYpF;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CA8B3C;AAID,MAAM,WAAW,+BAA+B;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,EACzC,KAAK,EACL,KAAK,GACN,EAAE,+BAA+B,GAAG,MAAM,GAAG,SAAS,CAmBtD;AAED,wEAAwE;AACxE,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,SAAS,CAK3D"}
1
+ {"version":3,"file":"readableColor.d.ts","sourceRoot":"","sources":["../../src/theme/readableColor.ts"],"names":[],"mappings":"AA2BA;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAU/E;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,SAAS,EACT,KAAK,EACL,GAAG,GACJ,EAAE;IACD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,GAAG,MAAM,GAAG,SAAS,CAarB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAYpF;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CA8B3C;AAUD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,UAAU,EACV,QAAQ,EACR,GAAG,GACJ,EAAE;IACD,UAAU,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;IAC5C,QAAQ,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,CAAC;IAC1C,GAAG,EAAE,MAAM,CAAC;CACb,GAAG,MAAM,CAqBT;AAED;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAkBhD;AAID,MAAM,WAAW,+BAA+B;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,EACzC,KAAK,EACL,KAAK,GACN,EAAE,+BAA+B,GAAG,MAAM,GAAG,SAAS,CAmBtD;AAED,wEAAwE;AACxE,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,SAAS,CAK3D"}