@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,46 @@
1
+ import type { ReactElement } from "react";
2
+ import { ScrollView, type ScrollViewProps, View, type ViewProps } from "react-native";
3
+ import { cn } from "../../lib/cn";
4
+ import { drawerVariants } from "./drawer.variants";
5
+
6
+ export type DrawerBodyProps =
7
+ /** Scrolls when its content is taller than the panel. The default. */
8
+ | ({ isScrollable?: true; className?: string; contentContainerClassName?: string } & ScrollViewProps)
9
+ /** A plain column, for content that fits — a few chips and a slider. */
10
+ | ({ isScrollable: false; className?: string } & ViewProps);
11
+
12
+ /**
13
+ * The panel's content, between the header and the footer, taking the height
14
+ * left over.
15
+ *
16
+ * Scrolls by default. The panel's pan waits for travel along its own axis and
17
+ * fails on travel across it, so a vertical scroll in a start or end drawer
18
+ * never moves the drawer. A top or bottom drawer's pan runs along the same axis
19
+ * as a vertical scroll — keep their content short, or reach for a `BottomSheet`.
20
+ *
21
+ * @example
22
+ * <Drawer.Body>
23
+ * <ListGroup>…</ListGroup>
24
+ * </Drawer.Body>
25
+ *
26
+ * @example
27
+ * <Drawer.Body isScrollable={false}>…</Drawer.Body>
28
+ */
29
+ export function DrawerBody(props: DrawerBodyProps): ReactElement {
30
+ const slots = drawerVariants();
31
+
32
+ if (props.isScrollable === false) {
33
+ const { isScrollable: _isScrollable, className, ...rest } = props;
34
+ return <View className={cn(slots.body(), slots.bodyContent(), className)} {...rest} />;
35
+ }
36
+
37
+ const { isScrollable: _isScrollable, className, contentContainerClassName, ...rest } = props;
38
+ return (
39
+ <ScrollView
40
+ className={slots.body({ className })}
41
+ contentContainerClassName={slots.bodyContent({ className: contentContainerClassName })}
42
+ {...rest}
43
+ />
44
+ );
45
+ }
46
+ DrawerBody.displayName = "DelacourUI.Drawer.Body";
@@ -0,0 +1,76 @@
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 { useDrawerPart } from "./drawer.context";
7
+ import { DRAWER_CLOSE_HIT_SLOP, drawerVariants } from "./drawer.variants";
8
+
9
+ export type DrawerCloseProps =
10
+ /** Donates the close to the single child — a `Button` in the footer, a navigation row. */
11
+ | ({ asChild: true; children: ReactElement } & Omit<PressableProps, "asChild" | "children">)
12
+ /** The ✕ glyph. */
13
+ | ({ asChild?: false; accessibilityLabel?: string } & Omit<PressableProps, "asChild" | "children">);
14
+
15
+ /**
16
+ * Closes the drawer.
17
+ *
18
+ * Without children it is a ✕: this library's `Pressable` with `fade` feedback
19
+ * — a scale on a glyph this small reads as a jitter — 8pt of slop because a
20
+ * bare glyph has no capsule to bring it toward 44pt, and `"Close"` as its
21
+ * label. `Drawer.Header` writes one at its trailing edge unless told not to.
22
+ *
23
+ * With `asChild` it donates the close to its child, chained ahead of the
24
+ * child's own `onPress` — a navigation row that should also shut the menu.
25
+ * Either way it is the same `setOpen(false)` the scrim, the swipe and the back
26
+ * button take, and it closes even a non-dismissible drawer: it is one of the
27
+ * drawer's own actions.
28
+ *
29
+ * @example
30
+ * <Drawer.Close asChild>
31
+ * <Button variant="secondary">Done</Button>
32
+ * </Drawer.Close>
33
+ */
34
+ export function DrawerClose(props: DrawerCloseProps): ReactElement {
35
+ const { close } = useDrawerPart("Drawer.Close");
36
+ const { onPress } = props;
37
+
38
+ const handlePress = useCallback(() => {
39
+ close();
40
+ onPress?.();
41
+ }, [close, onPress]);
42
+
43
+ if (props.asChild) {
44
+ const { asChild: _asChild, children, onPress: _onPress, ...rest } = props;
45
+ return (
46
+ <Slot onPress={handlePress} {...rest}>
47
+ {children}
48
+ </Slot>
49
+ );
50
+ }
51
+
52
+ const {
53
+ asChild: _asChild,
54
+ accessibilityLabel = "Close",
55
+ className,
56
+ feedback = "fade",
57
+ hitSlop = DRAWER_CLOSE_HIT_SLOP,
58
+ onPress: _onPress,
59
+ ...rest
60
+ } = props;
61
+
62
+ return (
63
+ <Pressable
64
+ accessibilityLabel={accessibilityLabel}
65
+ accessibilityRole="button"
66
+ className={drawerVariants().close({ className })}
67
+ feedback={feedback}
68
+ hitSlop={hitSlop}
69
+ onPress={handlePress}
70
+ {...rest}
71
+ >
72
+ <Icon color="muted-foreground" icon={IconCrossSmall} />
73
+ </Pressable>
74
+ );
75
+ }
76
+ DrawerClose.displayName = "DelacourUI.Drawer.Close";
@@ -0,0 +1,186 @@
1
+ import { type ReactElement, useCallback, useEffect, useId, useMemo } from "react";
2
+ import {
3
+ AccessibilityInfo,
4
+ findNodeHandle,
5
+ I18nManager,
6
+ useWindowDimensions,
7
+ View,
8
+ type ViewProps,
9
+ } from "react-native";
10
+ import { GestureDetector } from "react-native-gesture-handler";
11
+ import Animated, { useAnimatedReaction, 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 DrawerFocusTarget, DrawerPanelProvider, type DrawerPanelValue, useDrawerPart } from "./drawer.context";
15
+ import {
16
+ type DrawerSide,
17
+ type DrawerSize,
18
+ drawerVariants,
19
+ resolveDrawerDragFraction,
20
+ resolveDrawerEdge,
21
+ resolveDrawerExtent,
22
+ resolveDrawerFrameExtent,
23
+ resolveDrawerInsets,
24
+ resolveDrawerOffset,
25
+ } from "./drawer.variants";
26
+ import { useDrawerPan } from "./use-drawer-pan";
27
+
28
+ export type DrawerContentProps = ViewProps & {
29
+ className?: string;
30
+ /** Classes for the scrim behind the panel. */
31
+ scrimClassName?: string;
32
+ /** The edge it opens from. `start` and `end` follow the layout direction. Default `"start"`. */
33
+ side?: DrawerSide;
34
+ /** The panel's width (start, end) or height (top, bottom): `sm` 62% to 280, `md` 78% to 320, `lg` 88% to 400, `full` 94%. Default `"md"`. */
35
+ size?: DrawerSize;
36
+ /** Whether a swipe toward the edge dismisses it. Default true. */
37
+ isSwipeDismissible?: boolean;
38
+ };
39
+
40
+ /** Moves VoiceOver / TalkBack focus to a mounted element. A no-op when it has gone. */
41
+ function focus(target: DrawerFocusTarget | null): void {
42
+ if (target === null) return;
43
+ const handle = findNodeHandle(target as Parameters<typeof findNodeHandle>[0]);
44
+ if (handle !== null) AccessibilityInfo.setAccessibilityFocus(handle);
45
+ }
46
+
47
+ /**
48
+ * The panel, its scrim and the portal they draw through.
49
+ *
50
+ * Renders nothing until the drawer opens, and stays mounted through the exit
51
+ * (`useOverlayPresence`), so the portal, the z-order and the back button all
52
+ * follow presence. It draws in the `modal` band — over the navigator's header
53
+ * and over an open bottom sheet.
54
+ *
55
+ * - **Geometry.** `side` resolves to a physical edge under `I18nManager.isRTL`;
56
+ * `size` to an extent along that edge's axis. The panel pads the safe-area
57
+ * insets on every side that meets a screen edge.
58
+ * - **Motion.** Opening and closing slide the panel along `progress`; under
59
+ * reduce motion it fades in place instead. A swipe toward the edge drags it
60
+ * and thins the scrim with it; see `useDrawerPan` for the release.
61
+ * - **Dismissal.** A scrim tap, Android back, the iOS escape gesture and the
62
+ * swipe close a dismissible drawer. With `isDismissible={false}` the scrim
63
+ * still takes the touch and only the drawer's own actions close it.
64
+ * - **Accessibility.** The panel is modal to assistive technology, a `dialog`
65
+ * labelled by the title; focus moves to the title once it has entered and
66
+ * back to the trigger, if still mounted, once it has gone.
67
+ *
68
+ * @example
69
+ * <Drawer.Content side="end" size="lg">
70
+ * <Drawer.Header>…</Drawer.Header>
71
+ * <Drawer.Body>…</Drawer.Body>
72
+ * </Drawer.Content>
73
+ */
74
+ export function DrawerContent({
75
+ className,
76
+ scrimClassName,
77
+ side = "start",
78
+ size = "md",
79
+ isSwipeDismissible = true,
80
+ children,
81
+ style,
82
+ ...props
83
+ }: DrawerContentProps): ReactElement | null {
84
+ const { isOpen, close, isDismissible, titleId, titleRef, triggerRef } = useDrawerPart("Drawer.Content");
85
+ const id = useId();
86
+
87
+ const isRTL = I18nManager.isRTL;
88
+ const edge = resolveDrawerEdge(side, isRTL);
89
+ const isHorizontal = edge === "left" || edge === "right";
90
+ const window = useWindowDimensions();
91
+ const safeArea = useSafeAreaInsets();
92
+ const insets = resolveDrawerInsets(edge, safeArea);
93
+ const extent = resolveDrawerFrameExtent(
94
+ edge,
95
+ resolveDrawerExtent(size, isHorizontal ? window.width : window.height),
96
+ insets
97
+ );
98
+
99
+ const handleEntered = useCallback(() => focus(titleRef.current), [titleRef]);
100
+ const handleExited = useCallback(() => focus(triggerRef.current), [triggerRef]);
101
+ const { isPresent, isReduced, progress } = useOverlayPresence({
102
+ isOpen,
103
+ onEntered: handleEntered,
104
+ onExited: handleExited,
105
+ });
106
+
107
+ useOverlayBackHandler({ id, isEnabled: isPresent && isDismissible, onBack: close });
108
+
109
+ const drag = useSharedValue(0);
110
+ useEffect(() => {
111
+ if (isOpen) drag.value = 0;
112
+ }, [isOpen, drag]);
113
+
114
+ const pan = useDrawerPan({
115
+ drag,
116
+ edge,
117
+ extent,
118
+ isEnabled: isSwipeDismissible && isDismissible,
119
+ onDismiss: close,
120
+ });
121
+
122
+ // The scrim fades with presence and thins as a drag carries the panel out.
123
+ // A shared value of its own, because `Overlay.Scrim` takes a writable one.
124
+ const scrimProgress = useSharedValue(0);
125
+ useAnimatedReaction(
126
+ () => progress.value * (1 - resolveDrawerDragFraction(edge, drag.value, extent)),
127
+ (visibility) => {
128
+ scrimProgress.value = visibility;
129
+ },
130
+ [edge, extent]
131
+ );
132
+
133
+ const panelStyle = useAnimatedStyle(() => {
134
+ const slide = isReduced ? { translateX: 0, translateY: 0 } : resolveDrawerOffset(edge, extent, progress.value);
135
+ return {
136
+ opacity: isReduced ? progress.value : 1,
137
+ transform: [
138
+ { translateX: slide.translateX + (isHorizontal ? drag.value : 0) },
139
+ { translateY: slide.translateY + (isHorizontal ? 0 : drag.value) },
140
+ ],
141
+ };
142
+ }, [edge, extent, isHorizontal, isReduced]);
143
+
144
+ const panel = useMemo<DrawerPanelValue>(() => ({ edge, side }), [edge, side]);
145
+
146
+ if (!isPresent) return null;
147
+
148
+ const slots = drawerVariants({ edge });
149
+ const onEscape = isDismissible ? close : undefined;
150
+ const frame = {
151
+ height: isHorizontal ? undefined : extent,
152
+ paddingBottom: insets.bottom,
153
+ paddingLeft: insets.left,
154
+ paddingRight: insets.right,
155
+ paddingTop: insets.top,
156
+ width: isHorizontal ? extent : undefined,
157
+ };
158
+
159
+ return (
160
+ <Overlay.Portal id={id} layer="modal">
161
+ <Overlay.Scrim
162
+ className={slots.scrim({ className: scrimClassName })}
163
+ onDismiss={onEscape}
164
+ progress={scrimProgress}
165
+ />
166
+ <View className={slots.positioner()} pointerEvents="box-none" style={{ direction: "ltr" }}>
167
+ <GestureDetector gesture={pan}>
168
+ <Animated.View
169
+ accessibilityLabelledBy={titleId}
170
+ accessibilityViewIsModal
171
+ className={slots.content({ className })}
172
+ onAccessibilityEscape={onEscape}
173
+ role="dialog"
174
+ style={[frame, panelStyle, style]}
175
+ {...props}
176
+ >
177
+ <View className={slots.inner()} style={{ direction: isRTL ? "rtl" : "ltr" }}>
178
+ <DrawerPanelProvider value={panel}>{children}</DrawerPanelProvider>
179
+ </View>
180
+ </Animated.View>
181
+ </GestureDetector>
182
+ </View>
183
+ </Overlay.Portal>
184
+ );
185
+ }
186
+ DrawerContent.displayName = "DelacourUI.Drawer.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 { useDrawerPart } from "./drawer.context";
5
+
6
+ export type DrawerDescriptionProps = 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. `color` is an ordinary prop. It has no slot in `drawerVariants` —
13
+ * see the note there.
14
+ *
15
+ * @example
16
+ * <Drawer.Description>Signed in as aria@harbour.studio</Drawer.Description>
17
+ */
18
+ export function DrawerDescription({ className, color = "muted", ...props }: DrawerDescriptionProps): ReactElement {
19
+ const { descriptionId } = useDrawerPart("Drawer.Description");
20
+
21
+ return <Text.Paragraph className={cn(className)} color={color} nativeID={descriptionId} {...props} />;
22
+ }
23
+ DrawerDescription.displayName = "DelacourUI.Drawer.Description";
@@ -0,0 +1,21 @@
1
+ import type { ReactElement } from "react";
2
+ import { View, type ViewProps } from "react-native";
3
+ import { drawerVariants } from "./drawer.variants";
4
+
5
+ export type DrawerFooterProps = ViewProps & { className?: string };
6
+
7
+ /**
8
+ * Actions at the panel's far end, under a hairline: a row, right-aligned in a
9
+ * left-to-right layout. It pushes itself to the panel's end with `mt-auto`, so
10
+ * it sits at the bottom however short the content is — with or without a body.
11
+ *
12
+ * @example
13
+ * <Drawer.Footer>
14
+ * <Button variant="secondary" onPress={reset}>Reset</Button>
15
+ * <Drawer.Close asChild><Button>Show results</Button></Drawer.Close>
16
+ * </Drawer.Footer>
17
+ */
18
+ export function DrawerFooter({ className, ...props }: DrawerFooterProps): ReactElement {
19
+ return <View className={drawerVariants().footer({ className })} {...props} />;
20
+ }
21
+ DrawerFooter.displayName = "DelacourUI.Drawer.Footer";
@@ -0,0 +1,42 @@
1
+ import type { ReactElement } from "react";
2
+ import { View, type ViewProps } from "react-native";
3
+ import { drawerVariants } from "./drawer.variants";
4
+ import { DrawerClose } from "./drawer-close";
5
+
6
+ export type DrawerHeaderProps = ViewProps & {
7
+ className?: string;
8
+ /** Hide the ✕ the header writes at its trailing edge. Default false. */
9
+ isCloseHidden?: boolean;
10
+ };
11
+
12
+ /**
13
+ * The top of the panel: `Drawer.Title` and `Drawer.Description` in a column
14
+ * that takes the row's slack, and a ✕ at the trailing edge.
15
+ *
16
+ * The ✕ is in the flow, not pinned to a corner, so a long title wraps before
17
+ * it rather than under it, and it lands on the correct side under RTL with no
18
+ * rule of its own. `isCloseHidden` drops it — a navigation drawer whose rows
19
+ * all close it, or a drawer with a `Close` in its footer.
20
+ *
21
+ * @example
22
+ * <Drawer.Header>
23
+ * <Drawer.Title>Filters</Drawer.Title>
24
+ * <Drawer.Description>12 results</Drawer.Description>
25
+ * </Drawer.Header>
26
+ */
27
+ export function DrawerHeader({
28
+ className,
29
+ isCloseHidden = false,
30
+ children,
31
+ ...props
32
+ }: DrawerHeaderProps): ReactElement {
33
+ const slots = drawerVariants();
34
+
35
+ return (
36
+ <View className={slots.header({ className })} {...props}>
37
+ <View className={slots.heading()}>{children}</View>
38
+ {isCloseHidden ? null : <DrawerClose />}
39
+ </View>
40
+ );
41
+ }
42
+ DrawerHeader.displayName = "DelacourUI.Drawer.Header";
@@ -0,0 +1,35 @@
1
+ import { type ReactElement, useCallback } from "react";
2
+ import { cn } from "../../lib/cn";
3
+ import { Text, type TextPresetProps } from "../text";
4
+ import { type DrawerFocusTarget, useDrawerPart } from "./drawer.context";
5
+
6
+ /**
7
+ * A `Text.Header`'s props less `ref`: the drawer holds the title's ref itself,
8
+ * to move accessibility focus onto it.
9
+ */
10
+ export type DrawerTitleProps = Omit<TextPresetProps, "ref">;
11
+
12
+ /**
13
+ * The drawer's heading.
14
+ *
15
+ * *Is* a `Text.Header`. It carries the `nativeID` the panel is labelled by on
16
+ * Android, and it is where accessibility focus lands once the panel has
17
+ * finished entering, so a screen reader announces what the drawer is first.
18
+ *
19
+ * @example
20
+ * <Drawer.Title>Menu</Drawer.Title>
21
+ */
22
+ export function DrawerTitle({ className, ...props }: DrawerTitleProps): ReactElement {
23
+ const { titleId, titleRef } = useDrawerPart("Drawer.Title");
24
+ const setTitleRef = useCallback(
25
+ (node: DrawerFocusTarget | null) => {
26
+ titleRef.current = node;
27
+ },
28
+ [titleRef]
29
+ );
30
+
31
+ return (
32
+ <Text.Header accessibilityRole="header" className={cn(className)} nativeID={titleId} ref={setTitleRef} {...props} />
33
+ );
34
+ }
35
+ DrawerTitle.displayName = "DelacourUI.Drawer.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 { useDrawerPart } from "./drawer.context";
6
+
7
+ export type DrawerTriggerProps = PressableProps;
8
+
9
+ /**
10
+ * The control that opens the drawer.
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 drawer would never
16
+ * open. With `asChild` the open is chained ahead of the child's own `onPress`.
17
+ *
18
+ * The trigger's ref is kept so accessibility focus can return to it when the
19
+ * drawer closes.
20
+ *
21
+ * @example
22
+ * <Drawer.Trigger asChild>
23
+ * <Button accessibilityLabel="Menu" size="icon-md" variant="ghost">
24
+ * <Icon icon={IconMenu} />
25
+ * </Button>
26
+ * </Drawer.Trigger>
27
+ */
28
+ export function DrawerTrigger({ asChild = false, children, onPress, ref, ...props }: DrawerTriggerProps): ReactElement {
29
+ const { setOpen, triggerRef } = useDrawerPart("Drawer.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
+ DrawerTrigger.displayName = "DelacourUI.Drawer.Trigger";
@@ -0,0 +1,114 @@
1
+ import { createContext, type ReactElement, type ReactNode, type RefObject, use } from "react";
2
+ import { I18nManager } from "react-native";
3
+ import { type DrawerEdge, type DrawerSide, resolveDrawerEdge } from "./drawer.variants";
4
+
5
+ /** Anything `AccessibilityInfo.setAccessibilityFocus` can be pointed at through `findNodeHandle`. */
6
+ export type DrawerFocusTarget = object;
7
+
8
+ export type DrawerContextValue = {
9
+ /** Whether the drawer is open — the owner's state, not the exit animation's. */
10
+ isOpen: boolean;
11
+ /** Opens or closes it. Reports through `onOpenChange` only when the value changes. */
12
+ setOpen: (isOpen: boolean) => void;
13
+ /** `setOpen(false)`, for an action inside the panel. */
14
+ close: () => void;
15
+ /** Whether the scrim, Android back and the iOS escape gesture close it. */
16
+ isDismissible: boolean;
17
+ /** The logical side `Drawer.Content` opens from. `"start"` outside the content. */
18
+ side: DrawerSide;
19
+ /** The physical edge that side resolves to under the layout direction. */
20
+ edge: DrawerEdge;
21
+ };
22
+
23
+ /** The root's state, before `Drawer.Content` adds its side. */
24
+ export type DrawerRootValue = Omit<DrawerContextValue, "side" | "edge"> & {
25
+ /** The `nativeID` `Drawer.Title` carries and the panel is labelled by. */
26
+ titleId: string;
27
+ /** The `nativeID` `Drawer.Description` carries. */
28
+ descriptionId: string;
29
+ /** The trigger, so focus can return to it on close. */
30
+ triggerRef: RefObject<DrawerFocusTarget | null>;
31
+ /** The title, so focus can land on it on open. */
32
+ titleRef: RefObject<DrawerFocusTarget | null>;
33
+ };
34
+
35
+ /** The side and edge `Drawer.Content` resolved, for the parts inside it. */
36
+ export type DrawerPanelValue = { side: DrawerSide; edge: DrawerEdge };
37
+
38
+ const DrawerContext = createContext<DrawerRootValue | null>(null);
39
+
40
+ const DrawerPanelContext = createContext<DrawerPanelValue>({
41
+ edge: resolveDrawerEdge("start", I18nManager.isRTL),
42
+ side: "start",
43
+ });
44
+
45
+ /**
46
+ * Supplies the drawer's state to its parts.
47
+ *
48
+ * Lives in its own module, importing nothing but `drawer.variants`, so a part
49
+ * can read it without importing `./drawer` — that import would close a cycle,
50
+ * and Metro serves a partially initialised module for one.
51
+ */
52
+ export function DrawerProvider({ value, children }: { value: DrawerRootValue; children: ReactNode }): ReactElement {
53
+ return <DrawerContext value={value}>{children}</DrawerContext>;
54
+ }
55
+ DrawerProvider.displayName = "DelacourUI.Drawer.Provider";
56
+
57
+ /** Supplies the resolved side and edge to the parts inside `Drawer.Content`. */
58
+ export function DrawerPanelProvider({
59
+ value,
60
+ children,
61
+ }: {
62
+ value: DrawerPanelValue;
63
+ children: ReactNode;
64
+ }): ReactElement {
65
+ return <DrawerPanelContext value={value}>{children}</DrawerPanelContext>;
66
+ }
67
+ DrawerPanelProvider.displayName = "DelacourUI.Drawer.PanelProvider";
68
+
69
+ /** The enclosing drawer's context, or null outside a `<Drawer>`. */
70
+ export function useDrawerContext(): DrawerContextValue | null {
71
+ const root = use(DrawerContext);
72
+ const panel = use(DrawerPanelContext);
73
+ if (!root) return null;
74
+ return {
75
+ close: root.close,
76
+ edge: panel.edge,
77
+ isDismissible: root.isDismissible,
78
+ isOpen: root.isOpen,
79
+ setOpen: root.setOpen,
80
+ side: panel.side,
81
+ };
82
+ }
83
+
84
+ /**
85
+ * Reads the enclosing drawer's open state, its setter and — inside
86
+ * `Drawer.Content` — the side it opened from and the edge that resolved to.
87
+ *
88
+ * Lets a custom row close the drawer after it navigates, without the caller
89
+ * threading a setter down. Throws outside a `<Drawer>`.
90
+ */
91
+ export function useDrawer(): DrawerContextValue {
92
+ const context = useDrawerContext();
93
+ if (!context) {
94
+ throw new Error("useDrawer must be called inside a <Drawer>.");
95
+ }
96
+ return context;
97
+ }
98
+
99
+ /**
100
+ * The root's full context, for a part that cannot work without one. Internal:
101
+ * not re-exported from `index.ts`.
102
+ */
103
+ export function useDrawerPart(component: string): DrawerRootValue {
104
+ const context = use(DrawerContext);
105
+ if (!context) {
106
+ throw new Error(`${component} must be rendered inside a <Drawer>.`);
107
+ }
108
+ return context;
109
+ }
110
+
111
+ /** The side and edge `Drawer.Content` resolved. */
112
+ export function useDrawerPanel(): DrawerPanelValue {
113
+ return use(DrawerPanelContext);
114
+ }