@duro-app/ui 5.11.0 → 5.13.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 (331) hide show
  1. package/dist/Table-Bms2ftt3.js +1694 -0
  2. package/dist/Table-Bms2ftt3.js.map +1 -0
  3. package/dist/compiler-runtime-BuugS4D0.js.map +1 -1
  4. package/dist/components/Alert/Alert.d.ts.map +1 -1
  5. package/dist/components/AppShell/native.css.d.ts +53 -0
  6. package/dist/components/AppShell/native.css.d.ts.map +1 -0
  7. package/dist/components/Button/Button.d.ts +2 -1
  8. package/dist/components/Button/Button.d.ts.map +1 -1
  9. package/dist/components/Button/styles.css.d.ts +1 -0
  10. package/dist/components/Button/styles.css.d.ts.map +1 -1
  11. package/dist/components/Callout/Callout.d.ts.map +1 -1
  12. package/dist/components/Callout/styles.css.d.ts +3 -0
  13. package/dist/components/Callout/styles.css.d.ts.map +1 -1
  14. package/dist/components/Checkbox/Checkbox.d.ts.map +1 -1
  15. package/dist/components/ConfirmDialog/ConfirmDialog.d.ts.map +1 -1
  16. package/dist/components/DetailPanel/DetailPanel.d.ts.map +1 -1
  17. package/dist/components/Dialog/Dialog.d.ts +2 -17
  18. package/dist/components/Dialog/Dialog.d.ts.map +1 -1
  19. package/dist/components/Dialog/Dialog.stories.d.ts +4 -0
  20. package/dist/components/Dialog/Dialog.stories.d.ts.map +1 -1
  21. package/dist/components/Dialog/native.css.d.ts +82 -0
  22. package/dist/components/Dialog/native.css.d.ts.map +1 -0
  23. package/dist/components/Dialog/parts.css.d.ts +82 -0
  24. package/dist/components/Dialog/parts.css.d.ts.map +1 -0
  25. package/dist/components/Dialog/parts.d.ts +35 -0
  26. package/dist/components/Dialog/parts.d.ts.map +1 -0
  27. package/dist/components/Dialog/styles.css.d.ts +1 -74
  28. package/dist/components/Dialog/styles.css.d.ts.map +1 -1
  29. package/dist/components/Drawer/Drawer.d.ts +7 -35
  30. package/dist/components/Drawer/Drawer.d.ts.map +1 -1
  31. package/dist/components/Drawer/Drawer.meta.d.ts.map +1 -1
  32. package/dist/components/Drawer/DrawerScroll.stories.d.ts.map +1 -1
  33. package/dist/components/Drawer/DrawerSnap.stories.d.ts +20 -0
  34. package/dist/components/Drawer/DrawerSnap.stories.d.ts.map +1 -0
  35. package/dist/components/Drawer/native.css.d.ts +55 -0
  36. package/dist/components/Drawer/native.css.d.ts.map +1 -0
  37. package/dist/components/Drawer/parts.css.d.ts +96 -0
  38. package/dist/components/Drawer/parts.css.d.ts.map +1 -0
  39. package/dist/components/Drawer/parts.d.ts +56 -0
  40. package/dist/components/Drawer/parts.d.ts.map +1 -0
  41. package/dist/components/Drawer/sheetPhysics.d.ts +56 -0
  42. package/dist/components/Drawer/sheetPhysics.d.ts.map +1 -0
  43. package/dist/components/Drawer/styles.css.d.ts +15 -58
  44. package/dist/components/Drawer/styles.css.d.ts.map +1 -1
  45. package/dist/components/Drawer/touchInput.d.ts +35 -0
  46. package/dist/components/Drawer/touchInput.d.ts.map +1 -0
  47. package/dist/components/Drawer/useDrawerRoot.d.ts +33 -0
  48. package/dist/components/Drawer/useDrawerRoot.d.ts.map +1 -0
  49. package/dist/components/Drawer/useSheetHeights.d.ts +7 -0
  50. package/dist/components/Drawer/useSheetHeights.d.ts.map +1 -0
  51. package/dist/components/Drawer/useSwipeDismiss.d.ts +13 -2
  52. package/dist/components/Drawer/useSwipeDismiss.d.ts.map +1 -1
  53. package/dist/components/Grid/styles.css.d.ts +1 -0
  54. package/dist/components/Grid/styles.css.d.ts.map +1 -1
  55. package/dist/components/Icon/Icon.d.ts +3 -9
  56. package/dist/components/Icon/Icon.d.ts.map +1 -1
  57. package/dist/components/Icon/geometry.d.ts +60 -0
  58. package/dist/components/Icon/geometry.d.ts.map +1 -0
  59. package/dist/components/Icon/types.d.ts +15 -0
  60. package/dist/components/Icon/types.d.ts.map +1 -0
  61. package/dist/components/Input/Input.d.ts +5 -1
  62. package/dist/components/Input/Input.d.ts.map +1 -1
  63. package/dist/components/Input/styles.css.d.ts +1 -0
  64. package/dist/components/Input/styles.css.d.ts.map +1 -1
  65. package/dist/components/LinkButton/styles.css.d.ts +1 -0
  66. package/dist/components/LinkButton/styles.css.d.ts.map +1 -1
  67. package/dist/components/Menu/Menu.d.ts +7 -1
  68. package/dist/components/Menu/Menu.d.ts.map +1 -1
  69. package/dist/components/Menu/native.css.d.ts +14 -0
  70. package/dist/components/Menu/native.css.d.ts.map +1 -0
  71. package/dist/components/Menu/useMenuOpen.d.ts +16 -0
  72. package/dist/components/Menu/useMenuOpen.d.ts.map +1 -0
  73. package/dist/components/Menu/useMenuRoot.d.ts +3 -6
  74. package/dist/components/Menu/useMenuRoot.d.ts.map +1 -1
  75. package/dist/components/Meter/styles.css.d.ts +1 -0
  76. package/dist/components/Meter/styles.css.d.ts.map +1 -1
  77. package/dist/components/PageShell/styles.css.d.ts +2 -0
  78. package/dist/components/PageShell/styles.css.d.ts.map +1 -1
  79. package/dist/components/Popover/native.css.d.ts +11 -0
  80. package/dist/components/Popover/native.css.d.ts.map +1 -0
  81. package/dist/components/RadioGroup/RadioGroup.d.ts +1 -17
  82. package/dist/components/RadioGroup/RadioGroup.d.ts.map +1 -1
  83. package/dist/components/RadioGroup/RadioGroup.stories.d.ts +3 -0
  84. package/dist/components/RadioGroup/RadioGroup.stories.d.ts.map +1 -1
  85. package/dist/components/RadioGroup/group.d.ts +31 -0
  86. package/dist/components/RadioGroup/group.d.ts.map +1 -0
  87. package/dist/components/RadioGroup/styles.css.d.ts +10 -0
  88. package/dist/components/RadioGroup/styles.css.d.ts.map +1 -1
  89. package/dist/components/ScrollArea/styles.css.d.ts +1 -0
  90. package/dist/components/ScrollArea/styles.css.d.ts.map +1 -1
  91. package/dist/components/SideNav/Chevron.d.ts +4 -0
  92. package/dist/components/SideNav/Chevron.d.ts.map +1 -0
  93. package/dist/components/SideNav/SideNav.d.ts.map +1 -1
  94. package/dist/components/SideNav/styles.css.d.ts +6 -0
  95. package/dist/components/SideNav/styles.css.d.ts.map +1 -1
  96. package/dist/components/SideNav/types.d.ts +6 -0
  97. package/dist/components/SideNav/types.d.ts.map +1 -0
  98. package/dist/components/Spinner/Spinner.d.ts +2 -6
  99. package/dist/components/Spinner/Spinner.d.ts.map +1 -1
  100. package/dist/components/Spinner/Spinner.types.d.ts +6 -0
  101. package/dist/components/Spinner/Spinner.types.d.ts.map +1 -0
  102. package/dist/components/Spinner/ring.css.d.ts +39 -0
  103. package/dist/components/Spinner/ring.css.d.ts.map +1 -0
  104. package/dist/components/Spinner/styles.css.d.ts +1 -28
  105. package/dist/components/Spinner/styles.css.d.ts.map +1 -1
  106. package/dist/components/SplitPane/SplitPane.d.ts +38 -0
  107. package/dist/components/SplitPane/SplitPane.d.ts.map +1 -0
  108. package/dist/components/SplitPane/SplitPane.meta.d.ts +3 -0
  109. package/dist/components/SplitPane/SplitPane.meta.d.ts.map +1 -0
  110. package/dist/components/SplitPane/SplitPane.stories.d.ts +17 -0
  111. package/dist/components/SplitPane/SplitPane.stories.d.ts.map +1 -0
  112. package/dist/components/SplitPane/styles.css.d.ts +78 -0
  113. package/dist/components/SplitPane/styles.css.d.ts.map +1 -0
  114. package/dist/components/StatusIcon/StatusIcon.d.ts.map +1 -1
  115. package/dist/components/StatusIcon/styles.css.d.ts +3 -0
  116. package/dist/components/StatusIcon/styles.css.d.ts.map +1 -1
  117. package/dist/components/Switch/Switch.d.ts +2 -18
  118. package/dist/components/Switch/Switch.d.ts.map +1 -1
  119. package/dist/components/Switch/Switch.types.d.ts +18 -0
  120. package/dist/components/Switch/Switch.types.d.ts.map +1 -0
  121. package/dist/components/TableOfContents/styles.css.d.ts +1 -0
  122. package/dist/components/TableOfContents/styles.css.d.ts.map +1 -1
  123. package/dist/components/Tabs/TabLayout.d.ts +7 -0
  124. package/dist/components/Tabs/TabLayout.d.ts.map +1 -0
  125. package/dist/components/Tabs/Tabs.d.ts.map +1 -1
  126. package/dist/components/Tabs/indicator.d.ts +28 -0
  127. package/dist/components/Tabs/indicator.d.ts.map +1 -0
  128. package/dist/components/Tabs/styles.css.d.ts +4 -0
  129. package/dist/components/Tabs/styles.css.d.ts.map +1 -1
  130. package/dist/components/Tabs/useTabIndicator.d.ts +4 -0
  131. package/dist/components/Tabs/useTabIndicator.d.ts.map +1 -0
  132. package/dist/components/Tabs/useTabKeys.d.ts +13 -0
  133. package/dist/components/Tabs/useTabKeys.d.ts.map +1 -0
  134. package/dist/components/Tag/RemoveGlyph.d.ts +4 -0
  135. package/dist/components/Tag/RemoveGlyph.d.ts.map +1 -0
  136. package/dist/components/Tag/Tag.d.ts +2 -24
  137. package/dist/components/Tag/Tag.d.ts.map +1 -1
  138. package/dist/components/Tag/styles.css.d.ts +8 -0
  139. package/dist/components/Tag/styles.css.d.ts.map +1 -1
  140. package/dist/components/Tag/types.d.ts +39 -0
  141. package/dist/components/Tag/types.d.ts.map +1 -0
  142. package/dist/components/Textarea/styles.css.d.ts +1 -0
  143. package/dist/components/Textarea/styles.css.d.ts.map +1 -1
  144. package/dist/components/Toast/ToastProvider.d.ts +2 -21
  145. package/dist/components/Toast/ToastProvider.d.ts.map +1 -1
  146. package/dist/components/Toast/native.css.d.ts +81 -0
  147. package/dist/components/Toast/native.css.d.ts.map +1 -0
  148. package/dist/components/Toast/store.d.ts +47 -0
  149. package/dist/components/Toast/store.d.ts.map +1 -0
  150. package/dist/components/Toggle/Toggle.d.ts.map +1 -1
  151. package/dist/components/Toggle/styles.css.d.ts +11 -2
  152. package/dist/components/Toggle/styles.css.d.ts.map +1 -1
  153. package/dist/components/Tooltip/TooltipHint.d.ts +9 -0
  154. package/dist/components/Tooltip/TooltipHint.d.ts.map +1 -0
  155. package/dist/components/Tree/Tree.d.ts.map +1 -1
  156. package/dist/components/Tree/styles.css.d.ts +6 -0
  157. package/dist/components/Tree/styles.css.d.ts.map +1 -1
  158. package/dist/components/Tree/useTreeKeyboard.d.ts +19 -0
  159. package/dist/components/Tree/useTreeKeyboard.d.ts.map +1 -0
  160. package/dist/components/VirtualList/VirtualList.d.ts +33 -0
  161. package/dist/components/VirtualList/VirtualList.d.ts.map +1 -0
  162. package/dist/components/VirtualList/VirtualList.meta.d.ts +3 -0
  163. package/dist/components/VirtualList/VirtualList.meta.d.ts.map +1 -0
  164. package/dist/components/VirtualList/VirtualList.stories.d.ts +15 -0
  165. package/dist/components/VirtualList/VirtualList.stories.d.ts.map +1 -0
  166. package/dist/components/VirtualList/styles.css.d.ts +28 -0
  167. package/dist/components/VirtualList/styles.css.d.ts.map +1 -0
  168. package/dist/components/VirtualTable/styles.css.d.ts +6 -0
  169. package/dist/components/VirtualTable/styles.css.d.ts.map +1 -1
  170. package/dist/index.css +1 -1
  171. package/dist/index.d.ts +3 -1
  172. package/dist/index.d.ts.map +1 -1
  173. package/dist/index.js +6202 -4794
  174. package/dist/index.js.map +1 -1
  175. package/dist/shared/ButtonHost.d.ts +11 -0
  176. package/dist/shared/ButtonHost.d.ts.map +1 -0
  177. package/dist/shared/portal/hostRegistry.d.ts +33 -0
  178. package/dist/shared/portal/hostRegistry.d.ts.map +1 -0
  179. package/dist/shared/pressableChild.d.ts +14 -0
  180. package/dist/shared/pressableChild.d.ts.map +1 -0
  181. package/dist/shared/useOverlayOpenState.d.ts +40 -0
  182. package/dist/shared/useOverlayOpenState.d.ts.map +1 -0
  183. package/dist/table.js +141 -135
  184. package/dist/table.js.map +1 -1
  185. package/package.json +34 -4
  186. package/src/components/Alert/Alert.tsx +14 -2
  187. package/src/components/AppShell/AppShell.meta.ts +1 -1
  188. package/src/components/AppShell/AppShell.native.tsx +174 -0
  189. package/src/components/AppShell/native.css.ts +56 -0
  190. package/src/components/Button/Button.tsx +6 -5
  191. package/src/components/Button/styles.css.ts +2 -0
  192. package/src/components/Callout/Callout.tsx +17 -3
  193. package/src/components/Callout/styles.css.ts +5 -0
  194. package/src/components/Checkbox/Checkbox.native.tsx +67 -0
  195. package/src/components/Checkbox/Checkbox.tsx +1 -50
  196. package/src/components/ConfirmDialog/ConfirmDialog.tsx +3 -0
  197. package/src/components/DetailPanel/DetailPanel.tsx +9 -45
  198. package/src/components/Dialog/Dialog.native.tsx +296 -0
  199. package/src/components/Dialog/Dialog.stories.tsx +71 -0
  200. package/src/components/Dialog/Dialog.tsx +41 -130
  201. package/src/components/Dialog/native.css.ts +78 -0
  202. package/src/components/Dialog/parts.css.ts +101 -0
  203. package/src/components/Dialog/parts.tsx +61 -0
  204. package/src/components/Dialog/styles.css.ts +2 -89
  205. package/src/components/Drawer/Drawer.meta.ts +2 -1
  206. package/src/components/Drawer/Drawer.native.tsx +469 -0
  207. package/src/components/Drawer/Drawer.tsx +63 -179
  208. package/src/components/Drawer/DrawerScroll.stories.tsx +4 -54
  209. package/src/components/Drawer/DrawerSnap.stories.tsx +205 -0
  210. package/src/components/Drawer/native.css.ts +58 -0
  211. package/src/components/Drawer/parts.css.ts +125 -0
  212. package/src/components/Drawer/parts.tsx +110 -0
  213. package/src/components/Drawer/sheetPhysics.ts +103 -0
  214. package/src/components/Drawer/styles.css.ts +19 -74
  215. package/src/components/Drawer/touchInput.ts +81 -0
  216. package/src/components/Drawer/useDrawerRoot.ts +90 -0
  217. package/src/components/Drawer/useSheetHeights.ts +42 -0
  218. package/src/components/Drawer/useSwipeDismiss.ts +146 -23
  219. package/src/components/Grid/styles.css.ts +2 -0
  220. package/src/components/Icon/Icon.native.tsx +54 -0
  221. package/src/components/Icon/Icon.tsx +19 -430
  222. package/src/components/Icon/geometry.ts +456 -0
  223. package/src/components/Icon/types.ts +74 -0
  224. package/src/components/Input/Input.tsx +8 -0
  225. package/src/components/Input/styles.css.ts +2 -0
  226. package/src/components/LinkButton/styles.css.ts +2 -0
  227. package/src/components/Menu/Menu.meta.ts +1 -1
  228. package/src/components/Menu/Menu.native.tsx +268 -0
  229. package/src/components/Menu/Menu.tsx +11 -2
  230. package/src/components/Menu/native.css.ts +18 -0
  231. package/src/components/Menu/useMenuOpen.ts +33 -0
  232. package/src/components/Menu/useMenuRoot.ts +7 -26
  233. package/src/components/Meter/styles.css.ts +2 -0
  234. package/src/components/PageShell/styles.css.ts +4 -0
  235. package/src/components/Popover/Popover.meta.ts +1 -1
  236. package/src/components/Popover/Popover.native.tsx +205 -0
  237. package/src/components/Popover/native.css.ts +13 -0
  238. package/src/components/RadioGroup/RadioGroup.meta.ts +1 -1
  239. package/src/components/RadioGroup/RadioGroup.native.tsx +48 -0
  240. package/src/components/RadioGroup/RadioGroup.stories.tsx +64 -1
  241. package/src/components/RadioGroup/RadioGroup.tsx +8 -83
  242. package/src/components/RadioGroup/group.tsx +85 -0
  243. package/src/components/RadioGroup/styles.css.ts +13 -0
  244. package/src/components/ScrollArea/styles.css.ts +2 -0
  245. package/src/components/SideNav/Chevron.native.tsx +20 -0
  246. package/src/components/SideNav/Chevron.tsx +20 -0
  247. package/src/components/SideNav/SideNav.meta.ts +1 -1
  248. package/src/components/SideNav/SideNav.tsx +57 -41
  249. package/src/components/SideNav/styles.css.ts +8 -0
  250. package/src/components/SideNav/types.ts +6 -0
  251. package/src/components/Spinner/Spinner.native.tsx +37 -0
  252. package/src/components/Spinner/Spinner.tsx +6 -15
  253. package/src/components/Spinner/Spinner.types.ts +6 -0
  254. package/src/components/Spinner/ring.css.ts +45 -0
  255. package/src/components/Spinner/styles.css.ts +2 -32
  256. package/src/components/SplitPane/SplitPane.meta.ts +40 -0
  257. package/src/components/SplitPane/SplitPane.native.tsx +47 -0
  258. package/src/components/SplitPane/SplitPane.stories.tsx +148 -0
  259. package/src/components/SplitPane/SplitPane.tsx +218 -0
  260. package/src/components/SplitPane/styles.css.ts +81 -0
  261. package/src/components/StatusIcon/StatusIcon.tsx +13 -2
  262. package/src/components/StatusIcon/styles.css.ts +5 -0
  263. package/src/components/Switch/Switch.native.tsx +45 -0
  264. package/src/components/Switch/Switch.tsx +22 -44
  265. package/src/components/Switch/Switch.types.ts +18 -0
  266. package/src/components/TableOfContents/styles.css.ts +2 -0
  267. package/src/components/Tabs/TabLayout.native.tsx +9 -0
  268. package/src/components/Tabs/TabLayout.tsx +6 -0
  269. package/src/components/Tabs/Tabs.meta.ts +1 -1
  270. package/src/components/Tabs/Tabs.tsx +38 -136
  271. package/src/components/Tabs/indicator.ts +36 -0
  272. package/src/components/Tabs/styles.css.ts +7 -0
  273. package/src/components/Tabs/useTabIndicator.native.ts +35 -0
  274. package/src/components/Tabs/useTabIndicator.ts +49 -0
  275. package/src/components/Tabs/useTabKeys.native.ts +2 -0
  276. package/src/components/Tabs/useTabKeys.ts +99 -0
  277. package/src/components/Tag/RemoveGlyph.native.tsx +17 -0
  278. package/src/components/Tag/RemoveGlyph.tsx +10 -0
  279. package/src/components/Tag/Tag.meta.ts +1 -1
  280. package/src/components/Tag/Tag.native.tsx +70 -0
  281. package/src/components/Tag/Tag.tsx +9 -37
  282. package/src/components/Tag/styles.css.ts +9 -0
  283. package/src/components/Tag/types.ts +43 -0
  284. package/src/components/Textarea/styles.css.ts +2 -0
  285. package/src/components/ThemeProvider/ThemeProvider.native.tsx +70 -10
  286. package/src/components/Toast/Toast.meta.ts +1 -1
  287. package/src/components/Toast/ToastProvider.native.tsx +226 -0
  288. package/src/components/Toast/ToastProvider.tsx +22 -84
  289. package/src/components/Toast/native.css.ts +78 -0
  290. package/src/components/Toast/store.ts +96 -0
  291. package/src/components/Toggle/Toggle.tsx +5 -3
  292. package/src/components/Toggle/styles.css.ts +14 -2
  293. package/src/components/Tooltip/Tooltip.meta.ts +1 -1
  294. package/src/components/Tooltip/Tooltip.native.tsx +36 -0
  295. package/src/components/Tooltip/TooltipHint.ts +13 -0
  296. package/src/components/Tree/Tree.meta.ts +1 -1
  297. package/src/components/Tree/Tree.tsx +23 -135
  298. package/src/components/Tree/styles.css.ts +6 -0
  299. package/src/components/Tree/useTreeKeyboard.native.ts +5 -0
  300. package/src/components/Tree/useTreeKeyboard.ts +152 -0
  301. package/src/components/VirtualList/VirtualList.meta.ts +39 -0
  302. package/src/components/VirtualList/VirtualList.stories.tsx +95 -0
  303. package/src/components/VirtualList/VirtualList.tsx +103 -0
  304. package/src/components/VirtualList/styles.css.ts +31 -0
  305. package/src/components/VirtualTable/styles.css.ts +12 -0
  306. package/src/hooks/useContainerBelow.native.ts +30 -0
  307. package/src/index.native.ts +61 -1
  308. package/src/index.ts +8 -1
  309. package/src/native-guard/globals.d.ts +10 -0
  310. package/src/native-guard/react-dom.d.ts +3 -0
  311. package/src/shared/ButtonHost.native.tsx +70 -0
  312. package/src/shared/ButtonHost.tsx +12 -0
  313. package/src/shared/accessibilityFocus.native.ts +23 -0
  314. package/src/shared/portal/AnchoredOverlay.native.tsx +117 -0
  315. package/src/shared/portal/OverlayModal.native.tsx +51 -0
  316. package/src/shared/portal/PortalHost.native.tsx +102 -0
  317. package/src/shared/portal/hostRegistry.ts +76 -0
  318. package/src/shared/portal/themeScope.native.ts +18 -0
  319. package/src/shared/pressableChild.ts +33 -0
  320. package/src/shared/safeArea.native.ts +22 -0
  321. package/src/shared/useKeyboardHeight.native.ts +18 -0
  322. package/src/shared/useOverlayOpenState.ts +128 -0
  323. package/src/shared/useScreenReaderEnabled.native.ts +19 -0
  324. package/dist/Table-ColfpgKV.js +0 -1552
  325. package/dist/Table-ColfpgKV.js.map +0 -1
  326. package/dist/components/ThemeProvider/ThemeProvider.native.d.ts +0 -10
  327. package/dist/components/ThemeProvider/ThemeProvider.native.d.ts.map +0 -1
  328. package/dist/index.native.d.ts +0 -33
  329. package/dist/index.native.d.ts.map +0 -1
  330. package/dist/platform.native.d.ts +0 -2
  331. package/dist/platform.native.d.ts.map +0 -1
@@ -0,0 +1,56 @@
1
+ import {css} from 'react-strict-dom'
2
+ import {colors} from '@duro-app/tokens/tokens/colors.css'
3
+ import {spacing} from '@duro-app/tokens/tokens/spacing.css'
4
+ import {sizes} from '@duro-app/tokens/tokens/sizes.css'
5
+ import {borders} from '@duro-app/tokens/tokens/borders.css'
6
+
7
+ /**
8
+ * The native AppShell: a phone is below every `collapseBelow`, so it is
9
+ * always the narrow shape — the bar on top, Main below, the rail in a Drawer.
10
+ */
11
+ export const nativeStyles = css.create({
12
+ shell: {
13
+ flexGrow: 1,
14
+ display: 'flex',
15
+ flexDirection: 'column',
16
+ color: colors.text,
17
+ backgroundColor: colors.bg,
18
+ },
19
+ bar: {
20
+ display: 'flex',
21
+ flexDirection: 'row',
22
+ alignItems: 'center',
23
+ gap: spacing.sm,
24
+ minHeight: sizes.appBarH,
25
+ paddingLeft: spacing.md,
26
+ paddingRight: spacing.md,
27
+ backgroundColor: colors.bgCard,
28
+ borderBottomWidth: borders.hairline,
29
+ borderBottomStyle: 'solid',
30
+ borderBottomColor: colors.border,
31
+ },
32
+ // The bar sits below the status bar: the top safe-area inset is its padding.
33
+ barTop: (top: number) => ({paddingTop: top}),
34
+ header: {
35
+ display: 'flex',
36
+ flexDirection: 'row',
37
+ alignItems: 'center',
38
+ gap: spacing.sm,
39
+ flexGrow: 1,
40
+ flexShrink: 1,
41
+ },
42
+ main: {
43
+ flexGrow: 1,
44
+ flexShrink: 1,
45
+ paddingTop: spacing.lg,
46
+ paddingLeft: spacing.md,
47
+ paddingRight: spacing.md,
48
+ },
49
+ drawerFooter: {
50
+ marginTop: spacing.md,
51
+ paddingTop: spacing.md,
52
+ borderTopWidth: borders.hairline,
53
+ borderTopStyle: 'solid',
54
+ borderTopColor: colors.border,
55
+ },
56
+ })
@@ -1,7 +1,7 @@
1
1
  import type {ReactNode, Ref} from 'react'
2
- import {html} from 'react-strict-dom'
3
2
  import {isNative} from '../../platform'
4
3
  import {mergeRefs} from '../../shared/mergeRefs'
4
+ import {ButtonHost} from '../../shared/ButtonHost'
5
5
  import type {ControlSize} from '../../shared/types'
6
6
  import {useGroupedControl} from '../ButtonGroup/useGroupedControl'
7
7
  import {styles} from './styles.css'
@@ -24,7 +24,8 @@ interface ButtonProps {
24
24
  'aria-controls'?: string
25
25
  /**
26
26
  * The underlying <button>, e.g. to anchor a Popover or return focus to it.
27
- * Web only: on native the ref is not attached.
27
+ * On native it is react-strict-dom's element (a Pressable host), which
28
+ * measures with `measureInWindow` and takes accessibility focus.
28
29
  */
29
30
  ref?: Ref<HTMLButtonElement>
30
31
  children: ReactNode
@@ -50,8 +51,8 @@ export function Button({
50
51
  }: ButtonProps) {
51
52
  const grouped = useGroupedControl<HTMLButtonElement>({disabled})
52
53
  return (
53
- <html.button
54
- ref={isNative ? undefined : mergeRefs(ref, grouped.ref)}
54
+ <ButtonHost
55
+ ref={isNative ? ref : mergeRefs(ref, grouped.ref)}
55
56
  tabIndex={grouped.tabIndex}
56
57
  onFocus={grouped.onFocus}
57
58
  type={type}
@@ -71,6 +72,6 @@ export function Button({
71
72
  ]}
72
73
  >
73
74
  {children}
74
- </html.button>
75
+ </ButtonHost>
75
76
  )
76
77
  }
@@ -130,6 +130,8 @@ export const styles = css.create({
130
130
  },
131
131
  fullWidth: {
132
132
  width: '100%',
133
+ // Native has no border-box reset (the web's global-reset.css); declare it.
134
+ boxSizing: 'border-box',
133
135
  },
134
136
  disabled: {
135
137
  opacity: 0.5,
@@ -2,6 +2,8 @@ import type {ReactNode} from 'react'
2
2
  import {html} from 'react-strict-dom'
3
3
  import {Icon} from '../Icon'
4
4
  import type {IconName} from '../Icon'
5
+ import type {ColorToken} from '@duro-app/tokens/keys'
6
+ import {isNative} from '../../platform'
5
7
  import {styles} from './styles.css'
6
8
 
7
9
  export type CalloutVariant = 'error' | 'success' | 'warning' | 'info'
@@ -13,6 +15,15 @@ const defaultIcons: Record<CalloutVariant, IconName> = {
13
15
  error: 'x-circle-filled',
14
16
  }
15
17
 
18
+ // On native an Icon cannot inherit the variant's text colour, so it is passed
19
+ // (the web keeps inheriting it).
20
+ const iconColor = {
21
+ error: 'errorText',
22
+ success: 'successText',
23
+ warning: 'warningText',
24
+ info: 'infoText',
25
+ } as const satisfies Record<CalloutVariant, ColorToken>
26
+
16
27
  interface CalloutProps {
17
28
  variant?: CalloutVariant
18
29
  /** Built-in icon name, custom ReactNode, or false to hide. Defaults to variant icon. */
@@ -28,8 +39,9 @@ interface CalloutProps {
28
39
 
29
40
  function resolveIcon(icon: CalloutProps['icon'], variant: CalloutVariant): ReactNode | null {
30
41
  if (icon === false) return null
31
- if (icon === undefined) return <Icon name={defaultIcons[variant]} size="xl" />
32
- if (typeof icon === 'string') return <Icon name={icon as IconName} size="xl" />
42
+ const color = isNative ? iconColor[variant] : undefined
43
+ if (icon === undefined) return <Icon name={defaultIcons[variant]} size="xl" color={color} />
44
+ if (typeof icon === 'string') return <Icon name={icon as IconName} size="xl" color={color} />
33
45
  return icon
34
46
  }
35
47
 
@@ -45,7 +57,9 @@ export function Callout({variant = 'info', icon, align = 'center', children}: Ca
45
57
  align === 'center' ? styles.alignCenter : styles.alignStart,
46
58
  ]}
47
59
  >
48
- {resolvedIcon && <html.span style={styles.icon}>{resolvedIcon}</html.span>}
60
+ {resolvedIcon && (
61
+ <html.span style={[styles.icon, isNative && styles.nativeFlex]}>{resolvedIcon}</html.span>
62
+ )}
49
63
  <html.div style={styles.content}>{children}</html.div>
50
64
  </html.div>
51
65
  )
@@ -55,4 +55,9 @@ export const styles = css.create({
55
55
  borderColor: colors.infoBorder,
56
56
  color: colors.infoText,
57
57
  },
58
+ // RSD-native only honors display:'flex' (not 'inline-flex'). Layered on
59
+ // native only.
60
+ nativeFlex: {
61
+ display: 'flex',
62
+ },
58
63
  })
@@ -0,0 +1,67 @@
1
+ import {type ReactNode, useState, useCallback} from 'react'
2
+ import {html} from 'react-strict-dom'
3
+ import {styles} from './styles.css'
4
+
5
+ interface CheckboxProps {
6
+ name?: string
7
+ value?: string
8
+ checked?: boolean
9
+ defaultChecked?: boolean
10
+ disabled?: boolean
11
+ /** Accessible label when no visible label (children) is provided. */
12
+ 'aria-label'?: string
13
+ onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
14
+ children?: ReactNode
15
+ }
16
+
17
+ export function Checkbox({
18
+ name,
19
+ value,
20
+ checked: controlledChecked,
21
+ defaultChecked = false,
22
+ disabled = false,
23
+ 'aria-label': ariaLabel,
24
+ onChange,
25
+ children,
26
+ }: CheckboxProps) {
27
+ const isControlled = controlledChecked !== undefined
28
+ const [internalChecked, setInternalChecked] = useState(defaultChecked)
29
+ const isChecked = isControlled ? controlledChecked : internalChecked
30
+
31
+ // RSD only makes <button> pressable and has no <input type=checkbox>, so the
32
+ // whole row is a button and we synthesize the change payload that consumers
33
+ // read (e.target.checked).
34
+ const handlePress = useCallback(() => {
35
+ if (disabled) return
36
+ const next = !isChecked
37
+ if (!isControlled) {
38
+ setInternalChecked(next)
39
+ }
40
+ onChange?.({
41
+ target: {checked: next, name, value},
42
+ } as unknown as React.ChangeEvent<HTMLInputElement>)
43
+ }, [disabled, isChecked, isControlled, onChange, name, value])
44
+
45
+ return (
46
+ <html.button
47
+ type="button"
48
+ onClick={handlePress}
49
+ disabled={disabled}
50
+ role="checkbox"
51
+ aria-checked={isChecked}
52
+ aria-label={!children ? ariaLabel : undefined}
53
+ style={[styles.root, styles.nativeFlex, styles.nativeButton, disabled && styles.rootDisabled]}
54
+ >
55
+ <html.div
56
+ style={[styles.box, styles.nativeFlex, isChecked ? styles.boxChecked : styles.boxUnchecked]}
57
+ >
58
+ {/* CSS-border checkmark (rotated right+bottom borders) — no SVG, no
59
+ react-native-svg dep, so nothing is added to the web bundle. */}
60
+ <html.div
61
+ style={[styles.checkmark, isChecked ? styles.checkmarkVisible : styles.checkmarkHidden]}
62
+ />
63
+ </html.div>
64
+ {children && <html.span style={styles.labelText}>{children}</html.span>}
65
+ </html.button>
66
+ )
67
+ }
@@ -1,6 +1,5 @@
1
1
  import {type ReactNode, useState, useCallback} from 'react'
2
2
  import {html} from 'react-strict-dom'
3
- import {isNative} from '../../platform'
4
3
  import {styles} from './styles.css'
5
4
  import {visuallyHidden} from '../../styles/visually-hidden.css'
6
5
 
@@ -30,7 +29,7 @@ export function Checkbox({
30
29
  const [internalChecked, setInternalChecked] = useState(defaultChecked)
31
30
  const isChecked = isControlled ? controlledChecked : internalChecked
32
31
 
33
- // Web: the real <input type=checkbox> drives toggling, a11y and form submit.
32
+ // The real <input type=checkbox> drives toggling, a11y and form submit.
34
33
  const handleChange = useCallback(
35
34
  (e: React.ChangeEvent<HTMLInputElement>) => {
36
35
  if (!isControlled) {
@@ -41,54 +40,6 @@ export function Checkbox({
41
40
  [isControlled, onChange],
42
41
  )
43
42
 
44
- // Native: RSD only makes <button> pressable and has no <input type=checkbox>,
45
- // so the whole row is a button and we synthesize the change payload that
46
- // consumers read (e.target.checked).
47
- const handlePress = useCallback(() => {
48
- if (disabled) return
49
- const next = !isChecked
50
- if (!isControlled) {
51
- setInternalChecked(next)
52
- }
53
- onChange?.({
54
- target: {checked: next, name, value},
55
- } as unknown as React.ChangeEvent<HTMLInputElement>)
56
- }, [disabled, isChecked, isControlled, onChange, name, value])
57
-
58
- if (isNative) {
59
- return (
60
- <html.button
61
- type="button"
62
- onClick={handlePress}
63
- disabled={disabled}
64
- role="checkbox"
65
- aria-checked={isChecked}
66
- aria-label={!children ? ariaLabel : undefined}
67
- style={[
68
- styles.root,
69
- styles.nativeFlex,
70
- styles.nativeButton,
71
- disabled && styles.rootDisabled,
72
- ]}
73
- >
74
- <html.div
75
- style={[
76
- styles.box,
77
- styles.nativeFlex,
78
- isChecked ? styles.boxChecked : styles.boxUnchecked,
79
- ]}
80
- >
81
- {/* CSS-border checkmark (rotated right+bottom borders) — no SVG, no
82
- react-native-svg dep, so nothing is added to the web bundle. */}
83
- <html.div
84
- style={[styles.checkmark, isChecked ? styles.checkmarkVisible : styles.checkmarkHidden]}
85
- />
86
- </html.div>
87
- {children && <html.span style={styles.labelText}>{children}</html.span>}
88
- </html.button>
89
- )
90
- }
91
-
92
43
  return (
93
44
  <html.label style={[styles.root, disabled && styles.rootDisabled]}>
94
45
  <html.input
@@ -74,6 +74,9 @@ export function ConfirmDialog({
74
74
  value={typed}
75
75
  onChange={(e) => setTyped(e.target.value)}
76
76
  autoFocus
77
+ // The phrase must match exactly: no capitalising or correcting it.
78
+ autoCapitalize="none"
79
+ spellCheck={false}
77
80
  aria-label="confirmation phrase"
78
81
  />
79
82
  </Field.Root>
@@ -10,7 +10,8 @@ import {
10
10
  useRef,
11
11
  } from 'react'
12
12
  import {html} from 'react-strict-dom'
13
- import {DURATION_MS, type DurationToken} from '@duro-app/tokens/keys'
13
+ import type {DurationToken} from '@duro-app/tokens/keys'
14
+ import {useOverlayOpenState} from '../../shared/useOverlayOpenState'
14
15
  import {styles} from './styles.css'
15
16
 
16
17
  // --- Types ---
@@ -71,34 +72,19 @@ function Root({
71
72
  onOpenChange,
72
73
  closeAnimationDuration = 'base',
73
74
  }: RootProps) {
74
- const isControlled = controlledOpen !== undefined
75
- const [internalOpen, setInternalOpen] = useState(defaultOpen)
76
- const [present, setPresent] = useState(defaultOpen)
77
- const [closing, setClosing] = useState(false)
78
- const closingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
75
+ const {present, closing, requestOpen, requestClose} = useOverlayOpenState({
76
+ open: controlledOpen,
77
+ defaultOpen,
78
+ onOpenChange,
79
+ exitDuration: closeAnimationDuration,
80
+ closeNotice: 'at-once',
81
+ })
79
82
  const panelRef = useRef<HTMLDivElement>(null)
80
83
  const previousFocusRef = useRef<Element | null>(null)
81
84
 
82
85
  const titleId = useId()
83
86
  const [labelId, setLabelId] = useState<string | undefined>(undefined)
84
87
 
85
- const isOpen = isControlled ? controlledOpen : internalOpen
86
-
87
- // Sync presence with open state
88
- useEffect(() => {
89
- if (isOpen && !present) {
90
- setPresent(true)
91
- setClosing(false)
92
- } else if (!isOpen && present && !closing) {
93
- setClosing(true)
94
- closingTimerRef.current = setTimeout(() => {
95
- setClosing(false)
96
- setPresent(false)
97
- closingTimerRef.current = null
98
- }, DURATION_MS[closeAnimationDuration])
99
- }
100
- }, [isOpen, present, closing, closeAnimationDuration])
101
-
102
88
  // Focus management: move focus on open, restore on close
103
89
  useEffect(() => {
104
90
  if (present && !closing) {
@@ -131,28 +117,6 @@ function Root({
131
117
  return () => document.removeEventListener('keydown', onKeyDown)
132
118
  }, [present, closing]) // eslint-disable-line react-hooks/exhaustive-deps
133
119
 
134
- // Clean up timer on unmount
135
- useEffect(() => {
136
- return () => {
137
- if (closingTimerRef.current) clearTimeout(closingTimerRef.current)
138
- }
139
- }, [])
140
-
141
- const requestOpen = useCallback(() => {
142
- if (closingTimerRef.current) {
143
- clearTimeout(closingTimerRef.current)
144
- closingTimerRef.current = null
145
- setClosing(false)
146
- }
147
- if (!isControlled) setInternalOpen(true)
148
- onOpenChange?.(true)
149
- }, [isControlled, onOpenChange])
150
-
151
- const requestClose = useCallback(() => {
152
- if (!isControlled) setInternalOpen(false)
153
- onOpenChange?.(false)
154
- }, [isControlled, onOpenChange])
155
-
156
120
  const registerTitle = useCallback((id: string) => {
157
121
  setLabelId(id)
158
122
  }, [])
@@ -0,0 +1,296 @@
1
+ import {
2
+ Children,
3
+ cloneElement,
4
+ createContext,
5
+ isValidElement,
6
+ type ReactElement,
7
+ type ReactNode,
8
+ type Ref,
9
+ type RefObject,
10
+ useCallback,
11
+ useContext,
12
+ useEffect,
13
+ useId,
14
+ useRef,
15
+ useState,
16
+ } from 'react'
17
+ import {
18
+ KeyboardAvoidingView,
19
+ Platform,
20
+ ScrollView,
21
+ StyleSheet,
22
+ useWindowDimensions,
23
+ View,
24
+ } from 'react-native'
25
+ import {html} from 'react-strict-dom'
26
+ import type {DurationToken} from '@duro-app/tokens/keys'
27
+ import {mergeRefs} from '../../shared/mergeRefs'
28
+ import {ModalContext} from '../../shared/ModalContext'
29
+ import {OverlayModal} from '../../shared/portal/OverlayModal'
30
+ import {focusForAccessibility} from '../../shared/accessibilityFocus'
31
+ import {useOverlayOpenState} from '../../shared/useOverlayOpenState'
32
+ import {ControlContextBoundary} from '../Toolbar/ControlContextBoundary'
33
+ import {Icon} from '../Icon/Icon'
34
+ import {nativeStyles} from './native.css'
35
+ import {partStyles} from './parts.css'
36
+ import {
37
+ Description,
38
+ DialogContext,
39
+ Footer,
40
+ Header,
41
+ Title,
42
+ useDialog,
43
+ type DialogSize,
44
+ } from './parts'
45
+
46
+ export type {DialogSize}
47
+
48
+ const exitDurationMap = {
49
+ instant: nativeStyles.exitInstant,
50
+ minimal: nativeStyles.exitMinimal,
51
+ quick: nativeStyles.exitQuick,
52
+ fast: nativeStyles.exitFast,
53
+ brisk: nativeStyles.exitBrisk,
54
+ base: nativeStyles.exitBase,
55
+ slow: nativeStyles.exitSlow,
56
+ } as const satisfies Record<DurationToken, unknown>
57
+
58
+ /** Native-only wiring: where accessibility focus goes on open and returns on close. */
59
+ interface NativeDialogContextValue {
60
+ triggerRef: RefObject<unknown>
61
+ panelRef: RefObject<View | null>
62
+ closeOnEscape: boolean
63
+ }
64
+
65
+ const NativeDialogContext = createContext<NativeDialogContextValue | null>(null)
66
+
67
+ function useNativeDialog() {
68
+ const ctx = useContext(NativeDialogContext)
69
+ if (!ctx) throw new Error('Dialog compound components must be used within Dialog.Root')
70
+ return ctx
71
+ }
72
+
73
+ interface RootProps {
74
+ children: ReactNode
75
+ open?: boolean
76
+ defaultOpen?: boolean
77
+ onOpenChange?: (open: boolean) => void
78
+ /** Whether pressing the backdrop closes the dialog. Default: true */
79
+ dismissable?: boolean
80
+ /** Whether Android's back button closes the dialog (Escape on the web). Default: true */
81
+ closeOnEscape?: boolean
82
+ /** Web only: on native, screen-reader focus moves to the dialog itself. */
83
+ initialFocus?: RefObject<HTMLElement | null>
84
+ closeAnimationDuration?: DurationToken
85
+ }
86
+
87
+ /**
88
+ * Native Dialog (plan N0, Slice A): the same compound API, rendered in an
89
+ * OverlayModal. The backdrop press and Android's back button close it; the
90
+ * screen reader's focus moves into the panel on open and back to the trigger
91
+ * on close.
92
+ */
93
+ function Root({
94
+ children,
95
+ open: controlledOpen,
96
+ defaultOpen = false,
97
+ onOpenChange,
98
+ dismissable = true,
99
+ closeOnEscape = true,
100
+ closeAnimationDuration = 'fast',
101
+ }: RootProps) {
102
+ const {present, closing, requestOpen, requestClose} = useOverlayOpenState({
103
+ open: controlledOpen,
104
+ defaultOpen,
105
+ onOpenChange,
106
+ exitDuration: closeAnimationDuration,
107
+ closeNotice: 'after-exit',
108
+ })
109
+ const titleId = useId()
110
+ const descriptionId = useId()
111
+ const popupRef = useRef<HTMLDivElement>(null)
112
+ const triggerRef = useRef<unknown>(null)
113
+ const panelRef = useRef<View>(null)
114
+ const registerClose = useCallback(() => () => {}, [])
115
+
116
+ // Back to the trigger once the dialog is gone.
117
+ const wasPresent = useRef(present)
118
+ useEffect(() => {
119
+ if (wasPresent.current && !present) focusForAccessibility(triggerRef.current, 'Dialog')
120
+ wasPresent.current = present
121
+ }, [present])
122
+
123
+ return (
124
+ <DialogContext.Provider
125
+ value={{
126
+ open: present,
127
+ closing,
128
+ closeDuration: closeAnimationDuration,
129
+ dismissable,
130
+ requestOpen,
131
+ requestClose,
132
+ titleId,
133
+ descriptionId,
134
+ popupRef,
135
+ registerClose,
136
+ }}
137
+ >
138
+ <NativeDialogContext.Provider value={{triggerRef, panelRef, closeOnEscape}}>
139
+ {children}
140
+ </NativeDialogContext.Provider>
141
+ </DialogContext.Provider>
142
+ )
143
+ }
144
+
145
+ interface PressableChildProps {
146
+ onClick?: () => void
147
+ ref?: Ref<unknown>
148
+ }
149
+
150
+ /**
151
+ * Opens the dialog from its one child (a Button). Native presses do not
152
+ * bubble to a wrapper as web clicks do, so the child gets the handler, and
153
+ * its ref is where focus returns on close.
154
+ */
155
+ function Trigger({children}: {children: ReactNode}) {
156
+ const {requestOpen} = useDialog()
157
+ const {triggerRef} = useNativeDialog()
158
+ const child = Children.only(children)
159
+ if (!isValidElement<PressableChildProps>(child)) return null
160
+ const own = child.props.onClick
161
+ return cloneElement(child as ReactElement<PressableChildProps>, {
162
+ ref: mergeRefs(child.props.ref, triggerRef),
163
+ onClick: () => {
164
+ own?.()
165
+ requestOpen()
166
+ },
167
+ })
168
+ }
169
+
170
+ interface PortalProps {
171
+ children: ReactNode
172
+ size?: DialogSize
173
+ }
174
+
175
+ function Portal({children, size = 'md'}: PortalProps) {
176
+ const {open, closing, closeDuration, dismissable, requestClose, titleId} = useDialog()
177
+ const {panelRef, closeOnEscape} = useNativeDialog()
178
+ // First frame hidden, then shown: the enter transition has a start state.
179
+ const [entered, setEntered] = useState(false)
180
+ useEffect(() => {
181
+ if (!open) {
182
+ setEntered(false)
183
+ return
184
+ }
185
+ const frame = requestAnimationFrame(() => setEntered(true))
186
+ return () => cancelAnimationFrame(frame)
187
+ }, [open])
188
+
189
+ // 85% of the window, as the web caps the panel at 85vh.
190
+ const maxPanelHeight = useWindowDimensions().height * 0.85
191
+ const shown = entered && !closing
192
+ const visibility = shown ? nativeStyles.shown : nativeStyles.hidden
193
+ return (
194
+ <OverlayModal
195
+ visible={open}
196
+ onRequestClose={() => {
197
+ if (closeOnEscape) requestClose()
198
+ }}
199
+ onShow={() => focusForAccessibility(panelRef.current, 'Dialog')}
200
+ >
201
+ <html.div
202
+ aria-hidden
203
+ onClick={dismissable ? requestClose : undefined}
204
+ style={[
205
+ nativeStyles.fill,
206
+ nativeStyles.backdrop,
207
+ shown ? nativeStyles.shown : nativeStyles.hidden,
208
+ closing && exitDurationMap[closeDuration],
209
+ ]}
210
+ />
211
+ <KeyboardAvoidingView
212
+ behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
213
+ style={StyleSheet.absoluteFill}
214
+ pointerEvents="box-none"
215
+ >
216
+ <View style={localStyles.viewport} pointerEvents="box-none">
217
+ <html.div style={nativeStyles.inset}>
218
+ <View ref={panelRef} accessibilityViewIsModal style={localStyles.panelHost}>
219
+ <html.div
220
+ role="dialog"
221
+ aria-modal
222
+ aria-labelledby={titleId}
223
+ style={[
224
+ nativeStyles.popup,
225
+ nativeStyles.maxHeight(maxPanelHeight),
226
+ partStyles[size],
227
+ visibility,
228
+ closing && exitDurationMap[closeDuration],
229
+ ]}
230
+ >
231
+ <ModalContext.Provider value={true}>
232
+ <ControlContextBoundary>{children}</ControlContextBoundary>
233
+ </ModalContext.Provider>
234
+ </html.div>
235
+ </View>
236
+ </html.div>
237
+ </View>
238
+ </KeyboardAvoidingView>
239
+ </OverlayModal>
240
+ )
241
+ }
242
+
243
+ // Layout-only RN views: the centring viewport lets presses outside the panel
244
+ // through to the backdrop (`box-none`, which RSD styles cannot express), and
245
+ // the panel host spans the width so the panel's size cap applies.
246
+ const localStyles = StyleSheet.create({
247
+ viewport: {flex: 1, alignItems: 'center', justifyContent: 'center'},
248
+ panelHost: {width: '100%', alignItems: 'center'},
249
+ body: {flexGrow: 0, flexShrink: 1},
250
+ })
251
+
252
+ /** Native Body: a ScrollView, since a native div does not scroll its overflow. */
253
+ function Body({children}: {children: ReactNode}) {
254
+ return (
255
+ <ScrollView style={localStyles.body}>
256
+ <html.div style={nativeStyles.bodyContent}>{children}</html.div>
257
+ </ScrollView>
258
+ )
259
+ }
260
+
261
+ interface CloseProps {
262
+ children?: ReactNode
263
+ 'aria-label'?: string
264
+ }
265
+
266
+ function Close({children, 'aria-label': ariaLabel = 'Close'}: CloseProps) {
267
+ const {requestClose} = useDialog()
268
+ if (children) {
269
+ const child = Children.only(children)
270
+ if (!isValidElement<PressableChildProps>(child)) return null
271
+ const own = child.props.onClick
272
+ return cloneElement(child as ReactElement<PressableChildProps>, {
273
+ onClick: () => {
274
+ own?.()
275
+ requestClose()
276
+ },
277
+ })
278
+ }
279
+ return (
280
+ <html.button onClick={requestClose} aria-label={ariaLabel} style={partStyles.closeButton}>
281
+ <Icon name="x" size="sm" color="textMuted" />
282
+ </html.button>
283
+ )
284
+ }
285
+
286
+ export const Dialog = {
287
+ Root,
288
+ Trigger,
289
+ Portal,
290
+ Header,
291
+ Title,
292
+ Description,
293
+ Body,
294
+ Footer,
295
+ Close,
296
+ }