@delacour/react-native-ui 0.1.0-alpha.4 → 0.2.0-alpha.20261010061731

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 (493) hide show
  1. package/README.md +10 -6
  2. package/package.json +49 -5
  3. package/src/components/accordion/AGENTS.md +21 -9
  4. package/src/components/accordion/accordion-content.tsx +7 -4
  5. package/src/components/accordion/accordion-item.tsx +15 -13
  6. package/src/components/accordion/accordion.context.tsx +11 -5
  7. package/src/components/accordion/accordion.variants.test.ts +64 -3
  8. package/src/components/accordion/accordion.variants.ts +33 -9
  9. package/src/components/alert/AGENTS.md +102 -0
  10. package/src/components/alert/alert-action.tsx +17 -0
  11. package/src/components/alert/alert-close-button.tsx +50 -0
  12. package/src/components/alert/alert-content.tsx +15 -0
  13. package/src/components/alert/alert-description.tsx +16 -0
  14. package/src/components/alert/alert-glyphs.ts +25 -0
  15. package/src/components/alert/alert-indicator.tsx +40 -0
  16. package/src/components/alert/alert-title.tsx +18 -0
  17. package/src/components/alert/alert.context.tsx +69 -0
  18. package/src/components/alert/alert.tsx +160 -0
  19. package/src/components/alert/alert.types.ts +10 -0
  20. package/src/components/alert/alert.variants.test.ts +218 -0
  21. package/src/components/alert/alert.variants.ts +180 -0
  22. package/src/components/alert/index.ts +23 -0
  23. package/src/components/avatar/AGENTS.md +106 -0
  24. package/src/components/avatar/avatar-badge.tsx +51 -0
  25. package/src/components/avatar/avatar-group.tsx +139 -0
  26. package/src/components/avatar/avatar.context.tsx +93 -0
  27. package/src/components/avatar/avatar.tsx +201 -0
  28. package/src/components/avatar/avatar.utils.test.ts +174 -0
  29. package/src/components/avatar/avatar.utils.ts +141 -0
  30. package/src/components/avatar/avatar.variants.test.ts +251 -0
  31. package/src/components/avatar/avatar.variants.ts +270 -0
  32. package/src/components/avatar/index.ts +39 -0
  33. package/src/components/badge/AGENTS.md +10 -0
  34. package/src/components/badge/badge-close-button.tsx +2 -2
  35. package/src/components/badge/badge.context.tsx +2 -1
  36. package/src/components/badge/badge.tsx +13 -4
  37. package/src/components/badge/badge.variants.test.ts +26 -0
  38. package/src/components/badge/badge.variants.ts +21 -0
  39. package/src/components/bottom-sheet/AGENTS.md +186 -231
  40. package/src/components/bottom-sheet/bottom-sheet-background.tsx +31 -22
  41. package/src/components/bottom-sheet/bottom-sheet-close.tsx +23 -30
  42. package/src/components/bottom-sheet/bottom-sheet-container.tsx +54 -208
  43. package/src/components/bottom-sheet/bottom-sheet-content.tsx +25 -48
  44. package/src/components/bottom-sheet/bottom-sheet-description.tsx +8 -2
  45. package/src/components/bottom-sheet/bottom-sheet-flat-list.tsx +65 -0
  46. package/src/components/bottom-sheet/bottom-sheet-footer.tsx +53 -122
  47. package/src/components/bottom-sheet/bottom-sheet-handle.tsx +31 -26
  48. package/src/components/bottom-sheet/bottom-sheet-legend-list.tsx +69 -0
  49. package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +25 -147
  50. package/src/components/bottom-sheet/bottom-sheet-provider.tsx +42 -0
  51. package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +38 -62
  52. package/src/components/bottom-sheet/bottom-sheet-section-list.tsx +58 -0
  53. package/src/components/bottom-sheet/bottom-sheet-steps.tsx +88 -0
  54. package/src/components/bottom-sheet/bottom-sheet-text-input.tsx +44 -0
  55. package/src/components/bottom-sheet/bottom-sheet-title.tsx +12 -8
  56. package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +19 -37
  57. package/src/components/bottom-sheet/bottom-sheet.tsx +113 -60
  58. package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +47 -87
  59. package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -126
  60. package/src/components/bottom-sheet/index.ts +39 -17
  61. package/src/components/bottom-sheet/use-bottom-sheet-input.ts +16 -115
  62. package/src/components/button/AGENTS.md +51 -11
  63. package/src/components/button/button-group.tsx +44 -8
  64. package/src/components/button/button.context.tsx +10 -0
  65. package/src/components/button/button.tsx +7 -1
  66. package/src/components/button/button.variants.test.ts +190 -0
  67. package/src/components/button/button.variants.ts +87 -6
  68. package/src/components/button/index.ts +2 -0
  69. package/src/components/calendar/AGENTS.md +142 -0
  70. package/src/components/calendar/calendar-caption.tsx +116 -0
  71. package/src/components/calendar/calendar-day.tsx +103 -0
  72. package/src/components/calendar/calendar-grid.tsx +183 -0
  73. package/src/components/calendar/calendar-header.tsx +34 -0
  74. package/src/components/calendar/calendar-nav.tsx +51 -0
  75. package/src/components/calendar/calendar-picker.tsx +128 -0
  76. package/src/components/calendar/calendar-weekdays.tsx +48 -0
  77. package/src/components/calendar/calendar.context.tsx +108 -0
  78. package/src/components/calendar/calendar.date.test.ts +456 -0
  79. package/src/components/calendar/calendar.date.ts +584 -0
  80. package/src/components/calendar/calendar.tsx +389 -0
  81. package/src/components/calendar/calendar.types.ts +87 -0
  82. package/src/components/calendar/calendar.variants.test.ts +363 -0
  83. package/src/components/calendar/calendar.variants.ts +426 -0
  84. package/src/components/calendar/index.ts +96 -0
  85. package/src/components/card/AGENTS.md +92 -0
  86. package/src/components/card/card-action.tsx +16 -0
  87. package/src/components/card/card-content.tsx +12 -0
  88. package/src/components/card/card-description.tsx +12 -0
  89. package/src/components/card/card-footer.tsx +30 -0
  90. package/src/components/card/card-header.tsx +45 -0
  91. package/src/components/card/card-title.tsx +25 -0
  92. package/src/components/card/card.context.tsx +64 -0
  93. package/src/components/card/card.tsx +100 -0
  94. package/src/components/card/card.types.ts +13 -0
  95. package/src/components/card/card.variants.test.ts +240 -0
  96. package/src/components/card/card.variants.ts +177 -0
  97. package/src/components/card/index.ts +15 -0
  98. package/src/components/chart/AGENTS.md +8 -0
  99. package/src/components/chart/chart.tsx +8 -1
  100. package/src/components/chip/AGENTS.md +113 -0
  101. package/src/components/chip/chip-close-button.tsx +48 -0
  102. package/src/components/chip/chip-end-content.tsx +15 -0
  103. package/src/components/chip/chip-label.tsx +21 -0
  104. package/src/components/chip/chip-start-content.tsx +16 -0
  105. package/src/components/chip/chip.context.tsx +66 -0
  106. package/src/components/chip/chip.tsx +268 -0
  107. package/src/components/chip/chip.types.ts +10 -0
  108. package/src/components/chip/chip.variants.test.ts +335 -0
  109. package/src/components/chip/chip.variants.ts +321 -0
  110. package/src/components/chip/index.ts +24 -0
  111. package/src/components/collapsible/AGENTS.md +97 -0
  112. package/src/components/collapsible/collapsible-content.tsx +97 -0
  113. package/src/components/collapsible/collapsible-description.tsx +17 -0
  114. package/src/components/collapsible/collapsible-indicator.tsx +92 -0
  115. package/src/components/collapsible/collapsible-title.tsx +17 -0
  116. package/src/components/collapsible/collapsible-trigger.tsx +133 -0
  117. package/src/components/collapsible/collapsible.context.tsx +91 -0
  118. package/src/components/collapsible/collapsible.tsx +167 -0
  119. package/src/components/collapsible/collapsible.types.ts +9 -0
  120. package/src/components/collapsible/collapsible.variants.test.ts +338 -0
  121. package/src/components/collapsible/collapsible.variants.ts +228 -0
  122. package/src/components/collapsible/index.ts +32 -0
  123. package/src/components/context-menu/AGENTS.md +103 -0
  124. package/src/components/context-menu/context-menu-content.tsx +64 -0
  125. package/src/components/context-menu/context-menu-item.tsx +22 -0
  126. package/src/components/context-menu/context-menu-preview.tsx +60 -0
  127. package/src/components/context-menu/context-menu-trigger.tsx +160 -0
  128. package/src/components/context-menu/context-menu.context.tsx +91 -0
  129. package/src/components/context-menu/context-menu.tsx +90 -0
  130. package/src/components/context-menu/context-menu.variants.test.ts +266 -0
  131. package/src/components/context-menu/context-menu.variants.ts +147 -0
  132. package/src/components/context-menu/index.ts +18 -0
  133. package/src/components/dialog/AGENTS.md +97 -0
  134. package/src/components/dialog/dialog-body.tsx +21 -0
  135. package/src/components/dialog/dialog-close.tsx +82 -0
  136. package/src/components/dialog/dialog-content.tsx +153 -0
  137. package/src/components/dialog/dialog-description.tsx +23 -0
  138. package/src/components/dialog/dialog-footer.tsx +32 -0
  139. package/src/components/dialog/dialog-header.tsx +20 -0
  140. package/src/components/dialog/dialog-title.tsx +42 -0
  141. package/src/components/dialog/dialog-trigger.tsx +54 -0
  142. package/src/components/dialog/dialog.context.tsx +90 -0
  143. package/src/components/dialog/dialog.tsx +119 -0
  144. package/src/components/dialog/dialog.variants.test.ts +224 -0
  145. package/src/components/dialog/dialog.variants.ts +173 -0
  146. package/src/components/dialog/index.ts +30 -0
  147. package/src/components/drawer/AGENTS.md +123 -0
  148. package/src/components/drawer/drawer-body.tsx +46 -0
  149. package/src/components/drawer/drawer-close.tsx +76 -0
  150. package/src/components/drawer/drawer-content.tsx +186 -0
  151. package/src/components/drawer/drawer-description.tsx +23 -0
  152. package/src/components/drawer/drawer-footer.tsx +21 -0
  153. package/src/components/drawer/drawer-header.tsx +42 -0
  154. package/src/components/drawer/drawer-title.tsx +35 -0
  155. package/src/components/drawer/drawer-trigger.tsx +54 -0
  156. package/src/components/drawer/drawer.context.tsx +114 -0
  157. package/src/components/drawer/drawer.tsx +119 -0
  158. package/src/components/drawer/drawer.variants.test.ts +392 -0
  159. package/src/components/drawer/drawer.variants.ts +244 -0
  160. package/src/components/drawer/index.ts +41 -0
  161. package/src/components/drawer/use-drawer-pan.ts +108 -0
  162. package/src/components/empty-state/AGENTS.md +90 -0
  163. package/src/components/empty-state/empty-state-content.tsx +17 -0
  164. package/src/components/empty-state/empty-state-description.tsx +12 -0
  165. package/src/components/empty-state/empty-state-header.tsx +17 -0
  166. package/src/components/empty-state/empty-state-media.tsx +56 -0
  167. package/src/components/empty-state/empty-state-title.tsx +18 -0
  168. package/src/components/empty-state/empty-state.context.tsx +65 -0
  169. package/src/components/empty-state/empty-state.tsx +84 -0
  170. package/src/components/empty-state/empty-state.types.ts +14 -0
  171. package/src/components/empty-state/empty-state.variants.test.ts +245 -0
  172. package/src/components/empty-state/empty-state.variants.ts +132 -0
  173. package/src/components/empty-state/index.ts +20 -0
  174. package/src/components/fab/AGENTS.md +119 -0
  175. package/src/components/fab/fab-action.tsx +118 -0
  176. package/src/components/fab/fab-group.tsx +214 -0
  177. package/src/components/fab/fab-label.tsx +18 -0
  178. package/src/components/fab/fab.context.tsx +125 -0
  179. package/src/components/fab/fab.tsx +156 -0
  180. package/src/components/fab/fab.types.ts +17 -0
  181. package/src/components/fab/fab.variants.test.ts +274 -0
  182. package/src/components/fab/fab.variants.ts +234 -0
  183. package/src/components/fab/index.ts +37 -0
  184. package/src/components/feedback/AGENTS.md +95 -0
  185. package/src/components/feedback/feedback-action.tsx +16 -0
  186. package/src/components/feedback/feedback-cancel.tsx +34 -0
  187. package/src/components/feedback/feedback-close.tsx +21 -0
  188. package/src/components/feedback/feedback-content.tsx +24 -0
  189. package/src/components/feedback/feedback-field.tsx +70 -0
  190. package/src/components/feedback/feedback-footer.tsx +21 -0
  191. package/src/components/feedback/feedback-panel.tsx +54 -0
  192. package/src/components/feedback/feedback-submit.tsx +74 -0
  193. package/src/components/feedback/feedback-title.tsx +34 -0
  194. package/src/components/feedback/feedback-trigger.tsx +20 -0
  195. package/src/components/feedback/feedback.context.tsx +73 -0
  196. package/src/components/feedback/feedback.tsx +135 -0
  197. package/src/components/feedback/feedback.variants.test.ts +185 -0
  198. package/src/components/feedback/feedback.variants.ts +128 -0
  199. package/src/components/feedback/index.ts +24 -0
  200. package/src/components/field/AGENTS.md +19 -0
  201. package/src/components/field/field-label.tsx +19 -5
  202. package/src/components/field/field.context.tsx +17 -0
  203. package/src/components/field/field.tsx +15 -2
  204. package/src/components/field/field.variants.test.ts +24 -0
  205. package/src/components/field/field.variants.ts +20 -0
  206. package/src/components/field/index.ts +1 -0
  207. package/src/components/input/AGENTS.md +5 -0
  208. package/src/components/input/input.variants.test.ts +32 -0
  209. package/src/components/input/input.variants.ts +13 -1
  210. package/src/components/item/AGENTS.md +92 -0
  211. package/src/components/item/index.ts +25 -0
  212. package/src/components/item/item-actions.tsx +28 -0
  213. package/src/components/item/item-content.tsx +15 -0
  214. package/src/components/item/item-description.tsx +12 -0
  215. package/src/components/item/item-footer.tsx +13 -0
  216. package/src/components/item/item-group.tsx +32 -0
  217. package/src/components/item/item-header.tsx +13 -0
  218. package/src/components/item/item-media.tsx +37 -0
  219. package/src/components/item/item-separator.tsx +13 -0
  220. package/src/components/item/item-title.tsx +17 -0
  221. package/src/components/item/item.context.tsx +66 -0
  222. package/src/components/item/item.tsx +218 -0
  223. package/src/components/item/item.types.ts +12 -0
  224. package/src/components/item/item.variants.test.ts +263 -0
  225. package/src/components/item/item.variants.ts +200 -0
  226. package/src/components/kpi/AGENTS.md +125 -0
  227. package/src/components/kpi/index.ts +45 -0
  228. package/src/components/kpi/kpi-action.tsx +12 -0
  229. package/src/components/kpi/kpi-content.tsx +35 -0
  230. package/src/components/kpi/kpi-footer.tsx +52 -0
  231. package/src/components/kpi/kpi-group.tsx +70 -0
  232. package/src/components/kpi/kpi-header.tsx +25 -0
  233. package/src/components/kpi/kpi-icon.tsx +28 -0
  234. package/src/components/kpi/kpi-sparkline.tsx +145 -0
  235. package/src/components/kpi/kpi-stat.tsx +20 -0
  236. package/src/components/kpi/kpi-title.tsx +19 -0
  237. package/src/components/kpi/kpi-trend.tsx +105 -0
  238. package/src/components/kpi/kpi-value.tsx +32 -0
  239. package/src/components/kpi/kpi.context.tsx +111 -0
  240. package/src/components/kpi/kpi.tsx +154 -0
  241. package/src/components/kpi/kpi.types.ts +9 -0
  242. package/src/components/kpi/kpi.variants.test.ts +428 -0
  243. package/src/components/kpi/kpi.variants.ts +347 -0
  244. package/src/components/label/AGENTS.md +89 -0
  245. package/src/components/label/index.ts +10 -0
  246. package/src/components/label/label.tsx +97 -0
  247. package/src/components/label/label.variants.test.ts +176 -0
  248. package/src/components/label/label.variants.ts +123 -0
  249. package/src/components/menu/AGENTS.md +176 -0
  250. package/src/components/menu/index.ts +44 -0
  251. package/src/components/menu/menu-background.tsx +17 -0
  252. package/src/components/menu/menu-checkbox-item.tsx +57 -0
  253. package/src/components/menu/menu-content.tsx +303 -0
  254. package/src/components/menu/menu-item.tsx +162 -0
  255. package/src/components/menu/menu-label.tsx +14 -0
  256. package/src/components/menu/menu-radio-group.tsx +50 -0
  257. package/src/components/menu/menu-radio-item.tsx +57 -0
  258. package/src/components/menu/menu-separator.tsx +11 -0
  259. package/src/components/menu/menu-sub-content.tsx +72 -0
  260. package/src/components/menu/menu-sub-trigger.tsx +50 -0
  261. package/src/components/menu/menu-sub.tsx +75 -0
  262. package/src/components/menu/menu-trigger.tsx +57 -0
  263. package/src/components/menu/menu.context.tsx +214 -0
  264. package/src/components/menu/menu.tsx +88 -0
  265. package/src/components/menu/menu.types.ts +33 -0
  266. package/src/components/menu/menu.variants.test.ts +400 -0
  267. package/src/components/menu/menu.variants.ts +353 -0
  268. package/src/components/meter/AGENTS.md +135 -0
  269. package/src/components/meter/index.ts +43 -0
  270. package/src/components/meter/meter-fill.tsx +19 -0
  271. package/src/components/meter/meter-header.tsx +17 -0
  272. package/src/components/meter/meter-label.tsx +18 -0
  273. package/src/components/meter/meter-output.tsx +42 -0
  274. package/src/components/meter/meter-segments.tsx +83 -0
  275. package/src/components/meter/meter-track.tsx +16 -0
  276. package/src/components/meter/meter.context.tsx +62 -0
  277. package/src/components/meter/meter.tsx +286 -0
  278. package/src/components/meter/meter.types.ts +30 -0
  279. package/src/components/meter/meter.variants.test.ts +430 -0
  280. package/src/components/meter/meter.variants.ts +347 -0
  281. package/src/components/overlay/AGENTS.md +89 -0
  282. package/src/components/overlay/index.ts +27 -0
  283. package/src/components/overlay/overlay-portal.tsx +84 -0
  284. package/src/components/overlay/overlay-presence.test.ts +46 -0
  285. package/src/components/overlay/overlay-presence.ts +49 -0
  286. package/src/components/overlay/overlay-registry.test.ts +96 -0
  287. package/src/components/overlay/overlay-registry.ts +99 -0
  288. package/src/components/overlay/overlay-scrim.tsx +49 -0
  289. package/src/components/overlay/overlay.context.tsx +48 -0
  290. package/src/components/overlay/overlay.tsx +118 -0
  291. package/src/components/overlay/overlay.variants.test.ts +37 -0
  292. package/src/components/overlay/overlay.variants.ts +33 -0
  293. package/src/components/overlay/use-overlay-back-handler.ts +34 -0
  294. package/src/components/overlay/use-overlay-presence.ts +87 -0
  295. package/src/components/popover/AGENTS.md +138 -0
  296. package/src/components/popover/index.ts +48 -0
  297. package/src/components/popover/popover-anchor.tsx +49 -0
  298. package/src/components/popover/popover-arrow.tsx +89 -0
  299. package/src/components/popover/popover-close.tsx +73 -0
  300. package/src/components/popover/popover-content.tsx +235 -0
  301. package/src/components/popover/popover-description.tsx +18 -0
  302. package/src/components/popover/popover-title.tsx +33 -0
  303. package/src/components/popover/popover-trigger.tsx +56 -0
  304. package/src/components/popover/popover.context.tsx +71 -0
  305. package/src/components/popover/popover.position.test.ts +354 -0
  306. package/src/components/popover/popover.position.ts +286 -0
  307. package/src/components/popover/popover.tsx +123 -0
  308. package/src/components/popover/popover.variants.test.ts +135 -0
  309. package/src/components/popover/popover.variants.ts +85 -0
  310. package/src/components/popover/use-anchor-measure.ts +76 -0
  311. package/src/components/popover/use-anchored-content.ts +219 -0
  312. package/src/components/pressable/AGENTS.md +11 -1
  313. package/src/components/pressable/pressable.tsx +7 -2
  314. package/src/components/pressable/pressable.variants.test.ts +29 -0
  315. package/src/components/pressable/pressable.variants.ts +17 -0
  316. package/src/components/progress/AGENTS.md +120 -0
  317. package/src/components/progress/index.ts +40 -0
  318. package/src/components/progress/progress-fill.tsx +152 -0
  319. package/src/components/progress/progress-header.tsx +19 -0
  320. package/src/components/progress/progress-label.tsx +27 -0
  321. package/src/components/progress/progress-output.tsx +47 -0
  322. package/src/components/progress/progress-track.tsx +38 -0
  323. package/src/components/progress/progress.context.tsx +79 -0
  324. package/src/components/progress/progress.tsx +171 -0
  325. package/src/components/progress/progress.types.ts +23 -0
  326. package/src/components/progress/progress.variants.test.ts +335 -0
  327. package/src/components/progress/progress.variants.ts +284 -0
  328. package/src/components/progress-button/AGENTS.md +116 -0
  329. package/src/components/progress-button/index.ts +36 -0
  330. package/src/components/progress-button/progress-button-done.tsx +33 -0
  331. package/src/components/progress-button/progress-button-label.tsx +27 -0
  332. package/src/components/progress-button/progress-button.context.tsx +93 -0
  333. package/src/components/progress-button/progress-button.tsx +414 -0
  334. package/src/components/progress-button/progress-button.variants.test.ts +314 -0
  335. package/src/components/progress-button/progress-button.variants.ts +236 -0
  336. package/src/components/provider/AGENTS.md +38 -25
  337. package/src/components/provider/provider.tsx +38 -11
  338. package/src/components/radio/AGENTS.md +8 -10
  339. package/src/components/radio/radio.variants.test.ts +5 -6
  340. package/src/components/radio/radio.variants.ts +6 -7
  341. package/src/components/rating/AGENTS.md +143 -0
  342. package/src/components/rating/index.ts +43 -0
  343. package/src/components/rating/rating-output.tsx +39 -0
  344. package/src/components/rating/rating-star.tsx +70 -0
  345. package/src/components/rating/rating-stars.tsx +219 -0
  346. package/src/components/rating/rating.context.tsx +79 -0
  347. package/src/components/rating/rating.tsx +163 -0
  348. package/src/components/rating/rating.variants.test.ts +347 -0
  349. package/src/components/rating/rating.variants.ts +354 -0
  350. package/src/components/selection-mode/AGENTS.md +155 -0
  351. package/src/components/selection-mode/index.ts +43 -0
  352. package/src/components/selection-mode/selection-mode-action.tsx +65 -0
  353. package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
  354. package/src/components/selection-mode/selection-mode-group.tsx +123 -0
  355. package/src/components/selection-mode/selection-mode-header.tsx +85 -0
  356. package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
  357. package/src/components/selection-mode/selection-mode-item.tsx +182 -0
  358. package/src/components/selection-mode/selection-mode.context.tsx +89 -0
  359. package/src/components/selection-mode/selection-mode.tsx +207 -0
  360. package/src/components/selection-mode/selection-mode.types.ts +36 -0
  361. package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
  362. package/src/components/selection-mode/selection-mode.variants.ts +329 -0
  363. package/src/components/skeleton/AGENTS.md +120 -0
  364. package/src/components/skeleton/index.ts +15 -0
  365. package/src/components/skeleton/skeleton-clock.ts +48 -0
  366. package/src/components/skeleton/skeleton-group.tsx +64 -0
  367. package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
  368. package/src/components/skeleton/skeleton.context.tsx +45 -0
  369. package/src/components/skeleton/skeleton.tsx +227 -0
  370. package/src/components/skeleton/skeleton.variants.test.ts +245 -0
  371. package/src/components/skeleton/skeleton.variants.ts +222 -0
  372. package/src/components/slide-button/AGENTS.md +122 -0
  373. package/src/components/slide-button/index.ts +43 -0
  374. package/src/components/slide-button/slide-button-label.tsx +70 -0
  375. package/src/components/slide-button/slide-button-thumb.tsx +93 -0
  376. package/src/components/slide-button/slide-button.context.tsx +76 -0
  377. package/src/components/slide-button/slide-button.tsx +457 -0
  378. package/src/components/slide-button/slide-button.variants.test.ts +288 -0
  379. package/src/components/slide-button/slide-button.variants.ts +324 -0
  380. package/src/components/slider/index.ts +2 -0
  381. package/src/components/slider/slider-thumb.tsx +33 -1
  382. package/src/components/slider/slider.variants.test.ts +52 -0
  383. package/src/components/slider/slider.variants.ts +56 -2
  384. package/src/components/stack-card/AGENTS.md +169 -0
  385. package/src/components/stack-card/index.ts +37 -0
  386. package/src/components/stack-card/stack-card-action.tsx +66 -0
  387. package/src/components/stack-card/stack-card-actions.tsx +18 -0
  388. package/src/components/stack-card/stack-card-card.tsx +71 -0
  389. package/src/components/stack-card/stack-card-empty.tsx +30 -0
  390. package/src/components/stack-card/stack-card-slot.tsx +90 -0
  391. package/src/components/stack-card/stack-card-stamp.tsx +76 -0
  392. package/src/components/stack-card/stack-card.context.tsx +117 -0
  393. package/src/components/stack-card/stack-card.tsx +557 -0
  394. package/src/components/stack-card/stack-card.types.ts +26 -0
  395. package/src/components/stack-card/stack-card.variants.test.ts +355 -0
  396. package/src/components/stack-card/stack-card.variants.ts +360 -0
  397. package/src/components/steps/AGENTS.md +153 -0
  398. package/src/components/steps/index.ts +45 -0
  399. package/src/components/steps/steps-connector.tsx +31 -0
  400. package/src/components/steps/steps-description.tsx +26 -0
  401. package/src/components/steps/steps-indicator.tsx +106 -0
  402. package/src/components/steps/steps-item.tsx +225 -0
  403. package/src/components/steps/steps-panel.tsx +41 -0
  404. package/src/components/steps/steps-title.tsx +32 -0
  405. package/src/components/steps/steps-track.tsx +59 -0
  406. package/src/components/steps/steps.context.tsx +123 -0
  407. package/src/components/steps/steps.tsx +166 -0
  408. package/src/components/steps/steps.variants.test.ts +388 -0
  409. package/src/components/steps/steps.variants.ts +468 -0
  410. package/src/components/surface/AGENTS.md +82 -0
  411. package/src/components/surface/index.ts +15 -0
  412. package/src/components/surface/surface.context.tsx +56 -0
  413. package/src/components/surface/surface.tsx +87 -0
  414. package/src/components/surface/surface.variants.test.ts +227 -0
  415. package/src/components/surface/surface.variants.ts +165 -0
  416. package/src/components/swipe/AGENTS.md +140 -0
  417. package/src/components/swipe/index.ts +31 -0
  418. package/src/components/swipe/swipe-action.tsx +99 -0
  419. package/src/components/swipe/swipe-end.tsx +14 -0
  420. package/src/components/swipe/swipe-group.tsx +55 -0
  421. package/src/components/swipe/swipe-panel.tsx +55 -0
  422. package/src/components/swipe/swipe-start.tsx +15 -0
  423. package/src/components/swipe/swipe.context.tsx +135 -0
  424. package/src/components/swipe/swipe.tsx +393 -0
  425. package/src/components/swipe/swipe.types.ts +7 -0
  426. package/src/components/swipe/swipe.variants.test.ts +337 -0
  427. package/src/components/swipe/swipe.variants.ts +290 -0
  428. package/src/components/switch/switch.variants.ts +2 -2
  429. package/src/components/tabs/AGENTS.md +4 -3
  430. package/src/components/tabs/tabs.variants.test.ts +1 -1
  431. package/src/components/tabs/tabs.variants.ts +4 -6
  432. package/src/components/text/AGENTS.md +8 -2
  433. package/src/components/text/text.tsx +7 -0
  434. package/src/components/text/text.variants.test.ts +24 -1
  435. package/src/components/text/text.variants.ts +5 -0
  436. package/src/components/textarea/AGENTS.md +89 -0
  437. package/src/components/textarea/index.ts +16 -0
  438. package/src/components/textarea/textarea.tsx +157 -0
  439. package/src/components/textarea/textarea.variants.test.ts +219 -0
  440. package/src/components/textarea/textarea.variants.ts +171 -0
  441. package/src/components/toast/AGENTS.md +140 -0
  442. package/src/components/toast/index.ts +74 -0
  443. package/src/components/toast/toast-action.tsx +59 -0
  444. package/src/components/toast/toast-api.ts +32 -0
  445. package/src/components/toast/toast-close.tsx +44 -0
  446. package/src/components/toast/toast-content.tsx +11 -0
  447. package/src/components/toast/toast-description.tsx +11 -0
  448. package/src/components/toast/toast-indicator.tsx +34 -0
  449. package/src/components/toast/toast-item.tsx +308 -0
  450. package/src/components/toast/toast-title.tsx +13 -0
  451. package/src/components/toast/toast-viewport.tsx +135 -0
  452. package/src/components/toast/toast.context.tsx +69 -0
  453. package/src/components/toast/toast.store.test.ts +259 -0
  454. package/src/components/toast/toast.store.ts +338 -0
  455. package/src/components/toast/toast.timer.test.ts +64 -0
  456. package/src/components/toast/toast.timer.ts +51 -0
  457. package/src/components/toast/toast.tsx +132 -0
  458. package/src/components/toast/toast.variants.test.ts +393 -0
  459. package/src/components/toast/toast.variants.ts +304 -0
  460. package/src/components/toggle-button/AGENTS.md +106 -0
  461. package/src/components/toggle-button/index.ts +30 -0
  462. package/src/components/toggle-button/toggle-button-group.tsx +156 -0
  463. package/src/components/toggle-button/toggle-button-label.tsx +19 -0
  464. package/src/components/toggle-button/toggle-button.context.tsx +130 -0
  465. package/src/components/toggle-button/toggle-button.tsx +172 -0
  466. package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
  467. package/src/components/toggle-button/toggle-button.variants.ts +160 -0
  468. package/src/components/tooltip/AGENTS.md +133 -0
  469. package/src/components/tooltip/index.ts +28 -0
  470. package/src/components/tooltip/tooltip-arrow.tsx +40 -0
  471. package/src/components/tooltip/tooltip-content.tsx +179 -0
  472. package/src/components/tooltip/tooltip-description.tsx +19 -0
  473. package/src/components/tooltip/tooltip-text.tsx +21 -0
  474. package/src/components/tooltip/tooltip-title.tsx +20 -0
  475. package/src/components/tooltip/tooltip-trigger.tsx +103 -0
  476. package/src/components/tooltip/tooltip.context.tsx +68 -0
  477. package/src/components/tooltip/tooltip.tsx +213 -0
  478. package/src/components/tooltip/tooltip.variants.test.ts +231 -0
  479. package/src/components/tooltip/tooltip.variants.ts +177 -0
  480. package/src/hooks/use-calm-motion.tsx +31 -0
  481. package/src/lib/calm-motion.test.ts +17 -0
  482. package/src/lib/calm-motion.ts +23 -0
  483. package/src/lib/cn.parity.test.ts +62 -0
  484. package/src/lib/cn.test.ts +14 -0
  485. package/src/lib/cn.ts +11 -6
  486. package/src/no-gorhom.test.ts +54 -0
  487. package/src/styles/geometry.test.ts +1 -0
  488. package/src/styles/theme.css +4 -0
  489. package/src/styles/tokens.css +44 -0
  490. package/src/styles/tokens.test.ts +56 -0
  491. package/src/styles/tokens.ts +29 -1
  492. package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
  493. package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
@@ -0,0 +1,308 @@
1
+ import { type ReactElement, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { AccessibilityInfo, type LayoutChangeEvent, Platform, View } from "react-native";
3
+ import { Gesture, GestureDetector } from "react-native-gesture-handler";
4
+ import Animated, {
5
+ Easing,
6
+ type SharedValue,
7
+ useAnimatedStyle,
8
+ useReducedMotion,
9
+ useSharedValue,
10
+ withDelay,
11
+ withSpring,
12
+ withTiming,
13
+ } from "react-native-reanimated";
14
+ import { scheduleOnRN, scheduleOnUI } from "react-native-worklets";
15
+ import { OVERLAY_MOTION } from "../overlay/overlay.variants";
16
+ import { playHaptic } from "../pressable";
17
+ import { Spinner } from "../spinner";
18
+ import { Toast } from "./toast";
19
+ import { ToastItemProvider } from "./toast.context";
20
+ import type { ToastHandle, ToastItem, ToastPlacement, ToastStore } from "./toast.store";
21
+ import {
22
+ createToastTimer,
23
+ pauseToastTimer,
24
+ startToastTimer,
25
+ type ToastTimer,
26
+ toastTimerRemaining,
27
+ } from "./toast.timer";
28
+ import {
29
+ resolveToastAnnouncement,
30
+ resolveToastDepthStyle,
31
+ resolveToastDrag,
32
+ resolveToastDuration,
33
+ resolveToastEnterDelay,
34
+ resolveToastHaptic,
35
+ resolveToastInterrupts,
36
+ resolveToastRelease,
37
+ resolveToastStackedHeight,
38
+ TOAST_ENTER_DISTANCE,
39
+ toastVariants,
40
+ } from "./toast.variants";
41
+
42
+ const ENTER_EASING = Easing.out(Easing.cubic);
43
+ const EXIT_EASING = Easing.in(Easing.cubic);
44
+ const SETTLE_SPRING = { damping: 20, stiffness: 260, mass: 0.6 } as const;
45
+ const DEPTH_MS = 220;
46
+ const SWIPE_OUT_MS = 180;
47
+
48
+ export type ToastViewportItemProps = {
49
+ item: ToastItem;
50
+ store: ToastStore;
51
+ placement: ToastPlacement;
52
+ depth: number;
53
+ isExiting: boolean;
54
+ /** Points from the screen edge to the toast's edge — safe area plus offset. */
55
+ edgeOffset: number;
56
+ /** False while the app is backgrounded; the clock stops. */
57
+ isAppActive: boolean;
58
+ isScreenReaderEnabled: boolean;
59
+ /** The front toast's height in this stack — the front one writes it, the ones behind take it. */
60
+ frontHeight: SharedValue<number>;
61
+ };
62
+
63
+ /** The default card for a message toast. */
64
+ function renderMessage(item: ToastItem & { kind: "message" }, handle: ToastHandle): ReactNode {
65
+ const { action } = item;
66
+ return (
67
+ <Toast onHide={handle.hide} status={item.status} testID={`toast-${item.id}`}>
68
+ <Toast.Indicator>{item.isLoading ? <Spinner /> : undefined}</Toast.Indicator>
69
+ <Toast.Content>
70
+ <Toast.Title>{item.title}</Toast.Title>
71
+ {item.description ? <Toast.Description>{item.description}</Toast.Description> : null}
72
+ </Toast.Content>
73
+ {action ? (
74
+ <Toast.Action onPress={() => action.onPress(handle)} testID={`toast-${item.id}-action`}>
75
+ {action.label}
76
+ </Toast.Action>
77
+ ) : null}
78
+ {item.isLoading ? null : <Toast.Close testID={`toast-${item.id}-close`} />}
79
+ </Toast>
80
+ );
81
+ }
82
+
83
+ /**
84
+ * One toast in the viewport: its motion, its clock and its swipe.
85
+ *
86
+ * - **Motion.** It enters from its edge — 16pt and a fade, after its stagger
87
+ * delay — and recedes with `depth`: further toward the edge, smaller and
88
+ * fainter, animated as depth changes. Hidden, it fades back toward its edge
89
+ * and only then leaves the store. Under reduce motion the entrance is a fade.
90
+ * - **Clock.** The auto-hide clock restarts on every update (a promise toast's
91
+ * success gets a full duration) and pauses while the app is in the
92
+ * background and while a finger is on the toast.
93
+ * - **Swipe.** A pan follows the finger sideways and toward the edge and
94
+ * rubber-bands toward the centre; `resolveToastRelease` decides on release.
95
+ * It activates only past 10pt, so a tap still reaches the action and the ✕.
96
+ * - **Announcement.** Each revision is announced and plays its haptic once.
97
+ */
98
+ export function ToastViewportItem({
99
+ item,
100
+ store,
101
+ placement,
102
+ depth,
103
+ isExiting,
104
+ edgeOffset,
105
+ isAppActive,
106
+ isScreenReaderEnabled,
107
+ frontHeight,
108
+ }: ToastViewportItemProps): ReactElement {
109
+ const { id, createdAt, batchIndex, revision, onHide } = item;
110
+ const isReduced = useReducedMotion();
111
+ const presence = useSharedValue(0);
112
+ const depthValue = useSharedValue(depth);
113
+ const dragX = useSharedValue(0);
114
+ const dragY = useSharedValue(0);
115
+ const width = useSharedValue(0);
116
+ const height = useSharedValue(0);
117
+ const isFront = depth === 0 && !isExiting;
118
+ const [isTouched, setTouched] = useState(false);
119
+
120
+ const hide = useCallback(() => store.dispatch({ type: "hide", id }), [store, id]);
121
+ const handle = useMemo<ToastHandle>(() => ({ id, hide }), [id, hide]);
122
+ const itemContext = useMemo(() => ({ id, hide }), [id, hide]);
123
+
124
+ const remove = useCallback(() => {
125
+ store.dispatch({ type: "remove", id });
126
+ onHide?.();
127
+ }, [store, id, onHide]);
128
+
129
+ useEffect(() => {
130
+ if (isExiting) {
131
+ presence.value = withTiming(0, { duration: OVERLAY_MOTION.exitMs, easing: EXIT_EASING }, (finished) => {
132
+ if (finished) scheduleOnRN(remove);
133
+ });
134
+ return;
135
+ }
136
+ const delay = resolveToastEnterDelay({ createdAt, batchIndex, now: Date.now() });
137
+ presence.value = withDelay(delay, withTiming(1, { duration: OVERLAY_MOTION.enterMs, easing: ENTER_EASING }));
138
+ }, [isExiting, createdAt, batchIndex, presence, remove]);
139
+
140
+ useEffect(() => {
141
+ depthValue.value = withTiming(depth, { duration: DEPTH_MS, easing: ENTER_EASING });
142
+ }, [depth, depthValue]);
143
+
144
+ useEffect(() => {
145
+ if (isFront && height.value > 0) frontHeight.value = height.value;
146
+ }, [isFront, height, frontHeight]);
147
+
148
+ const duration =
149
+ item.kind === "message"
150
+ ? resolveToastDuration(
151
+ { duration: item.duration, hasAction: item.action !== undefined, isLoading: item.isLoading },
152
+ { isScreenReaderEnabled }
153
+ )
154
+ : resolveToastDuration({ duration: item.duration }, { isScreenReaderEnabled });
155
+ const isPaused = !isAppActive || isTouched;
156
+ const clock = useRef<{ key: string; timer: ToastTimer } | null>(null);
157
+
158
+ useEffect(() => {
159
+ if (isExiting) return;
160
+ const key = `${revision}:${duration}`;
161
+ if (clock.current?.key !== key) clock.current = { key, timer: createToastTimer(duration) };
162
+ if (isPaused) return;
163
+ const running = clock.current;
164
+ running.timer = startToastTimer(running.timer, Date.now());
165
+ const remaining = toastTimerRemaining(running.timer, Date.now());
166
+ const timeout = Number.isFinite(remaining) ? setTimeout(hide, remaining) : null;
167
+ return () => {
168
+ if (timeout !== null) clearTimeout(timeout);
169
+ running.timer = pauseToastTimer(running.timer, Date.now());
170
+ };
171
+ }, [revision, duration, isPaused, isExiting, hide]);
172
+
173
+ const status = item.kind === "message" ? item.status : "default";
174
+ const announcement =
175
+ item.kind === "message" ? resolveToastAnnouncement({ title: item.title, description: item.description }) : null;
176
+ const haptic = resolveToastHaptic(status, item.haptic);
177
+
178
+ // Once per revision: a promise toast is announced loading and again when it settles.
179
+ // biome-ignore lint/correctness/useExhaustiveDependencies: revision is the trigger; the rest only changes with it
180
+ useEffect(() => {
181
+ if (haptic !== false) scheduleOnUI(playHaptic, haptic);
182
+ if (announcement === null) return;
183
+ if (Platform.OS === "ios" && resolveToastInterrupts(status)) {
184
+ AccessibilityInfo.announceForAccessibilityWithOptions(announcement, { queue: false });
185
+ } else {
186
+ AccessibilityInfo.announceForAccessibility(announcement);
187
+ }
188
+ }, [revision]);
189
+
190
+ const pan = useMemo(
191
+ () =>
192
+ Gesture.Pan()
193
+ .activeOffsetX([-10, 10])
194
+ .activeOffsetY([-10, 10])
195
+ .onBegin(() => {
196
+ "worklet";
197
+ scheduleOnRN(setTouched, true);
198
+ })
199
+ .onUpdate((event) => {
200
+ "worklet";
201
+ const drag = resolveToastDrag({
202
+ placement,
203
+ translationX: event.translationX,
204
+ translationY: event.translationY,
205
+ });
206
+ dragX.value = drag.x;
207
+ dragY.value = drag.y;
208
+ })
209
+ .onEnd((event) => {
210
+ "worklet";
211
+ const release = resolveToastRelease({
212
+ placement,
213
+ translationX: event.translationX,
214
+ translationY: event.translationY,
215
+ velocityX: event.velocityX,
216
+ velocityY: event.velocityY,
217
+ width: width.value,
218
+ height: height.value,
219
+ });
220
+ if (release.kind === "settle") {
221
+ dragX.value = withSpring(0, SETTLE_SPRING);
222
+ dragY.value = withSpring(0, SETTLE_SPRING);
223
+ return;
224
+ }
225
+ if (release.axis === "x") {
226
+ dragX.value = withTiming(release.direction * (width.value + 48), { duration: SWIPE_OUT_MS });
227
+ } else {
228
+ dragY.value = withTiming(release.direction * (height.value + edgeOffset + 24), {
229
+ duration: SWIPE_OUT_MS,
230
+ });
231
+ }
232
+ scheduleOnRN(hide);
233
+ })
234
+ .onFinalize(() => {
235
+ "worklet";
236
+ scheduleOnRN(setTouched, false);
237
+ }),
238
+ [placement, dragX, dragY, width, height, edgeOffset, hide]
239
+ );
240
+
241
+ const animatedStyle = useAnimatedStyle(() => {
242
+ const stack = resolveToastDepthStyle(depthValue.value, placement);
243
+ const towardEdge = placement === "bottom" ? 1 : -1;
244
+ const enter = isReduced ? 0 : towardEdge * TOAST_ENTER_DISTANCE * (1 - presence.value);
245
+ const stackedHeight = resolveToastStackedHeight({
246
+ depth: depthValue.value,
247
+ natural: height.value,
248
+ front: frontHeight.value,
249
+ });
250
+ return {
251
+ height: stackedHeight ?? undefined,
252
+ opacity: Math.max(0, presence.value * stack.opacity),
253
+ transform: [
254
+ { translateX: dragX.value },
255
+ { translateY: stack.translateY + enter + dragY.value },
256
+ { scale: stack.scale },
257
+ ],
258
+ };
259
+ });
260
+
261
+ const onLayout = useCallback(
262
+ (event: LayoutChangeEvent) => {
263
+ width.value = event.nativeEvent.layout.width;
264
+ },
265
+ [width]
266
+ );
267
+
268
+ // The card's own height, measured inside the clip, so a toast behind still
269
+ // knows how tall it is when it moves up — and the front one publishes it.
270
+ const onCardLayout = useCallback(
271
+ (event: LayoutChangeEvent) => {
272
+ height.value = event.nativeEvent.layout.height;
273
+ if (isFront) frontHeight.value = event.nativeEvent.layout.height;
274
+ },
275
+ [height, isFront, frontHeight]
276
+ );
277
+
278
+ const zIndex = 2 * (50 - depth) - (isExiting ? 1 : 0);
279
+ const edge = placement === "bottom" ? { bottom: edgeOffset } : { top: edgeOffset };
280
+
281
+ return (
282
+ <View className={toastVariants().item()} pointerEvents="box-none" style={[edge, { zIndex }]}>
283
+ <GestureDetector gesture={pan}>
284
+ <Animated.View
285
+ onLayout={onLayout}
286
+ pointerEvents={isExiting ? "none" : "auto"}
287
+ style={[
288
+ {
289
+ width: "100%",
290
+ maxWidth: 560,
291
+ overflow: "hidden",
292
+ justifyContent: placement === "bottom" ? "flex-end" : "flex-start",
293
+ transformOrigin: placement === "bottom" ? "bottom" : "top",
294
+ },
295
+ animatedStyle,
296
+ ]}
297
+ >
298
+ <View onLayout={onCardLayout} style={{ flexShrink: 0 }}>
299
+ <ToastItemProvider value={itemContext}>
300
+ {item.kind === "message" ? renderMessage(item, handle) : item.render(handle)}
301
+ </ToastItemProvider>
302
+ </View>
303
+ </Animated.View>
304
+ </GestureDetector>
305
+ </View>
306
+ );
307
+ }
308
+ ToastViewportItem.displayName = "DelacourUI.ToastViewport.Item";
@@ -0,0 +1,13 @@
1
+ import type { ReactElement } from "react";
2
+ import { Text, type TextPresetProps } from "../text";
3
+ import { useToastPart } from "./toast.context";
4
+ import { toastVariants } from "./toast.variants";
5
+
6
+ export type ToastTitleProps = TextPresetProps;
7
+
8
+ /** What happened, in the status's colour — Alert's token for it, the same shade as the glyph. */
9
+ export function ToastTitle({ className, ...props }: ToastTitleProps): ReactElement {
10
+ const { status } = useToastPart("Toast.Title");
11
+ return <Text className={toastVariants({ status }).title({ className })} {...props} />;
12
+ }
13
+ ToastTitle.displayName = "DelacourUI.Toast.Title";
@@ -0,0 +1,135 @@
1
+ import { type ReactElement, useEffect, useState, useSyncExternalStore } from "react";
2
+ import { AccessibilityInfo, AppState, View } from "react-native";
3
+ import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
4
+ import Animated, { useAnimatedStyle, useSharedValue } from "react-native-reanimated";
5
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
6
+ import { Overlay } from "../overlay";
7
+ import type { ToastItem, ToastPlacement, ToastStore } from "./toast.store";
8
+ import { resolveToastStack, TOAST_PLACEMENTS, toastVariants } from "./toast.variants";
9
+ import { toastStore } from "./toast-api";
10
+ import { ToastViewportItem } from "./toast-item";
11
+
12
+ /** The gap between a toast and the safe area, in points, when no `offset` is given. */
13
+ const DEFAULT_EDGE_GAP = 8;
14
+
15
+ export type ToastViewportProps = {
16
+ /** Extra points between each stack and its safe-area edge — clear a tab bar with `{ bottom: 56 }`. Default 8 each. */
17
+ offset?: { top?: number; bottom?: number };
18
+ /** The store to draw. Default: the one `toast` writes to. */
19
+ store?: ToastStore;
20
+ };
21
+
22
+ /** Whether VoiceOver or TalkBack is running, kept current. */
23
+ function useScreenReaderEnabled(): boolean {
24
+ const [isEnabled, setEnabled] = useState(false);
25
+ useEffect(() => {
26
+ let isMounted = true;
27
+ AccessibilityInfo.isScreenReaderEnabled().then((value) => {
28
+ if (isMounted) setEnabled(value);
29
+ });
30
+ const subscription = AccessibilityInfo.addEventListener("screenReaderChanged", setEnabled);
31
+ return () => {
32
+ isMounted = false;
33
+ subscription.remove();
34
+ };
35
+ }, []);
36
+ return isEnabled;
37
+ }
38
+
39
+ /** False while the app is in the background or inactive — every toast's clock stops. */
40
+ function useAppActive(): boolean {
41
+ const [isActive, setActive] = useState(AppState.currentState === "active");
42
+ useEffect(() => {
43
+ const subscription = AppState.addEventListener("change", (state) => setActive(state === "active"));
44
+ return () => subscription.remove();
45
+ }, []);
46
+ return isActive;
47
+ }
48
+
49
+ /**
50
+ * Draws every toast — mount it once, inside `OverlayProvider`, beside the
51
+ * navigator.
52
+ *
53
+ * Two stacks, top and bottom, in the overlay z-order's `toast` band, above
54
+ * every sheet and every dialog. Both are `box-none`: only a toast's own card
55
+ * takes a touch, so the app under the viewport stays tappable. The top stack
56
+ * sits under the status bar; the bottom one above the home indicator, and it
57
+ * rides up with the keyboard. Each stack draws its newest three and queues the
58
+ * rest (`resolveToastStack`).
59
+ *
60
+ * Renders nothing — and holds no overlay registration — while there is no
61
+ * toast. Without an `OverlayProvider` it draws inline and warns once in
62
+ * development, like every overlay.
63
+ *
64
+ * @example
65
+ * <OverlayProvider>
66
+ * <BottomSheetProvider>
67
+ * <Stack />
68
+ * <ToastViewport />
69
+ * </BottomSheetProvider>
70
+ * </OverlayProvider>
71
+ */
72
+ export function ToastViewport({ offset, store = toastStore }: ToastViewportProps): ReactElement | null {
73
+ const items = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
74
+ const insets = useSafeAreaInsets();
75
+ const isAppActive = useAppActive();
76
+ const isScreenReaderEnabled = useScreenReaderEnabled();
77
+ const keyboard = useReanimatedKeyboardAnimation();
78
+ const insetBottom = insets.bottom;
79
+ const topFrontHeight = useSharedValue(0);
80
+ const bottomFrontHeight = useSharedValue(0);
81
+
82
+ // The keyboard covers the home indicator, so the bottom stack lifts by the
83
+ // part of the keyboard above that inset — no more.
84
+ const keyboardLift = useAnimatedStyle(() => ({
85
+ transform: [{ translateY: -Math.max(0, -keyboard.height.value - insetBottom) }],
86
+ }));
87
+
88
+ if (items.length === 0) return null;
89
+
90
+ const byId = new Map<string, ToastItem>(items.map((item) => [item.id, item]));
91
+ const edgeOffsets: Record<ToastPlacement, number> = {
92
+ top: insets.top + (offset?.top ?? DEFAULT_EDGE_GAP),
93
+ bottom: insets.bottom + (offset?.bottom ?? DEFAULT_EDGE_GAP),
94
+ };
95
+ const slots = toastVariants();
96
+
97
+ const stacks = TOAST_PLACEMENTS.map((placement) => ({
98
+ placement,
99
+ entries: resolveToastStack(items, placement).filter((entry) => entry.isVisible),
100
+ }));
101
+
102
+ return (
103
+ <Overlay.Portal layer="toast">
104
+ <View className={slots.viewport()} pointerEvents="box-none">
105
+ {stacks.map(({ placement, entries }) => (
106
+ <Animated.View
107
+ className={slots.stack()}
108
+ key={placement}
109
+ pointerEvents="box-none"
110
+ style={placement === "bottom" ? keyboardLift : undefined}
111
+ >
112
+ {entries.map((entry) => {
113
+ const item = byId.get(entry.id);
114
+ return item ? (
115
+ <ToastViewportItem
116
+ depth={entry.depth}
117
+ edgeOffset={edgeOffsets[placement]}
118
+ frontHeight={placement === "bottom" ? bottomFrontHeight : topFrontHeight}
119
+ isAppActive={isAppActive}
120
+ isExiting={entry.isExiting}
121
+ isScreenReaderEnabled={isScreenReaderEnabled}
122
+ item={item}
123
+ key={item.id}
124
+ placement={placement}
125
+ store={store}
126
+ />
127
+ ) : null;
128
+ })}
129
+ </Animated.View>
130
+ ))}
131
+ </View>
132
+ </Overlay.Portal>
133
+ );
134
+ }
135
+ ToastViewport.displayName = "DelacourUI.ToastViewport";
@@ -0,0 +1,69 @@
1
+ import { createContext, type ReactElement, type ReactNode, use } from "react";
2
+ import type { ToastStatus } from "./toast.store";
3
+
4
+ /** The action `Toast.Action` lends its root, so a screen reader's `activate` runs it. */
5
+ export type ToastRegisteredAction = {
6
+ label: string;
7
+ run: () => void;
8
+ };
9
+
10
+ export type ToastContextValue = {
11
+ status: ToastStatus;
12
+ /** Hides the toast — the root's `onHide`, or the viewport's for the toast it is drawing. */
13
+ hide: () => void;
14
+ /** `Toast.Action` registers itself here while mounted, and passes `null` on unmount. */
15
+ registerAction: (action: ToastRegisteredAction | null) => void;
16
+ };
17
+
18
+ const ToastContext = createContext<ToastContextValue | null>(null);
19
+
20
+ /**
21
+ * Supplies the enclosing toast's status and `hide` to its parts.
22
+ *
23
+ * Its own module, importing nothing but types, so a part can read it without
24
+ * importing `./toast` — that import would close a cycle (package rule 3).
25
+ */
26
+ export function ToastProvider({ value, children }: { value: ToastContextValue; children: ReactNode }): ReactElement {
27
+ return <ToastContext value={value}>{children}</ToastContext>;
28
+ }
29
+ ToastProvider.displayName = "DelacourUI.Toast.Provider";
30
+
31
+ /** The enclosing toast's context, or null outside a `<Toast>`. */
32
+ export function useToastContext(): ToastContextValue | null {
33
+ return use(ToastContext);
34
+ }
35
+
36
+ /** Internal: the enclosing toast's context for a part that cannot work without one. */
37
+ export function useToastPart(component: string): ToastContextValue {
38
+ const context = useToastContext();
39
+ if (!context) throw new Error(`${component} must be rendered inside a <Toast>.`);
40
+ return context;
41
+ }
42
+
43
+ /** What the viewport tells a toast it is drawing: which one, and how to hide it. */
44
+ export type ToastItemContextValue = {
45
+ id: string;
46
+ hide: () => void;
47
+ };
48
+
49
+ const ToastItemContext = createContext<ToastItemContextValue | null>(null);
50
+
51
+ /**
52
+ * Wraps each toast the viewport draws, so a `<Toast>` inside a custom
53
+ * `render` hides the right toast with no `onHide` of its own.
54
+ */
55
+ export function ToastItemProvider({
56
+ value,
57
+ children,
58
+ }: {
59
+ value: ToastItemContextValue;
60
+ children: ReactNode;
61
+ }): ReactElement {
62
+ return <ToastItemContext value={value}>{children}</ToastItemContext>;
63
+ }
64
+ ToastItemProvider.displayName = "DelacourUI.ToastViewport.ItemProvider";
65
+
66
+ /** The toast the viewport is drawing here, or null outside one. */
67
+ export function useToastItemContext(): ToastItemContextValue | null {
68
+ return use(ToastItemContext);
69
+ }