@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.
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
@@ -3,20 +3,23 @@
3
3
  *
4
4
  * Stored numbers are the source of truth. Derived projections (heights,
5
5
  * recipes, box/type/icon variants, touch heights, radii) are never
6
- * handwritten at a call site. The DEFAULT ladder is the measured Tamagui
7
- * v5 table (`tokenTable`): padX / font / icon / radius per size token,
8
- * gap == radius at every stop. A linear ratio is only the fallback when
9
- * a theme sets `{ baseHeight }` and drops the table — it is not the
10
- * default ladder and is not a signed-off divergence (MPO-46 / R6).
6
+ * handwritten at a call site. The default inputs are READ off the generated
7
+ * token scale (`scale/generateScale.ts`): height = `$size`, padX = `$space`,
8
+ * radius (and gap) = `$radius` at the same key, so a raw `$size`/`$space`
9
+ * prop and the recipe at that key are the same number. `{ control }` (and
10
+ * `{ type }`) regenerate every table from a new scale.
11
11
  *
12
12
  * Radius steps with the size token. The borderRadius knob overrides the
13
13
  * painted corners while internal gap stays on the size recipe. `radiusPx`
14
- * on inputs is the $4 / medium default (9).
14
+ * on inputs is `$radius.true`, the $4 / medium default.
15
15
  *
16
16
  * Tamagui `createThemes` / theme-builder still emits COLOR palettes only.
17
17
  * Do not stuff recipe keys into `getTheme()` / `themes`.
18
18
  */
19
19
 
20
+ import { scaleRadiusPx } from "./scale/activeScale";
21
+ import { generateScale } from "./scale/generateScale";
22
+ import { SIZE_RECIPE_TOKENS, type SizeRecipeToken, defaultScaleInputs } from "./scale/scaleInputs";
20
23
  import {
21
24
  cloneTileSizeInputs,
22
25
  generateTileSizeLadder,
@@ -25,22 +28,10 @@ import {
25
28
  type TileSizeLadder,
26
29
  } from "./tileSizes";
27
30
 
28
- export const SIZE_RECIPE_TOKENS = [
29
- "$1",
30
- "$1.5",
31
- "$2",
32
- "$2.5",
33
- "$3",
34
- "$3.5",
35
- "$4",
36
- "$true",
37
- "$5",
38
- "$6",
39
- ] as const;
40
-
41
- export type SizeRecipeToken = (typeof SIZE_RECIPE_TOKENS)[number];
42
-
43
- export type SizeRecipeFamilyName = "control" | "controlCompact";
31
+ export { SIZE_RECIPE_TOKENS, SIZE_TOKEN_EXPONENTS } from "./scale/scaleInputs";
32
+ export type { SizeRecipeToken } from "./scale/scaleInputs";
33
+
34
+ export type SizeRecipeFamilyName = "control" | "chip" | "menuRow" | "tab";
44
35
  /** @deprecated Use `SizeRecipeFamilyName`. */
45
36
  export type RecipeFamilyName = SizeRecipeFamilyName;
46
37
 
@@ -61,49 +52,44 @@ export type SizeRecipeTokenChannels = {
61
52
 
62
53
  export type SizeRecipeTokenTable = Partial<Record<SizeRecipeToken, SizeRecipeTokenChannels>>;
63
54
 
55
+ const defaultScale = generateScale(defaultScaleInputs);
56
+
64
57
  /**
65
- * borderRadius knob stops (px). Mirrors the v5 radius tokens the knob
66
- * already resolves ($0/$2/$4/$6/$12 = 0/5/9/16/50). These shape overrides
67
- * do not retarget the control's internal gap.
58
+ * borderRadius knob stops (px): the `$radius` tokens the knob resolves
59
+ * ($0/$2/$4/$6/$12). These shape overrides do not retarget the control's
60
+ * internal gap.
68
61
  */
69
62
  export const SIZE_RECIPE_RADIUS_STOPS = {
70
- none: 0,
71
- small: 5,
72
- medium: 9,
73
- large: 16,
74
- full: 50,
63
+ none: defaultScale.tokens.radius[0],
64
+ small: defaultScale.tokens.radius[2],
65
+ medium: defaultScale.tokens.radius[4],
66
+ large: defaultScale.tokens.radius[6],
67
+ full: defaultScale.tokens.radius[12],
75
68
  } as const;
76
69
 
77
70
  export type SizeRecipeRadiusStop = keyof typeof SIZE_RECIPE_RADIUS_STOPS;
78
71
 
79
72
  export type SizeRecipeInputs = {
80
- baseHeight: number;
81
- stepRatio: number;
73
+ /** `$size.$4` px of the scale the tables were generated from (`ScaleInputs.control`). */
74
+ control: number;
75
+ /** Body `$true` px of that scale (`ScaleInputs.type`); the label/icon ladders follow it. */
76
+ type: number;
82
77
  tokenSteps: Record<string, number>;
83
- /** Absolute desktop px. When omitted, heights come from baseHeight * stepRatio ** step (or sizeFactors). */
84
- tokenHeights?: Record<string, number>;
85
- /**
86
- * Optional per-token scale relative to `baseHeight`. Used only when
87
- * `tokenHeights` is omitted. Factors are `stepRatio ** exponent` otherwise.
88
- */
89
- sizeFactors?: Partial<Record<SizeRecipeToken, number>>;
90
- /**
91
- * Measured per-token channels (padX / font / icon / radius). When
92
- * present, enumerable recipes read this table and set gap = radius.
93
- * `{ baseHeight }` without an explicit tokenTable drops it so the
94
- * geometric fallback can run.
95
- */
96
- tokenTable?: SizeRecipeTokenTable;
97
- /** Same-height tighter family (Chip / `recipeFamily="controlCompact"`). */
98
- compactTokenTable?: SizeRecipeTokenTable;
78
+ /** Desktop px per recipe key: `$size` at the key. */
79
+ tokenHeights: Record<string, number>;
80
+ /** Per-token channels (padX / font / icon / radius); recipes set gap = radius. */
81
+ tokenTable: SizeRecipeTokenTable;
82
+ /** Same-height tighter family (Chip / `recipeFamily="chip"`). */
83
+ chipTokenTable: SizeRecipeTokenTable;
99
84
  /**
100
85
  * Input padX (one space half-step below the button at the same token).
101
- * $4 = 16 vs the button's 18. Stored on inputs so the generator remains
86
+ * $4 = 14 vs the button's 16. Stored on inputs so the generator remains
102
87
  * the single writer; Input consumes this, not a second handwritten map.
103
88
  */
104
- inputPadX?: Partial<Record<SizeRecipeToken, number>>;
89
+ inputPadX: Partial<Record<SizeRecipeToken, number>>;
90
+ /** Channel / height at $4; sizes recipes for heights off the table (touch, numeric `size`). */
105
91
  ratios: SizeRecipeRatios;
106
- compactRatios: SizeRecipeRatios;
92
+ chipRatios: SizeRecipeRatios;
107
93
  radiusPx: number;
108
94
  touchFloor: number;
109
95
  /**
@@ -167,245 +153,80 @@ export type GeneratedSizeRecipes = {
167
153
  tiles: TileSizeLadder;
168
154
  };
169
155
 
170
- export const SIZE_TOKEN_EXPONENTS: Record<SizeRecipeToken, number> = {
171
- $1: -3,
172
- "$1.5": -2.5,
173
- $2: -2,
174
- "$2.5": -1.5,
175
- $3: -1,
176
- "$3.5": -0.5,
177
- $4: 0,
178
- $true: 0,
179
- $5: 1,
180
- $6: 2,
181
- };
182
-
183
- // Stock Tamagui `$size` px as measured from the live tamagui.dev CSS vars
184
- // (MPO-13 fidelity pass): $1:20 $1.5:24 $2:28 $2.5:32 $3:36 $3.5:40
185
- // $4/$true:44 $5:52 $6:64. The recipe key "$4" and the raw Tamagui token
186
- // `$size.4` resolve to the SAME height — the C1 split (recipe 32 vs token
187
- // 44) is gone.
188
- const DEFAULT_TOKEN_HEIGHTS: Record<string, number> = {
189
- $1: 20,
190
- "$1.5": 24,
191
- $2: 28,
192
- "$2.5": 32,
193
- $3: 36,
194
- "$3.5": 40,
195
- $4: 44,
196
- $true: 44,
197
- $5: 52,
198
- $6: 64,
199
- };
200
-
201
- /** Lossless relative table: the stock Tamagui 20/24/28/32/36/40/44/52/64 ramp at baseHeight 44. */
202
- export const defaultSizeFactors: Record<SizeRecipeToken, number> = {
203
- $1: 20 / 44,
204
- "$1.5": 24 / 44,
205
- $2: 28 / 44,
206
- "$2.5": 32 / 44,
207
- $3: 36 / 44,
208
- "$3.5": 40 / 44,
209
- $4: 1,
210
- $true: 1,
211
- $5: 52 / 44,
212
- $6: 64 / 44,
213
- };
214
-
215
- /**
216
- * Measured Tamagui v5 Button ladder (rendered reference §6.1) plus the
217
- * integer token-scale fills for stops the site did not photograph.
218
- *
219
- * padX = `$space` at the same key (2/4/7/10/13/16/18/24/32).
220
- * radius = `$radius` at the same key (3/5/7/9/10/16); half-steps sit
221
- * halfway on that scale (4/6/8) because v5 has no $1.5/$2.5/$3.5 radius.
222
- * font = Inter body at the same key (11/12/13/14/16/18); half-steps take
223
- * the next integer stop.
224
- * icon steps with size and keeps the existing $4 = 16 contract.
225
- * $2/$3/$4/$6 padX / font / radius are the photographed values (7/13/18/32,
226
- * 12/13/14/18, 5/7/9/16).
227
- */
228
- export const TAMAGUI_CONTROL_SIZE_TABLE: Record<SizeRecipeToken, SizeRecipeTokenChannels> = {
229
- $1: { paddingHorizontal: 2, fontSize: 11, iconSize: 10, radius: 3 },
230
- "$1.5": { paddingHorizontal: 4, fontSize: 12, iconSize: 11, radius: 4 },
231
- $2: { paddingHorizontal: 7, fontSize: 12, iconSize: 12, radius: 5 },
232
- "$2.5": { paddingHorizontal: 10, fontSize: 13, iconSize: 14, radius: 6 },
233
- $3: { paddingHorizontal: 13, fontSize: 13, iconSize: 14, radius: 7 },
234
- "$3.5": { paddingHorizontal: 16, fontSize: 14, iconSize: 15, radius: 8 },
235
- $4: { paddingHorizontal: 18, fontSize: 14, iconSize: 16, radius: 9 },
236
- $true: { paddingHorizontal: 18, fontSize: 14, iconSize: 16, radius: 9 },
237
- $5: { paddingHorizontal: 24, fontSize: 16, iconSize: 18, radius: 10 },
238
- $6: { paddingHorizontal: 32, fontSize: 18, iconSize: 20, radius: 16 },
239
- };
240
-
241
- /**
242
- * Input padX — `getSpace(val, { shift: -1 })` on the v5 space scale
243
- * (rendered reference §6.2). $2/$3/$4 are the photographed 4/10/16;
244
- * $4 is 16 against the button's 18.
245
- */
246
- export const TAMAGUI_INPUT_PADX: Record<SizeRecipeToken, number> = {
247
- $1: 1,
248
- "$1.5": 2,
249
- $2: 4,
250
- "$2.5": 7,
251
- $3: 10,
252
- "$3.5": 13,
253
- $4: 16,
254
- $true: 16,
255
- $5: 21,
256
- $6: 24,
257
- };
258
-
259
- /**
260
- * controlCompact: same height / radius (so gap == radius) as control;
261
- * padX is the Input table (one space half-step tighter); font/icon sit
262
- * one type step below the control table.
263
- */
264
- export const TAMAGUI_COMPACT_SIZE_TABLE: Record<SizeRecipeToken, SizeRecipeTokenChannels> = {
265
- $1: { paddingHorizontal: 1, fontSize: 10, iconSize: 9, radius: 3 },
266
- "$1.5": { paddingHorizontal: 2, fontSize: 11, iconSize: 10, radius: 4 },
267
- $2: { paddingHorizontal: 4, fontSize: 11, iconSize: 11, radius: 5 },
268
- "$2.5": { paddingHorizontal: 7, fontSize: 12, iconSize: 12, radius: 6 },
269
- $3: { paddingHorizontal: 10, fontSize: 12, iconSize: 13, radius: 7 },
270
- "$3.5": { paddingHorizontal: 13, fontSize: 13, iconSize: 14, radius: 8 },
271
- $4: { paddingHorizontal: 16, fontSize: 13, iconSize: 14, radius: 9 },
272
- $true: { paddingHorizontal: 16, fontSize: 13, iconSize: 14, radius: 9 },
273
- $5: { paddingHorizontal: 21, fontSize: 14, iconSize: 16, radius: 10 },
274
- $6: { paddingHorizontal: 24, fontSize: 16, iconSize: 18, radius: 16 },
275
- };
276
-
277
- // Geometric fallback only — used when `{ baseHeight }` drops tokenTable.
278
- // Anchored at the measured $4 so a doubled baseHeight still doubles $4.
279
- const FALLBACK_RATIOS: SizeRecipeRatios = {
280
- padX: 18 / 44,
281
- font: 14 / 44,
282
- icon: 16 / 44,
283
- gap: 9 / 44,
284
- };
285
-
286
- const FALLBACK_COMPACT_RATIOS: SizeRecipeRatios = {
287
- padX: 16 / 44,
288
- font: 13 / 44,
289
- icon: 14 / 44,
290
- gap: 9 / 44,
291
- };
292
-
293
- export const DEFAULT_SIZE_RECIPE_INPUTS: SizeRecipeInputs = {
294
- // Tamagui `$size.true` = 44 — the iOS touch minimum, measured on every
295
- // tamagui.dev button and input (MPO-13 C2).
296
- baseHeight: 44,
297
- stepRatio: 1.25,
298
- tokenSteps: { ...SIZE_TOKEN_EXPONENTS },
299
- tokenHeights: { ...DEFAULT_TOKEN_HEIGHTS },
300
- sizeFactors: { ...defaultSizeFactors },
301
- tokenTable: { ...TAMAGUI_CONTROL_SIZE_TABLE },
302
- compactTokenTable: { ...TAMAGUI_COMPACT_SIZE_TABLE },
303
- inputPadX: { ...TAMAGUI_INPUT_PADX },
304
- ratios: { ...FALLBACK_RATIOS },
305
- compactRatios: { ...FALLBACK_COMPACT_RATIOS },
306
- // Tamagui `$radius.true` = 9 — the $4 / medium default. Per-token
307
- // radii live on tokenTable; this is the knob's medium stop and the
308
- // $4 table row, not a flat radius for every size.
309
- radiusPx: 9,
310
- touchFloor: 44,
311
- };
156
+ export const DEFAULT_SIZE_RECIPE_INPUTS: SizeRecipeInputs = defaultScale.recipeInputs;
312
157
 
313
158
  export const defaultRecipeInputs = DEFAULT_SIZE_RECIPE_INPUTS;
314
159
 
315
160
  export const TOUCH_HEIGHT_FLOOR = DEFAULT_SIZE_RECIPE_INPUTS.touchFloor;
316
161
 
317
- const MD_TOUCH_MIN = 48;
318
-
319
- function cloneTokenTable(table?: SizeRecipeTokenTable): SizeRecipeTokenTable | undefined {
320
- if (!table) return undefined;
162
+ /** Rows of `over` replace rows of `base`; a `$4` override without `$true` carries to `$true`. */
163
+ function mergeTokenTable(
164
+ base: SizeRecipeTokenTable,
165
+ over?: SizeRecipeTokenTable,
166
+ ): SizeRecipeTokenTable {
321
167
  const next: SizeRecipeTokenTable = {};
322
168
  for (const token of SIZE_RECIPE_TOKENS) {
323
- const row = table[token];
169
+ const row = over?.[token] ?? (token === "$true" ? over?.$4 : undefined) ?? base[token];
324
170
  if (row) next[token] = { ...row };
325
171
  }
326
172
  return next;
327
173
  }
328
174
 
329
- function clonePadX(
330
- table?: Partial<Record<SizeRecipeToken, number>>,
331
- ): Partial<Record<SizeRecipeToken, number>> | undefined {
332
- return table ? { ...table } : undefined;
175
+ function mergeTokenHeights(
176
+ base: Record<string, number>,
177
+ over?: Record<string, number>,
178
+ ): Record<string, number> {
179
+ const next = { ...base, ...over };
180
+ if (over?.$4 != null && over.$true == null) next.$true = over.$4;
181
+ return next;
333
182
  }
334
183
 
335
184
  export function cloneSizeRecipeInputs(inputs: SizeRecipeInputs): SizeRecipeInputs {
336
185
  return {
337
186
  ...inputs,
338
187
  tokenSteps: { ...inputs.tokenSteps },
339
- tokenHeights: inputs.tokenHeights ? { ...inputs.tokenHeights } : undefined,
340
- sizeFactors: inputs.sizeFactors ? { ...inputs.sizeFactors } : undefined,
341
- tokenTable: cloneTokenTable(inputs.tokenTable),
342
- compactTokenTable: cloneTokenTable(inputs.compactTokenTable),
343
- inputPadX: clonePadX(inputs.inputPadX),
188
+ tokenHeights: { ...inputs.tokenHeights },
189
+ tokenTable: mergeTokenTable(inputs.tokenTable),
190
+ chipTokenTable: mergeTokenTable(inputs.chipTokenTable),
191
+ inputPadX: { ...inputs.inputPadX },
344
192
  ratios: { ...inputs.ratios },
345
- compactRatios: { ...inputs.compactRatios },
193
+ chipRatios: { ...inputs.chipRatios },
346
194
  tile: inputs.tile ? cloneTileSizeInputs(inputs.tile) : undefined,
347
195
  };
348
196
  }
349
197
 
350
198
  /**
351
- * Fill defaults. `{ baseHeight: 36 }` drops handwritten tokenHeights /
352
- * sizeFactors / token tables so every token regenerates from stepRatio.
199
+ * Fill defaults. `{ control }` / `{ type }` regenerate every table from a
200
+ * new scale; explicit tables, heights and ratios then merge over it per row.
353
201
  */
354
202
  export function resolveSizeRecipeInputs(partial: Partial<SizeRecipeInputs> = {}): SizeRecipeInputs {
355
- const scalingOverride =
356
- partial.baseHeight != null || partial.stepRatio != null || partial.tokenSteps != null;
357
- const hasTokenHeights = Object.prototype.hasOwnProperty.call(partial, "tokenHeights");
358
- const hasSizeFactors = Object.prototype.hasOwnProperty.call(partial, "sizeFactors");
359
- const hasTokenTable = Object.prototype.hasOwnProperty.call(partial, "tokenTable");
360
- const hasCompactTable = Object.prototype.hasOwnProperty.call(partial, "compactTokenTable");
361
- const hasInputPadX = Object.prototype.hasOwnProperty.call(partial, "inputPadX");
203
+ const base =
204
+ partial.control == null && partial.type == null
205
+ ? DEFAULT_SIZE_RECIPE_INPUTS
206
+ : generateScale({
207
+ ...defaultScaleInputs,
208
+ control: partial.control ?? DEFAULT_SIZE_RECIPE_INPUTS.control,
209
+ type: partial.type ?? DEFAULT_SIZE_RECIPE_INPUTS.type,
210
+ }).recipeInputs;
362
211
 
363
212
  const resolved: SizeRecipeInputs = {
364
- baseHeight: partial.baseHeight ?? DEFAULT_SIZE_RECIPE_INPUTS.baseHeight,
365
- stepRatio: partial.stepRatio ?? DEFAULT_SIZE_RECIPE_INPUTS.stepRatio,
366
- tokenSteps: { ...DEFAULT_SIZE_RECIPE_INPUTS.tokenSteps, ...partial.tokenSteps },
367
- ratios: { ...DEFAULT_SIZE_RECIPE_INPUTS.ratios, ...partial.ratios },
368
- compactRatios: { ...DEFAULT_SIZE_RECIPE_INPUTS.compactRatios, ...partial.compactRatios },
369
- radiusPx: partial.radiusPx ?? DEFAULT_SIZE_RECIPE_INPUTS.radiusPx,
370
- touchFloor: partial.touchFloor ?? DEFAULT_SIZE_RECIPE_INPUTS.touchFloor,
213
+ control: base.control,
214
+ type: base.type,
215
+ tokenSteps: { ...base.tokenSteps, ...partial.tokenSteps },
216
+ tokenHeights: mergeTokenHeights(base.tokenHeights, partial.tokenHeights),
217
+ tokenTable: mergeTokenTable(base.tokenTable, partial.tokenTable),
218
+ chipTokenTable: mergeTokenTable(base.chipTokenTable, partial.chipTokenTable),
219
+ inputPadX: { ...base.inputPadX, ...partial.inputPadX },
220
+ ratios: { ...base.ratios, ...partial.ratios },
221
+ chipRatios: { ...base.chipRatios, ...partial.chipRatios },
222
+ radiusPx: partial.radiusPx ?? base.radiusPx,
223
+ touchFloor: partial.touchFloor ?? base.touchFloor,
371
224
  };
372
225
 
373
226
  if (partial.tile !== undefined) {
374
227
  resolved.tile = partial.tile ? resolveTileSizeInputs(partial.tile) : undefined;
375
228
  }
376
229
 
377
- if (hasTokenHeights) {
378
- resolved.tokenHeights = partial.tokenHeights ? { ...partial.tokenHeights } : undefined;
379
- if (hasSizeFactors) {
380
- resolved.sizeFactors = partial.sizeFactors ? { ...partial.sizeFactors } : undefined;
381
- }
382
- } else if (hasSizeFactors) {
383
- resolved.sizeFactors = partial.sizeFactors ? { ...partial.sizeFactors } : undefined;
384
- } else if (!scalingOverride) {
385
- resolved.tokenHeights = { ...DEFAULT_SIZE_RECIPE_INPUTS.tokenHeights };
386
- resolved.sizeFactors = { ...DEFAULT_SIZE_RECIPE_INPUTS.sizeFactors };
387
- }
388
-
389
- if (hasTokenTable) {
390
- resolved.tokenTable = partial.tokenTable ? cloneTokenTable(partial.tokenTable) : undefined;
391
- } else if (!scalingOverride) {
392
- resolved.tokenTable = cloneTokenTable(DEFAULT_SIZE_RECIPE_INPUTS.tokenTable);
393
- }
394
-
395
- if (hasCompactTable) {
396
- resolved.compactTokenTable = partial.compactTokenTable
397
- ? cloneTokenTable(partial.compactTokenTable)
398
- : undefined;
399
- } else if (!scalingOverride) {
400
- resolved.compactTokenTable = cloneTokenTable(DEFAULT_SIZE_RECIPE_INPUTS.compactTokenTable);
401
- }
402
-
403
- if (hasInputPadX) {
404
- resolved.inputPadX = partial.inputPadX ? clonePadX(partial.inputPadX) : undefined;
405
- } else if (!scalingOverride) {
406
- resolved.inputPadX = clonePadX(DEFAULT_SIZE_RECIPE_INPUTS.inputPadX);
407
- }
408
-
409
230
  return resolved;
410
231
  }
411
232
 
@@ -416,12 +237,7 @@ function clampHeight(value: number): number {
416
237
  }
417
238
 
418
239
  function heightForToken(token: string, inputs: SizeRecipeInputs): number {
419
- const explicitPx = inputs.tokenHeights?.[token];
420
- if (explicitPx != null) return clampHeight(explicitPx);
421
- const factor = inputs.sizeFactors?.[token as SizeRecipeToken];
422
- if (factor != null) return clampHeight(inputs.baseHeight * factor);
423
- const step = inputs.tokenSteps[token] ?? 0;
424
- return clampHeight(inputs.baseHeight * inputs.stepRatio ** step);
240
+ return clampHeight(inputs.tokenHeights[token] ?? inputs.control);
425
241
  }
426
242
 
427
243
  export function sizeRecipeFromHeight(
@@ -456,7 +272,7 @@ export function applyRadiusStop<T extends { gap: number }>(
456
272
  recipe: T,
457
273
  stop: SizeRecipeRadiusStop,
458
274
  ): T {
459
- return { ...recipe, gap: SIZE_RECIPE_RADIUS_STOPS[stop] };
275
+ return { ...recipe, gap: scaleRadiusPx(SIZE_RECIPE_RADIUS_STOPS[stop]) };
460
276
  }
461
277
 
462
278
  /** Knob fragment token → stop name. Default medium is `$4`. */
@@ -482,7 +298,7 @@ export function resolveControlRadius(
482
298
  if (stop === "medium") {
483
299
  return { radius: recipe.gap, gap: recipe.gap };
484
300
  }
485
- const px = SIZE_RECIPE_RADIUS_STOPS[stop];
301
+ const px = scaleRadiusPx(SIZE_RECIPE_RADIUS_STOPS[stop]);
486
302
  return { radius: px, gap: recipe.gap };
487
303
  }
488
304
 
@@ -491,32 +307,9 @@ function generateTouchHeights(
491
307
  inputs: SizeRecipeInputs,
492
308
  tokens: readonly string[],
493
309
  ): Record<string, number> {
494
- const { touchFloor, tokenSteps } = inputs;
495
- const mdDesktop = heights.$4 ?? inputs.baseHeight;
496
- const floored: Record<string, number> = {};
497
- for (const token of tokens) {
498
- floored[token] = Math.max(heights[token], touchFloor);
499
- }
500
-
501
- let mdTouch = Math.max(floored.$4 ?? touchFloor, MD_TOUCH_MIN);
502
- const below = tokens.filter((token) => (tokenSteps[token] ?? 0) < 0);
503
- const maxBelow =
504
- below.length > 0 ? Math.max(...below.map((token) => floored[token])) : touchFloor;
505
- if (mdTouch <= maxBelow) {
506
- const nextSmaller = heights.$3 ?? mdDesktop;
507
- mdTouch = maxBelow + Math.max(mdDesktop - nextSmaller, 1);
508
- }
509
-
510
310
  const heightsTouch: Record<string, number> = {};
511
311
  for (const token of tokens) {
512
- const step = tokenSteps[token] ?? 0;
513
- if (step < 0) {
514
- heightsTouch[token] = floored[token];
515
- } else if (step === 0) {
516
- heightsTouch[token] = mdTouch;
517
- } else {
518
- heightsTouch[token] = Math.max(floored[token], mdTouch + (heights[token] - mdDesktop));
519
- }
312
+ heightsTouch[token] = Math.max(heights[token], inputs.touchFloor);
520
313
  }
521
314
  return heightsTouch;
522
315
  }
@@ -533,13 +326,14 @@ export function generateSizeRecipeTables(
533
326
  inputs: SizeRecipeInputs,
534
327
  ratios: SizeRecipeRatios = inputs.ratios,
535
328
  table: SizeRecipeTokenTable | undefined = inputs.tokenTable,
329
+ heightOf: (token: SizeRecipeToken) => number = (token) => heightForToken(token, inputs),
536
330
  ): SizeRecipeTables {
537
331
  const tokens = SIZE_RECIPE_TOKENS;
538
332
  const heights: Record<string, number> = {};
539
333
  for (const token of tokens) {
540
- heights[token] = heightForToken(token, inputs);
334
+ heights[token] = heightOf(token);
541
335
  }
542
- if (inputs.tokenHeights?.$true == null) {
336
+ if (inputs.tokenHeights.$true == null) {
543
337
  heights.$true = heights.$4;
544
338
  }
545
339
 
@@ -559,7 +353,7 @@ export function generateSizeRecipeTables(
559
353
  : sizeRecipeFromHeight(heights[token], ratios);
560
354
  const radius = channels ? channels.radius : recipe.gap;
561
355
  recipes[token] = recipe;
562
- recipesTouch[token] = sizeRecipeFromHeight(touchHeights[token], ratios);
356
+ recipesTouch[token] = { ...recipe, height: touchHeights[token] };
563
357
  radii[token] = radius;
564
358
  boxVariants[token] = {
565
359
  height: recipe.height,
@@ -586,19 +380,83 @@ export function generateSizeRecipeTables(
586
380
  };
587
381
  }
588
382
 
383
+ /** Recipe keys in ladder order; `$true` reads as `$4`. */
384
+ const RECIPE_LADDER = SIZE_RECIPE_TOKENS.filter((token) => token !== "$true");
385
+
386
+ /** The recipe key one step below `token` on the ladder ($4 / $true -> $3.5, floor $1). */
387
+ export function recipeKeyBelow(token: SizeRecipeToken): SizeRecipeToken {
388
+ const index = RECIPE_LADDER.indexOf(token === "$true" ? "$4" : token);
389
+ return RECIPE_LADDER[Math.max(0, index - 1)];
390
+ }
391
+
392
+ /** The recipe key one step above `token` on the ladder ($4 / $true -> $5, ceiling $6). */
393
+ export function recipeKeyAbove(token: SizeRecipeToken): SizeRecipeToken {
394
+ const index = RECIPE_LADDER.indexOf(token === "$true" ? "$4" : token);
395
+ return RECIPE_LADDER[Math.min(RECIPE_LADDER.length - 1, index + 1)];
396
+ }
397
+
398
+ function ratiosOf(table: SizeRecipeTokenTable, height: number): SizeRecipeRatios {
399
+ const row = table.$4;
400
+ if (!row) return { padX: 0, font: 0, icon: 0, gap: 0 };
401
+ return {
402
+ padX: row.paddingHorizontal / height,
403
+ font: row.fontSize / height,
404
+ icon: row.iconSize / height,
405
+ gap: row.radius / height,
406
+ };
407
+ }
408
+
409
+ /**
410
+ * Row families read off the control table, never a second px source:
411
+ * - `menuRow` (menu / listbox / command rows): height one key below the
412
+ * control token (36 at `$4`), padX the `$3` space, type and icon the
413
+ * token's own.
414
+ * - `tab`: height and padX one key below, type and icon the token's own.
415
+ * Both lift to `touchFloor` on touch like every family.
416
+ */
417
+ function deriveRowFamilyTables(inputs: SizeRecipeInputs): {
418
+ menuRow: SizeRecipeTokenTable;
419
+ tab: SizeRecipeTokenTable;
420
+ } {
421
+ const menuRow: SizeRecipeTokenTable = {};
422
+ const tab: SizeRecipeTokenTable = {};
423
+ const rowPadX = inputs.tokenTable.$3?.paddingHorizontal;
424
+ for (const token of SIZE_RECIPE_TOKENS) {
425
+ const own = channelsForToken(token, inputs.tokenTable);
426
+ const below = channelsForToken(recipeKeyBelow(token), inputs.tokenTable);
427
+ if (!own || !below) continue;
428
+ menuRow[token] = { ...own, paddingHorizontal: rowPadX ?? below.paddingHorizontal };
429
+ tab[token] = { ...own, paddingHorizontal: below.paddingHorizontal };
430
+ }
431
+ return { menuRow, tab };
432
+ }
433
+
589
434
  export function createRecipeFamilies(
590
435
  inputs: SizeRecipeInputs | Partial<SizeRecipeInputs> = {},
591
436
  ): RecipeFamilies {
592
437
  const resolved = cloneSizeRecipeInputs(resolveSizeRecipeInputs(inputs));
593
438
  const control = generateSizeRecipeTables(resolved, resolved.ratios, resolved.tokenTable);
594
- const controlCompact = generateSizeRecipeTables(
439
+ const chip = generateSizeRecipeTables(resolved, resolved.chipRatios, resolved.chipTokenTable);
440
+ const rows = deriveRowFamilyTables(resolved);
441
+ const heightBelow = (token: SizeRecipeToken) => heightForToken(recipeKeyBelow(token), resolved);
442
+ const rowHeight = heightBelow("$4");
443
+ const menuRow = generateSizeRecipeTables(
444
+ resolved,
445
+ ratiosOf(rows.menuRow, rowHeight),
446
+ rows.menuRow,
447
+ heightBelow,
448
+ );
449
+ const tab = generateSizeRecipeTables(
595
450
  resolved,
596
- resolved.compactRatios,
597
- resolved.compactTokenTable,
451
+ ratiosOf(rows.tab, rowHeight),
452
+ rows.tab,
453
+ heightBelow,
598
454
  );
599
455
  return {
600
456
  control: { ...control, name: "control" },
601
- controlCompact: { ...controlCompact, name: "controlCompact" },
457
+ chip: { ...chip, name: "chip" },
458
+ menuRow: { ...menuRow, name: "menuRow" },
459
+ tab: { ...tab, name: "tab" },
602
460
  };
603
461
  }
604
462