@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
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Controls:
|
|
7
7
|
* - Color scheme (system / light / dark) via @vxrn/color-scheme
|
|
8
|
-
* - Preset picker (using registered presets)
|
|
8
|
+
* - Preset picker (using registered presets), each listed with its token
|
|
9
|
+
* scale (control / type px): size and spacing come from the preset's scale
|
|
9
10
|
* - All knobs: fillStyle, borderRadius, cornerSmoothing, borderWidth,
|
|
10
|
-
* elevation,
|
|
11
|
-
* fontWeight, animation,
|
|
11
|
+
* elevation, textAccent, headingFont, bodyFont, fontWeight, animation,
|
|
12
12
|
* plus house decisions (fieldLabelPlacement, requiredMarking, tableZebra,
|
|
13
13
|
* bulkBarPlacement, selectAllScope, timestampStyle, disabledStyle,
|
|
14
14
|
* formAutofocus)
|
|
@@ -55,9 +55,6 @@ import {
|
|
|
55
55
|
CornerSmoothing,
|
|
56
56
|
BorderWidth,
|
|
57
57
|
Elevation,
|
|
58
|
-
Space,
|
|
59
|
-
Size,
|
|
60
|
-
Density,
|
|
61
58
|
TextAccent,
|
|
62
59
|
HeadingFont,
|
|
63
60
|
BodyFont,
|
|
@@ -73,14 +70,12 @@ import {
|
|
|
73
70
|
interactionStates,
|
|
74
71
|
} from "../knobs";
|
|
75
72
|
import { getPresetNames, getPreset } from "../shared";
|
|
73
|
+
import { defaultScaleInputs } from "../scale/scaleInputs";
|
|
76
74
|
import { extractAnimationNames } from "./animationOptions";
|
|
77
75
|
import { ColorLineVisualizer } from "./ColorLineVisualizer";
|
|
78
76
|
import { buildSnapshot } from "./buildSnapshot";
|
|
79
77
|
import {
|
|
80
78
|
fillIcon as _fillIcon,
|
|
81
|
-
spaceIcon as _spaceIcon,
|
|
82
|
-
sizeIcon as _sizeIcon,
|
|
83
|
-
densityIcon as _densityIcon,
|
|
84
79
|
borderRadiusIcon as _borderRadiusIcon,
|
|
85
80
|
cornerSmoothingIcon as _cornerSmoothingIcon,
|
|
86
81
|
borderWidthIcon as _borderWidthIcon,
|
|
@@ -128,9 +123,6 @@ const knobKeys: (keyof Knobs)[] = [
|
|
|
128
123
|
"cornerSmoothing",
|
|
129
124
|
"borderWidth",
|
|
130
125
|
"elevation",
|
|
131
|
-
"space",
|
|
132
|
-
"size",
|
|
133
|
-
"density",
|
|
134
126
|
"textAccent",
|
|
135
127
|
"headingFont",
|
|
136
128
|
"bodyFont",
|
|
@@ -152,7 +144,6 @@ const stateKnobKeys: (keyof Knobs)[] = [
|
|
|
152
144
|
"borderRadius",
|
|
153
145
|
"borderWidth",
|
|
154
146
|
"elevation",
|
|
155
|
-
"space",
|
|
156
147
|
"textAccent",
|
|
157
148
|
"headingFont",
|
|
158
149
|
"bodyFont",
|
|
@@ -170,8 +161,6 @@ const designProfiles: Record<string, DesignProfile> = {
|
|
|
170
161
|
borderRadius: "small",
|
|
171
162
|
borderWidth: "medium",
|
|
172
163
|
elevation: "none",
|
|
173
|
-
space: "medium",
|
|
174
|
-
size: "medium",
|
|
175
164
|
textAccent: "high",
|
|
176
165
|
headingFont: "sans-serif",
|
|
177
166
|
bodyFont: "sans-serif",
|
|
@@ -184,8 +173,6 @@ const designProfiles: Record<string, DesignProfile> = {
|
|
|
184
173
|
borderRadius: "large",
|
|
185
174
|
borderWidth: "none",
|
|
186
175
|
elevation: "small",
|
|
187
|
-
space: "medium",
|
|
188
|
-
size: "medium",
|
|
189
176
|
textAccent: "high",
|
|
190
177
|
headingFont: "sans-serif",
|
|
191
178
|
bodyFont: "sans-serif",
|
|
@@ -199,8 +186,6 @@ const designProfiles: Record<string, DesignProfile> = {
|
|
|
199
186
|
borderRadius: "small",
|
|
200
187
|
borderWidth: "small",
|
|
201
188
|
elevation: "none",
|
|
202
|
-
space: "medium",
|
|
203
|
-
size: "medium",
|
|
204
189
|
textAccent: "high",
|
|
205
190
|
headingFont: "sans-serif",
|
|
206
191
|
bodyFont: "sans-serif",
|
|
@@ -213,8 +198,6 @@ const designProfiles: Record<string, DesignProfile> = {
|
|
|
213
198
|
borderRadius: "small",
|
|
214
199
|
borderWidth: "small",
|
|
215
200
|
elevation: "small",
|
|
216
|
-
space: "medium",
|
|
217
|
-
size: "medium",
|
|
218
201
|
textAccent: "high",
|
|
219
202
|
headingFont: "sans-serif",
|
|
220
203
|
bodyFont: "sans-serif",
|
|
@@ -231,6 +214,15 @@ const profileNames = ["", ...Object.keys(designProfiles)] as const;
|
|
|
231
214
|
// Helpers
|
|
232
215
|
// ---------------------------------------------------------------------------
|
|
233
216
|
|
|
217
|
+
/** A preset picker option with the token scale it renders on (control / type px). */
|
|
218
|
+
function formatPresetOption(name: string): string {
|
|
219
|
+
if (name === "") return "custom";
|
|
220
|
+
const scale = getPreset(name)?.scale;
|
|
221
|
+
const control = scale?.control ?? defaultScaleInputs.control;
|
|
222
|
+
const type = scale?.type ?? defaultScaleInputs.type;
|
|
223
|
+
return `${name} · ${control}/${type}`;
|
|
224
|
+
}
|
|
225
|
+
|
|
234
226
|
type SchemeHook = {
|
|
235
227
|
setting: "system" | "light" | "dark";
|
|
236
228
|
value: "light" | "dark";
|
|
@@ -705,18 +697,6 @@ function fillIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
|
705
697
|
return _fillIcon(v, ic(active, dark));
|
|
706
698
|
}
|
|
707
699
|
|
|
708
|
-
function spaceIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
709
|
-
return _spaceIcon(v, ic(active, dark));
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
function sizeIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
713
|
-
return _sizeIcon(v, ic(active, dark));
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
function densityIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
717
|
-
return _densityIcon(v, ic(active, dark));
|
|
718
|
-
}
|
|
719
|
-
|
|
720
700
|
function borderRadiusIcon(v: string, active: boolean, dark: boolean): ReactNode {
|
|
721
701
|
return _borderRadiusIcon(v, ic(active, dark));
|
|
722
702
|
}
|
|
@@ -1134,8 +1114,6 @@ export function ThemeDevtoolsPanel({
|
|
|
1134
1114
|
borderRadius: Object.values(BorderRadius),
|
|
1135
1115
|
borderWidth: Object.values(BorderWidth),
|
|
1136
1116
|
elevation: Object.values(Elevation),
|
|
1137
|
-
space: Object.values(Space),
|
|
1138
|
-
size: Object.values(Size),
|
|
1139
1117
|
textAccent: Object.values(TextAccent),
|
|
1140
1118
|
headingFont: Object.values(HeadingFont),
|
|
1141
1119
|
bodyFont: Object.values(BodyFont),
|
|
@@ -1155,7 +1133,6 @@ export function ThemeDevtoolsPanel({
|
|
|
1155
1133
|
borderRadius: Object.values(BorderRadius),
|
|
1156
1134
|
borderWidth: Object.values(BorderWidth),
|
|
1157
1135
|
elevation: Object.values(Elevation),
|
|
1158
|
-
space: Object.values(Space),
|
|
1159
1136
|
textAccent: Object.values(TextAccent),
|
|
1160
1137
|
headingFont: Object.values(HeadingFont),
|
|
1161
1138
|
bodyFont: Object.values(BodyFont),
|
|
@@ -1353,7 +1330,7 @@ export function ThemeDevtoolsPanel({
|
|
|
1353
1330
|
options={presetOptions}
|
|
1354
1331
|
value={activePresetName ?? ""}
|
|
1355
1332
|
onChange={(v) => handlePresetChange(v)}
|
|
1356
|
-
format={
|
|
1333
|
+
format={formatPresetOption}
|
|
1357
1334
|
styles={styles}
|
|
1358
1335
|
/>
|
|
1359
1336
|
)}
|
|
@@ -1406,46 +1383,6 @@ export function ThemeDevtoolsPanel({
|
|
|
1406
1383
|
>
|
|
1407
1384
|
{stateRows("fillStyle", Object.values(FillStyle), fillIcon)}
|
|
1408
1385
|
</IconGroup>
|
|
1409
|
-
<IconGroup
|
|
1410
|
-
label="Size"
|
|
1411
|
-
options={Object.values(Size)}
|
|
1412
|
-
value={getOverride("size")}
|
|
1413
|
-
onChange={(v) => set("size", v)}
|
|
1414
|
-
icon={sizeIcon}
|
|
1415
|
-
styles={styles}
|
|
1416
|
-
isDark={isDark}
|
|
1417
|
-
pinned={pinnedKnobs.has("size")}
|
|
1418
|
-
onTogglePin={() => togglePin("size")}
|
|
1419
|
-
onUnset={() => unset("size")}
|
|
1420
|
-
/>
|
|
1421
|
-
<IconGroup
|
|
1422
|
-
label="Space"
|
|
1423
|
-
options={Object.values(Space)}
|
|
1424
|
-
value={getOverride("space")}
|
|
1425
|
-
onChange={(v) => set("space", v)}
|
|
1426
|
-
icon={spaceIcon}
|
|
1427
|
-
styles={styles}
|
|
1428
|
-
isDark={isDark}
|
|
1429
|
-
pinned={pinnedKnobs.has("space")}
|
|
1430
|
-
onTogglePin={() => togglePin("space")}
|
|
1431
|
-
expanded={expandedKnobs.has("space")}
|
|
1432
|
-
onToggleExpand={() => toggleExpand("space")}
|
|
1433
|
-
onUnset={() => unset("space")}
|
|
1434
|
-
>
|
|
1435
|
-
{stateRows("space", Object.values(Space), spaceIcon)}
|
|
1436
|
-
</IconGroup>
|
|
1437
|
-
<IconGroup
|
|
1438
|
-
label="Density"
|
|
1439
|
-
options={Object.values(Density)}
|
|
1440
|
-
value={getOverride("density")}
|
|
1441
|
-
onChange={(v) => set("density", v)}
|
|
1442
|
-
icon={densityIcon}
|
|
1443
|
-
styles={styles}
|
|
1444
|
-
isDark={isDark}
|
|
1445
|
-
pinned={pinnedKnobs.has("density")}
|
|
1446
|
-
onTogglePin={() => togglePin("density")}
|
|
1447
|
-
onUnset={() => unset("density")}
|
|
1448
|
-
/>
|
|
1449
1386
|
<IconGroup
|
|
1450
1387
|
label="Radius"
|
|
1451
1388
|
options={Object.values(BorderRadius)}
|
|
@@ -1,44 +1,30 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import { motionStops, animations } from "../animations/css";
|
|
3
3
|
import { extractAnimationNames } from "./animationOptions";
|
|
4
4
|
|
|
5
|
-
describe("extractAnimationNames
|
|
6
|
-
it("
|
|
7
|
-
|
|
8
|
-
expect(names).toEqual([...animationNames]);
|
|
9
|
-
for (const member of [
|
|
10
|
-
"usePresence",
|
|
11
|
-
"ResetPresence",
|
|
12
|
-
"inputStyle",
|
|
13
|
-
"outputStyle",
|
|
14
|
-
"useAnimatedNumber",
|
|
15
|
-
"useAnimations",
|
|
16
|
-
]) {
|
|
17
|
-
expect(names).not.toContain(member);
|
|
18
|
-
}
|
|
5
|
+
describe("extractAnimationNames", () => {
|
|
6
|
+
it("offers only ordered motion stops on the house driver", () => {
|
|
7
|
+
expect(extractAnimationNames({ animations })).toEqual([...motionStops]);
|
|
19
8
|
});
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const driver = { ...animations, animations: { fast: "ease-in 50ms", calm: "ease-in 900ms" } };
|
|
36
|
-
expect(extractAnimationNames({ animations: driver })).toEqual(["fast", "calm"]);
|
|
9
|
+
it("filters custom drivers to registered knob stops in duration order", () => {
|
|
10
|
+
expect(
|
|
11
|
+
extractAnimationNames({
|
|
12
|
+
animations: {
|
|
13
|
+
animations: {
|
|
14
|
+
slow: "450ms",
|
|
15
|
+
quick: "150ms",
|
|
16
|
+
quickest: "80ms",
|
|
17
|
+
tooltip: "450ms",
|
|
18
|
+
"100ms": "100ms",
|
|
19
|
+
foreign: "1s",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
).toEqual(["quickest", "quick", "slow"]);
|
|
37
24
|
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
expect(extractAnimationNames(undefined)).toEqual([...
|
|
41
|
-
expect(extractAnimationNames({})).toEqual([...
|
|
42
|
-
expect(extractAnimationNames({ animations: { animations: {} } })).toEqual([...animationNames]);
|
|
25
|
+
it("returns no stops for an empty driver, and defaults when no driver is available", () => {
|
|
26
|
+
expect(extractAnimationNames({ animations: { animations: {} } })).toEqual([]);
|
|
27
|
+
expect(extractAnimationNames(undefined)).toEqual([...motionStops]);
|
|
28
|
+
expect(extractAnimationNames({})).toEqual([...motionStops]);
|
|
43
29
|
});
|
|
44
30
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { motionStops } from "../animations/css";
|
|
2
2
|
|
|
3
3
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
4
4
|
return typeof value === "object" && value !== null;
|
|
@@ -14,9 +14,7 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
14
14
|
export function extractAnimationNames(tamaguiConfig: unknown): string[] {
|
|
15
15
|
const driver = isRecord(tamaguiConfig) ? tamaguiConfig.animations : undefined;
|
|
16
16
|
const named = isRecord(driver) ? driver.animations : undefined;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
return [...animationNames];
|
|
17
|
+
return isRecord(named)
|
|
18
|
+
? motionStops.filter((stop) => Object.hasOwn(named, stop))
|
|
19
|
+
: [...motionStops];
|
|
22
20
|
}
|
|
@@ -18,29 +18,6 @@ export function fillIcon(value: string, color: string): ReactNode {
|
|
|
18
18
|
return <div style={{ width: 14, height: 14, border: `1.5px solid ${color}`, borderRadius: 2 }} />;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
export function spaceIcon(value: string, color: string): ReactNode {
|
|
22
|
-
const size = ({ small: 8, medium: 12, large: 16 } as Record<string, number>)[value] ?? 12;
|
|
23
|
-
return <div style={{ width: size, height: size, background: color, borderRadius: 2 }} />;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function sizeIcon(value: string, color: string): ReactNode {
|
|
27
|
-
const height = ({ small: 8, medium: 11, large: 14 } as Record<string, number>)[value] ?? 11;
|
|
28
|
-
return <div style={{ width: 4, height: height, background: color, borderRadius: 1 }} />;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function densityIcon(value: string, color: string): ReactNode {
|
|
32
|
-
// Comfortable = taller stack; compact = compressed stack.
|
|
33
|
-
const gap = value === "compact" ? 1 : 2;
|
|
34
|
-
const barH = value === "compact" ? 3 : 4;
|
|
35
|
-
return (
|
|
36
|
-
<div style={{ display: "flex", flexDirection: "column", gap, width: 14 }}>
|
|
37
|
-
<div style={{ height: barH, background: color, borderRadius: 1 }} />
|
|
38
|
-
<div style={{ height: barH, background: color, borderRadius: 1 }} />
|
|
39
|
-
<div style={{ height: barH, background: color, borderRadius: 1 }} />
|
|
40
|
-
</div>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
21
|
export function borderRadiusIcon(value: string, color: string): ReactNode {
|
|
45
22
|
const radius =
|
|
46
23
|
({ none: 0, small: 2, medium: 4, large: 7, full: 10 } as Record<string, number>)[value] ?? 0;
|
|
@@ -350,7 +327,10 @@ export function formAutofocusIcon(value: string, color: string): ReactNode {
|
|
|
350
327
|
height: 14,
|
|
351
328
|
border: `1.5px solid ${color}`,
|
|
352
329
|
borderRadius: 2,
|
|
353
|
-
|
|
330
|
+
outlineWidth: value === "on" ? 2 : 0,
|
|
331
|
+
outlineStyle: "solid",
|
|
332
|
+
outlineColor: color,
|
|
333
|
+
outlineOffset: 0,
|
|
354
334
|
opacity: value === "on" ? 1 : 0.45,
|
|
355
335
|
}}
|
|
356
336
|
/>
|
|
@@ -142,36 +142,15 @@ describe("$outlineColor token contrast (DF-03 / DG-A11Y-01)", () => {
|
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
it("
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// `color9` ink step (1.67 / 1.55 / 1.44 in this build). Same verdict, same
|
|
155
|
-
// reason, different surfaces — the board's error fills (#ce2c31 / #ff9794)
|
|
156
|
-
// are not in these default sub-theme ramps at all, so the fill side is the
|
|
157
|
-
// board's to own and not this package's.
|
|
158
|
-
//
|
|
159
|
-
// What this assertion IS: no sub-theme ink step the inset band could land
|
|
160
|
-
// on clears the floor, which keeps the offset-0 geometry load-bearing. If
|
|
161
|
-
// the ramps ever lighten past 3:1 this test goes stale, not wrong.
|
|
162
|
-
for (const [scheme, intent] of [
|
|
163
|
-
["light", "accent"],
|
|
164
|
-
["light", "error"],
|
|
165
|
-
["dark", "error"],
|
|
166
|
-
] as const) {
|
|
167
|
-
const ring = normalizeToHex(themes[scheme].outlineColor);
|
|
168
|
-
const fill = normalizeToHex(themes[`${scheme}_${intent}`].color9);
|
|
169
|
-
expect(ring).toBeTruthy();
|
|
170
|
-
expect(fill).toBeTruthy();
|
|
171
|
-
expect(contrastRatio(relativeLuminance(ring!), relativeLuminance(fill!))).toBeLessThan(
|
|
172
|
-
FOCUS_RING_MIN_CONTRAST,
|
|
173
|
-
);
|
|
174
|
-
}
|
|
145
|
+
it("an inset ring cannot guarantee contrast over every solid intent fill", () => {
|
|
146
|
+
const ratios = (["light", "dark"] as const).flatMap((scheme) =>
|
|
147
|
+
["accent", "error", "success", "warning"].map((intent) => {
|
|
148
|
+
const ring = normalizeToHex(themes[scheme].outlineColor)!;
|
|
149
|
+
const fill = normalizeToHex(themes[`${scheme}_${intent}_Button`].background)!;
|
|
150
|
+
return contrastRatio(relativeLuminance(ring), relativeLuminance(fill));
|
|
151
|
+
}),
|
|
152
|
+
);
|
|
153
|
+
expect(ratios.some((ratio) => ratio < FOCUS_RING_MIN_CONTRAST)).toBe(true);
|
|
175
154
|
});
|
|
176
155
|
});
|
|
177
156
|
|
package/src/theme/index.ts
CHANGED
|
@@ -22,12 +22,17 @@ export * from "./layoutTokens";
|
|
|
22
22
|
export * from "./Preset";
|
|
23
23
|
export type { Preset as PresetDefinition, IntentOverride } from "./preset.types";
|
|
24
24
|
export * from "./PresetContext";
|
|
25
|
+
export * from "./presetPreview";
|
|
25
26
|
export * from "./presets";
|
|
27
|
+
export * from "./presetStore";
|
|
28
|
+
export * from "./presetUserSync";
|
|
26
29
|
export * from "./radiusClass";
|
|
27
30
|
export * from "./radiusClassTable.generated";
|
|
28
31
|
export * from "./readableColor";
|
|
29
32
|
export * from "./recipes";
|
|
30
33
|
export * from "./resolveKnobs";
|
|
34
|
+
export * from "./schemeStore";
|
|
35
|
+
export * from "./scale/index";
|
|
31
36
|
export * from "./shared";
|
|
32
37
|
export * from "./themeValue";
|
|
33
38
|
export * from "./sizeRecipes";
|
|
@@ -40,4 +45,16 @@ export * from "./theme";
|
|
|
40
45
|
export * from "./Tint";
|
|
41
46
|
export * from "./useResolvedKnobs";
|
|
42
47
|
export * from "./useTheme";
|
|
48
|
+
export * from "./usePresetSwitch";
|
|
49
|
+
export * from "./useSchemePreference";
|
|
43
50
|
export * from "./useTouchSurface";
|
|
51
|
+
export * from "./overlaySurfaces";
|
|
52
|
+
|
|
53
|
+
export * from "./motion";
|
|
54
|
+
export {
|
|
55
|
+
animationConfig as cssAnimationConfig,
|
|
56
|
+
animationDurations,
|
|
57
|
+
motionStops,
|
|
58
|
+
type MotionStop,
|
|
59
|
+
} from "./animations/css";
|
|
60
|
+
export { animationConfig as nativeAnimationConfig } from "./animations/reactNative";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { contrastRatio, normalizeToHex, relativeLuminance } from "./colorRules";
|
|
3
|
+
import { createThemesBuilder } from "./createThemes";
|
|
4
|
+
import { defaultAccentTheme } from "./defaults/accent";
|
|
5
|
+
import { defaultBaseTheme } from "./defaults/base";
|
|
6
|
+
import { defaultBuilderOptions } from "./defaults/builderOptions";
|
|
7
|
+
|
|
8
|
+
const themes = createThemesBuilder(
|
|
9
|
+
defaultBaseTheme,
|
|
10
|
+
defaultAccentTheme,
|
|
11
|
+
defaultBuilderOptions,
|
|
12
|
+
).themes() as Record<string, Record<string, string>>;
|
|
13
|
+
|
|
14
|
+
const hex = (raw: string) => {
|
|
15
|
+
const value = normalizeToHex(raw);
|
|
16
|
+
if (!value) throw new Error(`not an opaque colour: ${raw}`);
|
|
17
|
+
return value;
|
|
18
|
+
};
|
|
19
|
+
const luminance = (raw: string) => relativeLuminance(hex(raw));
|
|
20
|
+
const ratio = (a: string, b: string) => contrastRatio(luminance(a), luminance(b));
|
|
21
|
+
const lightness = (raw: string) => {
|
|
22
|
+
const value = hex(raw);
|
|
23
|
+
const channels = [1, 3, 5].map((offset) => Number.parseInt(value.slice(offset, offset + 2), 16));
|
|
24
|
+
return ((Math.max(...channels) + Math.min(...channels)) / (2 * 255)) * 100;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe("FZ-7158 WP-b5: semantic Button fills hold their pale ink", () => {
|
|
28
|
+
for (const scheme of ["light", "dark"] as const) {
|
|
29
|
+
for (const intent of ["error", "warning", "success"] as const) {
|
|
30
|
+
it(`${scheme}_${intent}_Button darkens from its resting fill in both interaction states`, () => {
|
|
31
|
+
const theme = themes[`${scheme}_${intent}_Button`];
|
|
32
|
+
expect(lightness(theme.background)).toBeLessThan(60);
|
|
33
|
+
expect(luminance(theme.color)).toBeGreaterThan(0.5);
|
|
34
|
+
for (const fill of [theme.background, theme.backgroundHover, theme.backgroundPress]) {
|
|
35
|
+
expect(ratio(theme.color, fill)).toBeGreaterThanOrEqual(4.5);
|
|
36
|
+
}
|
|
37
|
+
expect(lightness(theme.backgroundHover)).toBeLessThan(lightness(theme.background));
|
|
38
|
+
expect(lightness(theme.backgroundPress)).toBeLessThan(lightness(theme.backgroundHover));
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
package/src/theme/knobs.ts
CHANGED
|
@@ -55,27 +55,6 @@ export const Elevation = {
|
|
|
55
55
|
} as const;
|
|
56
56
|
export type Elevation = (typeof Elevation)[keyof typeof Elevation];
|
|
57
57
|
|
|
58
|
-
export const Space = {
|
|
59
|
-
Small: "small",
|
|
60
|
-
Medium: "medium",
|
|
61
|
-
Large: "large",
|
|
62
|
-
} as const;
|
|
63
|
-
export type Space = (typeof Space)[keyof typeof Space];
|
|
64
|
-
|
|
65
|
-
export const Size = {
|
|
66
|
-
Small: "small",
|
|
67
|
-
Medium: "medium",
|
|
68
|
-
Large: "large",
|
|
69
|
-
} as const;
|
|
70
|
-
export type Size = (typeof Size)[keyof typeof Size];
|
|
71
|
-
|
|
72
|
-
/** Global density mode — compact steps space down one level; size is independent. */
|
|
73
|
-
export const Density = {
|
|
74
|
-
Comfortable: "comfortable",
|
|
75
|
-
Compact: "compact",
|
|
76
|
-
} as const;
|
|
77
|
-
export type Density = (typeof Density)[keyof typeof Density];
|
|
78
|
-
|
|
79
58
|
// ── House decisions (design-guidelines.md) ─────────────────
|
|
80
59
|
// Behavioral knobs with recommended defaults — not hard constraints.
|
|
81
60
|
|
|
@@ -191,17 +170,11 @@ export const FontWeight = {
|
|
|
191
170
|
export type FontWeight = (typeof FontWeight)[keyof typeof FontWeight];
|
|
192
171
|
|
|
193
172
|
/**
|
|
194
|
-
* Page-title type step (D-11
|
|
195
|
-
*
|
|
196
|
-
* `
|
|
197
|
-
*
|
|
198
|
-
* (Axiom
|
|
199
|
-
* clip. `display` ($10 = 64px) is the deliberate marketing/hero opt-in,
|
|
200
|
-
* reached through the `hero` preset or an explicit knob override; it is never
|
|
201
|
-
* the ambient default.
|
|
202
|
-
*
|
|
203
|
-
* Two values, not a token dial: a free size invites per-screen drift
|
|
204
|
-
* (Axiom 5), and the semantic level stays `headingLevel`'s job either way.
|
|
173
|
+
* Page-title type step (D-11 dial). `moderate` ($10, 40 px at type 15) is the
|
|
174
|
+
* product default: a page title is wayfinding, not content (Axiom 10).
|
|
175
|
+
* `display` ($12, 52 px) is the hero opt-in, through `heroPreset` or an
|
|
176
|
+
* explicit override. Two values, not a free size: a free size invites
|
|
177
|
+
* per-screen drift (Axiom 5), and the semantic level stays `headingLevel`'s.
|
|
205
178
|
*/
|
|
206
179
|
export const PageTitleScale = {
|
|
207
180
|
Moderate: "moderate",
|
|
@@ -211,9 +184,16 @@ export type PageTitleScale = (typeof PageTitleScale)[keyof typeof PageTitleScale
|
|
|
211
184
|
|
|
212
185
|
// ── Animation ──────────────────────────────────────────────
|
|
213
186
|
|
|
214
|
-
import type
|
|
187
|
+
import { motionStops, type MotionStop } from "./motionStops";
|
|
188
|
+
|
|
189
|
+
export type Animation = "none" | MotionStop;
|
|
215
190
|
|
|
216
|
-
export
|
|
191
|
+
export function normalizeAnimationKnob(value: unknown): Animation | undefined {
|
|
192
|
+
if (value === "none" || motionStops.includes(value as MotionStop)) return value as Animation;
|
|
193
|
+
if (value === "tooltip" || value === "gentle") return "slow";
|
|
194
|
+
if (value === "snappy") return "quickest";
|
|
195
|
+
return undefined;
|
|
196
|
+
}
|
|
217
197
|
|
|
218
198
|
// ── Interaction state overrides ────────────────────────────
|
|
219
199
|
// Structural knobs that can vary per interaction state.
|
|
@@ -223,7 +203,6 @@ export interface StateKnobs {
|
|
|
223
203
|
borderRadius?: BorderRadius;
|
|
224
204
|
borderWidth?: BorderWidth;
|
|
225
205
|
elevation?: Elevation;
|
|
226
|
-
space?: Space;
|
|
227
206
|
textAccent?: TextAccent;
|
|
228
207
|
headingFont?: HeadingFont;
|
|
229
208
|
bodyFont?: BodyFont;
|
|
@@ -250,10 +229,6 @@ export interface Knobs {
|
|
|
250
229
|
cornerSmoothing?: CornerSmoothing;
|
|
251
230
|
borderWidth: BorderWidth;
|
|
252
231
|
elevation: Elevation;
|
|
253
|
-
space: Space;
|
|
254
|
-
size: Size;
|
|
255
|
-
/** Propagating density mode. Default comfortable; compact steps space only. */
|
|
256
|
-
density: Density;
|
|
257
232
|
textAccent: TextAccent;
|
|
258
233
|
headingFont: HeadingFont;
|
|
259
234
|
bodyFont: BodyFont;
|
|
@@ -296,9 +271,6 @@ export const defaultKnobs: Knobs = {
|
|
|
296
271
|
cornerSmoothing: "round",
|
|
297
272
|
borderWidth: "medium",
|
|
298
273
|
elevation: "small",
|
|
299
|
-
space: "medium",
|
|
300
|
-
size: "medium",
|
|
301
|
-
density: "comfortable",
|
|
302
274
|
textAccent: "high",
|
|
303
275
|
headingFont: "sans-serif",
|
|
304
276
|
bodyFont: "sans-serif",
|
|
@@ -205,6 +205,17 @@ export function readingWidthStyle(
|
|
|
205
205
|
return { maxWidth: `${ch}ch`, width: "100%" };
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
// ── Form readable width ───────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* A form column's readable max width: wide enough for two FormGrid columns
|
|
212
|
+
* (FORM_GRID_COLLAPSE_WIDTH 480), narrow enough that a label, its field and
|
|
213
|
+
* the actions read as one group on a wide screen. Inside the 480-640 band.
|
|
214
|
+
*/
|
|
215
|
+
export const FORM_READABLE_WIDTH = 560;
|
|
216
|
+
export const FORM_READABLE_WIDTH_MIN = 480;
|
|
217
|
+
export const FORM_READABLE_WIDTH_MAX = 640;
|
|
218
|
+
|
|
208
219
|
// ── Min press target (DG-LAY-04) ──────────────────────────────────────────
|
|
209
220
|
|
|
210
221
|
/** Platform floor for interactive targets (Fluent/HIG web+iOS 44; M3 48 on Android). */
|
|
@@ -283,15 +294,18 @@ export const OVERLAY_ATTACH_GAP = 0;
|
|
|
283
294
|
// ── Semantic gaps (DG-LAY-03) ─────────────────────────────────────────────
|
|
284
295
|
|
|
285
296
|
/**
|
|
286
|
-
* Semantic spacing roles
|
|
297
|
+
* Semantic spacing roles (SP-GAP): `tight` → `knobProps.gapSm`,
|
|
287
298
|
* `within-group` → `knobProps.gap`, `between-groups` → `knobProps.gapLg`.
|
|
299
|
+
* Fixed `$space` keys; the active scale supplies their px.
|
|
288
300
|
*/
|
|
289
301
|
export function useSemanticGaps(): {
|
|
302
|
+
tight: { gap: string };
|
|
290
303
|
withinGroup: { gap: string };
|
|
291
304
|
betweenGroups: { gap: string };
|
|
292
305
|
} {
|
|
293
306
|
const { knobProps } = useResolvedKnobs();
|
|
294
307
|
return {
|
|
308
|
+
tight: knobProps.gapSm,
|
|
295
309
|
withinGroup: knobProps.gap,
|
|
296
310
|
betweenGroups: knobProps.gapLg,
|
|
297
311
|
};
|
|
@@ -4,7 +4,7 @@ import { TestProviders } from "@multiplatform.one/test-utils";
|
|
|
4
4
|
* viewport-driven `useLayoutSizeClass`, the pane budget `useMultiPane`, and
|
|
5
5
|
* the semantic gap roles `useSemanticGaps`. Locks down: widths resolve to the
|
|
6
6
|
* canonical size classes at mount, one matchMedia listener per breakpoint is
|
|
7
|
-
* registered and removed, and gap roles
|
|
7
|
+
* registered and removed, and gap roles are the fixed spacing roles.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { act, renderHook } from "@testing-library/react";
|
|
@@ -147,13 +147,17 @@ describe("useMultiPane", () => {
|
|
|
147
147
|
});
|
|
148
148
|
|
|
149
149
|
describe("useSemanticGaps", () => {
|
|
150
|
-
it("maps within-group to
|
|
150
|
+
it("maps tight to gapSm, within-group to gap and between-groups to gapLg", () => {
|
|
151
151
|
setViewportWidth(1000);
|
|
152
152
|
const { result: knobs } = renderHook(() => useResolvedKnobs(), { wrapper: TestProviders });
|
|
153
153
|
const { result } = renderHook(() => useSemanticGaps(), { wrapper: TestProviders });
|
|
154
|
+
expect(result.current.tight).toEqual(knobs.current.knobProps.gapSm);
|
|
154
155
|
expect(result.current.withinGroup).toEqual(knobs.current.knobProps.gap);
|
|
155
156
|
expect(result.current.betweenGroups).toEqual(knobs.current.knobProps.gapLg);
|
|
156
|
-
|
|
157
|
-
|
|
157
|
+
expect(result.current).toEqual({
|
|
158
|
+
tight: { gap: "$2" },
|
|
159
|
+
withinGroup: { gap: "$3" },
|
|
160
|
+
betweenGroups: { gap: "$5" },
|
|
161
|
+
});
|
|
158
162
|
});
|
|
159
163
|
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { TamaguiInternalConfig } from "@tamagui/web";
|
|
2
|
+
|
|
3
|
+
export type TokensGlobal = { __tamaguiTokensMerged?: unknown };
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The `$`-prefixed and bare token lookup `getTokenValue("$4", "size")` reads.
|
|
7
|
+
* `createTamagui` publishes it as a global beside the config, so installing a
|
|
8
|
+
* config without it would leave token reads on the previous config's values.
|
|
9
|
+
*/
|
|
10
|
+
export function mergedTokens(
|
|
11
|
+
config: TamaguiInternalConfig,
|
|
12
|
+
): Record<string, Record<string, unknown>> {
|
|
13
|
+
const merged: Record<string, Record<string, unknown>> = {};
|
|
14
|
+
const tokens = config.tokens as unknown as Record<string, Record<string, unknown>> | undefined;
|
|
15
|
+
const parsed = config.tokensParsed as unknown as Record<string, Record<string, unknown>>;
|
|
16
|
+
for (const category of Object.keys(parsed)) {
|
|
17
|
+
merged[category] = { ...tokens?.[category], ...parsed[category] };
|
|
18
|
+
}
|
|
19
|
+
return merged;
|
|
20
|
+
}
|