@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,70 @@
1
+ import {html} from 'react-strict-dom'
2
+ import {SIZES_PX} from '@duro-app/tokens/keys'
3
+ import {styles} from './styles.css'
4
+ import {RemoveGlyph} from './RemoveGlyph'
5
+ import {variantText, type TagProps} from './types'
6
+
7
+ export type {TagProps, TagSize, TagVariant} from './types'
8
+
9
+ const sizeMap = {
10
+ sm: styles.sizeSm,
11
+ md: styles.sizeMd,
12
+ } as const
13
+
14
+ const removableSizeMap = {
15
+ sm: styles.removableSm,
16
+ md: styles.removableMd,
17
+ } as const
18
+
19
+ /**
20
+ * Native Tag (plan N0, Slice B): the label and, when removable, the remove
21
+ * button. TagGroup is not ported (its grid keyboard model is web only,
22
+ * Decision 6), so `value` is unused here; the remove button's name comes from
23
+ * a string label.
24
+ */
25
+ export function Tag({
26
+ variant = 'default',
27
+ size = 'md',
28
+ removable = false,
29
+ onRemove,
30
+ disabled = false,
31
+ wrap = false,
32
+ children,
33
+ }: TagProps) {
34
+ const removeLabel = typeof children === 'string' ? `Remove ${children}` : 'Remove'
35
+ // A div, not the web's span: a native span is a Text, which would lay the
36
+ // remove button out inline, raised off the label's centre line.
37
+ return (
38
+ <html.div
39
+ style={[
40
+ styles.base,
41
+ styles.nativeFlex,
42
+ sizeMap[size],
43
+ removable && removableSizeMap[size],
44
+ styles[variant],
45
+ disabled && styles.disabled,
46
+ wrap && styles.wrap,
47
+ ]}
48
+ >
49
+ <html.span>{children}</html.span>
50
+ {removable && (
51
+ <html.button
52
+ type="button"
53
+ aria-label={removeLabel}
54
+ disabled={disabled}
55
+ onClick={disabled ? undefined : onRemove}
56
+ style={[
57
+ styles.removeButton,
58
+ styles.nativeButtonFlex,
59
+ disabled && styles.removeButtonDisabled,
60
+ ]}
61
+ >
62
+ <RemoveGlyph
63
+ size={size === 'sm' ? SIZES_PX.glyphXs : SIZES_PX.glyphSm}
64
+ color={variantText[variant]}
65
+ />
66
+ </html.button>
67
+ )}
68
+ </html.div>
69
+ )
70
+ }
@@ -1,31 +1,12 @@
1
- import {type ReactNode, useCallback, useRef} from 'react'
1
+ import {useCallback, useRef} from 'react'
2
2
  import {html} from 'react-strict-dom'
3
+ import {SIZES_PX} from '@duro-app/tokens/keys'
3
4
  import {useTagGroup} from '../TagGroup/TagGroupContext'
4
5
  import {styles} from './styles.css'
6
+ import {RemoveGlyph} from './RemoveGlyph'
7
+ import {variantText, type TagProps} from './types'
5
8
 
6
- export type TagVariant = 'default' | 'success' | 'warning' | 'error' | 'info'
7
- export type TagSize = 'sm' | 'md'
8
-
9
- interface TagProps {
10
- /** Value identifier — required when used inside TagGroup */
11
- value?: string
12
- variant?: TagVariant
13
- size?: TagSize
14
- /** Show remove button. Auto-derived from TagGroup context when inside one. */
15
- removable?: boolean
16
- /** Called when remove button is clicked. Auto-wired to TagGroup when inside one. */
17
- onRemove?: () => void
18
- disabled?: boolean
19
- /**
20
- * Allow the label to wrap / break instead of staying on one line. Tags are
21
- * `white-space: nowrap` by default (they read as pills); set this for long
22
- * identifier-style values (e.g. `lldap.group.member.remove`) sitting in a
23
- * constrained column, so they break within their cell instead of leaking
24
- * into the next one.
25
- */
26
- wrap?: boolean
27
- children: ReactNode
28
- }
9
+ export type {TagProps, TagSize, TagVariant} from './types'
29
10
 
30
11
  const sizeMap = {
31
12
  sm: styles.sizeSm,
@@ -112,19 +93,10 @@ export function Tag({
112
93
  tabIndex={-1}
113
94
  style={[styles.removeButton, disabled && styles.removeButtonDisabled]}
114
95
  >
115
- <svg
116
- width={size === 'sm' ? 10 : 12}
117
- height={size === 'sm' ? 10 : 12}
118
- viewBox="0 0 12 12"
119
- fill="none"
120
- >
121
- <path
122
- d="M3 3l6 6M9 3l-6 6"
123
- stroke="currentColor"
124
- strokeWidth={1.5}
125
- strokeLinecap="round"
126
- />
127
- </svg>
96
+ <RemoveGlyph
97
+ size={size === 'sm' ? SIZES_PX.glyphXs : SIZES_PX.glyphSm}
98
+ color={variantText[variant]}
99
+ />
128
100
  </html.button>
129
101
  </html.span>
130
102
  )}
@@ -110,6 +110,15 @@ export const styles = css.create({
110
110
  },
111
111
  outlineWidth: 0,
112
112
  },
113
+ // RSD on native lays out only display: flex, not the web's inline-flex.
114
+ nativeFlex: {
115
+ display: 'flex',
116
+ flexDirection: 'row',
117
+ alignSelf: 'flex-start',
118
+ },
119
+ nativeButtonFlex: {
120
+ display: 'flex',
121
+ },
113
122
  removeButtonDisabled: {
114
123
  cursor: 'not-allowed',
115
124
  opacity: 0.4,
@@ -0,0 +1,43 @@
1
+ import type {ReactNode} from 'react'
2
+ import type {ColorToken} from '@duro-app/tokens/keys'
3
+
4
+ export type TagVariant = 'default' | 'success' | 'warning' | 'error' | 'info'
5
+
6
+ /** Each variant's text colour, which the remove cross draws in. */
7
+ export const variantText = {
8
+ default: 'text',
9
+ success: 'successText',
10
+ warning: 'warningText',
11
+ error: 'errorText',
12
+ info: 'infoText',
13
+ } as const satisfies Record<TagVariant, ColorToken>
14
+
15
+ export type TagSize = 'sm' | 'md'
16
+
17
+ export interface TagProps {
18
+ /** Value identifier — required when used inside TagGroup */
19
+ value?: string
20
+ variant?: TagVariant
21
+ size?: TagSize
22
+ /** Show remove button. Auto-derived from TagGroup context when inside one. */
23
+ removable?: boolean
24
+ /** Called when remove button is clicked. Auto-wired to TagGroup when inside one. */
25
+ onRemove?: () => void
26
+ disabled?: boolean
27
+ /**
28
+ * Allow the label to wrap / break instead of staying on one line. Tags are
29
+ * `white-space: nowrap` by default (they read as pills); set this for long
30
+ * identifier-style values (e.g. `lldap.group.member.remove`) sitting in a
31
+ * constrained column, so they break within their cell instead of leaking
32
+ * into the next one.
33
+ */
34
+ wrap?: boolean
35
+ children: ReactNode
36
+ }
37
+
38
+ export interface RemoveGlyphProps {
39
+ /** px, from the glyph size tokens */
40
+ size: number
41
+ /** Native only: the web inherits the tag's text colour. */
42
+ color: ColorToken
43
+ }
@@ -8,6 +8,8 @@ import {borders} from '@duro-app/tokens/tokens/borders.css'
8
8
  export const styles = css.create({
9
9
  base: {
10
10
  width: '100%',
11
+ // Native has no border-box reset (the web's global-reset.css); declare it.
12
+ boxSizing: 'border-box',
11
13
  paddingTop: spacing.sm,
12
14
  paddingBottom: spacing.sm,
13
15
  paddingLeft: spacing.md,
@@ -1,13 +1,23 @@
1
1
  // Native (React Native / Expo) ThemeProvider — Metro resolves this over
2
2
  // ThemeProvider.tsx via the platform extension. The web version uses
3
3
  // `display:contents` + a fixed portal-mount, neither of which exists on RN, so
4
- // this applies the theme StyleX styles to a plain flex-column container and
5
- // drops the portal mount (native overlays don't portal to a fixed div).
6
- import {createContext, type ReactNode, useContext} from 'react'
4
+ // this applies the theme to a plain flex-column container, and passive
5
+ // overlays (toasts) draw through the host registry instead (plan N0,
6
+ // Decision 2).
7
+ import {createContext, type ReactNode, useContext, useMemo, useState} from 'react'
7
8
  import {css, html} from 'react-strict-dom'
9
+ import type {ColorToken} from '@duro-app/tokens/keys'
10
+ import {darkColors, highContrastColors, lightColors, type RawColors} from '@duro-app/tokens/raw'
8
11
  import {colors} from '@duro-app/tokens/tokens/colors.css'
9
12
  import {lightTheme, lightShadows} from '@duro-app/tokens/themes/light.css'
10
13
  import {highContrastTheme, highContrastShadows} from '@duro-app/tokens/themes/high-contrast.css'
14
+ import {createHostRegistry, type HostRegistry} from '../../shared/portal/hostRegistry'
15
+ import {HostRegistryContext, PortalHost} from '../../shared/portal/PortalHost'
16
+ import {
17
+ ResolvedColorsContext,
18
+ ThemeScopeContext,
19
+ type ThemeScope,
20
+ } from '../../shared/portal/themeScope'
11
21
 
12
22
  export type ThemeName = 'dark' | 'light' | 'high-contrast'
13
23
 
@@ -16,34 +26,84 @@ interface ThemeProviderProps {
16
26
  children: ReactNode
17
27
  }
18
28
 
19
- const themeMap: Partial<Record<ThemeName, readonly [typeof lightTheme, typeof lightShadows]>> = {
29
+ // On the web, `dark` is the base vars and needs no theme. On native,
30
+ // createTheme is a runtime call, so dark gets an explicit one: a dark
31
+ // provider nested in a light one then draws dark, and its resolved colours
32
+ // (below) agree with what RSD draws.
33
+ const darkTheme = css.createTheme(colors, darkColors)
34
+
35
+ const themeMap: Record<ThemeName, readonly unknown[]> = {
36
+ dark: [darkTheme],
20
37
  light: [lightTheme, lightShadows],
21
38
  'high-contrast': [highContrastTheme, highContrastShadows],
22
39
  }
23
40
 
41
+ const palettes: Record<ThemeName, RawColors> = {
42
+ dark: darkColors,
43
+ light: lightColors,
44
+ 'high-contrast': highContrastColors,
45
+ }
46
+
24
47
  const styles = css.create({
25
48
  // No `display:contents` on RN. RSD uses web flex semantics on native, so
26
- // declare flex + column explicitly; flex:1 lets a root provider fill.
49
+ // declare flex + column explicitly.
27
50
  root: {
28
51
  display: 'flex',
29
52
  flexDirection: 'column',
30
- flex: 1,
31
53
  color: colors.text,
32
54
  },
55
+ // The app canvas: the web paints it on body from vars.css; native has no
56
+ // body, so the outermost provider paints it and fills its host (height,
57
+ // not flex: the host is an RN view, which RSD does not know is flex).
58
+ canvas: {
59
+ backgroundColor: colors.bg,
60
+ height: '100%',
61
+ },
33
62
  })
34
63
 
35
64
  type DivStyle = Parameters<typeof html.div>[0]['style']
36
65
 
37
66
  // Parity with the web export — there's no portal mount on native, so this is
38
- // always null. (The components that consume it aren't in the native barrel.)
67
+ // always null.
39
68
  const PortalMountContext = createContext<HTMLElement | null>(null)
40
69
 
41
70
  export function usePortalMount(): HTMLElement | null {
42
71
  return useContext(PortalMountContext)
43
72
  }
44
73
 
74
+ /**
75
+ * The nearest ThemeProvider's colours as real colour strings, for native hosts
76
+ * that cannot read a `var(--…)` reference: an Icon renderer, an animated
77
+ * backdrop, a Pressable. RSD styles keep using the `colors` tokens.
78
+ */
79
+ export function useResolvedColors(): Record<ColorToken, string> {
80
+ return useContext(ResolvedColorsContext)
81
+ }
82
+
45
83
  export function ThemeProvider({theme = 'dark', children}: ThemeProviderProps) {
46
- const overrides = themeMap[theme]
47
- const themeStyles = [overrides?.[0], overrides?.[1], styles.root] as DivStyle
48
- return <html.div style={themeStyles}>{children}</html.div>
84
+ const outerRegistry = useContext(HostRegistryContext)
85
+ const outerScope = useContext(ThemeScopeContext)
86
+ // The outermost provider owns the registry and draws the root host.
87
+ const [ownRegistry] = useState<HostRegistry | null>(() =>
88
+ outerRegistry ? null : createHostRegistry(),
89
+ )
90
+ const registry = outerRegistry ?? ownRegistry
91
+ const themeStyles = themeMap[theme]
92
+ const scope = useMemo<ThemeScope>(
93
+ () => ({styles: [...outerScope.styles, ...themeStyles]}),
94
+ [outerScope, themeStyles],
95
+ )
96
+
97
+ return (
98
+ <HostRegistryContext.Provider value={registry}>
99
+ <ThemeScopeContext.Provider value={scope}>
100
+ <ResolvedColorsContext.Provider value={palettes[theme]}>
101
+ <html.div style={[...themeStyles, styles.root, ownRegistry && styles.canvas] as DivStyle}>
102
+ {children}
103
+ {ownRegistry ? <PortalHost placement="base" /> : null}
104
+ </html.div>
105
+ </ResolvedColorsContext.Provider>
106
+ </ThemeScopeContext.Provider>
107
+ </HostRegistryContext.Provider>
108
+ )
49
109
  }
@@ -2,7 +2,7 @@ import type {ComponentMeta} from '../component-meta'
2
2
 
3
3
  export const meta: ComponentMeta = {
4
4
  description:
5
- 'Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it).',
5
+ 'Transient, self-dismissing notification for the RESULT of an action (saved, sent, revoked, failed). Mount <ToastProvider> once at the app root and call useToast().toast() from anywhere. Toasts stack and portal above dialogs. Variants: success, info, warning, error. The countdown pauses while a toast is hovered or holds focus, and every toast has a close button (`dismissLabel` on the provider names it). On React Native the same API draws through the portal host above every open overlay and the keyboard, as a card with a status-coloured edge, its action and its close in one row; a sideways swipe dismisses, a press holds the countdown, and so does an active screen reader (WCAG 2.2.1).',
6
6
  whenToUse: [
7
7
  'Confirming an action succeeded (e.g. "Grant revoked")',
8
8
  'Reporting an action failed, with the reason',
@@ -0,0 +1,226 @@
1
+ import {
2
+ createContext,
3
+ type ReactNode,
4
+ useCallback,
5
+ useContext,
6
+ useEffect,
7
+ useMemo,
8
+ useRef,
9
+ useState,
10
+ useSyncExternalStore,
11
+ } from 'react'
12
+ import {
13
+ AccessibilityInfo,
14
+ type LayoutChangeEvent,
15
+ Platform,
16
+ Pressable,
17
+ StyleSheet,
18
+ } from 'react-native'
19
+ import {Gesture, GestureDetector} from 'react-native-gesture-handler'
20
+ import Animated, {
21
+ Easing,
22
+ useAnimatedStyle,
23
+ useSharedValue,
24
+ withTiming,
25
+ } from 'react-native-reanimated'
26
+ import {scheduleOnRN} from 'react-native-worklets'
27
+ import {html} from 'react-strict-dom'
28
+ import {DURATION_MS, LAYERS, SPACING_PX} from '@duro-app/tokens/keys'
29
+ import {Icon} from '../Icon/Icon'
30
+ import {Portal} from '../../shared/portal/PortalHost'
31
+ import {useSafeAreaInsets} from '../../shared/safeArea'
32
+ import {useKeyboardHeight} from '../../shared/useKeyboardHeight'
33
+ import {useScreenReaderEnabled} from '../../shared/useScreenReaderEnabled'
34
+ import {DISMISS_DURATION, DRAG_SLOP, SNAP_DURATION, shouldDismissEdge} from '../Drawer/sheetPhysics'
35
+ import {nativeStyles} from './native.css'
36
+ import {createToastStore, type ToastContextValue, type ToastEntry, type ToastStore} from './store'
37
+
38
+ export type {ToastAction, ToastContextValue, ToastOptions, ToastVariant} from './store'
39
+
40
+ const ToastContext = createContext<ToastContextValue | null>(null)
41
+
42
+ export function useToast(): ToastContextValue {
43
+ const ctx = useContext(ToastContext)
44
+ if (!ctx) throw new Error('useToast must be used within a <ToastProvider>')
45
+ return ctx
46
+ }
47
+
48
+ const easeOut = Easing.out(Easing.cubic)
49
+
50
+ function ToastItem({
51
+ entry,
52
+ store,
53
+ onDismiss,
54
+ dismissLabel,
55
+ screenReader,
56
+ }: {
57
+ entry: ToastEntry
58
+ store: ToastStore
59
+ onDismiss: (id: string) => void
60
+ dismissLabel: string
61
+ screenReader: boolean
62
+ }) {
63
+ const {id, variant, message, action, duration} = entry
64
+ const [pressed, setPressed] = useState(false)
65
+ // A press holds the toast as hover does on the web; a screen reader holds
66
+ // it for as long as it is on (WCAG 2.2.1).
67
+ const paused = pressed || screenReader
68
+
69
+ // The store keeps what is left, so a toast that moves to another host
70
+ // (a Dialog above it closed) carries on where it was.
71
+ useEffect(() => {
72
+ if (duration <= 0 || paused) return
73
+ const started = Date.now()
74
+ const timer = setTimeout(() => onDismiss(id), Math.max(store.remaining(id), 0))
75
+ return () => {
76
+ clearTimeout(timer)
77
+ store.spend(id, Date.now() - started)
78
+ }
79
+ }, [id, duration, paused, onDismiss, store])
80
+
81
+ // iOS has no live regions: announce the message once. Android reads the
82
+ // live region itself, so announcing there too would read it twice.
83
+ const announced = useRef(false)
84
+ useEffect(() => {
85
+ if (Platform.OS !== 'ios' || announced.current || typeof message !== 'string') return
86
+ announced.current = true
87
+ AccessibilityInfo.announceForAccessibility(message)
88
+ }, [message])
89
+
90
+ const x = useSharedValue(0)
91
+ const shown = useSharedValue(0)
92
+ const width = useSharedValue(1)
93
+ useEffect(() => {
94
+ shown.value = withTiming(1, {duration: DURATION_MS.brisk, easing: easeOut})
95
+ }, [shown])
96
+
97
+ const pan = useMemo(
98
+ () =>
99
+ Gesture.Pan()
100
+ .activeOffsetX([-DRAG_SLOP, DRAG_SLOP])
101
+ .failOffsetY([-DRAG_SLOP, DRAG_SLOP])
102
+ .onUpdate((e) => {
103
+ x.value = e.translationX
104
+ })
105
+ .onEnd((e) => {
106
+ const distance = Math.abs(e.translationX)
107
+ if (shouldDismissEdge(distance, width.value, Math.abs(e.velocityX) / 1000)) {
108
+ const off = Math.sign(e.translationX || e.velocityX) * width.value
109
+ x.value = withTiming(off, {duration: DISMISS_DURATION}, (done) => {
110
+ if (done) scheduleOnRN(onDismiss, id)
111
+ })
112
+ } else {
113
+ x.value = withTiming(0, {duration: SNAP_DURATION, easing: easeOut})
114
+ }
115
+ }),
116
+ [id, onDismiss, x, width],
117
+ )
118
+
119
+ const motion = useAnimatedStyle(() => ({
120
+ opacity: shown.value * (1 - Math.min(1, Math.abs(x.value) / width.value)),
121
+ transform: [{translateX: x.value}, {translateY: (1 - shown.value) * SPACING_PX.sm}],
122
+ }))
123
+
124
+ const onLayout = (e: LayoutChangeEvent) => {
125
+ width.value = e.nativeEvent.layout.width
126
+ }
127
+
128
+ return (
129
+ <GestureDetector gesture={pan}>
130
+ <Animated.View onLayout={onLayout} style={[layout.row, motion]}>
131
+ <Pressable
132
+ onPressIn={() => setPressed(true)}
133
+ onPressOut={() => setPressed(false)}
134
+ accessible={false}
135
+ style={layout.row}
136
+ >
137
+ <html.div
138
+ role={variant === 'error' ? 'alert' : 'status'}
139
+ aria-live={variant === 'error' ? 'assertive' : 'polite'}
140
+ style={[nativeStyles.toast, nativeStyles[variant]]}
141
+ >
142
+ <html.div style={nativeStyles.message}>{message}</html.div>
143
+ {action ? (
144
+ <html.button
145
+ type="button"
146
+ style={nativeStyles.action}
147
+ onClick={() => {
148
+ action.onClick()
149
+ onDismiss(id)
150
+ }}
151
+ >
152
+ {action.label}
153
+ </html.button>
154
+ ) : null}
155
+ <html.button
156
+ type="button"
157
+ aria-label={dismissLabel}
158
+ style={nativeStyles.close}
159
+ onClick={() => onDismiss(id)}
160
+ >
161
+ <Icon name="x" size="sm" color="textMuted" />
162
+ </html.button>
163
+ </html.div>
164
+ </Pressable>
165
+ </Animated.View>
166
+ </GestureDetector>
167
+ )
168
+ }
169
+
170
+ // Layout-only: each toast row spans the region and centres its card.
171
+ const layout = StyleSheet.create({row: {width: '100%', alignItems: 'center'}})
172
+
173
+ export interface ToastProviderProps {
174
+ /** Accessible name of each toast's close button, e.g. from the app's message catalog. */
175
+ dismissLabel?: string
176
+ children: ReactNode
177
+ }
178
+
179
+ /**
180
+ * Native ToastProvider (plan N0, Slice B): the same store and countdown as
181
+ * the web, drawn through the portal host at `LAYERS.toast`, so a toast shows
182
+ * above the app, every open Duro overlay (a sheet included) and the
183
+ * keyboard. A sideways swipe or the close button dismisses it.
184
+ */
185
+ export function ToastProvider({dismissLabel = 'Dismiss', children}: ToastProviderProps) {
186
+ const storeRef = useRef<ToastStore>(null)
187
+ if (storeRef.current === null) storeRef.current = createToastStore()
188
+ const store = storeRef.current
189
+ const entries = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot)
190
+ const insets = useSafeAreaInsets()
191
+ const keyboard = useKeyboardHeight()
192
+ const screenReader = useScreenReaderEnabled()
193
+
194
+ const value = useMemo<ToastContextValue>(
195
+ () => ({toast: (opts) => store.add(opts), dismiss: (id) => store.remove(id)}),
196
+ [store],
197
+ )
198
+ const dismiss = useCallback((id: string) => store.remove(id), [store])
199
+ const bottom = Math.max(insets.bottom, keyboard) + SPACING_PX.md
200
+
201
+ return (
202
+ <ToastContext.Provider value={value}>
203
+ {children}
204
+ {entries.length > 0 && (
205
+ <Portal layer={LAYERS.toast}>
206
+ <html.div
207
+ role="region"
208
+ aria-label="Notifications"
209
+ style={[nativeStyles.region, nativeStyles.regionBottom(bottom)]}
210
+ >
211
+ {entries.map((entry) => (
212
+ <ToastItem
213
+ key={entry.id}
214
+ entry={entry}
215
+ store={store}
216
+ onDismiss={dismiss}
217
+ dismissLabel={dismissLabel}
218
+ screenReader={screenReader}
219
+ />
220
+ ))}
221
+ </html.div>
222
+ </Portal>
223
+ )}
224
+ </ToastContext.Provider>
225
+ )
226
+ }