@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,15 +4,10 @@ import { useTouchSurface } from "./useTouchSurface";
|
|
|
4
4
|
import { defaultKnobs, type Knobs } from "./knobs";
|
|
5
5
|
import type { IntentOverride } from "./preset.types";
|
|
6
6
|
import type { KnobPropsOverride, ResolvedKnobs } from "./recipes";
|
|
7
|
-
import {
|
|
7
|
+
import { resolveKnobs } from "./resolveKnobs";
|
|
8
8
|
import { usePresetContext } from "./PresetContext";
|
|
9
9
|
import { defaultIntents } from "./intents";
|
|
10
|
-
import {
|
|
11
|
-
clampSurfaceDensity,
|
|
12
|
-
clampSurfaceSize,
|
|
13
|
-
SurfaceContext,
|
|
14
|
-
type SurfaceIntent,
|
|
15
|
-
} from "./Surface";
|
|
10
|
+
import { clampSurfaceSize, SurfaceContext } from "./Surface";
|
|
16
11
|
|
|
17
12
|
export const IntentContext = createContext<string | null>(null);
|
|
18
13
|
|
|
@@ -46,61 +41,23 @@ function usePrefersReducedMotion(): boolean {
|
|
|
46
41
|
return useSyncExternalStore(subscribeReducedMotion, readReducedMotion, serverReducedMotion);
|
|
47
42
|
}
|
|
48
43
|
|
|
49
|
-
function applySurfaceToKnobs(
|
|
50
|
-
knobs: Knobs,
|
|
51
|
-
surface: SurfaceIntent,
|
|
52
|
-
opts: { sizeOverride: boolean; densityOverride: boolean },
|
|
53
|
-
): Knobs {
|
|
54
|
-
let next = knobs;
|
|
55
|
-
if (surface.size !== "unset") {
|
|
56
|
-
if (!opts.sizeOverride) {
|
|
57
|
-
next = { ...next, size: surface.size };
|
|
58
|
-
} else {
|
|
59
|
-
const size = clampSurfaceSize(next.size, surface.size);
|
|
60
|
-
if (size !== "unset" && size !== next.size) {
|
|
61
|
-
next = { ...next, size };
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
if (surface.density !== "unset") {
|
|
66
|
-
if (!opts.densityOverride) {
|
|
67
|
-
next = { ...next, density: surface.density };
|
|
68
|
-
} else {
|
|
69
|
-
const density = clampSurfaceDensity(next.density, surface.density);
|
|
70
|
-
if (density !== "unset" && density !== next.density) {
|
|
71
|
-
next = { ...next, density };
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return next;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
44
|
export interface UseResolvedKnobsOptions {
|
|
79
45
|
intent?: string;
|
|
80
46
|
component?: string;
|
|
81
47
|
override?: KnobPropsOverride;
|
|
82
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* The control size token (Tamagui's own `size`, "$3"). Clamped to the
|
|
50
|
+
* nearest `<Surface size>` ceiling (LC-78); omitted = the Surface's token,
|
|
51
|
+
* else `$true`.
|
|
52
|
+
*/
|
|
83
53
|
size?: SizeTokens;
|
|
84
54
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* `true` forces compact density; `false` forces comfortable.
|
|
88
|
-
* Then re-clamped to the parent Surface ceiling (LC-68).
|
|
89
|
-
* When omitted, the density knob / Surface controls compact vs comfortable.
|
|
55
|
+
* Whether an ancestor already paints a surface box. Omitted = the nearest
|
|
56
|
+
* `<Surface painted>`; overlay frames pass `false` because they float.
|
|
90
57
|
*/
|
|
91
|
-
|
|
58
|
+
painted?: boolean;
|
|
92
59
|
}
|
|
93
60
|
|
|
94
|
-
const sizeTokenToKnobSize: Record<string, Knobs["size"]> = {
|
|
95
|
-
$1: "small",
|
|
96
|
-
$2: "small",
|
|
97
|
-
$3: "small",
|
|
98
|
-
$4: "medium",
|
|
99
|
-
$5: "large",
|
|
100
|
-
$6: "large",
|
|
101
|
-
$true: "medium",
|
|
102
|
-
};
|
|
103
|
-
|
|
104
61
|
export const solidButtonIntents = new Set(["error", "warning", "success"]);
|
|
105
62
|
|
|
106
63
|
/**
|
|
@@ -118,12 +75,11 @@ export const solidPaletteThemes = new Set(["accent"]);
|
|
|
118
75
|
* 1. Base knobs from nearest <Preset> context
|
|
119
76
|
* 2. Merge intent-level overrides (if intent provided or from context)
|
|
120
77
|
* 3. Merge intent+component overrides (if both provided)
|
|
121
|
-
* 4.
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* 9. Call resolveKnobs(mergedKnobs, override, scheme) — override merges last
|
|
78
|
+
* 4. Size token: the `size` option clamped to the Surface ceiling (LC-78);
|
|
79
|
+
* `painted` from the option, else the Surface
|
|
80
|
+
* 5. OS reduced-motion forces animation="none"
|
|
81
|
+
* 6. Call resolveKnobs(mergedKnobs, override, scheme, { touch, sizeToken, painted })
|
|
82
|
+
* — override merges last
|
|
127
83
|
*/
|
|
128
84
|
export function useResolvedKnobs(options?: UseResolvedKnobsOptions): ResolvedKnobs {
|
|
129
85
|
const touch = useTouchSurface();
|
|
@@ -134,13 +90,12 @@ export function useResolvedKnobs(options?: UseResolvedKnobsOptions): ResolvedKno
|
|
|
134
90
|
const scheme = themeName?.startsWith("dark") ? "dark" : "light";
|
|
135
91
|
const surfaceCtx = SurfaceContext.useStyledContext();
|
|
136
92
|
const surfaceSize = surfaceCtx?.size ?? "unset";
|
|
137
|
-
const
|
|
93
|
+
const painted = options?.painted ?? surfaceCtx?.painted ?? false;
|
|
138
94
|
|
|
139
95
|
const intent = options?.intent ?? contextIntent ?? undefined;
|
|
140
96
|
const component = options?.component;
|
|
141
97
|
const override = options?.override;
|
|
142
98
|
const sizeOverride = options?.size;
|
|
143
|
-
const compact = options?.compact;
|
|
144
99
|
|
|
145
100
|
return useMemo(() => {
|
|
146
101
|
const baseKnobs: Knobs = presetCtx
|
|
@@ -171,37 +126,16 @@ export function useResolvedKnobs(options?: UseResolvedKnobsOptions): ResolvedKno
|
|
|
171
126
|
}
|
|
172
127
|
}
|
|
173
128
|
|
|
174
|
-
if (sizeOverride) {
|
|
175
|
-
const sizeStr = String(sizeOverride);
|
|
176
|
-
const knobSize = sizeTokenToKnobSize[sizeStr];
|
|
177
|
-
if (knobSize) {
|
|
178
|
-
merged = { ...merged, size: knobSize };
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const surfaceIntent: SurfaceIntent = { size: surfaceSize, density: surfaceDensity };
|
|
183
|
-
merged = applySurfaceToKnobs(merged, surfaceIntent, {
|
|
184
|
-
sizeOverride: Boolean(sizeOverride),
|
|
185
|
-
densityOverride: compact !== undefined,
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
const spaceBeforeDensity = merged.space;
|
|
189
|
-
merged = applyDensity(merged, compact);
|
|
190
|
-
merged = applySurfaceToKnobs(merged, surfaceIntent, {
|
|
191
|
-
sizeOverride: true,
|
|
192
|
-
densityOverride: true,
|
|
193
|
-
});
|
|
194
|
-
if (merged.density === "compact") {
|
|
195
|
-
merged = { ...merged, space: compactSpaceMap[spaceBeforeDensity] };
|
|
196
|
-
} else {
|
|
197
|
-
merged = { ...merged, space: spaceBeforeDensity };
|
|
198
|
-
}
|
|
199
|
-
|
|
200
129
|
if (prefersReducedMotion) {
|
|
201
130
|
merged = { ...merged, animation: "none" };
|
|
202
131
|
}
|
|
203
132
|
|
|
204
|
-
const
|
|
133
|
+
const sizeToken = clampSurfaceSize(sizeOverride ?? "unset", surfaceSize);
|
|
134
|
+
const resolved = resolveKnobs(merged, override, scheme, {
|
|
135
|
+
touch,
|
|
136
|
+
sizeToken: sizeToken === "unset" ? undefined : String(sizeToken),
|
|
137
|
+
painted,
|
|
138
|
+
});
|
|
205
139
|
|
|
206
140
|
if (intent && component === "Button" && solidButtonIntents.has(intent)) {
|
|
207
141
|
const labelColor = merged.fillStyle === "filled" ? "$color" : "$color11";
|
|
@@ -221,10 +155,9 @@ export function useResolvedKnobs(options?: UseResolvedKnobsOptions): ResolvedKno
|
|
|
221
155
|
component,
|
|
222
156
|
override,
|
|
223
157
|
sizeOverride,
|
|
224
|
-
compact,
|
|
225
158
|
prefersReducedMotion,
|
|
226
159
|
surfaceSize,
|
|
227
|
-
|
|
160
|
+
painted,
|
|
228
161
|
scheme,
|
|
229
162
|
touch,
|
|
230
163
|
]);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { TestProviders } from "@multiplatform.one/test-utils";
|
|
2
2
|
/**
|
|
3
3
|
* useResolvedKnobs behavior specs — the resolution rules consumers feel but
|
|
4
|
-
* that aren't covered by the pure resolveKnobs specs: Tamagui size
|
|
5
|
-
*
|
|
4
|
+
* that aren't covered by the pure resolveKnobs specs: the Tamagui size token
|
|
5
|
+
* passing through as the control token, OS reduced-motion forcing animation off, the legacy
|
|
6
6
|
* addListener matchMedia branch, and the semantic-intent Button label color
|
|
7
7
|
* rule (filled binds $color, outlined pins $color11).
|
|
8
8
|
*/
|
|
@@ -79,22 +79,23 @@ function makePreset(overrides: Partial<PresetType> = {}): PresetType {
|
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
describe("size token
|
|
83
|
-
it.each([
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
82
|
+
describe("size token", () => {
|
|
83
|
+
it.each(["$1", "$2", "$3", "$4", "$5", "$6", "$true"] as const)(
|
|
84
|
+
"the Tamagui size %s is the control token, unmapped",
|
|
85
|
+
(token) => {
|
|
86
|
+
const { result } = renderHook(() => useResolvedKnobs({ size: token }));
|
|
87
|
+
expect(result.current.knobProps.sizeToken).toBe(token);
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
it("defaults to $true with no size option and no Surface", () => {
|
|
92
|
+
const { result } = renderHook(() => useResolvedKnobs());
|
|
93
|
+
expect(result.current.knobProps.sizeToken).toBe("$true");
|
|
93
94
|
});
|
|
94
95
|
|
|
95
|
-
it("
|
|
96
|
-
const { result } = renderHook(() => useResolvedKnobs({ size:
|
|
97
|
-
expect(result.current.knobProps.
|
|
96
|
+
it("a numeric size is not a token and resolves $true", () => {
|
|
97
|
+
const { result } = renderHook(() => useResolvedKnobs({ size: 40 as never }));
|
|
98
|
+
expect(result.current.knobProps.sizeToken).toBe("$true");
|
|
98
99
|
});
|
|
99
100
|
});
|
|
100
101
|
|
|
@@ -176,19 +177,13 @@ describe("intent + component knob merging", () => {
|
|
|
176
177
|
);
|
|
177
178
|
});
|
|
178
179
|
|
|
179
|
-
it("compact
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
);
|
|
187
|
-
expect(compact.result.current.knobProps.control.height).toBe(
|
|
188
|
-
comfortable.result.current.knobProps.control.height,
|
|
189
|
-
);
|
|
190
|
-
expect(compact.result.current.knobProps.density).toBe("compact");
|
|
191
|
-
expect(comfortable.result.current.knobProps.gap).toEqual({ gap: "$4" });
|
|
192
|
-
expect(compact.result.current.knobProps.gap).toEqual({ gap: "$2" });
|
|
180
|
+
it("the options carry no compact switch: a smaller size token is the only per-site control", () => {
|
|
181
|
+
const options: Parameters<typeof useResolvedKnobs>[0] = {
|
|
182
|
+
// @ts-expect-error density is folded into the preset's token scale
|
|
183
|
+
compact: true,
|
|
184
|
+
};
|
|
185
|
+
const { result } = renderHook(() => useResolvedKnobs(options));
|
|
186
|
+
expect(result.current.knobProps.gap).toEqual({ gap: "$3" });
|
|
187
|
+
expect(result.current.knobProps).not.toHaveProperty("density");
|
|
193
188
|
});
|
|
194
189
|
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useEffect, useSyncExternalStore } from "react";
|
|
2
|
+
import { Appearance, useColorScheme } from "react-native";
|
|
3
|
+
import { useDidFinishSSR } from "tamagui";
|
|
4
|
+
import { isWeb } from "@multiplatform.one/platform";
|
|
5
|
+
import { getStoredScheme, setStoredScheme, subscribeStoredScheme } from "./schemeStore";
|
|
6
|
+
import type { ThemeScheme } from "./useTheme";
|
|
7
|
+
|
|
8
|
+
export interface SchemePreference {
|
|
9
|
+
setting: ThemeScheme;
|
|
10
|
+
value: "light" | "dark";
|
|
11
|
+
set(scheme: ThemeScheme): void;
|
|
12
|
+
}
|
|
13
|
+
const serverScheme = (): ThemeScheme => "system";
|
|
14
|
+
|
|
15
|
+
export function useSchemePreference(): SchemePreference {
|
|
16
|
+
const setting = useSyncExternalStore(subscribeStoredScheme, getStoredScheme, serverScheme);
|
|
17
|
+
const os = useColorScheme();
|
|
18
|
+
const hydrated = useDidFinishSSR();
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (!isWeb) Appearance.setColorScheme(setting === "system" ? "unspecified" : setting);
|
|
21
|
+
}, [setting]);
|
|
22
|
+
return {
|
|
23
|
+
setting: hydrated ? setting : "system",
|
|
24
|
+
value: !hydrated
|
|
25
|
+
? "light"
|
|
26
|
+
: setting === "system"
|
|
27
|
+
? os === "dark"
|
|
28
|
+
? "dark"
|
|
29
|
+
: "light"
|
|
30
|
+
: setting,
|
|
31
|
+
set: setStoredScheme,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { act, cleanup, renderHook } from "@testing-library/react";
|
|
2
|
+
import { afterEach, expect, it, vi } from "vitest";
|
|
3
|
+
import { useSyncExternalStore } from "react";
|
|
4
|
+
|
|
5
|
+
const os = vi.hoisted(() => ({
|
|
6
|
+
value: "light",
|
|
7
|
+
forced: null as string | null,
|
|
8
|
+
listeners: new Set<() => void>(),
|
|
9
|
+
}));
|
|
10
|
+
vi.mock("@multiplatform.one/platform", () => ({ isWeb: false }));
|
|
11
|
+
vi.mock("@multiplatform.one/store", () => ({
|
|
12
|
+
storage: { getItem: () => null, setItem: () => {} },
|
|
13
|
+
}));
|
|
14
|
+
vi.mock("tamagui", () => ({ useDidFinishSSR: () => true }));
|
|
15
|
+
vi.mock("react-native", () => ({
|
|
16
|
+
useColorScheme: () =>
|
|
17
|
+
useSyncExternalStore(
|
|
18
|
+
(l) => {
|
|
19
|
+
os.listeners.add(l);
|
|
20
|
+
return () => {
|
|
21
|
+
os.listeners.delete(l);
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
() => os.forced ?? os.value,
|
|
25
|
+
),
|
|
26
|
+
Appearance: {
|
|
27
|
+
setColorScheme: (value: string | null) => {
|
|
28
|
+
os.forced = value === "unspecified" ? null : value;
|
|
29
|
+
os.listeners.forEach((l) => l());
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
import { useSchemePreference } from "./useSchemePreference.native";
|
|
34
|
+
afterEach(cleanup);
|
|
35
|
+
|
|
36
|
+
it("shares selection, clears the native override for System and follows subsequent OS changes", () => {
|
|
37
|
+
const first = renderHook(useSchemePreference);
|
|
38
|
+
const second = renderHook(useSchemePreference);
|
|
39
|
+
act(() => first.result.current.set("dark"));
|
|
40
|
+
expect(second.result.current.value).toBe("dark");
|
|
41
|
+
expect(os.forced).toBe("dark");
|
|
42
|
+
act(() => first.result.current.set("system"));
|
|
43
|
+
expect(os.forced).toBeNull();
|
|
44
|
+
expect(second.result.current.value).toBe("light");
|
|
45
|
+
act(() => {
|
|
46
|
+
os.value = "dark";
|
|
47
|
+
os.listeners.forEach((l) => l());
|
|
48
|
+
});
|
|
49
|
+
expect(second.result.current.value).toBe("dark");
|
|
50
|
+
expect(second.result.current.setting).toBe("system");
|
|
51
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useSyncExternalStore } from "react";
|
|
2
|
+
import { useDidFinishSSR } from "tamagui";
|
|
3
|
+
import { getStoredScheme, setStoredScheme, subscribeStoredScheme } from "./schemeStore";
|
|
4
|
+
import type { ThemeScheme } from "./useTheme";
|
|
5
|
+
|
|
6
|
+
export interface SchemePreference {
|
|
7
|
+
setting: ThemeScheme;
|
|
8
|
+
value: "light" | "dark";
|
|
9
|
+
set(scheme: ThemeScheme): void;
|
|
10
|
+
}
|
|
11
|
+
const serverScheme = (): ThemeScheme => "system";
|
|
12
|
+
const serverDark = () => false;
|
|
13
|
+
const prefersDark = () =>
|
|
14
|
+
typeof window !== "undefined" &&
|
|
15
|
+
window.matchMedia?.("(prefers-color-scheme: dark)").matches === true;
|
|
16
|
+
function subscribeOs(listener: () => void) {
|
|
17
|
+
const query = window.matchMedia?.("(prefers-color-scheme: dark)");
|
|
18
|
+
if (!query) return () => {};
|
|
19
|
+
query.addEventListener("change", listener);
|
|
20
|
+
return () => query.removeEventListener("change", listener);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function useSchemePreference(): SchemePreference {
|
|
24
|
+
const setting = useSyncExternalStore(subscribeStoredScheme, getStoredScheme, serverScheme);
|
|
25
|
+
const dark = useSyncExternalStore(subscribeOs, prefersDark, serverDark);
|
|
26
|
+
const hydrated = useDidFinishSSR();
|
|
27
|
+
return {
|
|
28
|
+
setting: hydrated ? setting : "system",
|
|
29
|
+
value: !hydrated ? "light" : setting === "system" ? (dark ? "dark" : "light") : setting,
|
|
30
|
+
set: setStoredScheme,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { act, cleanup, renderHook } from "@testing-library/react";
|
|
2
|
+
import { afterEach, expect, it, vi } from "vitest";
|
|
3
|
+
import { useSchemePreference } from "./useSchemePreference";
|
|
4
|
+
import { setStoredScheme } from "./schemeStore";
|
|
5
|
+
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
cleanup();
|
|
8
|
+
vi.unstubAllGlobals();
|
|
9
|
+
});
|
|
10
|
+
it("follows browser OS changes only while System is selected", () => {
|
|
11
|
+
let dark = false;
|
|
12
|
+
const listeners = new Set<() => void>();
|
|
13
|
+
vi.stubGlobal("matchMedia", () => ({
|
|
14
|
+
get matches() {
|
|
15
|
+
return dark;
|
|
16
|
+
},
|
|
17
|
+
addEventListener: (_event: string, listener: () => void) => listeners.add(listener),
|
|
18
|
+
removeEventListener: (_event: string, listener: () => void) => listeners.delete(listener),
|
|
19
|
+
}));
|
|
20
|
+
setStoredScheme("system");
|
|
21
|
+
const hook = renderHook(useSchemePreference);
|
|
22
|
+
expect(hook.result.current.value).toBe("light");
|
|
23
|
+
act(() => {
|
|
24
|
+
dark = true;
|
|
25
|
+
listeners.forEach((l) => l());
|
|
26
|
+
});
|
|
27
|
+
expect(hook.result.current.value).toBe("dark");
|
|
28
|
+
act(() => hook.result.current.set("light"));
|
|
29
|
+
expect(hook.result.current.value).toBe("light");
|
|
30
|
+
act(() => hook.result.current.set("system"));
|
|
31
|
+
expect(hook.result.current.value).toBe("dark");
|
|
32
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { act, cleanup, renderHook } from "@testing-library/react";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
|
|
4
|
+
const { items } = vi.hoisted(() => ({ items: new Map<string, string>() }));
|
|
5
|
+
vi.mock("@multiplatform.one/store", () => ({
|
|
6
|
+
storage: {
|
|
7
|
+
getItem: (key: string) => items.get(key) ?? null,
|
|
8
|
+
setItem: (key: string, value: string) => {
|
|
9
|
+
items.set(key, value);
|
|
10
|
+
},
|
|
11
|
+
removeItem: (key: string) => {
|
|
12
|
+
items.delete(key);
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
vi.mock("./shared", async () => {
|
|
18
|
+
const { densePreset, roomyPreset } = await import("./presets");
|
|
19
|
+
return {
|
|
20
|
+
getPreset: (name: string) =>
|
|
21
|
+
name === "dense" ? densePreset : name === "roomy" ? roomyPreset : undefined,
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
items.clear();
|
|
27
|
+
vi.resetModules();
|
|
28
|
+
});
|
|
29
|
+
afterEach(cleanup);
|
|
30
|
+
|
|
31
|
+
async function nativeHooks() {
|
|
32
|
+
const hooks = await import("./useTheme.native");
|
|
33
|
+
const store = await import("./presetStore");
|
|
34
|
+
return { ...hooks, ...store };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe("native useTheme persistence", () => {
|
|
38
|
+
it("persists the active preset and hydrates it after an in-memory reset", async () => {
|
|
39
|
+
const first = await nativeHooks();
|
|
40
|
+
const { result, unmount } = renderHook(() => first.useTheme());
|
|
41
|
+
act(() => result.current[3]("dense"));
|
|
42
|
+
await vi.waitFor(() => expect(items.get("mp.preset")).toBe("dense"));
|
|
43
|
+
expect(result.current[4]).toBe("dense");
|
|
44
|
+
expect(items.has("mp.preset.by-user")).toBe(false);
|
|
45
|
+
unmount();
|
|
46
|
+
vi.resetModules();
|
|
47
|
+
const fresh = await nativeHooks();
|
|
48
|
+
await fresh.hydrateStoredPreset();
|
|
49
|
+
expect(fresh.getStoredPreset()).toEqual({ presetName: "dense", overrides: {}, hydrated: true });
|
|
50
|
+
expect(renderHook(() => fresh.useTheme()).result.current[4]).toBe("dense");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("records the user's choice only when options are supplied", async () => {
|
|
54
|
+
const hooks = await nativeHooks();
|
|
55
|
+
const { result } = renderHook(() => hooks.useTheme());
|
|
56
|
+
act(() => result.current[3]("roomy", { userId: "u1" }));
|
|
57
|
+
await vi.waitFor(() => expect(items.get("mp.preset")).toBe("roomy"));
|
|
58
|
+
expect(hooks.getPresetChoices()).toEqual({ users: { u1: "roomy" }, owner: "u1" });
|
|
59
|
+
await vi.waitFor(() => expect(items.has("mp.preset.by-user")).toBe(true));
|
|
60
|
+
expect(JSON.parse(items.get("mp.preset.by-user")!)).toEqual(hooks.getPresetChoices());
|
|
61
|
+
act(() => result.current[3]("dense", {}));
|
|
62
|
+
expect(hooks.getPresetChoices()).toEqual({
|
|
63
|
+
device: "dense",
|
|
64
|
+
users: { u1: "roomy" },
|
|
65
|
+
owner: undefined,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import { useCallback,
|
|
1
|
+
import { useCallback, useMemo, useSyncExternalStore } from "react";
|
|
2
|
+
import { computeOverrides } from "./cookies";
|
|
2
3
|
import { type Knobs, defaultKnobs } from "./knobs";
|
|
4
|
+
import {
|
|
5
|
+
getStoredPreset,
|
|
6
|
+
recordPresetChoice,
|
|
7
|
+
setStoredOverrides,
|
|
8
|
+
setStoredPreset,
|
|
9
|
+
subscribeStoredPreset,
|
|
10
|
+
} from "./presetStore";
|
|
3
11
|
import { getPreset } from "./shared";
|
|
4
12
|
|
|
5
13
|
// ---------------------------------------------------------------------------
|
|
@@ -9,41 +17,58 @@ import { getPreset } from "./shared";
|
|
|
9
17
|
export type ThemeScheme = "system" | "dark" | "light";
|
|
10
18
|
|
|
11
19
|
// ---------------------------------------------------------------------------
|
|
12
|
-
// useTheme hook — knob-aware (native variant
|
|
20
|
+
// useTheme hook — knob-aware (native variant persists through the preset store)
|
|
13
21
|
// ---------------------------------------------------------------------------
|
|
14
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Same tuple as the web hook. State lives in the shared preset store
|
|
25
|
+
* (`presetStore.ts`), persisted to `@multiplatform.one/store` storage under
|
|
26
|
+
* `mp.preset` / `mp.ov`, so every caller and the ThemeProvider see one preset
|
|
27
|
+
* and it survives a relaunch. The fourth element switches presets; supplying
|
|
28
|
+
* options also records the user's (or signed-out device's) choice.
|
|
29
|
+
*/
|
|
15
30
|
export function useTheme(): [
|
|
16
31
|
Knobs,
|
|
17
32
|
(knobs?: Partial<Knobs>) => void,
|
|
18
33
|
boolean,
|
|
19
|
-
(presetName: string) => void,
|
|
34
|
+
(presetName: string, options?: { userId?: string | null }) => void,
|
|
20
35
|
string | undefined,
|
|
21
36
|
] {
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
37
|
+
const stored = useSyncExternalStore(subscribeStoredPreset, getStoredPreset, getStoredPreset);
|
|
38
|
+
const presetName = stored.presetName;
|
|
39
|
+
const baseKnobs = (presetName ? getPreset(presetName)?.knobs : undefined) ?? defaultKnobs;
|
|
40
|
+
|
|
41
|
+
const knobs = useMemo<Knobs>(
|
|
42
|
+
() => ({ ...baseKnobs, ...stored.overrides }),
|
|
43
|
+
[baseKnobs, stored.overrides],
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const setKnobs = useCallback(
|
|
47
|
+
(partial?: Partial<Knobs>): void => {
|
|
48
|
+
if (!partial) return;
|
|
49
|
+
let changed = false;
|
|
50
|
+
const next = { ...knobs };
|
|
30
51
|
for (const key of Object.keys(partial) as Array<keyof Knobs>) {
|
|
31
|
-
const
|
|
32
|
-
if (
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
const value = partial[key];
|
|
53
|
+
if (value === undefined) continue;
|
|
54
|
+
const differs =
|
|
55
|
+
typeof value === "object" && value !== null
|
|
56
|
+
? JSON.stringify(value) !== JSON.stringify(knobs[key])
|
|
57
|
+
: value !== knobs[key];
|
|
58
|
+
if (differs) {
|
|
59
|
+
(next as Record<string, unknown>)[key] = value;
|
|
60
|
+
changed = true;
|
|
35
61
|
}
|
|
36
62
|
}
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
63
|
+
if (changed) setStoredOverrides(computeOverrides(baseKnobs, next));
|
|
64
|
+
},
|
|
65
|
+
[knobs, baseKnobs],
|
|
66
|
+
);
|
|
40
67
|
|
|
41
|
-
const setPresetFn = useCallback((newPresetName: string) => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
setActivePreset(newPresetName);
|
|
45
|
-
setLocalKnobs(knobs);
|
|
68
|
+
const setPresetFn = useCallback((newPresetName: string, options?: { userId?: string | null }) => {
|
|
69
|
+
if (options) recordPresetChoice(newPresetName, options.userId);
|
|
70
|
+
setStoredPreset(newPresetName);
|
|
46
71
|
}, []);
|
|
47
72
|
|
|
48
|
-
return [
|
|
73
|
+
return [knobs, setKnobs, presetName != null, setPresetFn, presetName];
|
|
49
74
|
}
|
package/src/theme/useTheme.ts
CHANGED
|
@@ -5,11 +5,13 @@ import {
|
|
|
5
5
|
cookiePreset,
|
|
6
6
|
computeOverrides,
|
|
7
7
|
getCookieWatchList,
|
|
8
|
+
isKnobKey,
|
|
8
9
|
readOverridesCookie,
|
|
9
10
|
readPresetCookie,
|
|
10
11
|
writeOverridesCookie,
|
|
11
12
|
} from "./cookies";
|
|
12
13
|
import { type Knobs, defaultKnobs } from "./knobs";
|
|
14
|
+
import { recordPresetChoice } from "./presetStore";
|
|
13
15
|
import { getPreset } from "./shared";
|
|
14
16
|
|
|
15
17
|
// ---------------------------------------------------------------------------
|
|
@@ -34,7 +36,7 @@ export type ThemeScheme = "system" | "dark" | "light";
|
|
|
34
36
|
* Knobs, // resolved knobs
|
|
35
37
|
* (knobs?: Partial<Knobs>) => void, // merge partial knob updates
|
|
36
38
|
* boolean, // true if initialized from cookie
|
|
37
|
-
* (presetName: string) => void, // switch preset
|
|
39
|
+
* (presetName: string, options?: { userId?: string | null }) => void, // switch preset; options record a choice
|
|
38
40
|
* string | undefined, // active preset name
|
|
39
41
|
* ]
|
|
40
42
|
* ```
|
|
@@ -47,7 +49,7 @@ export function useTheme(): [
|
|
|
47
49
|
Knobs,
|
|
48
50
|
(knobs?: Partial<Knobs>) => void,
|
|
49
51
|
boolean,
|
|
50
|
-
(presetName: string) => void,
|
|
52
|
+
(presetName: string, options?: { userId?: string | null }) => void,
|
|
51
53
|
string | undefined,
|
|
52
54
|
] {
|
|
53
55
|
const isCookieDisabled =
|
|
@@ -132,7 +134,7 @@ export function useTheme(): [
|
|
|
132
134
|
let hasChanges = false;
|
|
133
135
|
const updated = { ...current };
|
|
134
136
|
|
|
135
|
-
for (const key of Object.keys(partial)
|
|
137
|
+
for (const key of Object.keys(partial).filter(isKnobKey)) {
|
|
136
138
|
const newValue = partial[key];
|
|
137
139
|
if (newValue === undefined) continue;
|
|
138
140
|
const oldValue = current[key];
|
|
@@ -177,7 +179,8 @@ export function useTheme(): [
|
|
|
177
179
|
// setPreset — switch the active preset (resets overrides to empty)
|
|
178
180
|
// -------------------------------------------------------------------------
|
|
179
181
|
const setPresetFn = useCallback(
|
|
180
|
-
(newPresetName: string) => {
|
|
182
|
+
(newPresetName: string, options?: { userId?: string | null }) => {
|
|
183
|
+
if (options) recordPresetChoice(newPresetName, options.userId);
|
|
181
184
|
const preset = getPreset(newPresetName);
|
|
182
185
|
const knobs = preset?.knobs ?? defaultKnobs;
|
|
183
186
|
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* Tamagui token steps defined by the knob system. Off-scale values indicate
|
|
6
6
|
* styles that bypassed the constraint system.
|
|
7
7
|
*
|
|
8
|
-
* Token values are
|
|
8
|
+
* Token values are the active generated scale (`globalThis.__MPO_SCALE__`,
|
|
9
|
+
* else the default `generateScale`) + resolveKnobs.ts mappings.
|
|
9
10
|
* Run this in-browser via Playwright's page.evaluate(runConstraintAudit).
|
|
10
11
|
*/
|
|
11
12
|
export interface PropertyAudit {
|
|
@@ -76,13 +77,13 @@ export interface AuditReport {
|
|
|
76
77
|
elements: ElementAudit[];
|
|
77
78
|
}
|
|
78
79
|
/**
|
|
79
|
-
*
|
|
80
|
-
* Source:
|
|
80
|
+
* Within-group gap roles → pixel value on the scale.
|
|
81
|
+
* Source: spacingRoles in resolveKnobs.ts (gapSm $2, gap $3).
|
|
81
82
|
*/
|
|
82
83
|
export declare const GAP_KNOB: Record<string, number>;
|
|
83
84
|
/**
|
|
84
|
-
*
|
|
85
|
-
* Source:
|
|
85
|
+
* Between-groups gap role → pixel value on the scale.
|
|
86
|
+
* Source: spacingRoles in resolveKnobs.ts (gapLg $5).
|
|
86
87
|
*/
|
|
87
88
|
export declare const GAP_LG_KNOB: Record<string, number>;
|
|
88
89
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constraintAudit.d.ts","sourceRoot":"","sources":["../../src/audit/constraintAudit.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"constraintAudit.d.ts","sourceRoot":"","sources":["../../src/audit/constraintAudit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,KAAK,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,8BAA8B;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,6CAA6C;IAC7C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6EAA6E;IAC7E,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,mBAAmB,CAAC,EAAE,aAAa,CAAC;IACpC,oBAAoB,CAAC,EAAE,aAAa,CAAC;IACrC,uBAAuB,CAAC,EAAE,aAAa,CAAC;IACxC,sBAAsB,CAAC,EAAE,aAAa,CAAC;IACvC,WAAW,CAAC,EAAE,aAAa,CAAC;IAC5B,GAAG,CAAC,EAAE,aAAa,CAAC;IACpB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,YAAY,CAAC,EAAE,aAAa,CAAC;IAC7B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,WAAW,CAAC,EAAE,aAAa,CAAC;IAC5B,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,UAAU,CAAC,EAAE,iBAAiB,CAAC;IAC/B,6EAA6E;IAC7E,WAAW,CAAC,EAAE,iBAAiB,CAAC;IAChC,uCAAuC;IACvC,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE;QACV,YAAY,EAAE,MAAM,CAAC;QACrB,WAAW,EAAE,MAAM,CAAC;QACpB,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;CACH;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,YAAY,CAAC;IACtB,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B,sDAAsD;IACtD,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B;AA+DD;;;GAGG;AACH,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAG3C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAE9C,CAAC;AAmJF;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,WAAW,CA4vBrE"}
|