@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
@@ -22,11 +22,11 @@ import { IconSlot } from "@multiplatform.one/theme";
22
22
  * Sheet at ≤640 via FloatingPanel's overlay family pivot.
23
23
  */
24
24
 
25
+ import { MenuCheckColumn } from "../CheckGlyph/MenuCheckColumn";
25
26
  import {
26
27
  transitionProps,
27
28
  hairline,
28
29
  keyboardFocusRingProps,
29
- useMenuRowCheckInk,
30
30
  menuRowChecked,
31
31
  menuRowFrame,
32
32
  useResolvedKnobs,
@@ -49,7 +49,6 @@ import {
49
49
  type RefObject,
50
50
  } from "react";
51
51
 
52
- import { CheckIcon } from "@phosphor-icons/react";
53
52
  import { FloatingPanel } from "@multiplatform.one/forms";
54
53
  import type { GetProps, TamaguiElement } from "tamagui";
55
54
  import {
@@ -257,16 +256,15 @@ function ContextMenuRoot({
257
256
  contentPadding="none"
258
257
  triggerSizing={triggerSizing}
259
258
  {...freeOverlay}
260
- // LC-112 (FZ-8480 Round 37): a bar button's menu opens ON the button
261
- // at every width and draws the button again in place as an X, so
262
- // pressing the button's spot closes the menu.
259
+ // LC-112 (FZ-8665 Round 45): a bar button's menu opens ON the button
260
+ // at every width, and its rows start at its top edge.
263
261
  {...(anchor === "trigger"
264
262
  ? {
265
263
  compactFace: "menu" as const,
266
264
  anchorMode: "cover-trigger" as const,
267
265
  coverAlign: "auto" as const,
268
266
  triggerEcho: false,
269
- iconEcho: "menu" as const,
267
+ iconCover: true,
270
268
  }
271
269
  : null)}
272
270
  >
@@ -618,7 +616,6 @@ export interface ContextMenuCheckboxItemProps extends Omit<ContextMenuItemProps,
618
616
 
619
617
  const ContextMenuCheckboxItem = forwardRef<TamaguiElement, ContextMenuCheckboxItemProps>(
620
618
  function ContextMenuCheckboxItem({ checked, onCheckedChange, onSelect, ...props }, ref) {
621
- const checkInk = useMenuRowCheckInk();
622
619
  return (
623
620
  <ContextMenuItemBody
624
621
  ref={ref}
@@ -630,21 +627,7 @@ const ContextMenuCheckboxItem = forwardRef<TamaguiElement, ContextMenuCheckboxIt
630
627
  onCheckedChange?.(!checked);
631
628
  onSelect?.();
632
629
  }}
633
- trailing={
634
- <View
635
- width={18}
636
- flexShrink={0}
637
- alignItems="center"
638
- justifyContent="center"
639
- opacity={checked ? 1 : 0}
640
- data-mp-check="trailing"
641
- {...(isWeb && checkInk
642
- ? ({ style: { color: checkInk } } as Record<string, unknown>)
643
- : {})}
644
- >
645
- <IconSlot icon={<CheckIcon size={14} weight="bold" color={checkInk} />} />
646
- </View>
647
- }
630
+ trailing={<MenuCheckColumn checked={checked} iconRow={props.icon != null} />}
648
631
  {...props}
649
632
  />
650
633
  );
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * FZ-8220, LC-112, LC-262: an avatar trigger is an icon trigger. The account
3
3
  * menu opens on it (FZ-8438) with no text echo row, the avatar alone or inside
4
- * the press boxes an app wraps around it, and draws the avatar again in its
5
- * own spot as an X in the avatar's circle, where a press closes the menu
6
- * (FZ-8480 Round 37).
4
+ * the press boxes an app wraps around it. It covers the avatar and repeats
5
+ * nothing: no X, and the rows start at the panel's top edge (FZ-8665 Round 45).
7
6
  */
8
7
  import { renderWithProviders } from "@multiplatform.one/test-utils";
9
8
  import { waitFor } from "@testing-library/react";
@@ -62,7 +61,7 @@ describe("DropdownMenu avatar trigger (FZ-8220)", () => {
62
61
  expect(identity?.textContent).toContain("clayrisser@gmail.com");
63
62
  });
64
63
 
65
- it("draws the avatar again in place as an X at phone width, a menu and not a sheet (FZ-8480)", async () => {
64
+ it("covers the avatar at phone width, a menu and not a sheet, with no X (FZ-8665)", async () => {
66
65
  const width = window.innerWidth;
67
66
  Object.defineProperty(window, "innerWidth", { configurable: true, writable: true, value: 390 });
68
67
  try {
@@ -81,11 +80,9 @@ describe("DropdownMenu avatar trigger (FZ-8220)", () => {
81
80
  expect(found?.querySelector('[role="menu"]')).toBeTruthy();
82
81
  return found as HTMLElement;
83
82
  });
84
- const echo = panel.querySelector<HTMLElement>('[data-testid="floating-panel-icon-echo"]');
85
- expect(echo?.getAttribute("aria-label")).toBe("Close");
86
- // The avatar turns into an X: no initials, an X glyph.
87
- expect(echo?.textContent).toBe("");
88
- expect(echo?.querySelector("svg")).toBeTruthy();
83
+ expect(panel.querySelector('[aria-label="Close"]')).toBeNull();
84
+ // The avatar's initials stay on the page, under the panel.
85
+ expect(panel.textContent).not.toContain("CR");
89
86
  } finally {
90
87
  Object.defineProperty(window, "innerWidth", {
91
88
  configurable: true,
@@ -21,12 +21,12 @@ import { IconSlot } from "@multiplatform.one/theme";
21
21
  * `onCheckedChange` and `onSelect`.
22
22
  */
23
23
 
24
+ import { MenuCheckColumn } from "../CheckGlyph/MenuCheckColumn";
24
25
  import {
25
26
  transitionProps,
26
27
  Surface,
27
28
  hairline,
28
29
  keyboardFocusRingProps,
29
- useMenuRowCheckInk,
30
30
  menuRowChecked,
31
31
  menuRowFrame,
32
32
  useResolvedKnobs,
@@ -53,7 +53,6 @@ import {
53
53
  type RefObject,
54
54
  } from "react";
55
55
 
56
- import { CheckIcon } from "@phosphor-icons/react";
57
56
  import { FloatingPanel, type PanelWidth } from "@multiplatform.one/forms";
58
57
  import type { GetProps, SizeTokens, TamaguiElement } from "tamagui";
59
58
  import {
@@ -457,9 +456,9 @@ function DropdownMenuRoot({
457
456
  anchorMode={resolvedAnchorMode}
458
457
  triggerEcho={!iconTrigger}
459
458
  coverAlign={iconTrigger ? "auto" : "start"}
460
- // LC-112 (FZ-8480 Round 37): the menu covers its button and draws
461
- // it again in place as an X that closes the menu.
462
- iconEcho={iconTrigger ? "menu" : undefined}
459
+ // LC-112 (FZ-8665 Round 45): the menu covers its button, and its
460
+ // rows start at its top edge.
461
+ iconCover={iconTrigger}
463
462
  triggerRow={triggerRow}
464
463
  contentPadding="none"
465
464
  triggerSizing={triggerSizing}
@@ -906,7 +905,6 @@ export interface DropdownMenuCheckboxItemProps extends Omit<DropdownMenuItemProp
906
905
 
907
906
  const DropdownMenuCheckboxItem = forwardRef<TamaguiElement, DropdownMenuCheckboxItemProps>(
908
907
  function DropdownMenuCheckboxItem({ checked, onCheckedChange, onSelect, ...props }, ref) {
909
- const checkInk = useMenuRowCheckInk();
910
908
  return (
911
909
  <DropdownMenuItemBody
912
910
  ref={ref}
@@ -918,18 +916,7 @@ const DropdownMenuCheckboxItem = forwardRef<TamaguiElement, DropdownMenuCheckbox
918
916
  onCheckedChange?.(!checked);
919
917
  onSelect?.();
920
918
  }}
921
- trailing={
922
- <View
923
- width={18}
924
- flexShrink={0}
925
- alignItems="center"
926
- justifyContent="center"
927
- opacity={checked ? 1 : 0}
928
- data-mp-check="trailing"
929
- >
930
- <IconSlot icon={<CheckIcon size={14} weight="bold" color={checkInk} />} />
931
- </View>
932
- }
919
+ trailing={<MenuCheckColumn checked={checked} iconRow={props.icon != null} />}
933
920
  {...props}
934
921
  />
935
922
  );
@@ -97,7 +97,7 @@ describe("Popover cover-trigger adapter", () => {
97
97
  fireEvent.click(result.getByRole("button", { name: "Apply filters" }));
98
98
  await waitFor(() => expect(result.queryByTestId("floating-panel-viewport")).toBeNull());
99
99
  });
100
- it("an icon trigger's panel covers the button and draws it again as an X that closes it (FZ-8480)", async () => {
100
+ it("an icon trigger's panel covers the button with no X, a press outside closing it (FZ-8665)", async () => {
101
101
  const result = renderWithProviders(
102
102
  <Popover anchorMode="cover-trigger">
103
103
  <Popover.Trigger asChild>
@@ -112,8 +112,8 @@ describe("Popover cover-trigger adapter", () => {
112
112
  const panel = await result.findByTestId("floating-panel-viewport");
113
113
  expect(within(panel).queryByRole("button", { name: "Add node" })).toBeNull();
114
114
  expect(panel.querySelector("[data-fp-trigger-row]")).toBeNull();
115
- const close = within(panel).getByRole("button", { name: "Close" });
116
- fireEvent.click(close);
115
+ expect(within(panel).queryByRole("button", { name: "Close" })).toBeNull();
116
+ fireEvent.click(document.body);
117
117
  await waitFor(() => expect(result.queryByTestId("floating-panel-viewport")).toBeNull());
118
118
  });
119
119
  });
package/src/Popover.tsx CHANGED
@@ -143,9 +143,9 @@ const CoverPopover = forwardRef<Popover, PopoverProps>(function CoverPopover(
143
143
  }
144
144
  }, [content.length, unsupportedContent]);
145
145
  const trigger = triggerPart?.props.children ?? triggerRow;
146
- // LC-112 (FZ-8480): a glyph trigger (the "+") is covered by its panel and
147
- // drawn again in its spot as an X that closes it, the iOS + to X morph. The
148
- // panel stays a panel at phone width, sized by its content.
146
+ // LC-112 (FZ-8665): a glyph trigger (the "+") is covered by its panel, and a
147
+ // press outside closes it. The panel stays a panel at phone width, sized by
148
+ // its content.
149
149
  const iconTrigger = triggerRow == null && isIconTrigger(trigger);
150
150
  const decorated = isValidElement(trigger)
151
151
  ? cloneElement(trigger as ReactElement<Record<string, unknown>>, {
@@ -168,7 +168,7 @@ const CoverPopover = forwardRef<Popover, PopoverProps>(function CoverPopover(
168
168
  {...(iconTrigger
169
169
  ? {
170
170
  triggerEcho: false,
171
- iconEcho: "close" as const,
171
+ iconCover: true,
172
172
  coverAlign: "auto" as const,
173
173
  compactFace: "menu" as const,
174
174
  widthMode: "fit-content" as const,
@@ -220,7 +220,7 @@ export const Popover = withStaticProperties(PopoverRoot, {
220
220
  Trigger: TamaguiPopover.Trigger,
221
221
  Content: StackedPopoverContent,
222
222
  Close: PopoverClose,
223
- /** A panel's title row; over a + panel it shares the X's line (LC-112). */
223
+ /** A panel's title row, on the panel's padding (LC-112). */
224
224
  Header: PanelHeader,
225
225
  Anchor: TamaguiPopover.Anchor,
226
226
  Adapt: TamaguiPopover.Adapt,
@@ -1,4 +1,4 @@
1
- import { CircleHalfIcon, MoonIcon, SunIcon, XIcon } from "@phosphor-icons/react";
1
+ import { CircleHalfIcon, MoonIcon, SunIcon } from "@phosphor-icons/react";
2
2
  import { fireEvent, render, waitFor } from "@testing-library/react";
3
3
  import { describe, expect, it, vi } from "vitest";
4
4
  import { renderWithProviders } from "@multiplatform.one/test-utils";
@@ -43,7 +43,7 @@ describe("SchemeToggle (FZ-8158)", () => {
43
43
  expect(set).toHaveBeenCalledWith("system");
44
44
  });
45
45
 
46
- it("opens its menu on the button and draws the button again in place as an X, a press there closing it (FZ-8480)", async () => {
46
+ it("opens its menu on the button with no X, a press outside closing it (FZ-8665)", async () => {
47
47
  const { useUserScheme } = preference("dark", "dark");
48
48
  const { getByRole } = renderWithProviders(<SchemeToggle useUserScheme={useUserScheme} />);
49
49
  const live = getByRole("button", { name: "Appearance: Dark" });
@@ -53,15 +53,9 @@ describe("SchemeToggle (FZ-8158)", () => {
53
53
  expect(found?.querySelector('[role="menu"]')).toBeTruthy();
54
54
  return found as HTMLElement;
55
55
  });
56
- const echo = panel.querySelector<HTMLElement>('[data-testid="floating-panel-icon-echo"]');
57
- expect(echo?.getAttribute("aria-label")).toBe("Close");
58
- const pathOf = (node: ParentNode | null | undefined) =>
59
- node?.querySelector("svg path")?.getAttribute("d");
60
- const x = render(<XIcon weight="bold" />);
61
- expect(pathOf(echo)).toBe(pathOf(x.container));
62
- expect(pathOf(echo)).not.toBe(pathOf(live));
63
- x.unmount();
64
- fireEvent.click(echo as HTMLElement);
56
+ expect(panel.querySelector('[aria-label="Close"]')).toBeNull();
57
+ expect(panel.querySelector(`[aria-label="${live.getAttribute("aria-label")}"]`)).toBeNull();
58
+ fireEvent.click(document.body);
65
59
  await waitFor(() => expect(document.querySelector('[role="menu"]')).toBeFalsy());
66
60
  });
67
61
 
@@ -0,0 +1,21 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-native-web-vite";
2
+ import { XStack } from "tamagui";
3
+ import { SchemeToggle } from "./index";
4
+
5
+ const meta: Meta<typeof SchemeToggle> = {
6
+ title: "Components/SchemeToggle",
7
+ component: SchemeToggle,
8
+ parameters: { status: { type: "beta" } },
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof SchemeToggle>;
13
+
14
+ /** A tap flips light and dark; a right-click (long-press on native) opens System, Light and Dark. */
15
+ export const Default: Story = {
16
+ render: () => (
17
+ <XStack padding="$4" justifyContent="flex-end">
18
+ <SchemeToggle />
19
+ </XStack>
20
+ ),
21
+ };
@@ -1,6 +1,7 @@
1
1
  import { CircleHalfIcon, MoonIcon, SunIcon } from "@phosphor-icons/react";
2
2
  import { IconButton, type IconButtonProps } from "@multiplatform.one/forms";
3
3
  import {
4
+ menuRowIconGlyph,
4
5
  useSchemePreference,
5
6
  type SchemePreference,
6
7
  type ThemeScheme,
@@ -60,7 +61,7 @@ export function SchemeToggle({
60
61
  const Glyph = schemeGlyphs[value];
61
62
  return {
62
63
  label: name(value),
63
- icon: <Glyph size={18} weight="bold" />,
64
+ icon: <Glyph size={menuRowIconGlyph.size} weight={menuRowIconGlyph.weight} />,
64
65
  checked: scheme.setting === value,
65
66
  onSelect: () => scheme.set(value),
66
67
  };
@@ -28,6 +28,7 @@ import {
28
28
  useResolvedKnobs,
29
29
  type MotionStop,
30
30
  nativeAnimationConfig as animationConfig,
31
+ overlayScrim,
31
32
  } from "@multiplatform.one/theme";
32
33
  import {
33
34
  useCallback,
@@ -424,7 +425,7 @@ export function SheetModal({
424
425
  accessibilityRole="button"
425
426
  accessibilityLabel={t("Close")}
426
427
  >
427
- <View flex={1} backgroundColor="$shadow6" />
428
+ <View flex={1} backgroundColor={overlayScrim} />
428
429
  </Pressable>
429
430
  <Animated.View
430
431
  onLayout={(e) => {
@@ -9,7 +9,7 @@
9
9
  * on device). Dismissal: overlay tap / snap-to-bottom / Escape (LC-21).
10
10
  */
11
11
 
12
- import { transitionProps, useResolvedKnobs } from "@multiplatform.one/theme";
12
+ import { overlayScrim, transitionProps, useResolvedKnobs } from "@multiplatform.one/theme";
13
13
  import { OverlayStackProvider, useOverlayZIndex } from "@multiplatform.one/forms";
14
14
  import { useEffect, useId, useRef, type ReactNode } from "react";
15
15
  import { isWeb, Sheet, View, useWindowDimensions } from "tamagui";
@@ -128,7 +128,7 @@ export function SheetModal({
128
128
  portalProps={{ zIndex: sheetZIndex }}
129
129
  >
130
130
  <Sheet.Overlay
131
- backgroundColor="$shadow6"
131
+ backgroundColor={overlayScrim}
132
132
  {...transitionProps(knobProps.motion.scrim)}
133
133
  enterStyle={{ opacity: 0 }}
134
134
  exitStyle={{ opacity: 0 }}
@@ -160,6 +160,9 @@ export function SheetModal({
160
160
  : null)}
161
161
  borderBottomLeftRadius={0}
162
162
  borderBottomRightRadius={0}
163
+ borderLeftWidth={0}
164
+ borderRightWidth={0}
165
+ borderBottomWidth={0}
163
166
  overflow="hidden"
164
167
  {...frameRingReset}
165
168
  >
@@ -11,11 +11,11 @@ import {
11
11
  panelViewportPadding,
12
12
  useOverlayZIndex,
13
13
  } from "@multiplatform.one/forms";
14
+ import { CheckGlyph } from "../CheckGlyph";
14
15
  import {
15
16
  CaretDownIcon,
16
17
  CaretLeftIcon,
17
18
  CaretRightIcon,
18
- CheckIcon,
19
19
  FileIcon,
20
20
  FolderIcon,
21
21
  FolderOpenIcon,
@@ -27,7 +27,6 @@ import {
27
27
  ensureKeyboardModalityTracking,
28
28
  keyboardFocusRingProps,
29
29
  listHeightForRows,
30
- useMenuRowCheckInk,
31
30
  menuRowChecked,
32
31
  useResolvedKnobs,
33
32
  } from "@multiplatform.one/theme";
@@ -110,7 +109,6 @@ function TreeSelectRows({
110
109
  }) {
111
110
  const { t } = useTranslation();
112
111
  const { knobProps } = useResolvedKnobs();
113
- const checkInk = useMenuRowCheckInk();
114
112
  const checkedFill = menuRowChecked(knobProps.scheme);
115
113
  const nestedPx = knobProps.nestedControl.px;
116
114
  const indentStep = 16;
@@ -271,7 +269,7 @@ function TreeSelectRows({
271
269
  ) : null}
272
270
  {isSelected ? (
273
271
  <View flexShrink={0} width={14} height={14}>
274
- <CheckIcon size={14} weight="bold" color={checkInk} />
272
+ <CheckGlyph />
275
273
  </View>
276
274
  ) : null}
277
275
  </XStack>
@@ -0,0 +1,289 @@
1
+ /**
2
+ * FZ-7996 (LC-175 MPO-ONLY-REUSE): one composed app frame. An app passes its
3
+ * destinations and the path; the shell picks the phone or the split layout,
4
+ * selects the destination, draws back on a pushed route and steps aside for a
5
+ * route that mounts its own chrome or a page that pins its own top bar.
6
+ */
7
+ import { renderWithProviders } from "@multiplatform.one/test-utils";
8
+ import { HouseIcon, LightningIcon, SparkleIcon } from "@phosphor-icons/react";
9
+ import { act, cleanup, fireEvent, screen } from "@testing-library/react";
10
+ import { afterEach, describe, expect, it, vi } from "vitest";
11
+ import {
12
+ CONTENT_MAX_WIDTH,
13
+ PageHeader,
14
+ READABLE_MAX_WIDTH,
15
+ Screen,
16
+ ScreenToolbar,
17
+ } from "../layouts/page";
18
+ import { ListPageLayout } from "../layouts/PageTemplates";
19
+ import { AppShell, appShellRoute, type AppShellDestination } from "./AppShell";
20
+
21
+ const size = vi.hoisted(() => ({ value: "compact" as string }));
22
+ vi.mock("@multiplatform.one/theme", async (importOriginal) => ({
23
+ ...(await importOriginal<typeof import("@multiplatform.one/theme")>()),
24
+ useLayoutSizeClass: () => size.value,
25
+ }));
26
+
27
+ afterEach(() => {
28
+ cleanup();
29
+ size.value = "compact";
30
+ });
31
+
32
+ const destinations: AppShellDestination[] = [
33
+ { key: "/", label: "Home", icon: HouseIcon },
34
+ { key: "/pokemon", label: "Pokemon", icon: SparkleIcon },
35
+ { key: "/moves", label: "Moves", icon: LightningIcon, placement: "more" },
36
+ ];
37
+
38
+ function shell(path: string, props: Partial<Parameters<typeof AppShell>[0]> = {}) {
39
+ const onNavigate = vi.fn();
40
+ const onBack = vi.fn();
41
+ const view = renderWithProviders(
42
+ <AppShell
43
+ destinations={destinations}
44
+ path={path}
45
+ onNavigate={onNavigate}
46
+ onBack={onBack}
47
+ title="multiplatform.one"
48
+ logo={<span data-testid="logo" />}
49
+ actions={<button type="button">Theme</button>}
50
+ ownChrome={["/desk"]}
51
+ {...props}
52
+ >
53
+ {props.children ?? <span data-testid="page">page</span>}
54
+ </AppShell>,
55
+ );
56
+ return { ...view, onNavigate, onBack };
57
+ }
58
+
59
+ describe("appShellRoute", () => {
60
+ it("selects the longest destination the path sits under", () => {
61
+ expect(appShellRoute(destinations, "/")).toMatchObject({ pushed: false });
62
+ expect(appShellRoute(destinations, "/").destination?.key).toBe("/");
63
+ expect(appShellRoute(destinations, "/pokemon").destination?.key).toBe("/pokemon");
64
+ expect(appShellRoute(destinations, "/pokemon/").pushed).toBe(false);
65
+ const detail = appShellRoute(destinations, "/pokemon/pikachu");
66
+ expect(detail.destination?.key).toBe("/pokemon");
67
+ expect(detail.pushed).toBe(true);
68
+ });
69
+
70
+ it("matches no destination outside them, and '/' only exactly", () => {
71
+ expect(appShellRoute(destinations, "/todos").destination).toBeUndefined();
72
+ expect(appShellRoute(destinations, "/pokemonx").destination).toBeUndefined();
73
+ });
74
+ });
75
+
76
+ describe("AppShell on a phone", () => {
77
+ it("draws the nav bar, the content and the tab bar of the tab destinations", () => {
78
+ shell("/pokemon");
79
+ expect(screen.getByTestId("app-shell").getAttribute("data-size")).toBe("compact");
80
+ expect(screen.getByTestId("app-shell-bar")).toBeTruthy();
81
+ expect(screen.getByTestId("page")).toBeTruthy();
82
+ const tabs = screen.getAllByRole("tab");
83
+ expect(tabs.map((tab) => tab.getAttribute("aria-label"))).toEqual(["Home", "Pokemon"]);
84
+ expect(tabs[1]!.getAttribute("aria-selected")).toBe("true");
85
+ expect(screen.queryByTestId("app-shell-sidebar")).toBeNull();
86
+ });
87
+
88
+ it("navigates from a tab", () => {
89
+ const { onNavigate } = shell("/pokemon");
90
+ fireEvent.click(screen.getAllByRole("tab")[0]!);
91
+ expect(onNavigate).toHaveBeenCalledWith("/");
92
+ });
93
+
94
+ it("puts the more destinations in the bar's pull-down", () => {
95
+ shell("/");
96
+ expect(screen.getByTestId("app-shell-more")).toBeTruthy();
97
+ });
98
+
99
+ it("draws the pull-down trigger as the bar's icon action, a bare accent glyph (LC-176)", () => {
100
+ shell("/");
101
+ expect(
102
+ screen
103
+ .getByTestId("app-shell-more")
104
+ .closest("[data-top-bar-control]")
105
+ ?.getAttribute("data-top-bar-control"),
106
+ ).toBe("action");
107
+ });
108
+
109
+ it("leads with the app's mark at a tab root and a tinted back on a pushed route", () => {
110
+ shell("/pokemon");
111
+ expect(screen.getByTestId("app-shell-home").getAttribute("aria-label")).toBe(
112
+ "multiplatform.one",
113
+ );
114
+ cleanup();
115
+ const { onBack } = shell("/pokemon/pikachu");
116
+ const back = document.querySelector("[data-top-bar-control='back']") as HTMLElement;
117
+ expect(back.textContent).toContain("Pokemon");
118
+ fireEvent.click(back);
119
+ expect(onBack).toHaveBeenCalledWith("/pokemon");
120
+ expect(screen.getAllByRole("tab")[1]!.getAttribute("aria-selected")).toBe("true");
121
+ });
122
+
123
+ it("shows the destination as the inline title when the page has no large title", () => {
124
+ shell("/pokemon");
125
+ const title = screen.getByTestId("app-shell-bar-title");
126
+ expect(title.textContent).toBe("Pokemon");
127
+ expect(title.getAttribute("aria-hidden")).toBe("false");
128
+ });
129
+
130
+ it("keeps the page's large title out of the bar until it scrolls away", () => {
131
+ shell("/pokemon", {
132
+ children: (
133
+ <Screen>
134
+ <PageHeader title="All Pokemon" />
135
+ </Screen>
136
+ ),
137
+ });
138
+ const title = screen.getByTestId("app-shell-bar-title");
139
+ expect(title.textContent).toBe("All Pokemon");
140
+ expect(title.getAttribute("aria-hidden")).toBe("true");
141
+ });
142
+
143
+ it("steps aside for a page that pins its own top bar", () => {
144
+ shell("/pokemon/pikachu", {
145
+ children: (
146
+ <Screen topBar={<ScreenToolbar leading={<span>own back</span>} />}>
147
+ <span>record</span>
148
+ </Screen>
149
+ ),
150
+ });
151
+ expect(screen.queryByTestId("app-shell-bar")).toBeNull();
152
+ expect(screen.getByText("own back")).toBeTruthy();
153
+ expect(screen.getAllByRole("tab")).toHaveLength(2);
154
+ });
155
+
156
+ it("draws no tab bar outside the tab destinations", () => {
157
+ shell("/todos");
158
+ expect(screen.queryByRole("tablist")).toBeNull();
159
+ expect(screen.getByTestId("app-shell-bar")).toBeTruthy();
160
+ });
161
+
162
+ it("hands the bottom accessory to the tab bar, which floats it above its row (LC-174)", () => {
163
+ shell("/pokemon", { accessory: <span data-testid="pager">pager</span> });
164
+ const accessory = screen.getByTestId("tab-bar-accessory");
165
+ expect(accessory.contains(screen.getByTestId("pager"))).toBe(true);
166
+ expect(screen.getByTestId("tab-bar").contains(accessory)).toBe(true);
167
+ const tablist = screen.getByRole("tablist");
168
+ expect(accessory.compareDocumentPosition(tablist) & Node.DOCUMENT_POSITION_FOLLOWING).toBe(
169
+ Node.DOCUMENT_POSITION_FOLLOWING,
170
+ );
171
+ });
172
+
173
+ it("still hosts the accessory on a pushed route, where no tab bar shows", () => {
174
+ shell("/todos", { accessory: <span data-testid="pager">pager</span> });
175
+ expect(screen.queryByRole("tablist")).toBeNull();
176
+ const frame = screen.getByTestId("app-shell-content").parentElement;
177
+ expect(frame?.contains(screen.getByTestId("pager"))).toBe(true);
178
+ });
179
+
180
+ it("draws nothing around a route that mounts its own chrome", () => {
181
+ shell("/desk/Pokemon");
182
+ expect(screen.getByTestId("app-shell").getAttribute("data-chrome")).toBe("own");
183
+ expect(screen.queryByTestId("app-shell-bar")).toBeNull();
184
+ expect(screen.queryByRole("tablist")).toBeNull();
185
+ expect(screen.getByTestId("page")).toBeTruthy();
186
+ });
187
+
188
+ it("keeps the tab bar around an own-chrome route that is a tab, and on a route below it", () => {
189
+ const withDesk = [...destinations, { key: "/desk", label: "Desk", icon: HouseIcon }];
190
+ for (const path of ["/desk", "/desk/Pokemon"]) {
191
+ const { onNavigate } = shell(path, { destinations: withDesk });
192
+ expect(screen.getByTestId("app-shell").getAttribute("data-chrome")).toBe("own");
193
+ expect(screen.queryByTestId("app-shell-bar")).toBeNull();
194
+ const tabs = screen.getAllByRole("tab");
195
+ expect(tabs.map((tab) => tab.getAttribute("aria-label"))).toEqual([
196
+ "Home",
197
+ "Pokemon",
198
+ "Desk",
199
+ ]);
200
+ expect(tabs[2]!.getAttribute("aria-selected")).toBe("true");
201
+ fireEvent.click(tabs[0]!);
202
+ expect(onNavigate).toHaveBeenCalledWith("/");
203
+ expect(screen.getByTestId("page")).toBeTruthy();
204
+ cleanup();
205
+ }
206
+ });
207
+
208
+ it("draws no tab bar around an own-chrome route at regular width", () => {
209
+ size.value = "expanded";
210
+ shell("/desk", {
211
+ destinations: [...destinations, { key: "/desk", label: "Desk", icon: HouseIcon }],
212
+ });
213
+ expect(screen.getByTestId("app-shell").getAttribute("data-chrome")).toBe("own");
214
+ expect(screen.queryByRole("tablist")).toBeNull();
215
+ expect(screen.queryByTestId("app-shell-sidebar")).toBeNull();
216
+ });
217
+ });
218
+
219
+ describe("AppShell at regular width", () => {
220
+ it("lists every destination in the sidebar, under the app's name, and no tab bar", () => {
221
+ size.value = "expanded";
222
+ const { onNavigate } = shell("/pokemon/pikachu");
223
+ expect(screen.getByTestId("app-shell").getAttribute("data-size")).toBe("regular");
224
+ const sidebar = screen.getByTestId("app-shell-sidebar");
225
+ expect(sidebar.textContent).toContain("multiplatform.one");
226
+ const rows = sidebar.querySelectorAll("[data-sidebar-row]");
227
+ expect([...rows].map((row) => row.textContent)).toEqual(["Home", "Pokemon", "Moves"]);
228
+ expect(rows[1]!.getAttribute("aria-current")).toBe("page");
229
+ fireEvent.click(rows[2]!);
230
+ expect(onNavigate).toHaveBeenCalledWith("/moves");
231
+ expect(screen.queryByRole("tablist")).toBeNull();
232
+ expect(screen.queryByTestId("app-shell-more")).toBeNull();
233
+ });
234
+
235
+ it("puts the app's actions at the sidebar's foot and draws no bar on a destination root", () => {
236
+ size.value = "expanded";
237
+ shell("/pokemon");
238
+ const actions = screen.getByTestId("app-shell-sidebar-actions");
239
+ expect(actions.textContent).toContain("Theme");
240
+ expect(screen.getByTestId("app-shell-sidebar").contains(actions)).toBe(true);
241
+ expect(screen.queryByTestId("app-shell-bar")).toBeNull();
242
+ });
243
+
244
+ it("draws the bar with the tinted back on a pushed route, without the actions", () => {
245
+ size.value = "expanded";
246
+ const { onBack } = shell("/pokemon/pikachu");
247
+ const bar = screen.getByTestId("app-shell-bar");
248
+ expect(bar.textContent).toContain("Pokemon");
249
+ expect(bar.textContent).not.toContain("Theme");
250
+ fireEvent.click(bar.querySelector('[data-top-bar-control="back"]') as HTMLElement);
251
+ expect(onBack).toHaveBeenCalledWith("/pokemon");
252
+ });
253
+
254
+ it("centres the content in the readable column", () => {
255
+ size.value = "expanded";
256
+ shell("/pokemon");
257
+ const readable = screen.getByTestId("app-shell-readable");
258
+ expect(screen.getByTestId("app-shell-content").contains(readable)).toBe(true);
259
+ expect(readable.contains(screen.getByTestId("page"))).toBe(true);
260
+ expect(READABLE_MAX_WIDTH).toBeLessThan(CONTENT_MAX_WIDTH);
261
+ });
262
+
263
+ it("puts the app's name in the sidebar's pinned header", () => {
264
+ size.value = "expanded";
265
+ shell("/pokemon");
266
+ const header = screen
267
+ .getByTestId("app-shell-sidebar")
268
+ .querySelector("[data-sidebar-header]") as HTMLElement;
269
+ expect(header.textContent).toContain("multiplatform.one");
270
+ });
271
+
272
+ it("stacks a page's panel under the sidebar's rows (LC-101)", async () => {
273
+ size.value = "expanded";
274
+ await act(async () => {
275
+ shell("/pokemon", {
276
+ children: (
277
+ <ListPageLayout title="Pokemon" sidebar={<span>filters</span>} sidebarLabel="Filters">
278
+ <span>grid</span>
279
+ </ListPageLayout>
280
+ ),
281
+ });
282
+ });
283
+ const panel = screen.getByTestId("app-shell-page-panel");
284
+ expect(panel.textContent).toContain("Filters");
285
+ expect(panel.textContent).toContain("filters");
286
+ expect(screen.getByTestId("app-shell-sidebar").contains(panel)).toBe(true);
287
+ expect(screen.queryByTestId("list-page-sidebar")).toBeNull();
288
+ });
289
+ });