@multiplatform.one/theme 7.41.0 → 7.43.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.
|
|
3
|
+
"version": "7.43.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.
|
|
63
|
-
"@multiplatform.one/store": "^7.
|
|
62
|
+
"@multiplatform.one/platform": "^7.43.0",
|
|
63
|
+
"@multiplatform.one/store": "^7.43.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.
|
|
77
|
-
"@multiplatform.one/test-utils": "7.
|
|
76
|
+
"@multiplatform.one/config": "7.43.0",
|
|
77
|
+
"@multiplatform.one/test-utils": "7.43.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 {
|
|
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":"
|
|
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"}
|