@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,97 @@
1
+ # Dialog
2
+
3
+ A centred card over a dimmed app that asks for a decision or a short input before the user goes
4
+ on: confirm a delete, rename a file, accept terms. Compound root plus `Dialog.Trigger`,
5
+ `Dialog.Content`, `Dialog.Close`, `Dialog.Header`, `Dialog.Title`, `Dialog.Description`,
6
+ `Dialog.Body` and `Dialog.Footer`, and `useDialog()`.
7
+
8
+ `import { Dialog } from "@delacour/react-native-ui/dialog";`
9
+
10
+ It is drawn with the [overlay foundation](../overlay/AGENTS.md) — `Overlay.Portal`,
11
+ `Overlay.Scrim`, `useOverlayPresence`, `useOverlayBackHandler` — so the app has to mount
12
+ `OverlayProvider` once at its root. Without one the dialog draws inline and warns once.
13
+
14
+ ## Files
15
+
16
+ | File | What it holds |
17
+ | --- | --- |
18
+ | `index.ts` | → `@delacour/react-native-ui/dialog` |
19
+ | `dialog.tsx` | Root — the open state, the ids and the focus refs — and the `Object.assign` compound surface |
20
+ | `dialog.context.tsx` | **Leaf.** `DialogProvider`, `DialogSizeProvider`, `useDialog()`, `useDialogContext()`, `useDialogPart()`, `useDialogSize()` |
21
+ | `dialog-trigger.tsx` | `Dialog.Trigger` — donates the open with `asChild`, keeps the trigger's ref for focus return |
22
+ | `dialog-content.tsx` | `Dialog.Content` — presence, portal, scrim, card, motion, keyboard lift, back button, focus |
23
+ | `dialog-close.tsx` | `Dialog.Close` — the corner ✕, or with `asChild` any control that closes |
24
+ | `dialog-header.tsx` | `Dialog.Header` |
25
+ | `dialog-title.tsx` | `Dialog.Title` — a `Text.Header` carrying the title `nativeID` and the focus ref |
26
+ | `dialog-description.tsx` | `Dialog.Description` — a muted `Text.Paragraph` |
27
+ | `dialog-body.tsx` | `Dialog.Body` |
28
+ | `dialog-footer.tsx` | `Dialog.Footer` — `plain` or `panel`, row or stacked by size |
29
+ | `dialog.variants.ts` | The slotted `tv()`, the size classes, the motion and slop constants, and three pure resolvers — no RN imports |
30
+ | `dialog.variants.test.ts` | Slots per size and footer variant, tokens in both themes, the resolvers |
31
+
32
+ ## Design
33
+
34
+ - **One axis on the card, one on the footer.** `size` (`sm` 320, `md` 400, `lg` 520, `full`) is a
35
+ `max-w-*` on a `w-full` card, so a narrow phone shrinks every size to the screen gutter and only
36
+ a wider screen reaches the cap. `Dialog.Footer`'s `variant` is `plain` — a right-aligned row
37
+ inside the card's padding — or `panel`, which bleeds out of the padding to the card's edges on a
38
+ `bg-muted` band under a hairline, rounding only its bottom corners.
39
+ - **An `sm` footer stacks.** Two labelled buttons side by side on a 320pt card leave each label a
40
+ few characters before it truncates. `resolveDialogFooterDirection` is the rule, and the footer
41
+ reads the size from `DialogSizeProvider`, which `Dialog.Content` mounts — a context of its own
42
+ rather than a field on the root's, because the size belongs to the content, not the dialog.
43
+ Stacked actions keep written order, so the primary action, written last, sits last either way.
44
+ - **The card is a popover surface with the card corner.** `bg-popover`, the token a layer over the
45
+ app paints with (as `BottomSheet` does), and `rounded-lg`, the package's card step. The scrim is
46
+ the foundation's `bg-overlay` at opacity 1, faded by presence.
47
+ - **`Dialog.Content` renders nothing until the dialog opens, and stays through the exit.**
48
+ `useOverlayPresence` keeps it mounted until the exit animation ends, and the portal is mounted
49
+ only while present, so the registry entry, the z-order and the back handler all follow presence
50
+ — the foundation's contract. A re-open mid-exit reverses from wherever `progress` is.
51
+ - **It draws in the `modal` band**, above every bottom sheet and the navigator's header. A sheet
52
+ opened *from* a dialog would draw under it; open a dialog from a sheet, never the reverse.
53
+ - **Every path to closed is one `setOpen(false)`** — the trigger, `Dialog.Close`, the scrim, the
54
+ back button, the escape gesture and `useDialog().close()`. The root drops a set that does not
55
+ change the value, so a second tap on the scrim while the card is already leaving does not report
56
+ `onOpenChange(false)` twice.
57
+ - **`isDismissible={false}` turns off the ways out the user did not choose.** The scrim still takes
58
+ the touch — the app under an alert dialog must not be pressable — but does nothing; the back
59
+ button is not subscribed; `onAccessibilityEscape` is not set; and the card announces as
60
+ `alertdialog`. `Dialog.Close` still closes it: it is one of the dialog's own actions.
61
+ - **Triggers and closes donate the press.** With `asChild` they hand `onPress` to the child,
62
+ chained ahead of the child's own, for `BottomSheet.Trigger`'s reason: two tap gestures in an
63
+ ancestor/descendant pair are not simultaneous, so a pressable wrapped around a `Button` never
64
+ fires. The child has to be something that handles `onPress`.
65
+ - **The corner ✕ presses with `fade` and 8pt of slop.** A scale on a glyph this small reads as a
66
+ jitter, and a bare glyph has no capsule to bring it toward 44pt. It sits out of the flow, and
67
+ `Dialog.Title`'s `pr-8` reserves its clearance on every dialog, so adding a close never reflows
68
+ the title.
69
+ - **The title and description carry no type of their own.** They *are* `Text.Header` and a muted
70
+ `Text.Paragraph`; a size in the slot would be a second definition of a preset. A test asserts
71
+ the `title` slot is exactly `pr-8`. There is no `description` slot: `tv` emits `undefined` for an
72
+ empty string, so the part merges its `className` with `cn()`, as `BottomSheet.Description` does.
73
+ - **Motion.** Scrim opacity is `progress`; the card fades with it and grows from
74
+ `DIALOG_ENTER_SCALE` (0.96) and `DIALOG_ENTER_TRANSLATE_Y` (8pt) below. Under reduce motion the
75
+ transforms drop and the fade stays, per the foundation. `isMotionCalm` does not still it.
76
+ - **The keyboard lifts the card just enough.** `resolveDialogKeyboardLift` is the smallest lift
77
+ that keeps the card's bottom `DIALOG_KEYBOARD_MARGIN` (16pt) above the keyboard, never negative,
78
+ and capped so the card's top never rises under the top safe-area inset — a card too tall for
79
+ both keeps its title on screen. It is a worklet read in the card's animated style against
80
+ `useReanimatedKeyboardAnimation`'s height, so it rides the keyboard's own frames. The card's
81
+ edges come from `onLayout`, which ignores transforms, so the lift never feeds back into itself.
82
+ The positioner fills the portal host, which fills the window, so layout `y` is window `y`.
83
+ - **Accessibility.** The card is `accessibilityViewIsModal` with `role="dialog"` (or
84
+ `"alertdialog"`, `resolveDialogRole`) and `accessibilityLabelledBy` the title's `nativeID` for
85
+ Android. Once the entrance finishes (`onEntered`) focus moves to the title; once the exit
86
+ finishes, to the trigger, if one is still mounted. `findNodeHandle` of a ref that has gone is
87
+ null, and focus is then left where the system puts it. `Dialog.Title` takes no `ref` for that
88
+ reason — the dialog holds it.
89
+ - **No blur backdrop.** Frosted scrims need `expo-blur`, which is not a peer; recorded as a
90
+ follow-up for every overlay.
91
+
92
+ ## Testing
93
+
94
+ `bun test` reaches `dialogVariants`, the size classes, the footer direction, the role and the
95
+ keyboard lift. Opening from a trigger and controlled, every dismissal path, the alert dialog, the
96
+ z-order over a sheet, the exit, the keyboard, VoiceOver focus and reduce motion are verified on a
97
+ simulator through `/dialog` in the playground — `over-sheet` is the z-order case.
@@ -0,0 +1,21 @@
1
+ import type { ReactElement } from "react";
2
+ import { View, type ViewProps } from "react-native";
3
+ import { dialogVariants } from "./dialog.variants";
4
+
5
+ export type DialogBodyProps = ViewProps & { className?: string };
6
+
7
+ /**
8
+ * Whatever the dialog asks for between the header and the footer — a field, a
9
+ * checkbox, a short list. Optional: a confirmation is a header and a footer.
10
+ *
11
+ * It does not scroll. Content long enough to need it belongs in a `BottomSheet`.
12
+ *
13
+ * @example
14
+ * <Dialog.Body>
15
+ * <Input placeholder="Project name" />
16
+ * </Dialog.Body>
17
+ */
18
+ export function DialogBody({ className, ...props }: DialogBodyProps): ReactElement {
19
+ return <View className={dialogVariants().body({ className })} {...props} />;
20
+ }
21
+ DialogBody.displayName = "DelacourUI.Dialog.Body";
@@ -0,0 +1,82 @@
1
+ import { type ReactElement, useCallback } from "react";
2
+ import { IconCrossSmall } from "../../icons/central";
3
+ import { Slot } from "../../lib/slot";
4
+ import { Icon } from "../icon";
5
+ import { Pressable, type PressableProps } from "../pressable";
6
+ import { useDialogPart } from "./dialog.context";
7
+ import { DIALOG_CLOSE_HIT_SLOP, dialogVariants } from "./dialog.variants";
8
+
9
+ export type DialogCloseProps =
10
+ /** Donates the close to the single child — a `Button` in the footer, say. */
11
+ | ({ asChild: true; children: ReactElement } & Omit<PressableProps, "asChild" | "children">)
12
+ /** The ✕ glyph in the card's top-right corner. */
13
+ | ({ asChild?: false; accessibilityLabel?: string } & Omit<PressableProps, "asChild" | "children">);
14
+
15
+ /**
16
+ * Closes the dialog.
17
+ *
18
+ * Without children it is the ✕ in the card's corner: this library's
19
+ * `Pressable` with `fade` feedback — a scale on a glyph this small reads as a
20
+ * jitter — 8pt of slop because a bare glyph has no capsule to bring it toward
21
+ * 44pt, and `"Close"` as its label. It sits out of the flow; `Dialog.Title`
22
+ * reserves the clearance.
23
+ *
24
+ * With `asChild` it donates the close to its child, chained ahead of the
25
+ * child's own `onPress`, the way `Dialog.Trigger` donates the open. Either way
26
+ * the close is the same `setOpen(false)` the scrim and the back button take, so
27
+ * `onOpenChange` is the one thing to watch.
28
+ *
29
+ * It closes even an alert dialog: `isDismissible={false}` turns off the ways
30
+ * out the user did not choose, not the ones the dialog offers.
31
+ *
32
+ * @example
33
+ * <Dialog.Close />
34
+ *
35
+ * @example
36
+ * <Dialog.Close asChild>
37
+ * <Button variant="secondary">Cancel</Button>
38
+ * </Dialog.Close>
39
+ */
40
+ export function DialogClose(props: DialogCloseProps): ReactElement {
41
+ const { close } = useDialogPart("Dialog.Close");
42
+ const { onPress } = props;
43
+
44
+ const handlePress = useCallback(() => {
45
+ close();
46
+ onPress?.();
47
+ }, [close, onPress]);
48
+
49
+ if (props.asChild) {
50
+ const { asChild: _asChild, children, onPress: _onPress, ...rest } = props;
51
+ return (
52
+ <Slot onPress={handlePress} {...rest}>
53
+ {children}
54
+ </Slot>
55
+ );
56
+ }
57
+
58
+ const {
59
+ asChild: _asChild,
60
+ accessibilityLabel = "Close",
61
+ className,
62
+ feedback = "fade",
63
+ hitSlop = DIALOG_CLOSE_HIT_SLOP,
64
+ onPress: _onPress,
65
+ ...rest
66
+ } = props;
67
+
68
+ return (
69
+ <Pressable
70
+ accessibilityLabel={accessibilityLabel}
71
+ accessibilityRole="button"
72
+ className={dialogVariants().close({ className })}
73
+ feedback={feedback}
74
+ hitSlop={hitSlop}
75
+ onPress={handlePress}
76
+ {...rest}
77
+ >
78
+ <Icon color="muted-foreground" icon={IconCrossSmall} />
79
+ </Pressable>
80
+ );
81
+ }
82
+ DialogClose.displayName = "DelacourUI.Dialog.Close";
@@ -0,0 +1,153 @@
1
+ import { type ReactElement, useCallback, useId } from "react";
2
+ import {
3
+ AccessibilityInfo,
4
+ findNodeHandle,
5
+ type LayoutChangeEvent,
6
+ useWindowDimensions,
7
+ View,
8
+ type ViewProps,
9
+ } from "react-native";
10
+ import { useReanimatedKeyboardAnimation } from "react-native-keyboard-controller";
11
+ import Animated, { interpolate, useAnimatedStyle, useSharedValue } from "react-native-reanimated";
12
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
13
+ import { Overlay, useOverlayBackHandler, useOverlayPresence } from "../overlay";
14
+ import { type DialogFocusTarget, DialogSizeProvider, useDialogPart } from "./dialog.context";
15
+ import {
16
+ DIALOG_ENTER_SCALE,
17
+ DIALOG_ENTER_TRANSLATE_Y,
18
+ DIALOG_KEYBOARD_MARGIN,
19
+ type DialogSize,
20
+ dialogVariants,
21
+ resolveDialogKeyboardLift,
22
+ resolveDialogRole,
23
+ } from "./dialog.variants";
24
+
25
+ export type DialogContentProps = ViewProps & {
26
+ className?: string;
27
+ /** Classes for the scrim behind the card. */
28
+ scrimClassName?: string;
29
+ /** The card's width cap: `sm` 320, `md` 400, `lg` 520, `full` the gutter. Default `md`. */
30
+ size?: DialogSize;
31
+ };
32
+
33
+ /** Moves VoiceOver / TalkBack focus to a mounted element. A no-op when it has gone. */
34
+ function focus(target: DialogFocusTarget | null): void {
35
+ if (target === null) return;
36
+ const handle = findNodeHandle(target as Parameters<typeof findNodeHandle>[0]);
37
+ if (handle !== null) AccessibilityInfo.setAccessibilityFocus(handle);
38
+ }
39
+
40
+ /**
41
+ * The card, its scrim and the portal they draw through.
42
+ *
43
+ * Renders nothing until the dialog opens, and stays mounted through the exit
44
+ * animation (`useOverlayPresence`), so the portal, the z-order and the back
45
+ * button all follow presence with nothing else to keep in sync. It draws in the
46
+ * `modal` band, above every bottom sheet and the navigator's header.
47
+ *
48
+ * - **Motion.** The scrim's opacity is `progress`; the card fades with it and
49
+ * grows from 0.96 and 8pt below. Under reduce motion only the fade runs.
50
+ * - **Keyboard.** The card lifts by `resolveDialogKeyboardLift` — just enough
51
+ * to keep its bottom 16pt above the keyboard, never past the top inset — on
52
+ * the keyboard's own frames, and settles back when it closes.
53
+ * - **Dismissal.** A scrim tap, Android back and the iOS escape gesture close a
54
+ * dismissible dialog. With `isDismissible={false}` the scrim still takes the
55
+ * touch so the app under it cannot be pressed, and only the dialog's own
56
+ * actions close it.
57
+ * - **Accessibility.** The card is modal to assistive technology, announces as
58
+ * a `dialog` (an `alertdialog` when not dismissible), is labelled by the
59
+ * title, and moves focus to the title once it has entered — and back to the
60
+ * trigger, if one is still mounted, once it has gone.
61
+ *
62
+ * @example
63
+ * <Dialog.Content size="sm">
64
+ * <Dialog.Header>…</Dialog.Header>
65
+ * </Dialog.Content>
66
+ */
67
+ export function DialogContent({
68
+ className,
69
+ scrimClassName,
70
+ size = "md",
71
+ children,
72
+ onLayout,
73
+ style,
74
+ ...props
75
+ }: DialogContentProps): ReactElement | null {
76
+ const { isOpen, close, isDismissible, titleId, titleRef, triggerRef } = useDialogPart("Dialog.Content");
77
+ const id = useId();
78
+
79
+ const handleEntered = useCallback(() => focus(titleRef.current), [titleRef]);
80
+ const handleExited = useCallback(() => focus(triggerRef.current), [triggerRef]);
81
+ const { isPresent, isReduced, progress } = useOverlayPresence({
82
+ isOpen,
83
+ onEntered: handleEntered,
84
+ onExited: handleExited,
85
+ });
86
+
87
+ useOverlayBackHandler({ id, isEnabled: isPresent && isDismissible, onBack: close });
88
+
89
+ const keyboard = useReanimatedKeyboardAnimation();
90
+ const { top: topInset } = useSafeAreaInsets();
91
+ const { height: windowHeight } = useWindowDimensions();
92
+ const cardTop = useSharedValue(0);
93
+ const cardBottom = useSharedValue(0);
94
+
95
+ const handleLayout = useCallback(
96
+ (event: LayoutChangeEvent) => {
97
+ const { y, height } = event.nativeEvent.layout;
98
+ cardTop.value = y;
99
+ cardBottom.value = y + height;
100
+ onLayout?.(event);
101
+ },
102
+ [cardTop, cardBottom, onLayout]
103
+ );
104
+
105
+ const cardStyle = useAnimatedStyle(() => {
106
+ const lift = resolveDialogKeyboardLift({
107
+ cardBottom: cardBottom.value,
108
+ cardTop: cardTop.value,
109
+ keyboardHeight: keyboard.height.value,
110
+ margin: DIALOG_KEYBOARD_MARGIN,
111
+ topInset,
112
+ windowHeight,
113
+ });
114
+
115
+ if (isReduced) {
116
+ return { opacity: progress.value, transform: [{ translateY: -lift }] };
117
+ }
118
+
119
+ return {
120
+ opacity: progress.value,
121
+ transform: [
122
+ { translateY: interpolate(progress.value, [0, 1], [DIALOG_ENTER_TRANSLATE_Y, 0]) - lift },
123
+ { scale: interpolate(progress.value, [0, 1], [DIALOG_ENTER_SCALE, 1]) },
124
+ ],
125
+ };
126
+ }, [isReduced, topInset, windowHeight]);
127
+
128
+ if (!isPresent) return null;
129
+
130
+ const slots = dialogVariants({ size });
131
+ const onEscape = isDismissible ? close : undefined;
132
+
133
+ return (
134
+ <Overlay.Portal id={id} layer="modal">
135
+ <Overlay.Scrim className={slots.scrim({ className: scrimClassName })} onDismiss={onEscape} progress={progress} />
136
+ <View className={slots.positioner()} pointerEvents="box-none">
137
+ <Animated.View
138
+ accessibilityLabelledBy={titleId}
139
+ accessibilityViewIsModal
140
+ className={slots.content({ className })}
141
+ onAccessibilityEscape={onEscape}
142
+ onLayout={handleLayout}
143
+ role={resolveDialogRole(isDismissible)}
144
+ style={[cardStyle, style]}
145
+ {...props}
146
+ >
147
+ <DialogSizeProvider value={size}>{children}</DialogSizeProvider>
148
+ </Animated.View>
149
+ </View>
150
+ </Overlay.Portal>
151
+ );
152
+ }
153
+ DialogContent.displayName = "DelacourUI.Dialog.Content";
@@ -0,0 +1,23 @@
1
+ import type { ReactElement } from "react";
2
+ import { cn } from "../../lib/cn";
3
+ import { Text, type TextPresetProps } from "../text";
4
+ import { useDialogPart } from "./dialog.context";
5
+
6
+ export type DialogDescriptionProps = TextPresetProps;
7
+
8
+ /**
9
+ * Supporting copy under the title.
10
+ *
11
+ * *Is* a `Text.Paragraph`, muted by default so the title and this read as a
12
+ * hierarchy rather than two equal lines. `color` is an ordinary prop. It has no
13
+ * slot in `dialogVariants` — see the note there.
14
+ *
15
+ * @example
16
+ * <Dialog.Description>This cannot be undone.</Dialog.Description>
17
+ */
18
+ export function DialogDescription({ className, color = "muted", ...props }: DialogDescriptionProps): ReactElement {
19
+ const { descriptionId } = useDialogPart("Dialog.Description");
20
+
21
+ return <Text.Paragraph className={cn(className)} color={color} nativeID={descriptionId} {...props} />;
22
+ }
23
+ DialogDescription.displayName = "DelacourUI.Dialog.Description";
@@ -0,0 +1,32 @@
1
+ import type { ReactElement } from "react";
2
+ import { View, type ViewProps } from "react-native";
3
+ import { useDialogSize } from "./dialog.context";
4
+ import { type DialogFooterVariant, dialogVariants, resolveDialogFooterDirection } from "./dialog.variants";
5
+
6
+ export type DialogFooterProps = ViewProps & {
7
+ className?: string;
8
+ /** `plain` sits inside the card's padding; `panel` bleeds to its edges on a muted band. Default `plain`. */
9
+ variant?: DialogFooterVariant;
10
+ };
11
+
12
+ /**
13
+ * The dialog's actions.
14
+ *
15
+ * A right-aligned row on every size but `sm`, where two labelled buttons side
16
+ * by side would truncate, so they stack full width in written order — write the
17
+ * primary action last and it sits last either way. The direction follows the
18
+ * card's size from `Dialog.Content`; `resolveDialogFooterDirection` is the rule.
19
+ *
20
+ * @example
21
+ * <Dialog.Footer variant="panel">
22
+ * <Dialog.Close asChild><Button variant="secondary">Cancel</Button></Dialog.Close>
23
+ * <Button onPress={save}>Save</Button>
24
+ * </Dialog.Footer>
25
+ */
26
+ export function DialogFooter({ className, variant = "plain", ...props }: DialogFooterProps): ReactElement {
27
+ const size = useDialogSize();
28
+ const footerDirection = resolveDialogFooterDirection(size);
29
+
30
+ return <View className={dialogVariants({ footer: variant, footerDirection }).footer({ className })} {...props} />;
31
+ }
32
+ DialogFooter.displayName = "DelacourUI.Dialog.Footer";
@@ -0,0 +1,20 @@
1
+ import type { ReactElement } from "react";
2
+ import { View, type ViewProps } from "react-native";
3
+ import { dialogVariants } from "./dialog.variants";
4
+
5
+ export type DialogHeaderProps = ViewProps & { className?: string };
6
+
7
+ /**
8
+ * The column holding `Dialog.Title` and `Dialog.Description`, a tight gap apart
9
+ * so the two read as one block above the body.
10
+ *
11
+ * @example
12
+ * <Dialog.Header>
13
+ * <Dialog.Title>Delete project?</Dialog.Title>
14
+ * <Dialog.Description>This cannot be undone.</Dialog.Description>
15
+ * </Dialog.Header>
16
+ */
17
+ export function DialogHeader({ className, ...props }: DialogHeaderProps): ReactElement {
18
+ return <View className={dialogVariants().header({ className })} {...props} />;
19
+ }
20
+ DialogHeader.displayName = "DelacourUI.Dialog.Header";
@@ -0,0 +1,42 @@
1
+ import { type ReactElement, useCallback } from "react";
2
+ import { Text, type TextPresetProps } from "../text";
3
+ import { type DialogFocusTarget, useDialogPart } from "./dialog.context";
4
+ import { dialogVariants } from "./dialog.variants";
5
+
6
+ /**
7
+ * A `Text.Header`'s props less `ref`: the dialog holds the title's ref itself,
8
+ * to move accessibility focus onto it.
9
+ */
10
+ export type DialogTitleProps = Omit<TextPresetProps, "ref">;
11
+
12
+ /**
13
+ * The dialog's heading.
14
+ *
15
+ * *Is* a `Text.Header` — the preset supplies the type, and this adds only the
16
+ * clearance the corner ✕ needs. It carries the `nativeID` the card is labelled
17
+ * by on Android, and it is where accessibility focus lands once the card has
18
+ * finished entering, so a screen reader announces what the dialog is for first.
19
+ *
20
+ * @example
21
+ * <Dialog.Title>Delete project?</Dialog.Title>
22
+ */
23
+ export function DialogTitle({ className, ...props }: DialogTitleProps): ReactElement {
24
+ const { titleId, titleRef } = useDialogPart("Dialog.Title");
25
+ const setTitleRef = useCallback(
26
+ (node: DialogFocusTarget | null) => {
27
+ titleRef.current = node;
28
+ },
29
+ [titleRef]
30
+ );
31
+
32
+ return (
33
+ <Text.Header
34
+ accessibilityRole="header"
35
+ className={dialogVariants().title({ className })}
36
+ nativeID={titleId}
37
+ ref={setTitleRef}
38
+ {...props}
39
+ />
40
+ );
41
+ }
42
+ DialogTitle.displayName = "DelacourUI.Dialog.Title";
@@ -0,0 +1,54 @@
1
+ import { type ReactElement, useCallback } from "react";
2
+ import { composeRefs } from "../../lib/compose-refs";
3
+ import { Slot } from "../../lib/slot";
4
+ import { Pressable, type PressableProps } from "../pressable";
5
+ import { useDialogPart } from "./dialog.context";
6
+
7
+ export type DialogTriggerProps = PressableProps;
8
+
9
+ /**
10
+ * The control that opens the dialog.
11
+ *
12
+ * On its own it is this library's `Pressable`. **`asChild` donates the press
13
+ * rather than wrapping the child**, the way `BottomSheet.Trigger` does: two tap
14
+ * gestures in an ancestor/descendant pair are not simultaneous, so a `Button`
15
+ * wrapped in a pressable trigger would win the touch and the dialog would never
16
+ * open. With `asChild` the open is chained ahead of the child's own `onPress`,
17
+ * and the child keeps its gesture, feedback and haptic — so the child has to be
18
+ * something that handles `onPress`.
19
+ *
20
+ * The trigger's ref is kept so accessibility focus can return to it when the
21
+ * dialog closes.
22
+ *
23
+ * @example
24
+ * <Dialog.Trigger asChild>
25
+ * <Button variant="destructive">Delete</Button>
26
+ * </Dialog.Trigger>
27
+ */
28
+ export function DialogTrigger({ asChild = false, children, onPress, ref, ...props }: DialogTriggerProps): ReactElement {
29
+ const { setOpen, triggerRef } = useDialogPart("Dialog.Trigger");
30
+
31
+ const handlePress = useCallback(() => {
32
+ setOpen(true);
33
+ onPress?.();
34
+ }, [setOpen, onPress]);
35
+
36
+ const composedRef = composeRefs(ref, (node) => {
37
+ triggerRef.current = node;
38
+ });
39
+
40
+ if (asChild) {
41
+ return (
42
+ <Slot onPress={handlePress} ref={composedRef} {...props}>
43
+ {children}
44
+ </Slot>
45
+ );
46
+ }
47
+
48
+ return (
49
+ <Pressable accessibilityRole="button" onPress={handlePress} ref={composedRef} {...props}>
50
+ {children}
51
+ </Pressable>
52
+ );
53
+ }
54
+ DialogTrigger.displayName = "DelacourUI.Dialog.Trigger";
@@ -0,0 +1,90 @@
1
+ import { createContext, type ReactElement, type ReactNode, type RefObject, use } from "react";
2
+ import type { DialogSize } from "./dialog.variants";
3
+
4
+ /** Anything `AccessibilityInfo.setAccessibilityFocus` can be pointed at through `findNodeHandle`. */
5
+ export type DialogFocusTarget = object;
6
+
7
+ export type DialogContextValue = {
8
+ /** Whether the dialog is open — the owner's state, not the exit animation's. */
9
+ isOpen: boolean;
10
+ /** Opens or closes it. Reports through `onOpenChange` only when the value changes. */
11
+ setOpen: (isOpen: boolean) => void;
12
+ /** `setOpen(false)`, for an action inside the card. */
13
+ close: () => void;
14
+ /** Whether the scrim, Android back and the iOS escape gesture close it. */
15
+ isDismissible: boolean;
16
+ /** The `nativeID` `Dialog.Title` carries and the card is labelled by. */
17
+ titleId: string;
18
+ /** The `nativeID` `Dialog.Description` carries. */
19
+ descriptionId: string;
20
+ };
21
+
22
+ /**
23
+ * What the parts share that a caller has no use for: where focus goes on open
24
+ * and on close, and the card's size for the footer.
25
+ */
26
+ export type DialogInternalValue = DialogContextValue & {
27
+ /** The trigger, so focus can return to it on close. */
28
+ triggerRef: RefObject<DialogFocusTarget | null>;
29
+ /** The title, so focus can land on it on open. */
30
+ titleRef: RefObject<DialogFocusTarget | null>;
31
+ };
32
+
33
+ const DialogContext = createContext<DialogInternalValue | null>(null);
34
+
35
+ /** The card's size, provided by `Dialog.Content` so the footer can choose its direction. */
36
+ const DialogSizeContext = createContext<DialogSize>("md");
37
+
38
+ /**
39
+ * Supplies the dialog's state to its parts.
40
+ *
41
+ * Lives in its own module, importing nothing but `dialog.variants`' types, so a
42
+ * part can read it without importing `./dialog` — that import would close a
43
+ * cycle, and Metro serves a partially initialised module for one.
44
+ */
45
+ export function DialogProvider({ value, children }: { value: DialogInternalValue; children: ReactNode }): ReactElement {
46
+ return <DialogContext value={value}>{children}</DialogContext>;
47
+ }
48
+ DialogProvider.displayName = "DelacourUI.Dialog.Provider";
49
+
50
+ /** Supplies the card's size to the parts inside `Dialog.Content`. */
51
+ export function DialogSizeProvider({ value, children }: { value: DialogSize; children: ReactNode }): ReactElement {
52
+ return <DialogSizeContext value={value}>{children}</DialogSizeContext>;
53
+ }
54
+ DialogSizeProvider.displayName = "DelacourUI.Dialog.SizeProvider";
55
+
56
+ /** The enclosing dialog's context, or null outside a `<Dialog>`. */
57
+ export function useDialogContext(): DialogContextValue | null {
58
+ return use(DialogContext);
59
+ }
60
+
61
+ /**
62
+ * Reads the enclosing dialog's open state, its setter and its ids.
63
+ *
64
+ * Lets a custom child close the dialog — a form's submit, say — without the
65
+ * caller threading a setter down. Throws outside a `<Dialog>`.
66
+ */
67
+ export function useDialog(): DialogContextValue {
68
+ const context = use(DialogContext);
69
+ if (!context) {
70
+ throw new Error("useDialog must be called inside a <Dialog>.");
71
+ }
72
+ return context;
73
+ }
74
+
75
+ /**
76
+ * The full context, for a part that cannot work without one. Internal: not
77
+ * re-exported from `index.ts`.
78
+ */
79
+ export function useDialogPart(component: string): DialogInternalValue {
80
+ const context = use(DialogContext);
81
+ if (!context) {
82
+ throw new Error(`${component} must be rendered inside a <Dialog>.`);
83
+ }
84
+ return context;
85
+ }
86
+
87
+ /** The card's size. `md` outside `Dialog.Content`. */
88
+ export function useDialogSize(): DialogSize {
89
+ return use(DialogSizeContext);
90
+ }