@multiplatform.one/theme 7.34.4 → 7.36.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,10 +1,12 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it } from "vitest";
|
|
2
2
|
import { defaultKnobs } from "./knobs";
|
|
3
|
-
import type { Knobs } from "./knobs";
|
|
4
3
|
import { resolveKnobs } from "./resolveKnobs";
|
|
4
|
+
import { getActiveScaleTokens, setActiveScaleTokens } from "./scale/activeScale";
|
|
5
|
+
import { generateScale } from "./scale/generateScale";
|
|
5
6
|
import {
|
|
6
7
|
DEFAULT_SIZE_RECIPE_INPUTS,
|
|
7
|
-
|
|
8
|
+
SIZE_RECIPE_CHIP_RATIOS,
|
|
9
|
+
SIZE_RECIPE_FAMILY_NAMES,
|
|
8
10
|
SIZE_RECIPE_HEIGHTS,
|
|
9
11
|
SIZE_RECIPE_HEIGHTS_TOUCH,
|
|
10
12
|
SIZE_RECIPE_RADII,
|
|
@@ -14,6 +16,7 @@ import {
|
|
|
14
16
|
generateSizeRecipeTables,
|
|
15
17
|
generateSizeRecipes,
|
|
16
18
|
getSizeRecipeFamily,
|
|
19
|
+
recipeKeyBelow,
|
|
17
20
|
recipeFamilies,
|
|
18
21
|
setSizeRecipeInputs,
|
|
19
22
|
sizeRecipeBoxVariants,
|
|
@@ -27,10 +30,6 @@ import {
|
|
|
27
30
|
type SizeRecipe,
|
|
28
31
|
} from "./sizeRecipes";
|
|
29
32
|
|
|
30
|
-
function knobsWith(overrides: Partial<Knobs>): Knobs {
|
|
31
|
-
return { ...defaultKnobs, ...overrides };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
33
|
const RECIPE_KEYS = ["$1", "$1.5", "$2", "$2.5", "$3", "$3.5", "$4", "$true", "$5", "$6"] as const;
|
|
35
34
|
|
|
36
35
|
function expectNoRadius(value: object) {
|
|
@@ -39,23 +38,23 @@ function expectNoRadius(value: object) {
|
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
describe("size recipes", () => {
|
|
42
|
-
it("medium / $4 matches the
|
|
41
|
+
it("medium / $4 matches the default 40/16/15/16/8 scale", () => {
|
|
43
42
|
const recipe = sizeRecipes.$4;
|
|
44
43
|
expect(recipe).toEqual({
|
|
45
|
-
height:
|
|
46
|
-
paddingHorizontal:
|
|
47
|
-
fontSize:
|
|
44
|
+
height: 40,
|
|
45
|
+
paddingHorizontal: 16,
|
|
46
|
+
fontSize: 15,
|
|
48
47
|
iconSize: 16,
|
|
49
|
-
gap:
|
|
48
|
+
gap: 8,
|
|
50
49
|
});
|
|
51
|
-
expect(sizeRecipeFromHeight(
|
|
50
|
+
expect(sizeRecipeFromHeight(40)).toEqual(recipe);
|
|
52
51
|
expect(sizeRecipeForToken("$4")).toEqual(recipe);
|
|
53
|
-
expect(SIZE_RECIPE_HEIGHTS.$4).toBe(
|
|
52
|
+
expect(SIZE_RECIPE_HEIGHTS.$4).toBe(40);
|
|
54
53
|
expect(SIZE_RECIPE_RATIOS).toEqual({
|
|
55
|
-
padX:
|
|
56
|
-
font:
|
|
57
|
-
icon: 16 /
|
|
58
|
-
gap:
|
|
54
|
+
padX: 16 / 40,
|
|
55
|
+
font: 15 / 40,
|
|
56
|
+
icon: 16 / 40,
|
|
57
|
+
gap: 8 / 40,
|
|
59
58
|
});
|
|
60
59
|
});
|
|
61
60
|
|
|
@@ -67,7 +66,7 @@ describe("size recipes", () => {
|
|
|
67
66
|
|
|
68
67
|
it("radius is not on the recipe or any variant object", () => {
|
|
69
68
|
expectNoRadius(sizeRecipes.$4);
|
|
70
|
-
expectNoRadius(sizeRecipeFromHeight(
|
|
69
|
+
expectNoRadius(sizeRecipeFromHeight(40));
|
|
71
70
|
expectNoRadius(sizeRecipeForToken("$4"));
|
|
72
71
|
for (const token of RECIPE_KEYS) {
|
|
73
72
|
expectNoRadius(sizeRecipes[token]);
|
|
@@ -91,7 +90,7 @@ describe("size recipes", () => {
|
|
|
91
90
|
height: recipe.iconSize,
|
|
92
91
|
});
|
|
93
92
|
}
|
|
94
|
-
expect(sizeRecipeFromHeight(
|
|
93
|
+
expect(sizeRecipeFromHeight(40)).toEqual(sizeRecipes.$4);
|
|
95
94
|
});
|
|
96
95
|
|
|
97
96
|
it("sizeRecipeForToken falls back to $4", () => {
|
|
@@ -99,9 +98,9 @@ describe("size recipes", () => {
|
|
|
99
98
|
expect(sizeRecipeForToken("")).toEqual(sizeRecipes.$4);
|
|
100
99
|
});
|
|
101
100
|
|
|
102
|
-
it("
|
|
103
|
-
const small = resolveKnobs(
|
|
104
|
-
const large = resolveKnobs(
|
|
101
|
+
it("$3 vs $5: height/padX/font/icon/gap all change; borderRadius stays identical", () => {
|
|
102
|
+
const small = resolveKnobs(defaultKnobs, undefined, "light", { sizeToken: "$3" });
|
|
103
|
+
const large = resolveKnobs(defaultKnobs, undefined, "light", { sizeToken: "$5" });
|
|
105
104
|
const smallRecipe = sizeRecipeForToken(small.knobProps.sizeToken);
|
|
106
105
|
const largeRecipe = sizeRecipeForToken(large.knobProps.sizeToken);
|
|
107
106
|
|
|
@@ -137,26 +136,44 @@ describe("size recipes", () => {
|
|
|
137
136
|
expect(small.knobProps.borderRadius).toEqual(large.knobProps.borderRadius);
|
|
138
137
|
});
|
|
139
138
|
|
|
140
|
-
it("layout gap
|
|
141
|
-
const { knobProps } = resolveKnobs(
|
|
142
|
-
expect(knobProps.sizeToken).toBe("$
|
|
143
|
-
expect(knobProps.gap.gap).toBe("$
|
|
139
|
+
it("the layout gap is a spacing role, not the recipe gap (LC-79)", () => {
|
|
140
|
+
const { knobProps } = resolveKnobs(defaultKnobs);
|
|
141
|
+
expect(knobProps.sizeToken).toBe("$true");
|
|
142
|
+
expect(knobProps.gap.gap).toBe("$3");
|
|
144
143
|
expect(knobProps.control.gap).toBe(sizeRecipes.$4.gap);
|
|
145
144
|
});
|
|
146
|
-
it("touch
|
|
145
|
+
it("touch floors only height at 44", () => {
|
|
147
146
|
const desktop = sizeRecipeForToken("$4");
|
|
148
147
|
expect(desktop).toEqual(sizeRecipes.$4);
|
|
149
|
-
expect(applyTouchFloor(desktop, false).height).toBe(
|
|
150
|
-
expect(applyTouchFloor(desktop, true)).toEqual(
|
|
151
|
-
expect(sizeRecipeForToken("$4", { touch: true })).toEqual(
|
|
148
|
+
expect(applyTouchFloor(desktop, false).height).toBe(40);
|
|
149
|
+
expect(applyTouchFloor(desktop, true)).toEqual({ ...desktop, height: 44 });
|
|
150
|
+
expect(sizeRecipeForToken("$4", { touch: true })).toEqual({ ...desktop, height: 44 });
|
|
152
151
|
expect(sizeRecipeForToken("$3", { touch: true }).height).toBe(44);
|
|
153
|
-
expect(sizeRecipeForToken("$5", { touch: true }).height).toBe(
|
|
154
|
-
expect(sizeRecipeForToken("$6", { touch: true }).height).toBe(
|
|
155
|
-
expect(sizeRecipeBoxVariants.$4.height).toBe(
|
|
152
|
+
expect(sizeRecipeForToken("$5", { touch: true }).height).toBe(48);
|
|
153
|
+
expect(sizeRecipeForToken("$6", { touch: true }).height).toBe(58);
|
|
154
|
+
expect(sizeRecipeBoxVariants.$4.height).toBe(40);
|
|
156
155
|
expectNoRadius(applyTouchFloor(desktop, true));
|
|
157
156
|
});
|
|
158
157
|
});
|
|
159
158
|
|
|
159
|
+
describe("touch preserves every other recipe channel", () => {
|
|
160
|
+
for (const family of SIZE_RECIPE_FAMILY_NAMES) {
|
|
161
|
+
it(`${family} keeps padding, text, icon and gap at every token`, () => {
|
|
162
|
+
for (const token of RECIPE_KEYS) {
|
|
163
|
+
const desktop = sizeRecipeForToken(token, { family });
|
|
164
|
+
const expected = { ...desktop, height: Math.max(desktop.height, 44) };
|
|
165
|
+
expect(sizeRecipeForToken(token, { family, touch: true })).toEqual(expected);
|
|
166
|
+
expect(applyTouchFloor(desktop, true, family)).toEqual(expected);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
it("preserves custom off-table channels in the fallback", () => {
|
|
171
|
+
const recipe = { height: 31, paddingHorizontal: 7, fontSize: 13, iconSize: 17, gap: 5 };
|
|
172
|
+
expect(applyTouchFloor(recipe, true)).toEqual({ ...recipe, height: 44 });
|
|
173
|
+
expect(applyTouchFloor({ ...recipe, height: 51 }, true)).toEqual({ ...recipe, height: 51 });
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
160
177
|
describe("SizeRecipe shape", () => {
|
|
161
178
|
it("has no radius field on the type-level contract", () => {
|
|
162
179
|
const recipe: SizeRecipe = sizeRecipes.$4;
|
|
@@ -170,30 +187,21 @@ describe("generateSizeRecipes (LC-70)", () => {
|
|
|
170
187
|
setSizeRecipeInputs();
|
|
171
188
|
});
|
|
172
189
|
|
|
173
|
-
it("DEFAULT $4 is the
|
|
190
|
+
it("DEFAULT $4 is the default 40/16/15/16/8 with no radius", () => {
|
|
174
191
|
const generated = generateSizeRecipes(DEFAULT_SIZE_RECIPE_INPUTS);
|
|
175
192
|
expect(generated.recipes.$4).toEqual({
|
|
176
|
-
height:
|
|
177
|
-
paddingHorizontal:
|
|
178
|
-
fontSize:
|
|
193
|
+
height: 40,
|
|
194
|
+
paddingHorizontal: 16,
|
|
195
|
+
fontSize: 15,
|
|
179
196
|
iconSize: 16,
|
|
180
|
-
gap:
|
|
197
|
+
gap: 8,
|
|
181
198
|
});
|
|
182
199
|
expectNoRadius(generated.recipes.$4);
|
|
183
200
|
expectNoRadius(generated.boxVariants.$4);
|
|
184
|
-
expect(generated.inputs.radiusPx).toBe(
|
|
185
|
-
expect(generated.heights).toEqual(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
$2: 28,
|
|
189
|
-
"$2.5": 32,
|
|
190
|
-
$3: 36,
|
|
191
|
-
"$3.5": 40,
|
|
192
|
-
$4: 44,
|
|
193
|
-
$true: 44,
|
|
194
|
-
$5: 52,
|
|
195
|
-
$6: 64,
|
|
196
|
-
});
|
|
201
|
+
expect(generated.inputs.radiusPx).toBe(8);
|
|
202
|
+
expect(generated.heights).toEqual(
|
|
203
|
+
Object.fromEntries(RECIPE_KEYS.map((token) => [token, generateScale().tokens.size[token]])),
|
|
204
|
+
);
|
|
197
205
|
expect(generated.heightsTouch).toEqual({
|
|
198
206
|
$1: 44,
|
|
199
207
|
"$1.5": 44,
|
|
@@ -201,39 +209,48 @@ describe("generateSizeRecipes (LC-70)", () => {
|
|
|
201
209
|
"$2.5": 44,
|
|
202
210
|
$3: 44,
|
|
203
211
|
"$3.5": 44,
|
|
204
|
-
$4:
|
|
205
|
-
$true:
|
|
206
|
-
$5:
|
|
207
|
-
$6:
|
|
212
|
+
$4: 44,
|
|
213
|
+
$true: 44,
|
|
214
|
+
$5: 48,
|
|
215
|
+
$6: 58,
|
|
208
216
|
});
|
|
209
217
|
expect(generateSizeRecipeTables(defaultRecipeInputs).touchHeights).toEqual(
|
|
210
218
|
generated.heightsTouch,
|
|
211
219
|
);
|
|
212
|
-
expect(SIZE_RECIPE_HEIGHTS_TOUCH.$4).toBe(
|
|
220
|
+
expect(SIZE_RECIPE_HEIGHTS_TOUCH.$4).toBe(44);
|
|
213
221
|
});
|
|
214
222
|
|
|
215
|
-
it("
|
|
216
|
-
const generated = generateSizeRecipes({
|
|
217
|
-
|
|
223
|
+
it("control: 40 regenerates $4 from the scale and keeps $5 distinct", () => {
|
|
224
|
+
const generated = generateSizeRecipes({ control: 40 });
|
|
225
|
+
const { size, space, radius } = generateScale({ control: 40, type: 15 }).tokens;
|
|
226
|
+
expect(generated.inputs.control).toBe(40);
|
|
218
227
|
expect(generated.heights.$4).toBe(40);
|
|
228
|
+
expect(generated.recipes.$4).toEqual({
|
|
229
|
+
height: size.$4,
|
|
230
|
+
paddingHorizontal: space.$4,
|
|
231
|
+
fontSize: 15,
|
|
232
|
+
iconSize: 16,
|
|
233
|
+
gap: radius[4],
|
|
234
|
+
});
|
|
219
235
|
expect(generated.recipes.$4).toEqual({
|
|
220
236
|
height: 40,
|
|
221
|
-
paddingHorizontal:
|
|
222
|
-
fontSize:
|
|
223
|
-
iconSize:
|
|
224
|
-
gap:
|
|
237
|
+
paddingHorizontal: 16,
|
|
238
|
+
fontSize: 15,
|
|
239
|
+
iconSize: 16,
|
|
240
|
+
gap: 8,
|
|
225
241
|
});
|
|
242
|
+
expect(generated.heights.$5).toBe(size.$5);
|
|
226
243
|
expect(generated.heights.$5).not.toBe(generated.heights.$4);
|
|
227
244
|
expectNoRadius(generated.recipes.$4);
|
|
228
245
|
});
|
|
229
246
|
|
|
230
|
-
it("
|
|
247
|
+
it("chip padX is tighter than control at the same $4 height", () => {
|
|
231
248
|
const generated = generateSizeRecipes(DEFAULT_SIZE_RECIPE_INPUTS);
|
|
232
|
-
expect(generated.families.control.recipes.$4.height).toBe(
|
|
233
|
-
expect(generated.families.
|
|
234
|
-
expect(generated.families.control.recipes.$4.paddingHorizontal).toBe(
|
|
235
|
-
expect(generated.families.
|
|
236
|
-
expect(generated.families.
|
|
249
|
+
expect(generated.families.control.recipes.$4.height).toBe(40);
|
|
250
|
+
expect(generated.families.chip.recipes.$4.height).toBe(40);
|
|
251
|
+
expect(generated.families.control.recipes.$4.paddingHorizontal).toBe(16);
|
|
252
|
+
expect(generated.families.chip.recipes.$4.paddingHorizontal).toBe(14);
|
|
253
|
+
expect(generated.families.chip.recipes.$4.paddingHorizontal).toBeLessThan(
|
|
237
254
|
generated.families.control.recipes.$4.paddingHorizontal,
|
|
238
255
|
);
|
|
239
256
|
});
|
|
@@ -244,11 +261,11 @@ describe("generateSizeRecipes (LC-70)", () => {
|
|
|
244
261
|
expectNoRadius(generated.recipes.$4);
|
|
245
262
|
expectNoRadius(generated.recipesTouch.$4);
|
|
246
263
|
expectNoRadius(generated.boxVariants.$4);
|
|
247
|
-
expectNoRadius(generated.families.
|
|
264
|
+
expectNoRadius(generated.families.chip.recipes.$4);
|
|
248
265
|
});
|
|
249
266
|
|
|
250
|
-
it("setSizeRecipeInputs({
|
|
251
|
-
setSizeRecipeInputs({
|
|
267
|
+
it("setSizeRecipeInputs({ control }) regenerates live tables from the scale", () => {
|
|
268
|
+
setSizeRecipeInputs({ control: 36 });
|
|
252
269
|
expect(SIZE_RECIPE_HEIGHTS.$4).toBe(36);
|
|
253
270
|
expect(sizeRecipes.$4.height).toBe(36);
|
|
254
271
|
expect(sizeRecipeForToken("$4").height).toBe(36);
|
|
@@ -270,15 +287,40 @@ describe("generateSizeRecipes (LC-70)", () => {
|
|
|
270
287
|
expect(sizeRecipeEscape("keep")).toBe("keep");
|
|
271
288
|
});
|
|
272
289
|
|
|
273
|
-
it("textFieldInset
|
|
274
|
-
expect(textFieldInset(sizeRecipes.$4)).toBe(
|
|
275
|
-
expect(textFieldInset({ height:
|
|
290
|
+
it("textFieldInset snaps min(padX, floor((height-fontSize)/2)) down to a $space value — $4 → 12", () => {
|
|
291
|
+
expect(textFieldInset(sizeRecipes.$4)).toBe(12);
|
|
292
|
+
expect(textFieldInset({ height: 40, paddingHorizontal: 16, fontSize: 15 })).toBe(12);
|
|
276
293
|
expect(textFieldInset({ height: 32, paddingHorizontal: 12, fontSize: 14 })).toBe(9);
|
|
277
|
-
expect(textFieldInset({ height: 48, paddingHorizontal: 8, fontSize: 14 })).toBe(
|
|
294
|
+
expect(textFieldInset({ height: 48, paddingHorizontal: 8, fontSize: 14 })).toBe(6);
|
|
278
295
|
expect(textFieldInset(sizeRecipes.$4)).toBeLessThanOrEqual(
|
|
279
296
|
Math.floor((sizeRecipes.$4.height - sizeRecipes.$4.fontSize) / 2),
|
|
280
297
|
);
|
|
281
298
|
});
|
|
299
|
+
|
|
300
|
+
it("textFieldInset is a $space value of the active scale on every preset", () => {
|
|
301
|
+
const installed = getActiveScaleTokens();
|
|
302
|
+
for (const inputs of [
|
|
303
|
+
{ control: 36, type: 14 },
|
|
304
|
+
{ control: 40, type: 15 },
|
|
305
|
+
{ control: 48, type: 16 },
|
|
306
|
+
]) {
|
|
307
|
+
const scale = generateScale(inputs);
|
|
308
|
+
setActiveScaleTokens(scale.tokens);
|
|
309
|
+
try {
|
|
310
|
+
const row = scale.recipeInputs.tokenTable.$4;
|
|
311
|
+
const inset = textFieldInset({
|
|
312
|
+
height: scale.tokens.size.$4,
|
|
313
|
+
paddingHorizontal: row?.paddingHorizontal ?? 0,
|
|
314
|
+
fontSize: row?.fontSize ?? 0,
|
|
315
|
+
});
|
|
316
|
+
expect(Object.values(scale.tokens.space), `${inputs.control}/${inputs.type}`).toContain(
|
|
317
|
+
inset,
|
|
318
|
+
);
|
|
319
|
+
} finally {
|
|
320
|
+
setActiveScaleTokens(installed);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
});
|
|
282
324
|
});
|
|
283
325
|
|
|
284
326
|
describe("size recipe families", () => {
|
|
@@ -290,22 +332,74 @@ describe("size recipe families", () => {
|
|
|
290
332
|
expect(control.heightVariants[token]).toEqual({ height: SIZE_RECIPE_HEIGHTS[token] });
|
|
291
333
|
}
|
|
292
334
|
expect(control.boxVariants.$4).toEqual({
|
|
293
|
-
height:
|
|
294
|
-
paddingHorizontal:
|
|
295
|
-
gap:
|
|
335
|
+
height: 40,
|
|
336
|
+
paddingHorizontal: 16,
|
|
337
|
+
gap: 8,
|
|
296
338
|
});
|
|
297
339
|
});
|
|
298
340
|
|
|
299
|
-
it("
|
|
300
|
-
const compact = getSizeRecipeFamily("
|
|
341
|
+
it("chip keeps height and tightens padX", () => {
|
|
342
|
+
const compact = getSizeRecipeFamily("chip");
|
|
301
343
|
const control = getSizeRecipeFamily("control");
|
|
302
|
-
expect(compact.ratios).toEqual(
|
|
344
|
+
expect(compact.ratios).toEqual(SIZE_RECIPE_CHIP_RATIOS);
|
|
303
345
|
expect(compact.boxVariants.$4.height).toBe(control.boxVariants.$4.height);
|
|
304
346
|
expect(compact.boxVariants.$4.paddingHorizontal).toBeLessThan(
|
|
305
347
|
control.boxVariants.$4.paddingHorizontal,
|
|
306
348
|
);
|
|
307
|
-
expect(compact.boxVariants.$4.paddingHorizontal).toBe(
|
|
308
|
-
expect(compact.boxVariants.$4.gap).toBe(
|
|
309
|
-
expect(sizeRecipeForToken("$4", { family: "
|
|
349
|
+
expect(compact.boxVariants.$4.paddingHorizontal).toBe(14);
|
|
350
|
+
expect(compact.boxVariants.$4.gap).toBe(8);
|
|
351
|
+
expect(sizeRecipeForToken("$4", { family: "chip" })).toEqual(compact.recipes.$4);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("names every generated family", () => {
|
|
355
|
+
expect(SIZE_RECIPE_FAMILY_NAMES).toEqual(["control", "chip", "menuRow", "tab"]);
|
|
356
|
+
expect(Object.keys(generateSizeRecipes().families).sort()).toEqual(
|
|
357
|
+
[...SIZE_RECIPE_FAMILY_NAMES].sort(),
|
|
358
|
+
);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it("chip reads input padX and type one key below at the control's heights", () => {
|
|
362
|
+
const chip = getSizeRecipeFamily("chip");
|
|
363
|
+
for (const token of RECIPE_KEYS) {
|
|
364
|
+
expect(chip.heights[token]).toBe(SIZE_RECIPE_HEIGHTS[token]);
|
|
365
|
+
expect(chip.recipes[token].paddingHorizontal).toBe(
|
|
366
|
+
DEFAULT_SIZE_RECIPE_INPUTS.inputPadX[token],
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("menuRow is one key below the control token, padX $3, the token's own type", () => {
|
|
372
|
+
const menuRow = getSizeRecipeFamily("menuRow");
|
|
373
|
+
expect(menuRow.recipes.$4).toMatchObject({ height: 36, paddingHorizontal: 12, fontSize: 15 });
|
|
374
|
+
expect(menuRow.recipes.$true).toEqual(menuRow.recipes.$4);
|
|
375
|
+
expect(sizeRecipeForToken("$4", { family: "menuRow", touch: true }).height).toBe(44);
|
|
376
|
+
for (const token of RECIPE_KEYS) {
|
|
377
|
+
expect(menuRow.heights[token]).toBe(SIZE_RECIPE_HEIGHTS[recipeKeyBelow(token)]);
|
|
378
|
+
expect(menuRow.recipes[token].paddingHorizontal).toBe(sizeRecipes.$3.paddingHorizontal);
|
|
379
|
+
expect(menuRow.recipes[token].fontSize).toBe(sizeRecipes[token].fontSize);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("tab takes height and padX one key below and the token's own type", () => {
|
|
384
|
+
const tab = getSizeRecipeFamily("tab");
|
|
385
|
+
expect(tab.recipes.$4).toMatchObject({ height: 36, fontSize: 15 });
|
|
386
|
+
for (const token of RECIPE_KEYS) {
|
|
387
|
+
const below = sizeRecipes[recipeKeyBelow(token)];
|
|
388
|
+
expect(tab.heights[token]).toBe(below.height);
|
|
389
|
+
expect(tab.recipes[token].paddingHorizontal).toBe(below.paddingHorizontal);
|
|
390
|
+
expect(tab.recipes[token].fontSize).toBe(sizeRecipes[token].fontSize);
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it("row families follow a regenerated scale", () => {
|
|
395
|
+
try {
|
|
396
|
+
setSizeRecipeInputs({ control: 44 });
|
|
397
|
+
expect(getSizeRecipeFamily("menuRow").heights.$4).toBe(
|
|
398
|
+
getSizeRecipeFamily("control").heights["$3.5"],
|
|
399
|
+
);
|
|
400
|
+
} finally {
|
|
401
|
+
setSizeRecipeInputs();
|
|
402
|
+
}
|
|
403
|
+
expect(getSizeRecipeFamily("menuRow").heights.$4).toBe(36);
|
|
310
404
|
});
|
|
311
405
|
});
|
package/src/theme/sizeRecipes.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Coordinated control-size recipes (LC-75 SIZE-RECIPE, LC-80 RECIPE-INPUTS).
|
|
3
3
|
*
|
|
4
|
-
* The default ladder is the
|
|
4
|
+
* The default ladder is read off the generated token scale in recipeInputs
|
|
5
5
|
* (padX / font / icon / radius per size token). Default gap equals the
|
|
6
6
|
* size-table radius; corner shape overrides leave that spacing intact.
|
|
7
|
-
* A linear ratio is only the `{ baseHeight }` fallback — not a
|
|
8
|
-
* documented divergence (MPO-46 / R6).
|
|
9
7
|
*
|
|
10
8
|
* Tables are GENERATED from `defaultRecipeInputs` at module eval (Tamagui
|
|
11
9
|
* 2.0.0-rc.41 flattens `styled(View, { variants: { size: tables } })` when
|
|
@@ -15,13 +13,18 @@
|
|
|
15
13
|
* reserve it for non-enumerable values only. Do NOT put recipe tables on a
|
|
16
14
|
* `createStyledContext` primitive (`neverFlatten`).
|
|
17
15
|
*
|
|
18
|
-
* Reference md ($4 / $true): height
|
|
19
|
-
* gap
|
|
16
|
+
* Reference md ($4 / $true): height 40, padX 16, font 15, icon 16, radius 8,
|
|
17
|
+
* gap 8. The v5 44px control is generated with `v5ScaleInputs`.
|
|
20
18
|
* `sizeToken` remains the recipe KEY (string "$4") so styled() variants
|
|
21
19
|
* can switch; it is not a height scalar.
|
|
22
20
|
*
|
|
23
|
-
* Families
|
|
24
|
-
* `
|
|
21
|
+
* Families (`SIZE_RECIPE_FAMILY_NAMES`), all generated from the scale:
|
|
22
|
+
* - `control` reads `inputs.tokenTable`.
|
|
23
|
+
* - `chip` reads `chipTokenTable` at the SAME heights (Input padX, type one
|
|
24
|
+
* key below).
|
|
25
|
+
* - `menuRow` (menu / listbox / command rows): height one key below the
|
|
26
|
+
* control token (36 at default), padX `$3`, the token's own type.
|
|
27
|
+
* - `tab`: height and padX one key below, the token's own type.
|
|
25
28
|
*
|
|
26
29
|
* On touch (LC-77), `applyTouchFloor` / `sizeRecipeForToken(token, { touch: true })`
|
|
27
30
|
* lift painted height to ≥ 44. Static `sizeRecipe*Variants` stay on the
|
|
@@ -34,9 +37,6 @@ export {
|
|
|
34
37
|
SIZE_RECIPE_RADIUS_STOPS,
|
|
35
38
|
SIZE_RECIPE_TOKENS,
|
|
36
39
|
SIZE_TOKEN_EXPONENTS,
|
|
37
|
-
TAMAGUI_COMPACT_SIZE_TABLE,
|
|
38
|
-
TAMAGUI_CONTROL_SIZE_TABLE,
|
|
39
|
-
TAMAGUI_INPUT_PADX,
|
|
40
40
|
TOUCH_HEIGHT_FLOOR,
|
|
41
41
|
applyRadiusStop,
|
|
42
42
|
SIZE_RECIPE_RADIUS_TOKEN_TO_STOP,
|
|
@@ -44,7 +44,8 @@ export {
|
|
|
44
44
|
resolveControlRadius,
|
|
45
45
|
createRecipeFamilies,
|
|
46
46
|
defaultRecipeInputs,
|
|
47
|
-
|
|
47
|
+
recipeKeyAbove,
|
|
48
|
+
recipeKeyBelow,
|
|
48
49
|
generateSizeRecipeTables,
|
|
49
50
|
generateSizeRecipes,
|
|
50
51
|
mergeRecipeInputs,
|
|
@@ -71,6 +72,7 @@ export type {
|
|
|
71
72
|
SizeRecipeTypeVariant,
|
|
72
73
|
} from "./recipeInputs";
|
|
73
74
|
|
|
75
|
+
import { getActiveScaleTokens } from "./scale/activeScale";
|
|
74
76
|
import {
|
|
75
77
|
cloneSizeRecipeInputs,
|
|
76
78
|
defaultRecipeInputs,
|
|
@@ -123,9 +125,9 @@ export const SIZE_RECIPE_HEIGHTS = recipeFamilies.control.heights;
|
|
|
123
125
|
export const SIZE_RECIPE_HEIGHTS_TOUCH = recipeFamilies.control.touchHeights;
|
|
124
126
|
export const SIZE_RECIPE_RADII = recipeFamilies.control.radii;
|
|
125
127
|
export const SIZE_RECIPE_RATIOS = recipeFamilies.control.ratios;
|
|
126
|
-
export const
|
|
128
|
+
export const SIZE_RECIPE_CHIP_RATIOS = recipeFamilies.chip.ratios;
|
|
127
129
|
|
|
128
|
-
export const SIZE_RECIPE_FAMILY_NAMES = ["control", "
|
|
130
|
+
export const SIZE_RECIPE_FAMILY_NAMES = ["control", "chip", "menuRow", "tab"] as const;
|
|
129
131
|
|
|
130
132
|
/**
|
|
131
133
|
* Live tile-size ladder (MPO-31) — generated from `inputs.tile` over the
|
|
@@ -139,8 +141,8 @@ export function getTileSizeLadder(): typeof tileSizeLadder {
|
|
|
139
141
|
}
|
|
140
142
|
|
|
141
143
|
function adoptGenerated(next: GeneratedSizeRecipes): GeneratedSizeRecipes {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
+
for (const name of SIZE_RECIPE_FAMILY_NAMES)
|
|
145
|
+
adoptFamily(recipeFamilies[name], next.families[name]);
|
|
144
146
|
replaceRecord(
|
|
145
147
|
tileSizeLadder as unknown as Record<string, unknown>,
|
|
146
148
|
next.tiles as unknown as Record<string, unknown>,
|
|
@@ -197,11 +199,22 @@ export function applyTouchFloor(
|
|
|
197
199
|
): SizeRecipe {
|
|
198
200
|
if (!touch) return recipe;
|
|
199
201
|
const tables = resolveFamily(family);
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
);
|
|
202
|
+
// Shifted families repeat a height at the floor ($1 / $1.5), so match the
|
|
203
|
+
// whole recipe first and the height only for an off-table recipe.
|
|
204
|
+
const keys = Object.keys(tables.heights) as SizeRecipeToken[];
|
|
205
|
+
const token =
|
|
206
|
+
keys.find((key) => {
|
|
207
|
+
const row = tables.recipes[key];
|
|
208
|
+
return (
|
|
209
|
+
row.height === recipe.height &&
|
|
210
|
+
row.paddingHorizontal === recipe.paddingHorizontal &&
|
|
211
|
+
row.fontSize === recipe.fontSize &&
|
|
212
|
+
row.iconSize === recipe.iconSize &&
|
|
213
|
+
row.gap === recipe.gap
|
|
214
|
+
);
|
|
215
|
+
}) ?? keys.find((key) => tables.heights[key] === recipe.height);
|
|
203
216
|
if (token && tables.recipesTouch[token]) return tables.recipesTouch[token];
|
|
204
|
-
return
|
|
217
|
+
return { ...recipe, height: Math.max(recipe.height, tables.inputs.touchFloor) };
|
|
205
218
|
}
|
|
206
219
|
|
|
207
220
|
export function sizeRecipeForToken(
|
|
@@ -232,16 +245,26 @@ export function sizeRecipeEscape<T>(value: T, _reason?: string): T {
|
|
|
232
245
|
}
|
|
233
246
|
|
|
234
247
|
/**
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
248
|
+
* TEXT-INSET — optical vertical inset of a text field, never larger than the
|
|
249
|
+
* recipe padX, snapped down to the largest `$space` value of the active
|
|
250
|
+
* scale at or under it so the inset is a token on every preset (LC-100).
|
|
251
|
+
* Extra horizontal space is legal only when an icon well occupies that side,
|
|
252
|
+
* not as empty-field padX.
|
|
238
253
|
*
|
|
239
|
-
* min(paddingHorizontal, floor((height − fontSize) / 2))
|
|
254
|
+
* snap(min(paddingHorizontal, floor((height − fontSize) / 2)))
|
|
240
255
|
*
|
|
241
|
-
* `$4` (
|
|
256
|
+
* `$4` (40 / 16 / 15) → 12 (`$3`); roomy 48 / 21 / 16 → 15; dense 36 / 13 / 14 → 9.
|
|
242
257
|
*/
|
|
243
258
|
export function textFieldInset(
|
|
244
259
|
recipe: Pick<SizeRecipe, "height" | "paddingHorizontal" | "fontSize">,
|
|
245
260
|
): number {
|
|
246
|
-
|
|
261
|
+
const optical = Math.min(
|
|
262
|
+
recipe.paddingHorizontal,
|
|
263
|
+
Math.floor((recipe.height - recipe.fontSize) / 2),
|
|
264
|
+
);
|
|
265
|
+
let inset = 0;
|
|
266
|
+
for (const [key, px] of Object.entries(getActiveScaleTokens().space)) {
|
|
267
|
+
if (!key.startsWith("-") && px <= optical && px > inset) inset = px;
|
|
268
|
+
}
|
|
269
|
+
return inset;
|
|
247
270
|
}
|
|
@@ -13,7 +13,7 @@ vi.mock("tamagui", () => ({
|
|
|
13
13
|
vi.mock("./animations/index", () => ({ animations: {} }));
|
|
14
14
|
|
|
15
15
|
import { createDefaultThemeConfig } from "./createDefaultThemeConfig";
|
|
16
|
-
import { createThemesBuilder } from "./createThemes";
|
|
16
|
+
import { createThemesBuilder, houseComponentThemes } from "./createThemes";
|
|
17
17
|
import { defaultAccentTheme } from "./defaults/accent";
|
|
18
18
|
import { defaultBaseTheme } from "./defaults/base";
|
|
19
19
|
import { defaultBuilderOptions } from "./defaults/builderOptions";
|
|
@@ -79,16 +79,22 @@ describe("subsetThemes over the house matrix", () => {
|
|
|
79
79
|
components: ["Button"],
|
|
80
80
|
tints: ["accent", "active", "alt1", "alt2", "error", "success", "warning"],
|
|
81
81
|
});
|
|
82
|
-
expect(Object.keys(
|
|
82
|
+
expect(Object.keys(built)).toHaveLength(462);
|
|
83
|
+
expect(Object.keys(kept)).toHaveLength(44);
|
|
83
84
|
for (const name of ["light", "dark", "light_accent", "light_Button", "dark_error_Button"]) {
|
|
84
85
|
expect(kept).toHaveProperty(name);
|
|
85
86
|
}
|
|
86
|
-
for (const name of ["light_blue", "dark_red", "
|
|
87
|
+
for (const name of ["light_blue", "dark_red", "light_Slider", "light_accent_Input"]) {
|
|
87
88
|
expect(built).toHaveProperty(name);
|
|
88
89
|
expect(kept).not.toHaveProperty(name);
|
|
89
90
|
}
|
|
90
91
|
});
|
|
91
92
|
|
|
93
|
+
it("the shipped component list matches every v5 house surface", async () => {
|
|
94
|
+
const { themeSubset } = await import("../../../../packages/themes/subset");
|
|
95
|
+
expect(themeSubset.components).toEqual(Object.keys(houseComponentThemes).sort());
|
|
96
|
+
});
|
|
97
|
+
|
|
92
98
|
it("is what createDefaultThemeConfig({ subset }) ships, and omitting it ships everything", () => {
|
|
93
99
|
const full = createDefaultThemeConfig().tamagui.themes as Record<string, unknown>;
|
|
94
100
|
const subset = createDefaultThemeConfig({ subset: narrow }).tamagui.themes as Record<
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { configWithoutAnimations } from "@tamagui/config";
|
|
2
|
+
import { createTamagui } from "tamagui";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { contrastRatio, minContrastRatio, normalizeToHex, relativeLuminance } from "./colorRules";
|
|
5
|
+
import { createThemesBuilder } from "./createThemes";
|
|
6
|
+
import { defaultAccentTheme } from "./defaults/accent";
|
|
7
|
+
import { defaultBaseTheme } from "./defaults/base";
|
|
8
|
+
import { defaultBuilderOptions } from "./defaults/builderOptions";
|
|
9
|
+
import { defaultKnobs } from "./knobs";
|
|
10
|
+
import { resolveKnobs } from "./resolveKnobs";
|
|
11
|
+
|
|
12
|
+
const themes = createThemesBuilder(
|
|
13
|
+
defaultBaseTheme,
|
|
14
|
+
defaultAccentTheme,
|
|
15
|
+
defaultBuilderOptions,
|
|
16
|
+
).themes() as Record<string, Record<string, string>>;
|
|
17
|
+
const config = createTamagui({ ...configWithoutAnimations, themes });
|
|
18
|
+
|
|
19
|
+
const tintedSchemes = Object.keys(themes).filter(
|
|
20
|
+
(name) => /^(light|dark)(_[a-z]+)?$/.test(name) && !/_(active|accent)$/.test(name),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
function ratio(a: string, b: string) {
|
|
24
|
+
const aHex = normalizeToHex(a);
|
|
25
|
+
const bHex = normalizeToHex(b);
|
|
26
|
+
if (!aHex || !bHex) throw new Error(`Expected opaque colours: ${a}, ${b}`);
|
|
27
|
+
return contrastRatio(relativeLuminance(aHex), relativeLuminance(bHex));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
describe("switch thumb", () => {
|
|
31
|
+
it.each(["light", "dark"])("%s keeps a white thumb through its Switch child", (scheme) => {
|
|
32
|
+
expect(themes[scheme].switchThumb).toBe("#ffffff");
|
|
33
|
+
expect(Object.hasOwn(themes[`${scheme}_Switch`], "switchThumb")).toBe(false);
|
|
34
|
+
expect(config.themes[`${scheme}_Switch`].switchThumb?.val).toBe("#ffffff");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("resolves the web thumb shadow through the knob recipe", () => {
|
|
38
|
+
const { thumbShadow } = resolveKnobs(defaultKnobs).knobProps;
|
|
39
|
+
expect(thumbShadow.style?.boxShadow).toEqual(expect.any(String));
|
|
40
|
+
expect(thumbShadow.style?.boxShadow).not.toBe("");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it.each(tintedSchemes)(
|
|
44
|
+
"%s: the off track holds 3:1 under the thumb and stays apart from the on track",
|
|
45
|
+
(name) => {
|
|
46
|
+
const theme = config.themes[name] as Record<string, { val: string }>;
|
|
47
|
+
const scheme = name.split("_")[0] as "light" | "dark";
|
|
48
|
+
const off = theme.switchTrackOff.val;
|
|
49
|
+
expect(off).toBe(theme[scheme === "light" ? "gray10" : "gray7"].val);
|
|
50
|
+
expect(theme.switchTrackOffHover.val).toBe(theme[scheme === "light" ? "gray9" : "gray6"].val);
|
|
51
|
+
expect(theme.switchTrackOffPress.val).toBe(
|
|
52
|
+
theme[scheme === "light" ? "gray11" : "gray8"].val,
|
|
53
|
+
);
|
|
54
|
+
expect(ratio(off, theme.switchThumb.val)).toBeGreaterThanOrEqual(minContrastRatio);
|
|
55
|
+
expect(ratio(off, theme.accentBackground.val)).toBeGreaterThanOrEqual(1.5);
|
|
56
|
+
if (scheme === "light") {
|
|
57
|
+
for (const against of [theme.background.val, theme.color3.val]) {
|
|
58
|
+
expect(ratio(off, against)).toBeGreaterThanOrEqual(minContrastRatio);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
it.each(["light", "dark"])("%s_Switch inherits its scheme's off track", (scheme) => {
|
|
65
|
+
expect(config.themes[`${scheme}_Switch`].switchTrackOff?.val).toBe(
|
|
66
|
+
config.themes[scheme].switchTrackOff?.val,
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
});
|