@multiplatform.one/theme 7.34.4 → 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.
Files changed (280) hide show
  1. package/package.json +20 -5
  2. package/src/audit/constraintAudit.spec.ts +274 -87
  3. package/src/audit/constraintAudit.ts +135 -120
  4. package/src/audit/constraintScope.spec.ts +1 -1
  5. package/src/audit/designTokens.ts +42 -0
  6. package/src/audit/themeMatrix.spec.ts +83 -2
  7. package/src/audit/themeMatrix.ts +33 -1
  8. package/src/figma/figmaTokens.spec.ts +26 -34
  9. package/src/figma/figmaTokens.ts +26 -50
  10. package/src/fonts/createFontLoader.ts +4 -1
  11. package/src/menuRow.ts +4 -1
  12. package/src/theme/FontKnobStyles.tsx +2 -1
  13. package/src/theme/Preset.stories.tsx +0 -1
  14. package/src/theme/Preset.tsx +6 -1
  15. package/src/theme/PresetContext.ts +4 -0
  16. package/src/theme/Surface.spec.tsx +114 -67
  17. package/src/theme/Surface.stories.tsx +8 -25
  18. package/src/theme/Surface.tsx +87 -43
  19. package/src/theme/accentFloor.spec.ts +37 -11
  20. package/src/theme/activateThemeConfig.ts +22 -6
  21. package/src/theme/animations/animations.spec.ts +62 -0
  22. package/src/theme/animations/css.ts +9 -35
  23. package/src/theme/animations/reactNative.ts +98 -25
  24. package/src/theme/boundaryFloor.spec.ts +57 -0
  25. package/src/theme/chartPalette.spec.ts +56 -27
  26. package/src/theme/chartPalette.ts +33 -28
  27. package/src/theme/colorRules.spec.ts +32 -72
  28. package/src/theme/cookies.spec.ts +49 -18
  29. package/src/theme/cookies.ts +36 -7
  30. package/src/theme/cookiesPersistence.spec.ts +5 -5
  31. package/src/theme/createDefaultThemeConfig.spec.ts +182 -20
  32. package/src/theme/createDefaultThemeConfig.ts +34 -28
  33. package/src/theme/createThemes.ts +169 -218
  34. package/src/theme/defaults/accent.ts +3 -35
  35. package/src/theme/defaults/base.ts +14 -53
  36. package/src/theme/defaults/builderOptions.ts +68 -85
  37. package/src/theme/defaults/categoryFonts.ts +10 -0
  38. package/src/theme/defaults/fonts.spec.ts +84 -4
  39. package/src/theme/defaults/fonts.ts +166 -124
  40. package/src/theme/defaults/house.ts +48 -0
  41. package/src/theme/defaults/index.ts +1 -0
  42. package/src/theme/defaults/neutralRamp.spec.ts +52 -47
  43. package/src/theme/devtools/ColorLineVisualizer.tsx +6 -6
  44. package/src/theme/devtools/ThemeDevtoolsPanel.setMarkers.spec.tsx +13 -6
  45. package/src/theme/devtools/ThemeDevtoolsPanel.tsx +14 -77
  46. package/src/theme/devtools/animationOptions.spec.ts +23 -37
  47. package/src/theme/devtools/animationOptions.ts +4 -6
  48. package/src/theme/devtools/buildSnapshot.ts +0 -3
  49. package/src/theme/devtools/knobIcons.tsx +4 -24
  50. package/src/theme/focusState.spec.ts +9 -30
  51. package/src/theme/index.ts +17 -0
  52. package/src/theme/intentFill.spec.ts +42 -0
  53. package/src/theme/knobs.ts +14 -42
  54. package/src/theme/layoutTokens.ts +15 -1
  55. package/src/theme/layoutTokensHooks.spec.tsx +8 -4
  56. package/src/theme/mergedTokens.ts +20 -0
  57. package/src/theme/motion.spec.ts +84 -0
  58. package/src/theme/motion.ts +53 -0
  59. package/src/theme/motionStops.ts +17 -0
  60. package/src/theme/motionTiming.ts +59 -0
  61. package/src/theme/mpo13Proof.spec.ts +43 -46
  62. package/src/theme/mpo19Proof.spec.ts +13 -14
  63. package/src/theme/namedInkFloor.spec.ts +60 -0
  64. package/src/theme/overlaySurfaces.tsx +330 -0
  65. package/src/theme/preset.types.ts +16 -0
  66. package/src/theme/presetPreview.ts +32 -0
  67. package/src/theme/presetRoot.spec.tsx +89 -0
  68. package/src/theme/presetStore.persistence.spec.ts +141 -0
  69. package/src/theme/presetStore.spec.ts +205 -0
  70. package/src/theme/presetStore.ts +286 -0
  71. package/src/theme/presetUserSync.spec.tsx +168 -0
  72. package/src/theme/presetUserSync.ts +47 -0
  73. package/src/theme/presets.spec.ts +22 -15
  74. package/src/theme/presets.ts +22 -10
  75. package/src/theme/radiusClass.spec.ts +60 -41
  76. package/src/theme/radiusClass.ts +17 -4
  77. package/src/theme/radiusClassTable.generated.ts +3 -3
  78. package/src/theme/readableColor.spec.ts +59 -152
  79. package/src/theme/readableColor.ts +34 -174
  80. package/src/theme/recipeInputs.ts +153 -295
  81. package/src/theme/recipes.ts +58 -18
  82. package/src/theme/resolveKnobs.spec.ts +528 -588
  83. package/src/theme/resolveKnobs.ts +250 -325
  84. package/src/theme/scale/ScaleStyles.native.tsx +19 -0
  85. package/src/theme/scale/ScaleStyles.tsx +48 -0
  86. package/src/theme/scale/activeScale.ts +30 -0
  87. package/src/theme/scale/deriveRecipeInputs.ts +110 -0
  88. package/src/theme/scale/generateScale.spec.ts +260 -0
  89. package/src/theme/scale/generateScale.ts +147 -0
  90. package/src/theme/scale/index.ts +54 -0
  91. package/src/theme/scale/installScaleConfig.native.spec.ts +77 -0
  92. package/src/theme/scale/installScaleConfig.native.ts +57 -0
  93. package/src/theme/scale/installScaleConfig.ts +7 -0
  94. package/src/theme/scale/scaleAttribute.ts +21 -0
  95. package/src/theme/scale/scaleCss.probe.spec.tsx +64 -0
  96. package/src/theme/scale/scaleCss.spec.ts +132 -0
  97. package/src/theme/scale/scaleCss.ts +95 -0
  98. package/src/theme/scale/scaleFonts.ts +106 -0
  99. package/src/theme/scale/scaleInputs.ts +322 -0
  100. package/src/theme/scale/themeScales.ts +181 -0
  101. package/src/theme/scale/useActiveScale.spec.tsx +129 -0
  102. package/src/theme/scale/useActiveScale.ts +95 -0
  103. package/src/theme/schemeStore.spec.ts +81 -0
  104. package/src/theme/schemeStore.ts +67 -0
  105. package/src/theme/shared.spec.ts +7 -6
  106. package/src/theme/shared.tsx +37 -3
  107. package/src/theme/sizeLadder.spec.ts +37 -36
  108. package/src/theme/sizeRecipes.spec.ts +179 -85
  109. package/src/theme/sizeRecipes.ts +48 -25
  110. package/src/theme/subsetThemes.spec.ts +9 -3
  111. package/src/theme/switchThumb.spec.ts +69 -0
  112. package/src/theme/syntaxFloor.spec.ts +11 -8
  113. package/src/theme/theme.hydration.native.spec.tsx +51 -0
  114. package/src/theme/theme.native.tsx +59 -21
  115. package/src/theme/theme.palette.spec.tsx +76 -0
  116. package/src/theme/theme.preset.native.spec.tsx +73 -0
  117. package/src/theme/theme.provider.spec.ts +143 -1
  118. package/src/theme/theme.tsx +61 -25
  119. package/src/theme/tileSizes.ts +6 -7
  120. package/src/theme/transitionProps.native.ts +25 -2
  121. package/src/theme/transitionProps.spec.ts +34 -0
  122. package/src/theme/transitionProps.ts +26 -2
  123. package/src/theme/usePresetSwitch.native.spec.tsx +68 -0
  124. package/src/theme/usePresetSwitch.native.ts +32 -0
  125. package/src/theme/usePresetSwitch.spec.tsx +103 -0
  126. package/src/theme/usePresetSwitch.ts +54 -0
  127. package/src/theme/useResolvedKnobs.hydration.spec.tsx +2 -2
  128. package/src/theme/useResolvedKnobs.ts +23 -90
  129. package/src/theme/useResolvedKnobsBehavior.spec.tsx +25 -30
  130. package/src/theme/useSchemePreference.native.ts +33 -0
  131. package/src/theme/useSchemePreference.spec.tsx +51 -0
  132. package/src/theme/useSchemePreference.ts +32 -0
  133. package/src/theme/useSchemePreference.web.spec.tsx +32 -0
  134. package/src/theme/useTheme.native.spec.tsx +68 -0
  135. package/src/theme/useTheme.native.ts +49 -24
  136. package/src/theme/useTheme.ts +7 -4
  137. package/types/audit/constraintAudit.d.ts +6 -5
  138. package/types/audit/constraintAudit.d.ts.map +1 -1
  139. package/types/audit/designTokens.d.ts +13 -0
  140. package/types/audit/designTokens.d.ts.map +1 -0
  141. package/types/audit/themeMatrix.d.ts +10 -0
  142. package/types/audit/themeMatrix.d.ts.map +1 -1
  143. package/types/figma/figmaTokens.d.ts +11 -28
  144. package/types/figma/figmaTokens.d.ts.map +1 -1
  145. package/types/fonts/createFontLoader.d.ts.map +1 -1
  146. package/types/menuRow.d.ts +4 -1
  147. package/types/menuRow.d.ts.map +1 -1
  148. package/types/theme/FontKnobStyles.d.ts.map +1 -1
  149. package/types/theme/Preset.d.ts.map +1 -1
  150. package/types/theme/PresetContext.d.ts +4 -0
  151. package/types/theme/PresetContext.d.ts.map +1 -1
  152. package/types/theme/Surface.d.ts +57 -26
  153. package/types/theme/Surface.d.ts.map +1 -1
  154. package/types/theme/activateThemeConfig.d.ts +5 -3
  155. package/types/theme/activateThemeConfig.d.ts.map +1 -1
  156. package/types/theme/animations/css.d.ts +27 -34
  157. package/types/theme/animations/css.d.ts.map +1 -1
  158. package/types/theme/animations/reactNative.d.ts +196 -45
  159. package/types/theme/animations/reactNative.d.ts.map +1 -1
  160. package/types/theme/chartPalette.d.ts +11 -3
  161. package/types/theme/chartPalette.d.ts.map +1 -1
  162. package/types/theme/colorRules.d.ts +1 -1
  163. package/types/theme/cookies.d.ts +12 -1
  164. package/types/theme/cookies.d.ts.map +1 -1
  165. package/types/theme/createDefaultThemeConfig.d.ts +13 -13
  166. package/types/theme/createDefaultThemeConfig.d.ts.map +1 -1
  167. package/types/theme/createThemes.d.ts +58 -2
  168. package/types/theme/createThemes.d.ts.map +1 -1
  169. package/types/theme/defaults/accent.d.ts.map +1 -1
  170. package/types/theme/defaults/base.d.ts.map +1 -1
  171. package/types/theme/defaults/builderOptions.d.ts.map +1 -1
  172. package/types/theme/defaults/categoryFonts.d.ts +10 -0
  173. package/types/theme/defaults/categoryFonts.d.ts.map +1 -1
  174. package/types/theme/defaults/fonts.d.ts +330 -30
  175. package/types/theme/defaults/fonts.d.ts.map +1 -1
  176. package/types/theme/defaults/house.d.ts +8 -0
  177. package/types/theme/defaults/house.d.ts.map +1 -0
  178. package/types/theme/defaults/index.d.ts +1 -0
  179. package/types/theme/defaults/index.d.ts.map +1 -1
  180. package/types/theme/devtools/ThemeDevtoolsPanel.d.ts +3 -3
  181. package/types/theme/devtools/ThemeDevtoolsPanel.d.ts.map +1 -1
  182. package/types/theme/devtools/animationOptions.d.ts.map +1 -1
  183. package/types/theme/devtools/buildSnapshot.d.ts.map +1 -1
  184. package/types/theme/devtools/knobIcons.d.ts +0 -3
  185. package/types/theme/devtools/knobIcons.d.ts.map +1 -1
  186. package/types/theme/index.d.ts +11 -0
  187. package/types/theme/index.d.ts.map +1 -1
  188. package/types/theme/knobs.d.ts +8 -36
  189. package/types/theme/knobs.d.ts.map +1 -1
  190. package/types/theme/layoutTokens.d.ts +13 -1
  191. package/types/theme/layoutTokens.d.ts.map +1 -1
  192. package/types/theme/mergedTokens.d.ts +11 -0
  193. package/types/theme/mergedTokens.d.ts.map +1 -0
  194. package/types/theme/motion.d.ts +17 -0
  195. package/types/theme/motion.d.ts.map +1 -0
  196. package/types/theme/motionStops.d.ts +3 -0
  197. package/types/theme/motionStops.d.ts.map +1 -0
  198. package/types/theme/motionTiming.d.ts +36 -0
  199. package/types/theme/motionTiming.d.ts.map +1 -0
  200. package/types/theme/overlaySurfaces.d.ts +70 -0
  201. package/types/theme/overlaySurfaces.d.ts.map +1 -0
  202. package/types/theme/preset.types.d.ts +16 -0
  203. package/types/theme/preset.types.d.ts.map +1 -1
  204. package/types/theme/presetPreview.d.ts +14 -0
  205. package/types/theme/presetPreview.d.ts.map +1 -0
  206. package/types/theme/presetStore.d.ts +40 -0
  207. package/types/theme/presetStore.d.ts.map +1 -0
  208. package/types/theme/presetUserSync.d.ts +7 -0
  209. package/types/theme/presetUserSync.d.ts.map +1 -0
  210. package/types/theme/presets.d.ts +12 -4
  211. package/types/theme/presets.d.ts.map +1 -1
  212. package/types/theme/radiusClass.d.ts +5 -2
  213. package/types/theme/radiusClass.d.ts.map +1 -1
  214. package/types/theme/radiusClassTable.generated.d.ts +3 -3
  215. package/types/theme/readableColor.d.ts +11 -49
  216. package/types/theme/readableColor.d.ts.map +1 -1
  217. package/types/theme/recipeInputs.d.ts +39 -70
  218. package/types/theme/recipeInputs.d.ts.map +1 -1
  219. package/types/theme/recipes.d.ts +79 -18
  220. package/types/theme/recipes.d.ts.map +1 -1
  221. package/types/theme/resolveKnobs.d.ts +64 -32
  222. package/types/theme/resolveKnobs.d.ts.map +1 -1
  223. package/types/theme/scale/ScaleStyles.d.ts +18 -0
  224. package/types/theme/scale/ScaleStyles.d.ts.map +1 -0
  225. package/types/theme/scale/ScaleStyles.native.d.ts +10 -0
  226. package/types/theme/scale/ScaleStyles.native.d.ts.map +1 -0
  227. package/types/theme/scale/activeScale.d.ts +13 -0
  228. package/types/theme/scale/activeScale.d.ts.map +1 -0
  229. package/types/theme/scale/deriveRecipeInputs.d.ts +10 -0
  230. package/types/theme/scale/deriveRecipeInputs.d.ts.map +1 -0
  231. package/types/theme/scale/generateScale.d.ts +38 -0
  232. package/types/theme/scale/generateScale.d.ts.map +1 -0
  233. package/types/theme/scale/index.d.ts +17 -0
  234. package/types/theme/scale/index.d.ts.map +1 -0
  235. package/types/theme/scale/installScaleConfig.d.ts +4 -0
  236. package/types/theme/scale/installScaleConfig.d.ts.map +1 -0
  237. package/types/theme/scale/installScaleConfig.native.d.ts +13 -0
  238. package/types/theme/scale/installScaleConfig.native.d.ts.map +1 -0
  239. package/types/theme/scale/scaleAttribute.d.ts +14 -0
  240. package/types/theme/scale/scaleAttribute.d.ts.map +1 -0
  241. package/types/theme/scale/scaleCss.d.ts +25 -0
  242. package/types/theme/scale/scaleCss.d.ts.map +1 -0
  243. package/types/theme/scale/scaleFonts.d.ts +16 -0
  244. package/types/theme/scale/scaleFonts.d.ts.map +1 -0
  245. package/types/theme/scale/scaleInputs.d.ts +66 -0
  246. package/types/theme/scale/scaleInputs.d.ts.map +1 -0
  247. package/types/theme/scale/themeScales.d.ts +52 -0
  248. package/types/theme/scale/themeScales.d.ts.map +1 -0
  249. package/types/theme/scale/useActiveScale.d.ts +31 -0
  250. package/types/theme/scale/useActiveScale.d.ts.map +1 -0
  251. package/types/theme/schemeStore.d.ts +6 -0
  252. package/types/theme/schemeStore.d.ts.map +1 -0
  253. package/types/theme/shared.d.ts +18 -0
  254. package/types/theme/shared.d.ts.map +1 -1
  255. package/types/theme/sizeRecipes.d.ts +20 -15
  256. package/types/theme/sizeRecipes.d.ts.map +1 -1
  257. package/types/theme/theme.d.ts +2 -1
  258. package/types/theme/theme.d.ts.map +1 -1
  259. package/types/theme/theme.native.d.ts +10 -1
  260. package/types/theme/theme.native.d.ts.map +1 -1
  261. package/types/theme/tileSizes.d.ts +6 -7
  262. package/types/theme/tileSizes.d.ts.map +1 -1
  263. package/types/theme/transitionProps.d.ts +3 -1
  264. package/types/theme/transitionProps.d.ts.map +1 -1
  265. package/types/theme/transitionProps.native.d.ts +3 -1
  266. package/types/theme/transitionProps.native.d.ts.map +1 -1
  267. package/types/theme/usePresetSwitch.d.ts +10 -0
  268. package/types/theme/usePresetSwitch.d.ts.map +1 -0
  269. package/types/theme/usePresetSwitch.native.d.ts +10 -0
  270. package/types/theme/usePresetSwitch.native.d.ts.map +1 -0
  271. package/types/theme/useResolvedKnobs.d.ts +13 -13
  272. package/types/theme/useResolvedKnobs.d.ts.map +1 -1
  273. package/types/theme/useSchemePreference.d.ts +8 -0
  274. package/types/theme/useSchemePreference.d.ts.map +1 -0
  275. package/types/theme/useSchemePreference.native.d.ts +8 -0
  276. package/types/theme/useSchemePreference.native.d.ts.map +1 -0
  277. package/types/theme/useTheme.d.ts +4 -2
  278. package/types/theme/useTheme.d.ts.map +1 -1
  279. package/types/theme/useTheme.native.d.ts +10 -1
  280. package/types/theme/useTheme.native.d.ts.map +1 -1
@@ -57,16 +57,16 @@ describe("persistPresetOverrides / loadPresetOverrides", () => {
57
57
 
58
58
  it("chunks oversized overrides across multiple cookies and reassembles them", () => {
59
59
  const bigValue = "x".repeat(8000);
60
- persistPresetOverrides({ animation: bigValue } as unknown as Partial<Knobs>);
60
+ persistPresetOverrides({ headingFont: bigValue } as unknown as Partial<Knobs>);
61
61
  // The serialized JSON exceeds the 3.5 KB split threshold → several chunks.
62
62
  expect(document.cookie).toContain(`${cookieOverridesPrefix}.0=`);
63
63
  expect(document.cookie).toContain(`${cookieOverridesPrefix}.1=`);
64
- expect(loadPresetOverrides()).toEqual({ animation: bigValue });
64
+ expect(loadPresetOverrides()).toEqual({ headingFont: bigValue });
65
65
  });
66
66
 
67
67
  it("cleans up stale chunk cookies when the payload shrinks", () => {
68
68
  const bigValue = "y".repeat(8000);
69
- persistPresetOverrides({ animation: bigValue } as unknown as Partial<Knobs>);
69
+ persistPresetOverrides({ headingFont: bigValue } as unknown as Partial<Knobs>);
70
70
  persistPresetOverrides({ borderRadius: "small" } as Partial<Knobs>);
71
71
  expect(loadPresetOverrides()).toEqual({ borderRadius: "small" });
72
72
  expect(document.cookie).not.toContain(`${cookieOverridesPrefix}.1=`);
@@ -74,9 +74,9 @@ describe("persistPresetOverrides / loadPresetOverrides", () => {
74
74
 
75
75
  it("reads from a raw cookie map (SSR) without touching the document", () => {
76
76
  const raw = {
77
- [`${cookieOverridesPrefix}.0`]: JSON.stringify({ space: "large" }),
77
+ [`${cookieOverridesPrefix}.0`]: JSON.stringify({ elevation: "large" }),
78
78
  };
79
- expect(loadPresetOverrides(raw)).toEqual({ space: "large" });
79
+ expect(loadPresetOverrides(raw)).toEqual({ elevation: "large" });
80
80
  });
81
81
 
82
82
  it("treats corrupted cookie JSON as no overrides", () => {
@@ -26,10 +26,16 @@ import {
26
26
  defaultConfig as v5DefaultConfig,
27
27
  mediaQueryDefaultActive as v5MediaQueryDefaultActive,
28
28
  } from "@tamagui/config/v5";
29
- import { tokens as v5Tokens } from "@tamagui/themes/v5";
29
+ import { tokens as v5Tokens, themes as stockThemes } from "@tamagui/themes/v5";
30
30
  import { contrastRatio, minContrastRatio, normalizeToHex, relativeLuminance } from "./colorRules";
31
31
  import { TAMAGUI_ERA, createDefaultThemeConfig } from "./createDefaultThemeConfig";
32
- import type { GetThemeProps } from "./createThemes";
32
+ import { generateScale } from "./scale/generateScale";
33
+ import { defaultScaleInputs, v5ScaleInputs } from "./scale/scaleInputs";
34
+ import { DEFAULT_SCALE_ID } from "./scale/themeScales";
35
+ import { createTamagui } from "tamagui";
36
+ import { boldPreset, densePreset, heroPreset, roomyPreset } from "./presets";
37
+ import { createThemesBuilder, type GetThemeProps } from "./createThemes";
38
+ import { defaultBaseTheme, defaultAccentTheme } from "./defaults";
33
39
  import { setSizeRecipeInputs } from "./sizeRecipes";
34
40
 
35
41
  // Mirrors the shc app theme config: a consumer getTheme written against the
@@ -122,25 +128,25 @@ describe("createDefaultThemeConfig — builderOptions.getTheme composition (DF-0
122
128
  }
123
129
  });
124
130
 
125
- it("recipeInputs: { baseHeight: 40 } regenerates the control family on ThemeConfig, not color themes", () => {
131
+ it("recipeInputs: { control: 40 } regenerates the control family on ThemeConfig, not color themes", () => {
126
132
  const config = createDefaultThemeConfig({
127
- recipeInputs: { baseHeight: 40 },
133
+ recipeInputs: { control: 40 },
128
134
  });
129
135
  expect(config.recipeFamilies?.control.boxVariants.$4.height).toBe(40);
130
136
  expect(config.recipeFamilies?.control.recipes.$4).toEqual({
131
137
  height: 40,
132
- paddingHorizontal: Math.round(40 * (18 / 44)),
133
- fontSize: Math.round(40 * (14 / 44)),
134
- iconSize: Math.round(40 * (16 / 44)),
135
- gap: Math.round(40 * (9 / 44)),
138
+ paddingHorizontal: 16,
139
+ fontSize: 15,
140
+ iconSize: 16,
141
+ gap: 8,
136
142
  });
137
- expect(config.recipeInputs?.baseHeight).toBe(40);
143
+ expect(config.recipeInputs?.control).toBe(40);
138
144
  const themes = (config.tamagui as unknown as { themes: RawThemes }).themes;
139
145
  for (const [name, theme] of Object.entries(themes)) {
140
- expect(theme, name).not.toHaveProperty("baseHeight");
146
+ expect(theme, name).not.toHaveProperty("control");
141
147
  expect(theme, name).not.toHaveProperty("radiusPx");
142
148
  expect(theme, name).not.toHaveProperty("paddingHorizontal");
143
- expect(theme, name).not.toHaveProperty("sizeFactors");
149
+ expect(theme, name).not.toHaveProperty("tokenHeights");
144
150
  }
145
151
  });
146
152
 
@@ -152,7 +158,7 @@ describe("createDefaultThemeConfig — builderOptions.getTheme composition (DF-0
152
158
  expect(normalizeToHex(theme.outlineColor)).toBeTruthy();
153
159
  expect(theme.textMuted).toBe(theme.color11);
154
160
  }
155
- expect(config.recipeFamilies?.control.boxVariants.$4.height).toBe(44);
161
+ expect(config.recipeFamilies?.control.boxVariants.$4.height).toBe(40);
156
162
  });
157
163
 
158
164
  it("registers serif so headingFont can resolve a family without a per-app font map", () => {
@@ -234,25 +240,181 @@ describe("createDefaultThemeConfig — Tamagui era pin (MPO-19 X13 / W0-era-pin)
234
240
  expect(config.tamagui.settings.mediaQueryDefaultActive).toEqual(v5MediaQueryDefaultActive);
235
241
  });
236
242
 
237
- it("resolves v5 token values exactly for every non-color group", () => {
243
+ it("the generator reproduces v5 token values exactly for every non-color group", () => {
238
244
  const groups = ["size", "space", "radius", "zIndex"] as const;
239
245
  for (const group of groups) {
240
- const resolved = tokenGroupValues(config.tamagui.tokens, group);
246
+ const resolved = tokenGroupValues(generateScale(v5ScaleInputs).tokens, group);
241
247
  const v5 = tokenGroupValues(v5Tokens as unknown as RawTokens, group);
242
248
  expect(Object.keys(v5).length, `${group} group present in v5`).toBeGreaterThan(0);
243
249
  expect(resolved, `tokens.${group}`).toEqual(v5);
244
250
  }
245
251
  });
246
252
 
247
- it("anchors radius.true at 9, the v5 base radius", () => {
253
+ it("installs the house default scale tokens", () => {
254
+ expect(config.tamagui.tokens).toEqual(generateScale(defaultScaleInputs).tokens);
255
+ expect(tokenGroupValues(config.tamagui.tokens, "space").true).toBe(16);
256
+ });
257
+
258
+ it("anchors radius.true at 8, the house base radius", () => {
248
259
  const radius = tokenGroupValues(config.tamagui.tokens, "radius");
249
- expect(radius.true).toBe(9);
250
- expect(radius["4"]).toBe(9);
260
+ expect(radius.true).toBe(8);
261
+ expect(radius["4"]).toBe(8);
251
262
  });
252
263
 
253
- it("anchors size.true at 44, the v5 base control height", () => {
264
+ it("anchors size.true at 40, the house base control height", () => {
254
265
  const size = tokenGroupValues(config.tamagui.tokens, "size");
255
- expect(size.true).toBe(44);
256
- expect(size["4"]).toBe(44);
266
+ expect(size.true).toBe(40);
267
+ expect(size["4"]).toBe(40);
268
+ });
269
+ });
270
+
271
+ describe("createDefaultThemeConfig — preset scales (FZ-7158 WP-a2)", () => {
272
+ const presets = { hero: heroPreset, bold: boldPreset, dense: densePreset, roomy: roomyPreset };
273
+
274
+ it("keys one scale per preset name and shares entries across equal inputs", () => {
275
+ const config = createDefaultThemeConfig({ presets });
276
+ const scales = config.scales ?? {};
277
+ expect(Object.keys(scales).sort()).toEqual([
278
+ "bold",
279
+ DEFAULT_SCALE_ID,
280
+ "dense",
281
+ "hero",
282
+ "roomy",
283
+ ]);
284
+ expect(scales.hero).toBe(scales[DEFAULT_SCALE_ID]);
285
+ expect(scales.bold).toBe(scales[DEFAULT_SCALE_ID]);
286
+ expect(scales.dense?.id).toBe("dense");
287
+ expect(scales.roomy?.id).toBe("roomy");
288
+ expect(scales[DEFAULT_SCALE_ID]?.presets).toEqual([DEFAULT_SCALE_ID, "hero", "bold"]);
289
+ });
290
+
291
+ it("reuses the base config for the default scale and builds the others only when asked", () => {
292
+ const build = vi.mocked(createTamagui);
293
+ build.mockClear();
294
+ const config = createDefaultThemeConfig({ presets });
295
+ expect(build).toHaveBeenCalledTimes(1);
296
+ expect(config.scales?.[DEFAULT_SCALE_ID]?.tamagui()).toBe(config.tamagui);
297
+ expect(build).toHaveBeenCalledTimes(1);
298
+
299
+ const dense = config.scales?.dense?.tamagui();
300
+ expect(build).toHaveBeenCalledTimes(2);
301
+ expect(config.scales?.dense?.tamagui()).toBe(dense);
302
+ expect(build).toHaveBeenCalledTimes(2);
303
+ });
304
+
305
+ it("builds a scale config from the same props with only its tokens and fonts swapped", () => {
306
+ const config = createDefaultThemeConfig({ presets });
307
+ const base = config.tamagui as unknown as Record<string, unknown>;
308
+ const dense = config.scales?.dense?.tamagui() as unknown as Record<string, unknown>;
309
+ expect(dense.themes).toBe(base.themes);
310
+ expect(dense.settings).toBe(base.settings);
311
+ expect(dense.media).toBe(base.media);
312
+ expect(dense.tokens).toEqual(generateScale({ control: 36, type: 14 }).tokens);
313
+ const fonts = dense.fonts as Record<string, { size: Record<string, number> }>;
314
+ expect(fonts.body.size.true).toBe(14);
315
+ expect(fonts.heading.size["4"]).toBe(
316
+ generateScale({ control: 36, type: 14 }).fonts.headingSizes[4],
317
+ );
318
+ expect(Object.keys(fonts).sort()).toEqual(Object.keys(base.fonts as object).sort());
319
+ });
320
+
321
+ it("derives each scale's recipe inputs from its own tokens", () => {
322
+ const config = createDefaultThemeConfig({ presets });
323
+ expect(config.scales?.dense?.recipeInputs.tokenHeights.$4).toBe(36);
324
+ expect(config.scales?.roomy?.recipeInputs.tokenHeights.$4).toBe(48);
325
+ expect(config.scales?.[DEFAULT_SCALE_ID]?.recipeInputs).toEqual(config.recipeInputs);
326
+ });
327
+ });
328
+
329
+ describe("the v5 theme matrix", () => {
330
+ const config = createDefaultThemeConfig();
331
+ const themes = config.tamagui.themes as unknown as RawThemes;
332
+
333
+ it("binds the v5 shadow3 alpha in both schemes", () => {
334
+ for (const [scheme, alphas] of [
335
+ ["light", [0.04, 0.08, 0.12, 0.22, 0.33, 0.44]],
336
+ ["dark", [0.15, 0.23, 0.33, 0.45, 0.65, 0.8]],
337
+ ] as const) {
338
+ alphas.forEach((alpha, index) => {
339
+ expect(themes[scheme][`shadow${index + 1}`]).toBe(`rgba(0,0,0,${alpha})`);
340
+ });
341
+ expect(themes[scheme].shadowColor).toBe(`rgba(0,0,0,${alphas[2]})`);
342
+ }
343
+ });
344
+
345
+ it("uses v5 surface templates for pages and components", () => {
346
+ for (const scheme of ["light", "dark"] as const) {
347
+ expect(themes[scheme].background).toBe(themes[scheme].color2);
348
+ expect(themes[`${scheme}_Card`].background).toBe(themes[scheme].color3);
349
+ }
350
+ const button = themes.light_Button;
351
+ expect(relativeLuminance(normalizeToHex(button.backgroundHover)!)).toBeGreaterThan(
352
+ relativeLuminance(normalizeToHex(button.background)!),
353
+ );
354
+ for (const component of [
355
+ "SwitchThumb",
356
+ "SliderTrack",
357
+ "Tooltip",
358
+ "Checkbox",
359
+ "RadioGroupItem",
360
+ ]) {
361
+ expect(themes).not.toHaveProperty(`light_${component}`);
362
+ }
363
+ });
364
+
365
+ it("builds gray children from their own opaque palette", () => {
366
+ for (const scheme of ["light", "dark"] as const) {
367
+ expect(themes[`${scheme}_gray`].color3).toBe(stockThemes[`${scheme}_gray`].color3);
368
+ for (const suffix of ["active", "alt1", "alt2"]) {
369
+ const child = themes[`${scheme}_gray_${suffix}`];
370
+ expect(child).toBeDefined();
371
+ for (const key of ["color", "background", "borderColor"]) {
372
+ expect(child[key]).toMatch(/, 1\)$/);
373
+ }
374
+ }
375
+ }
376
+ });
377
+
378
+ it("uses v5 templates for additional themes and honors component overrides", () => {
379
+ const custom = createDefaultThemeConfig({
380
+ additionalThemes: { brand: defaultAccentTheme },
381
+ builderOptions: { componentThemes: { Custom: { template: "surface1" } } },
382
+ }).tamagui.themes as unknown as RawThemes;
383
+ for (const scheme of ["light", "dark"] as const) {
384
+ expect(custom[`${scheme}_brand`].background).toBe(custom[`${scheme}_brand`].color2);
385
+ expect(custom[`${scheme}_Custom`].background).toBe(custom[scheme].color3);
386
+ expect(custom).not.toHaveProperty(`${scheme}_Button`);
387
+ }
388
+ });
389
+
390
+ it("honors an explicit grandchildren map in both builder paths", () => {
391
+ const seen: string[] = [];
392
+ const builder = createThemesBuilder(defaultBaseTheme, defaultAccentTheme, {
393
+ grandChildrenThemes: { raised: { template: "surface1" } },
394
+ getTheme: ({ name }) => {
395
+ seen.push(name);
396
+ return {};
397
+ },
398
+ });
399
+ expect(builder.themes()).toHaveProperty("light_blue_raised");
400
+ expect(builder.themes()).not.toHaveProperty("light_blue_alt1");
401
+ seen.length = 0;
402
+ builder.addTheme("brand", defaultAccentTheme);
403
+ expect(seen).toContain("light_blue_raised");
404
+ expect(seen).not.toContain("light_blue_alt1");
405
+ });
406
+
407
+ it("allows callers to disable component themes", () => {
408
+ const custom = createThemesBuilder(defaultBaseTheme, defaultAccentTheme, {
409
+ componentThemes: false,
410
+ }).themes();
411
+ expect(Object.keys(custom).some((name) => /_[A-Z]/.test(name))).toBe(false);
412
+ });
413
+
414
+ it("ships 462 themes and the heading-scope category fonts in under 260 KB of CSS (the v4 matrix and stock spread shipped 913 KB)", async () => {
415
+ const { createTamagui: build } = await vi.importActual<typeof import("tamagui")>("tamagui");
416
+ const css = build(config.tamagui).getCSS();
417
+ expect(Object.keys(themes)).toHaveLength(462);
418
+ expect(Buffer.byteLength(css, "utf8")).toBeLessThan(260_000);
257
419
  });
258
420
  });
@@ -13,8 +13,11 @@ import { defaultAccentTheme } from "./defaults/accent";
13
13
  import { defaultBaseTheme } from "./defaults/base";
14
14
  import { defaultBuilderOptions } from "./defaults/builderOptions";
15
15
  import { defaultFonts } from "./defaults/fonts";
16
+ import { scaleFontSet } from "./scale/scaleFonts";
16
17
  import type { Preset } from "./preset.types";
17
18
  import { type SizeRecipeInputs } from "./recipeInputs";
19
+ import { generateScale } from "./scale/generateScale";
20
+ import { defaultScaleInputs } from "./scale/scaleInputs";
18
21
  import { type ThemeConfig, createThemeConfig } from "./shared";
19
22
  import { assertResolvableThemeValues } from "./themeValue";
20
23
  import { getGeneratedSizeRecipes, getSizeRecipeInputs, setSizeRecipeInputs } from "./sizeRecipes";
@@ -30,9 +33,10 @@ import { type ThemeSubset, subsetThemes } from "./subsetThemes";
30
33
  * against v5, never against v4 or the legacy root exports. Asserted by the
31
34
  * era block in `createDefaultThemeConfig.spec.ts`.
32
35
  *
33
- * Tokens come from `defaultConfig` and from nothing else — the legacy
34
- * `@tamagui/themes` override is gone (measured identical for size/space/
35
- * radius/zIndex; its only addition was unused Radix color steps).
36
+ * The non-color tokens and the body/heading type tables come from one
37
+ * generator (`scale/generateScale.ts`), spec-pinned to the v5 values at the
38
+ * default inputs, so `$size`/`$space` props and the size recipes read the
39
+ * same numbers.
36
40
  */
37
41
  export const TAMAGUI_ERA = "v5" as const;
38
42
  /** MPO-19 proof alias — same pin as `TAMAGUI_ERA`. */
@@ -49,7 +53,7 @@ export interface CreateDefaultThemeConfigOptions {
49
53
  fonts?: Record<string, GenericFont>;
50
54
  /** Animation config (defaults to framework reanimated animations) */
51
55
  animations?: CreateTamaguiProps["animations"];
52
- /** Options for the theme builder (grandChildrenThemes, getTheme) */
56
+ /** Options for the theme builder (componentThemes, grandChildrenThemes, getTheme) */
53
57
  builderOptions?: CreateThemesBuilderOptions;
54
58
  /** Override tamagui settings */
55
59
  settings?: Partial<NonNullable<CreateTamaguiProps["settings"]>>;
@@ -60,10 +64,11 @@ export interface CreateDefaultThemeConfigOptions {
60
64
  /** Default preset name to use when no `mp.preset` cookie is set */
61
65
  defaultPreset?: string;
62
66
  /**
63
- * Size-recipe inputs (LC-70). Merged over `defaultRecipeInputs`; tables are
64
- * generated, written into the live aliases Button/Input already import, and
65
- * attached on ThemeConfig as `recipeInputs` / `recipeFamilies`. Not written
66
- * into Tamagui `themes` (those stay color palettes from createThemes /
67
+ * Size-recipe inputs (LC-70). Merged over the inputs derived from the
68
+ * default scale (`defaultRecipeInputs`); tables are generated, written into
69
+ * the live aliases Button/Input already import, and attached on
70
+ * ThemeConfig as `recipeInputs` / `recipeFamilies`. Not written into
71
+ * Tamagui `themes` (those stay color palettes from createThemes /
67
72
  * theme-builder).
68
73
  */
69
74
  recipeInputs?: Partial<SizeRecipeInputs>;
@@ -76,15 +81,14 @@ export interface CreateDefaultThemeConfigOptions {
76
81
  }
77
82
 
78
83
  /**
79
- * Compose a consumer `getTheme` WITH the framework default instead of
80
- * replacing it. The default always runs first — it derives the solid ≥3:1
81
- * `$outlineColor` focus ring (DG-A11Y-01), the interaction ramp and the
82
- * component sub-theme border/outline anchors — then the consumer's result is
84
+ * Compose a consumer `getTheme` WITH the framework default instead of replacing
85
+ * it. The default always runs first — it derives the solid ≥3:1 `$outlineColor`
86
+ * focus ring (DG-A11Y-01) and semantic aliases — then the consumer's result is
83
87
  * merged over it per key, so a partial override keeps every derivation it
84
88
  * doesn't explicitly change. Consumer entries that resolve to nullish are
85
- * dropped: a getTheme written against the full base ramp (`theme.color5`
86
- * etc.) also runs inside narrow component sub-themes where those steps don't
87
- * exist, and its undefineds must not erase the defaults (DF-03).
89
+ * dropped: a getTheme written against the full base ramp (`theme.color5` etc.)
90
+ * also runs inside narrow component sub-themes where those steps don't exist,
91
+ * and its undefineds must not erase the defaults (DF-03).
88
92
  */
89
93
  function composeGetTheme(
90
94
  custom: CreateThemesBuilderOptions["getTheme"],
@@ -108,11 +112,9 @@ function composeGetTheme(
108
112
  }
109
113
 
110
114
  /**
111
- * Builds a complete Tamagui theme config with batteries included: calling it
112
- * with zero options yields the framework's polished defaults — the neutral
113
- * Radix-mauve-curve base ramps, the solid-surface brand accent ramps, Inter
114
- * heading/body fonts, the category fonts the bodyFont knob resolves, and the
115
- * interaction-ramp `getTheme` theme builder.
115
+ * Builds Tamagui's v5 grey ramp, inverted-base accent and colour themes,
116
+ * with the house accessibility floors. Fonts include Inter heading/body
117
+ * and the category faces selected by the bodyFont knob.
116
118
  * Every option overrides its piece independently: `base` and `accent` each
117
119
  * replace their default, `fonts` are spread OVER the defaults so a partial
118
120
  * font map keeps the default heading/body and category faces, and `builderOptions.getTheme`
@@ -123,6 +125,7 @@ export function createDefaultThemeConfig(
123
125
  options: CreateDefaultThemeConfigOptions = {},
124
126
  ): ThemeConfig {
125
127
  setSizeRecipeInputs(options.recipeInputs ?? {});
128
+ const scale = generateScale(defaultScaleInputs);
126
129
  const base = options.base ?? defaultBaseTheme;
127
130
  const accent = options.accent ?? defaultAccentTheme;
128
131
  const themesBuilder = createThemesBuilder(base, accent, {
@@ -144,24 +147,23 @@ export function createDefaultThemeConfig(
144
147
  // pipeline (e.g. the Frappe SPA) where runtime JS themes are required.
145
148
  // Always keep themes to ensure TamaguiProvider has valid theme data.
146
149
  const themes = options.subset ? subsetThemes(builtThemes, options.subset) : builtThemes;
150
+ const fonts = {
151
+ ...defaultConfig.fonts,
152
+ ...defaultFonts,
153
+ ...options.fonts,
154
+ } as Record<string, GenericFont>;
147
155
  return createThemeConfig(
148
156
  {
149
157
  ...defaultConfig,
158
+ tokens: scale.tokens,
150
159
  animations: options.animations ?? animations,
151
160
  disableSSR: false,
152
- fonts: {
153
- ...defaultConfig.fonts,
154
- ...defaultFonts,
155
- ...options.fonts,
156
- },
161
+ fonts,
157
162
  shorthands,
158
163
  shouldAddPrefersColorThemes: true,
159
164
  themeClassNameOnRoot: true,
160
165
  themes,
161
166
  allowedStyleValues: undefined,
162
- // Tokens come from `defaultConfig` above, and from nothing else — see
163
- // the `tamaguiEra` note. There is no second token source to disagree
164
- // with the era pin.
165
167
  settings: {
166
168
  ...defaultConfig.settings,
167
169
  allowedStyleValues: "somewhat-strict-web",
@@ -181,6 +183,10 @@ export function createDefaultThemeConfig(
181
183
  defaultPreset: options.defaultPreset,
182
184
  recipeInputs: getSizeRecipeInputs(),
183
185
  recipeFamilies: getGeneratedSizeRecipes().families,
186
+ scale: {
187
+ fonts: (tables) => scaleFontSet(fonts, scale.fonts, tables),
188
+ recipeOverrides: options.recipeInputs,
189
+ },
184
190
  },
185
191
  );
186
192
  }