@multiplatform.one/theme 7.34.3 → 7.35.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 +20 -5
- package/src/audit/constraintAudit.spec.ts +274 -87
- package/src/audit/constraintAudit.ts +135 -120
- package/src/audit/constraintScope.spec.ts +1 -1
- package/src/audit/designTokens.ts +42 -0
- package/src/audit/themeMatrix.spec.ts +83 -2
- package/src/audit/themeMatrix.ts +33 -1
- package/src/figma/figmaTokens.spec.ts +26 -34
- package/src/figma/figmaTokens.ts +26 -50
- package/src/fonts/createFontLoader.ts +4 -1
- package/src/menuRow.ts +4 -1
- package/src/theme/FontKnobStyles.tsx +2 -1
- package/src/theme/Preset.stories.tsx +0 -1
- package/src/theme/Preset.tsx +6 -1
- package/src/theme/PresetContext.ts +4 -0
- package/src/theme/Surface.spec.tsx +114 -67
- package/src/theme/Surface.stories.tsx +8 -25
- package/src/theme/Surface.tsx +87 -43
- package/src/theme/accentFloor.spec.ts +37 -11
- package/src/theme/activateThemeConfig.ts +22 -6
- package/src/theme/animations/animations.spec.ts +62 -0
- package/src/theme/animations/css.ts +9 -35
- package/src/theme/animations/reactNative.ts +98 -25
- package/src/theme/boundaryFloor.spec.ts +57 -0
- package/src/theme/chartPalette.spec.ts +56 -27
- package/src/theme/chartPalette.ts +33 -28
- package/src/theme/colorRules.spec.ts +32 -72
- package/src/theme/cookies.spec.ts +49 -18
- package/src/theme/cookies.ts +36 -7
- package/src/theme/cookiesPersistence.spec.ts +5 -5
- package/src/theme/createDefaultThemeConfig.spec.ts +182 -20
- package/src/theme/createDefaultThemeConfig.ts +34 -28
- package/src/theme/createThemes.ts +169 -218
- package/src/theme/defaults/accent.ts +3 -35
- package/src/theme/defaults/base.ts +14 -53
- package/src/theme/defaults/builderOptions.ts +68 -85
- package/src/theme/defaults/categoryFonts.ts +10 -0
- package/src/theme/defaults/fonts.spec.ts +84 -4
- package/src/theme/defaults/fonts.ts +166 -124
- package/src/theme/defaults/house.ts +48 -0
- package/src/theme/defaults/index.ts +1 -0
- package/src/theme/defaults/neutralRamp.spec.ts +52 -47
- package/src/theme/devtools/ColorLineVisualizer.tsx +6 -6
- package/src/theme/devtools/ThemeDevtoolsPanel.setMarkers.spec.tsx +13 -6
- package/src/theme/devtools/ThemeDevtoolsPanel.tsx +14 -77
- package/src/theme/devtools/animationOptions.spec.ts +23 -37
- package/src/theme/devtools/animationOptions.ts +4 -6
- package/src/theme/devtools/buildSnapshot.ts +0 -3
- package/src/theme/devtools/knobIcons.tsx +4 -24
- package/src/theme/focusState.spec.ts +9 -30
- package/src/theme/index.ts +17 -0
- package/src/theme/intentFill.spec.ts +42 -0
- package/src/theme/knobs.ts +14 -42
- package/src/theme/layoutTokens.ts +15 -1
- package/src/theme/layoutTokensHooks.spec.tsx +8 -4
- package/src/theme/mergedTokens.ts +20 -0
- package/src/theme/motion.spec.ts +84 -0
- package/src/theme/motion.ts +53 -0
- package/src/theme/motionStops.ts +17 -0
- package/src/theme/motionTiming.ts +59 -0
- package/src/theme/mpo13Proof.spec.ts +43 -46
- package/src/theme/mpo19Proof.spec.ts +13 -14
- package/src/theme/namedInkFloor.spec.ts +60 -0
- package/src/theme/overlaySurfaces.tsx +330 -0
- package/src/theme/preset.types.ts +16 -0
- package/src/theme/presetPreview.ts +32 -0
- package/src/theme/presetRoot.spec.tsx +89 -0
- package/src/theme/presetStore.persistence.spec.ts +141 -0
- package/src/theme/presetStore.spec.ts +205 -0
- package/src/theme/presetStore.ts +286 -0
- package/src/theme/presetUserSync.spec.tsx +168 -0
- package/src/theme/presetUserSync.ts +47 -0
- package/src/theme/presets.spec.ts +22 -15
- package/src/theme/presets.ts +22 -10
- package/src/theme/radiusClass.spec.ts +60 -41
- package/src/theme/radiusClass.ts +17 -4
- package/src/theme/radiusClassTable.generated.ts +3 -3
- package/src/theme/readableColor.spec.ts +59 -152
- package/src/theme/readableColor.ts +34 -174
- package/src/theme/recipeInputs.ts +153 -295
- package/src/theme/recipes.ts +58 -18
- package/src/theme/resolveKnobs.spec.ts +528 -588
- package/src/theme/resolveKnobs.ts +250 -325
- package/src/theme/scale/ScaleStyles.native.tsx +19 -0
- package/src/theme/scale/ScaleStyles.tsx +48 -0
- package/src/theme/scale/activeScale.ts +30 -0
- package/src/theme/scale/deriveRecipeInputs.ts +110 -0
- package/src/theme/scale/generateScale.spec.ts +260 -0
- package/src/theme/scale/generateScale.ts +147 -0
- package/src/theme/scale/index.ts +54 -0
- package/src/theme/scale/installScaleConfig.native.spec.ts +77 -0
- package/src/theme/scale/installScaleConfig.native.ts +57 -0
- package/src/theme/scale/installScaleConfig.ts +7 -0
- package/src/theme/scale/scaleAttribute.ts +21 -0
- package/src/theme/scale/scaleCss.probe.spec.tsx +64 -0
- package/src/theme/scale/scaleCss.spec.ts +132 -0
- package/src/theme/scale/scaleCss.ts +95 -0
- package/src/theme/scale/scaleFonts.ts +106 -0
- package/src/theme/scale/scaleInputs.ts +322 -0
- package/src/theme/scale/themeScales.ts +181 -0
- package/src/theme/scale/useActiveScale.spec.tsx +129 -0
- package/src/theme/scale/useActiveScale.ts +95 -0
- package/src/theme/schemeStore.spec.ts +81 -0
- package/src/theme/schemeStore.ts +67 -0
- package/src/theme/shared.spec.ts +7 -6
- package/src/theme/shared.tsx +37 -3
- package/src/theme/sizeLadder.spec.ts +37 -36
- package/src/theme/sizeRecipes.spec.ts +179 -85
- package/src/theme/sizeRecipes.ts +48 -25
- package/src/theme/subsetThemes.spec.ts +9 -3
- package/src/theme/switchThumb.spec.ts +69 -0
- package/src/theme/syntaxFloor.spec.ts +11 -8
- package/src/theme/theme.hydration.native.spec.tsx +51 -0
- package/src/theme/theme.native.tsx +59 -21
- package/src/theme/theme.palette.spec.tsx +76 -0
- package/src/theme/theme.preset.native.spec.tsx +73 -0
- package/src/theme/theme.provider.spec.ts +143 -1
- package/src/theme/theme.tsx +61 -25
- package/src/theme/tileSizes.ts +6 -7
- package/src/theme/transitionProps.native.ts +25 -2
- package/src/theme/transitionProps.spec.ts +34 -0
- package/src/theme/transitionProps.ts +26 -2
- package/src/theme/usePresetSwitch.native.spec.tsx +68 -0
- package/src/theme/usePresetSwitch.native.ts +32 -0
- package/src/theme/usePresetSwitch.spec.tsx +103 -0
- package/src/theme/usePresetSwitch.ts +54 -0
- package/src/theme/useResolvedKnobs.hydration.spec.tsx +2 -2
- package/src/theme/useResolvedKnobs.ts +23 -90
- package/src/theme/useResolvedKnobsBehavior.spec.tsx +25 -30
- package/src/theme/useSchemePreference.native.ts +33 -0
- package/src/theme/useSchemePreference.spec.tsx +51 -0
- package/src/theme/useSchemePreference.ts +32 -0
- package/src/theme/useSchemePreference.web.spec.tsx +32 -0
- package/src/theme/useTheme.native.spec.tsx +68 -0
- package/src/theme/useTheme.native.ts +49 -24
- package/src/theme/useTheme.ts +7 -4
- package/types/audit/constraintAudit.d.ts +6 -5
- package/types/audit/constraintAudit.d.ts.map +1 -1
- package/types/audit/designTokens.d.ts +13 -0
- package/types/audit/designTokens.d.ts.map +1 -0
- package/types/audit/themeMatrix.d.ts +10 -0
- package/types/audit/themeMatrix.d.ts.map +1 -1
- package/types/figma/figmaTokens.d.ts +11 -28
- package/types/figma/figmaTokens.d.ts.map +1 -1
- package/types/fonts/createFontLoader.d.ts.map +1 -1
- package/types/menuRow.d.ts +4 -1
- package/types/menuRow.d.ts.map +1 -1
- package/types/theme/FontKnobStyles.d.ts.map +1 -1
- package/types/theme/Preset.d.ts.map +1 -1
- package/types/theme/PresetContext.d.ts +4 -0
- package/types/theme/PresetContext.d.ts.map +1 -1
- package/types/theme/Surface.d.ts +57 -26
- package/types/theme/Surface.d.ts.map +1 -1
- package/types/theme/activateThemeConfig.d.ts +5 -3
- package/types/theme/activateThemeConfig.d.ts.map +1 -1
- package/types/theme/animations/css.d.ts +27 -34
- package/types/theme/animations/css.d.ts.map +1 -1
- package/types/theme/animations/reactNative.d.ts +196 -45
- package/types/theme/animations/reactNative.d.ts.map +1 -1
- package/types/theme/chartPalette.d.ts +11 -3
- package/types/theme/chartPalette.d.ts.map +1 -1
- package/types/theme/colorRules.d.ts +1 -1
- package/types/theme/cookies.d.ts +12 -1
- package/types/theme/cookies.d.ts.map +1 -1
- package/types/theme/createDefaultThemeConfig.d.ts +13 -13
- package/types/theme/createDefaultThemeConfig.d.ts.map +1 -1
- package/types/theme/createThemes.d.ts +58 -2
- package/types/theme/createThemes.d.ts.map +1 -1
- package/types/theme/defaults/accent.d.ts.map +1 -1
- package/types/theme/defaults/base.d.ts.map +1 -1
- package/types/theme/defaults/builderOptions.d.ts.map +1 -1
- package/types/theme/defaults/categoryFonts.d.ts +10 -0
- package/types/theme/defaults/categoryFonts.d.ts.map +1 -1
- package/types/theme/defaults/fonts.d.ts +330 -30
- package/types/theme/defaults/fonts.d.ts.map +1 -1
- package/types/theme/defaults/house.d.ts +8 -0
- package/types/theme/defaults/house.d.ts.map +1 -0
- package/types/theme/defaults/index.d.ts +1 -0
- package/types/theme/defaults/index.d.ts.map +1 -1
- package/types/theme/devtools/ThemeDevtoolsPanel.d.ts +3 -3
- package/types/theme/devtools/ThemeDevtoolsPanel.d.ts.map +1 -1
- package/types/theme/devtools/animationOptions.d.ts.map +1 -1
- package/types/theme/devtools/buildSnapshot.d.ts.map +1 -1
- package/types/theme/devtools/knobIcons.d.ts +0 -3
- package/types/theme/devtools/knobIcons.d.ts.map +1 -1
- package/types/theme/index.d.ts +11 -0
- package/types/theme/index.d.ts.map +1 -1
- package/types/theme/knobs.d.ts +8 -36
- package/types/theme/knobs.d.ts.map +1 -1
- package/types/theme/layoutTokens.d.ts +13 -1
- package/types/theme/layoutTokens.d.ts.map +1 -1
- package/types/theme/mergedTokens.d.ts +11 -0
- package/types/theme/mergedTokens.d.ts.map +1 -0
- package/types/theme/motion.d.ts +17 -0
- package/types/theme/motion.d.ts.map +1 -0
- package/types/theme/motionStops.d.ts +3 -0
- package/types/theme/motionStops.d.ts.map +1 -0
- package/types/theme/motionTiming.d.ts +36 -0
- package/types/theme/motionTiming.d.ts.map +1 -0
- package/types/theme/overlaySurfaces.d.ts +70 -0
- package/types/theme/overlaySurfaces.d.ts.map +1 -0
- package/types/theme/preset.types.d.ts +16 -0
- package/types/theme/preset.types.d.ts.map +1 -1
- package/types/theme/presetPreview.d.ts +14 -0
- package/types/theme/presetPreview.d.ts.map +1 -0
- package/types/theme/presetStore.d.ts +40 -0
- package/types/theme/presetStore.d.ts.map +1 -0
- package/types/theme/presetUserSync.d.ts +7 -0
- package/types/theme/presetUserSync.d.ts.map +1 -0
- package/types/theme/presets.d.ts +12 -4
- package/types/theme/presets.d.ts.map +1 -1
- package/types/theme/radiusClass.d.ts +5 -2
- package/types/theme/radiusClass.d.ts.map +1 -1
- package/types/theme/radiusClassTable.generated.d.ts +3 -3
- package/types/theme/readableColor.d.ts +11 -49
- package/types/theme/readableColor.d.ts.map +1 -1
- package/types/theme/recipeInputs.d.ts +39 -70
- package/types/theme/recipeInputs.d.ts.map +1 -1
- package/types/theme/recipes.d.ts +79 -18
- package/types/theme/recipes.d.ts.map +1 -1
- package/types/theme/resolveKnobs.d.ts +64 -32
- package/types/theme/resolveKnobs.d.ts.map +1 -1
- package/types/theme/scale/ScaleStyles.d.ts +18 -0
- package/types/theme/scale/ScaleStyles.d.ts.map +1 -0
- package/types/theme/scale/ScaleStyles.native.d.ts +10 -0
- package/types/theme/scale/ScaleStyles.native.d.ts.map +1 -0
- package/types/theme/scale/activeScale.d.ts +13 -0
- package/types/theme/scale/activeScale.d.ts.map +1 -0
- package/types/theme/scale/deriveRecipeInputs.d.ts +10 -0
- package/types/theme/scale/deriveRecipeInputs.d.ts.map +1 -0
- package/types/theme/scale/generateScale.d.ts +38 -0
- package/types/theme/scale/generateScale.d.ts.map +1 -0
- package/types/theme/scale/index.d.ts +17 -0
- package/types/theme/scale/index.d.ts.map +1 -0
- package/types/theme/scale/installScaleConfig.d.ts +4 -0
- package/types/theme/scale/installScaleConfig.d.ts.map +1 -0
- package/types/theme/scale/installScaleConfig.native.d.ts +13 -0
- package/types/theme/scale/installScaleConfig.native.d.ts.map +1 -0
- package/types/theme/scale/scaleAttribute.d.ts +14 -0
- package/types/theme/scale/scaleAttribute.d.ts.map +1 -0
- package/types/theme/scale/scaleCss.d.ts +25 -0
- package/types/theme/scale/scaleCss.d.ts.map +1 -0
- package/types/theme/scale/scaleFonts.d.ts +16 -0
- package/types/theme/scale/scaleFonts.d.ts.map +1 -0
- package/types/theme/scale/scaleInputs.d.ts +66 -0
- package/types/theme/scale/scaleInputs.d.ts.map +1 -0
- package/types/theme/scale/themeScales.d.ts +52 -0
- package/types/theme/scale/themeScales.d.ts.map +1 -0
- package/types/theme/scale/useActiveScale.d.ts +31 -0
- package/types/theme/scale/useActiveScale.d.ts.map +1 -0
- package/types/theme/schemeStore.d.ts +6 -0
- package/types/theme/schemeStore.d.ts.map +1 -0
- package/types/theme/shared.d.ts +18 -0
- package/types/theme/shared.d.ts.map +1 -1
- package/types/theme/sizeRecipes.d.ts +20 -15
- package/types/theme/sizeRecipes.d.ts.map +1 -1
- package/types/theme/theme.d.ts +2 -1
- package/types/theme/theme.d.ts.map +1 -1
- package/types/theme/theme.native.d.ts +10 -1
- package/types/theme/theme.native.d.ts.map +1 -1
- package/types/theme/tileSizes.d.ts +6 -7
- package/types/theme/tileSizes.d.ts.map +1 -1
- package/types/theme/transitionProps.d.ts +3 -1
- package/types/theme/transitionProps.d.ts.map +1 -1
- package/types/theme/transitionProps.native.d.ts +3 -1
- package/types/theme/transitionProps.native.d.ts.map +1 -1
- package/types/theme/usePresetSwitch.d.ts +10 -0
- package/types/theme/usePresetSwitch.d.ts.map +1 -0
- package/types/theme/usePresetSwitch.native.d.ts +10 -0
- package/types/theme/usePresetSwitch.native.d.ts.map +1 -0
- package/types/theme/useResolvedKnobs.d.ts +13 -13
- package/types/theme/useResolvedKnobs.d.ts.map +1 -1
- package/types/theme/useSchemePreference.d.ts +8 -0
- package/types/theme/useSchemePreference.d.ts.map +1 -0
- package/types/theme/useSchemePreference.native.d.ts +8 -0
- package/types/theme/useSchemePreference.native.d.ts.map +1 -0
- package/types/theme/useTheme.d.ts +4 -2
- package/types/theme/useTheme.d.ts.map +1 -1
- package/types/theme/useTheme.native.d.ts +10 -1
- package/types/theme/useTheme.native.d.ts.map +1 -1
|
@@ -1,173 +1,80 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
resolveAaSolidFill,
|
|
4
|
-
resolveAccentTintedSurface,
|
|
5
|
-
resolveLegibleInkOn,
|
|
6
|
-
} from "./readableColor";
|
|
2
|
+
import { resolveAccentTintedSurface, resolveLegibleInkOn } from "./readableColor";
|
|
7
3
|
import { contrastRatio, normalizeToHex, relativeLuminance } from "./colorRules";
|
|
8
4
|
|
|
9
|
-
// The stock accent ramp (defaults/accent.ts) as the theme resolves it.
|
|
10
|
-
const lightAccent = [
|
|
11
|
-
"hsla(250, 50%, 62%, 1)",
|
|
12
|
-
"hsla(250, 50%, 58%, 1)",
|
|
13
|
-
"hsla(250, 50%, 54%, 1)",
|
|
14
|
-
"hsla(250, 50%, 48%, 1)",
|
|
15
|
-
"hsla(250, 50%, 46%, 1)",
|
|
16
|
-
"hsla(250, 50%, 44%, 1)",
|
|
17
|
-
"hsla(250, 50%, 42%, 1)",
|
|
18
|
-
"hsla(250, 50%, 40%, 1)",
|
|
19
|
-
"hsla(250, 50%, 38%, 1)",
|
|
20
|
-
"hsla(250, 50%, 36%, 1)",
|
|
21
|
-
"hsla(250, 50%, 95%, 1)",
|
|
22
|
-
"hsla(250, 50%, 98%, 1)",
|
|
23
|
-
];
|
|
24
|
-
const darkAccent = [
|
|
25
|
-
"hsla(250, 50%, 35%, 1)",
|
|
26
|
-
"hsla(250, 50%, 38%, 1)",
|
|
27
|
-
"hsla(250, 50%, 41%, 1)",
|
|
28
|
-
"hsla(250, 50%, 43%, 1)",
|
|
29
|
-
"hsla(250, 50%, 46%, 1)",
|
|
30
|
-
"hsla(250, 50%, 49%, 1)",
|
|
31
|
-
"hsla(250, 50%, 52%, 1)",
|
|
32
|
-
"hsla(250, 50%, 54%, 1)",
|
|
33
|
-
"hsla(250, 50%, 57%, 1)",
|
|
34
|
-
"hsla(250, 50%, 60%, 1)",
|
|
35
|
-
"hsla(250, 50%, 90%, 1)",
|
|
36
|
-
"hsla(250, 50%, 95%, 1)",
|
|
37
|
-
];
|
|
38
|
-
|
|
39
5
|
const lightPaper = "#ffffff";
|
|
40
6
|
const lightInk = "#242226";
|
|
41
7
|
const darkPaper = "#161519";
|
|
42
8
|
const darkInk = "#f0eff1";
|
|
43
|
-
|
|
44
9
|
const lum = (raw: string) => relativeLuminance(normalizeToHex(raw)!);
|
|
45
10
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
it("stays a SURFACE tier — never the solid an accent CTA takes", () => {
|
|
78
|
-
// The solid is accentBackground (= the ramp's mid, palette-shape solid).
|
|
79
|
-
const lightSolid = lightAccent[2];
|
|
80
|
-
const darkSolid = darkAccent[9];
|
|
81
|
-
expect(
|
|
82
|
-
resolveAccentTintedSurface({ steps: lightAccent, paper: lightPaper, ink: lightInk }),
|
|
83
|
-
).not.toBe(lightSolid);
|
|
84
|
-
expect(
|
|
85
|
-
resolveAccentTintedSurface({ steps: darkAccent, paper: darkPaper, ink: darkInk }),
|
|
86
|
-
).not.toBe(darkSolid);
|
|
87
|
-
});
|
|
11
|
+
function hsl(raw: string) {
|
|
12
|
+
const hex = normalizeToHex(raw)!;
|
|
13
|
+
const [r, g, b] = [1, 3, 5].map((offset) => parseInt(hex.slice(offset, offset + 2), 16) / 255);
|
|
14
|
+
const max = Math.max(r, g, b);
|
|
15
|
+
const min = Math.min(r, g, b);
|
|
16
|
+
const delta = max - min;
|
|
17
|
+
const hue =
|
|
18
|
+
delta === 0
|
|
19
|
+
? 0
|
|
20
|
+
: max === r
|
|
21
|
+
? ((g - b) / delta) % 6
|
|
22
|
+
: max === g
|
|
23
|
+
? (b - r) / delta + 2
|
|
24
|
+
: (r - g) / delta + 4;
|
|
25
|
+
return { hue: (hue * 60 + 360) % 360, lightness: (max + min) * 50 };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const v5Schemes = [
|
|
29
|
+
{
|
|
30
|
+
scheme: "light",
|
|
31
|
+
paper: "hsla(0, 0%, 100%, 1)",
|
|
32
|
+
solid: "hsla(0, 0%, 10%, 1)",
|
|
33
|
+
ink: "hsla(0, 0%, 2%, 1)",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
scheme: "dark",
|
|
37
|
+
paper: "hsla(0, 0%, 4%, 1)",
|
|
38
|
+
solid: "hsla(0, 0%, 45%, 1)",
|
|
39
|
+
ink: "hsla(0, 0%, 100%, 1)",
|
|
40
|
+
},
|
|
41
|
+
];
|
|
88
42
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
contrastRatio(lum(tint), lum(c.ink)),
|
|
99
|
-
);
|
|
100
|
-
expect(best).toBeGreaterThanOrEqual(4.5);
|
|
101
|
-
}
|
|
43
|
+
describe("resolveAccentTintedSurface", () => {
|
|
44
|
+
it.each(v5Schemes)("keeps the $scheme v5 wash near the paper", ({ scheme, solid, paper }) => {
|
|
45
|
+
const wash = resolveAccentTintedSurface({ solid, paper })!;
|
|
46
|
+
expect(wash).toMatch(/^#[0-9a-f]{6}$/);
|
|
47
|
+
const contrast = contrastRatio(lum(wash), lum(paper));
|
|
48
|
+
expect(contrast).toBeGreaterThanOrEqual(1.18);
|
|
49
|
+
expect(contrast).toBeLessThanOrEqual(1.3);
|
|
50
|
+
if (scheme === "light") expect(hsl(wash).lightness).toBeGreaterThan(85);
|
|
51
|
+
else expect(lum(wash)).toBeGreaterThan(lum(paper));
|
|
102
52
|
});
|
|
103
53
|
|
|
104
|
-
it("
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
).
|
|
108
|
-
expect(
|
|
109
|
-
|
|
110
|
-
).toBeUndefined();
|
|
111
|
-
expect(
|
|
112
|
-
resolveAccentTintedSurface({
|
|
113
|
-
steps: Array.from({ length: 12 }, () => "var(--nope)"),
|
|
114
|
-
paper: lightPaper,
|
|
115
|
-
ink: lightInk,
|
|
116
|
-
}),
|
|
117
|
-
).toBeUndefined();
|
|
54
|
+
it("retains the hue of a chromatic solid in a pale wash", () => {
|
|
55
|
+
const solid = "#634fc4";
|
|
56
|
+
const wash = resolveAccentTintedSurface({ solid, paper: "#ffffff" })!;
|
|
57
|
+
const hueDistance = Math.abs(hsl(wash).hue - hsl(solid).hue);
|
|
58
|
+
expect(Math.min(hueDistance, 360 - hueDistance)).toBeLessThan(10);
|
|
59
|
+
expect(hsl(wash).lightness).toBeGreaterThan(85);
|
|
118
60
|
});
|
|
119
|
-
});
|
|
120
61
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const solids = lightAccent.slice(0, 10).map((value, i) => ({
|
|
124
|
-
token: `$accent${i + 1}`,
|
|
125
|
-
value,
|
|
126
|
-
}));
|
|
127
|
-
// Stock light accentBackground is the mid solid (step 3, 6.09:1 on paper).
|
|
62
|
+
it.each(v5Schemes)("keeps an anchor readable on the $scheme v5 wash", ({ solid, paper, ink }) => {
|
|
63
|
+
const wash = resolveAccentTintedSurface({ solid, paper })!;
|
|
128
64
|
expect(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
solids,
|
|
132
|
-
paper: lightPaper,
|
|
133
|
-
ink: lightInk,
|
|
134
|
-
}),
|
|
135
|
-
).toBe("$accentBackground");
|
|
65
|
+
Math.max(contrastRatio(lum(wash), lum(paper)), contrastRatio(lum(wash), lum(ink))),
|
|
66
|
+
).toBeGreaterThanOrEqual(4.5);
|
|
136
67
|
});
|
|
137
68
|
|
|
138
|
-
it("
|
|
139
|
-
|
|
140
|
-
token: `$accent${i + 1}`,
|
|
141
|
-
value,
|
|
142
|
-
}));
|
|
143
|
-
const token = resolveAaSolidFill({
|
|
144
|
-
preferred: { token: "$accentBackground", value: darkAccent[9] },
|
|
145
|
-
solids,
|
|
146
|
-
paper: darkPaper,
|
|
147
|
-
ink: darkInk,
|
|
148
|
-
});
|
|
149
|
-
expect(token).not.toBe("$accentBackground");
|
|
150
|
-
const picked = solids.find((s) => s.token === token);
|
|
151
|
-
expect(picked?.value).toBeTruthy();
|
|
152
|
-
const fillLum = lum(picked!.value!);
|
|
153
|
-
const best = Math.max(
|
|
154
|
-
contrastRatio(fillLum, lum(darkPaper)),
|
|
155
|
-
contrastRatio(fillLum, lum(darkInk)),
|
|
156
|
-
);
|
|
157
|
-
expect(best).toBeGreaterThanOrEqual(4.5);
|
|
158
|
-
// Nearest passing solid to the 60% mid is accent9 (57%, 4.60:1).
|
|
159
|
-
expect(token).toBe("$accent9");
|
|
69
|
+
it("returns the solid when no mix reaches the wash contrast", () => {
|
|
70
|
+
expect(resolveAccentTintedSurface({ solid: "#fafafa", paper: "#ffffff" })).toBe("#fafafa");
|
|
160
71
|
});
|
|
161
72
|
|
|
162
|
-
it("returns
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
paper: undefined,
|
|
168
|
-
ink: darkInk,
|
|
169
|
-
}),
|
|
170
|
-
).toBe("$accentBackground");
|
|
73
|
+
it("returns undefined for missing or unparseable inputs", () => {
|
|
74
|
+
for (const invalid of [undefined, "var(--nope)"]) {
|
|
75
|
+
expect(resolveAccentTintedSurface({ solid: invalid, paper: "#ffffff" })).toBeUndefined();
|
|
76
|
+
expect(resolveAccentTintedSurface({ solid: "#634fc4", paper: invalid })).toBeUndefined();
|
|
77
|
+
}
|
|
171
78
|
});
|
|
172
79
|
});
|
|
173
80
|
|
|
@@ -186,7 +93,7 @@ describe("resolveLegibleInkOn (MPO-448)", () => {
|
|
|
186
93
|
it("hands a muted title on the dark accent tint to the readable anchor (4.05:1)", () => {
|
|
187
94
|
expect(
|
|
188
95
|
resolveLegibleInkOn({
|
|
189
|
-
fill:
|
|
96
|
+
fill: "hsla(250, 50%, 35%, 1)",
|
|
190
97
|
preferred: "#a09ba6",
|
|
191
98
|
paper: darkPaper,
|
|
192
99
|
ink: darkInk,
|
|
@@ -10,27 +10,9 @@ import {
|
|
|
10
10
|
} from "./colorRules";
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* Two catalog-wide contrast traps this module closes:
|
|
17
|
-
*
|
|
18
|
-
* 1. Text sitting ON an accent/solid FILL (`useReadableTextOn`). Components
|
|
19
|
-
* that paint a selected/active row, chip or avatar with `$accentBackground`
|
|
20
|
-
* (or any solid step) and then let the label inherit the scheme's `$color`
|
|
21
|
-
* render ink-on-accent (~2.6:1) or, worse, near-white-on-bright-tint
|
|
22
|
-
* (~1.2:1). The readable foreground is whichever scheme anchor
|
|
23
|
-
* (paper `$color1` / ink `$color12`) carries more contrast on the ACTUAL
|
|
24
|
-
* fill — never assumed.
|
|
25
|
-
*
|
|
26
|
-
* 2. Accent-hued text on the PAGE (`useAccentOnSurface`). The readable accent
|
|
27
|
-
* is the most chromatic step of the theme's own accent ramp that still
|
|
28
|
-
* clears AA (4.5:1) on both page surfaces — a mid accent in light, a pale
|
|
29
|
-
* accent in dark.
|
|
30
|
-
*
|
|
31
|
-
* Both read live theme values so they stay correct across schemes, tints and
|
|
32
|
-
* custom accent palettes; they mirror the runtime pattern already used by the
|
|
33
|
-
* Switch thumb, Toast and Calendar chip.
|
|
13
|
+
* Live-theme contrast helpers for text on solids and accent ink on surfaces.
|
|
14
|
+
* The theme build holds the accent solid to the text floor. Accent-intent
|
|
15
|
+
* surfaces use a wash; selected rows and segments use the neutral tier.
|
|
34
16
|
*/
|
|
35
17
|
|
|
36
18
|
function resolveTokenValue(
|
|
@@ -43,10 +25,10 @@ function resolveTokenValue(
|
|
|
43
25
|
}
|
|
44
26
|
|
|
45
27
|
/**
|
|
46
|
-
* Readable text/glyph color to sit ON `fill` (a token like "$
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
28
|
+
* Readable text/glyph color to sit ON `fill` (a token like "$color8" or a
|
|
29
|
+
* resolved value, such as the wash from `useAccentTintedSurface`): the
|
|
30
|
+
* scheme's paper (`$color1`) or ink (`$color12`) anchor, whichever contrasts
|
|
31
|
+
* more on the resolved fill, returned as a concrete
|
|
50
32
|
* value so no nested sub-theme can flip it. When neither anchor clears AA on
|
|
51
33
|
* the fill, the pole that does (`holdInkFloor`). Returns `undefined` when any
|
|
52
34
|
* color fails to resolve/parse — callers keep their own default in that case.
|
|
@@ -153,168 +135,46 @@ export function useAccentOnSurface(): string {
|
|
|
153
135
|
}, [c1, c2, ink, steps.join("|")]);
|
|
154
136
|
}
|
|
155
137
|
|
|
156
|
-
|
|
157
|
-
* Accent SURFACE tier of the accent ramp, mirrored by scheme — the fill for a
|
|
158
|
-
* selected/current chip that must carry the accent language at SURFACE
|
|
159
|
-
* strength rather than as a solid (LC-05: selection is accent; the ViewSwitcher
|
|
160
|
-
* rider pins segmented-control strength at "tinted", not the solid an accent
|
|
161
|
-
* CTA takes).
|
|
162
|
-
*
|
|
163
|
-
* The step is a declared ramp POSITION, the same way the neutral ramp declares
|
|
164
|
-
* its tiers (surface `$color2`, UI background `$color3`-`$color5`, text
|
|
165
|
-
* `$color11`-`$color12`): the accent ramp's surface end is its palest step in a
|
|
166
|
-
* light scheme and its deepest in a dark one, so the chip reads as a tinted
|
|
167
|
-
* surface on either page. Scheme polarity comes from the paper/ink ANCHORS,
|
|
168
|
-
* never from a surface token — a tint re-ramps surfaces, and keying off one
|
|
169
|
-
* made this pick drift a step per tint (measured: the chip slid from the pale
|
|
170
|
-
* accent to the palest accent under a yellow tint).
|
|
171
|
-
*
|
|
172
|
-
* Returns `undefined` when the accent ramp is absent or unparseable (tint
|
|
173
|
-
* sub-themes without an accent ramp) — callers keep their neutral default
|
|
174
|
-
* rather than render an unthemed chip.
|
|
175
|
-
*/
|
|
176
|
-
// Ramp positions (1-based) of the accent surface tier per scheme.
|
|
177
|
-
const ACCENT_SURFACE_STEP_LIGHT = 11;
|
|
178
|
-
const ACCENT_SURFACE_STEP_DARK = 1;
|
|
138
|
+
const ACCENT_WASH_RATIO = 1.2;
|
|
179
139
|
|
|
180
140
|
export interface ResolveAccentTintedSurfaceInput {
|
|
181
|
-
|
|
182
|
-
steps: (string | undefined)[];
|
|
183
|
-
/** Scheme paper anchor (`$color1`). */
|
|
141
|
+
solid?: string;
|
|
184
142
|
paper?: string;
|
|
185
|
-
/** Scheme ink anchor (`$color12`). */
|
|
186
|
-
ink?: string;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/** Pure resolver behind {@link useAccentTintedSurface} — see its docs. */
|
|
190
|
-
export function resolveAccentTintedSurface({
|
|
191
|
-
steps,
|
|
192
|
-
paper,
|
|
193
|
-
ink,
|
|
194
|
-
}: ResolveAccentTintedSurfaceInput): string | undefined {
|
|
195
|
-
const paperHex = paper ? normalizeToHex(paper) : null;
|
|
196
|
-
const inkHex = ink ? normalizeToHex(ink) : null;
|
|
197
|
-
if (!paperHex || !inkHex) return undefined;
|
|
198
|
-
const isLight = relativeLuminance(paperHex) > relativeLuminance(inkHex);
|
|
199
|
-
const step = steps[(isLight ? ACCENT_SURFACE_STEP_LIGHT : ACCENT_SURFACE_STEP_DARK) - 1];
|
|
200
|
-
if (!step || !normalizeToHex(step)) return undefined;
|
|
201
|
-
return step;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
export function useAccentTintedSurface(): string | undefined {
|
|
205
|
-
const theme = useTamaguiTheme() as unknown as Record<string, { val?: unknown } | undefined>;
|
|
206
|
-
const paper = resolveTokenValue(theme, "color1");
|
|
207
|
-
const ink = resolveTokenValue(theme, "color12");
|
|
208
|
-
const steps: (string | undefined)[] = [];
|
|
209
|
-
for (let i = 1; i <= 12; i++) steps.push(resolveTokenValue(theme, `accent${i}`));
|
|
210
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
211
|
-
return useMemo(
|
|
212
|
-
() => resolveAccentTintedSurface({ steps, paper, ink }),
|
|
213
|
-
[paper, ink, steps.join("|")],
|
|
214
|
-
);
|
|
215
143
|
}
|
|
216
144
|
|
|
217
145
|
/**
|
|
218
|
-
*
|
|
219
|
-
*
|
|
146
|
+
* Accent wash for an accent-intent surface, mixed from the accent solid and
|
|
147
|
+
* paper. Selection uses the neutral tier in `componentColors.selection` or
|
|
148
|
+
* `formSelectionColors`, never this wash.
|
|
220
149
|
*/
|
|
221
|
-
export
|
|
222
|
-
|
|
223
|
-
value: string | undefined;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export interface ResolveAaSolidFillInput {
|
|
227
|
-
/** The LC-05 selected solid, usually `$accentBackground`. */
|
|
228
|
-
preferred: AaSolidCandidate;
|
|
229
|
-
/**
|
|
230
|
-
* Accent solids (steps 1–10). Pale anchors 11–12 are tinted surfaces, not
|
|
231
|
-
* selection solids — they would pass AA as chips and fail LC-05 as marks.
|
|
232
|
-
*/
|
|
233
|
-
solids: AaSolidCandidate[];
|
|
234
|
-
paper?: string;
|
|
235
|
-
ink?: string;
|
|
236
|
-
/** Defaults to {@link aaTextContrastRatio} (4.5). */
|
|
237
|
-
floor?: number;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function bestAnchorContrast(fill: string, paperLum: number, inkLum: number): number {
|
|
241
|
-
const hex = normalizeToHex(fill);
|
|
242
|
-
if (!hex) return 0;
|
|
243
|
-
const fillLum = relativeLuminance(hex);
|
|
244
|
-
return Math.max(contrastRatio(fillLum, paperLum), contrastRatio(fillLum, inkLum));
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Pick an accent solid that can carry AA text (paper or ink ≥ 4.5).
|
|
249
|
-
*
|
|
250
|
-
* A mid-chroma solid can clear the graphic 3:1 tier and still miss the text
|
|
251
|
-
* floor with both scheme anchors (the stock dark `#7766cc` measured 4.02:1
|
|
252
|
-
* before the build held `$accentBackground` to it, MPO-448). Walking to the
|
|
253
|
-
* nearest passing solid keeps the mark on the accent channel (LC-05) instead
|
|
254
|
-
* of dropping to ink or a tinted surface.
|
|
255
|
-
*
|
|
256
|
-
* Returns the preferred token when it already clears the floor, otherwise
|
|
257
|
-
* the solid token whose resolved luminance is closest to the preferred fill.
|
|
258
|
-
*/
|
|
259
|
-
export function resolveAaSolidFill({
|
|
260
|
-
preferred,
|
|
261
|
-
solids,
|
|
150
|
+
export function resolveAccentTintedSurface({
|
|
151
|
+
solid,
|
|
262
152
|
paper,
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}: ResolveAaSolidFillInput): string {
|
|
153
|
+
}: ResolveAccentTintedSurfaceInput): string | undefined {
|
|
154
|
+
const solidHex = solid ? normalizeToHex(solid) : null;
|
|
266
155
|
const paperHex = paper ? normalizeToHex(paper) : null;
|
|
267
|
-
|
|
268
|
-
if (!paperHex || !inkHex) return preferred.token;
|
|
156
|
+
if (!solidHex || !paperHex) return undefined;
|
|
269
157
|
const paperLum = relativeLuminance(paperHex);
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
158
|
+
const solidRgb = [1, 3, 5].map((offset) => parseInt(solidHex.slice(offset, offset + 2), 16));
|
|
159
|
+
const paperRgb = [1, 3, 5].map((offset) => parseInt(paperHex.slice(offset, offset + 2), 16));
|
|
160
|
+
for (let step = 1; step <= 50; step++) {
|
|
161
|
+
const share = step * 0.02;
|
|
162
|
+
const mix = `#${paperRgb
|
|
163
|
+
.map((channel, index) =>
|
|
164
|
+
Math.round(channel + (solidRgb[index] - channel) * share)
|
|
165
|
+
.toString(16)
|
|
166
|
+
.padStart(2, "0"),
|
|
167
|
+
)
|
|
168
|
+
.join("")}`;
|
|
169
|
+
if (contrastRatio(relativeLuminance(mix), paperLum) >= ACCENT_WASH_RATIO) return mix;
|
|
274
170
|
}
|
|
275
|
-
|
|
276
|
-
const preferredHex = preferred.value ? normalizeToHex(preferred.value) : null;
|
|
277
|
-
const preferredLum = preferredHex ? relativeLuminance(preferredHex) : undefined;
|
|
278
|
-
let bestToken: string | undefined;
|
|
279
|
-
let bestDist = Number.POSITIVE_INFINITY;
|
|
280
|
-
for (const { token, value } of solids) {
|
|
281
|
-
if (!value || bestAnchorContrast(value, paperLum, inkLum) < floor) continue;
|
|
282
|
-
const hex = normalizeToHex(value);
|
|
283
|
-
if (!hex) continue;
|
|
284
|
-
const dist = preferredLum === undefined ? 0 : Math.abs(relativeLuminance(hex) - preferredLum);
|
|
285
|
-
if (dist < bestDist) {
|
|
286
|
-
bestDist = dist;
|
|
287
|
-
bestToken = token;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return bestToken ?? preferred.token;
|
|
171
|
+
return solidHex;
|
|
291
172
|
}
|
|
292
173
|
|
|
293
|
-
/**
|
|
294
|
-
|
|
295
|
-
* See {@link resolveAaSolidFill}.
|
|
296
|
-
*/
|
|
297
|
-
export function useAaSolidFill(preferredToken: string): string {
|
|
174
|
+
/** Accent-intent wash; selection belongs to the shared neutral tier. */
|
|
175
|
+
export function useAccentTintedSurface(): string | undefined {
|
|
298
176
|
const theme = useTamaguiTheme() as unknown as Record<string, { val?: unknown } | undefined>;
|
|
177
|
+
const solid = resolveTokenValue(theme, "accentBackground");
|
|
299
178
|
const paper = resolveTokenValue(theme, "color1");
|
|
300
|
-
|
|
301
|
-
const preferredValue = preferredToken.startsWith("$")
|
|
302
|
-
? resolveTokenValue(theme, preferredToken)
|
|
303
|
-
: preferredToken;
|
|
304
|
-
const solids: AaSolidCandidate[] = [];
|
|
305
|
-
for (let i = 1; i <= 10; i++) {
|
|
306
|
-
solids.push({ token: `$accent${i}`, value: resolveTokenValue(theme, `accent${i}`) });
|
|
307
|
-
}
|
|
308
|
-
const solidsKey = solids.map((s) => s.value ?? "").join("|");
|
|
309
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
310
|
-
return useMemo(
|
|
311
|
-
() =>
|
|
312
|
-
resolveAaSolidFill({
|
|
313
|
-
preferred: { token: preferredToken, value: preferredValue },
|
|
314
|
-
solids,
|
|
315
|
-
paper,
|
|
316
|
-
ink,
|
|
317
|
-
}),
|
|
318
|
-
[preferredToken, preferredValue, paper, ink, solidsKey],
|
|
319
|
-
);
|
|
179
|
+
return useMemo(() => resolveAccentTintedSurface({ solid, paper }), [solid, paper]);
|
|
320
180
|
}
|