@multiplatform.one/theme 8.3.0 → 8.4.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/dist/cjs/audit/constraintAudit.native.js.map +1 -1
- package/dist/cjs/audit/designTokens.native.js.map +1 -1
- package/dist/cjs/audit/diffReport.native.js.map +1 -1
- package/dist/cjs/audit/index.native.js.map +1 -1
- package/dist/cjs/audit/themeMatrix.native.js.map +1 -1
- package/dist/cjs/dates.native.js.map +1 -1
- package/dist/cjs/devWarn.native.js.map +1 -1
- package/dist/cjs/expo.native.js.map +1 -1
- package/dist/cjs/figma/figmaTokens.native.js.map +1 -1
- package/dist/cjs/font.native.js.map +1 -1
- package/dist/cjs/fonts/createFontLoader.native.js.map +1 -1
- package/dist/cjs/fonts/index.native.js.map +1 -1
- package/dist/cjs/fonts/interFaces.native.js.map +1 -1
- package/dist/cjs/fonts/types.native.js.map +1 -1
- package/dist/cjs/index.gnome.cjs +4 -1
- package/dist/cjs/index.gnome.native.js +3 -0
- package/dist/cjs/index.gnome.native.js.map +1 -1
- package/dist/cjs/index.native.js +1 -1
- package/dist/cjs/index.native.js.map +1 -1
- package/dist/cjs/keyboardFocusRing.native.js.map +1 -1
- package/dist/cjs/menuRow.cjs +1 -1
- package/dist/cjs/menuRow.native.js +1 -1
- package/dist/cjs/menuRow.native.js.map +1 -1
- package/dist/cjs/nativeTwins.native.js.map +1 -1
- package/dist/cjs/numbers.native.js.map +1 -1
- package/dist/cjs/theme/Heading.native.js.map +1 -1
- package/dist/cjs/theme/IconSlot.native.js.map +1 -1
- package/dist/cjs/theme/Intent.native.js.map +1 -1
- package/dist/cjs/theme/Preset.native.js.map +1 -1
- package/dist/cjs/theme/PresetContext.native.js.map +1 -1
- package/dist/cjs/theme/Surface.native.js.map +1 -1
- package/dist/cjs/theme/Text.native.js.map +1 -1
- package/dist/cjs/theme/Tint.native.js.map +1 -1
- package/dist/cjs/theme/accentInk.cjs +6 -2
- package/dist/cjs/theme/accentInk.native.js +6 -2
- package/dist/cjs/theme/accentInk.native.js.map +1 -1
- package/dist/cjs/theme/activateThemeConfig.native.js.map +1 -1
- package/dist/cjs/theme/animations/css.native.js.map +1 -1
- package/dist/cjs/theme/animations/index.native.js.map +1 -1
- package/dist/cjs/theme/animations/reactNative.native.js.map +1 -1
- package/dist/cjs/theme/chartPalette.cjs +3 -3
- package/dist/cjs/theme/chartPalette.native.js +5 -4
- package/dist/cjs/theme/chartPalette.native.js.map +1 -1
- package/dist/cjs/theme/colorRules.cjs +5 -0
- package/dist/cjs/theme/colorRules.native.js +6 -0
- package/dist/cjs/theme/colorRules.native.js.map +1 -1
- package/dist/cjs/theme/controlTiers.cjs +9 -5
- package/dist/cjs/theme/controlTiers.native.js +10 -5
- package/dist/cjs/theme/controlTiers.native.js.map +1 -1
- package/dist/cjs/theme/cookies.native.js.map +1 -1
- package/dist/cjs/theme/createDefaultThemeConfig.native.js.map +1 -1
- package/dist/cjs/theme/createThemes.cjs +212 -8
- package/dist/cjs/theme/createThemes.native.js +397 -12
- package/dist/cjs/theme/createThemes.native.js.map +1 -1
- package/dist/cjs/theme/dedupeInherited.native.js.map +1 -1
- package/dist/cjs/theme/defaults/accent.native.js.map +1 -1
- package/dist/cjs/theme/defaults/base.native.js.map +1 -1
- package/dist/cjs/theme/defaults/builderOptions.cjs +9 -4
- package/dist/cjs/theme/defaults/builderOptions.native.js +9 -4
- package/dist/cjs/theme/defaults/builderOptions.native.js.map +1 -1
- package/dist/cjs/theme/defaults/categoryFonts.native.js.map +1 -1
- package/dist/cjs/theme/defaults/fonts.native.js.map +1 -1
- package/dist/cjs/theme/defaults/house.native.js.map +1 -1
- package/dist/cjs/theme/defaults/index.native.js.map +1 -1
- package/dist/cjs/theme/devtools/ColorLineVisualizer.native.js.map +1 -1
- package/dist/cjs/theme/devtools/ThemeDevtoolsPanel.native.js.map +1 -1
- package/dist/cjs/theme/devtools/animationOptions.native.js.map +1 -1
- package/dist/cjs/theme/devtools/buildSnapshot.native.js.map +1 -1
- package/dist/cjs/theme/devtools/index.native.js.map +1 -1
- package/dist/cjs/theme/devtools/knobIcons.native.js.map +1 -1
- package/dist/cjs/theme/devtools/vizConstants.native.js.map +1 -1
- package/dist/cjs/theme/dynamicType.native.js.map +1 -1
- package/dist/cjs/theme/fieldFocus.native.js.map +1 -1
- package/dist/cjs/theme/fillContrast.cjs +4 -1
- package/dist/cjs/theme/fillContrast.native.js +4 -1
- package/dist/cjs/theme/fillContrast.native.js.map +1 -1
- package/dist/cjs/theme/focusState.native.js.map +1 -1
- package/dist/cjs/theme/fontCategoryMetrics.native.js.map +1 -1
- package/dist/cjs/theme/fontCategoryStacks.native.js.map +1 -1
- package/dist/cjs/theme/glyphPaint.native.js.map +1 -1
- package/dist/cjs/theme/glyphTint.native.js.map +1 -1
- package/dist/cjs/theme/groupGeometry.native.js.map +1 -1
- package/dist/cjs/theme/index.cjs +1 -0
- package/dist/cjs/theme/index.native.js +1 -0
- package/dist/cjs/theme/index.native.js.map +1 -1
- package/dist/cjs/theme/intents.native.js.map +1 -1
- package/dist/cjs/theme/knobs.native.js.map +1 -1
- package/dist/cjs/theme/layoutTokens.native.js.map +1 -1
- package/dist/cjs/theme/listRows.native.js.map +1 -1
- package/dist/cjs/theme/mergedTokens.native.js.map +1 -1
- package/dist/cjs/theme/motion.native.js.map +1 -1
- package/dist/cjs/theme/motionStops.native.js.map +1 -1
- package/dist/cjs/theme/motionTiming.native.js.map +1 -1
- package/dist/cjs/theme/nativeMonoFamily.native.js.map +1 -1
- package/dist/cjs/theme/nativePresetSwitch.native.js.map +1 -1
- package/dist/cjs/theme/nativeSchemePreference.native.js.map +1 -1
- package/dist/cjs/theme/nativeTheme.native.js.map +1 -1
- package/dist/cjs/theme/nativeTransitionProps.native.js.map +1 -1
- package/dist/cjs/theme/overlaySurfaces.native.js.map +1 -1
- package/dist/cjs/theme/pageGutter.native.js.map +1 -1
- package/dist/cjs/theme/preset.types.native.js.map +1 -1
- package/dist/cjs/theme/presetPreview.native.js.map +1 -1
- package/dist/cjs/theme/presetStore.native.js.map +1 -1
- package/dist/cjs/theme/presetUserSync.native.js.map +1 -1
- package/dist/cjs/theme/presets.native.js.map +1 -1
- package/dist/cjs/theme/radiusClass.native.js.map +1 -1
- package/dist/cjs/theme/radiusClassTable.generated.native.js.map +1 -1
- package/dist/cjs/theme/ramps.native.js.map +1 -1
- package/dist/cjs/theme/readableColor.cjs +20 -9
- package/dist/cjs/theme/readableColor.native.js +20 -9
- package/dist/cjs/theme/readableColor.native.js.map +1 -1
- package/dist/cjs/theme/recipeInputs.native.js.map +1 -1
- package/dist/cjs/theme/recipes.native.js.map +1 -1
- package/dist/cjs/theme/resolveKnobs.cjs +4 -1
- package/dist/cjs/theme/resolveKnobs.native.js +4 -1
- package/dist/cjs/theme/resolveKnobs.native.js.map +1 -1
- package/dist/cjs/theme/scale/activeScale.native.js.map +1 -1
- package/dist/cjs/theme/scale/deriveRecipeInputs.native.js.map +1 -1
- package/dist/cjs/theme/scale/generateScale.native.js.map +1 -1
- package/dist/cjs/theme/scale/index.native.js.map +1 -1
- package/dist/cjs/theme/scale/scaleAttribute.native.js.map +1 -1
- package/dist/cjs/theme/scale/scaleCss.native.js.map +1 -1
- package/dist/cjs/theme/scale/scaleFonts.native.js.map +1 -1
- package/dist/cjs/theme/scale/scaleInputs.native.js.map +1 -1
- package/dist/cjs/theme/scale/themeScales.native.js.map +1 -1
- package/dist/cjs/theme/scale/useActiveScale.native.js.map +1 -1
- package/dist/cjs/theme/schemeStore.native.js.map +1 -1
- package/dist/cjs/theme/shared.native.js.map +1 -1
- package/dist/cjs/theme/sizeRecipes.native.js.map +1 -1
- package/dist/cjs/theme/subsetThemes.native.js.map +1 -1
- package/dist/cjs/theme/themeValue.native.js.map +1 -1
- package/dist/cjs/theme/tileSizes.native.js.map +1 -1
- package/dist/cjs/theme/tintHues.native.js.map +1 -1
- package/dist/cjs/theme/tokenNumber.cjs +34 -0
- package/dist/cjs/theme/tokenNumber.native.js +39 -0
- package/dist/cjs/theme/tokenNumber.native.js.map +1 -0
- package/dist/cjs/theme/touchManipulation.native.js.map +1 -1
- package/dist/cjs/theme/useResolvedKnobs.native.js.map +1 -1
- package/dist/cjs/theme/useTouchSurface.native.js.map +1 -1
- package/dist/cjs/theme/v3TokenRoles.native.js.map +1 -1
- package/dist/esm/index.gnome.mjs +3 -2
- package/dist/esm/index.gnome.mjs.map +1 -1
- package/dist/esm/index.gnome.native.js +3 -2
- package/dist/esm/index.gnome.native.js.map +1 -1
- package/dist/esm/menuRow.mjs +1 -1
- package/dist/esm/menuRow.native.js +1 -1
- package/dist/esm/theme/accentInk.mjs +6 -3
- package/dist/esm/theme/accentInk.mjs.map +1 -1
- package/dist/esm/theme/accentInk.native.js +6 -3
- package/dist/esm/theme/accentInk.native.js.map +1 -1
- package/dist/esm/theme/chartPalette.mjs +3 -3
- package/dist/esm/theme/chartPalette.mjs.map +1 -1
- package/dist/esm/theme/chartPalette.native.js +5 -4
- package/dist/esm/theme/chartPalette.native.js.map +1 -1
- package/dist/esm/theme/colorRules.mjs +5 -1
- package/dist/esm/theme/colorRules.mjs.map +1 -1
- package/dist/esm/theme/colorRules.native.js +6 -1
- package/dist/esm/theme/colorRules.native.js.map +1 -1
- package/dist/esm/theme/controlTiers.mjs +9 -6
- package/dist/esm/theme/controlTiers.mjs.map +1 -1
- package/dist/esm/theme/controlTiers.native.js +10 -6
- package/dist/esm/theme/controlTiers.native.js.map +1 -1
- package/dist/esm/theme/createThemes.mjs +215 -12
- package/dist/esm/theme/createThemes.mjs.map +1 -1
- package/dist/esm/theme/createThemes.native.js +400 -16
- package/dist/esm/theme/createThemes.native.js.map +1 -1
- package/dist/esm/theme/defaults/builderOptions.mjs +10 -6
- package/dist/esm/theme/defaults/builderOptions.mjs.map +1 -1
- package/dist/esm/theme/defaults/builderOptions.native.js +10 -6
- package/dist/esm/theme/defaults/builderOptions.native.js.map +1 -1
- package/dist/esm/theme/fillContrast.mjs +5 -2
- package/dist/esm/theme/fillContrast.mjs.map +1 -1
- package/dist/esm/theme/fillContrast.native.js +5 -2
- package/dist/esm/theme/fillContrast.native.js.map +1 -1
- package/dist/esm/theme/index.mjs +1 -0
- package/dist/esm/theme/index.mjs.map +1 -1
- package/dist/esm/theme/index.native.js +1 -0
- package/dist/esm/theme/index.native.js.map +1 -1
- package/dist/esm/theme/nativeTheme.mjs.map +1 -1
- package/dist/esm/theme/nativeTheme.native.js.map +1 -1
- package/dist/esm/theme/readableColor.mjs +20 -9
- package/dist/esm/theme/readableColor.mjs.map +1 -1
- package/dist/esm/theme/readableColor.native.js +20 -9
- package/dist/esm/theme/readableColor.native.js.map +1 -1
- package/dist/esm/theme/resolveKnobs.mjs +4 -1
- package/dist/esm/theme/resolveKnobs.mjs.map +1 -1
- package/dist/esm/theme/resolveKnobs.native.js +4 -1
- package/dist/esm/theme/resolveKnobs.native.js.map +1 -1
- package/dist/esm/theme/tokenNumber.mjs +9 -0
- package/dist/esm/theme/tokenNumber.mjs.map +1 -0
- package/dist/esm/theme/tokenNumber.native.js +11 -0
- package/dist/esm/theme/tokenNumber.native.js.map +1 -0
- package/package.json +5 -5
- package/src/figma/figmaTokens.spec.ts +2 -1
- package/src/index.gnome.ts +2 -0
- package/src/menuRow.palette.spec.ts +20 -7
- package/src/menuRow.spec.ts +2 -2
- package/src/menuRow.ts +3 -3
- package/src/theme/accentInk.ts +8 -2
- package/src/theme/accentOnSurface.spec.tsx +6 -1
- package/src/theme/chartPalette.spec.ts +2 -2
- package/src/theme/chartPalette.ts +9 -9
- package/src/theme/colorRules.ts +13 -0
- package/src/theme/controlEdges.spec.ts +114 -0
- package/src/theme/controlTiers.ts +18 -9
- package/src/theme/createDefaultThemeConfig.spec.ts +135 -12
- package/src/theme/createThemes.ts +363 -24
- package/src/theme/defaults/builderOptions.ts +25 -8
- package/src/theme/defaults/secondaryInk.spec.ts +10 -5
- package/src/theme/fillContrast.spec.ts +8 -2
- package/src/theme/fillContrast.ts +10 -2
- package/src/theme/houseGround.spec.ts +1 -1
- package/src/theme/index.ts +1 -0
- package/src/theme/nativeTheme.tsx +1 -1
- package/src/theme/readableColor.ts +23 -11
- package/src/theme/recipes.ts +1 -0
- package/src/theme/resolveKnobs.spec.ts +4 -4
- package/src/theme/resolveKnobs.ts +6 -4
- package/src/theme/selectionFill.spec.ts +58 -0
- package/src/theme/switchThumb.spec.ts +13 -1
- package/src/theme/themeKeyRegistry.spec.ts +26 -0
- package/src/theme/tokenNumber.spec.tsx +47 -0
- package/src/theme/tokenNumber.ts +18 -0
- package/types/index.gnome.d.ts +2 -1
- package/types/index.gnome.d.ts.map +1 -1
- package/types/menuRow.d.ts +5 -5
- package/types/theme/accentInk.d.ts +1 -0
- package/types/theme/accentInk.d.ts.map +1 -1
- package/types/theme/chartPalette.d.ts +2 -2
- package/types/theme/colorRules.d.ts +9 -0
- package/types/theme/colorRules.d.ts.map +1 -1
- package/types/theme/controlTiers.d.ts +11 -5
- package/types/theme/controlTiers.d.ts.map +1 -1
- package/types/theme/createThemes.d.ts +21 -12
- package/types/theme/createThemes.d.ts.map +1 -1
- package/types/theme/defaults/builderOptions.d.ts +9 -1
- package/types/theme/defaults/builderOptions.d.ts.map +1 -1
- package/types/theme/fillContrast.d.ts.map +1 -1
- package/types/theme/index.d.ts +1 -0
- package/types/theme/index.d.ts.map +1 -1
- package/types/theme/nativeTheme.d.ts +1 -1
- package/types/theme/nativeTheme.d.ts.map +1 -1
- package/types/theme/readableColor.d.ts.map +1 -1
- package/types/theme/recipes.d.ts +3 -0
- package/types/theme/recipes.d.ts.map +1 -1
- package/types/theme/resolveKnobs.d.ts.map +1 -1
- package/types/theme/tokenNumber.d.ts +10 -0
- package/types/theme/tokenNumber.d.ts.map +1 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { contrastRatio, minContrastRatio, normalizeToHex, relativeLuminance } from "./colorRules";
|
|
3
|
+
import { createThemesBuilder } from "./createThemes";
|
|
4
|
+
import { resolveThemeThroughParents } from "./dedupeInherited";
|
|
5
|
+
import { defaultAccentTheme } from "./defaults/accent";
|
|
6
|
+
import { defaultBaseTheme } from "./defaults/base";
|
|
7
|
+
import { defaultBuilderOptions } from "./defaults/builderOptions";
|
|
8
|
+
|
|
9
|
+
const themes = createThemesBuilder(
|
|
10
|
+
defaultBaseTheme,
|
|
11
|
+
defaultAccentTheme,
|
|
12
|
+
defaultBuilderOptions,
|
|
13
|
+
).themes() as Record<string, Record<string, string>>;
|
|
14
|
+
|
|
15
|
+
const intents = new Set(["error", "warning", "success"]);
|
|
16
|
+
|
|
17
|
+
function ratio(a: string | undefined, b: string | undefined) {
|
|
18
|
+
const aHex = normalizeToHex(a ?? "");
|
|
19
|
+
const bHex = normalizeToHex(b ?? "");
|
|
20
|
+
if (!aHex || !bHex) throw new Error(`Expected opaque colours: ${a}, ${b}`);
|
|
21
|
+
return contrastRatio(relativeLuminance(aHex), relativeLuminance(bHex));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const resolve = (name: string) =>
|
|
25
|
+
resolveThemeThroughParents(themes, name) as unknown as Record<string, string>;
|
|
26
|
+
|
|
27
|
+
const scopes = Object.keys(themes).filter(
|
|
28
|
+
(name) => /^(light|dark)(_[a-z]+)?$/.test(name) && !/_(accent|active)$/.test(name),
|
|
29
|
+
);
|
|
30
|
+
const hueScopes = scopes.filter((name) => !intents.has(name.split("_")[1] ?? ""));
|
|
31
|
+
const intentScopes = scopes.filter((name) => intents.has(name.split("_")[1] ?? ""));
|
|
32
|
+
|
|
33
|
+
describe("LC-299 CONTROL-EDGE: control edges clear WCAG 1.4.11's 3:1", () => {
|
|
34
|
+
it("covers both schemes and every preset hue", () => {
|
|
35
|
+
for (const hue of ["blue", "purple", "green", "orange", "pink"]) {
|
|
36
|
+
expect(hueScopes).toContain(`light_${hue}`);
|
|
37
|
+
expect(hueScopes).toContain(`dark_${hue}`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
for (const scope of hueScopes) {
|
|
42
|
+
it(`${scope}: field edges hold 3:1 on the page and on their own fill`, () => {
|
|
43
|
+
const page = resolve(scope);
|
|
44
|
+
for (const control of ["Input", "TextArea", "SelectTrigger", "InputContainerFrame"]) {
|
|
45
|
+
const field = resolve(`${scope}_${control}`);
|
|
46
|
+
for (const key of ["borderColorField", "borderColorFieldHover"]) {
|
|
47
|
+
expect(
|
|
48
|
+
ratio(field[key], page.background),
|
|
49
|
+
`${control} ${key} on page`,
|
|
50
|
+
).toBeGreaterThanOrEqual(minContrastRatio);
|
|
51
|
+
expect(
|
|
52
|
+
ratio(field[key], field.background),
|
|
53
|
+
`${control} ${key} on its fill`,
|
|
54
|
+
).toBeGreaterThanOrEqual(minContrastRatio);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it(`${scope}: the segmented frame edge holds 3:1 on the page`, () => {
|
|
60
|
+
const page = resolve(scope);
|
|
61
|
+
expect(ratio(page.borderColorControl, page.background)).toBeGreaterThanOrEqual(
|
|
62
|
+
minContrastRatio,
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it(`${scope}: the off Switch track edge`, () => {
|
|
67
|
+
const page = resolve(scope);
|
|
68
|
+
const track = resolve(`${scope}_Switch`);
|
|
69
|
+
if (scope.startsWith("light")) {
|
|
70
|
+
expect(ratio(track.switchTrackOffBorder, page.background)).toBeGreaterThanOrEqual(
|
|
71
|
+
minContrastRatio,
|
|
72
|
+
);
|
|
73
|
+
} else {
|
|
74
|
+
expect(track.switchTrackOffBorder).toBe(track.switchTrackOff);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const scope of intentScopes) {
|
|
80
|
+
it(`${scope}: an intent field edge comes from its own ramp and holds 3:1`, () => {
|
|
81
|
+
const scheme = scope.split("_")[0]!;
|
|
82
|
+
const own = Array.from({ length: 12 }, (_, index) => themes[scope]![`color${index + 1}`]);
|
|
83
|
+
const pages = hueScopes.filter((name) => name.startsWith(`${scheme}_`)).concat(scheme);
|
|
84
|
+
for (const control of ["Input", "TextArea", "SelectTrigger", "InputContainerFrame"]) {
|
|
85
|
+
const field = resolve(`${scope}_${control}`);
|
|
86
|
+
for (const key of ["borderColorField", "borderColorFieldHover"]) {
|
|
87
|
+
expect(own, `${control} ${key}`).toContain(field[key]);
|
|
88
|
+
for (const page of pages)
|
|
89
|
+
expect(
|
|
90
|
+
ratio(field[key], resolve(page).background),
|
|
91
|
+
`${control} ${key} on ${page}`,
|
|
92
|
+
).toBeGreaterThanOrEqual(minContrastRatio);
|
|
93
|
+
expect(
|
|
94
|
+
ratio(field[key], field.background),
|
|
95
|
+
`${control} ${key} on its fill`,
|
|
96
|
+
).toBeGreaterThanOrEqual(minContrastRatio);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
it("picks a neutral edge, not a tint that reads as the accent", () => {
|
|
103
|
+
for (const scope of hueScopes) {
|
|
104
|
+
const scheme = scope.split("_")[0]!;
|
|
105
|
+
const neutral = Array.from(
|
|
106
|
+
{ length: 12 },
|
|
107
|
+
(_, index) => themes[scheme]![`color${index + 1}`],
|
|
108
|
+
);
|
|
109
|
+
const page = resolve(scope);
|
|
110
|
+
expect(neutral, `${scope} field`).toContain(page.borderColorField);
|
|
111
|
+
expect(neutral, `${scope} control`).toContain(page.borderColorControl);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
*
|
|
5
5
|
* - `tinted`, in content: a filled wash with no edge, the preset's radius and
|
|
6
6
|
* the size token's height. Light washes at `$color3` (#ededed on the stock
|
|
7
|
-
* ramp), dark one step
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* ramp), dark at `$color5`, one step above the `$color4` card and field it
|
|
8
|
+
* usually sits in (LC-151, FZ-8400). On an elevated ground
|
|
9
|
+
* (`useElevatedSurface()`: an overlay frame or an unnested Panel) dark
|
|
10
|
+
* climbs one more step to `$color6`, because the dark elevated tint is
|
|
11
|
+
* `$color5` itself (HIG: a control's fill sits above the elevated
|
|
12
12
|
* background it is on). Light's elevated ground is the page step, so its
|
|
13
13
|
* wash stays put.
|
|
14
14
|
* - `plain`, inside a bar (`useBarSurface()`): ink only at rest, the tinted
|
|
@@ -30,11 +30,11 @@ export interface ControlTierPaint {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
const lightWash: ControlTierPaint = { rest: "$color3", hover: "$color4", press: "$color5" };
|
|
33
|
-
const darkWash: ControlTierPaint = { rest: "$
|
|
33
|
+
const darkWash: ControlTierPaint = { rest: "$color5", hover: "$color6", press: "$color7" };
|
|
34
34
|
const darkElevatedWash: ControlTierPaint = {
|
|
35
|
-
rest: "$
|
|
36
|
-
hover: "$
|
|
37
|
-
press: "$
|
|
35
|
+
rest: "$color6",
|
|
36
|
+
hover: "$color7",
|
|
37
|
+
press: "$color8",
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
/** The tinted wash for a scheme and ground: rest, hover and press fills. */
|
|
@@ -43,6 +43,15 @@ export function controlTierWash(dark: boolean, elevated = false): ControlTierPai
|
|
|
43
43
|
return elevated ? darkElevatedWash : darkWash;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* A Slider or Progress track: `$color5`, a step above a dark `$color4` card;
|
|
48
|
+
* on a dark elevated ground `$color6`, a step above the `$color5` overlay
|
|
49
|
+
* (FZ-7944, FZ-8400). Light keeps `$color5`.
|
|
50
|
+
*/
|
|
51
|
+
export function controlTrackFill(dark: boolean, elevated = false): string {
|
|
52
|
+
return dark && elevated ? "$color6" : "$color5";
|
|
53
|
+
}
|
|
54
|
+
|
|
46
55
|
/** Rest, hover and press fills for a tier in a scheme, on the page or an elevated ground. */
|
|
47
56
|
export function controlTierPaint(
|
|
48
57
|
tier: ControlTier,
|
|
@@ -36,6 +36,7 @@ import { createTamagui } from "tamagui";
|
|
|
36
36
|
import { boldPreset, densePreset, heroPreset, roomyPreset } from "./presets";
|
|
37
37
|
import { createThemesBuilder, type GetThemeProps } from "./createThemes";
|
|
38
38
|
import { defaultBaseTheme, defaultAccentTheme } from "./defaults";
|
|
39
|
+
import { darkHairlineContrast } from "./defaults/builderOptions";
|
|
39
40
|
import { setSizeRecipeInputs } from "./sizeRecipes";
|
|
40
41
|
import { resolveThemeThroughParents } from "./dedupeInherited";
|
|
41
42
|
|
|
@@ -157,8 +158,9 @@ describe("createDefaultThemeConfig — builderOptions.getTheme composition (DF-0
|
|
|
157
158
|
for (const scheme of ["light", "dark"] as const) {
|
|
158
159
|
const theme = themes[scheme];
|
|
159
160
|
expect(normalizeToHex(theme.outlineColor)).toBeTruthy();
|
|
160
|
-
// LC-152: textMuted is the secondary ink, $color9 in light,
|
|
161
|
-
|
|
161
|
+
// LC-152: textMuted is the secondary ink, $color9 in light and, on the
|
|
162
|
+
// dark $color4 card (FZ-8400), $color11 in dark.
|
|
163
|
+
expect(theme.textMuted).toBe(theme[scheme === "light" ? "color9" : "color11"]);
|
|
162
164
|
}
|
|
163
165
|
expect(config.recipeFamilies?.control.boxVariants.$4.height).toBe(40);
|
|
164
166
|
});
|
|
@@ -333,9 +335,10 @@ describe("the v5 theme matrix", () => {
|
|
|
333
335
|
const themes = config.tamagui.themes as unknown as RawThemes;
|
|
334
336
|
|
|
335
337
|
// LC-151 SURFACE-LADDER: the grouped step. Light lifts cards, list items
|
|
336
|
-
// and fields to white on the grey page; dark
|
|
337
|
-
//
|
|
338
|
-
// so it costs no CSS; tracks move to surface2 to stay off
|
|
338
|
+
// and fields to white on the grey page; dark drops the page to $color1 and
|
|
339
|
+
// lifts them to $color4, iOS's grouped step (FZ-8400). The grouped step is
|
|
340
|
+
// surface1 itself, so it costs no CSS; tracks move to surface2 to stay off
|
|
341
|
+
// a white card.
|
|
339
342
|
it("puts cards, list items and fields on the grouped step", () => {
|
|
340
343
|
for (const component of [
|
|
341
344
|
"Card",
|
|
@@ -349,7 +352,7 @@ describe("the v5 theme matrix", () => {
|
|
|
349
352
|
expect(themes[`light_${component}`].background, `light_${component}`).toBe(
|
|
350
353
|
themes.light.color1,
|
|
351
354
|
);
|
|
352
|
-
expect(themes[`dark_${component}`].background, `dark_${component}`).toBe(themes.dark.
|
|
355
|
+
expect(themes[`dark_${component}`].background, `dark_${component}`).toBe(themes.dark.color4);
|
|
353
356
|
expect(themes[`light_${component}`].backgroundPress, `light_${component} press`).toBe(
|
|
354
357
|
themes.light.color3,
|
|
355
358
|
);
|
|
@@ -364,6 +367,123 @@ describe("the v5 theme matrix", () => {
|
|
|
364
367
|
expect(themes.light_orange_Card.background).toBe(themes.light_orange.color1);
|
|
365
368
|
});
|
|
366
369
|
|
|
370
|
+
const edgeSurfaces = ["_Card", "_ListItem", "_Input", "_TextArea", "_SelectTrigger"];
|
|
371
|
+
|
|
372
|
+
// FZ-9281 / LC-148: iOS's dark opaqueSeparator #38383A over its grouped cell
|
|
373
|
+
// #1C1C1E is 1.454:1. Every dark ramp theme's hairline clears that against
|
|
374
|
+
// the page and against the grouped surface (a Card) it divides.
|
|
375
|
+
it("lifts every dark hairline off the page and the card by iOS's separator step", () => {
|
|
376
|
+
const iosSeparatorStep = contrastRatio(
|
|
377
|
+
relativeLuminance("#38383a"),
|
|
378
|
+
relativeLuminance("#1c1c1e"),
|
|
379
|
+
);
|
|
380
|
+
expect(darkHairlineContrast).toBeCloseTo(iosSeparatorStep, 2);
|
|
381
|
+
const hex = (value: unknown) =>
|
|
382
|
+
normalizeToHex(String(typeof value === "string" ? value : (value as { val?: string })?.val))!;
|
|
383
|
+
const contrastOf = (a: unknown, b: unknown) =>
|
|
384
|
+
contrastRatio(relativeLuminance(hex(a)), relativeLuminance(hex(b)));
|
|
385
|
+
const names = Object.keys(themes).filter((name) => /^dark(_[a-z]+)?$/.test(name));
|
|
386
|
+
expect(names.length).toBeGreaterThan(8);
|
|
387
|
+
const luminanceOf = (value: unknown) => relativeLuminance(hex(value));
|
|
388
|
+
for (const name of names) {
|
|
389
|
+
if (name === "dark_active" || name === "dark_accent") continue;
|
|
390
|
+
const page = resolveThemeThroughParents(themes, name);
|
|
391
|
+
for (const surface of ["", ...edgeSurfaces]) {
|
|
392
|
+
const theme = resolveThemeThroughParents(themes, `${name}${surface}`);
|
|
393
|
+
const label = `${name}${surface}`;
|
|
394
|
+
const grounds = surface ? [page.background, theme.background] : [page.background];
|
|
395
|
+
for (const ground of grounds) {
|
|
396
|
+
expect(
|
|
397
|
+
contrastOf(theme.borderColorHairline, ground),
|
|
398
|
+
`${label} hairline on ${ground === page.background ? "page" : "surface"}`,
|
|
399
|
+
).toBeGreaterThanOrEqual(iosSeparatorStep);
|
|
400
|
+
}
|
|
401
|
+
for (const ground of grounds) {
|
|
402
|
+
expect(
|
|
403
|
+
contrastOf(theme.borderColor, ground),
|
|
404
|
+
`${label} medium edge on ${ground === page.background ? "page" : "surface"}`,
|
|
405
|
+
).toBeGreaterThanOrEqual(iosSeparatorStep);
|
|
406
|
+
}
|
|
407
|
+
expect(luminanceOf(theme.borderColor), `${label} medium above hairline`).toBeGreaterThan(
|
|
408
|
+
luminanceOf(theme.borderColorHairline),
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it("keeps every light edge where it was (FZ-9281 is dark only)", () => {
|
|
415
|
+
const value = (raw: unknown) =>
|
|
416
|
+
String(typeof raw === "string" ? raw : (raw as { val?: string })?.val);
|
|
417
|
+
const edges = Object.keys(themes)
|
|
418
|
+
.filter((name) => /^light(_[a-z]+)?$/.test(name))
|
|
419
|
+
.sort()
|
|
420
|
+
.flatMap((name) =>
|
|
421
|
+
["", ...edgeSurfaces].map((surface) => {
|
|
422
|
+
const theme = resolveThemeThroughParents(themes, `${name}${surface}`);
|
|
423
|
+
return `${name}${surface} ${value(theme.borderColorHairline)} ${value(theme.borderColor)}`;
|
|
424
|
+
}),
|
|
425
|
+
);
|
|
426
|
+
expect(edges.join("\n")).toMatchSnapshot();
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// FZ-8400: iOS's grouped step is secondarySystemGroupedBackground #1C1C1E
|
|
430
|
+
// on systemGroupedBackground #000000, 1.23:1. Every preset's dark page and
|
|
431
|
+
// its inset group (a Card) clear it, the group's own small edge reads off
|
|
432
|
+
// its fill, and a Button and an overlay still step off the card.
|
|
433
|
+
it("steps dark grouped surfaces off the page by at least iOS's grouped step", () => {
|
|
434
|
+
const iosGroupedStep = contrastRatio(
|
|
435
|
+
relativeLuminance("#1c1c1e"),
|
|
436
|
+
relativeLuminance("#000000"),
|
|
437
|
+
);
|
|
438
|
+
const contrastOf = (a: string, b: string) =>
|
|
439
|
+
contrastRatio(relativeLuminance(normalizeToHex(a)!), relativeLuminance(normalizeToHex(b)!));
|
|
440
|
+
const read = (name: string) => resolveThemeThroughParents(themes, name);
|
|
441
|
+
// The neutral ramp, the built-in presets' hues and the app presets' hues.
|
|
442
|
+
const presetHues = [boldPreset, heroPreset, densePreset, roomyPreset].map((p) => p.theme);
|
|
443
|
+
for (const hue of new Set([
|
|
444
|
+
"",
|
|
445
|
+
...presetHues,
|
|
446
|
+
"blue",
|
|
447
|
+
"gray",
|
|
448
|
+
"purple",
|
|
449
|
+
"green",
|
|
450
|
+
"orange",
|
|
451
|
+
"pink",
|
|
452
|
+
])) {
|
|
453
|
+
const at = (suffix: string) => read(["dark", hue, suffix].filter(Boolean).join("_"));
|
|
454
|
+
const page = at("");
|
|
455
|
+
const card = at("Card");
|
|
456
|
+
const label = hue || "neutral";
|
|
457
|
+
expect(
|
|
458
|
+
contrastOf(card.background, page.background),
|
|
459
|
+
`${label} card on page`,
|
|
460
|
+
).toBeGreaterThanOrEqual(iosGroupedStep);
|
|
461
|
+
expect(
|
|
462
|
+
relativeLuminance(normalizeToHex(at("Button").background)!),
|
|
463
|
+
`${label} button over card`,
|
|
464
|
+
).toBeGreaterThan(relativeLuminance(normalizeToHex(card.background)!));
|
|
465
|
+
// LC-05 selection fills inside the group never land on its own fill,
|
|
466
|
+
// and on the page they stay the ramp steps they replaced.
|
|
467
|
+
for (const key of ["selectFill", "selectPress"]) {
|
|
468
|
+
expect(contrastOf(card[key], card.background), `${label} ${key} on card`).toBeGreaterThan(
|
|
469
|
+
1.1,
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
expect(page.selectFill, `${label} page selectFill`).toBe(page.color4);
|
|
473
|
+
expect(page.selectPress, `${label} page selectPress`).toBe(page.color5);
|
|
474
|
+
}
|
|
475
|
+
for (const scheme of ["light", "dark"] as const) {
|
|
476
|
+
for (const name of [scheme, `${scheme}_blue`, `${scheme}_gray`]) {
|
|
477
|
+
const page = themes[name];
|
|
478
|
+
expect([page.selectFill, page.selectPress], name).toEqual([page.color4, page.color5]);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
expect(read("light_Card").selectFill).toBe(themes.light.color4);
|
|
482
|
+
// Light keeps the grey page and the white grouped step.
|
|
483
|
+
expect(themes.light.background).toBe(themes.light.color2);
|
|
484
|
+
expect(themes.light_Card.background).toBe(themes.light.color1);
|
|
485
|
+
});
|
|
486
|
+
|
|
367
487
|
it("binds the v5 shadow3 alpha in both schemes", () => {
|
|
368
488
|
for (const [scheme, alphas] of [
|
|
369
489
|
["light", [0.04, 0.08, 0.12, 0.22, 0.33, 0.44]],
|
|
@@ -377,9 +497,9 @@ describe("the v5 theme matrix", () => {
|
|
|
377
497
|
});
|
|
378
498
|
|
|
379
499
|
it("uses v5 surface templates for pages and components", () => {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
500
|
+
expect(themes.light.background).toBe(themes.light.color2);
|
|
501
|
+
// FZ-8400: the dark page is the ramp's darkest step, iOS's black ground.
|
|
502
|
+
expect(themes.dark.background).toBe(themes.dark.color1);
|
|
383
503
|
const button = themes.light_Button;
|
|
384
504
|
expect(relativeLuminance(normalizeToHex(button.backgroundHover)!)).toBeGreaterThan(
|
|
385
505
|
relativeLuminance(normalizeToHex(button.background)!),
|
|
@@ -415,10 +535,13 @@ describe("the v5 theme matrix", () => {
|
|
|
415
535
|
builderOptions: { componentThemes: { Custom: { template: "surface1" } } },
|
|
416
536
|
}).tamagui.themes as unknown as RawThemes;
|
|
417
537
|
for (const scheme of ["light", "dark"] as const) {
|
|
418
|
-
|
|
419
|
-
|
|
538
|
+
// The page is $color2 in light and $color1 in dark (LC-151, FZ-8400).
|
|
539
|
+
expect(custom[`${scheme}_brand`].background).toBe(
|
|
540
|
+
custom[`${scheme}_brand`][scheme === "light" ? "color2" : "color1"],
|
|
541
|
+
);
|
|
542
|
+
// surface1 is the LC-151 grouped step: white in light, $color4 in dark.
|
|
420
543
|
expect(custom[`${scheme}_Custom`].background).toBe(
|
|
421
|
-
custom[scheme][scheme === "light" ? "color1" : "
|
|
544
|
+
custom[scheme][scheme === "light" ? "color1" : "color4"],
|
|
422
545
|
);
|
|
423
546
|
expect(custom).not.toHaveProperty(`${scheme}_Button`);
|
|
424
547
|
}
|