@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
|
@@ -4,9 +4,6 @@ import {
|
|
|
4
4
|
defaultKnobs,
|
|
5
5
|
emptyStateKnobs,
|
|
6
6
|
FillStyle,
|
|
7
|
-
Space,
|
|
8
|
-
Size,
|
|
9
|
-
Density,
|
|
10
7
|
CornerSmoothing,
|
|
11
8
|
FieldLabelPlacement,
|
|
12
9
|
RequiredMarking,
|
|
@@ -22,12 +19,21 @@ import type { KnobProps } from "./recipes";
|
|
|
22
19
|
import { cornerSmoothClassName } from "./cornerSmoothing";
|
|
23
20
|
import { defaultPreset, boldPreset, heroPreset } from "./presets";
|
|
24
21
|
import {
|
|
25
|
-
applyDensity,
|
|
26
22
|
capContainerRadius,
|
|
23
|
+
capFieldRadius,
|
|
27
24
|
containerCapProps,
|
|
25
|
+
labelFontSizeFor,
|
|
26
|
+
nestedControlPx,
|
|
27
|
+
paintedCardFill,
|
|
28
|
+
panelPaddingPx,
|
|
28
29
|
resolveKnobs,
|
|
29
30
|
resolvePageTitleScale,
|
|
31
|
+
spacingRoles,
|
|
32
|
+
sizedElevation,
|
|
30
33
|
} from "./resolveKnobs";
|
|
34
|
+
import { getActiveScaleTokens, setActiveScaleTokens } from "./scale/activeScale";
|
|
35
|
+
import { generateScale } from "./scale/generateScale";
|
|
36
|
+
import { defaultScaleInputs } from "./scale/scaleInputs";
|
|
31
37
|
import {
|
|
32
38
|
normalizeToHex,
|
|
33
39
|
relativeLuminance,
|
|
@@ -40,14 +46,29 @@ function knobsWith(overrides: Partial<Knobs>): Knobs {
|
|
|
40
46
|
return { ...defaultKnobs, ...overrides };
|
|
41
47
|
}
|
|
42
48
|
|
|
49
|
+
/** Resolve with another scale installed, then put the default scale back. */
|
|
50
|
+
function onScale<T>(control: number, type: number, read: () => T): T {
|
|
51
|
+
const previous = getActiveScaleTokens();
|
|
52
|
+
setActiveScaleTokens(generateScale({ control, type }).tokens);
|
|
53
|
+
try {
|
|
54
|
+
return read();
|
|
55
|
+
} finally {
|
|
56
|
+
setActiveScaleTokens(previous);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const defaultScaleTokens = generateScale(defaultScaleInputs).tokens;
|
|
61
|
+
|
|
43
62
|
const expectedKnobPropKeys: (keyof KnobProps)[] = [
|
|
44
63
|
"borderRadius",
|
|
45
64
|
"borderRadiusNested",
|
|
46
65
|
"borderRadiusOuter",
|
|
47
66
|
"containerRadius",
|
|
67
|
+
"fieldRadius",
|
|
48
68
|
"elevation",
|
|
49
69
|
"overlayElevation",
|
|
50
70
|
"elevationChrome",
|
|
71
|
+
"thumbShadow",
|
|
51
72
|
"scheme",
|
|
52
73
|
"surface",
|
|
53
74
|
"cardSurface",
|
|
@@ -55,6 +76,7 @@ const expectedKnobPropKeys: (keyof KnobProps)[] = [
|
|
|
55
76
|
"featureSurface",
|
|
56
77
|
"inputSurface",
|
|
57
78
|
"panelPadding",
|
|
79
|
+
"gapSm",
|
|
58
80
|
"gap",
|
|
59
81
|
"gapLg",
|
|
60
82
|
"sizeToken",
|
|
@@ -63,9 +85,16 @@ const expectedKnobPropKeys: (keyof KnobProps)[] = [
|
|
|
63
85
|
"control",
|
|
64
86
|
"controlType",
|
|
65
87
|
"controlIcon",
|
|
88
|
+
"menuRow",
|
|
89
|
+
"menuRowType",
|
|
90
|
+
"tab",
|
|
91
|
+
"tabType",
|
|
92
|
+
"tabHitSlop",
|
|
93
|
+
"bar",
|
|
66
94
|
"heading",
|
|
67
95
|
"pageTitle",
|
|
68
96
|
"body",
|
|
97
|
+
"typography",
|
|
69
98
|
"textWeight",
|
|
70
99
|
"transition",
|
|
71
100
|
"outlined",
|
|
@@ -73,9 +102,6 @@ const expectedKnobPropKeys: (keyof KnobProps)[] = [
|
|
|
73
102
|
"textAccent",
|
|
74
103
|
"textAccentColor",
|
|
75
104
|
"inputBackground",
|
|
76
|
-
"space",
|
|
77
|
-
"size",
|
|
78
|
-
"density",
|
|
79
105
|
"fieldLabelPlacement",
|
|
80
106
|
"requiredMarking",
|
|
81
107
|
"tableZebra",
|
|
@@ -160,14 +186,49 @@ describe("resolveKnobs", () => {
|
|
|
160
186
|
expect(a).toEqual(b);
|
|
161
187
|
});
|
|
162
188
|
|
|
189
|
+
it("menuRow, tab and bar fragments read the recipe families and the scale", () => {
|
|
190
|
+
const desktop = resolveKnobs(defaultKnobs, undefined, "light", { touch: false }).knobProps;
|
|
191
|
+
expect(desktop.menuRow).toEqual({ minHeight: 36, paddingHorizontal: 12, gap: 8 });
|
|
192
|
+
expect(desktop.menuRowType).toEqual({ fontSize: 15 });
|
|
193
|
+
expect(desktop.tab.height).toBe(36);
|
|
194
|
+
expect(desktop.tabType).toEqual({ fontSize: 15 });
|
|
195
|
+
expect(desktop.tabHitSlop).toEqual({ top: 4, bottom: 4, left: 4, right: 4 });
|
|
196
|
+
expect(desktop.bar).toEqual({
|
|
197
|
+
frame: { paddingVertical: "$2", paddingHorizontal: "$4", minHeight: 52 },
|
|
198
|
+
sizeToken: "$3",
|
|
199
|
+
});
|
|
200
|
+
const touch = resolveKnobs(defaultKnobs, undefined, "light", { touch: true }).knobProps;
|
|
201
|
+
expect(touch.menuRow.minHeight).toBe(44);
|
|
202
|
+
expect(touch.tab.height).toBe(44);
|
|
203
|
+
expect(touch.tabHitSlop).toEqual({ top: 0, bottom: 0, left: 0, right: 0 });
|
|
204
|
+
});
|
|
205
|
+
|
|
163
206
|
it("matches snapshot for defaultKnobs", () => {
|
|
164
207
|
const resolved = resolveKnobs(defaultKnobs);
|
|
165
208
|
expect(resolved).toMatchSnapshot();
|
|
166
209
|
});
|
|
167
210
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
211
|
+
it("knobProps carries no size, space or density channel (LC-100)", () => {
|
|
212
|
+
const { knobProps } = resolveKnobs(defaultKnobs);
|
|
213
|
+
for (const retired of ["size", "space", "density"]) {
|
|
214
|
+
expect(expectedKnobPropKeys).not.toContain(retired);
|
|
215
|
+
expect(knobProps).not.toHaveProperty(retired);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("Knobs has no size, space or density member (LC-100)", () => {
|
|
220
|
+
// @ts-expect-error size is not a knob: a use site passes Tamagui's own size token
|
|
221
|
+
const size: Knobs["size"] = undefined;
|
|
222
|
+
// @ts-expect-error space is not a knob: spacing is the scale's token values
|
|
223
|
+
const space: Knobs["space"] = undefined;
|
|
224
|
+
// @ts-expect-error density is not a knob: a preset carries its own scale
|
|
225
|
+
const density: Knobs["density"] = undefined;
|
|
226
|
+
expect([size, space, density]).toEqual([undefined, undefined, undefined]);
|
|
227
|
+
for (const retired of ["size", "space", "density"]) {
|
|
228
|
+
expect(defaultKnobs).not.toHaveProperty(retired);
|
|
229
|
+
expect(defaultPreset.knobs).not.toHaveProperty(retired);
|
|
230
|
+
expect(boldPreset.knobs).not.toHaveProperty(retired);
|
|
231
|
+
}
|
|
171
232
|
});
|
|
172
233
|
|
|
173
234
|
// ── borderRadius ──────────────────────────────────────────
|
|
@@ -220,75 +281,83 @@ describe("resolveKnobs", () => {
|
|
|
220
281
|
|
|
221
282
|
// ── elevation ──────────────────────────────────────────────
|
|
222
283
|
|
|
223
|
-
describe("
|
|
224
|
-
it(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
expect(
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it("overlay elevation at small is $2, not the control $1 token", () => {
|
|
235
|
-
const { knobProps } = resolveKnobs(knobsWith({ elevation: "small" }));
|
|
236
|
-
expect(knobProps.overlayElevation).toBe("$2");
|
|
237
|
-
expect(knobProps.elevatedSurface.elevation).toBe("$2");
|
|
238
|
-
expect(knobProps.elevatedSurface.elevation).not.toBe(knobProps.elevation);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
it('maps "medium" → "$2"', () => {
|
|
242
|
-
expect(resolveKnobs(knobsWith({ elevation: "medium" })).knobProps.elevation).toBe("$2");
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it('maps "large" → "$4"', () => {
|
|
246
|
-
expect(resolveKnobs(knobsWith({ elevation: "large" })).knobProps.elevation).toBe("$4");
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it("light scheme keeps overlay fill at $background and paints a black shadow", () => {
|
|
250
|
-
const { knobProps } = resolveKnobs(knobsWith({ elevation: "large" }), undefined, "light");
|
|
251
|
-
expect(knobProps.scheme).toBe("light");
|
|
252
|
-
expect(knobProps.elevatedSurface.backgroundColor).toBe("$background");
|
|
253
|
-
expect(knobProps.cardSurface.backgroundColor).toBe("$background");
|
|
254
|
-
expect(knobProps.inputBackground).toBe("$background");
|
|
255
|
-
expect(knobProps.elevatedSurface.shadowColor).toBe("#000");
|
|
256
|
-
expect(knobProps.elevatedSurface.shadowOpacity).toBe(0.12);
|
|
257
|
-
expect(knobProps.elevationChrome.elevation).toBe(8);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
it("dark scheme tints overlay/card/input up the existing ramp (LC-85)", () => {
|
|
261
|
-
const none = resolveKnobs(knobsWith({ elevation: "none" }), undefined, "dark");
|
|
262
|
-
const small = resolveKnobs(knobsWith({ elevation: "small" }), undefined, "dark");
|
|
263
|
-
const large = resolveKnobs(knobsWith({ elevation: "large" }), undefined, "dark");
|
|
264
|
-
expect(none.knobProps.elevatedSurface.backgroundColor).toBe("$background");
|
|
265
|
-
expect(small.knobProps.elevatedSurface.backgroundColor).toBe("$color2");
|
|
266
|
-
expect(large.knobProps.elevatedSurface.backgroundColor).toBe("$color4");
|
|
267
|
-
expect(large.knobProps.cardSurface.backgroundColor).toBe("$color4");
|
|
268
|
-
expect(large.knobProps.inputBackground).toBe("$color5");
|
|
269
|
-
expect(large.knobProps.elevatedSurface.shadowColor).toBe("$color12");
|
|
270
|
-
expect(large.knobProps.elevatedSurface.shadowOpacity).toBe(0.12);
|
|
271
|
-
expect(none.knobProps.elevationChrome).toEqual({});
|
|
284
|
+
describe("sizedElevation", () => {
|
|
285
|
+
it.each([
|
|
286
|
+
[26, 8, 15, 16],
|
|
287
|
+
[28, 8, 16, 16],
|
|
288
|
+
[36, 10, 20, 20],
|
|
289
|
+
[44, 12, 24, 24],
|
|
290
|
+
[52, 14, 28, 28],
|
|
291
|
+
])("%i px uses the Tamagui formula", (px, offsetY, radius, android) => {
|
|
292
|
+
expect(sizedElevation(px)).toEqual({ offsetY, radius, android });
|
|
272
293
|
});
|
|
294
|
+
});
|
|
273
295
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
undefined,
|
|
278
|
-
"
|
|
296
|
+
describe("elevation knob", () => {
|
|
297
|
+
for (const scheme of ["light", "dark"] as const) {
|
|
298
|
+
for (const [stop, key] of [
|
|
299
|
+
["none", undefined],
|
|
300
|
+
["small", undefined],
|
|
301
|
+
["medium", "$2"],
|
|
302
|
+
["large", "$4"],
|
|
303
|
+
] as const) {
|
|
304
|
+
it(`${scheme} ${stop} paints the active size formula`, () => {
|
|
305
|
+
const { knobProps } = resolveKnobs(knobsWith({ elevation: stop }), undefined, scheme);
|
|
306
|
+
expect(knobProps.elevation).toBe(key);
|
|
307
|
+
expect(knobProps.cardSurface.backgroundColor).toBe("$background");
|
|
308
|
+
expect(knobProps.elevatedSurface.backgroundColor).toBe(
|
|
309
|
+
scheme === "dark" ? "$color4" : "$background",
|
|
310
|
+
);
|
|
311
|
+
expect(knobProps.elevatedSurface.borderColor).toBe(
|
|
312
|
+
scheme === "dark" ? "$color6" : "$borderColor",
|
|
313
|
+
);
|
|
314
|
+
if (!key) {
|
|
315
|
+
expect(knobProps.elevationChrome).toEqual({});
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const { offsetY, radius, android } = sizedElevation(getActiveScaleTokens().size[key]);
|
|
319
|
+
expect(knobProps.elevationChrome).toEqual({
|
|
320
|
+
shadowColor: "$shadowColor",
|
|
321
|
+
shadowOffset: { width: 0, height: offsetY },
|
|
322
|
+
shadowRadius: radius,
|
|
323
|
+
shadowOpacity: 1,
|
|
324
|
+
elevation: android,
|
|
325
|
+
style: { boxShadow: `0px ${offsetY}px ${radius}px var(--shadowColor)` },
|
|
326
|
+
});
|
|
327
|
+
expect(knobProps.elevationChrome.shadowColor).not.toBe("$color12");
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
it("dark elevated fill is one palette step above the Card surface", async () => {
|
|
332
|
+
const { createDefaultThemeConfig } = await import("./createDefaultThemeConfig");
|
|
333
|
+
const themes = createDefaultThemeConfig().tamagui.themes;
|
|
334
|
+
const { knobProps } = resolveKnobs(defaultKnobs, undefined, "dark");
|
|
335
|
+
const palette = Array.from(
|
|
336
|
+
{ length: 12 },
|
|
337
|
+
(_, index) => themes.dark[`color${index + 1}`].val,
|
|
279
338
|
);
|
|
280
|
-
expect(knobProps.inputBackground).toBe("transparent");
|
|
281
339
|
expect(knobProps.elevatedSurface.backgroundColor).toBe("$color4");
|
|
340
|
+
const cardIndex = palette.indexOf(themes.dark_Card.background.val);
|
|
341
|
+
const overlayIndex = palette.indexOf(themes.dark.color4.val);
|
|
342
|
+
expect(cardIndex).toBeGreaterThanOrEqual(0);
|
|
343
|
+
expect(overlayIndex - cardIndex).toBe(1);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it("recomputes paint when the active scale changes", () => {
|
|
347
|
+
const before = resolveKnobs(knobsWith({ elevation: "large" })).knobProps;
|
|
348
|
+
onScale(48, 16, () => {
|
|
349
|
+
const after = resolveKnobs(knobsWith({ elevation: "large" })).knobProps;
|
|
350
|
+
const { offsetY, radius } = sizedElevation(getActiveScaleTokens().size.$4);
|
|
351
|
+
expect(after.elevationChrome.style?.boxShadow).toBe(
|
|
352
|
+
`0px ${offsetY}px ${radius}px var(--shadowColor)`,
|
|
353
|
+
);
|
|
354
|
+
expect(after.elevationChrome).not.toEqual(before.elevationChrome);
|
|
355
|
+
});
|
|
282
356
|
});
|
|
283
357
|
});
|
|
284
358
|
|
|
285
|
-
// ── elevation on NATIVE (SB-N-04) ──────────────────────────
|
|
286
|
-
// Android's RCTView `elevation` prop is a Double; a token string reaching a
|
|
287
|
-
// plain RN View kills the ReactHost. Native must emit the numeric
|
|
288
|
-
// tokens.size values Tamagui would resolve those tokens to.
|
|
289
|
-
|
|
290
359
|
describe("elevation knob (native)", () => {
|
|
291
|
-
it("
|
|
360
|
+
it("returns active scale numbers with the same paint in both schemes", async () => {
|
|
292
361
|
vi.resetModules();
|
|
293
362
|
vi.doMock("@multiplatform.one/platform", () => ({
|
|
294
363
|
isWeb: false,
|
|
@@ -296,23 +365,57 @@ describe("resolveKnobs", () => {
|
|
|
296
365
|
isWebTouchable: false,
|
|
297
366
|
}));
|
|
298
367
|
const { resolveKnobs: resolveNative } = await import("./resolveKnobs");
|
|
368
|
+
const active = await import("./scale/activeScale");
|
|
369
|
+
const previous = active.getActiveScaleTokens();
|
|
299
370
|
try {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
371
|
+
for (const control of [40, 48]) {
|
|
372
|
+
active.setActiveScaleTokens(generateScale({ control }).tokens);
|
|
373
|
+
for (const scheme of ["light", "dark"] as const) {
|
|
374
|
+
for (const [stop, key, overlay] of [
|
|
375
|
+
["none", undefined, undefined],
|
|
376
|
+
["small", undefined, "$4"],
|
|
377
|
+
["medium", "$2", "$4.5"],
|
|
378
|
+
["large", "$4", "$5"],
|
|
379
|
+
] as const) {
|
|
380
|
+
const { knobProps } = resolveNative(
|
|
381
|
+
knobsWith({ elevation: stop }),
|
|
382
|
+
undefined,
|
|
383
|
+
scheme,
|
|
384
|
+
);
|
|
385
|
+
expect(knobProps.elevation).toBe(
|
|
386
|
+
key ? active.getActiveScaleTokens().size[key] : undefined,
|
|
387
|
+
);
|
|
388
|
+
expect(knobProps.overlayElevation).toBe(
|
|
389
|
+
overlay ? active.getActiveScaleTokens().size[overlay] : undefined,
|
|
390
|
+
);
|
|
391
|
+
expect(knobProps.elevatedSurface.elevation).toBe(knobProps.overlayElevation);
|
|
392
|
+
const state = resolveNative(
|
|
393
|
+
knobsWith({ hover: { elevation: stop } }),
|
|
394
|
+
undefined,
|
|
395
|
+
scheme,
|
|
396
|
+
);
|
|
397
|
+
expect(state.elevation.hoverKnobProps?.elevation).toBe(
|
|
398
|
+
key ? active.getActiveScaleTokens().size[key] : undefined,
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
expect(knobProps.elevationChrome.style).toBeUndefined();
|
|
402
|
+
if (key) {
|
|
403
|
+
const metrics = sizedElevation(active.getActiveScaleTokens().size[key]);
|
|
404
|
+
expect(knobProps.elevationChrome).toEqual({
|
|
405
|
+
shadowColor: "$shadowColor",
|
|
406
|
+
shadowOpacity: 1,
|
|
407
|
+
shadowOffset: { width: 0, height: metrics.offsetY },
|
|
408
|
+
shadowRadius: metrics.radius,
|
|
409
|
+
elevation: metrics.android,
|
|
410
|
+
});
|
|
411
|
+
} else expect(knobProps.elevationChrome).toEqual({});
|
|
412
|
+
expect(knobProps.thumbShadow.style).toBeUndefined();
|
|
413
|
+
expect(knobProps.thumbShadow.shadowColor).toBe("$shadowColor");
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
315
417
|
} finally {
|
|
418
|
+
active.setActiveScaleTokens(previous);
|
|
316
419
|
vi.doUnmock("@multiplatform.one/platform");
|
|
317
420
|
vi.resetModules();
|
|
318
421
|
}
|
|
@@ -320,48 +423,54 @@ describe("resolveKnobs", () => {
|
|
|
320
423
|
});
|
|
321
424
|
|
|
322
425
|
describe("overlay elevation ladder (MPO-99)", () => {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
for (const stop of ["medium", "large"] as const) {
|
|
340
|
-
const knobProps = paint(stop, scheme);
|
|
341
|
-
expect(knobProps.elevatedSurface.style?.boxShadow).toBeTruthy();
|
|
342
|
-
expect(knobProps.elevationChrome.style?.boxShadow).toBeTruthy();
|
|
426
|
+
for (const scheme of ["light", "dark"] as const) {
|
|
427
|
+
for (const [stop, key] of [
|
|
428
|
+
["small", "$4"],
|
|
429
|
+
["medium", "$4.5"],
|
|
430
|
+
["large", "$5"],
|
|
431
|
+
] as const) {
|
|
432
|
+
it(`${scheme} ${stop} uses its own overlay stop`, () => {
|
|
433
|
+
const { knobProps } = resolveKnobs(knobsWith({ elevation: stop }), undefined, scheme);
|
|
434
|
+
const { offsetY, radius } = sizedElevation(getActiveScaleTokens().size[key]);
|
|
435
|
+
expect(knobProps.overlayElevation).toBe(key);
|
|
436
|
+
expect(knobProps.elevatedSurface.shadowColor).toBe("$shadowColor");
|
|
437
|
+
expect(knobProps.elevatedSurface.shadowColor).not.toBe("$color12");
|
|
438
|
+
expect(knobProps.elevatedSurface.shadowOpacity).toBe(1);
|
|
439
|
+
expect(knobProps.elevatedSurface.style?.boxShadow).toBe(
|
|
440
|
+
`0px ${offsetY}px ${radius}px var(--shadowColor)`,
|
|
441
|
+
);
|
|
343
442
|
expect(knobProps.elevatedSurface.style?.boxShadow).not.toBe(
|
|
344
443
|
knobProps.elevationChrome.style?.boxShadow,
|
|
345
444
|
);
|
|
346
|
-
}
|
|
445
|
+
});
|
|
347
446
|
}
|
|
447
|
+
}
|
|
448
|
+
it("none has no overlay paint", () => {
|
|
449
|
+
const { knobProps } = resolveKnobs(knobsWith({ elevation: "none" }));
|
|
450
|
+
expect(knobProps.overlayElevation).toBeUndefined();
|
|
451
|
+
expect(knobProps.elevatedSurface.style).toBeUndefined();
|
|
452
|
+
expect(knobProps.elevatedSurface.shadowColor).toBeUndefined();
|
|
348
453
|
});
|
|
454
|
+
});
|
|
349
455
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
456
|
+
describe("thumbShadow", () => {
|
|
457
|
+
it("paints the half-size stop at every elevation stop and follows the active scale", () => {
|
|
458
|
+
for (const control of [40, 48])
|
|
459
|
+
onScale(control, 16, () => {
|
|
460
|
+
for (const elevation of ["none", "small", "medium", "large"] as const) {
|
|
461
|
+
const { offsetY, radius, android } = sizedElevation(
|
|
462
|
+
getActiveScaleTokens().size["$0.5"],
|
|
463
|
+
);
|
|
464
|
+
expect(resolveKnobs(knobsWith({ elevation })).knobProps.thumbShadow).toEqual({
|
|
465
|
+
shadowColor: "$shadowColor",
|
|
466
|
+
shadowOffset: { width: 0, height: offsetY },
|
|
467
|
+
shadowOpacity: 1,
|
|
468
|
+
shadowRadius: radius,
|
|
469
|
+
elevation: android,
|
|
470
|
+
style: { boxShadow: `0px ${offsetY}px ${radius}px var(--shadowColor)` },
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
});
|
|
365
474
|
});
|
|
366
475
|
});
|
|
367
476
|
|
|
@@ -411,113 +520,143 @@ describe("resolveKnobs", () => {
|
|
|
411
520
|
});
|
|
412
521
|
});
|
|
413
522
|
|
|
414
|
-
// ──
|
|
523
|
+
// ── spacing roles (SP-GAP, A9) ─────────────────────────────
|
|
415
524
|
|
|
416
|
-
describe("
|
|
417
|
-
it(
|
|
418
|
-
const { knobProps } = resolveKnobs(
|
|
419
|
-
expect(knobProps.
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
// T2.1c — space "large" still maps gapLg → "$6" regardless of size
|
|
445
|
-
it('space "large" maps gapLg → "$6" regardless of size', () => {
|
|
446
|
-
const { knobProps } = resolveKnobs(knobsWith({ space: "large", size: "small" }));
|
|
447
|
-
expect(knobProps.gapLg.gap).toBe("$6");
|
|
448
|
-
});
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
// ── size ──────────────────────────────────────────────────
|
|
452
|
-
|
|
453
|
-
describe("size knob", () => {
|
|
454
|
-
// T1.1b — size "large" maps sizeToken → "$5"
|
|
455
|
-
it('maps sizeToken "large" → "$5"', () => {
|
|
456
|
-
const { knobProps } = resolveKnobs(knobsWith({ size: "large" }));
|
|
457
|
-
expect(knobProps.sizeToken).toBe("$5");
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
// T1.1c — size "small" maps sizeToken → "$3"
|
|
461
|
-
it('maps sizeToken "small" → "$3"', () => {
|
|
462
|
-
const { knobProps } = resolveKnobs(knobsWith({ size: "small" }));
|
|
463
|
-
expect(knobProps.sizeToken).toBe("$3");
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
// T1.1e — knobProps.size passthrough
|
|
467
|
-
it("passes size value through to knobProps", () => {
|
|
468
|
-
for (const val of Object.values(Size)) {
|
|
469
|
-
const { knobProps } = resolveKnobs(knobsWith({ size: val }));
|
|
470
|
-
expect(knobProps.size).toBe(val);
|
|
525
|
+
describe("spacing roles (SP-GAP)", () => {
|
|
526
|
+
it("defaults: gapSm $2, gap $3, gapLg $5, panelPadding $4, sizeToken $true", () => {
|
|
527
|
+
const { knobProps } = resolveKnobs(defaultKnobs);
|
|
528
|
+
expect(knobProps.gapSm).toEqual({ gap: "$2" });
|
|
529
|
+
expect(knobProps.gap).toEqual({ gap: "$3" });
|
|
530
|
+
expect(knobProps.gapLg).toEqual({ gap: "$5" });
|
|
531
|
+
expect(knobProps.panelPadding).toEqual({ padding: "$4" });
|
|
532
|
+
expect(knobProps.sizeToken).toBe("$true");
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it("the roles are the same keys under every knob value: no knob moves them", () => {
|
|
536
|
+
const variants: Partial<Knobs>[] = [
|
|
537
|
+
{ borderRadius: "full" },
|
|
538
|
+
{ borderRadius: "none" },
|
|
539
|
+
{ fillStyle: "outlined" },
|
|
540
|
+
{ elevation: "large" },
|
|
541
|
+
{ fontWeight: "bold" },
|
|
542
|
+
{ textAccent: "low" },
|
|
543
|
+
{ hover: { borderWidth: "large" } },
|
|
544
|
+
];
|
|
545
|
+
for (const variant of variants) {
|
|
546
|
+
const { knobProps } = resolveKnobs(knobsWith(variant));
|
|
547
|
+
expect(knobProps.gapSm.gap, JSON.stringify(variant)).toBe(spacingRoles.gapSm);
|
|
548
|
+
expect(knobProps.gap.gap, JSON.stringify(variant)).toBe(spacingRoles.gap);
|
|
549
|
+
expect(knobProps.gapLg.gap, JSON.stringify(variant)).toBe(spacingRoles.gapLg);
|
|
550
|
+
expect(knobProps.panelPadding.padding, JSON.stringify(variant)).toBe(
|
|
551
|
+
spacingRoles.panelPadding,
|
|
552
|
+
);
|
|
471
553
|
}
|
|
472
554
|
});
|
|
473
555
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
expect(
|
|
478
|
-
expect(
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
it("
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
556
|
+
it("tight < within-group < inset < between-groups on the default scale (6 / 12 / 16 / 21)", () => {
|
|
557
|
+
const px = (key: string) => defaultScaleTokens.space[key as "$2"];
|
|
558
|
+
expect(px(spacingRoles.gapSm)).toBe(6);
|
|
559
|
+
expect(px(spacingRoles.gap)).toBe(12);
|
|
560
|
+
expect(px(spacingRoles.panelPadding)).toBe(16);
|
|
561
|
+
expect(px(spacingRoles.gapLg)).toBe(21);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
it("a preset scale changes the roles' px, never their keys", () => {
|
|
565
|
+
for (const [control, type] of [
|
|
566
|
+
[36, 14],
|
|
567
|
+
[48, 16],
|
|
568
|
+
] as const) {
|
|
569
|
+
const { knobProps, padding } = onScale(control, type, () => ({
|
|
570
|
+
knobProps: resolveKnobs(defaultKnobs).knobProps,
|
|
571
|
+
padding: panelPaddingPx(),
|
|
572
|
+
}));
|
|
573
|
+
const scale = generateScale({ control, type }).tokens;
|
|
574
|
+
expect(knobProps.gap).toEqual({ gap: "$3" });
|
|
575
|
+
expect(knobProps.panelPadding).toEqual({ padding: "$4" });
|
|
576
|
+
expect(padding, `${control}/${type}`).toBe(scale.space.$4);
|
|
577
|
+
expect(padding, `${control}/${type}`).not.toBe(defaultScaleTokens.space.$4);
|
|
489
578
|
}
|
|
490
579
|
});
|
|
491
580
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
expect(resolved.knobProps.sizeToken).toBe("$4");
|
|
496
|
-
expect(resolved.knobProps.gap.gap).toBe("$4");
|
|
497
|
-
expect(resolved.knobProps.panelPadding.padding).toBe("$4");
|
|
498
|
-
expect(resolved.knobProps.gapLg.gap).toBe("$5");
|
|
581
|
+
it("state knobs carry no padding: space is not a state channel", () => {
|
|
582
|
+
const { control } = resolveKnobs(knobsWith({ hover: { borderWidth: "large" } }));
|
|
583
|
+
expect(control.hoverKnobProps).not.toHaveProperty("padding");
|
|
499
584
|
});
|
|
585
|
+
});
|
|
500
586
|
|
|
501
|
-
|
|
587
|
+
// ── size token (environment.sizeToken) ────────────────────
|
|
588
|
+
|
|
589
|
+
describe("size token", () => {
|
|
590
|
+
it("fills control / controlType / controlIcon from the $true recipe by default", () => {
|
|
502
591
|
const { knobProps } = resolveKnobs(defaultKnobs);
|
|
503
592
|
expect(knobProps.control).toEqual({
|
|
504
|
-
height:
|
|
505
|
-
paddingHorizontal:
|
|
506
|
-
gap:
|
|
593
|
+
height: 40,
|
|
594
|
+
paddingHorizontal: 16,
|
|
595
|
+
gap: 8,
|
|
507
596
|
});
|
|
508
|
-
expect(knobProps.controlType).toEqual({ fontSize:
|
|
597
|
+
expect(knobProps.controlType).toEqual({ fontSize: 15 });
|
|
509
598
|
expect(knobProps.controlIcon).toEqual({ width: 16, height: 16 });
|
|
510
599
|
expect(knobProps.control).not.toHaveProperty("borderRadius");
|
|
511
600
|
expect(knobProps.controlType).not.toHaveProperty("borderRadius");
|
|
512
601
|
expect(knobProps.controlIcon).not.toHaveProperty("borderRadius");
|
|
513
602
|
});
|
|
514
603
|
|
|
515
|
-
it("
|
|
516
|
-
const small = resolveKnobs(
|
|
517
|
-
const large = resolveKnobs(
|
|
518
|
-
expect(small.knobProps.
|
|
519
|
-
expect(
|
|
520
|
-
expect(
|
|
604
|
+
it("environment.sizeToken picks the recipe at that $size key", () => {
|
|
605
|
+
const small = resolveKnobs(defaultKnobs, undefined, "light", { sizeToken: "$3" });
|
|
606
|
+
const large = resolveKnobs(defaultKnobs, undefined, "light", { sizeToken: "$5" });
|
|
607
|
+
expect(small.knobProps.sizeToken).toBe("$3");
|
|
608
|
+
expect(small.knobProps.control.height).toBe(defaultScaleTokens.size.$3);
|
|
609
|
+
expect(large.knobProps.sizeToken).toBe("$5");
|
|
610
|
+
expect(large.knobProps.control.height).toBe(defaultScaleTokens.size.$5);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
it("a size token is not a $size key falls back to $true", () => {
|
|
614
|
+
expect(
|
|
615
|
+
resolveKnobs(defaultKnobs, undefined, "light", { sizeToken: "40" }).knobProps.sizeToken,
|
|
616
|
+
).toBe("$true");
|
|
617
|
+
expect(
|
|
618
|
+
resolveKnobs(defaultKnobs, undefined, "light", { sizeToken: "$medium" }).knobProps.control
|
|
619
|
+
.height,
|
|
620
|
+
).toBe(40);
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
it("the size token never moves the spacing roles or the radius fragment", () => {
|
|
624
|
+
const baseline = resolveKnobs(defaultKnobs).knobProps;
|
|
625
|
+
for (const sizeToken of ["$2", "$3", "$4", "$5", "$6"]) {
|
|
626
|
+
const { knobProps } = resolveKnobs(defaultKnobs, undefined, "light", { sizeToken });
|
|
627
|
+
expect(knobProps.gapSm, sizeToken).toEqual(baseline.gapSm);
|
|
628
|
+
expect(knobProps.gap, sizeToken).toEqual(baseline.gap);
|
|
629
|
+
expect(knobProps.gapLg, sizeToken).toEqual(baseline.gapLg);
|
|
630
|
+
expect(knobProps.panelPadding, sizeToken).toEqual(baseline.panelPadding);
|
|
631
|
+
expect(knobProps.borderRadius, sizeToken).toEqual(baseline.borderRadius);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it("label type is the body key at the size key, rounding half steps up", () => {
|
|
636
|
+
expect(labelFontSizeFor("$true")).toBe("$true");
|
|
637
|
+
expect(labelFontSizeFor("$4")).toBe("$4");
|
|
638
|
+
expect(labelFontSizeFor("$3")).toBe("$3");
|
|
639
|
+
expect(labelFontSizeFor("$3.5")).toBe("$4");
|
|
640
|
+
expect(labelFontSizeFor("$5")).toBe("$5");
|
|
641
|
+
expect(labelFontSizeFor("$1")).toBe("$1");
|
|
642
|
+
const { knobProps } = resolveKnobs(defaultKnobs, undefined, "light", { sizeToken: "$3" });
|
|
643
|
+
expect(knobProps.label.fontSize).toBe("$3");
|
|
644
|
+
expect(resolveKnobs(defaultKnobs).knobProps.label.fontSize).toBe(
|
|
645
|
+
resolveKnobs(defaultKnobs).knobProps.sizeToken,
|
|
646
|
+
);
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
it("nestedControl is 32/44 of the size token's $size, below the floor, press restored by hitSlop", () => {
|
|
650
|
+
const medium = resolveKnobs(defaultKnobs).knobProps.nestedControl;
|
|
651
|
+
expect(medium.px).toBe(nestedControlPx("$true"));
|
|
652
|
+
expect(medium.width).toBe(nestedControlPx("$true"));
|
|
653
|
+
expect(medium.maxWidth).toBe(medium.px);
|
|
654
|
+
expect(medium.hitSlop).toEqual({ top: 8, bottom: 8, left: 8, right: 8 });
|
|
655
|
+
const small = resolveKnobs(defaultKnobs, undefined, "light", { sizeToken: "$3" }).knobProps
|
|
656
|
+
.nestedControl;
|
|
657
|
+
expect(small.px).toBe(Math.round((defaultScaleTokens.size.$3 * 32) / 44));
|
|
658
|
+
expect(nestedControlPx("$5")).toBe(Math.round((defaultScaleTokens.size.$5 * 32) / 44));
|
|
659
|
+
expect(onScale(36, 14, () => nestedControlPx("$true"))).toBe(Math.round((36 * 32) / 44));
|
|
521
660
|
});
|
|
522
661
|
});
|
|
523
662
|
|
|
@@ -528,21 +667,21 @@ describe("resolveKnobs", () => {
|
|
|
528
667
|
const { knobProps } = resolveKnobs(defaultKnobs);
|
|
529
668
|
expect(knobProps.cardSurface).toEqual({
|
|
530
669
|
backgroundColor: "$background",
|
|
531
|
-
borderRadius: "$
|
|
670
|
+
borderRadius: "$6",
|
|
532
671
|
padding: "$4",
|
|
533
672
|
});
|
|
534
673
|
expect(knobProps.cardSurface).not.toHaveProperty("borderWidth");
|
|
535
674
|
expect(knobProps.cardSurface).not.toHaveProperty("elevation");
|
|
536
675
|
});
|
|
537
676
|
|
|
538
|
-
it("cardSurface tracks borderRadius
|
|
539
|
-
const { knobProps } = resolveKnobs(knobsWith({ borderRadius: "
|
|
540
|
-
expect(knobProps.cardSurface.borderRadius).toBe(
|
|
541
|
-
expect(knobProps.cardSurface.padding).toBe("$
|
|
677
|
+
it("cardSurface tracks the borderRadius knob (cap bites at full: 44px > the 16px panelPadding)", () => {
|
|
678
|
+
const { knobProps } = resolveKnobs(knobsWith({ borderRadius: "full" }));
|
|
679
|
+
expect(knobProps.cardSurface.borderRadius).toBe(16);
|
|
680
|
+
expect(knobProps.cardSurface.padding).toBe("$4");
|
|
542
681
|
});
|
|
543
682
|
|
|
544
683
|
it("cardSurface keeps the radius token while it fits the padding", () => {
|
|
545
|
-
const { knobProps } = resolveKnobs(knobsWith({ borderRadius: "
|
|
684
|
+
const { knobProps } = resolveKnobs(knobsWith({ borderRadius: "medium" }));
|
|
546
685
|
expect(knobProps.cardSurface.borderRadius).toBe("$6");
|
|
547
686
|
});
|
|
548
687
|
|
|
@@ -578,23 +717,16 @@ describe("resolveKnobs", () => {
|
|
|
578
717
|
expect(knobProps.featureSurface).not.toHaveProperty("borderWidth");
|
|
579
718
|
});
|
|
580
719
|
|
|
581
|
-
it("featureSurface keeps $10 / $6 at the default knobs and every other stop moves
|
|
720
|
+
it("featureSurface keeps $10 / $6 at the default knobs and every other radius stop moves the radius (MPO-21 GL-59)", () => {
|
|
582
721
|
expect(defaultKnobs.borderRadius).toBe("medium");
|
|
583
|
-
expect(defaultKnobs.space).toBe("medium");
|
|
584
722
|
for (const borderRadius of ["none", "small", "large", "full"] as const) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
).not.toBe("$10");
|
|
589
|
-
}
|
|
590
|
-
for (const space of ["small", "large"] as const) {
|
|
591
|
-
expect(resolveKnobs(knobsWith({ space })).knobProps.featureSurface.padding, space).not.toBe(
|
|
592
|
-
"$6",
|
|
593
|
-
);
|
|
723
|
+
const { featureSurface } = resolveKnobs(knobsWith({ borderRadius })).knobProps;
|
|
724
|
+
expect(featureSurface.borderRadius, borderRadius).not.toBe("$10");
|
|
725
|
+
expect(featureSurface.padding, borderRadius).toBe(spacingRoles.featurePadding);
|
|
594
726
|
}
|
|
595
727
|
});
|
|
596
728
|
|
|
597
|
-
it("featureSurface radius follows the radius knob, one token per stop around $10
|
|
729
|
+
it("featureSurface radius follows the radius knob, one token per stop around $10", () => {
|
|
598
730
|
const expected = {
|
|
599
731
|
none: "$0",
|
|
600
732
|
small: "$9",
|
|
@@ -603,46 +735,20 @@ describe("resolveKnobs", () => {
|
|
|
603
735
|
full: "$12",
|
|
604
736
|
} as const;
|
|
605
737
|
for (const [borderRadius, token] of Object.entries(expected)) {
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
).toBe(token);
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
it("featureSurface padding follows the space knob, one token per stop around $6", () => {
|
|
617
|
-
const expected = { small: "$5", medium: "$6", large: "$7" } as const;
|
|
618
|
-
for (const [space, token] of Object.entries(expected)) {
|
|
619
|
-
for (const borderRadius of ["none", "small", "medium", "large", "full"] as const) {
|
|
620
|
-
expect(
|
|
621
|
-
resolveKnobs(knobsWith({ borderRadius, space: space as Knobs["space"] })).knobProps
|
|
622
|
-
.featureSurface.padding,
|
|
623
|
-
`${borderRadius}/${space}`,
|
|
624
|
-
).toBe(token);
|
|
625
|
-
}
|
|
738
|
+
expect(
|
|
739
|
+
resolveKnobs(knobsWith({ borderRadius: borderRadius as Knobs["borderRadius"] })).knobProps
|
|
740
|
+
.featureSurface.borderRadius,
|
|
741
|
+
borderRadius,
|
|
742
|
+
).toBe(token);
|
|
626
743
|
}
|
|
627
744
|
});
|
|
628
745
|
|
|
629
|
-
it("
|
|
630
|
-
const { knobProps } = resolveKnobs(applyDensity(knobsWith({ density: "compact" })));
|
|
631
|
-
expect(knobProps.featureSurface.padding).toBe("$5");
|
|
632
|
-
expect(knobProps.featureSurface.borderRadius).toBe("$10");
|
|
633
|
-
});
|
|
634
|
-
|
|
635
|
-
it("featureSurface never clips a child at its inset: padding >= radius x (1 - 1/sqrt 2) at every stop pair", () => {
|
|
746
|
+
it("featureSurface never clips a child at its inset: padding >= radius x (1 - 1/sqrt 2) at every radius stop", () => {
|
|
636
747
|
for (const borderRadius of ["none", "small", "medium", "large", "full"] as const) {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
const paddingPx = (tokens.space as any)[featureSurface.padding.slice(1)].val as number;
|
|
642
|
-
expect(paddingPx, `${borderRadius}/${space}`).toBeGreaterThanOrEqual(
|
|
643
|
-
radiusPx * (1 - Math.SQRT1_2),
|
|
644
|
-
);
|
|
645
|
-
}
|
|
748
|
+
const { featureSurface } = resolveKnobs(knobsWith({ borderRadius })).knobProps;
|
|
749
|
+
const radiusPx = (tokens.radius as any)[featureSurface.borderRadius.slice(1)].val as number;
|
|
750
|
+
const paddingPx = (tokens.space as any)[featureSurface.padding.slice(1)].val as number;
|
|
751
|
+
expect(paddingPx, borderRadius).toBeGreaterThanOrEqual(radiusPx * (1 - Math.SQRT1_2));
|
|
646
752
|
}
|
|
647
753
|
});
|
|
648
754
|
});
|
|
@@ -650,78 +756,89 @@ describe("resolveKnobs", () => {
|
|
|
650
756
|
// ── container radius cap (Axiom 1 CLIP) ──────────────────
|
|
651
757
|
|
|
652
758
|
describe("containerRadius (Axiom 1 child-clip cap)", () => {
|
|
653
|
-
it("default
|
|
759
|
+
it("default containers use $6 = 14px inside the 16px panel padding", () => {
|
|
654
760
|
const { knobProps } = resolveKnobs(defaultKnobs);
|
|
655
|
-
expect(knobProps.containerRadius).toEqual({ borderRadius: "$
|
|
656
|
-
expect(knobProps.cardSurface.borderRadius).toBe("$
|
|
761
|
+
expect(knobProps.containerRadius).toEqual({ borderRadius: "$6" });
|
|
762
|
+
expect(knobProps.cardSurface.borderRadius).toBe("$6");
|
|
657
763
|
});
|
|
658
764
|
|
|
659
|
-
it("caps full radius at the
|
|
660
|
-
expect(
|
|
661
|
-
resolveKnobs(knobsWith({ borderRadius: "full", space: "small" })).knobProps.containerRadius
|
|
662
|
-
.borderRadius,
|
|
663
|
-
).toBe(13);
|
|
765
|
+
it("caps full radius at the panelPadding px of the installed scale", () => {
|
|
664
766
|
expect(
|
|
665
|
-
resolveKnobs(knobsWith({ borderRadius: "full"
|
|
666
|
-
|
|
667
|
-
).
|
|
767
|
+
resolveKnobs(knobsWith({ borderRadius: "full" })).knobProps.containerRadius.borderRadius,
|
|
768
|
+
).toBe(16);
|
|
769
|
+
const dense = generateScale({ control: 36, type: 14 }).tokens;
|
|
668
770
|
expect(
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
]
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
771
|
+
onScale(
|
|
772
|
+
36,
|
|
773
|
+
14,
|
|
774
|
+
() => resolveKnobs(knobsWith({ borderRadius: "full" })).knobProps.containerRadius,
|
|
775
|
+
).borderRadius,
|
|
776
|
+
).toBe(dense.space.$4);
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
it("caps large and full; smaller container corners keep their token", () => {
|
|
780
|
+
for (const [radius, token] of [
|
|
781
|
+
["none", "$0"],
|
|
782
|
+
["small", "$4"],
|
|
783
|
+
["medium", "$6"],
|
|
784
|
+
["large", 16],
|
|
785
|
+
["full", 16],
|
|
786
|
+
] as const) {
|
|
787
|
+
const { knobProps } = resolveKnobs(knobsWith({ borderRadius: radius }));
|
|
788
|
+
expect(knobProps.containerRadius.borderRadius, radius).toBe(token);
|
|
684
789
|
}
|
|
685
790
|
});
|
|
686
791
|
|
|
687
|
-
it("
|
|
688
|
-
expect(
|
|
689
|
-
resolveKnobs(knobsWith({ borderRadius: "large", space: "small" })).knobProps.containerRadius
|
|
690
|
-
.borderRadius,
|
|
691
|
-
).toBe(13);
|
|
692
|
-
expect(
|
|
693
|
-
resolveKnobs(knobsWith({ borderRadius: "large", space: "medium" })).knobProps
|
|
694
|
-
.containerRadius.borderRadius,
|
|
695
|
-
).toBe("$6");
|
|
696
|
-
expect(
|
|
697
|
-
resolveKnobs(knobsWith({ borderRadius: "large", space: "large" })).knobProps.containerRadius
|
|
698
|
-
.borderRadius,
|
|
699
|
-
).toBe("$6");
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
it("mirrored px values agree with the live @tamagui/themes tokens (drift guard)", () => {
|
|
792
|
+
it("compares generated scale px, so it agrees with the scale's own tokens (drift guard)", () => {
|
|
703
793
|
const radiusTokenByKnob = {
|
|
704
794
|
none: "$0",
|
|
705
|
-
small: "$
|
|
706
|
-
medium: "$
|
|
707
|
-
large: "$
|
|
795
|
+
small: "$4",
|
|
796
|
+
medium: "$6",
|
|
797
|
+
large: "$8",
|
|
708
798
|
full: "$12",
|
|
709
799
|
} as const;
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
800
|
+
for (const [control, type] of [
|
|
801
|
+
[40, 15],
|
|
802
|
+
[44, 15],
|
|
803
|
+
[36, 14],
|
|
804
|
+
[48, 16],
|
|
805
|
+
] as const) {
|
|
806
|
+
const scale = generateScale({ control, type }).tokens;
|
|
807
|
+
for (const [radiusKnob, radiusToken] of Object.entries(radiusTokenByKnob)) {
|
|
808
|
+
const radiusPx = scale.radius[radiusToken.slice(1) as "0"];
|
|
809
|
+
const paddingPx = scale.space.$4;
|
|
715
810
|
const expected = radiusPx > paddingPx ? paddingPx : radiusToken;
|
|
716
811
|
expect(
|
|
717
|
-
capContainerRadius(radiusKnob as Knobs["borderRadius"]
|
|
718
|
-
`radius=${radiusKnob}
|
|
812
|
+
onScale(control, type, () => capContainerRadius(radiusKnob as Knobs["borderRadius"])),
|
|
813
|
+
`radius=${radiusKnob} scale=${control}/${type}`,
|
|
719
814
|
).toBe(expected);
|
|
720
815
|
}
|
|
721
816
|
}
|
|
722
817
|
});
|
|
723
818
|
});
|
|
724
819
|
|
|
820
|
+
it("containers step above controls while TextArea keeps the capped Input corner at every stop", () => {
|
|
821
|
+
for (const [borderRadius, containerPx, fieldPx] of [
|
|
822
|
+
["none", 0, 0],
|
|
823
|
+
["small", 8, 4],
|
|
824
|
+
["medium", 14, 8],
|
|
825
|
+
["large", 16, 14],
|
|
826
|
+
["full", 16, 16],
|
|
827
|
+
] as const) {
|
|
828
|
+
const { knobProps } = resolveKnobs(knobsWith({ borderRadius }));
|
|
829
|
+
const px = (value: string | number) =>
|
|
830
|
+
typeof value === "number" ? value : defaultScaleTokens.radius[value.slice(1) as "0"];
|
|
831
|
+
const controlPx = px(knobProps.borderRadius.borderRadius);
|
|
832
|
+
expect(px(knobProps.containerRadius.borderRadius)).toBe(containerPx);
|
|
833
|
+
expect(px(knobProps.fieldRadius.borderRadius)).toBe(fieldPx);
|
|
834
|
+
expect(capFieldRadius(borderRadius)).toBe(knobProps.fieldRadius.borderRadius);
|
|
835
|
+
expect(fieldPx).toBe(Math.min(controlPx, panelPaddingPx()));
|
|
836
|
+
if (borderRadius === "none") expect(containerPx).toBe(controlPx);
|
|
837
|
+
else if (controlPx < panelPaddingPx()) expect(containerPx).toBeGreaterThan(controlPx);
|
|
838
|
+
else expect(containerPx).toBe(panelPaddingPx());
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
|
|
725
842
|
// ── cornerSmoothing (Axiom 15 OPTICS) ─────────────────────
|
|
726
843
|
|
|
727
844
|
describe("cornerSmoothing knob", () => {
|
|
@@ -793,13 +910,11 @@ describe("resolveKnobs", () => {
|
|
|
793
910
|
});
|
|
794
911
|
|
|
795
912
|
it("smooth keeps the Axiom 1 container cap (clamped radii keep their caps)", () => {
|
|
796
|
-
const smooth = resolveKnobs(
|
|
797
|
-
|
|
798
|
-
);
|
|
799
|
-
|
|
800
|
-
expect(smooth.knobProps.
|
|
801
|
-
expect(round.knobProps.containerRadius.borderRadius).toBe(13);
|
|
802
|
-
expect(smooth.knobProps.cardSurface.borderRadius).toBe(13);
|
|
913
|
+
const smooth = resolveKnobs(knobsWith({ cornerSmoothing: "smooth", borderRadius: "full" }));
|
|
914
|
+
const round = resolveKnobs(knobsWith({ borderRadius: "full" }));
|
|
915
|
+
expect(smooth.knobProps.containerRadius.borderRadius).toBe(16);
|
|
916
|
+
expect(round.knobProps.containerRadius.borderRadius).toBe(16);
|
|
917
|
+
expect(smooth.knobProps.cardSurface.borderRadius).toBe(16);
|
|
803
918
|
expect(smooth.knobProps.containerRadius.className).toBe(cornerSmoothClassName);
|
|
804
919
|
});
|
|
805
920
|
|
|
@@ -965,16 +1080,16 @@ describe("resolveKnobs", () => {
|
|
|
965
1080
|
resolveKnobs(knobsWith({ headingFont: "sans-serif" })).knobProps.heading.fontFamily,
|
|
966
1081
|
).toBe("$heading");
|
|
967
1082
|
expect(resolveKnobs(knobsWith({ headingFont: "serif" })).knobProps.heading.fontFamily).toBe(
|
|
968
|
-
"$
|
|
1083
|
+
"$headingSerif",
|
|
969
1084
|
);
|
|
970
1085
|
expect(resolveKnobs(knobsWith({ headingFont: "mono" })).knobProps.heading.fontFamily).toBe(
|
|
971
|
-
"$
|
|
1086
|
+
"$headingMono",
|
|
972
1087
|
);
|
|
973
1088
|
expect(resolveKnobs(knobsWith({ headingFont: "slab" })).knobProps.heading.fontFamily).toBe(
|
|
974
|
-
"$
|
|
1089
|
+
"$headingSlab",
|
|
975
1090
|
);
|
|
976
1091
|
expect(resolveKnobs(knobsWith({ headingFont: "pixel" })).knobProps.heading.fontFamily).toBe(
|
|
977
|
-
"$
|
|
1092
|
+
"$headingPixel",
|
|
978
1093
|
);
|
|
979
1094
|
});
|
|
980
1095
|
|
|
@@ -993,8 +1108,12 @@ describe("resolveKnobs", () => {
|
|
|
993
1108
|
});
|
|
994
1109
|
|
|
995
1110
|
it("fontWeight maps correctly", () => {
|
|
996
|
-
expect(
|
|
997
|
-
"
|
|
1111
|
+
expect(
|
|
1112
|
+
resolveKnobs(knobsWith({ fontWeight: "regular" })).knobProps.heading,
|
|
1113
|
+
).not.toHaveProperty("fontWeight");
|
|
1114
|
+
expect(resolveKnobs(defaultKnobs).knobProps.label).not.toHaveProperty("fontWeight");
|
|
1115
|
+
expect(resolveKnobs(knobsWith({ fontWeight: "bold" })).knobProps.label.fontWeight).toBe(
|
|
1116
|
+
"700",
|
|
998
1117
|
);
|
|
999
1118
|
expect(resolveKnobs(knobsWith({ fontWeight: "bold" })).knobProps.heading.fontWeight).toBe(
|
|
1000
1119
|
"700",
|
|
@@ -1005,31 +1124,31 @@ describe("resolveKnobs", () => {
|
|
|
1005
1124
|
// ── pageTitleScale (D-11 hero-H1 dial) ──────────────────────
|
|
1006
1125
|
|
|
1007
1126
|
describe("pageTitleScale knob", () => {
|
|
1008
|
-
it("defaults to the moderate page-title step ($
|
|
1009
|
-
expect(resolveKnobs(defaultKnobs).knobProps.pageTitle).toEqual({ size: "$
|
|
1127
|
+
it("defaults to the moderate page-title step ($10 = 40px)", () => {
|
|
1128
|
+
expect(resolveKnobs(defaultKnobs).knobProps.pageTitle).toEqual({ size: "$10" });
|
|
1010
1129
|
});
|
|
1011
1130
|
|
|
1012
|
-
it('"display" re-opens the hero step ($
|
|
1131
|
+
it('"display" re-opens the hero step ($12 = 52px)', () => {
|
|
1013
1132
|
expect(resolveKnobs(knobsWith({ pageTitleScale: "display" })).knobProps.pageTitle).toEqual({
|
|
1014
|
-
size: "$
|
|
1133
|
+
size: "$12",
|
|
1015
1134
|
});
|
|
1016
1135
|
});
|
|
1017
1136
|
|
|
1018
1137
|
it("an omitted knob still resolves moderate (preset constructions predate the dial)", () => {
|
|
1019
1138
|
const { pageTitleScale: _omitted, ...withoutDial } = defaultKnobs;
|
|
1020
|
-
expect(resolveKnobs(withoutDial as Knobs).knobProps.pageTitle).toEqual({ size: "$
|
|
1139
|
+
expect(resolveKnobs(withoutDial as Knobs).knobProps.pageTitle).toEqual({ size: "$10" });
|
|
1021
1140
|
});
|
|
1022
1141
|
|
|
1023
1142
|
it("resolvePageTitleScale is the one table both channels read", () => {
|
|
1024
|
-
expect(resolvePageTitleScale("moderate")).toEqual({ size: "$
|
|
1025
|
-
expect(resolvePageTitleScale("display")).toEqual({ size: "$
|
|
1143
|
+
expect(resolvePageTitleScale("moderate")).toEqual({ size: "$10" });
|
|
1144
|
+
expect(resolvePageTitleScale("display")).toEqual({ size: "$12" });
|
|
1026
1145
|
});
|
|
1027
1146
|
|
|
1028
1147
|
it("product presets ship moderate; `hero` is the only display opt-in", () => {
|
|
1029
1148
|
expect(defaultPreset.knobs.pageTitleScale).toBe("moderate");
|
|
1030
1149
|
expect(boldPreset.knobs.pageTitleScale).toBe("moderate");
|
|
1031
1150
|
expect(heroPreset.knobs.pageTitleScale).toBe("display");
|
|
1032
|
-
expect(resolveKnobs(heroPreset.knobs).knobProps.pageTitle).toEqual({ size: "$
|
|
1151
|
+
expect(resolveKnobs(heroPreset.knobs).knobProps.pageTitle).toEqual({ size: "$12" });
|
|
1033
1152
|
});
|
|
1034
1153
|
|
|
1035
1154
|
it("`hero` differs from the default preset in nothing but the title step", () => {
|
|
@@ -1043,6 +1162,19 @@ describe("resolveKnobs", () => {
|
|
|
1043
1162
|
// ── animation ─────────────────────────────────────────────
|
|
1044
1163
|
|
|
1045
1164
|
describe("animation knob", () => {
|
|
1165
|
+
it("resolves each role while keeping the deprecated control alias", () => {
|
|
1166
|
+
const { knobProps } = resolveKnobs(knobsWith({ animation: "quick" }));
|
|
1167
|
+
expect(knobProps.motion.control.transition).toBe("quick");
|
|
1168
|
+
expect(knobProps.motion.overlay.transition).toBe("quicker");
|
|
1169
|
+
expect(knobProps.motion.scrim.transition).toBe("slow");
|
|
1170
|
+
expect(knobProps.motion.reveal.transition).toBe("medium");
|
|
1171
|
+
expect(knobProps.motion.slide.transition).toBe("medium");
|
|
1172
|
+
expect(knobProps.transition).toBe(knobProps.motion.control.transition);
|
|
1173
|
+
for (const role of Object.values(
|
|
1174
|
+
resolveKnobs(knobsWith({ animation: "none" })).knobProps.motion,
|
|
1175
|
+
))
|
|
1176
|
+
expect(role.transition).toBeUndefined();
|
|
1177
|
+
});
|
|
1046
1178
|
it('"none" → transition is undefined', () => {
|
|
1047
1179
|
const { knobProps } = resolveKnobs(knobsWith({ animation: "none" }));
|
|
1048
1180
|
expect(knobProps.transition).toBeUndefined();
|
|
@@ -1078,251 +1210,26 @@ describe("resolveKnobs", () => {
|
|
|
1078
1210
|
expect(overridden.knobProps.elevation).toBe("$4");
|
|
1079
1211
|
});
|
|
1080
1212
|
|
|
1081
|
-
|
|
1082
|
-
it("override callback receives knobProps with both size and space", () => {
|
|
1213
|
+
it("override callback receives the resolved roles and size token", () => {
|
|
1083
1214
|
let receivedProps: KnobProps | undefined;
|
|
1084
|
-
resolveKnobs(
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
describe("gap review", () => {
|
|
1097
|
-
const sizeTokenMap: Record<string, string> = {
|
|
1098
|
-
small: "$3",
|
|
1099
|
-
medium: "$4",
|
|
1100
|
-
large: "$5",
|
|
1101
|
-
};
|
|
1102
|
-
|
|
1103
|
-
const gapMap: Record<string, string> = {
|
|
1104
|
-
small: "$2",
|
|
1105
|
-
medium: "$4",
|
|
1106
|
-
large: "$5",
|
|
1107
|
-
};
|
|
1108
|
-
|
|
1109
|
-
const panelPaddingMap: Record<string, string> = {
|
|
1110
|
-
small: "$3",
|
|
1111
|
-
medium: "$4",
|
|
1112
|
-
large: "$6",
|
|
1113
|
-
};
|
|
1114
|
-
|
|
1115
|
-
const gapLgMap: Record<string, string> = {
|
|
1116
|
-
small: "$3",
|
|
1117
|
-
medium: "$5",
|
|
1118
|
-
large: "$6",
|
|
1119
|
-
};
|
|
1120
|
-
|
|
1121
|
-
// T4.1a — every Size enum value produces a valid sizeToken
|
|
1122
|
-
it("every Size enum value produces a valid sizeToken", () => {
|
|
1123
|
-
for (const sizeVal of Object.values(Size)) {
|
|
1124
|
-
const { knobProps } = resolveKnobs(knobsWith({ size: sizeVal }));
|
|
1125
|
-
expect(knobProps.sizeToken).toBe(sizeTokenMap[sizeVal]);
|
|
1126
|
-
}
|
|
1127
|
-
});
|
|
1128
|
-
|
|
1129
|
-
// T4.1b — every Space enum value produces valid panelPadding, gap, gapLg
|
|
1130
|
-
it("every Space enum value produces valid panelPadding, gap, gapLg", () => {
|
|
1131
|
-
for (const spaceVal of Object.values(Space)) {
|
|
1132
|
-
const { knobProps } = resolveKnobs(knobsWith({ space: spaceVal }));
|
|
1133
|
-
expect(knobProps.panelPadding.padding).toBe(panelPaddingMap[spaceVal]);
|
|
1134
|
-
expect(knobProps.gap.gap).toBe(gapMap[spaceVal]);
|
|
1135
|
-
expect(knobProps.gapLg.gap).toBe(gapLgMap[spaceVal]);
|
|
1136
|
-
}
|
|
1137
|
-
});
|
|
1138
|
-
|
|
1139
|
-
// T4.1c — all 9 combinations of size × space
|
|
1140
|
-
it("all 9 size × space combinations produce expected sizeToken and gap pairs", () => {
|
|
1141
|
-
for (const sizeVal of Object.values(Size)) {
|
|
1142
|
-
for (const spaceVal of Object.values(Space)) {
|
|
1143
|
-
const { knobProps } = resolveKnobs(knobsWith({ size: sizeVal, space: spaceVal }));
|
|
1144
|
-
expect(knobProps.sizeToken).toBe(sizeTokenMap[sizeVal]);
|
|
1145
|
-
expect(knobProps.gap.gap).toBe(gapMap[spaceVal]);
|
|
1146
|
-
expect(knobProps.panelPadding.padding).toBe(panelPaddingMap[spaceVal]);
|
|
1147
|
-
expect(knobProps.gapLg.gap).toBe(gapLgMap[spaceVal]);
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
});
|
|
1151
|
-
|
|
1152
|
-
// T4.1e — knobProps.space still equals knobs.space
|
|
1153
|
-
it("knobProps.space equals knobs.space", () => {
|
|
1154
|
-
for (const spaceVal of Object.values(Space)) {
|
|
1155
|
-
const { knobProps } = resolveKnobs(knobsWith({ space: spaceVal }));
|
|
1156
|
-
expect(knobProps.space).toBe(spaceVal);
|
|
1157
|
-
}
|
|
1158
|
-
});
|
|
1159
|
-
|
|
1160
|
-
// T4.1f — knobProps.size equals knobs.size
|
|
1161
|
-
it("knobProps.size equals knobs.size", () => {
|
|
1162
|
-
for (const sizeVal of Object.values(Size)) {
|
|
1163
|
-
const { knobProps } = resolveKnobs(knobsWith({ size: sizeVal }));
|
|
1164
|
-
expect(knobProps.size).toBe(sizeVal);
|
|
1165
|
-
}
|
|
1166
|
-
});
|
|
1167
|
-
|
|
1168
|
-
// T4.1h — defaultKnobs has both size and space set to "medium"
|
|
1169
|
-
it('defaultKnobs has both size and space set to "medium"', () => {
|
|
1170
|
-
expect(defaultKnobs.size).toBe("medium");
|
|
1171
|
-
expect(defaultKnobs.space).toBe("medium");
|
|
1172
|
-
});
|
|
1173
|
-
|
|
1174
|
-
// T4.1i — emptyStateKnobs is still valid (W12: default hover layer remains)
|
|
1175
|
-
it("emptyStateKnobs is still valid (no required size field breaks it)", () => {
|
|
1176
|
-
const knobs = knobsWith({ hover: emptyStateKnobs });
|
|
1177
|
-
const { control } = resolveKnobs(knobs);
|
|
1178
|
-
expect(control.hoverKnobProps).toEqual({ backgroundColor: "$backgroundHover" });
|
|
1179
|
-
});
|
|
1180
|
-
|
|
1181
|
-
// T4.1j — size "large" does not change borderRadius
|
|
1182
|
-
it('size "large" does not change borderRadius', () => {
|
|
1183
|
-
const baseline = resolveKnobs(knobsWith({ size: "medium" }));
|
|
1184
|
-
const large = resolveKnobs(knobsWith({ size: "large" }));
|
|
1185
|
-
expect(large.knobProps.borderRadius).toEqual(baseline.knobProps.borderRadius);
|
|
1215
|
+
resolveKnobs(
|
|
1216
|
+
defaultKnobs,
|
|
1217
|
+
(props) => {
|
|
1218
|
+
receivedProps = props;
|
|
1219
|
+
return {};
|
|
1220
|
+
},
|
|
1221
|
+
"light",
|
|
1222
|
+
{ sizeToken: "$3" },
|
|
1223
|
+
);
|
|
1224
|
+
expect(receivedProps!.sizeToken).toBe("$3");
|
|
1225
|
+
expect(receivedProps!.gap).toEqual({ gap: "$3" });
|
|
1186
1226
|
});
|
|
1187
1227
|
});
|
|
1188
|
-
});
|
|
1189
|
-
|
|
1190
|
-
// ── Density (DG-DEN-01 / W8) ─────────────────────────────────
|
|
1191
|
-
|
|
1192
|
-
describe("applyDensity", () => {
|
|
1193
|
-
it('defaultKnobs.density is "comfortable"', () => {
|
|
1194
|
-
expect(defaultKnobs.density).toBe(Density.Comfortable);
|
|
1195
|
-
});
|
|
1196
1228
|
|
|
1197
|
-
it("
|
|
1198
|
-
const knobs = knobsWith({
|
|
1199
|
-
const
|
|
1200
|
-
expect(
|
|
1201
|
-
expect(applied.space).toBe("large");
|
|
1202
|
-
expect(applied.density).toBe("comfortable");
|
|
1203
|
-
});
|
|
1204
|
-
|
|
1205
|
-
it("compact density steps space down one level and leaves size unchanged", () => {
|
|
1206
|
-
expect(
|
|
1207
|
-
applyDensity(knobsWith({ size: "large", space: "large", density: "compact" })),
|
|
1208
|
-
).toMatchObject({ size: "large", space: "medium", density: "compact" });
|
|
1209
|
-
expect(
|
|
1210
|
-
applyDensity(knobsWith({ size: "medium", space: "medium", density: "compact" })),
|
|
1211
|
-
).toMatchObject({ size: "medium", space: "small", density: "compact" });
|
|
1212
|
-
expect(
|
|
1213
|
-
applyDensity(knobsWith({ size: "small", space: "small", density: "compact" })),
|
|
1214
|
-
).toMatchObject({ size: "small", space: "small", density: "compact" });
|
|
1215
|
-
});
|
|
1216
|
-
|
|
1217
|
-
it("compact:true override wins over comfortable density knob without changing size", () => {
|
|
1218
|
-
const applied = applyDensity(
|
|
1219
|
-
knobsWith({ size: "medium", space: "medium", density: "comfortable" }),
|
|
1220
|
-
true,
|
|
1221
|
-
);
|
|
1222
|
-
expect(applied).toMatchObject({ size: "medium", space: "small", density: "compact" });
|
|
1223
|
-
});
|
|
1224
|
-
|
|
1225
|
-
it("compact:false override wins over compact density knob", () => {
|
|
1226
|
-
const applied = applyDensity(
|
|
1227
|
-
knobsWith({ size: "medium", space: "medium", density: "compact" }),
|
|
1228
|
-
false,
|
|
1229
|
-
);
|
|
1230
|
-
expect(applied).toMatchObject({ size: "medium", space: "medium", density: "comfortable" });
|
|
1231
|
-
});
|
|
1232
|
-
|
|
1233
|
-
it("resolveKnobs exposes density on knobProps", () => {
|
|
1234
|
-
expect(resolveKnobs(defaultKnobs).knobProps.density).toBe("comfortable");
|
|
1235
|
-
expect(resolveKnobs(knobsWith({ density: "compact" })).knobProps.density).toBe("compact");
|
|
1236
|
-
});
|
|
1237
|
-
|
|
1238
|
-
it("compact and comfortable differ in padX while height stays 44 (LC-76)", () => {
|
|
1239
|
-
const comfortable = resolveKnobs(knobsWith({ density: "comfortable" }));
|
|
1240
|
-
const compact = resolveKnobs(knobsWith({ density: "compact" }));
|
|
1241
|
-
expect(comfortable.knobProps.control.height).toBe(44);
|
|
1242
|
-
expect(compact.knobProps.control.height).toBe(44);
|
|
1243
|
-
expect(comfortable.knobProps.sizeToken).toBe("$4");
|
|
1244
|
-
expect(compact.knobProps.sizeToken).toBe("$4");
|
|
1245
|
-
expect(compact.knobProps.control.paddingHorizontal).not.toBe(
|
|
1246
|
-
comfortable.knobProps.control.paddingHorizontal,
|
|
1247
|
-
);
|
|
1248
|
-
expect(comfortable.knobProps.control.paddingHorizontal).toBe(18);
|
|
1249
|
-
expect(compact.knobProps.control.paddingHorizontal).toBe(16);
|
|
1250
|
-
});
|
|
1251
|
-
|
|
1252
|
-
it("names which gap density moves: the LAYOUT gap, never the recipe gap (LC-79)", () => {
|
|
1253
|
-
// `control.gap` retains the size-table grouping across density and shape.
|
|
1254
|
-
// The gap a reader sees tighten is `knobProps.gap`, the
|
|
1255
|
-
// space-ramp one, and it only moves once `applyDensity` has stepped
|
|
1256
|
-
// `space` — `resolveKnobs` alone is a pure token mapper.
|
|
1257
|
-
const comfortable = resolveKnobs(applyDensity(knobsWith({ space: "medium" })));
|
|
1258
|
-
const compact = resolveKnobs(applyDensity(knobsWith({ space: "medium", density: "compact" })));
|
|
1259
|
-
expect(compact.knobProps.control.gap).toBe(comfortable.knobProps.control.gap);
|
|
1260
|
-
expect(compact.knobProps.gap).not.toEqual(comfortable.knobProps.gap);
|
|
1261
|
-
expect(compact.knobProps.panelPadding).not.toEqual(comfortable.knobProps.panelPadding);
|
|
1262
|
-
expect(compact.knobProps.control.height).toBe(44);
|
|
1263
|
-
expect(comfortable.knobProps.control.height).toBe(44);
|
|
1264
|
-
});
|
|
1265
|
-
|
|
1266
|
-
it("compact density produces the same sizeToken and a smaller gap than comfortable", () => {
|
|
1267
|
-
const comfortable = resolveKnobs(applyDensity(knobsWith({ size: "medium", space: "medium" })));
|
|
1268
|
-
const compact = resolveKnobs(
|
|
1269
|
-
applyDensity(knobsWith({ size: "medium", space: "medium", density: "compact" })),
|
|
1270
|
-
);
|
|
1271
|
-
expect(comfortable.knobProps.sizeToken).toBe("$4");
|
|
1272
|
-
expect(compact.knobProps.sizeToken).toBe("$4");
|
|
1273
|
-
expect(compact.knobProps.control.height).toBe(comfortable.knobProps.control.height);
|
|
1274
|
-
expect(comfortable.knobProps.gap).toEqual({ gap: "$4" });
|
|
1275
|
-
expect(compact.knobProps.gap).toEqual({ gap: "$2" });
|
|
1276
|
-
});
|
|
1277
|
-
|
|
1278
|
-
it("pairs label type to size — never the control-height sizeToken", () => {
|
|
1279
|
-
expect(resolveKnobs(knobsWith({ size: "small" })).knobProps.label.fontSize).toBe("$2");
|
|
1280
|
-
expect(resolveKnobs(knobsWith({ size: "medium" })).knobProps.label.fontSize).toBe("$3");
|
|
1281
|
-
expect(resolveKnobs(knobsWith({ size: "large" })).knobProps.label.fontSize).toBe("$4");
|
|
1282
|
-
expect(resolveKnobs(defaultKnobs).knobProps.label.fontSize).not.toBe(
|
|
1283
|
-
resolveKnobs(defaultKnobs).knobProps.sizeToken,
|
|
1284
|
-
);
|
|
1285
|
-
});
|
|
1286
|
-
|
|
1287
|
-
it("nestedControl paints below the 44px floor and restores press with hitSlop", () => {
|
|
1288
|
-
const medium = resolveKnobs(knobsWith({ size: "medium" })).knobProps.nestedControl;
|
|
1289
|
-
expect(medium.px).toBe(32);
|
|
1290
|
-
expect(medium.width).toBe(32);
|
|
1291
|
-
expect(medium.maxWidth).toBe(32);
|
|
1292
|
-
expect(medium.hitSlop).toEqual({ top: 6, bottom: 6, left: 6, right: 6 });
|
|
1293
|
-
|
|
1294
|
-
const compact = resolveKnobs(applyDensity(knobsWith({ size: "medium", density: "compact" })))
|
|
1295
|
-
.knobProps.nestedControl;
|
|
1296
|
-
// Compact steps space only (LC-76) — nestedControl follows size, so
|
|
1297
|
-
// medium+compact stays on the medium nested box.
|
|
1298
|
-
expect(compact.px).toBe(32);
|
|
1299
|
-
expect(compact.hitSlop).toEqual({ top: 6, bottom: 6, left: 6, right: 6 });
|
|
1300
|
-
});
|
|
1301
|
-
|
|
1302
|
-
it("compact density preserves house-decision knobs", () => {
|
|
1303
|
-
const knobs = knobsWith({
|
|
1304
|
-
density: "compact",
|
|
1305
|
-
fieldLabelPlacement: "side",
|
|
1306
|
-
requiredMarking: "asterisk",
|
|
1307
|
-
tableZebra: "on",
|
|
1308
|
-
bulkBarPlacement: "bottom",
|
|
1309
|
-
selectAllScope: "filtered",
|
|
1310
|
-
timestampStyle: "relative",
|
|
1311
|
-
disabledStyle: "dimWhole",
|
|
1312
|
-
formAutofocus: "on",
|
|
1313
|
-
});
|
|
1314
|
-
const applied = applyDensity(knobs);
|
|
1315
|
-
expect(applied).toMatchObject({
|
|
1316
|
-
density: "compact",
|
|
1317
|
-
fieldLabelPlacement: "side",
|
|
1318
|
-
requiredMarking: "asterisk",
|
|
1319
|
-
tableZebra: "on",
|
|
1320
|
-
bulkBarPlacement: "bottom",
|
|
1321
|
-
selectAllScope: "filtered",
|
|
1322
|
-
timestampStyle: "relative",
|
|
1323
|
-
disabledStyle: "dimWhole",
|
|
1324
|
-
formAutofocus: "on",
|
|
1325
|
-
});
|
|
1229
|
+
it("emptyStateKnobs is still valid (W12: default hover layer remains)", () => {
|
|
1230
|
+
const knobs = knobsWith({ hover: emptyStateKnobs });
|
|
1231
|
+
const { control } = resolveKnobs(knobs);
|
|
1232
|
+
expect(control.hoverKnobProps).toEqual({ backgroundColor: "$backgroundHover" });
|
|
1326
1233
|
});
|
|
1327
1234
|
});
|
|
1328
1235
|
|
|
@@ -1387,7 +1294,7 @@ describe("house decision knobs", () => {
|
|
|
1387
1294
|
expect(housey.knobProps.sizeToken).toBe(baseline.knobProps.sizeToken);
|
|
1388
1295
|
expect(housey.knobProps.gap).toEqual(baseline.knobProps.gap);
|
|
1389
1296
|
expect(housey.knobProps.borderRadius).toEqual(baseline.knobProps.borderRadius);
|
|
1390
|
-
expect(housey.knobProps.
|
|
1297
|
+
expect(housey.knobProps.panelPadding).toEqual(baseline.knobProps.panelPadding);
|
|
1391
1298
|
});
|
|
1392
1299
|
});
|
|
1393
1300
|
|
|
@@ -1481,11 +1388,11 @@ describe("LC-67 touch height floor via resolveKnobs", () => {
|
|
|
1481
1388
|
}));
|
|
1482
1389
|
const { resolveKnobs: resolveTouch } = await import("./resolveKnobs");
|
|
1483
1390
|
try {
|
|
1484
|
-
const small = resolveTouch(
|
|
1485
|
-
const medium = resolveTouch(
|
|
1391
|
+
const small = resolveTouch(defaultKnobs, undefined, "light", { sizeToken: "$3" });
|
|
1392
|
+
const medium = resolveTouch(defaultKnobs);
|
|
1486
1393
|
expect(small.knobProps.control.height).toBeGreaterThanOrEqual(44);
|
|
1487
1394
|
expect(medium.knobProps.control.height).toBeGreaterThanOrEqual(44);
|
|
1488
|
-
expect(medium.knobProps.control.height).
|
|
1395
|
+
expect(medium.knobProps.control.height).toBe(small.knobProps.control.height);
|
|
1489
1396
|
} finally {
|
|
1490
1397
|
vi.doUnmock("@multiplatform.one/platform");
|
|
1491
1398
|
vi.resetModules();
|
|
@@ -1495,16 +1402,49 @@ describe("LC-67 touch height floor via resolveKnobs", () => {
|
|
|
1495
1402
|
|
|
1496
1403
|
describe("containerCapProps (DG-RAD-04 provenance)", () => {
|
|
1497
1404
|
it("names the knob stop a borderRadiusMap token came from", () => {
|
|
1498
|
-
expect(containerCapProps("PageSection", "$12"
|
|
1405
|
+
expect(containerCapProps("PageSection", "$12")).toEqual({
|
|
1499
1406
|
"data-constraint-container": "PageSection",
|
|
1500
1407
|
"data-radius-knob": "full",
|
|
1501
|
-
"data-space-knob": "small",
|
|
1502
1408
|
});
|
|
1503
1409
|
});
|
|
1504
1410
|
|
|
1411
|
+
it("decodes control tokens even when a container uses a larger key", () => {
|
|
1412
|
+
expect(containerCapProps("PageSection", "$4")?.["data-radius-knob"]).toBe("medium");
|
|
1413
|
+
expect(containerCapProps("TextArea", "$6")?.["data-radius-knob"]).toBe("large");
|
|
1414
|
+
expect(containerCapProps("PageSection", "$8")).toBeUndefined();
|
|
1415
|
+
});
|
|
1416
|
+
|
|
1505
1417
|
it("makes no claim for a radius that did not come from the knob", () => {
|
|
1506
|
-
expect(containerCapProps("TextArea", 13
|
|
1507
|
-
expect(containerCapProps("TextArea", "$5"
|
|
1508
|
-
|
|
1418
|
+
expect(containerCapProps("TextArea", 13)).toBeUndefined();
|
|
1419
|
+
expect(containerCapProps("TextArea", "$5")).toBeUndefined();
|
|
1420
|
+
});
|
|
1421
|
+
});
|
|
1422
|
+
|
|
1423
|
+
describe("painted ancestor (WP-c2: nested surfaces stop re-elevating)", () => {
|
|
1424
|
+
const knobs = { ...defaultKnobs, elevation: "large" as const };
|
|
1425
|
+
|
|
1426
|
+
it("top level: elevatedSurface carries the overlay elevation and paint", () => {
|
|
1427
|
+
const { knobProps } = resolveKnobs(knobs);
|
|
1428
|
+
expect(knobProps.elevatedSurface.elevation).toBe(knobProps.overlayElevation);
|
|
1429
|
+
expect(knobProps.elevatedSurface.shadowRadius).toBeGreaterThan(0);
|
|
1430
|
+
expect(knobProps.cardSurface.backgroundColor).toBe("$background");
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
it("under a painted ancestor: elevatedSurface is border only, cardSurface one step off", () => {
|
|
1434
|
+
const { knobProps } = resolveKnobs(knobs, undefined, "light", { painted: true });
|
|
1435
|
+
expect(knobProps.elevatedSurface.elevation).toBeUndefined();
|
|
1436
|
+
expect(knobProps.elevatedSurface.shadowRadius).toBeUndefined();
|
|
1437
|
+
expect(knobProps.elevatedSurface.style).toBeUndefined();
|
|
1438
|
+
expect(knobProps.elevatedSurface.borderWidth).toBeGreaterThan(0);
|
|
1439
|
+
expect(knobProps.elevatedSurface.borderColor).toBe("$borderColor");
|
|
1440
|
+
expect(knobProps.cardSurface.backgroundColor).toBe(paintedCardFill);
|
|
1441
|
+
expect(paintedCardFill).toBe("$color2");
|
|
1442
|
+
});
|
|
1443
|
+
|
|
1444
|
+
it("border only survives borderWidth none: the hairline floor still draws the box", () => {
|
|
1445
|
+
const { knobProps } = resolveKnobs({ ...knobs, borderWidth: "none" }, undefined, "light", {
|
|
1446
|
+
painted: true,
|
|
1447
|
+
});
|
|
1448
|
+
expect(knobProps.elevatedSurface.borderWidth).toBeGreaterThan(0);
|
|
1509
1449
|
});
|
|
1510
1450
|
});
|