@multiplatform.one/components 7.28.1 → 7.29.1

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 (328) hide show
  1. package/dist/cjs/Accordion/index.cjs +26 -17
  2. package/dist/cjs/Accordion/index.native.js +36 -27
  3. package/dist/cjs/Accordion/index.native.js.map +1 -1
  4. package/dist/cjs/Heading.cjs +74 -30
  5. package/dist/cjs/Heading.native.js +81 -30
  6. package/dist/cjs/Heading.native.js.map +1 -1
  7. package/dist/cjs/ProgressSteps/index.native.js.map +1 -1
  8. package/dist/cjs/ScrollView/index.native.js +4 -0
  9. package/dist/cjs/ScrollView/index.native.js.map +1 -1
  10. package/dist/cjs/SortableList/SortableHandle.cjs +1 -1
  11. package/dist/cjs/SortableList/SortableHandle.native.js +1 -1
  12. package/dist/cjs/Text.cjs +58 -21
  13. package/dist/cjs/Text.native.js +62 -21
  14. package/dist/cjs/Text.native.js.map +1 -1
  15. package/dist/cjs/Toast/index.native.js.map +1 -1
  16. package/dist/cjs/charts/ChartDataView.cjs +2 -1
  17. package/dist/cjs/charts/ChartDataView.native.js +2 -1
  18. package/dist/cjs/charts/ChartDataView.native.js.map +1 -1
  19. package/dist/cjs/code/CodeBlock/index.cjs +8 -8
  20. package/dist/cjs/code/CodeBlock/index.native.js +8 -8
  21. package/dist/cjs/code/CodeBlock/index.native.js.map +1 -1
  22. package/dist/cjs/index.cjs +1 -0
  23. package/dist/cjs/index.native.js +1 -0
  24. package/dist/cjs/index.native.js.map +1 -1
  25. package/dist/cjs/layouts/EmptyState.cjs +4 -3
  26. package/dist/cjs/layouts/EmptyState.native.js +14 -9
  27. package/dist/cjs/layouts/EmptyState.native.js.map +1 -1
  28. package/dist/cjs/layouts/page.cjs +2 -1
  29. package/dist/cjs/layouts/page.native.js +2 -1
  30. package/dist/cjs/layouts/page.native.js.map +1 -1
  31. package/dist/cjs/layouts/sidebar.cjs +2 -1
  32. package/dist/cjs/layouts/sidebar.native.js +2 -1
  33. package/dist/cjs/layouts/sidebar.native.js.map +1 -1
  34. package/dist/cjs/layouts/tags.cjs +2 -1
  35. package/dist/cjs/layouts/tags.native.js +2 -1
  36. package/dist/cjs/layouts/tags.native.js.map +1 -1
  37. package/dist/cjs/layouts/toolbar.cjs +2 -1
  38. package/dist/cjs/layouts/toolbar.native.js +2 -1
  39. package/dist/cjs/layouts/toolbar.native.js.map +1 -1
  40. package/dist/cjs/shared/useReturnFocusOnClose.cjs +1 -0
  41. package/dist/cjs/shared/useReturnFocusOnClose.native.js +1 -0
  42. package/dist/cjs/shared/useReturnFocusOnClose.native.js.map +1 -1
  43. package/dist/cjs/surfaces.cjs +61 -0
  44. package/dist/cjs/surfaces.native.js +73 -0
  45. package/dist/cjs/surfaces.native.js.map +1 -1
  46. package/dist/cjs/views/Calendar.cjs +13 -10
  47. package/dist/cjs/views/Calendar.native.js +13 -10
  48. package/dist/cjs/views/Calendar.native.js.map +1 -1
  49. package/dist/cjs/views/Dashboard.cjs +2 -1
  50. package/dist/cjs/views/Dashboard.native.js +2 -1
  51. package/dist/cjs/views/Dashboard.native.js.map +1 -1
  52. package/dist/cjs/views/Gantt.cjs +39 -27
  53. package/dist/cjs/views/Gantt.native.js +38 -23
  54. package/dist/cjs/views/Gantt.native.js.map +1 -1
  55. package/dist/cjs/views/Kanban.cjs +2 -1
  56. package/dist/cjs/views/Kanban.native.js +1 -0
  57. package/dist/cjs/views/Kanban.native.js.map +1 -1
  58. package/dist/cjs/views/List.cjs +1 -0
  59. package/dist/cjs/views/List.native.js +1 -0
  60. package/dist/cjs/views/List.native.js.map +1 -1
  61. package/dist/cjs/views/Matrix.cjs +5 -3
  62. package/dist/cjs/views/Matrix.native.js +5 -3
  63. package/dist/cjs/views/Matrix.native.js.map +1 -1
  64. package/dist/cjs/views/ReportBuilder.cjs +2 -1
  65. package/dist/cjs/views/ReportBuilder.native.js +2 -1
  66. package/dist/cjs/views/ReportBuilder.native.js.map +1 -1
  67. package/dist/cjs/views/TreeView.cjs +2 -1
  68. package/dist/cjs/views/TreeView.native.js +2 -1
  69. package/dist/cjs/views/TreeView.native.js.map +1 -1
  70. package/dist/esm/Accordion/index.mjs +26 -18
  71. package/dist/esm/Accordion/index.mjs.map +1 -1
  72. package/dist/esm/Accordion/index.native.js +36 -28
  73. package/dist/esm/Accordion/index.native.js.map +1 -1
  74. package/dist/esm/Heading.mjs +74 -30
  75. package/dist/esm/Heading.mjs.map +1 -1
  76. package/dist/esm/Heading.native.js +81 -30
  77. package/dist/esm/Heading.native.js.map +1 -1
  78. package/dist/esm/ProgressSteps/index.mjs.map +1 -1
  79. package/dist/esm/ProgressSteps/index.native.js.map +1 -1
  80. package/dist/esm/ScrollView/index.native.js +4 -0
  81. package/dist/esm/ScrollView/index.native.js.map +1 -1
  82. package/dist/esm/SortableList/SortableHandle.mjs +1 -1
  83. package/dist/esm/SortableList/SortableHandle.native.js +1 -1
  84. package/dist/esm/SortableList/SortableHandle.native.js.map +1 -1
  85. package/dist/esm/Text.mjs +58 -21
  86. package/dist/esm/Text.mjs.map +1 -1
  87. package/dist/esm/Text.native.js +62 -21
  88. package/dist/esm/Text.native.js.map +1 -1
  89. package/dist/esm/Toast/index.mjs.map +1 -1
  90. package/dist/esm/Toast/index.native.js.map +1 -1
  91. package/dist/esm/charts/ChartDataView.mjs +2 -1
  92. package/dist/esm/charts/ChartDataView.mjs.map +1 -1
  93. package/dist/esm/charts/ChartDataView.native.js +2 -1
  94. package/dist/esm/charts/ChartDataView.native.js.map +1 -1
  95. package/dist/esm/code/CodeBlock/index.mjs +1 -1
  96. package/dist/esm/code/CodeBlock/index.native.js +1 -1
  97. package/dist/esm/index.mjs +2 -2
  98. package/dist/esm/index.mjs.map +1 -1
  99. package/dist/esm/index.native.js +2 -2
  100. package/dist/esm/index.native.js.map +1 -1
  101. package/dist/esm/layouts/EmptyState.mjs +4 -3
  102. package/dist/esm/layouts/EmptyState.mjs.map +1 -1
  103. package/dist/esm/layouts/EmptyState.native.js +14 -9
  104. package/dist/esm/layouts/EmptyState.native.js.map +1 -1
  105. package/dist/esm/layouts/page.mjs +2 -1
  106. package/dist/esm/layouts/page.mjs.map +1 -1
  107. package/dist/esm/layouts/page.native.js +2 -1
  108. package/dist/esm/layouts/page.native.js.map +1 -1
  109. package/dist/esm/layouts/sidebar.mjs +2 -1
  110. package/dist/esm/layouts/sidebar.mjs.map +1 -1
  111. package/dist/esm/layouts/sidebar.native.js +2 -1
  112. package/dist/esm/layouts/sidebar.native.js.map +1 -1
  113. package/dist/esm/layouts/tags.mjs +2 -1
  114. package/dist/esm/layouts/tags.mjs.map +1 -1
  115. package/dist/esm/layouts/tags.native.js +2 -1
  116. package/dist/esm/layouts/tags.native.js.map +1 -1
  117. package/dist/esm/layouts/toolbar.mjs +2 -1
  118. package/dist/esm/layouts/toolbar.mjs.map +1 -1
  119. package/dist/esm/layouts/toolbar.native.js +2 -1
  120. package/dist/esm/layouts/toolbar.native.js.map +1 -1
  121. package/dist/esm/shared/useReturnFocusOnClose.mjs +1 -0
  122. package/dist/esm/shared/useReturnFocusOnClose.mjs.map +1 -1
  123. package/dist/esm/shared/useReturnFocusOnClose.native.js +1 -0
  124. package/dist/esm/shared/useReturnFocusOnClose.native.js.map +1 -1
  125. package/dist/esm/surfaces.mjs +63 -3
  126. package/dist/esm/surfaces.mjs.map +1 -1
  127. package/dist/esm/surfaces.native.js +75 -3
  128. package/dist/esm/surfaces.native.js.map +1 -1
  129. package/dist/esm/views/Calendar.mjs +12 -9
  130. package/dist/esm/views/Calendar.mjs.map +1 -1
  131. package/dist/esm/views/Calendar.native.js +12 -9
  132. package/dist/esm/views/Calendar.native.js.map +1 -1
  133. package/dist/esm/views/Dashboard.mjs +2 -1
  134. package/dist/esm/views/Dashboard.mjs.map +1 -1
  135. package/dist/esm/views/Dashboard.native.js +2 -1
  136. package/dist/esm/views/Dashboard.native.js.map +1 -1
  137. package/dist/esm/views/Gantt.mjs +37 -25
  138. package/dist/esm/views/Gantt.mjs.map +1 -1
  139. package/dist/esm/views/Gantt.native.js +36 -21
  140. package/dist/esm/views/Gantt.native.js.map +1 -1
  141. package/dist/esm/views/Kanban.mjs +2 -1
  142. package/dist/esm/views/Kanban.mjs.map +1 -1
  143. package/dist/esm/views/Kanban.native.js +1 -0
  144. package/dist/esm/views/Kanban.native.js.map +1 -1
  145. package/dist/esm/views/List.mjs +1 -0
  146. package/dist/esm/views/List.mjs.map +1 -1
  147. package/dist/esm/views/List.native.js +1 -0
  148. package/dist/esm/views/List.native.js.map +1 -1
  149. package/dist/esm/views/Matrix.mjs +3 -1
  150. package/dist/esm/views/Matrix.mjs.map +1 -1
  151. package/dist/esm/views/Matrix.native.js +3 -1
  152. package/dist/esm/views/Matrix.native.js.map +1 -1
  153. package/dist/esm/views/ReportBuilder.mjs +2 -1
  154. package/dist/esm/views/ReportBuilder.mjs.map +1 -1
  155. package/dist/esm/views/ReportBuilder.native.js +2 -1
  156. package/dist/esm/views/ReportBuilder.native.js.map +1 -1
  157. package/dist/esm/views/TreeView.mjs +2 -1
  158. package/dist/esm/views/TreeView.mjs.map +1 -1
  159. package/dist/esm/views/TreeView.native.js +2 -1
  160. package/dist/esm/views/TreeView.native.js.map +1 -1
  161. package/dist/jsx/Accordion/index.mjs +26 -18
  162. package/dist/jsx/Accordion/index.mjs.map +1 -1
  163. package/dist/jsx/Accordion/index.native.js +36 -27
  164. package/dist/jsx/Accordion/index.native.js.map +1 -1
  165. package/dist/jsx/Heading.mjs +74 -30
  166. package/dist/jsx/Heading.mjs.map +1 -1
  167. package/dist/jsx/Heading.native.js +81 -30
  168. package/dist/jsx/Heading.native.js.map +1 -1
  169. package/dist/jsx/ProgressSteps/index.mjs.map +1 -1
  170. package/dist/jsx/ProgressSteps/index.native.js.map +1 -1
  171. package/dist/jsx/ScrollView/index.native.js +4 -0
  172. package/dist/jsx/ScrollView/index.native.js.map +1 -1
  173. package/dist/jsx/SortableList/SortableHandle.mjs +1 -1
  174. package/dist/jsx/SortableList/SortableHandle.native.js +1 -1
  175. package/dist/jsx/Text.mjs +58 -21
  176. package/dist/jsx/Text.mjs.map +1 -1
  177. package/dist/jsx/Text.native.js +62 -21
  178. package/dist/jsx/Text.native.js.map +1 -1
  179. package/dist/jsx/Toast/index.mjs.map +1 -1
  180. package/dist/jsx/Toast/index.native.js.map +1 -1
  181. package/dist/jsx/charts/ChartDataView.mjs +2 -1
  182. package/dist/jsx/charts/ChartDataView.mjs.map +1 -1
  183. package/dist/jsx/charts/ChartDataView.native.js +2 -1
  184. package/dist/jsx/charts/ChartDataView.native.js.map +1 -1
  185. package/dist/jsx/code/CodeBlock/index.mjs +1 -1
  186. package/dist/jsx/code/CodeBlock/index.native.js +8 -8
  187. package/dist/jsx/code/CodeBlock/index.native.js.map +1 -1
  188. package/dist/jsx/index.js +2 -2
  189. package/dist/jsx/index.js.map +1 -1
  190. package/dist/jsx/index.mjs +2 -2
  191. package/dist/jsx/index.mjs.map +1 -1
  192. package/dist/jsx/index.native.js +1 -0
  193. package/dist/jsx/index.native.js.map +1 -1
  194. package/dist/jsx/layouts/EmptyState.mjs +4 -3
  195. package/dist/jsx/layouts/EmptyState.mjs.map +1 -1
  196. package/dist/jsx/layouts/EmptyState.native.js +14 -9
  197. package/dist/jsx/layouts/EmptyState.native.js.map +1 -1
  198. package/dist/jsx/layouts/page.mjs +2 -1
  199. package/dist/jsx/layouts/page.mjs.map +1 -1
  200. package/dist/jsx/layouts/page.native.js +2 -1
  201. package/dist/jsx/layouts/page.native.js.map +1 -1
  202. package/dist/jsx/layouts/sidebar.mjs +2 -1
  203. package/dist/jsx/layouts/sidebar.mjs.map +1 -1
  204. package/dist/jsx/layouts/sidebar.native.js +2 -1
  205. package/dist/jsx/layouts/sidebar.native.js.map +1 -1
  206. package/dist/jsx/layouts/tags.mjs +2 -1
  207. package/dist/jsx/layouts/tags.mjs.map +1 -1
  208. package/dist/jsx/layouts/tags.native.js +2 -1
  209. package/dist/jsx/layouts/tags.native.js.map +1 -1
  210. package/dist/jsx/layouts/toolbar.mjs +2 -1
  211. package/dist/jsx/layouts/toolbar.mjs.map +1 -1
  212. package/dist/jsx/layouts/toolbar.native.js +2 -1
  213. package/dist/jsx/layouts/toolbar.native.js.map +1 -1
  214. package/dist/jsx/shared/useReturnFocusOnClose.mjs +1 -0
  215. package/dist/jsx/shared/useReturnFocusOnClose.mjs.map +1 -1
  216. package/dist/jsx/shared/useReturnFocusOnClose.native.js +1 -0
  217. package/dist/jsx/shared/useReturnFocusOnClose.native.js.map +1 -1
  218. package/dist/jsx/surfaces.mjs +63 -3
  219. package/dist/jsx/surfaces.mjs.map +1 -1
  220. package/dist/jsx/surfaces.native.js +73 -0
  221. package/dist/jsx/surfaces.native.js.map +1 -1
  222. package/dist/jsx/views/Calendar.mjs +12 -9
  223. package/dist/jsx/views/Calendar.mjs.map +1 -1
  224. package/dist/jsx/views/Calendar.native.js +13 -10
  225. package/dist/jsx/views/Calendar.native.js.map +1 -1
  226. package/dist/jsx/views/Dashboard.mjs +2 -1
  227. package/dist/jsx/views/Dashboard.mjs.map +1 -1
  228. package/dist/jsx/views/Dashboard.native.js +2 -1
  229. package/dist/jsx/views/Dashboard.native.js.map +1 -1
  230. package/dist/jsx/views/Gantt.mjs +37 -25
  231. package/dist/jsx/views/Gantt.mjs.map +1 -1
  232. package/dist/jsx/views/Gantt.native.js +38 -23
  233. package/dist/jsx/views/Gantt.native.js.map +1 -1
  234. package/dist/jsx/views/Kanban.mjs +2 -1
  235. package/dist/jsx/views/Kanban.mjs.map +1 -1
  236. package/dist/jsx/views/Kanban.native.js +1 -0
  237. package/dist/jsx/views/Kanban.native.js.map +1 -1
  238. package/dist/jsx/views/List.mjs +1 -0
  239. package/dist/jsx/views/List.mjs.map +1 -1
  240. package/dist/jsx/views/List.native.js +1 -0
  241. package/dist/jsx/views/List.native.js.map +1 -1
  242. package/dist/jsx/views/Matrix.mjs +3 -1
  243. package/dist/jsx/views/Matrix.mjs.map +1 -1
  244. package/dist/jsx/views/Matrix.native.js +5 -3
  245. package/dist/jsx/views/Matrix.native.js.map +1 -1
  246. package/dist/jsx/views/ReportBuilder.mjs +2 -1
  247. package/dist/jsx/views/ReportBuilder.mjs.map +1 -1
  248. package/dist/jsx/views/ReportBuilder.native.js +2 -1
  249. package/dist/jsx/views/ReportBuilder.native.js.map +1 -1
  250. package/dist/jsx/views/TreeView.mjs +2 -1
  251. package/dist/jsx/views/TreeView.mjs.map +1 -1
  252. package/dist/jsx/views/TreeView.native.js +2 -1
  253. package/dist/jsx/views/TreeView.native.js.map +1 -1
  254. package/package.json +9 -9
  255. package/src/Accordion/index.tsx +34 -15
  256. package/src/Heading.stories.tsx +13 -0
  257. package/src/Heading.tsx +62 -21
  258. package/src/ProgressSteps/ProgressSteps.spec.tsx +2 -3
  259. package/src/ProgressSteps/index.tsx +3 -4
  260. package/src/ScrollView/ScrollView.native.spec.tsx +97 -1
  261. package/src/ScrollView/index.native.tsx +4 -0
  262. package/src/SortableList/SortableHandle.tsx +2 -2
  263. package/src/Tabs/Tabs.lifecycle.spec.tsx +3 -1
  264. package/src/Text.tsx +32 -20
  265. package/src/Toast/index.tsx +4 -3
  266. package/src/charts/ChartDataView.tsx +2 -1
  267. package/src/code/CodeBlock/index.tsx +1 -1
  268. package/src/feedback/ConfirmDialog.spec.tsx +2 -2
  269. package/src/icons/phosphorTokenColor.spec.tsx +86 -0
  270. package/src/index.ts +1 -1
  271. package/src/layouts/EmptyState.spec.tsx +17 -0
  272. package/src/layouts/EmptyState.tsx +10 -3
  273. package/src/layouts/Screen.keyboardTaps.spec.tsx +57 -0
  274. package/src/layouts/Screen.stories.tsx +55 -2
  275. package/src/layouts/page.tsx +2 -14
  276. package/src/layouts/sidebar.tsx +2 -1
  277. package/src/layouts/tags.tsx +1 -1
  278. package/src/layouts/toolbar.tsx +2 -1
  279. package/src/shared/useReturnFocusOnClose.spec.tsx +39 -0
  280. package/src/shared/useReturnFocusOnClose.ts +4 -0
  281. package/src/surfaces.spec.tsx +272 -3
  282. package/src/surfaces.tsx +97 -4
  283. package/src/textWeight.houseFonts.spec.tsx +127 -0
  284. package/src/textWeight.native.spec.tsx +27 -0
  285. package/src/textWeight.spec.tsx +15 -0
  286. package/src/views/Calendar.tsx +37 -29
  287. package/src/views/Dashboard.tsx +2 -13
  288. package/src/views/Gantt.scroll.spec.tsx +4 -1
  289. package/src/views/Gantt.tsx +40 -25
  290. package/src/views/Kanban.native.tsx +1 -0
  291. package/src/views/Kanban.tsx +2 -10
  292. package/src/views/List.tsx +1 -0
  293. package/src/views/Matrix.tsx +3 -1
  294. package/src/views/ReportBuilder.tsx +2 -1
  295. package/src/views/TreeView.tsx +2 -1
  296. package/src/views/views.spec.tsx +14 -2
  297. package/types/Accordion/index.d.ts +8 -0
  298. package/types/Accordion/index.d.ts.map +1 -1
  299. package/types/Chip/index.d.ts +1 -1
  300. package/types/Heading.d.ts +7 -7
  301. package/types/Heading.d.ts.map +1 -1
  302. package/types/ProgressSteps/index.d.ts.map +1 -1
  303. package/types/ScrollView/index.native.d.ts.map +1 -1
  304. package/types/SortableList/SortableHandle.d.ts +1 -1
  305. package/types/Tabs/index.d.ts +1 -1
  306. package/types/Text.d.ts +4 -4
  307. package/types/Text.d.ts.map +1 -1
  308. package/types/Toast/index.d.ts.map +1 -1
  309. package/types/charts/ChartDataView.d.ts.map +1 -1
  310. package/types/index.d.ts +1 -1
  311. package/types/index.d.ts.map +1 -1
  312. package/types/layouts/EmptyState.d.ts.map +1 -1
  313. package/types/layouts/page.d.ts.map +1 -1
  314. package/types/layouts/sidebar.d.ts.map +1 -1
  315. package/types/layouts/tags.d.ts.map +1 -1
  316. package/types/layouts/toolbar.d.ts.map +1 -1
  317. package/types/shared/useReturnFocusOnClose.d.ts.map +1 -1
  318. package/types/surfaces.d.ts +14 -0
  319. package/types/surfaces.d.ts.map +1 -1
  320. package/types/views/Calendar.d.ts +18 -1
  321. package/types/views/Calendar.d.ts.map +1 -1
  322. package/types/views/Dashboard.d.ts.map +1 -1
  323. package/types/views/Gantt.d.ts.map +1 -1
  324. package/types/views/Kanban.d.ts.map +1 -1
  325. package/types/views/List.d.ts.map +1 -1
  326. package/types/views/Matrix.d.ts.map +1 -1
  327. package/types/views/ReportBuilder.d.ts.map +1 -1
  328. package/types/views/TreeView.d.ts.map +1 -1
@@ -11,6 +11,7 @@ import {
11
11
  stackEdgeRadius,
12
12
  useAccentTintedSurface,
13
13
  useGroupPosition,
14
+ useLegibleInkOn,
14
15
  useReadableTextOn,
15
16
  useResolvedKnobs,
16
17
  wasKeyboardFocus,
@@ -165,6 +166,20 @@ const AccordionItemCtx = createContext<AccordionItemCtxValue>({
165
166
  position: "only",
166
167
  });
167
168
 
169
+ /** The fill an open trigger paints under its title (the accent tint). */
170
+ const AccordionTriggerFillCtx = createContext<string | undefined>(undefined);
171
+
172
+ /**
173
+ * Ink for a title node a consumer renders inside `Accordion.Trigger`:
174
+ * `preferred` while it clears AA on the open trigger's fill, the fill's
175
+ * readable anchor where it does not (a muted `$color11` title on the dark
176
+ * accent tint measured 4.05:1, MPO-448). Bare string titles already get this
177
+ * from the trigger.
178
+ */
179
+ export function useAccordionTriggerInk(preferred: string): string {
180
+ return useLegibleInkOn(useContext(AccordionTriggerFillCtx), preferred);
181
+ }
182
+
168
183
  function useAccordionKnobs() {
169
184
  const { compact, size } = useContext(AccordionCtx);
170
185
  return useResolvedKnobs({ component: "Accordion", compact, size });
@@ -490,9 +505,11 @@ const AccordionTrigger = forwardRef<TamaguiElement, AccordionTriggerProps>(
490
505
  : item.open && onSelectedTint
491
506
  ? onSelectedTint
492
507
  : componentColors.text.primary;
493
- const descriptionColor = item.disabled
494
- ? disabledState.textKnobProps.color
495
- : knobProps.textAccentColor;
508
+ const openDescriptionInk = useLegibleInkOn(
509
+ item.open ? selectedTint : undefined,
510
+ knobProps.textAccentColor,
511
+ );
512
+ const descriptionColor = item.disabled ? disabledState.textKnobProps.color : openDescriptionInk;
496
513
  const visualPx = tokenPx(knobProps.sizeToken, MIN_PRESS_TARGET, "size");
497
514
  const chevronPx = tokenPx(
498
515
  knobProps.label.fontSize,
@@ -568,24 +585,26 @@ const AccordionTrigger = forwardRef<TamaguiElement, AccordionTriggerProps>(
568
585
  {icon}
569
586
  </View>
570
587
  ) : null}
571
- <YStack flex={1} minWidth={0} alignItems="flex-start">
572
- {/* LC-82 TEXT-RIDES-TEXT: wrap ALL bare string/number children
588
+ <AccordionTriggerFillCtx.Provider value={item.open ? selectedTint : undefined}>
589
+ <YStack flex={1} minWidth={0} alignItems="flex-start">
590
+ {/* LC-82 TEXT-RIDES-TEXT: wrap ALL bare string/number children
573
591
  (coalesced runs), not just the singleton case. */}
574
- {/* Label weight rides the fontWeight knob (400 / 700). Never 500
592
+ {/* Label weight rides the fontWeight knob (400 / 700). Never 500
575
593
  or 600 — those are not house label weights (§6.1 / R7 text-node
576
594
  trap). Selected state is fill (LC-71), not a heavier weight. */}
577
- {wrapBareTextChildren(children, (label, key) => (
578
- <Text key={key} {...knobProps.label} textAlign="left" color={titleColor}>
579
- {label}
580
- </Text>
581
- ))}
582
- {description != null &&
583
- wrapBareTextChildren(description, (label, key) => (
584
- <Text key={key} {...knobProps.label} textAlign="left" color={descriptionColor}>
595
+ {wrapBareTextChildren(children, (label, key) => (
596
+ <Text key={key} {...knobProps.label} textAlign="left" color={titleColor}>
585
597
  {label}
586
598
  </Text>
587
599
  ))}
588
- </YStack>
600
+ {description != null &&
601
+ wrapBareTextChildren(description, (label, key) => (
602
+ <Text key={key} {...knobProps.label} textAlign="left" color={descriptionColor}>
603
+ {label}
604
+ </Text>
605
+ ))}
606
+ </YStack>
607
+ </AccordionTriggerFillCtx.Provider>
589
608
  </TamaguiAccordion.Trigger>
590
609
  </View>
591
610
  );
@@ -69,3 +69,16 @@ export const FontWeightFlip: Story = {
69
69
  </YStack>
70
70
  ),
71
71
  };
72
+
73
+ export const SizedHeadings: Story = {
74
+ name: "Sized headings",
75
+ render: () => (
76
+ <YStack gap="$3" maxWidth={640}>
77
+ <H2 size="$6">H2 at $6: weight follows fontWeight in every headingFont</H2>
78
+ <Heading size="$4">Heading at $4</Heading>
79
+ <H2 size="$6" fontWeight="700">
80
+ H2 at $6 with an explicit 700
81
+ </H2>
82
+ </YStack>
83
+ ),
84
+ };
package/src/Heading.tsx CHANGED
@@ -41,39 +41,80 @@ import {
41
41
 
42
42
  export type { HeadingProps };
43
43
 
44
- /** Subordinate heading voice — mono, weight 400. Consumer props still eject. */
45
- const SUBORDINATE = { fontFamily: "$mono", fontWeight: "400" } as const;
44
+ /** Subordinate heading voice: mono, weight 400. Consumer props still eject. */
45
+ const SUBORDINATE_FAMILY = "$mono";
46
+ const SUBORDINATE_WEIGHT = "400";
46
47
 
47
- export function Heading(props: HeadingProps) {
48
+ type Weight = HeadingProps["fontWeight"];
49
+
50
+ /**
51
+ * The heading fragment without its weight, and the weight a heading writes
52
+ * as its last prop, so neither a `size` nor a `fontFamily` variant can paint
53
+ * the font ramp's weight over it (MPO-446, ./Text.tsx WEIGHT RIDES LAST).
54
+ * That ramp is why a sized heading swapped to `headingFont: serif` fell from
55
+ * Inter's 600 to the category font's 400.
56
+ */
57
+ function useHeadingVoice(fontWeight: Weight) {
48
58
  const { knobProps } = useResolvedKnobs();
49
- return <RawHeading {...knobProps.heading} {...props} />;
59
+ const { fontWeight: knobWeight, ...heading } = knobProps.heading;
60
+ return {
61
+ heading,
62
+ pageTitle: knobProps.pageTitle,
63
+ weight: fontWeight ?? (knobWeight as Weight),
64
+ };
50
65
  }
51
66
 
52
- export function H1(props: HeadingProps) {
53
- const { knobProps } = useResolvedKnobs();
54
- // pageTitle first: H1's size variant paints `_fow-f-weight-8` (the $8
55
- // heading-weight token). heading.fontWeight must land AFTER that so
56
- // regular/bold reach the TEXT NODE instead of being swallowed.
57
- return <RawH1 {...knobProps.pageTitle} {...knobProps.heading} {...props} />;
67
+ export function Heading({ fontWeight, ...props }: HeadingProps) {
68
+ const { heading, weight } = useHeadingVoice(fontWeight);
69
+ return <RawHeading {...heading} {...props} fontWeight={weight} />;
58
70
  }
59
71
 
60
- export function H2(props: HeadingProps) {
61
- const { knobProps } = useResolvedKnobs();
62
- return <RawH2 {...knobProps.heading} {...props} />;
72
+ export function H1({ fontWeight, ...props }: HeadingProps) {
73
+ const { heading, pageTitle, weight } = useHeadingVoice(fontWeight);
74
+ return <RawH1 {...pageTitle} {...heading} {...props} fontWeight={weight} />;
75
+ }
76
+
77
+ export function H2({ fontWeight, ...props }: HeadingProps) {
78
+ const { heading, weight } = useHeadingVoice(fontWeight);
79
+ return <RawH2 {...heading} {...props} fontWeight={weight} />;
63
80
  }
64
81
 
65
- export function H3(props: HeadingProps) {
66
- return <RawH3 {...SUBORDINATE} {...props} />;
82
+ export function H3({ fontWeight, ...props }: HeadingProps) {
83
+ return (
84
+ <RawH3
85
+ fontFamily={SUBORDINATE_FAMILY}
86
+ {...props}
87
+ fontWeight={fontWeight ?? SUBORDINATE_WEIGHT}
88
+ />
89
+ );
67
90
  }
68
91
 
69
- export function H4(props: HeadingProps) {
70
- return <RawH4 {...SUBORDINATE} {...props} />;
92
+ export function H4({ fontWeight, ...props }: HeadingProps) {
93
+ return (
94
+ <RawH4
95
+ fontFamily={SUBORDINATE_FAMILY}
96
+ {...props}
97
+ fontWeight={fontWeight ?? SUBORDINATE_WEIGHT}
98
+ />
99
+ );
71
100
  }
72
101
 
73
- export function H5(props: HeadingProps) {
74
- return <RawH5 {...SUBORDINATE} {...props} />;
102
+ export function H5({ fontWeight, ...props }: HeadingProps) {
103
+ return (
104
+ <RawH5
105
+ fontFamily={SUBORDINATE_FAMILY}
106
+ {...props}
107
+ fontWeight={fontWeight ?? SUBORDINATE_WEIGHT}
108
+ />
109
+ );
75
110
  }
76
111
 
77
- export function H6(props: HeadingProps) {
78
- return <RawH6 {...SUBORDINATE} {...props} />;
112
+ export function H6({ fontWeight, ...props }: HeadingProps) {
113
+ return (
114
+ <RawH6
115
+ fontFamily={SUBORDINATE_FAMILY}
116
+ {...props}
117
+ fontWeight={fontWeight ?? SUBORDINATE_WEIGHT}
118
+ />
119
+ );
79
120
  }
@@ -52,7 +52,7 @@ describe("ProgressSteps", () => {
52
52
  expect(current.className).toContain("_bg-accentBackg");
53
53
  });
54
54
 
55
- it("walks the current fill off the dark mid-solid so the numeral can clear 4.5", () => {
55
+ it("keeps the dark current fill on $accentBackground now the build holds it to 4.5 (MPO-448)", () => {
56
56
  const { container } = render(
57
57
  <TamaguiProvider config={houseConfig} defaultTheme="dark" disableInjectCSS>
58
58
  <ProgressSteps steps={steps} current={1} />
@@ -60,8 +60,7 @@ describe("ProgressSteps", () => {
60
60
  );
61
61
  const current = container.querySelector('[aria-current="step"]') as HTMLElement;
62
62
  expect(current).toBeTruthy();
63
- expect(current.className).toMatch(/_bg-accent/);
64
- expect(current.className).not.toContain("_bg-accentBackg");
63
+ expect(current.className).toContain("_bg-accentBackg");
65
64
  });
66
65
 
67
66
  it("calls onStepChange when jumping back to a completed step", () => {
@@ -158,10 +158,9 @@ export function ProgressSteps({
158
158
  if (isWeb) ensureKeyboardModalityTracking();
159
159
  const [kbFocusIndex, setKbFocusIndex] = useState<number | null>(null);
160
160
  // LC-05: current marker rides an accent solid; completed/error fills pick
161
- // paper/ink from luminance rather than an assumed white glyph. The stock
162
- // dark `$accentBackground` tops out at 4.02:1 against either scheme
163
- // anchor, under the in-repo 4.5 text floor, so the current fill walks to
164
- // the nearest accent solid that can carry the numeral.
161
+ // paper/ink from luminance rather than an assumed white glyph. An
162
+ // `$accentBackground` under the 4.5 text floor against both scheme anchors
163
+ // walks to the nearest accent solid that can carry the numeral.
165
164
  const currentFill = useAaSolidFill(componentColors.indicator.selected);
166
165
  const onSelected = useReadableTextOn(currentFill);
167
166
  const onCompleted = useReadableTextOn(componentColors.indicator.completed);
@@ -4,7 +4,7 @@
4
4
  * ../../tests/nativeTamagui.ts); vitest's usual web build keeps onScroll and
5
5
  * would pass with or without the fix.
6
6
  */
7
- import { readdirSync, readFileSync } from "node:fs";
7
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
8
8
  import { join, relative } from "node:path";
9
9
  import { act, createElement, createRef } from "react";
10
10
  import { createRoot, type Root } from "react-dom/client";
@@ -78,6 +78,35 @@ describe("tamagui native ScrollView (upstream control)", () => {
78
78
  expect(host.onContentSizeChange).toBe(props.onContentSizeChange);
79
79
  expect(host.onScroll).toBeUndefined();
80
80
  });
81
+
82
+ it("leaves keyboardShouldPersistTaps unset, so RN falls back to never", () => {
83
+ const host = render(createElement(runtime.RawScrollView as AnyScrollView, { testID: "probe" }));
84
+ expect(host.keyboardShouldPersistTaps).toBeUndefined();
85
+ });
86
+ });
87
+
88
+ describe("components ScrollView keyboard taps on native (MPO-457)", () => {
89
+ it("hands keyboardShouldPersistTaps=handled to the RN ScrollView by default", () => {
90
+ const host = render(createElement(ScrollView as unknown as AnyScrollView, { testID: "probe" }));
91
+ expect(host.keyboardShouldPersistTaps).toBe("handled");
92
+ });
93
+
94
+ it("keeps the default through a styled() extension", () => {
95
+ const Extended = styled(ScrollView, { name: "Extended" });
96
+ const host = render(createElement(Extended as unknown as AnyScrollView, { testID: "probe" }));
97
+ expect(host.keyboardShouldPersistTaps).toBe("handled");
98
+ });
99
+
100
+ it("lets a consumer value win", () => {
101
+ for (const value of ["never", "always"]) {
102
+ const host = render(
103
+ createElement(ScrollView as unknown as AnyScrollView, { keyboardShouldPersistTaps: value }),
104
+ );
105
+ expect(host.keyboardShouldPersistTaps).toBe(value);
106
+ act(() => root.unmount());
107
+ container.remove();
108
+ }
109
+ });
81
110
  });
82
111
 
83
112
  describe("components ScrollView on native", () => {
@@ -137,6 +166,35 @@ describe("catalog onScroll consumers", () => {
137
166
  });
138
167
  });
139
168
 
169
+ describe("catalog scroll owners (MPO-457)", () => {
170
+ it("take the components ScrollView, or declare keyboardShouldPersistTaps", () => {
171
+ const publicDir = join(import.meta.dirname, "../../..");
172
+ const house = join(import.meta.dirname, "index.native.tsx");
173
+ const raw: Array<[binding: string, from: string]> = [
174
+ ["ScrollView", "tamagui"],
175
+ ["ScrollView", "react-native"],
176
+ ["FlashList", "@shopify/flash-list"],
177
+ ];
178
+ const offenders = readdirSync(publicDir, { withFileTypes: true })
179
+ .filter((entry) => entry.isDirectory())
180
+ .flatMap((entry) => {
181
+ const src = join(publicDir, entry.name, "src");
182
+ return existsSync(src) ? sourceFiles(src) : [];
183
+ })
184
+ .filter((file) => file !== house)
185
+ .flatMap((file) => {
186
+ const code = readFileSync(file, "utf8");
187
+ return raw
188
+ .flatMap(([name, from]) => importedAs(code, name, from))
189
+ .flatMap((local) => [local, ...aliasesOf(code, local)])
190
+ .flatMap((local) => openingTags(code, local))
191
+ .filter((tag) => !/\bkeyboardShouldPersistTaps\b/.test(tag.text))
192
+ .map((tag) => `${relative(publicDir, file)}:${tag.line}`);
193
+ });
194
+ expect(offenders).toEqual([]);
195
+ });
196
+ });
197
+
140
198
  function sourceFiles(dir: string): string[] {
141
199
  const out: string[] = [];
142
200
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
@@ -155,3 +213,41 @@ function importsScrollView(code: string, from: string): boolean {
155
213
  match[1]!.split(",").some((part) => /^ScrollView\b/.test(part.trim())),
156
214
  );
157
215
  }
216
+
217
+ /** Local names a value import of `name` from `from` binds. */
218
+ function importedAs(code: string, name: string, from: string): string[] {
219
+ const re = new RegExp(`import\\s+\\{([^}]*)\\}\\s+from\\s+["']${from}["']`, "g");
220
+ const alias = new RegExp(`^${name}(?:\\s+as\\s+(\\w+))?$`);
221
+ return [...code.matchAll(re)].flatMap((match) =>
222
+ match[1]!.split(",").flatMap((part) => {
223
+ const found = part.trim().match(alias);
224
+ return found ? [found[1] ?? name] : [];
225
+ }),
226
+ );
227
+ }
228
+
229
+ /** Consts that pick `local`, e.g. `const Scroller = isWeb ? ScrollView : RNScrollView`. */
230
+ function aliasesOf(code: string, local: string): string[] {
231
+ const re = new RegExp(`const\\s+(\\w+)\\s*=[^;\\n]*\\b${local}\\b`, "g");
232
+ return [...code.matchAll(re)].map((match) => match[1]!);
233
+ }
234
+
235
+ /** Every JSX opening tag of `name`, props included, with its line. */
236
+ function openingTags(code: string, name: string): Array<{ text: string; line: number }> {
237
+ const out: Array<{ text: string; line: number }> = [];
238
+ for (const match of code.matchAll(new RegExp(`<${name}(?![\\w.])`, "g"))) {
239
+ let depth = 0;
240
+ let end = match.index + match[0].length;
241
+ for (; end < code.length; end++) {
242
+ const char = code[end];
243
+ if (char === "{") depth++;
244
+ else if (char === "}") depth--;
245
+ else if (char === ">" && depth === 0) break;
246
+ }
247
+ out.push({
248
+ text: code.slice(match.index, end + 1),
249
+ line: code.slice(0, match.index).split("\n").length,
250
+ });
251
+ }
252
+ return out;
253
+ }
@@ -27,6 +27,10 @@ const ScrollFrame = styled(
27
27
  {
28
28
  name: "ScrollView",
29
29
  scrollEnabled: true,
30
+ // MPO-457: at RN's "never" a ScrollView claims, in the capture phase, the
31
+ // first tap on any descendant while the keyboard is up, and a native
32
+ // sheet's rows are React descendants of the screen that opened it.
33
+ keyboardShouldPersistTaps: "handled",
30
34
  variants: {
31
35
  fullscreen: {
32
36
  true: { position: "absolute", inset: 0 },
@@ -27,7 +27,7 @@ export interface SortableHandleProps {
27
27
  }
28
28
 
29
29
  /**
30
- * Leading grab handle: nested well, 16px 6-dot grip, opacity .45 → 1.
30
+ * Leading grab handle: nested well, 16px 6-dot grip, opacity .6 → 1.
31
31
  * Focusable (W1-focus-ring): 2px ring, offset 0. Keyboard path can also
32
32
  * live on the row — this control is tabIndex 0 so tables have a target
33
33
  * that is not an in-cell editor.
@@ -58,7 +58,7 @@ export function SortableHandle({
58
58
  minHeight={well}
59
59
  alignItems="center"
60
60
  justifyContent="center"
61
- opacity={isDragging ? 1 : 0.45}
61
+ opacity={isDragging ? 1 : 0.6}
62
62
  hoverStyle={{ opacity: 1 }}
63
63
  {...({
64
64
  tag: "button",
@@ -18,7 +18,9 @@ vi.mock("tamagui", async (original) => {
18
18
  const observers: ControlledResizeObserver[] = [];
19
19
  class ControlledResizeObserver {
20
20
  elements = new Set<Element>();
21
- constructor(readonly callback: ResizeObserverCallback) {
21
+ readonly callback: ResizeObserverCallback;
22
+ constructor(callback: ResizeObserverCallback) {
23
+ this.callback = callback;
22
24
  observers.push(this);
23
25
  }
24
26
  observe(element: Element) {
package/src/Text.tsx CHANGED
@@ -18,6 +18,14 @@
18
18
  * — the text node itself carries fontFamily/fontWeight/color on every
19
19
  * platform (the MPO-44/48 lesson: the frame is not the text node).
20
20
  *
21
+ * WEIGHT RIDES LAST (MPO-446). `size` and `fontFamily` are variants on the
22
+ * raw primitive, and tamagui's getFontSized writes the font ramp's weight
23
+ * (`var(--f-weight-N)`) at the position that key holds in the props object.
24
+ * A knob weight spread before a consumer `size` lost to it, and a consumer
25
+ * `fontWeight` sat at the knob's position too, since a spread key keeps its
26
+ * first slot. So the weight is split off the fragment and written as the
27
+ * final prop: the knob's, or the consumer's when one is passed.
28
+ *
21
29
  * Knob channels wired here:
22
30
  * - bodyFont → knobProps.body.fontFamily (every platform, not just web)
23
31
  * - fontWeight → knobProps.body.fontWeight (regular 400 / bold 700 —
@@ -55,32 +63,36 @@ export type { AnchorProps, ParagraphProps, SizableTextProps, TextProps };
55
63
  /** Measured $4 label ramp — Inter 14/25, weight from the fontWeight knob. */
56
64
  const LABEL_RAMP = { fontSize: 14, lineHeight: 25 } as const;
57
65
 
58
- export function Text(props: TextProps) {
66
+ type Weight = TextProps["fontWeight"];
67
+
68
+ /** The body fragment without its weight, and the weight a voice writes last. */
69
+ function useBodyVoice(fontWeight: Weight) {
59
70
  const { knobProps } = useResolvedKnobs();
60
- return (
61
- <RawText {...LABEL_RAMP} {...knobProps.body} color={knobProps.textAccentColor} {...props} />
62
- );
71
+ const { fontWeight: knobWeight, ...body } = knobProps.body;
72
+ return {
73
+ body,
74
+ color: knobProps.textAccentColor,
75
+ weight: fontWeight ?? (knobWeight as Weight),
76
+ };
63
77
  }
64
78
 
65
- export function SizableText(props: SizableTextProps) {
66
- const { knobProps } = useResolvedKnobs();
67
- return <RawSizableText {...knobProps.body} color={knobProps.textAccentColor} {...props} />;
79
+ export function Text({ fontWeight, ...props }: TextProps) {
80
+ const { body, color, weight } = useBodyVoice(fontWeight);
81
+ return <RawText {...LABEL_RAMP} {...body} color={color} {...props} fontWeight={weight} />;
68
82
  }
69
83
 
70
- export function Paragraph(props: ParagraphProps) {
71
- const { knobProps } = useResolvedKnobs();
72
- return (
73
- <RawParagraph
74
- {...LABEL_RAMP}
75
- {...knobProps.body}
76
- color={knobProps.textAccentColor}
77
- {...props}
78
- />
79
- );
84
+ export function SizableText({ fontWeight, ...props }: SizableTextProps) {
85
+ const { body, color, weight } = useBodyVoice(fontWeight);
86
+ return <RawSizableText {...body} color={color} {...props} fontWeight={weight} />;
80
87
  }
81
88
 
82
- export function Anchor(props: AnchorProps) {
83
- const { knobProps } = useResolvedKnobs();
89
+ export function Paragraph({ fontWeight, ...props }: ParagraphProps) {
90
+ const { body, color, weight } = useBodyVoice(fontWeight);
91
+ return <RawParagraph {...LABEL_RAMP} {...body} color={color} {...props} fontWeight={weight} />;
92
+ }
93
+
94
+ export function Anchor({ fontWeight, ...props }: AnchorProps) {
95
+ const { body, weight } = useBodyVoice(fontWeight);
84
96
  const linkInk = useAccentOnSurface();
85
- return <RawAnchor {...knobProps.body} color={linkInk} {...props} />;
97
+ return <RawAnchor {...body} color={linkInk} {...props} fontWeight={weight} />;
86
98
  }
@@ -364,9 +364,10 @@ function useToastVisuals(intent: ToastIntent, compact?: boolean) {
364
364
  const isAccent = intent === "accent";
365
365
  // Semantic hues: step 11 — lowest AA-safe step of the hue on the step-1/2
366
366
  // surface (resolves to a CSS var on web, concrete value on native).
367
- // Accent action ink: $accentColor is the on-fill foreground (~1.9:1 on the
368
- // dark toast surface). useAccentOnSurface picks the most chromatic accent
369
- // step that still clears AA 4.5:1 on color1/color2 (R7 / MPO-48).
367
+ // Accent glyphs ride $accentColor, which the build holds to 4.5:1 on the
368
+ // page surfaces in both schemes (MPO-449). The action label takes
369
+ // useAccentOnSurface: the most chromatic accent step that still clears AA
370
+ // 4.5:1 on color1/color2 (R7 / MPO-48).
370
371
  const iconColor = (isAccent ? theme.accentColor?.get() : theme.color11?.get()) as
371
372
  | string
372
373
  | undefined;
@@ -2,7 +2,8 @@ import { Button } from "@multiplatform.one/forms";
2
2
  import { isWeb } from "@multiplatform.one/platform";
3
3
  import { useResolvedKnobs } from "@multiplatform.one/theme";
4
4
  import { createContext, useContext, useId, useState, type ReactNode } from "react";
5
- import { Dialog, Paragraph, ScrollView, XStack, YStack } from "tamagui";
5
+ import { Dialog, Paragraph, XStack, YStack } from "tamagui";
6
+ import { ScrollView } from "../ScrollView";
6
7
  import { sectionHeading } from "../componentColors";
7
8
  import { useTranslation } from "../shared/i18n";
8
9
  import { DialogContent, DialogOverlay } from "../surfaces";
@@ -19,7 +19,7 @@ import {
19
19
  import { useEffect, useRef, useState } from "react";
20
20
  import { useTranslation } from "../../shared/i18n";
21
21
  import type { TamaguiElement } from "tamagui";
22
- import { ScrollView } from "tamagui";
22
+ import { ScrollView } from "../../ScrollView";
23
23
  import { Button, Input, zIndex } from "@multiplatform.one/forms";
24
24
  import { Spacer, XStack, YStack } from "tamagui";
25
25
  import { settleMs } from "../../AnimateHeight";
@@ -31,7 +31,7 @@ function alertdialog(): Element | null {
31
31
  }
32
32
 
33
33
  describe("ConfirmDialog", () => {
34
- it("keeps a disabled destructive label on Button's readable text tier", () => {
34
+ it("inks a disabled destructive label on the error hue's $color12 text step (LC-40)", () => {
35
35
  renderWithProviders(
36
36
  <ConfirmDialog
37
37
  open
@@ -47,7 +47,7 @@ describe("ConfirmDialog", () => {
47
47
  (node) => node.textContent === "Delete",
48
48
  );
49
49
  expect(label).toBeTruthy();
50
- expect(label?.className).toContain("_col-color11");
50
+ expect(label?.className).toContain("_col-color12");
51
51
  expect(label?.className).toContain("_fow-400");
52
52
  });
53
53
 
@@ -0,0 +1,86 @@
1
+ /**
2
+ * MPO-450. A phosphor glyph handed a Tamagui token paints that token's colour
3
+ * on web. Raw @phosphor-icons/react writes `color` straight into `<svg fill>`;
4
+ * "$color9" is not a paint, the browser drops it and the glyph falls back to
5
+ * black. createVitestConfig carries the same IconBase resolution every web
6
+ * bundle gets (phosphorTokenColorPlugin), so these renders go through it.
7
+ */
8
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
9
+ import { FolderIcon, HeartIcon } from "@phosphor-icons/react";
10
+ import { Theme, useTheme } from "tamagui";
11
+ import { describe, expect, it } from "vitest";
12
+
13
+ function ThemeValue({ name }: { name: string }) {
14
+ const theme = useTheme() as unknown as Record<string, { val?: unknown } | undefined>;
15
+ return <span data-theme-value={name} data-val={String(theme[name]?.val ?? "")} />;
16
+ }
17
+
18
+ function fillOf(container: HTMLElement, testId: string) {
19
+ return container.querySelector(`[data-testid="${testId}"]`)?.getAttribute("fill") ?? "";
20
+ }
21
+
22
+ function valueOf(container: HTMLElement, name: string) {
23
+ return container.querySelector(`[data-theme-value="${name}"]`)?.getAttribute("data-val") ?? "";
24
+ }
25
+
26
+ describe("phosphor glyph token colour on web (MPO-450)", () => {
27
+ it("paints $color9 with the theme's resolved colour, not the token string", () => {
28
+ const { container } = renderWithProviders(
29
+ <>
30
+ <FolderIcon data-testid="glyph" color="$color9" />
31
+ <ThemeValue name="color9" />
32
+ </>,
33
+ );
34
+ const fill = fillOf(container, "glyph");
35
+ expect(valueOf(container, "color9")).not.toBe("");
36
+ expect(fill).toBe(valueOf(container, "color9"));
37
+ expect(fill.startsWith("$")).toBe(false);
38
+ });
39
+
40
+ it("resolves against the theme in scope, so dark paints the dark step", () => {
41
+ const { container } = renderWithProviders(
42
+ <>
43
+ <HeartIcon data-testid="light" color="$color11" />
44
+ <Theme name="dark">
45
+ <HeartIcon data-testid="dark" color="$color11" />
46
+ <ThemeValue name="color11" />
47
+ </Theme>
48
+ </>,
49
+ );
50
+ const dark = fillOf(container, "dark");
51
+ expect(dark).toBe(valueOf(container, "color11"));
52
+ expect(dark).not.toBe(fillOf(container, "light"));
53
+ });
54
+
55
+ it("resolves a palette token such as the liked heart's $red11", () => {
56
+ const { container } = renderWithProviders(
57
+ <>
58
+ <HeartIcon data-testid="glyph" weight="fill" color="$red11" />
59
+ <ThemeValue name="red11" />
60
+ </>,
61
+ );
62
+ expect(valueOf(container, "red11")).not.toBe("");
63
+ expect(fillOf(container, "glyph")).toBe(valueOf(container, "red11"));
64
+ });
65
+
66
+ it("leaves a literal colour alone", () => {
67
+ const { container } = renderWithProviders(<HeartIcon data-testid="glyph" color="#123456" />);
68
+ expect(fillOf(container, "glyph")).toBe("#123456");
69
+ });
70
+
71
+ it("inherits currentColor when no colour is given", () => {
72
+ const { container } = renderWithProviders(<HeartIcon data-testid="glyph" />);
73
+ expect(fillOf(container, "glyph")).toBe("currentColor");
74
+ });
75
+
76
+ it("takes the theme's ink, never black, for a token the theme does not carry", () => {
77
+ const { container } = renderWithProviders(
78
+ <>
79
+ <HeartIcon data-testid="glyph" color="$notAThemeKey" />
80
+ <ThemeValue name="color" />
81
+ </>,
82
+ );
83
+ expect(valueOf(container, "color")).not.toBe("");
84
+ expect(fillOf(container, "glyph")).toBe(valueOf(container, "color"));
85
+ });
86
+ });
package/src/index.ts CHANGED
@@ -51,7 +51,7 @@ export type { WizardFormLike, WizardProps, WizardStatus, WizardStep } from "./Wi
51
51
  export { unwrapText } from "./utils/unwrapText";
52
52
  export { Pagination, getPaginationItems } from "./Pagination";
53
53
  export type { PaginationProps, PaginationItem } from "./Pagination";
54
- export { Accordion } from "./Accordion";
54
+ export { Accordion, useAccordionTriggerInk } from "./Accordion";
55
55
  export type {
56
56
  AccordionProps,
57
57
  AccordionVariant,
@@ -41,6 +41,23 @@ describe("EmptyState", () => {
41
41
  expect(container.querySelector("h2")?.textContent).toBe("Create your first record");
42
42
  expect(container.textContent).toContain("Records you add will show up here.");
43
43
  });
44
+
45
+ // MPO-473: a refusal's server reason arrives as one message per line. One
46
+ // web paragraph collapsed the break, so the lines read as a single sentence.
47
+ it("renders each line of the description as its own paragraph", () => {
48
+ const { container } = renderWithProviders(
49
+ <EmptyState
50
+ title="You can't create Pokemon"
51
+ description={
52
+ "You are not permitted to access this resource. Login to access\nFunction frappe.desk.form.load.getdoctype is not whitelisted.\n"
53
+ }
54
+ />,
55
+ );
56
+ expect(Array.from(container.querySelectorAll("p")).map((node) => node.textContent)).toEqual([
57
+ "You are not permitted to access this resource. Login to access",
58
+ "Function frappe.desk.form.load.getdoctype is not whitelisted.",
59
+ ]);
60
+ });
44
61
  });
45
62
 
46
63
  /**