@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.
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
@@ -38,9 +38,6 @@ const minimalPreset: Preset = {
38
38
  borderRadius: "none",
39
39
  borderWidth: "none",
40
40
  elevation: "none",
41
- space: "small",
42
- size: "small",
43
- density: "comfortable",
44
41
  textAccent: "low",
45
42
  headingFont: "sans-serif",
46
43
  bodyFont: "sans-serif",
@@ -63,9 +60,6 @@ const boldPreset: Preset = {
63
60
  borderRadius: "full",
64
61
  borderWidth: "large",
65
62
  elevation: "medium",
66
- space: "large",
67
- size: "large",
68
- density: "comfortable",
69
63
  textAccent: "high",
70
64
  headingFont: "sans-serif",
71
65
  bodyFont: "sans-serif",
@@ -88,9 +82,6 @@ const roundedPreset: Preset = {
88
82
  borderRadius: "large",
89
83
  borderWidth: "small",
90
84
  elevation: "small",
91
- space: "medium",
92
- size: "medium",
93
- density: "comfortable",
94
85
  textAccent: "high",
95
86
  headingFont: "sans-serif",
96
87
  bodyFont: "sans-serif",
@@ -142,10 +133,10 @@ describe("Theme Preset System", () => {
142
133
  registerPreset("minimal", minimalPreset);
143
134
  const updated: Preset = {
144
135
  ...minimalPreset,
145
- knobs: { ...minimalPreset.knobs, space: "large" },
136
+ knobs: { ...minimalPreset.knobs, textAccent: "high" },
146
137
  };
147
138
  registerPreset("minimal", updated);
148
- expect(getPreset("minimal")?.knobs.space).toBe("large");
139
+ expect(getPreset("minimal")?.knobs.textAccent).toBe("high");
149
140
  });
150
141
  });
151
142
 
@@ -158,16 +149,16 @@ describe("Theme Preset System", () => {
158
149
  const resolved = resolveThemeFromPreset("minimal", {});
159
150
  expect(resolved.knobs.borderRadius).toBe("none");
160
151
  expect(resolved.knobs.elevation).toBe("none");
161
- expect(resolved.knobs.space).toBe("small");
152
+ expect(resolved.knobs.textAccent).toBe("low");
162
153
  });
163
154
 
164
155
  it("merges overrides on top of preset config", () => {
165
156
  registerPreset("minimal", minimalPreset);
166
- const overrides = { borderRadius: "full" as const, space: "large" as const };
157
+ const overrides = { borderRadius: "full" as const, textAccent: "high" as const };
167
158
  const resolved = resolveThemeFromPreset("minimal", overrides);
168
159
  // Overrides win
169
160
  expect(resolved.knobs.borderRadius).toBe("full");
170
- expect(resolved.knobs.space).toBe("large");
161
+ expect(resolved.knobs.textAccent).toBe("high");
171
162
  // Preset defaults preserved for non-overridden props
172
163
  expect(resolved.knobs.elevation).toBe("none");
173
164
  expect(resolved.knobs.fillStyle).toBe("outlined");
@@ -201,7 +192,7 @@ describe("Theme Preset System", () => {
201
192
  const resolved2 = resolveThemeFromPreset("bold", {});
202
193
  expect(resolved2.knobs.elevation).toBe("medium"); // bold preset's value
203
194
  expect(resolved2.knobs.borderRadius).toBe("full"); // bold preset's value
204
- expect(resolved2.knobs.space).toBe("large"); // bold preset's value
195
+ expect(resolved2.knobs.fontWeight).toBe("bold"); // bold preset's value
205
196
  });
206
197
  });
207
198
 
@@ -267,3 +258,19 @@ describe("Theme Preset System", () => {
267
258
  });
268
259
  });
269
260
  });
261
+
262
+ describe("token-scale presets (FZ-7158 WP-a2)", () => {
263
+ it("dense and roomy differ from the default only in their token scale", async () => {
264
+ const { defaultPreset, densePreset, roomyPreset } = await import("./presets");
265
+ expect(JSON.stringify(densePreset.knobs)).toBe(JSON.stringify(defaultPreset.knobs));
266
+ expect(JSON.stringify(roomyPreset.knobs)).toBe(JSON.stringify(defaultPreset.knobs));
267
+ const { scale: _dense, ...denseRest } = densePreset;
268
+ const { scale: _roomy, ...roomyRest } = roomyPreset;
269
+ const { scale: _default, ...defaultRest } = defaultPreset;
270
+ expect(denseRest).toEqual(defaultRest);
271
+ expect(roomyRest).toEqual(defaultRest);
272
+ expect(densePreset.scale).toEqual({ control: 36, type: 14 });
273
+ expect(roomyPreset.scale).toEqual({ control: 48, type: 16 });
274
+ expect(defaultPreset.scale).toEqual({ control: 40, type: 15 });
275
+ });
276
+ });
@@ -1,6 +1,7 @@
1
1
  import type { ThemeName } from "@tamagui/web";
2
2
  import { defaultIntents } from "./intents";
3
3
  import type { Preset } from "./preset.types";
4
+ import { defaultScaleInputs } from "./scale/scaleInputs";
4
5
 
5
6
  export const defaultPreset: Preset = {
6
7
  theme: "" as ThemeName,
@@ -10,9 +11,6 @@ export const defaultPreset: Preset = {
10
11
  cornerSmoothing: "round",
11
12
  borderWidth: "medium",
12
13
  elevation: "small",
13
- space: "medium",
14
- size: "medium",
15
- density: "comfortable",
16
14
  textAccent: "high",
17
15
  headingFont: "sans-serif",
18
16
  bodyFont: "sans-serif",
@@ -34,6 +32,7 @@ export const defaultPreset: Preset = {
34
32
  },
35
33
  intents: defaultIntents,
36
34
  tints: ["orange", "blue", "purple", "pink"] as ThemeName[],
35
+ scale: defaultScaleInputs,
37
36
  };
38
37
 
39
38
  export const boldPreset: Preset = {
@@ -44,9 +43,6 @@ export const boldPreset: Preset = {
44
43
  cornerSmoothing: "round",
45
44
  borderWidth: "none",
46
45
  elevation: "none",
47
- space: "small",
48
- size: "medium",
49
- density: "comfortable",
50
46
  textAccent: "high",
51
47
  headingFont: "sans-serif",
52
48
  bodyFont: "sans-serif",
@@ -71,10 +67,8 @@ export const boldPreset: Preset = {
71
67
  };
72
68
 
73
69
  /**
74
- * Marketing / hero surface preset (D-11 hero-H1 dial, owner ruling
75
- * 2026-08-12). Identical to `defaultPreset` except the page title returns to
76
- * the display step ($10 = 64px): product screens are wayfinding and take the
77
- * moderate step by default, so the loud scale has to be ASKED for.
70
+ * Marketing / hero preset: default knobs with the display title step
71
+ * ($12 = 52px at type 15). Product presets keep the moderate step.
78
72
  *
79
73
  * @example Whole marketing app
80
74
  * createDefaultThemeConfig({ presets: { default: defaultPreset, hero: heroPreset } })
@@ -88,3 +82,21 @@ export const heroPreset: Preset = {
88
82
  ...defaultPreset,
89
83
  knobs: { ...defaultPreset.knobs, pageTitleScale: "display" },
90
84
  };
85
+
86
+ /**
87
+ * `defaultPreset` on a tighter token scale ($4 = 36, body 14). Knobs are
88
+ * identical to the default's: every px it saves comes from token values.
89
+ */
90
+ export const densePreset: Preset = {
91
+ ...defaultPreset,
92
+ scale: { control: 36, type: 14 },
93
+ };
94
+
95
+ /**
96
+ * `defaultPreset` on a roomier token scale ($4 = 48, body 16). Knobs are
97
+ * identical to the default's: every px it adds comes from token values.
98
+ */
99
+ export const roomyPreset: Preset = {
100
+ ...defaultPreset,
101
+ scale: { control: 48, type: 16 },
102
+ };
@@ -3,6 +3,8 @@ import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { afterAll, describe, expect, it } from "vitest";
6
+ import { generateScale } from "./scale/generateScale";
7
+ import { defaultScaleInputs } from "./scale/scaleInputs";
6
8
  import type { BorderRadius } from "./knobs";
7
9
  import { radiusOverrideFor, resolveRadiusClass, resolveRadiusForPart } from "./radiusClass";
8
10
  import {
@@ -12,7 +14,7 @@ import {
12
14
  RADIUS_RESOLUTION_STOPS,
13
15
  RADIUS_RESOLUTION_TABLE,
14
16
  } from "./radiusClassTable.generated";
15
- import { capContainerRadius } from "./resolveKnobs";
17
+ import { capContainerRadius, panelPaddingPx } from "./resolveKnobs";
16
18
 
17
19
  // The generator is the doctrine's reading end — the spec imports it directly so a
18
20
  // doctrine edit and a generated-table edit are compared in the same run. Vite
@@ -25,15 +27,29 @@ const doctrine = registry.readRadiusClassDoctrine();
25
27
 
26
28
  const STOPS = ["none", "small", "medium", "large", "full"] as const;
27
29
 
28
- // Sample geometry: a 44px control (h/2 = 22) and the three space-stop paddings
29
- // the doctrine names (13/18/32 — panelPaddingPxMap).
30
- const H = 44;
31
- const PADS = { small: 13, medium: 18, large: 32 } as const;
30
+ const scale = generateScale(defaultScaleInputs).tokens;
31
+ const H = scale.size.$4;
32
+ const PADS = { small: scale.space.$3, medium: scale.space.$4, large: scale.space.$6 } as const;
33
+ const radiusStops = {
34
+ none: scale.radius[0],
35
+ small: scale.radius[2],
36
+ medium: scale.radius[4],
37
+ large: scale.radius[6],
38
+ full: scale.radius[12],
39
+ };
40
+
41
+ const containerStops = {
42
+ none: 0,
43
+ small: scale.radius[4],
44
+ medium: scale.radius[6],
45
+ large: scale.radius[8],
46
+ full: scale.radius[12],
47
+ };
32
48
 
33
49
  describe("the doctrine table, cell by cell (DG-RAD-01..04)", () => {
34
50
  const expected: Record<string, Record<BorderRadius, number>> = {
35
- DEFAULT: { none: 0, small: 5, medium: 9, large: 16, full: 50 },
36
- "CIRCULAR-AT-FULL": { none: 0, small: 5, medium: 9, large: 16, full: H / 2 },
51
+ DEFAULT: radiusStops,
52
+ "CIRCULAR-AT-FULL": { ...radiusStops, full: H / 2 },
37
53
  BINARY: { none: 0, small: H / 2, medium: H / 2, large: H / 2, full: H / 2 },
38
54
  };
39
55
 
@@ -49,11 +65,12 @@ describe("the doctrine table, cell by cell (DG-RAD-01..04)", () => {
49
65
  }
50
66
  }
51
67
 
52
- const containerCap: Record<keyof typeof PADS, Record<BorderRadius, number>> = {
53
- small: { none: 0, small: 5, medium: 9, large: 13, full: 13 },
54
- medium: { none: 0, small: 5, medium: 9, large: 16, full: 18 },
55
- large: { none: 0, small: 5, medium: 9, large: 16, full: 32 },
56
- };
68
+ const containerCap = Object.fromEntries(
69
+ Object.entries(PADS).map(([name, padding]) => [
70
+ name,
71
+ Object.fromEntries(STOPS.map((stop) => [stop, Math.min(containerStops[stop], padding)])),
72
+ ]),
73
+ ) as Record<keyof typeof PADS, Record<BorderRadius, number>>;
57
74
 
58
75
  for (const space of Object.keys(containerCap) as (keyof typeof PADS)[]) {
59
76
  for (const stop of STOPS) {
@@ -66,36 +83,38 @@ describe("the doctrine table, cell by cell (DG-RAD-01..04)", () => {
66
83
  }
67
84
  });
68
85
 
69
- describe("capContainerRadius agrees with the CONTAINER-CAP column (DG-RAD-04, unchanged)", () => {
86
+ describe("capContainerRadius agrees with the CONTAINER-CAP column (DG-RAD-04)", () => {
70
87
  // capContainerRadius returns the TOKEN while it fits and the padding px once
71
88
  // it would exceed; mirror the token px scale to compare resolutions.
72
- const tokenPx: Record<string, number> = { $0: 0, $2: 5, $4: 9, $6: 16, $12: 50 };
89
+ const tokenPx: Record<string, number> = {
90
+ $0: scale.radius[0],
91
+ $2: scale.radius[2],
92
+ $4: scale.radius[4],
93
+ $6: scale.radius[6],
94
+ $8: scale.radius[8],
95
+ $12: scale.radius[12],
96
+ };
73
97
  const asPx = (value: string | number): number =>
74
98
  typeof value === "number" ? value : tokenPx[value];
75
99
 
76
- for (const space of Object.keys(PADS) as (keyof typeof PADS)[]) {
77
- for (const stop of STOPS) {
78
- it(`at ${stop} under space=${space}`, () => {
79
- expect(asPx(capContainerRadius(stop, space))).toBe(
80
- resolveRadiusClass("CONTAINER-CAP", stop, { paddingPx: PADS[space] }),
81
- );
82
- });
83
- }
100
+ for (const stop of STOPS) {
101
+ it(`at ${stop} on the panelPadding role`, () => {
102
+ expect(asPx(capContainerRadius(stop))).toBe(
103
+ resolveRadiusClass("CONTAINER-CAP", stop, { paddingPx: panelPaddingPx() }),
104
+ );
105
+ });
84
106
  }
85
107
 
86
- it("the cap is the container's own padding at full, under every space", () => {
87
- expect(capContainerRadius("full", "small")).toBe(13);
88
- expect(capContainerRadius("full", "medium")).toBe(18);
89
- expect(capContainerRadius("full", "large")).toBe(32);
108
+ it("the cap is the container's own padding at full", () => {
109
+ expect(capContainerRadius("full")).toBe(panelPaddingPx());
110
+ expect(panelPaddingPx()).toBe(scale.space.$4);
90
111
  });
91
112
 
92
- it("the cap bites at large under space=small (16 > 13) and nowhere else below full", () => {
93
- expect(capContainerRadius("large", "small")).toBe(13);
94
- expect(capContainerRadius("large", "medium")).toBe("$6");
95
- expect(capContainerRadius("large", "large")).toBe("$6");
96
- expect(capContainerRadius("medium", "small")).toBe("$4");
97
- expect(capContainerRadius("small", "small")).toBe("$2");
98
- expect(capContainerRadius("none", "small")).toBe("$0");
113
+ it("the cap bites at large and full on the default scale", () => {
114
+ expect(capContainerRadius("large")).toBe(16);
115
+ expect(capContainerRadius("medium")).toBe("$6");
116
+ expect(capContainerRadius("small")).toBe("$4");
117
+ expect(capContainerRadius("none")).toBe("$0");
99
118
  });
100
119
  });
101
120
 
@@ -235,20 +254,20 @@ describe("the override layer wins over the class layer (DG-RAD-05)", () => {
235
254
  heightPx: glyph,
236
255
  });
237
256
  expect(resolved.source).toBe("override");
238
- expect(resolved.px).toBe(stop === "none" ? 0 : 5);
257
+ expect(resolved.px).toBe(stop === "none" ? 0 : scale.radius[2]);
239
258
  expect(resolved.px).not.toBe(glyph / 2);
240
259
  expect(resolved.overrideWhy).toMatch(/reads as a radio/);
241
260
  }
242
261
  });
243
262
 
244
- it("Checkbox at full is the clamp (5), not the kin circle (10) and not the token (50)", () => {
263
+ it("Checkbox at full uses the small token clamp", () => {
245
264
  const resolved = resolveRadiusForPart({
246
265
  part: "Checkbox",
247
266
  radiusClass: "CIRCULAR-AT-FULL",
248
267
  stop: "full",
249
268
  heightPx: 20,
250
269
  });
251
- expect(resolved.px).toBe(5);
270
+ expect(resolved.px).toBe(scale.radius[2]);
252
271
  });
253
272
 
254
273
  it("the Radio disc is round at every stop, including none (R2c — R2b inverted)", () => {
@@ -274,7 +293,7 @@ describe("the override layer wins over the class layer (DG-RAD-05)", () => {
274
293
  it("an UNDECLARED part still resolves by its class", () => {
275
294
  const resolved = resolveRadiusForPart({ part: "Button", radiusClass: "DEFAULT", stop: "full" });
276
295
  expect(resolved.source).toBe("class");
277
- expect(resolved.px).toBe(50);
296
+ expect(resolved.px).toBe(scale.radius[12]);
278
297
  expect(resolved.overrideWhy).toBeUndefined();
279
298
  // and with no part at all, the class is the resolution
280
299
  expect(resolveRadiusForPart({ radiusClass: "BINARY", stop: "medium", heightPx: H }).px).toBe(
@@ -340,7 +359,7 @@ describe("open adjudications are REPORTED, never silently skipped (MPO-56 AC-6)"
340
359
  });
341
360
 
342
361
  describe("closed R2 leftovers (RULINGS-2026-08-28)", () => {
343
- it("a single-line control at full is the DEFAULT pill token (50), not clampRadiusForControl's 16 (R2d)", () => {
362
+ it("a single-line control at full is the DEFAULT pill token, not clampRadiusForControl's 16 (R2d)", () => {
344
363
  const resolved = resolveRadiusForPart({
345
364
  part: "Input",
346
365
  radiusClass: "DEFAULT",
@@ -348,7 +367,7 @@ describe("closed R2 leftovers (RULINGS-2026-08-28)", () => {
348
367
  heightPx: 44,
349
368
  });
350
369
  expect(resolved.source).toBe("class");
351
- expect(resolved.px).toBe(50);
370
+ expect(resolved.px).toBe(scale.radius[12]);
352
371
  expect(resolved.px).not.toBe(16);
353
372
  });
354
373
 
@@ -365,10 +384,10 @@ describe("closed R2 leftovers (RULINGS-2026-08-28)", () => {
365
384
  expect(atNone.px).toBe(0);
366
385
  expect(
367
386
  resolveRadiusForPart({ part: "empty icon well", radiusClass: "DEFAULT", stop: "medium" }).px,
368
- ).toBe(9);
387
+ ).toBe(scale.radius[4]);
369
388
  expect(
370
389
  resolveRadiusForPart({ part: "empty icon well", radiusClass: "DEFAULT", stop: "full" }).px,
371
- ).toBe(50);
390
+ ).toBe(scale.radius[12]);
372
391
  });
373
392
 
374
393
  it("BINARY at none is 0 for thumb AND track — they square together (R2f)", () => {
@@ -35,6 +35,9 @@
35
35
  */
36
36
 
37
37
  import type { BorderRadius } from "./knobs";
38
+ import { getActiveScaleTokens } from "./scale/activeScale";
39
+ import { generateScale } from "./scale/generateScale";
40
+ import { RADIUS_KEYS, v5ScaleInputs } from "./scale/scaleInputs";
38
41
  import {
39
42
  RADIUS_OVERRIDE_TABLE,
40
43
  RADIUS_RESOLUTION_COMPOSITION,
@@ -48,7 +51,7 @@ import {
48
51
  * The radius classes whose parts stay round at `borderRadius:none`.
49
52
  *
50
53
  * - `R-PILL` — a pill/circle at every radius value (ornamental dots, badge
51
- * indicators). Size still tracks the size knob. Icon wells are DEFAULT,
54
+ * indicators). Size still tracks the size token. Icon wells are DEFAULT,
52
55
  * not R-PILL (R2e) — unless the well IS a count badge.
53
56
  * - `R-IDENTITY` — the shape IS the part's identity (Avatar, brand marks,
54
57
  * Radio disc). Membership is enumerated in the spec table and enforced
@@ -102,15 +105,25 @@ export function radiusResolutionProps(radiusClass: "BINARY"): RadiusResolutionDe
102
105
  // ── Radius resolution (DG-RAD-01..05) ─────────────────────────
103
106
 
104
107
  /**
108
+ * Doctrine px cells are v5 `$radius` values; they resolve to the installed
109
+ * scale's radius at the same key.
110
+ *
105
111
  * The geometry a doctrine cell may need. `h/2` cells need the part's own
106
112
  * painted height (shorter edge); CONTAINER-CAP cells need the container's own
107
- * padding at the current space stop (13/18/32 — `panelPaddingPxMap`).
113
+ * padding (the `panelPadding` role on the installed scale, `panelPaddingPx()`).
108
114
  */
109
115
  export interface RadiusClassGeometry {
110
116
  heightPx?: number;
111
117
  paddingPx?: number;
112
118
  }
113
119
 
120
+ const doctrineRadius = generateScale(v5ScaleInputs).tokens.radius;
121
+
122
+ function doctrineRadiusPx(px: number): number {
123
+ const key = RADIUS_KEYS.find((key) => key !== "true" && doctrineRadius[key] === px);
124
+ return key === undefined ? px : getActiveScaleTokens().radius[key];
125
+ }
126
+
114
127
  function resolveCell(
115
128
  cell: RadiusResolutionCell,
116
129
  geometry: RadiusClassGeometry,
@@ -118,7 +131,7 @@ function resolveCell(
118
131
  ): number {
119
132
  switch (cell.kind) {
120
133
  case "px":
121
- return cell.px;
134
+ return doctrineRadiusPx(cell.px);
122
135
  case "half-height": {
123
136
  if (geometry.heightPx === undefined || geometry.heightPx <= 0) {
124
137
  throw new Error(
@@ -133,7 +146,7 @@ function resolveCell(
133
146
  `resolveRadiusClass: ${where} caps at the container's own padding and needs geometry.paddingPx`,
134
147
  );
135
148
  }
136
- return Math.min(cell.tokenPx, geometry.paddingPx);
149
+ return Math.min(doctrineRadiusPx(cell.tokenPx), geometry.paddingPx);
137
150
  }
138
151
  case "padding": {
139
152
  if (geometry.paddingPx === undefined || geometry.paddingPx < 0) {
@@ -53,9 +53,9 @@ export const RADIUS_RESOLUTION_TABLE = {
53
53
  },
54
54
  "CONTAINER-CAP": {
55
55
  none: { kind: "px", px: 0 },
56
- small: { kind: "min-token-padding", tokenPx: 5 },
57
- medium: { kind: "min-token-padding", tokenPx: 9 },
58
- large: { kind: "min-token-padding", tokenPx: 16 },
56
+ small: { kind: "min-token-padding", tokenPx: 9 },
57
+ medium: { kind: "min-token-padding", tokenPx: 16 },
58
+ large: { kind: "min-token-padding", tokenPx: 22 },
59
59
  full: { kind: "padding" },
60
60
  },
61
61
  } as const satisfies Record<string, RadiusResolutionRow>;