@multiplatform.one/components 7.79.0 → 7.81.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 (411) hide show
  1. package/dist/cjs/CheckGlyph/MenuCheckColumn.cjs +56 -0
  2. package/dist/cjs/CheckGlyph/MenuCheckColumn.native.js +60 -0
  3. package/dist/cjs/CheckGlyph/MenuCheckColumn.native.js.map +1 -0
  4. package/dist/cjs/CheckGlyph/index.cjs +40 -0
  5. package/dist/cjs/CheckGlyph/index.native.js +44 -0
  6. package/dist/cjs/CheckGlyph/index.native.js.map +1 -0
  7. package/dist/cjs/ContextMenu/index.cjs +5 -22
  8. package/dist/cjs/ContextMenu/index.native.js +7 -25
  9. package/dist/cjs/ContextMenu/index.native.js.map +1 -1
  10. package/dist/cjs/DropdownMenu/index.cjs +5 -17
  11. package/dist/cjs/DropdownMenu/index.native.js +7 -19
  12. package/dist/cjs/DropdownMenu/index.native.js.map +1 -1
  13. package/dist/cjs/Popover.cjs +2 -2
  14. package/dist/cjs/Popover.native.js +2 -2
  15. package/dist/cjs/Popover.native.js.map +1 -1
  16. package/dist/cjs/SchemeToggle/index.cjs +2 -2
  17. package/dist/cjs/SchemeToggle/index.native.js +2 -2
  18. package/dist/cjs/SchemeToggle/index.native.js.map +1 -1
  19. package/dist/cjs/SheetModal/index.cjs +4 -1
  20. package/dist/cjs/SheetModal/index.native.js +1 -1
  21. package/dist/cjs/SheetModal/index.native.js.map +1 -1
  22. package/dist/cjs/TreeSelect/index.cjs +2 -6
  23. package/dist/cjs/TreeSelect/index.native.js +2 -6
  24. package/dist/cjs/TreeSelect/index.native.js.map +1 -1
  25. package/dist/cjs/appShell/AppShell.cjs +438 -0
  26. package/dist/cjs/appShell/AppShell.native.js +505 -0
  27. package/dist/cjs/appShell/AppShell.native.js.map +1 -0
  28. package/dist/cjs/appShell/ScopeMenu.cjs +39 -16
  29. package/dist/cjs/appShell/ScopeMenu.native.js +43 -17
  30. package/dist/cjs/appShell/ScopeMenu.native.js.map +1 -1
  31. package/dist/cjs/appShell/TabHost.native.js +41 -10
  32. package/dist/cjs/appShell/TabHost.native.js.map +1 -1
  33. package/dist/cjs/appShell/index.cjs +1 -0
  34. package/dist/cjs/appShell/index.native.js +1 -0
  35. package/dist/cjs/appShell/index.native.js.map +1 -1
  36. package/dist/cjs/layouts/page.cjs +3 -0
  37. package/dist/cjs/layouts/page.native.js +3 -0
  38. package/dist/cjs/layouts/page.native.js.map +1 -1
  39. package/dist/cjs/layouts/screenScroll.cjs +55 -0
  40. package/dist/cjs/layouts/screenScroll.native.js +59 -0
  41. package/dist/cjs/layouts/screenScroll.native.js.map +1 -1
  42. package/dist/cjs/layouts/sidebar.cjs +74 -16
  43. package/dist/cjs/layouts/sidebar.native.js +78 -16
  44. package/dist/cjs/layouts/sidebar.native.js.map +1 -1
  45. package/dist/esm/Accordion/index.native.js.map +1 -1
  46. package/dist/esm/AnimateHeight/index.native.js.map +1 -1
  47. package/dist/esm/Avatar/getInitials.native.js.map +1 -1
  48. package/dist/esm/Avatar/index.native.js.map +1 -1
  49. package/dist/esm/Badge/index.native.js.map +1 -1
  50. package/dist/esm/Button.native.js.map +1 -1
  51. package/dist/esm/Carousel/index.native.js.map +1 -1
  52. package/dist/esm/CheckGlyph/MenuCheckColumn.mjs +31 -0
  53. package/dist/esm/CheckGlyph/MenuCheckColumn.mjs.map +1 -0
  54. package/dist/esm/CheckGlyph/MenuCheckColumn.native.js +32 -0
  55. package/dist/esm/CheckGlyph/MenuCheckColumn.native.js.map +1 -0
  56. package/dist/esm/CheckGlyph/index.mjs +15 -0
  57. package/dist/esm/CheckGlyph/index.mjs.map +1 -0
  58. package/dist/esm/CheckGlyph/index.native.js +16 -0
  59. package/dist/esm/CheckGlyph/index.native.js.map +1 -0
  60. package/dist/esm/Chip/index.native.js.map +1 -1
  61. package/dist/esm/Collapsible.native.js.map +1 -1
  62. package/dist/esm/ContextMenu/index.mjs +6 -23
  63. package/dist/esm/ContextMenu/index.mjs.map +1 -1
  64. package/dist/esm/ContextMenu/index.native.js +8 -26
  65. package/dist/esm/ContextMenu/index.native.js.map +1 -1
  66. package/dist/esm/CopyField/CopyMenuButton.native.js.map +1 -1
  67. package/dist/esm/CopyField/index.native.js.map +1 -1
  68. package/dist/esm/Dialog.native.js.map +1 -1
  69. package/dist/esm/Diff/diff.native.js.map +1 -1
  70. package/dist/esm/Diff/index.native.js.map +1 -1
  71. package/dist/esm/DotIndicator/index.native.js.map +1 -1
  72. package/dist/esm/DropdownMenu/index.mjs +6 -18
  73. package/dist/esm/DropdownMenu/index.mjs.map +1 -1
  74. package/dist/esm/DropdownMenu/index.native.js +8 -20
  75. package/dist/esm/DropdownMenu/index.native.js.map +1 -1
  76. package/dist/esm/DropdownMenu/menuSettle.native.js.map +1 -1
  77. package/dist/esm/ElapsedProgress/index.native.js.map +1 -1
  78. package/dist/esm/ErrorBoundary/index.native.js.map +1 -1
  79. package/dist/esm/Heading.native.js.map +1 -1
  80. package/dist/esm/Kbd/index.native.js.map +1 -1
  81. package/dist/esm/Kbd/keys.native.js.map +1 -1
  82. package/dist/esm/Lightbox/MediaControl.native.js.map +1 -1
  83. package/dist/esm/Lightbox/index.gnome.native.js.map +1 -1
  84. package/dist/esm/Lightbox/shared.native.js.map +1 -1
  85. package/dist/esm/Lightbox/useLightbox.native.js.map +1 -1
  86. package/dist/esm/MachineLiteral/index.native.js.map +1 -1
  87. package/dist/esm/Material/index.native.js.map +1 -1
  88. package/dist/esm/Meter/index.native.js.map +1 -1
  89. package/dist/esm/Meter/meter.native.js.map +1 -1
  90. package/dist/esm/OverflowRow/index.native.js.map +1 -1
  91. package/dist/esm/Pagination.native.js.map +1 -1
  92. package/dist/esm/Popover.mjs +2 -2
  93. package/dist/esm/Popover.mjs.map +1 -1
  94. package/dist/esm/Popover.native.js +2 -2
  95. package/dist/esm/Popover.native.js.map +1 -1
  96. package/dist/esm/ProgressSteps/index.native.js.map +1 -1
  97. package/dist/esm/Quote/index.native.js.map +1 -1
  98. package/dist/esm/SchemeToggle/index.mjs +3 -3
  99. package/dist/esm/SchemeToggle/index.mjs.map +1 -1
  100. package/dist/esm/SchemeToggle/index.native.js +3 -3
  101. package/dist/esm/SchemeToggle/index.native.js.map +1 -1
  102. package/dist/esm/ScrollView/revealFocusedField.native.js.map +1 -1
  103. package/dist/esm/Separator/index.native.js.map +1 -1
  104. package/dist/esm/Sheet.native.js.map +1 -1
  105. package/dist/esm/SheetModal/SheetHeader.native.js.map +1 -1
  106. package/dist/esm/SheetModal/SheetNavigation.native.js.map +1 -1
  107. package/dist/esm/SheetModal/detents.native.js.map +1 -1
  108. package/dist/esm/SheetModal/index.mjs +5 -2
  109. package/dist/esm/SheetModal/index.mjs.map +1 -1
  110. package/dist/esm/SheetModal/index.native.js +2 -2
  111. package/dist/esm/SheetModal/index.native.js.map +1 -1
  112. package/dist/esm/SheetModal/useSheetFrameFocus.native.js.map +1 -1
  113. package/dist/esm/Skeleton/index.native.js.map +1 -1
  114. package/dist/esm/SkipLink/index.native.js.map +1 -1
  115. package/dist/esm/SortableList/SortableHandle.native.js.map +1 -1
  116. package/dist/esm/SortableList/index.native.js.map +1 -1
  117. package/dist/esm/SortableList/useSortableRows.native.js.map +1 -1
  118. package/dist/esm/SortableList/useUserOrder.native.js.map +1 -1
  119. package/dist/esm/Tabs/index.native.js.map +1 -1
  120. package/dist/esm/Text.native.js.map +1 -1
  121. package/dist/esm/ThemePresetPicker/index.native.js.map +1 -1
  122. package/dist/esm/Toast/index.native.js.map +1 -1
  123. package/dist/esm/Toast/store.native.js.map +1 -1
  124. package/dist/esm/Tooltip/nativePress.native.js.map +1 -1
  125. package/dist/esm/Tooltip/tooltipShared.native.js.map +1 -1
  126. package/dist/esm/TreeSelect/index.mjs +4 -8
  127. package/dist/esm/TreeSelect/index.mjs.map +1 -1
  128. package/dist/esm/TreeSelect/index.native.js +4 -8
  129. package/dist/esm/TreeSelect/index.native.js.map +1 -1
  130. package/dist/esm/TreeSelect/treeSelectModel.native.js.map +1 -1
  131. package/dist/esm/Video/PlayerChrome.native.js.map +1 -1
  132. package/dist/esm/Video/VideoFrame.native.js.map +1 -1
  133. package/dist/esm/Video/types.native.js.map +1 -1
  134. package/dist/esm/Wheel/index.native.js.map +1 -1
  135. package/dist/esm/Wizard/index.native.js.map +1 -1
  136. package/dist/esm/actions/ActionBar.native.js.map +1 -1
  137. package/dist/esm/actions/DangerSection.native.js.map +1 -1
  138. package/dist/esm/actions/DestructiveAction.native.js.map +1 -1
  139. package/dist/esm/actions/HeaderActions.native.js.map +1 -1
  140. package/dist/esm/actions/StandardAction.native.js.map +1 -1
  141. package/dist/esm/actions/devWarn.native.js.map +1 -1
  142. package/dist/esm/actions/index.native.js.map +1 -1
  143. package/dist/esm/actions/severity.native.js.map +1 -1
  144. package/dist/esm/appShell/AppShell.mjs +412 -0
  145. package/dist/esm/appShell/AppShell.mjs.map +1 -0
  146. package/dist/esm/appShell/AppShell.native.js +476 -0
  147. package/dist/esm/appShell/AppShell.native.js.map +1 -0
  148. package/dist/esm/appShell/Dock.native.js.map +1 -1
  149. package/dist/esm/appShell/MpoTabHost.native.js.map +1 -1
  150. package/dist/esm/appShell/ScopeMenu.mjs +43 -20
  151. package/dist/esm/appShell/ScopeMenu.mjs.map +1 -1
  152. package/dist/esm/appShell/ScopeMenu.native.js +47 -21
  153. package/dist/esm/appShell/ScopeMenu.native.js.map +1 -1
  154. package/dist/esm/appShell/SettingsSplit.native.js.map +1 -1
  155. package/dist/esm/appShell/Springboard.native.js.map +1 -1
  156. package/dist/esm/appShell/SystemBar.native.js.map +1 -1
  157. package/dist/esm/appShell/TabBar.mjs.map +1 -1
  158. package/dist/esm/appShell/TabBar.native.js.map +1 -1
  159. package/dist/esm/appShell/TabHost.native.js +41 -12
  160. package/dist/esm/appShell/TabHost.native.js.map +1 -1
  161. package/dist/esm/appShell/appTile.native.js.map +1 -1
  162. package/dist/esm/appShell/bottomChrome.native.js.map +1 -1
  163. package/dist/esm/appShell/index.mjs +1 -0
  164. package/dist/esm/appShell/index.mjs.map +1 -1
  165. package/dist/esm/appShell/index.native.js +1 -0
  166. package/dist/esm/appShell/index.native.js.map +1 -1
  167. package/dist/esm/charts/Axis.native.js.map +1 -1
  168. package/dist/esm/charts/BarChart.native.js.map +1 -1
  169. package/dist/esm/charts/ChartDataView.native.js.map +1 -1
  170. package/dist/esm/charts/ChartSurface.native.js.map +1 -1
  171. package/dist/esm/charts/LineChart.native.js.map +1 -1
  172. package/dist/esm/charts/PieChart.native.js.map +1 -1
  173. package/dist/esm/charts/Sparkline.native.js.map +1 -1
  174. package/dist/esm/charts/accessibility.native.js.map +1 -1
  175. package/dist/esm/charts/chartHeight.native.js.map +1 -1
  176. package/dist/esm/charts/composeChart.native.js.map +1 -1
  177. package/dist/esm/charts/index.native.js.map +1 -1
  178. package/dist/esm/charts/math.native.js.map +1 -1
  179. package/dist/esm/charts/series.native.js.map +1 -1
  180. package/dist/esm/charts/svg.native.js.map +1 -1
  181. package/dist/esm/charts/types.native.js.map +1 -1
  182. package/dist/esm/charts/useChartTheme.native.js.map +1 -1
  183. package/dist/esm/code/Code/index.native.js.map +1 -1
  184. package/dist/esm/code/CodeBlock/index.native.js.map +1 -1
  185. package/dist/esm/code/HighlightedCode/index.native.js.map +1 -1
  186. package/dist/esm/code/Highlighter/houseSyntaxTheme.native.js.map +1 -1
  187. package/dist/esm/code/Highlighter/index.native.js.map +1 -1
  188. package/dist/esm/code/Highlighter/namedGroupMatchAll.native.js.map +1 -1
  189. package/dist/esm/code/Pre/index.native.js.map +1 -1
  190. package/dist/esm/code/index.native.js.map +1 -1
  191. package/dist/esm/componentColors.native.js.map +1 -1
  192. package/dist/esm/feedback/ConfirmDialog.native.js.map +1 -1
  193. package/dist/esm/feedback/NotifyHost.native.js.map +1 -1
  194. package/dist/esm/feedback/copy.native.js.map +1 -1
  195. package/dist/esm/feedback/index.native.js.map +1 -1
  196. package/dist/esm/feedback/notify.native.js.map +1 -1
  197. package/dist/esm/feedback/route.native.js.map +1 -1
  198. package/dist/esm/feedback/store.native.js.map +1 -1
  199. package/dist/esm/formLayouts/FormLayouts.native.js.map +1 -1
  200. package/dist/esm/formLayouts/PreferencePanel.native.js.map +1 -1
  201. package/dist/esm/formLayouts/index.native.js.map +1 -1
  202. package/dist/esm/hooks/index.native.js.map +1 -1
  203. package/dist/esm/hooks/useClipboard.native.js.map +1 -1
  204. package/dist/esm/hooks/useDocumentTitle.native.js.map +1 -1
  205. package/dist/esm/hooks/useHoldCue.native.js.map +1 -1
  206. package/dist/esm/icons/CheckRegular.native.js.map +1 -1
  207. package/dist/esm/icons/MinusRegular.native.js.map +1 -1
  208. package/dist/esm/icons/index.native.js.map +1 -1
  209. package/dist/esm/icons/lucide.native.js.map +1 -1
  210. package/dist/esm/icons/themed.native.js.map +1 -1
  211. package/dist/esm/images/Image/ImageFrame.native.js.map +1 -1
  212. package/dist/esm/images/Image/framing.native.js.map +1 -1
  213. package/dist/esm/images/Image/mediaRadius.native.js.map +1 -1
  214. package/dist/esm/images/Image/shared.native.js.map +1 -1
  215. package/dist/esm/images/Image/types.native.js.map +1 -1
  216. package/dist/esm/images/index.native.js.map +1 -1
  217. package/dist/esm/index.native.js.map +1 -1
  218. package/dist/esm/knobImmuneExports.native.js.map +1 -1
  219. package/dist/esm/layouts/AsyncBoundary.native.js.map +1 -1
  220. package/dist/esm/layouts/Debug/index.native.js.map +1 -1
  221. package/dist/esm/layouts/EmptyState.native.js.map +1 -1
  222. package/dist/esm/layouts/IdentityPlate.native.js.map +1 -1
  223. package/dist/esm/layouts/KeyboardAvoidingWrapper/behavior.native.js.map +1 -1
  224. package/dist/esm/layouts/PageTemplates.native.js.map +1 -1
  225. package/dist/esm/layouts/PaneScaffold.native.js.map +1 -1
  226. package/dist/esm/layouts/SafeAreaWrapper/insets.native.js.map +1 -1
  227. package/dist/esm/layouts/StatusDot.native.js.map +1 -1
  228. package/dist/esm/layouts/StatusGlyphStrip.native.js.map +1 -1
  229. package/dist/esm/layouts/ViewSwitcher.native.js.map +1 -1
  230. package/dist/esm/layouts/attachments.native.js.map +1 -1
  231. package/dist/esm/layouts/bottomAccessory.native.js.map +1 -1
  232. package/dist/esm/layouts/breadcrumbs.native.js.map +1 -1
  233. package/dist/esm/layouts/index.native.js.map +1 -1
  234. package/dist/esm/layouts/page.mjs +4 -2
  235. package/dist/esm/layouts/page.mjs.map +1 -1
  236. package/dist/esm/layouts/page.native.js +4 -2
  237. package/dist/esm/layouts/page.native.js.map +1 -1
  238. package/dist/esm/layouts/pageHeadingScope.native.js.map +1 -1
  239. package/dist/esm/layouts/screenScroll.mjs +53 -1
  240. package/dist/esm/layouts/screenScroll.mjs.map +1 -1
  241. package/dist/esm/layouts/screenScroll.native.js +57 -1
  242. package/dist/esm/layouts/screenScroll.native.js.map +1 -1
  243. package/dist/esm/layouts/shellSidebarSlot.native.js.map +1 -1
  244. package/dist/esm/layouts/sidebar.mjs +74 -19
  245. package/dist/esm/layouts/sidebar.mjs.map +1 -1
  246. package/dist/esm/layouts/sidebar.native.js +78 -19
  247. package/dist/esm/layouts/sidebar.native.js.map +1 -1
  248. package/dist/esm/layouts/tags.native.js.map +1 -1
  249. package/dist/esm/layouts/timeline.native.js.map +1 -1
  250. package/dist/esm/layouts/toolbar.native.js.map +1 -1
  251. package/dist/esm/shared/actionScan.native.js.map +1 -1
  252. package/dist/esm/shared/i18n.native.js.map +1 -1
  253. package/dist/esm/shared/restoreRetries.native.js.map +1 -1
  254. package/dist/esm/shared/t.native.js.map +1 -1
  255. package/dist/esm/shared/textRidesText.native.js.map +1 -1
  256. package/dist/esm/shared/useReturnFocusOnClose.native.js.map +1 -1
  257. package/dist/esm/surfaces.native.js.map +1 -1
  258. package/dist/esm/tamagui.native.js.map +1 -1
  259. package/dist/esm/testSetup.native.js.map +1 -1
  260. package/dist/esm/utils/setTinted.native.js.map +1 -1
  261. package/dist/esm/utils/unwrapText.native.js.map +1 -1
  262. package/dist/esm/views/Calendar.native.js.map +1 -1
  263. package/dist/esm/views/Dashboard.native.js.map +1 -1
  264. package/dist/esm/views/Gantt.native.js.map +1 -1
  265. package/dist/esm/views/GanttDateGrip.native.js.map +1 -1
  266. package/dist/esm/views/ImageGrid.native.js.map +1 -1
  267. package/dist/esm/views/List.native.js.map +1 -1
  268. package/dist/esm/views/ListGroup.native.js.map +1 -1
  269. package/dist/esm/views/ListRow.native.js.map +1 -1
  270. package/dist/esm/views/Matrix.native.js.map +1 -1
  271. package/dist/esm/views/ReportBuilder.native.js.map +1 -1
  272. package/dist/esm/views/ShortcutCard.native.js.map +1 -1
  273. package/dist/esm/views/TreeView.native.js.map +1 -1
  274. package/dist/esm/views/calendarMath.native.js.map +1 -1
  275. package/dist/esm/views/ganttIntervals.native.js.map +1 -1
  276. package/dist/esm/views/ganttMath.native.js.map +1 -1
  277. package/dist/esm/views/index.native.js.map +1 -1
  278. package/dist/esm/views/kanbanDnd.native.js.map +1 -1
  279. package/dist/esm/views/matrixMath.native.js.map +1 -1
  280. package/dist/esm/views/sameNameScope.native.js.map +1 -1
  281. package/dist/esm/views/treeViewDnd.native.js.map +1 -1
  282. package/dist/esm/views/treeViewLayout.native.js.map +1 -1
  283. package/dist/esm/views/treeViewMotion.native.js.map +1 -1
  284. package/dist/esm/views/treeViewReveal.native.js.map +1 -1
  285. package/dist/esm/views/usePageFlowLoadMore.native.js.map +1 -1
  286. package/dist/esm/views/widgetChrome.native.js.map +1 -1
  287. package/dist/jsx/CheckGlyph/MenuCheckColumn.mjs +31 -0
  288. package/dist/jsx/CheckGlyph/MenuCheckColumn.mjs.map +1 -0
  289. package/dist/jsx/CheckGlyph/MenuCheckColumn.native.js +60 -0
  290. package/dist/jsx/CheckGlyph/MenuCheckColumn.native.js.map +1 -0
  291. package/dist/jsx/CheckGlyph/index.mjs +15 -0
  292. package/dist/jsx/CheckGlyph/index.mjs.map +1 -0
  293. package/dist/jsx/CheckGlyph/index.native.js +44 -0
  294. package/dist/jsx/CheckGlyph/index.native.js.map +1 -0
  295. package/dist/jsx/ContextMenu/index.mjs +6 -23
  296. package/dist/jsx/ContextMenu/index.mjs.map +1 -1
  297. package/dist/jsx/ContextMenu/index.native.js +7 -25
  298. package/dist/jsx/ContextMenu/index.native.js.map +1 -1
  299. package/dist/jsx/DropdownMenu/index.mjs +6 -18
  300. package/dist/jsx/DropdownMenu/index.mjs.map +1 -1
  301. package/dist/jsx/DropdownMenu/index.native.js +7 -19
  302. package/dist/jsx/DropdownMenu/index.native.js.map +1 -1
  303. package/dist/jsx/Popover.mjs +2 -2
  304. package/dist/jsx/Popover.mjs.map +1 -1
  305. package/dist/jsx/Popover.native.js +2 -2
  306. package/dist/jsx/Popover.native.js.map +1 -1
  307. package/dist/jsx/SchemeToggle/index.mjs +3 -3
  308. package/dist/jsx/SchemeToggle/index.mjs.map +1 -1
  309. package/dist/jsx/SchemeToggle/index.native.js +2 -2
  310. package/dist/jsx/SchemeToggle/index.native.js.map +1 -1
  311. package/dist/jsx/SheetModal/index.mjs +5 -2
  312. package/dist/jsx/SheetModal/index.mjs.map +1 -1
  313. package/dist/jsx/SheetModal/index.native.js +1 -1
  314. package/dist/jsx/SheetModal/index.native.js.map +1 -1
  315. package/dist/jsx/TreeSelect/index.mjs +4 -8
  316. package/dist/jsx/TreeSelect/index.mjs.map +1 -1
  317. package/dist/jsx/TreeSelect/index.native.js +2 -6
  318. package/dist/jsx/TreeSelect/index.native.js.map +1 -1
  319. package/dist/jsx/appShell/AppShell.mjs +412 -0
  320. package/dist/jsx/appShell/AppShell.mjs.map +1 -0
  321. package/dist/jsx/appShell/AppShell.native.js +505 -0
  322. package/dist/jsx/appShell/AppShell.native.js.map +1 -0
  323. package/dist/jsx/appShell/ScopeMenu.mjs +43 -20
  324. package/dist/jsx/appShell/ScopeMenu.mjs.map +1 -1
  325. package/dist/jsx/appShell/ScopeMenu.native.js +43 -17
  326. package/dist/jsx/appShell/ScopeMenu.native.js.map +1 -1
  327. package/dist/jsx/appShell/TabBar.mjs.map +1 -1
  328. package/dist/jsx/appShell/TabBar.native.js.map +1 -1
  329. package/dist/jsx/appShell/TabHost.native.js +41 -10
  330. package/dist/jsx/appShell/TabHost.native.js.map +1 -1
  331. package/dist/jsx/appShell/index.mjs +1 -0
  332. package/dist/jsx/appShell/index.mjs.map +1 -1
  333. package/dist/jsx/appShell/index.native.js +1 -0
  334. package/dist/jsx/appShell/index.native.js.map +1 -1
  335. package/dist/jsx/feedback/NotifyHost.native.js +1 -1
  336. package/dist/jsx/formLayouts/PreferencePanel.native.js +1 -1
  337. package/dist/jsx/index.native.js +1 -1
  338. package/dist/jsx/layouts/AsyncBoundary.native.js +1 -1
  339. package/dist/jsx/layouts/AsyncBoundary.native.js.map +1 -1
  340. package/dist/jsx/layouts/page.mjs +4 -2
  341. package/dist/jsx/layouts/page.mjs.map +1 -1
  342. package/dist/jsx/layouts/page.native.js +3 -0
  343. package/dist/jsx/layouts/page.native.js.map +1 -1
  344. package/dist/jsx/layouts/screenScroll.mjs +53 -1
  345. package/dist/jsx/layouts/screenScroll.mjs.map +1 -1
  346. package/dist/jsx/layouts/screenScroll.native.js +59 -0
  347. package/dist/jsx/layouts/screenScroll.native.js.map +1 -1
  348. package/dist/jsx/layouts/sidebar.mjs +74 -19
  349. package/dist/jsx/layouts/sidebar.mjs.map +1 -1
  350. package/dist/jsx/layouts/sidebar.native.js +78 -16
  351. package/dist/jsx/layouts/sidebar.native.js.map +1 -1
  352. package/package.json +9 -9
  353. package/src/CheckGlyph/CheckGlyph.spec.tsx +46 -0
  354. package/src/CheckGlyph/MenuCheckColumn.tsx +32 -0
  355. package/src/CheckGlyph/index.tsx +12 -0
  356. package/src/ContextMenu/ContextMenu.spec.tsx +46 -1
  357. package/src/ContextMenu/index.tsx +5 -22
  358. package/src/DropdownMenu/DropdownMenu.avatarTrigger.spec.tsx +6 -9
  359. package/src/DropdownMenu/index.tsx +5 -18
  360. package/src/Popover.coverTrigger.spec.tsx +3 -3
  361. package/src/Popover.tsx +5 -5
  362. package/src/SchemeToggle/SchemeToggle.spec.tsx +5 -11
  363. package/src/SchemeToggle/SchemeToggle.stories.tsx +21 -0
  364. package/src/SchemeToggle/index.tsx +2 -1
  365. package/src/SheetModal/index.native.tsx +2 -1
  366. package/src/SheetModal/index.tsx +5 -2
  367. package/src/TreeSelect/index.tsx +2 -4
  368. package/src/appShell/AppShell.spec.tsx +289 -0
  369. package/src/appShell/AppShell.stories.tsx +112 -0
  370. package/src/appShell/AppShell.tsx +535 -0
  371. package/src/appShell/ScopeMenu.spec.tsx +22 -0
  372. package/src/appShell/ScopeMenu.tsx +57 -18
  373. package/src/appShell/TabBar.tsx +13 -3
  374. package/src/appShell/TabHost.native.spec.tsx +53 -6
  375. package/src/appShell/TabHost.native.tsx +50 -14
  376. package/src/appShell/TabHost.stories.tsx +72 -0
  377. package/src/appShell/index.ts +1 -0
  378. package/src/layouts/PageHeader.stories.tsx +3 -3
  379. package/src/layouts/Sidebar.spec.tsx +92 -1
  380. package/src/layouts/page.tsx +8 -0
  381. package/src/layouts/pageActionButton.spec.tsx +7 -3
  382. package/src/layouts/screenScroll.tsx +71 -0
  383. package/src/layouts/sidebar.tsx +102 -10
  384. package/types/CheckGlyph/MenuCheckColumn.d.ts +11 -0
  385. package/types/CheckGlyph/MenuCheckColumn.d.ts.map +1 -0
  386. package/types/CheckGlyph/index.d.ts +10 -0
  387. package/types/CheckGlyph/index.d.ts.map +1 -0
  388. package/types/ContextMenu/index.d.ts.map +1 -1
  389. package/types/DropdownMenu/index.d.ts.map +1 -1
  390. package/types/Popover.d.ts +1 -1
  391. package/types/Popover.d.ts.map +1 -1
  392. package/types/SchemeToggle/index.d.ts.map +1 -1
  393. package/types/SheetModal/index.d.ts.map +1 -1
  394. package/types/SheetModal/index.native.d.ts.map +1 -1
  395. package/types/TreeSelect/index.d.ts.map +1 -1
  396. package/types/appShell/AppShell.d.ts +47 -0
  397. package/types/appShell/AppShell.d.ts.map +1 -0
  398. package/types/appShell/ScopeMenu.d.ts +0 -18
  399. package/types/appShell/ScopeMenu.d.ts.map +1 -1
  400. package/types/appShell/TabBar.d.ts +12 -6
  401. package/types/appShell/TabBar.d.ts.map +1 -1
  402. package/types/appShell/TabHost.native.d.ts +23 -5
  403. package/types/appShell/TabHost.native.d.ts.map +1 -1
  404. package/types/appShell/index.d.ts +1 -0
  405. package/types/appShell/index.d.ts.map +1 -1
  406. package/types/layouts/page.d.ts +5 -0
  407. package/types/layouts/page.d.ts.map +1 -1
  408. package/types/layouts/screenScroll.d.ts +24 -0
  409. package/types/layouts/screenScroll.d.ts.map +1 -1
  410. package/types/layouts/sidebar.d.ts +37 -1
  411. package/types/layouts/sidebar.d.ts.map +1 -1
@@ -14,13 +14,16 @@
14
14
  * side, never one menu with two sections.
15
15
  *
16
16
  * Narrow viewports and native get FloatingPanel's sheet (phones take
17
- * sheets); the first-row contract holds there too.
17
+ * sheets). A sheet isn't over its trigger, so it drops the first row for a
18
+ * sheet title, the picker's label (FZ-8557).
18
19
  */
19
- import { FloatingPanel, type PanelWidth } from "@multiplatform.one/forms";
20
- import { useMenuRowCheckInk, menuRowChecked, useResolvedKnobs } from "@multiplatform.one/theme";
21
- import { CaretDownIcon, CaretUpIcon, CheckIcon } from "@phosphor-icons/react";
20
+ import { CheckGlyph } from "../CheckGlyph";
21
+ import { FloatingPanel, type PanelWidth, useInPanelSheet } from "@multiplatform.one/forms";
22
+ import { menuRowCheckGlyph, menuRowChecked, useResolvedKnobs } from "@multiplatform.one/theme";
23
+ import { CaretDownIcon, CaretUpIcon } from "@phosphor-icons/react";
22
24
  import { useState } from "react";
23
- import { XStack, YStack } from "tamagui";
25
+ import { View, XStack, YStack } from "tamagui";
26
+ import { OverlayHeader } from "../actions/StandardAction";
24
27
  import { SizableText } from "../Text";
25
28
 
26
29
  export interface ScopeMenuOption {
@@ -60,8 +63,8 @@ function ScopeRow({
60
63
  ariaLabel?: string;
61
64
  }) {
62
65
  const { knobProps, control } = useResolvedKnobs();
66
+ const inSheet = useInPanelSheet();
63
67
  const iconSize = Number(knobProps.controlIcon?.width) || undefined;
64
- const checkInk = useMenuRowCheckInk();
65
68
  const Chevron = chevron === "up" ? CaretUpIcon : CaretDownIcon;
66
69
  return (
67
70
  <XStack
@@ -71,8 +74,11 @@ function ScopeRow({
71
74
  {...(role === "menuitemradio" ? { "aria-checked": !!checked } : {})}
72
75
  alignItems="center"
73
76
  gap="$2"
74
- minHeight="$3"
75
- paddingHorizontal="$2.5"
77
+ // A menu row is the menu row recipe's height, a touch target on a phone
78
+ // (44pt, HIG); the bar trigger and its echo keep the bar's size.
79
+ minHeight={role === "menuitemradio" ? knobProps.menuRow.minHeight : "$3"}
80
+ // A phone sheet's rows keep HIG's 16pt gutter; a bar menu's hug the trigger.
81
+ paddingHorizontal={inSheet ? "$4" : "$2.5"}
76
82
  cursor="pointer"
77
83
  onPress={onPress}
78
84
  // The echo row is the trigger itself, laid on the panel (LC-112): the
@@ -91,8 +97,8 @@ function ScopeRow({
91
97
  {...(chevron === "down" ? { borderRadius: knobProps.borderRadius.borderRadius } : undefined)}
92
98
  >
93
99
  {role === "menuitemradio" ? (
94
- <YStack width={iconSize} alignItems="center" aria-hidden>
95
- {checked ? <CheckIcon size={iconSize} weight="bold" color={checkInk} /> : null}
100
+ <YStack width={menuRowCheckGlyph.size} alignItems="center" aria-hidden>
101
+ {checked ? <CheckGlyph /> : null}
96
102
  </YStack>
97
103
  ) : null}
98
104
  <SizableText size="$2" fontWeight={chevron ? "500" : "400"} numberOfLines={1}>
@@ -103,6 +109,42 @@ function ScopeRow({
103
109
  );
104
110
  }
105
111
 
112
+ /**
113
+ * Over its trigger, the menu's first row is the pressed item with its chevron
114
+ * flipped (LC-112). A phone sheet isn't over its trigger, so it takes a sheet
115
+ * title, the picker's label, and a close (HIG, FZ-8557 Round 40).
116
+ */
117
+ function ScopeMenuHead({
118
+ label,
119
+ currentLabel,
120
+ onClose,
121
+ testID,
122
+ }: {
123
+ label: string;
124
+ currentLabel: string;
125
+ onClose: () => void;
126
+ testID: string;
127
+ }) {
128
+ const inSheet = useInPanelSheet();
129
+ if (inSheet) {
130
+ return (
131
+ <View paddingHorizontal="$4" paddingBottom="$2">
132
+ <OverlayHeader title={label} onClose={onClose} testID={`${testID}-sheet-header`} />
133
+ </View>
134
+ );
135
+ }
136
+ return (
137
+ <ScopeRow
138
+ role="button"
139
+ label={currentLabel}
140
+ chevron="up"
141
+ ariaLabel={`${label}: ${currentLabel}`}
142
+ onPress={onClose}
143
+ testID={`${testID}-first-row`}
144
+ />
145
+ );
146
+ }
147
+
106
148
  export function ScopeMenu({
107
149
  label,
108
150
  options,
@@ -144,14 +186,11 @@ export function ScopeMenu({
144
186
  }
145
187
  >
146
188
  <YStack role="menu" aria-label={label} data-testid={`${testID}-panel`}>
147
- {/* The pressed item, kept as the first row with its chevron flipped. */}
148
- <ScopeRow
149
- role="button"
150
- label={currentLabel}
151
- chevron="up"
152
- ariaLabel={`${label}: ${currentLabel}`}
153
- onPress={() => setOpen(false)}
154
- testID={`${testID}-first-row`}
189
+ <ScopeMenuHead
190
+ label={label}
191
+ currentLabel={currentLabel}
192
+ onClose={() => setOpen(false)}
193
+ testID={testID}
155
194
  />
156
195
  {options.map((option) => (
157
196
  <ScopeRow
@@ -21,6 +21,7 @@ import {
21
21
  import { zIndex } from "@multiplatform.one/forms";
22
22
  import { isWeb } from "@multiplatform.one/platform";
23
23
  import { type ComponentType, type ReactNode, useEffect, useState } from "react";
24
+ import type { ImageSourcePropType } from "react-native";
24
25
  import { XStack, YStack } from "tamagui";
25
26
  import { materials, useMaterial } from "../Material";
26
27
  import { MaterialBlurFill } from "../Material/blur";
@@ -41,10 +42,19 @@ export interface TabBarItem {
41
42
  label: string;
42
43
  icon: TabBarIcon;
43
44
  /**
44
- * The SF Symbols the system tab bar draws on iOS (TabHost). Without them
45
- * on every item, iOS keeps the MPO TabBar.
45
+ * The glyphs the system tab bar draws (TabHost): SF Symbols on iOS, a
46
+ * raster template image on Android (Material's bar draws no SF Symbol or
47
+ * SVG; a bundled PNG, so an OTA can carry it). A platform keeps the MPO
48
+ * TabBar unless every item names its glyph.
46
49
  */
47
- nativeIcon?: { sfSymbol: string; sfSymbolSelected?: string };
50
+ nativeIcon?: TabBarNativeIcon;
51
+ }
52
+
53
+ export interface TabBarNativeIcon {
54
+ sfSymbol?: string;
55
+ sfSymbolSelected?: string;
56
+ /** A monochrome PNG (24dp, @2x and @3x beside it); the bar tints it. */
57
+ android?: ImageSourcePropType;
48
58
  }
49
59
 
50
60
  export interface TabBarProps {
@@ -16,23 +16,36 @@ import {
16
16
  NativeTabHost,
17
17
  TabHost,
18
18
  itemsHaveNativeIcons,
19
+ nativeIconFor,
19
20
  nativeTabBarHeight,
21
+ nativeTabsPlatforms,
20
22
  resolveNativeTabs,
21
23
  } from "./TabHost.native";
22
24
 
23
25
  afterEach(cleanup);
24
26
 
25
27
  const Glyph = () => null;
28
+ const png = (name: string) => ({ uri: `${name}.png`, width: 24, height: 24 });
26
29
 
27
30
  const items: TabBarItem[] = [
28
31
  {
29
32
  key: "/",
30
33
  label: "Home",
31
34
  icon: Glyph,
32
- nativeIcon: { sfSymbol: "house", sfSymbolSelected: "house.fill" },
35
+ nativeIcon: { sfSymbol: "house", sfSymbolSelected: "house.fill", android: png("house") },
36
+ },
37
+ {
38
+ key: "/pokemon",
39
+ label: "Pokemon",
40
+ icon: Glyph,
41
+ nativeIcon: { sfSymbol: "sparkles", android: png("sparkle") },
42
+ },
43
+ {
44
+ key: "/moves",
45
+ label: "Moves",
46
+ icon: Glyph,
47
+ nativeIcon: { sfSymbol: "bolt", android: png("lightning") },
33
48
  },
34
- { key: "/pokemon", label: "Pokemon", icon: Glyph, nativeIcon: { sfSymbol: "sparkles" } },
35
- { key: "/moves", label: "Moves", icon: Glyph, nativeIcon: { sfSymbol: "bolt" } },
36
49
  ];
37
50
 
38
51
  const fakeTabs: NativeTabs = {
@@ -58,7 +71,8 @@ const fakeTabs: NativeTabs = {
58
71
  data-testid={`native-screen-${tabKey}`}
59
72
  data-focused={String(Boolean(isFocused))}
60
73
  data-inset-adjust={String(overrideScrollViewContentInsetAdjustmentBehavior)}
61
- data-symbol={icon?.ios.name}
74
+ data-symbol={icon?.ios?.name}
75
+ data-android-icon={(icon?.android?.imageSource as { uri?: string } | undefined)?.uri}
62
76
  aria-label={title}
63
77
  >
64
78
  {children}
@@ -113,19 +127,49 @@ describe("native tabs detection", () => {
113
127
  expect(resolveNativeTabs(sources(["RNSTabsHost", "RNSTabsScreen"], throws), "ios")).toBeNull();
114
128
  });
115
129
 
116
- it("keeps the MPO TabBar on Android and the web", () => {
130
+ it("keeps the MPO TabBar on Android and the web by default", () => {
117
131
  const screens = vi.fn(() => ({ Tabs: fakeTabs }));
118
132
  const all = sources(["RNSTabsHost", "RNSTabsScreen"], screens);
133
+ expect(nativeTabsPlatforms).toEqual({ ios: true, android: false });
119
134
  expect(resolveNativeTabs(all, "android")).toBeNull();
120
135
  expect(resolveNativeTabs(all, "web")).toBeNull();
121
136
  expect(screens).not.toHaveBeenCalled();
122
137
  });
123
138
 
124
- it("needs an SF Symbol on every item", () => {
139
+ it("picks the Material bar on Android once the platform is switched on (FZ-8140)", () => {
140
+ const on = { ios: true, android: true };
141
+ expect(
142
+ resolveNativeTabs(
143
+ sources(["RNSTabsHost", "RNSTabsScreen"], () => ({ Tabs: fakeTabs })),
144
+ "android",
145
+ on,
146
+ ),
147
+ ).toBe(fakeTabs);
148
+ const screens = vi.fn(() => ({ Tabs: fakeTabs }));
149
+ expect(resolveNativeTabs(sources([], screens), "android", on)).toBeNull();
150
+ expect(screens).not.toHaveBeenCalled();
151
+ });
152
+
153
+ it("needs this platform's glyph on every item: an SF Symbol on iOS, a PNG on Android", () => {
125
154
  expect(itemsHaveNativeIcons(items)).toBe(true);
155
+ expect(itemsHaveNativeIcons(items, "android")).toBe(true);
126
156
  expect(itemsHaveNativeIcons([...items, { key: "/x", label: "X", icon: Glyph }])).toBe(false);
157
+ const symbolsOnly = items.map(({ nativeIcon, ...item }) => ({
158
+ ...item,
159
+ nativeIcon: { sfSymbol: nativeIcon?.sfSymbol },
160
+ }));
161
+ expect(itemsHaveNativeIcons(symbolsOnly, "ios")).toBe(true);
162
+ expect(itemsHaveNativeIcons(symbolsOnly, "android")).toBe(false);
127
163
  expect(itemsHaveNativeIcons([])).toBe(false);
128
164
  });
165
+
166
+ it("hands react-native-screens each platform's glyph from the one item", () => {
167
+ expect(nativeIconFor(items[0]!)).toEqual({
168
+ ios: { type: "sfSymbol", name: "house" },
169
+ android: { type: "imageSource", imageSource: png("house") },
170
+ });
171
+ expect(nativeIconFor({ key: "/x", label: "X", icon: Glyph })).toEqual({});
172
+ });
129
173
  });
130
174
 
131
175
  describe("NativeTabHost", () => {
@@ -136,6 +180,9 @@ describe("NativeTabHost", () => {
136
180
  expect(pokemon.contains(screen.getByTestId("page"))).toBe(true);
137
181
  expect(screen.getByTestId("native-screen-/").getAttribute("data-focused")).toBe("false");
138
182
  expect(screen.getByTestId("native-screen-/").getAttribute("data-symbol")).toBe("house");
183
+ expect(screen.getByTestId("native-screen-/").getAttribute("data-android-icon")).toBe(
184
+ "house.png",
185
+ );
139
186
  expect(screen.getByTestId("native-host").getAttribute("data-hidden")).toBe("false");
140
187
  expect(screen.queryByTestId("tab-bar")).toBeNull();
141
188
  });
@@ -10,9 +10,10 @@
10
10
  *
11
11
  * The native view ships in react-native-screens' binary half, so it is
12
12
  * feature-detected before react-native-screens' Tabs is required: a binary
13
- * without RNSTabsHost, a platform other than iOS, or an item without an SF
14
- * Symbol keeps the MPO TabBar. Android stays on the MPO TabBar until the
15
- * items carry image icons the Material bar can draw.
13
+ * without RNSTabsHost, a platform switched off in `nativeTabsPlatforms`, or
14
+ * an item without its platform's glyph keeps the MPO TabBar. Android's arm
15
+ * (Material bottom navigation, PNG template icons) is built and switched off
16
+ * until an Android build proves it (FZ-8140).
16
17
  *
17
18
  * The bar's clearance is the inset this host publishes, as with the MPO
18
19
  * TabBar, so a Screen pads by it whether or not it scrolls. Each tab screen
@@ -20,9 +21,13 @@
20
21
  * otherwise flip the first scroll view to `automatic`, and UIKit would add
21
22
  * the bar's height a second time (FZ-8640).
22
23
  */
23
- import { useGlyphColor, useIdentityInkOnSurface } from "@multiplatform.one/theme";
24
+ import {
25
+ useAccentTintedSurface,
26
+ useGlyphColor,
27
+ useIdentityInkOnSurface,
28
+ } from "@multiplatform.one/theme";
24
29
  import { type ComponentType, type ReactNode, useEffect, useRef, useState } from "react";
25
- import { Platform, UIManager } from "react-native";
30
+ import { type ImageSourcePropType, Platform, UIManager } from "react-native";
26
31
  import { setBottomChromeInset } from "./bottomChrome";
27
32
  import { useBottomInset } from "./bottomInset";
28
33
  import { type TabBarItem, tabBarInactiveInk } from "./TabBar";
@@ -36,7 +41,17 @@ export const nativeTabsComponents = ["RNSTabsHost", "RNSTabsScreen"] as const;
36
41
  /** Points the system tab bar covers above the home-indicator band. */
37
42
  export const nativeTabBarHeight = 49;
38
43
 
39
- type IconProp = { ios: { type: "sfSymbol"; name: string } };
44
+ type ImageIcon = { type: "imageSource"; imageSource: ImageSourcePropType };
45
+ type IconProp = { ios?: { type: "sfSymbol"; name: string }; android?: ImageIcon };
46
+
47
+ /**
48
+ * The platforms whose system tab bar TabHost draws. Android stays off until
49
+ * an emulator build shows the Material bar in light and dark (FZ-8140).
50
+ */
51
+ export const nativeTabsPlatforms: Readonly<Record<string, boolean>> = {
52
+ ios: true,
53
+ android: false,
54
+ };
40
55
 
41
56
  export interface NativeTabs {
42
57
  Host: ComponentType<{
@@ -47,6 +62,8 @@ export interface NativeTabs {
47
62
  tabBarItemIconColorActive?: string;
48
63
  tabBarItemTitleFontColor?: string;
49
64
  tabBarItemTitleFontColorActive?: string;
65
+ tabBarBackgroundColor?: string;
66
+ tabBarItemActiveIndicatorColor?: string;
50
67
  experimentalControlNavigationStateInJS?: boolean;
51
68
  onNativeFocusChange?: (event: { nativeEvent: { tabKey: string } }) => void;
52
69
  }>;
@@ -56,7 +73,7 @@ export interface NativeTabs {
56
73
  title?: string;
57
74
  isFocused?: boolean;
58
75
  icon?: IconProp;
59
- selectedIcon?: IconProp["ios"];
76
+ selectedIcon?: { type: "sfSymbol"; name: string };
60
77
  tabBarItemAccessibilityLabel?: string;
61
78
  tabBarItemTestID?: string;
62
79
  overrideScrollViewContentInsetAdjustmentBehavior?: boolean;
@@ -74,8 +91,12 @@ const appSources: NativeTabsSources = {
74
91
  };
75
92
 
76
93
  /** react-native-screens' Tabs, or null when this platform or binary cannot draw them. */
77
- export function resolveNativeTabs(sources: NativeTabsSources, os: string): NativeTabs | null {
78
- if (os !== "ios") return null;
94
+ export function resolveNativeTabs(
95
+ sources: NativeTabsSources,
96
+ os: string,
97
+ platforms: Readonly<Record<string, boolean>> = nativeTabsPlatforms,
98
+ ): NativeTabs | null {
99
+ if (!platforms[os]) return null;
79
100
  try {
80
101
  if (!nativeTabsComponents.every((name) => sources.hasComponent(name))) return null;
81
102
  const tabs = sources.screens().Tabs;
@@ -92,9 +113,20 @@ function loadNativeTabs(): NativeTabs | null {
92
113
  return cached;
93
114
  }
94
115
 
95
- /** True when every item names the SF Symbol the system bar draws. */
96
- export function itemsHaveNativeIcons(items: readonly TabBarItem[]): boolean {
97
- return items.length > 0 && items.every((item) => Boolean(item.nativeIcon?.sfSymbol));
116
+ /** True when every item names the glyph this platform's system bar draws. */
117
+ export function itemsHaveNativeIcons(items: readonly TabBarItem[], os = "ios"): boolean {
118
+ const glyph = (item: TabBarItem) =>
119
+ os === "android" ? item.nativeIcon?.android : item.nativeIcon?.sfSymbol;
120
+ return items.length > 0 && items.every((item) => Boolean(glyph(item)));
121
+ }
122
+
123
+ /** The item's glyphs in react-native-screens' per-platform icon shape. */
124
+ export function nativeIconFor(item: TabBarItem): IconProp {
125
+ const { sfSymbol, android } = item.nativeIcon ?? {};
126
+ return {
127
+ ...(sfSymbol ? { ios: { type: "sfSymbol" as const, name: sfSymbol } } : {}),
128
+ ...(android ? { android: { type: "imageSource" as const, imageSource: android } } : {}),
129
+ };
98
130
  }
99
131
 
100
132
  export function NativeTabHost({
@@ -107,6 +139,8 @@ export function NativeTabHost({
107
139
  const { Host, Screen } = tabs;
108
140
  const accent = useIdentityInkOnSurface();
109
141
  const ink = useGlyphColor(tabBarInactiveInk);
142
+ const surface = useGlyphColor("$color2");
143
+ const indicator = useAccentTintedSurface();
110
144
  const deviceInset = useBottomInset();
111
145
  const [id] = useState(() => Symbol("native-tab-host"));
112
146
  const lastKey = useRef(activeKey ?? items[0]?.key);
@@ -126,6 +160,8 @@ export function NativeTabHost({
126
160
  tabBarItemIconColorActive={accent}
127
161
  tabBarItemTitleFontColor={ink}
128
162
  tabBarItemTitleFontColorActive={accent}
163
+ tabBarBackgroundColor={surface}
164
+ tabBarItemActiveIndicatorColor={indicator}
129
165
  experimentalControlNavigationStateInJS
130
166
  onNativeFocusChange={(event) => {
131
167
  const key = event.nativeEvent.tabKey;
@@ -138,7 +174,7 @@ export function NativeTabHost({
138
174
  tabKey={item.key}
139
175
  title={item.label}
140
176
  isFocused={item.key === focusedKey}
141
- icon={{ ios: { type: "sfSymbol", name: item.nativeIcon!.sfSymbol } }}
177
+ icon={nativeIconFor(item)}
142
178
  selectedIcon={
143
179
  item.nativeIcon?.sfSymbolSelected
144
180
  ? { type: "sfSymbol", name: item.nativeIcon.sfSymbolSelected }
@@ -157,6 +193,6 @@ export function NativeTabHost({
157
193
 
158
194
  export function TabHost(props: TabHostProps) {
159
195
  const tabs = loadNativeTabs();
160
- if (!tabs || !itemsHaveNativeIcons(props.items)) return <MpoTabHost {...props} />;
196
+ if (!tabs || !itemsHaveNativeIcons(props.items, Platform.OS)) return <MpoTabHost {...props} />;
161
197
  return <NativeTabHost tabs={tabs} {...props} />;
162
198
  }
@@ -0,0 +1,72 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
2
+ import { HouseIcon, LightningIcon, SparkleIcon, SquaresFourIcon } from "@phosphor-icons/react";
3
+ import { useState } from "react";
4
+ import { Screen } from "../layouts/page";
5
+ import { Text } from "../Text";
6
+ import type { TabBarItem } from "./TabBar";
7
+ import { TabHost } from "./TabHost";
8
+
9
+ const meta: Meta<typeof TabHost> = {
10
+ title: "Components/TabHost",
11
+ component: TabHost,
12
+ parameters: { status: { type: "beta" }, layout: "fullscreen" },
13
+ };
14
+
15
+ export default meta;
16
+ type Story = StoryObj<typeof TabHost>;
17
+
18
+ const items: TabBarItem[] = [
19
+ {
20
+ key: "/",
21
+ label: "Home",
22
+ icon: HouseIcon,
23
+ nativeIcon: { sfSymbol: "house", sfSymbolSelected: "house.fill" },
24
+ },
25
+ { key: "/pokemon", label: "Pokemon", icon: SparkleIcon, nativeIcon: { sfSymbol: "sparkles" } },
26
+ {
27
+ key: "/moves",
28
+ label: "Moves",
29
+ icon: LightningIcon,
30
+ nativeIcon: { sfSymbol: "bolt", sfSymbolSelected: "bolt.fill" },
31
+ },
32
+ {
33
+ key: "/backoffice",
34
+ label: "Backoffice",
35
+ icon: SquaresFourIcon,
36
+ nativeIcon: { sfSymbol: "square.grid.2x2", sfSymbolSelected: "square.grid.2x2.fill" },
37
+ },
38
+ ];
39
+
40
+ const rows = Array.from({ length: 30 }, (_, index) => `Row ${index + 1}`);
41
+
42
+ function Sections({ initial, outside }: { initial: string; outside?: boolean }) {
43
+ const [active, setActive] = useState(initial);
44
+ const label = items.find((item) => item.key === active)?.label;
45
+ return (
46
+ <TabHost items={items} activeKey={outside ? undefined : active} onTabPress={setActive}>
47
+ <Screen>
48
+ <Text>{outside ? "A route under no tab" : label}</Text>
49
+ {rows.map((row) => (
50
+ <Text key={row}>{row}</Text>
51
+ ))}
52
+ </Screen>
53
+ </TabHost>
54
+ );
55
+ }
56
+
57
+ /**
58
+ * One tab config: the system tab bar on an iPhone whose binary carries
59
+ * native tabs, the MPO floating TabBar on the web, GNOME, Android and an
60
+ * older binary.
61
+ */
62
+ export const Default: Story = {
63
+ render: () => <Sections initial="/pokemon" />,
64
+ };
65
+
66
+ /**
67
+ * A route under no tab (no `activeKey`): no bar on any platform, and the
68
+ * screen keeps its place. AppShell keeps the bar on routes below a tab.
69
+ */
70
+ export const OutsideTabs: Story = {
71
+ render: () => <Sections initial="/pokemon" outside />,
72
+ };
@@ -6,4 +6,5 @@ export { TabHost, type TabHostProps } from "./TabHost";
6
6
  export * from "./Springboard";
7
7
  export type { AppEntry } from "./appTile";
8
8
  export * from "./SettingsSplit";
9
+ export * from "./AppShell";
9
10
  export { useBottomChromeInset } from "./bottomChrome";
@@ -191,9 +191,9 @@ function Meter({ label, value, detail }: { label: string; value: number; detail:
191
191
  );
192
192
  }
193
193
 
194
- // FZ-8480 LC-112 (Round 37): each icon trigger's panel covers its button and
195
- // draws it again in place as an X that closes the panel. The join panel's
196
- // header shares the X's line.
194
+ // LC-112 (FZ-8665 Round 45): each icon trigger's panel covers its button with
195
+ // no X, and a press outside closes it. The join panel's title sits on the
196
+ // panel's padding.
197
197
  function AddNode() {
198
198
  return (
199
199
  <Popover anchorMode="cover-trigger">
@@ -1,7 +1,17 @@
1
1
  import { renderWithProviders } from "@multiplatform.one/test-utils";
2
- import { describe, expect, it } from "vitest";
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
3
  import { Sidebar } from "./sidebar";
4
4
 
5
+ const surface = vi.hoisted(() => ({ touch: false }));
6
+ vi.mock("@multiplatform.one/theme", async (original) => ({
7
+ ...(await original<typeof import("@multiplatform.one/theme")>()),
8
+ useTouchSurface: () => surface.touch,
9
+ }));
10
+
11
+ afterEach(() => {
12
+ surface.touch = false;
13
+ });
14
+
5
15
  describe("Sidebar selection tier", () => {
6
16
  it("paints active row labels and metadata with selection ink", () => {
7
17
  const result = renderWithProviders(
@@ -120,3 +130,84 @@ describe("Sidebar footer", () => {
120
130
  expect(result.getByTestId("rail").querySelector("[data-sidebar-footer]")).toBeNull();
121
131
  });
122
132
  });
133
+
134
+ describe("Sidebar rows on touch (FZ-8649)", () => {
135
+ const sections = [{ items: [{ value: "a", label: "Alpha", onPress: () => {} }] }];
136
+
137
+ it("holds a row to the 44pt floor on a touch surface", () => {
138
+ surface.touch = true;
139
+ const result = renderWithProviders(<Sidebar sections={sections} />);
140
+ expect(result.container.querySelector("[data-sidebar-row]")!.className).toContain("_mih-44px");
141
+ });
142
+
143
+ it("keeps a pointer rail at its token height", () => {
144
+ const result = renderWithProviders(<Sidebar sections={sections} />);
145
+ expect(result.container.querySelector("[data-sidebar-row]")!.className).not.toContain(
146
+ "_mih-44px",
147
+ );
148
+ });
149
+ });
150
+
151
+ describe("Sidebar hug (FZ-8649)", () => {
152
+ const sections = [{ items: [{ value: "a", label: "Alpha" }] }];
153
+ // Rows sit directly in the rail's first child unless a scroller wraps them.
154
+ const rowsAreDirect = (rail: HTMLElement) =>
155
+ Array.from(rail.firstElementChild!.children).some(
156
+ (child) => child.getAttribute("role") === "group",
157
+ );
158
+
159
+ it("fills its column by default and scrolls its rows", () => {
160
+ const result = renderWithProviders(
161
+ <Sidebar sections={sections} testID="rail" footer={<span>Account</span>} />,
162
+ );
163
+ const rail = result.getByTestId("rail");
164
+ expect(rail.className).toContain("_fg-1");
165
+ expect(rail.className).toContain("_h-10037");
166
+ expect(rail.querySelector("[data-sidebar-footer]")!.className).toContain("_mah-5037");
167
+ expect(rowsAreDirect(rail)).toBe(false);
168
+ });
169
+
170
+ it("takes the height of its rows and leaves scrolling to the container", () => {
171
+ const result = renderWithProviders(
172
+ <Sidebar sections={sections} testID="rail" hug footer={<span>Account</span>} />,
173
+ );
174
+ const rail = result.getByTestId("rail");
175
+ expect(rail.className).not.toContain("_fg-1");
176
+ expect(rail.className).not.toContain("_h-10037");
177
+ expect(rail.querySelector("[data-sidebar-footer]")!.className).not.toContain("_mah-5037");
178
+ expect(rowsAreDirect(rail)).toBe(true);
179
+ expect(result.getByText("Alpha")).toBeTruthy();
180
+ expect(result.getByText("Account")).toBeTruthy();
181
+ });
182
+ });
183
+
184
+ describe("Sidebar on the shell material (FZ-7996)", () => {
185
+ const sections = [
186
+ {
187
+ items: [
188
+ { value: "home", label: "Home", active: true, onPress: () => {} },
189
+ { value: "pokemon", label: "Pokemon", onPress: () => {} },
190
+ { value: "moves", label: "Moves", onPress: () => {} },
191
+ ],
192
+ },
193
+ ];
194
+
195
+ it("sets every row as a rounded pill with no rule between rows", () => {
196
+ const result = renderWithProviders(<Sidebar material sections={sections} />);
197
+ const rows = [...result.container.querySelectorAll("[data-sidebar-row]")];
198
+ expect(rows).toHaveLength(3);
199
+ for (const row of rows) {
200
+ expect(row.className).toMatch(/_btlr-/);
201
+ expect(row.className).toMatch(/_bbrr-/);
202
+ }
203
+ expect(rows[1]!.previousElementSibling).toBe(rows[0]);
204
+ expect(rows[2]!.previousElementSibling).toBe(rows[1]);
205
+ });
206
+
207
+ it("keeps the stacked group with rules between rows off the material", () => {
208
+ const result = renderWithProviders(<Sidebar sections={sections} />);
209
+ const rows = [...result.container.querySelectorAll("[data-sidebar-row]")];
210
+ expect(rows[1]!.previousElementSibling).not.toBe(rows[0]);
211
+ expect(rows[1]!.previousElementSibling?.hasAttribute("data-sidebar-row")).toBe(false);
212
+ });
213
+ });
@@ -67,6 +67,7 @@ import {
67
67
  ScreenScrollScope,
68
68
  TopBarSlot,
69
69
  useLargeTitle,
70
+ useReportScreenScroll,
70
71
  useScreenScroll,
71
72
  useScreenScrollController,
72
73
  useTopBarSlot,
@@ -97,6 +98,12 @@ function negateSpaceToken(token: string): `$-${string}` {
97
98
  */
98
99
  export const CONTENT_MAX_WIDTH = layoutBreakpoints.xl;
99
100
 
101
+ /**
102
+ * The readable column an app shell centres its content in at regular width
103
+ * (UIKit's readableContentGuide): the two-pane budget breakpoint.
104
+ */
105
+ export const READABLE_MAX_WIDTH = layoutBreakpoints.expanded;
106
+
100
107
  export interface ScreenProps extends YStackProps {
101
108
  /** Wrap content in a ScrollView. Default: true. */
102
109
  scroll?: boolean;
@@ -271,6 +278,7 @@ function PageShell({
271
278
  }: ScreenProps) {
272
279
  const { knobProps } = useResolvedKnobs({ component: "Screen" });
273
280
  const scrollState = useScreenScrollController();
281
+ useReportScreenScroll(scrollState.state, Boolean(topBar));
274
282
  const [accessories] = useState(createBottomAccessoryStore);
275
283
  const chromeInset = useBottomChromeInset();
276
284
  const inset = noPadding ? null : (knobProps.panelPadding.padding as string);
@@ -57,11 +57,15 @@ describe("PageActionButton (LC-128, LC-172)", () => {
57
57
 
58
58
  it("inks its glyph with the accent and keeps the back control's recipe", () => {
59
59
  const { action, menu } = renderBar();
60
- // The glyph is currentColor: the frame's colour is the accent ink on the
61
- // bar. The "…" beside it takes the bar tint too (LC-145, FZ-7993).
60
+ // The glyph carries the bar tint on its fill and its stroke, one ink
61
+ // (FZ-8649). The "…" beside it takes the bar tint too (LC-145, FZ-7993).
62
62
  expect(rest(action, "_col-")).toHaveLength(1);
63
63
  expect(rest(menu, "_col-")).toHaveLength(1);
64
- expect(action.querySelector("svg")?.getAttribute("fill")).toBe("currentColor");
64
+ const glyph = action.querySelector("svg");
65
+ const ink = glyph?.getAttribute("fill");
66
+ expect(ink).toBeTruthy();
67
+ expect(ink).not.toBe("currentColor");
68
+ if (glyph?.hasAttribute("stroke")) expect(glyph.getAttribute("stroke")).toBe(ink);
65
69
  expect(action.getAttribute("data-size-token")).toBe(menu.getAttribute("data-size-token"));
66
70
  expect(action.getAttribute("data-mp-button-height")).toBe(
67
71
  menu.getAttribute("data-mp-button-height"),