@delacour/react-native-ui 0.1.0-alpha.3 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (482) hide show
  1. package/README.md +11 -7
  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 +5 -0
  34. package/src/components/badge/badge.tsx +6 -1
  35. package/src/components/bottom-sheet/AGENTS.md +186 -221
  36. package/src/components/bottom-sheet/bottom-sheet-background.tsx +31 -22
  37. package/src/components/bottom-sheet/bottom-sheet-close.tsx +23 -30
  38. package/src/components/bottom-sheet/bottom-sheet-container.tsx +54 -207
  39. package/src/components/bottom-sheet/bottom-sheet-content.tsx +25 -48
  40. package/src/components/bottom-sheet/bottom-sheet-description.tsx +8 -2
  41. package/src/components/bottom-sheet/bottom-sheet-flat-list.tsx +65 -0
  42. package/src/components/bottom-sheet/bottom-sheet-footer.tsx +53 -120
  43. package/src/components/bottom-sheet/bottom-sheet-handle.tsx +31 -26
  44. package/src/components/bottom-sheet/bottom-sheet-legend-list.tsx +69 -0
  45. package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +25 -147
  46. package/src/components/bottom-sheet/bottom-sheet-provider.tsx +42 -0
  47. package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +38 -52
  48. package/src/components/bottom-sheet/bottom-sheet-section-list.tsx +58 -0
  49. package/src/components/bottom-sheet/bottom-sheet-steps.tsx +88 -0
  50. package/src/components/bottom-sheet/bottom-sheet-text-input.tsx +44 -0
  51. package/src/components/bottom-sheet/bottom-sheet-title.tsx +12 -8
  52. package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +19 -37
  53. package/src/components/bottom-sheet/bottom-sheet.tsx +113 -60
  54. package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +47 -70
  55. package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -105
  56. package/src/components/bottom-sheet/index.ts +39 -17
  57. package/src/components/bottom-sheet/use-bottom-sheet-input.ts +16 -115
  58. package/src/components/button/AGENTS.md +45 -11
  59. package/src/components/button/button-group.tsx +44 -8
  60. package/src/components/button/button.context.tsx +10 -0
  61. package/src/components/button/button.tsx +2 -1
  62. package/src/components/button/button.variants.test.ts +154 -0
  63. package/src/components/button/button.variants.ts +71 -6
  64. package/src/components/button/index.ts +2 -0
  65. package/src/components/calendar/AGENTS.md +142 -0
  66. package/src/components/calendar/calendar-caption.tsx +116 -0
  67. package/src/components/calendar/calendar-day.tsx +103 -0
  68. package/src/components/calendar/calendar-grid.tsx +183 -0
  69. package/src/components/calendar/calendar-header.tsx +34 -0
  70. package/src/components/calendar/calendar-nav.tsx +51 -0
  71. package/src/components/calendar/calendar-picker.tsx +128 -0
  72. package/src/components/calendar/calendar-weekdays.tsx +48 -0
  73. package/src/components/calendar/calendar.context.tsx +108 -0
  74. package/src/components/calendar/calendar.date.test.ts +456 -0
  75. package/src/components/calendar/calendar.date.ts +584 -0
  76. package/src/components/calendar/calendar.tsx +389 -0
  77. package/src/components/calendar/calendar.types.ts +87 -0
  78. package/src/components/calendar/calendar.variants.test.ts +363 -0
  79. package/src/components/calendar/calendar.variants.ts +426 -0
  80. package/src/components/calendar/index.ts +96 -0
  81. package/src/components/card/AGENTS.md +88 -0
  82. package/src/components/card/card-action.tsx +16 -0
  83. package/src/components/card/card-content.tsx +12 -0
  84. package/src/components/card/card-description.tsx +12 -0
  85. package/src/components/card/card-footer.tsx +30 -0
  86. package/src/components/card/card-header.tsx +45 -0
  87. package/src/components/card/card-title.tsx +25 -0
  88. package/src/components/card/card.context.tsx +64 -0
  89. package/src/components/card/card.tsx +94 -0
  90. package/src/components/card/card.types.ts +13 -0
  91. package/src/components/card/card.variants.test.ts +240 -0
  92. package/src/components/card/card.variants.ts +177 -0
  93. package/src/components/card/index.ts +15 -0
  94. package/src/components/chart/AGENTS.md +9 -1
  95. package/src/components/chart/chart.tsx +8 -1
  96. package/src/components/chip/AGENTS.md +113 -0
  97. package/src/components/chip/chip-close-button.tsx +48 -0
  98. package/src/components/chip/chip-end-content.tsx +15 -0
  99. package/src/components/chip/chip-label.tsx +21 -0
  100. package/src/components/chip/chip-start-content.tsx +16 -0
  101. package/src/components/chip/chip.context.tsx +66 -0
  102. package/src/components/chip/chip.tsx +268 -0
  103. package/src/components/chip/chip.types.ts +10 -0
  104. package/src/components/chip/chip.variants.test.ts +335 -0
  105. package/src/components/chip/chip.variants.ts +321 -0
  106. package/src/components/chip/index.ts +24 -0
  107. package/src/components/collapsible/AGENTS.md +97 -0
  108. package/src/components/collapsible/collapsible-content.tsx +97 -0
  109. package/src/components/collapsible/collapsible-description.tsx +17 -0
  110. package/src/components/collapsible/collapsible-indicator.tsx +92 -0
  111. package/src/components/collapsible/collapsible-title.tsx +17 -0
  112. package/src/components/collapsible/collapsible-trigger.tsx +133 -0
  113. package/src/components/collapsible/collapsible.context.tsx +91 -0
  114. package/src/components/collapsible/collapsible.tsx +167 -0
  115. package/src/components/collapsible/collapsible.types.ts +9 -0
  116. package/src/components/collapsible/collapsible.variants.test.ts +338 -0
  117. package/src/components/collapsible/collapsible.variants.ts +228 -0
  118. package/src/components/collapsible/index.ts +32 -0
  119. package/src/components/context-menu/AGENTS.md +103 -0
  120. package/src/components/context-menu/context-menu-content.tsx +64 -0
  121. package/src/components/context-menu/context-menu-item.tsx +22 -0
  122. package/src/components/context-menu/context-menu-preview.tsx +60 -0
  123. package/src/components/context-menu/context-menu-trigger.tsx +160 -0
  124. package/src/components/context-menu/context-menu.context.tsx +91 -0
  125. package/src/components/context-menu/context-menu.tsx +90 -0
  126. package/src/components/context-menu/context-menu.variants.test.ts +266 -0
  127. package/src/components/context-menu/context-menu.variants.ts +147 -0
  128. package/src/components/context-menu/index.ts +18 -0
  129. package/src/components/dialog/AGENTS.md +97 -0
  130. package/src/components/dialog/dialog-body.tsx +21 -0
  131. package/src/components/dialog/dialog-close.tsx +82 -0
  132. package/src/components/dialog/dialog-content.tsx +153 -0
  133. package/src/components/dialog/dialog-description.tsx +23 -0
  134. package/src/components/dialog/dialog-footer.tsx +32 -0
  135. package/src/components/dialog/dialog-header.tsx +20 -0
  136. package/src/components/dialog/dialog-title.tsx +42 -0
  137. package/src/components/dialog/dialog-trigger.tsx +54 -0
  138. package/src/components/dialog/dialog.context.tsx +90 -0
  139. package/src/components/dialog/dialog.tsx +119 -0
  140. package/src/components/dialog/dialog.variants.test.ts +224 -0
  141. package/src/components/dialog/dialog.variants.ts +173 -0
  142. package/src/components/dialog/index.ts +30 -0
  143. package/src/components/drawer/AGENTS.md +123 -0
  144. package/src/components/drawer/drawer-body.tsx +46 -0
  145. package/src/components/drawer/drawer-close.tsx +76 -0
  146. package/src/components/drawer/drawer-content.tsx +186 -0
  147. package/src/components/drawer/drawer-description.tsx +23 -0
  148. package/src/components/drawer/drawer-footer.tsx +21 -0
  149. package/src/components/drawer/drawer-header.tsx +42 -0
  150. package/src/components/drawer/drawer-title.tsx +35 -0
  151. package/src/components/drawer/drawer-trigger.tsx +54 -0
  152. package/src/components/drawer/drawer.context.tsx +114 -0
  153. package/src/components/drawer/drawer.tsx +119 -0
  154. package/src/components/drawer/drawer.variants.test.ts +392 -0
  155. package/src/components/drawer/drawer.variants.ts +244 -0
  156. package/src/components/drawer/index.ts +41 -0
  157. package/src/components/drawer/use-drawer-pan.ts +108 -0
  158. package/src/components/empty-state/AGENTS.md +90 -0
  159. package/src/components/empty-state/empty-state-content.tsx +17 -0
  160. package/src/components/empty-state/empty-state-description.tsx +12 -0
  161. package/src/components/empty-state/empty-state-header.tsx +17 -0
  162. package/src/components/empty-state/empty-state-media.tsx +56 -0
  163. package/src/components/empty-state/empty-state-title.tsx +18 -0
  164. package/src/components/empty-state/empty-state.context.tsx +65 -0
  165. package/src/components/empty-state/empty-state.tsx +84 -0
  166. package/src/components/empty-state/empty-state.types.ts +14 -0
  167. package/src/components/empty-state/empty-state.variants.test.ts +245 -0
  168. package/src/components/empty-state/empty-state.variants.ts +132 -0
  169. package/src/components/empty-state/index.ts +20 -0
  170. package/src/components/fab/AGENTS.md +119 -0
  171. package/src/components/fab/fab-action.tsx +118 -0
  172. package/src/components/fab/fab-group.tsx +214 -0
  173. package/src/components/fab/fab-label.tsx +18 -0
  174. package/src/components/fab/fab.context.tsx +125 -0
  175. package/src/components/fab/fab.tsx +156 -0
  176. package/src/components/fab/fab.types.ts +17 -0
  177. package/src/components/fab/fab.variants.test.ts +274 -0
  178. package/src/components/fab/fab.variants.ts +234 -0
  179. package/src/components/fab/index.ts +37 -0
  180. package/src/components/feedback/AGENTS.md +95 -0
  181. package/src/components/feedback/feedback-action.tsx +16 -0
  182. package/src/components/feedback/feedback-cancel.tsx +34 -0
  183. package/src/components/feedback/feedback-close.tsx +21 -0
  184. package/src/components/feedback/feedback-content.tsx +24 -0
  185. package/src/components/feedback/feedback-field.tsx +70 -0
  186. package/src/components/feedback/feedback-footer.tsx +21 -0
  187. package/src/components/feedback/feedback-panel.tsx +54 -0
  188. package/src/components/feedback/feedback-submit.tsx +74 -0
  189. package/src/components/feedback/feedback-title.tsx +34 -0
  190. package/src/components/feedback/feedback-trigger.tsx +20 -0
  191. package/src/components/feedback/feedback.context.tsx +73 -0
  192. package/src/components/feedback/feedback.tsx +135 -0
  193. package/src/components/feedback/feedback.variants.test.ts +185 -0
  194. package/src/components/feedback/feedback.variants.ts +128 -0
  195. package/src/components/feedback/index.ts +24 -0
  196. package/src/components/field/AGENTS.md +19 -0
  197. package/src/components/field/field-label.tsx +19 -5
  198. package/src/components/field/field.context.tsx +17 -0
  199. package/src/components/field/field.tsx +15 -2
  200. package/src/components/field/field.variants.test.ts +24 -0
  201. package/src/components/field/field.variants.ts +20 -0
  202. package/src/components/field/index.ts +1 -0
  203. package/src/components/item/AGENTS.md +92 -0
  204. package/src/components/item/index.ts +25 -0
  205. package/src/components/item/item-actions.tsx +28 -0
  206. package/src/components/item/item-content.tsx +15 -0
  207. package/src/components/item/item-description.tsx +12 -0
  208. package/src/components/item/item-footer.tsx +13 -0
  209. package/src/components/item/item-group.tsx +32 -0
  210. package/src/components/item/item-header.tsx +13 -0
  211. package/src/components/item/item-media.tsx +37 -0
  212. package/src/components/item/item-separator.tsx +13 -0
  213. package/src/components/item/item-title.tsx +17 -0
  214. package/src/components/item/item.context.tsx +66 -0
  215. package/src/components/item/item.tsx +218 -0
  216. package/src/components/item/item.types.ts +12 -0
  217. package/src/components/item/item.variants.test.ts +263 -0
  218. package/src/components/item/item.variants.ts +200 -0
  219. package/src/components/kpi/AGENTS.md +125 -0
  220. package/src/components/kpi/index.ts +45 -0
  221. package/src/components/kpi/kpi-action.tsx +12 -0
  222. package/src/components/kpi/kpi-content.tsx +35 -0
  223. package/src/components/kpi/kpi-footer.tsx +52 -0
  224. package/src/components/kpi/kpi-group.tsx +70 -0
  225. package/src/components/kpi/kpi-header.tsx +25 -0
  226. package/src/components/kpi/kpi-icon.tsx +28 -0
  227. package/src/components/kpi/kpi-sparkline.tsx +145 -0
  228. package/src/components/kpi/kpi-stat.tsx +20 -0
  229. package/src/components/kpi/kpi-title.tsx +19 -0
  230. package/src/components/kpi/kpi-trend.tsx +105 -0
  231. package/src/components/kpi/kpi-value.tsx +32 -0
  232. package/src/components/kpi/kpi.context.tsx +111 -0
  233. package/src/components/kpi/kpi.tsx +154 -0
  234. package/src/components/kpi/kpi.types.ts +9 -0
  235. package/src/components/kpi/kpi.variants.test.ts +428 -0
  236. package/src/components/kpi/kpi.variants.ts +347 -0
  237. package/src/components/label/AGENTS.md +89 -0
  238. package/src/components/label/index.ts +10 -0
  239. package/src/components/label/label.tsx +97 -0
  240. package/src/components/label/label.variants.test.ts +176 -0
  241. package/src/components/label/label.variants.ts +123 -0
  242. package/src/components/menu/AGENTS.md +176 -0
  243. package/src/components/menu/index.ts +44 -0
  244. package/src/components/menu/menu-background.tsx +17 -0
  245. package/src/components/menu/menu-checkbox-item.tsx +57 -0
  246. package/src/components/menu/menu-content.tsx +303 -0
  247. package/src/components/menu/menu-item.tsx +162 -0
  248. package/src/components/menu/menu-label.tsx +14 -0
  249. package/src/components/menu/menu-radio-group.tsx +50 -0
  250. package/src/components/menu/menu-radio-item.tsx +57 -0
  251. package/src/components/menu/menu-separator.tsx +11 -0
  252. package/src/components/menu/menu-sub-content.tsx +72 -0
  253. package/src/components/menu/menu-sub-trigger.tsx +50 -0
  254. package/src/components/menu/menu-sub.tsx +75 -0
  255. package/src/components/menu/menu-trigger.tsx +57 -0
  256. package/src/components/menu/menu.context.tsx +214 -0
  257. package/src/components/menu/menu.tsx +88 -0
  258. package/src/components/menu/menu.types.ts +33 -0
  259. package/src/components/menu/menu.variants.test.ts +400 -0
  260. package/src/components/menu/menu.variants.ts +353 -0
  261. package/src/components/meter/AGENTS.md +135 -0
  262. package/src/components/meter/index.ts +43 -0
  263. package/src/components/meter/meter-fill.tsx +19 -0
  264. package/src/components/meter/meter-header.tsx +17 -0
  265. package/src/components/meter/meter-label.tsx +18 -0
  266. package/src/components/meter/meter-output.tsx +42 -0
  267. package/src/components/meter/meter-segments.tsx +83 -0
  268. package/src/components/meter/meter-track.tsx +16 -0
  269. package/src/components/meter/meter.context.tsx +62 -0
  270. package/src/components/meter/meter.tsx +286 -0
  271. package/src/components/meter/meter.types.ts +30 -0
  272. package/src/components/meter/meter.variants.test.ts +430 -0
  273. package/src/components/meter/meter.variants.ts +347 -0
  274. package/src/components/overlay/AGENTS.md +89 -0
  275. package/src/components/overlay/index.ts +27 -0
  276. package/src/components/overlay/overlay-portal.tsx +84 -0
  277. package/src/components/overlay/overlay-presence.test.ts +46 -0
  278. package/src/components/overlay/overlay-presence.ts +49 -0
  279. package/src/components/overlay/overlay-registry.test.ts +96 -0
  280. package/src/components/overlay/overlay-registry.ts +99 -0
  281. package/src/components/overlay/overlay-scrim.tsx +49 -0
  282. package/src/components/overlay/overlay.context.tsx +48 -0
  283. package/src/components/overlay/overlay.tsx +118 -0
  284. package/src/components/overlay/overlay.variants.test.ts +37 -0
  285. package/src/components/overlay/overlay.variants.ts +33 -0
  286. package/src/components/overlay/use-overlay-back-handler.ts +34 -0
  287. package/src/components/overlay/use-overlay-presence.ts +87 -0
  288. package/src/components/popover/AGENTS.md +138 -0
  289. package/src/components/popover/index.ts +48 -0
  290. package/src/components/popover/popover-anchor.tsx +49 -0
  291. package/src/components/popover/popover-arrow.tsx +89 -0
  292. package/src/components/popover/popover-close.tsx +73 -0
  293. package/src/components/popover/popover-content.tsx +235 -0
  294. package/src/components/popover/popover-description.tsx +18 -0
  295. package/src/components/popover/popover-title.tsx +33 -0
  296. package/src/components/popover/popover-trigger.tsx +56 -0
  297. package/src/components/popover/popover.context.tsx +71 -0
  298. package/src/components/popover/popover.position.test.ts +354 -0
  299. package/src/components/popover/popover.position.ts +286 -0
  300. package/src/components/popover/popover.tsx +123 -0
  301. package/src/components/popover/popover.variants.test.ts +135 -0
  302. package/src/components/popover/popover.variants.ts +85 -0
  303. package/src/components/popover/use-anchor-measure.ts +76 -0
  304. package/src/components/popover/use-anchored-content.ts +219 -0
  305. package/src/components/pressable/AGENTS.md +11 -1
  306. package/src/components/pressable/pressable.tsx +7 -2
  307. package/src/components/pressable/pressable.variants.test.ts +29 -0
  308. package/src/components/pressable/pressable.variants.ts +17 -0
  309. package/src/components/progress/AGENTS.md +120 -0
  310. package/src/components/progress/index.ts +40 -0
  311. package/src/components/progress/progress-fill.tsx +152 -0
  312. package/src/components/progress/progress-header.tsx +19 -0
  313. package/src/components/progress/progress-label.tsx +27 -0
  314. package/src/components/progress/progress-output.tsx +47 -0
  315. package/src/components/progress/progress-track.tsx +38 -0
  316. package/src/components/progress/progress.context.tsx +79 -0
  317. package/src/components/progress/progress.tsx +171 -0
  318. package/src/components/progress/progress.types.ts +23 -0
  319. package/src/components/progress/progress.variants.test.ts +335 -0
  320. package/src/components/progress/progress.variants.ts +284 -0
  321. package/src/components/progress-button/AGENTS.md +116 -0
  322. package/src/components/progress-button/index.ts +36 -0
  323. package/src/components/progress-button/progress-button-done.tsx +33 -0
  324. package/src/components/progress-button/progress-button-label.tsx +27 -0
  325. package/src/components/progress-button/progress-button.context.tsx +93 -0
  326. package/src/components/progress-button/progress-button.tsx +414 -0
  327. package/src/components/progress-button/progress-button.variants.test.ts +314 -0
  328. package/src/components/progress-button/progress-button.variants.ts +236 -0
  329. package/src/components/provider/AGENTS.md +38 -25
  330. package/src/components/provider/provider.tsx +38 -11
  331. package/src/components/radio/AGENTS.md +8 -10
  332. package/src/components/radio/radio.variants.test.ts +5 -6
  333. package/src/components/radio/radio.variants.ts +6 -7
  334. package/src/components/rating/AGENTS.md +143 -0
  335. package/src/components/rating/index.ts +43 -0
  336. package/src/components/rating/rating-output.tsx +39 -0
  337. package/src/components/rating/rating-star.tsx +70 -0
  338. package/src/components/rating/rating-stars.tsx +219 -0
  339. package/src/components/rating/rating.context.tsx +79 -0
  340. package/src/components/rating/rating.tsx +163 -0
  341. package/src/components/rating/rating.variants.test.ts +347 -0
  342. package/src/components/rating/rating.variants.ts +354 -0
  343. package/src/components/screen/screen-navbar-back-button.tsx +1 -1
  344. package/src/components/selection-mode/AGENTS.md +155 -0
  345. package/src/components/selection-mode/index.ts +43 -0
  346. package/src/components/selection-mode/selection-mode-action.tsx +65 -0
  347. package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
  348. package/src/components/selection-mode/selection-mode-group.tsx +123 -0
  349. package/src/components/selection-mode/selection-mode-header.tsx +85 -0
  350. package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
  351. package/src/components/selection-mode/selection-mode-item.tsx +182 -0
  352. package/src/components/selection-mode/selection-mode.context.tsx +89 -0
  353. package/src/components/selection-mode/selection-mode.tsx +207 -0
  354. package/src/components/selection-mode/selection-mode.types.ts +36 -0
  355. package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
  356. package/src/components/selection-mode/selection-mode.variants.ts +329 -0
  357. package/src/components/skeleton/AGENTS.md +120 -0
  358. package/src/components/skeleton/index.ts +15 -0
  359. package/src/components/skeleton/skeleton-clock.ts +48 -0
  360. package/src/components/skeleton/skeleton-group.tsx +64 -0
  361. package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
  362. package/src/components/skeleton/skeleton.context.tsx +45 -0
  363. package/src/components/skeleton/skeleton.tsx +227 -0
  364. package/src/components/skeleton/skeleton.variants.test.ts +245 -0
  365. package/src/components/skeleton/skeleton.variants.ts +222 -0
  366. package/src/components/slide-button/AGENTS.md +122 -0
  367. package/src/components/slide-button/index.ts +43 -0
  368. package/src/components/slide-button/slide-button-label.tsx +70 -0
  369. package/src/components/slide-button/slide-button-thumb.tsx +93 -0
  370. package/src/components/slide-button/slide-button.context.tsx +76 -0
  371. package/src/components/slide-button/slide-button.tsx +457 -0
  372. package/src/components/slide-button/slide-button.variants.test.ts +288 -0
  373. package/src/components/slide-button/slide-button.variants.ts +324 -0
  374. package/src/components/slider/index.ts +2 -0
  375. package/src/components/slider/slider-thumb.tsx +33 -1
  376. package/src/components/slider/slider.variants.test.ts +52 -0
  377. package/src/components/slider/slider.variants.ts +56 -2
  378. package/src/components/stack-card/AGENTS.md +169 -0
  379. package/src/components/stack-card/index.ts +37 -0
  380. package/src/components/stack-card/stack-card-action.tsx +66 -0
  381. package/src/components/stack-card/stack-card-actions.tsx +18 -0
  382. package/src/components/stack-card/stack-card-card.tsx +71 -0
  383. package/src/components/stack-card/stack-card-empty.tsx +30 -0
  384. package/src/components/stack-card/stack-card-slot.tsx +90 -0
  385. package/src/components/stack-card/stack-card-stamp.tsx +76 -0
  386. package/src/components/stack-card/stack-card.context.tsx +117 -0
  387. package/src/components/stack-card/stack-card.tsx +557 -0
  388. package/src/components/stack-card/stack-card.types.ts +26 -0
  389. package/src/components/stack-card/stack-card.variants.test.ts +355 -0
  390. package/src/components/stack-card/stack-card.variants.ts +360 -0
  391. package/src/components/steps/AGENTS.md +153 -0
  392. package/src/components/steps/index.ts +45 -0
  393. package/src/components/steps/steps-connector.tsx +31 -0
  394. package/src/components/steps/steps-description.tsx +26 -0
  395. package/src/components/steps/steps-indicator.tsx +106 -0
  396. package/src/components/steps/steps-item.tsx +225 -0
  397. package/src/components/steps/steps-panel.tsx +41 -0
  398. package/src/components/steps/steps-title.tsx +32 -0
  399. package/src/components/steps/steps-track.tsx +59 -0
  400. package/src/components/steps/steps.context.tsx +123 -0
  401. package/src/components/steps/steps.tsx +166 -0
  402. package/src/components/steps/steps.variants.test.ts +388 -0
  403. package/src/components/steps/steps.variants.ts +468 -0
  404. package/src/components/surface/AGENTS.md +72 -0
  405. package/src/components/surface/index.ts +15 -0
  406. package/src/components/surface/surface.context.tsx +54 -0
  407. package/src/components/surface/surface.tsx +70 -0
  408. package/src/components/surface/surface.variants.test.ts +180 -0
  409. package/src/components/surface/surface.variants.ts +133 -0
  410. package/src/components/swipe/AGENTS.md +140 -0
  411. package/src/components/swipe/index.ts +31 -0
  412. package/src/components/swipe/swipe-action.tsx +99 -0
  413. package/src/components/swipe/swipe-end.tsx +14 -0
  414. package/src/components/swipe/swipe-group.tsx +55 -0
  415. package/src/components/swipe/swipe-panel.tsx +55 -0
  416. package/src/components/swipe/swipe-start.tsx +15 -0
  417. package/src/components/swipe/swipe.context.tsx +135 -0
  418. package/src/components/swipe/swipe.tsx +393 -0
  419. package/src/components/swipe/swipe.types.ts +7 -0
  420. package/src/components/swipe/swipe.variants.test.ts +337 -0
  421. package/src/components/swipe/swipe.variants.ts +290 -0
  422. package/src/components/switch/switch.variants.ts +2 -2
  423. package/src/components/tabs/AGENTS.md +4 -3
  424. package/src/components/tabs/tabs.variants.test.ts +1 -1
  425. package/src/components/tabs/tabs.variants.ts +4 -6
  426. package/src/components/textarea/AGENTS.md +86 -0
  427. package/src/components/textarea/index.ts +16 -0
  428. package/src/components/textarea/textarea.tsx +157 -0
  429. package/src/components/textarea/textarea.variants.test.ts +219 -0
  430. package/src/components/textarea/textarea.variants.ts +171 -0
  431. package/src/components/toast/AGENTS.md +140 -0
  432. package/src/components/toast/index.ts +74 -0
  433. package/src/components/toast/toast-action.tsx +59 -0
  434. package/src/components/toast/toast-api.ts +32 -0
  435. package/src/components/toast/toast-close.tsx +44 -0
  436. package/src/components/toast/toast-content.tsx +11 -0
  437. package/src/components/toast/toast-description.tsx +11 -0
  438. package/src/components/toast/toast-indicator.tsx +34 -0
  439. package/src/components/toast/toast-item.tsx +308 -0
  440. package/src/components/toast/toast-title.tsx +13 -0
  441. package/src/components/toast/toast-viewport.tsx +135 -0
  442. package/src/components/toast/toast.context.tsx +69 -0
  443. package/src/components/toast/toast.store.test.ts +259 -0
  444. package/src/components/toast/toast.store.ts +338 -0
  445. package/src/components/toast/toast.timer.test.ts +64 -0
  446. package/src/components/toast/toast.timer.ts +51 -0
  447. package/src/components/toast/toast.tsx +132 -0
  448. package/src/components/toast/toast.variants.test.ts +393 -0
  449. package/src/components/toast/toast.variants.ts +304 -0
  450. package/src/components/toggle-button/AGENTS.md +106 -0
  451. package/src/components/toggle-button/index.ts +30 -0
  452. package/src/components/toggle-button/toggle-button-group.tsx +156 -0
  453. package/src/components/toggle-button/toggle-button-label.tsx +19 -0
  454. package/src/components/toggle-button/toggle-button.context.tsx +130 -0
  455. package/src/components/toggle-button/toggle-button.tsx +172 -0
  456. package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
  457. package/src/components/toggle-button/toggle-button.variants.ts +160 -0
  458. package/src/components/tooltip/AGENTS.md +133 -0
  459. package/src/components/tooltip/index.ts +28 -0
  460. package/src/components/tooltip/tooltip-arrow.tsx +40 -0
  461. package/src/components/tooltip/tooltip-content.tsx +179 -0
  462. package/src/components/tooltip/tooltip-description.tsx +19 -0
  463. package/src/components/tooltip/tooltip-text.tsx +21 -0
  464. package/src/components/tooltip/tooltip-title.tsx +20 -0
  465. package/src/components/tooltip/tooltip-trigger.tsx +103 -0
  466. package/src/components/tooltip/tooltip.context.tsx +68 -0
  467. package/src/components/tooltip/tooltip.tsx +213 -0
  468. package/src/components/tooltip/tooltip.variants.test.ts +231 -0
  469. package/src/components/tooltip/tooltip.variants.ts +177 -0
  470. package/src/hooks/use-calm-motion.tsx +31 -0
  471. package/src/lib/calm-motion.test.ts +17 -0
  472. package/src/lib/calm-motion.ts +23 -0
  473. package/src/lib/cn.test.ts +14 -0
  474. package/src/lib/cn.ts +2 -2
  475. package/src/no-gorhom.test.ts +54 -0
  476. package/src/styles/geometry.test.ts +1 -0
  477. package/src/styles/theme.css +4 -0
  478. package/src/styles/tokens.css +11 -0
  479. package/src/styles/tokens.test.ts +22 -0
  480. package/src/styles/tokens.ts +20 -0
  481. package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
  482. package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
@@ -0,0 +1,97 @@
1
+ # Collapsible
2
+
3
+ One section of content, shown and hidden by its own trigger. Compound root plus
4
+ `Collapsible.Trigger`, `Collapsible.Title`, `Collapsible.Description`,
5
+ `Collapsible.Indicator` and `Collapsible.Content`. It is a single
6
+ [Accordion](../accordion/AGENTS.md) item without the group — the same row, the
7
+ same measured panel, the same spring — so read that file's **The measured panel**
8
+ section first. What follows is only where a standalone disclosure differs.
9
+
10
+ `import { Collapsible } from "@delacour/react-native-ui/collapsible";`
11
+
12
+ ## Files
13
+
14
+ | File | What it holds |
15
+ | --- | --- |
16
+ | `index.ts` | → `@delacour/react-native-ui/collapsible` |
17
+ | `collapsible.tsx` | Root — owns the open state, `progress`, the measured height and the spring; the `Object.assign` |
18
+ | `collapsible-trigger.tsx` | `Collapsible.Trigger`, and the row it assembles from its children |
19
+ | `collapsible-title.tsx` | `Collapsible.Title` |
20
+ | `collapsible-description.tsx` | `Collapsible.Description` |
21
+ | `collapsible-indicator.tsx` | `Collapsible.Indicator`, the one rotation |
22
+ | `collapsible-content.tsx` | `Collapsible.Content` — the measured clip |
23
+ | `collapsible.context.tsx` | `CollapsibleContext`, `useCollapsible` and the internal part hook |
24
+ | `collapsible.types.ts` | `CollapsibleTextProps`, shared by the title, description and trigger |
25
+ | `collapsible.variants.ts` | Pure `tv()` slots, the constants, the toggle and the accessibility resolver — no RN imports |
26
+ | `collapsible.variants.test.ts` | `collapsibleTravelTarget`, pinned equal to the accordion's, and the JS-thread `contentHeight.value` sweep |
27
+
28
+ ## Design
29
+
30
+ - **It looks exactly like a one-item `Accordion`, and a test holds it there.**
31
+ Variants (`default`, `secondary`, `tertiary`, `transparent`), sizes (`sm`,
32
+ `md`, `lg`), the trigger's metrics, the type scale, the glyph step and the
33
+ panel's inset are `Accordion`'s class for class. A collapsible and an accordion
34
+ on one screen are the same control at two scales; if either retunes, the test
35
+ fails until the other follows.
36
+ - **Its constants are restated, not imported from `../accordion`.** The registry
37
+ derives an item's dependencies from its imports, so importing even the
38
+ accordion's leaf would make `delacour add collapsible` copy an accordion in
39
+ too. The spring, fade, rotation, variants, sizes and glyph step are written
40
+ again here and pinned equal by `collapsible.variants.test.ts` — the test file
41
+ is the only thing that imports both, and tests are not shipped.
42
+ - **The root owns the state and the travel.** With no item layer, what
43
+ `Accordion.Item` owns — `progress`, `contentHeight`, the spring, the
44
+ `isMeasured` state `onMeasured` flips — lives on the root. The race it closes
45
+ (`onLayout` landing either side of React's effects) is the same one, so the
46
+ panel reports its first measurement and never starts the spring itself.
47
+ - **Nothing on the JS thread reads `contentHeight.value`** — the accordion's
48
+ Release-build bug, in the same shape: a JS read can lag the panel's queued
49
+ write, see `COLLAPSIBLE_UNMEASURED`, and leave the panel shut. "Measured" is
50
+ React state and the decision is `collapsibleTravelTarget`; the test sweeps both
51
+ files. See the accordion's AGENTS.md.
52
+ - **The disabled fade lands on the root.** Never on the trigger, which is a
53
+ `Pressable` whose `Animated.View` writes `opacity` every frame; the accordion
54
+ fades its item for the same reason, and a collapsible's root is its item.
55
+
56
+ ## State
57
+
58
+ - **`isOpen` / `defaultOpen` / `onOpenChange`, `BottomSheet`'s names.** One
59
+ boolean through `useControllableState`, so it is controlled or uncontrolled
60
+ from one hook and `onOpenChange` hears the trigger and `toggle()` alike.
61
+ - **Disabled refuses the tap in both directions.** `toggleCollapsibleOpen`
62
+ returns the current state while disabled, so a disabled section that is open
63
+ stays open. A disabled control is one that cannot be used, not one that undoes
64
+ itself. The trigger is also a disabled `Pressable`; the pure rule is the second
65
+ line, and it is what keeps a caller's own `useCollapsible().toggle()` honest.
66
+ - **A refused toggle reports nothing.** The root compares the next state with
67
+ the current and skips `setOpen`, so `onOpenChange` never fires for a change
68
+ that did not happen.
69
+ - **`toggle` and the change callback are ref-backed**, so the context keeps its
70
+ identity across a caller passing a fresh arrow. `Accordion`'s trampoline.
71
+
72
+ ## The trigger
73
+
74
+ - **A `Pressable` with `fade` and `haptic="selection"`**, `Accordion.Trigger`'s
75
+ two defaults for its two reasons. `onPress` is `Omit`ed: the press *is* the
76
+ toggle, and a side effect belongs on `onOpenChange`, which also hears changes
77
+ the trigger never sees.
78
+ - **It assembles its own row** — bare strings become one `Collapsible.Title`,
79
+ titles and descriptions stack in a column, anything else stays on the row, and
80
+ the indicator moves to the end or is composed in when absent. Same rule, same
81
+ limitation: an indicator must be a *direct* `Collapsible.Indicator` child, so
82
+ the indicator takes a render function (`({ isOpen }) => …`) rather than asking
83
+ for a wrapper.
84
+
85
+ ## Accessibility
86
+
87
+ - **`resolveCollapsibleAccessibility` is the one source.** The trigger announces
88
+ `expanded` and `disabled` from it; the panel takes
89
+ `accessibilityElementsHidden` and `importantForAccessibility` from it. Pure, so
90
+ `bun test` holds the matrix.
91
+ - **A closed panel is out of the tree, not just clipped.** It stays mounted after
92
+ its first open, and mounted content is content a screen reader reads. A
93
+ disabled but open panel is still on screen and still read.
94
+ - **The root is a plain `View` with no role**, the accordion's rule.
95
+ - **Reduce motion is Reanimated's default `System` policy.** With it on, the
96
+ spring completes instantly: the panel snaps between its two states. The state
97
+ is carried by whether the panel is there, so that is the right degradation.
@@ -0,0 +1,97 @@
1
+ import { type ReactElement, type ReactNode, useCallback, useState } from "react";
2
+ import { type LayoutChangeEvent, StyleSheet, View, type ViewProps } from "react-native";
3
+ import Animated, { interpolate, useAnimatedStyle } from "react-native-reanimated";
4
+ import { useCollapsiblePart } from "./collapsible.context";
5
+ import { COLLAPSIBLE_CONTENT_FADE, collapsibleVariants, resolveCollapsibleAccessibility } from "./collapsible.variants";
6
+
7
+ export type CollapsibleContentProps = Omit<ViewProps, "style"> & {
8
+ className?: string;
9
+ children?: ReactNode;
10
+ };
11
+
12
+ /**
13
+ * The body the trigger discloses.
14
+ *
15
+ * **Its height is measured and animated, never laid out.** The outer view clips,
16
+ * and its height runs from zero to whatever the inner view reported, so the
17
+ * animation lives on this one node and whatever sits below follows it through
18
+ * ordinary layout — nothing on the screen has to opt into anything.
19
+ *
20
+ * **The content mounts on first open and stays mounted**, so a form keeps what was
21
+ * typed and a list keeps where it was scrolled across every later close. A closed
22
+ * panel is therefore taken out of the accessibility tree and stops taking touches,
23
+ * rather than merely clipped to zero.
24
+ *
25
+ * It reports its first measurement and never starts the travel itself — the root
26
+ * owns the spring alone. See `onMeasured` on the context.
27
+ *
28
+ * `className` lands on the inner, measured layer, so padding set there is part of
29
+ * what the panel measures.
30
+ */
31
+ export function CollapsibleContent({ className, children, ...props }: CollapsibleContentProps): ReactElement {
32
+ const { contentHeight, isDisabled, isOpen, onMeasured, progress, size } = useCollapsiblePart("Collapsible.Content");
33
+ const slots = collapsibleVariants({ size });
34
+ const { content: a11y } = resolveCollapsibleAccessibility({ isDisabled, isOpen });
35
+
36
+ // Adjusted during render rather than in an effect, so the panel mounts in the
37
+ // same commit as the tap that opened it.
38
+ const [hasOpened, setHasOpened] = useState(isOpen);
39
+ if (isOpen && !hasOpened) setHasOpened(true);
40
+
41
+ const [isMeasured, setMeasured] = useState(false);
42
+
43
+ const handleLayout = useCallback(
44
+ (event: LayoutChangeEvent) => {
45
+ contentHeight.value = event.nativeEvent.layout.height;
46
+ // "First" is this component's own state, never `contentHeight.value`: the
47
+ // write above is queued onto the UI runtime, and a read straight after it
48
+ // can still see the old value.
49
+ if (isMeasured) return;
50
+ setMeasured(true);
51
+ onMeasured();
52
+ },
53
+ [contentHeight, isMeasured, onMeasured]
54
+ );
55
+
56
+ const clipStyle = useAnimatedStyle(() => ({
57
+ height: progress.value * Math.max(contentHeight.value, 0),
58
+ opacity: interpolate(
59
+ progress.value,
60
+ [0, COLLAPSIBLE_CONTENT_FADE.start, COLLAPSIBLE_CONTENT_FADE.end, 1],
61
+ [0, 0, 1, 1]
62
+ ),
63
+ }));
64
+
65
+ // Never `auto` while measuring: the clip is stretched over the collapsible for
66
+ // that commit, and an invisible layer over the trigger is a tap that goes nowhere.
67
+ const pointerEvents = isOpen && isMeasured ? "auto" : "none";
68
+
69
+ return (
70
+ <Animated.View
71
+ accessibilityElementsHidden={a11y.accessibilityElementsHidden}
72
+ className={slots.content()}
73
+ importantForAccessibility={a11y.importantForAccessibility}
74
+ pointerEvents={pointerEvents}
75
+ style={isMeasured ? clipStyle : collapsibleStyleSheet.measuring}
76
+ >
77
+ {hasOpened ? (
78
+ <View className={slots.contentInner({ className })} onLayout={handleLayout} {...props}>
79
+ {children}
80
+ </View>
81
+ ) : null}
82
+ </Animated.View>
83
+ );
84
+ }
85
+ CollapsibleContent.displayName = "DelacourUI.Collapsible.Content";
86
+
87
+ const collapsibleStyleSheet = StyleSheet.create({
88
+ /**
89
+ * What the clip wears until the panel has reported a height.
90
+ *
91
+ * Absolutely positioned and transparent, so nothing on screen moves while it
92
+ * measures — and deliberately not `height: 0`, because an out-of-flow child
93
+ * mounted into a zero-height parent never fires `onLayout` at all. Found on a
94
+ * simulator by `Accordion.Content`, which carries the same sheet.
95
+ */
96
+ measuring: { bottom: 0, left: 0, opacity: 0, position: "absolute", right: 0, top: 0 },
97
+ });
@@ -0,0 +1,17 @@
1
+ import type { ReactElement } from "react";
2
+ import { Text } from "../text";
3
+ import { useCollapsiblePart } from "./collapsible.context";
4
+ import type { CollapsibleTextProps } from "./collapsible.types";
5
+ import { collapsibleVariants } from "./collapsible.variants";
6
+
7
+ /**
8
+ * The trigger's secondary line, a step down in scale and on the muted token.
9
+ *
10
+ * Stacks under the title inside the column the trigger assembles, so the two
11
+ * need no wrapper part between them.
12
+ */
13
+ export function CollapsibleDescription({ className, ...props }: CollapsibleTextProps): ReactElement {
14
+ const { size } = useCollapsiblePart("Collapsible.Description");
15
+ return <Text className={collapsibleVariants({ size }).description({ className })} {...props} />;
16
+ }
17
+ CollapsibleDescription.displayName = "DelacourUI.Collapsible.Description";
@@ -0,0 +1,92 @@
1
+ import { type ReactElement, type ReactNode, useMemo } from "react";
2
+ import type { ViewProps } from "react-native";
3
+ import Animated, { interpolate, useAnimatedStyle } from "react-native-reanimated";
4
+ import { IconChevronBottom } from "../../icons/central";
5
+ import { Icon, type IconDefaults, IconDefaultsProvider } from "../icon";
6
+ import { useCollapsiblePart } from "./collapsible.context";
7
+ import {
8
+ COLLAPSIBLE_INDICATOR_ROTATION,
9
+ COLLAPSIBLE_INDICATOR_TOKEN,
10
+ collapsibleVariants,
11
+ } from "./collapsible.variants";
12
+
13
+ /** What a render-function indicator is handed. */
14
+ export type CollapsibleIndicatorState = { isOpen: boolean };
15
+
16
+ export type CollapsibleIndicatorProps = Omit<ViewProps, "children" | "style"> & {
17
+ /**
18
+ * Turn with the panel. Set `false` for a glyph that *swaps* rather than
19
+ * rotates — a plus becoming a minus reads as broken when it also spins.
20
+ */
21
+ isAnimated?: boolean;
22
+ className?: string;
23
+ /**
24
+ * A glyph, or a function handed the settled state.
25
+ *
26
+ * The function form exists so a swapping glyph needs no component of its own.
27
+ * The trigger finds its indicator by element type, so one wrapped in a
28
+ * component of the caller's own is invisible to it and a second, default one is
29
+ * composed in beside it — handing the state over removes the reason to write
30
+ * that wrapper.
31
+ */
32
+ children?: ReactNode | ((state: CollapsibleIndicatorState) => ReactNode);
33
+ };
34
+
35
+ /**
36
+ * The glyph saying the section opens, turning as it does.
37
+ *
38
+ * Composed into a trigger that holds none, so a bare `<Collapsible.Trigger>` is
39
+ * already complete.
40
+ *
41
+ * **The rotation interpolates off the collapsible's own `progress`**, the value
42
+ * the panel's height reads, so the glyph and the panel are in phase by
43
+ * construction — and a custom glyph turns exactly like the default one. The
44
+ * rotation lives on this node rather than the trigger's because `Pressable`
45
+ * already owns `transform` there.
46
+ *
47
+ * @example
48
+ * <Collapsible.Indicator isAnimated={false}>
49
+ * {({ isOpen }) => <Icon icon={isOpen ? IconMinusSmall : IconPlusSmall} />}
50
+ * </Collapsible.Indicator>
51
+ */
52
+ export function CollapsibleIndicator({
53
+ isAnimated = true,
54
+ className,
55
+ children,
56
+ ...props
57
+ }: CollapsibleIndicatorProps): ReactElement {
58
+ const { isOpen, progress, size } = useCollapsiblePart("Collapsible.Indicator");
59
+
60
+ const slots = collapsibleVariants({ size });
61
+ const glyphClassName = slots.glyph();
62
+
63
+ const rotationStyle = useAnimatedStyle(() => ({
64
+ transform: [
65
+ {
66
+ rotate: `${interpolate(
67
+ progress.value,
68
+ [0, 1],
69
+ [COLLAPSIBLE_INDICATOR_ROTATION.collapsed, COLLAPSIBLE_INDICATOR_ROTATION.expanded]
70
+ )}deg`,
71
+ },
72
+ ],
73
+ }));
74
+
75
+ const iconDefaults = useMemo<IconDefaults>(
76
+ () => ({ className: glyphClassName, color: COLLAPSIBLE_INDICATOR_TOKEN }),
77
+ [glyphClassName]
78
+ );
79
+
80
+ return (
81
+ <Animated.View className={slots.indicator({ className })} style={isAnimated ? rotationStyle : undefined} {...props}>
82
+ <IconDefaultsProvider value={iconDefaults}>{renderGlyph(children, isOpen)}</IconDefaultsProvider>
83
+ </Animated.View>
84
+ );
85
+ }
86
+ CollapsibleIndicator.displayName = "DelacourUI.Collapsible.Indicator";
87
+
88
+ /** The caller's glyph, the state they asked for, or the default chevron. */
89
+ function renderGlyph(children: CollapsibleIndicatorProps["children"], isOpen: boolean): ReactNode {
90
+ if (typeof children === "function") return children({ isOpen });
91
+ return children ?? <Icon icon={IconChevronBottom} />;
92
+ }
@@ -0,0 +1,17 @@
1
+ import type { ReactElement } from "react";
2
+ import { Text } from "../text";
3
+ import { useCollapsiblePart } from "./collapsible.context";
4
+ import type { CollapsibleTextProps } from "./collapsible.types";
5
+ import { collapsibleVariants } from "./collapsible.variants";
6
+
7
+ /**
8
+ * The trigger's primary line — what the section is called.
9
+ *
10
+ * Carries its own colour and type scale, read from the collapsible's context: a
11
+ * React Native `View` does not cascade colour to a `Text` descendant.
12
+ */
13
+ export function CollapsibleTitle({ className, ...props }: CollapsibleTextProps): ReactElement {
14
+ const { size } = useCollapsiblePart("Collapsible.Title");
15
+ return <Text className={collapsibleVariants({ size }).title({ className })} {...props} />;
16
+ }
17
+ CollapsibleTitle.displayName = "DelacourUI.Collapsible.Title";
@@ -0,0 +1,133 @@
1
+ import { Children, isValidElement, type ReactElement, type ReactNode, useMemo } from "react";
2
+ import { View } from "react-native";
3
+ import { type IconDefaults, IconDefaultsProvider } from "../icon";
4
+ import { Pressable, type PressableProps } from "../pressable";
5
+ import { useCollapsiblePart } from "./collapsible.context";
6
+ import {
7
+ COLLAPSIBLE_FOREGROUND_TOKEN,
8
+ collapsibleVariants,
9
+ resolveCollapsibleAccessibility,
10
+ } from "./collapsible.variants";
11
+ import { CollapsibleDescription } from "./collapsible-description";
12
+ import { CollapsibleIndicator } from "./collapsible-indicator";
13
+ import { CollapsibleTitle } from "./collapsible-title";
14
+
15
+ export type CollapsibleTriggerProps = Omit<PressableProps, "children" | "disabled" | "onPress"> & {
16
+ className?: string;
17
+ children?: ReactNode;
18
+ };
19
+
20
+ /**
21
+ * The row that opens and closes the section.
22
+ *
23
+ * A `Pressable`, so `feedback`, `haptic` and the rest are inherited. Two defaults
24
+ * differ, for `Accordion.Trigger`'s reasons: `fade`, because a full-bleed row that
25
+ * scales reads as the whole card flexing, and `haptic="selection"`, because the
26
+ * press is a state toggle.
27
+ *
28
+ * `onPress` is `Omit`ed rather than forwarded: the press **is** the toggle, and a
29
+ * side effect belongs on the root's `onOpenChange`, which also hears a controlled
30
+ * change the trigger never sees.
31
+ *
32
+ * It assembles its own row — titles and descriptions stack in a column, anything
33
+ * else stays where it was written, an indicator moves to the end, and one is
34
+ * composed in when the children hold none.
35
+ */
36
+ export function CollapsibleTrigger({
37
+ feedback = "fade",
38
+ haptic = "selection",
39
+ className,
40
+ children,
41
+ ...props
42
+ }: CollapsibleTriggerProps): ReactElement {
43
+ const { isDisabled, isOpen, size, toggle } = useCollapsiblePart("Collapsible.Trigger");
44
+ const { trigger: a11y } = resolveCollapsibleAccessibility({ isDisabled, isOpen });
45
+
46
+ const slots = collapsibleVariants({ size });
47
+ const columnClassName = slots.triggerContent();
48
+ const glyphClassName = slots.glyph();
49
+
50
+ const content = useMemo(() => composeRow(children, columnClassName), [children, columnClassName]);
51
+
52
+ const iconDefaults = useMemo<IconDefaults>(
53
+ () => ({ className: glyphClassName, color: COLLAPSIBLE_FOREGROUND_TOKEN }),
54
+ [glyphClassName]
55
+ );
56
+
57
+ return (
58
+ <Pressable
59
+ accessibilityState={{ expanded: a11y.expanded }}
60
+ className={slots.trigger({ className })}
61
+ disabled={a11y.disabled}
62
+ feedback={feedback}
63
+ haptic={haptic}
64
+ onPress={toggle}
65
+ {...props}
66
+ >
67
+ <IconDefaultsProvider value={iconDefaults}>{content}</IconDefaultsProvider>
68
+ </Pressable>
69
+ );
70
+ }
71
+ CollapsibleTrigger.displayName = "DelacourUI.Collapsible.Trigger";
72
+
73
+ /**
74
+ * Arranges the trigger's children into its row.
75
+ *
76
+ * Bare strings become one `Collapsible.Title` (a raw string outside a `<Text>`
77
+ * red-boxes), titles and descriptions stack in a column, anything else stays on
78
+ * the row where it was written, and the indicator goes last — composed in when
79
+ * there is none. `Accordion.Trigger`'s arrangement, rule for rule.
80
+ */
81
+ function composeRow(children: ReactNode, columnClassName: string): ReactNode {
82
+ const items = Children.toArray(children);
83
+ const row: ReactNode[] = [];
84
+ const trailing: ReactNode[] = [];
85
+ let column: ReactNode[] = [];
86
+ let run: (string | number)[] = [];
87
+ let hasIndicator = false;
88
+
89
+ const flushRun = () => {
90
+ if (run.length === 0) return;
91
+ column.push(<CollapsibleTitle key={`title-${column.length}`}>{run.join("")}</CollapsibleTitle>);
92
+ run = [];
93
+ };
94
+
95
+ const flushColumn = () => {
96
+ if (column.length === 0) return;
97
+ row.push(
98
+ <View className={columnClassName} key={`content-${row.length}`}>
99
+ {column}
100
+ </View>
101
+ );
102
+ column = [];
103
+ };
104
+
105
+ for (const child of items) {
106
+ if (typeof child === "string" || typeof child === "number") {
107
+ run.push(child);
108
+ continue;
109
+ }
110
+ flushRun();
111
+
112
+ if (isValidElement(child) && child.type === CollapsibleIndicator) {
113
+ hasIndicator = true;
114
+ trailing.push(child);
115
+ continue;
116
+ }
117
+
118
+ if (isValidElement(child) && (child.type === CollapsibleTitle || child.type === CollapsibleDescription)) {
119
+ column.push(child);
120
+ continue;
121
+ }
122
+
123
+ flushColumn();
124
+ row.push(child);
125
+ }
126
+
127
+ flushRun();
128
+ flushColumn();
129
+
130
+ if (!hasIndicator) trailing.push(<CollapsibleIndicator key="indicator" />);
131
+
132
+ return [...row, ...trailing];
133
+ }
@@ -0,0 +1,91 @@
1
+ import { createContext, type ReactElement, type ReactNode, use } from "react";
2
+ import type { SharedValue } from "react-native-reanimated";
3
+ import type { CollapsibleSize, CollapsibleVariant } from "./collapsible.variants";
4
+
5
+ export type CollapsibleContextValue = {
6
+ variant: CollapsibleVariant;
7
+ size: CollapsibleSize;
8
+ /** The trigger is inert and the surface faded. An open section stays open. */
9
+ isDisabled: boolean;
10
+ /** Whether the panel is open, on the **JS thread** — what accessibility reads. */
11
+ isOpen: boolean;
12
+ /** Opens or closes the panel. Refused while disabled. Stable for the collapsible's lifetime. */
13
+ toggle: () => void;
14
+ /**
15
+ * How far open the panel is, on the **UI thread**: `0` closed, `1` open.
16
+ *
17
+ * The one value every animated style reads — the panel's height, its opacity
18
+ * and the indicator's rotation — so they cannot drift out of step by a frame.
19
+ */
20
+ progress: SharedValue<number>;
21
+ /**
22
+ * The panel's natural height in points, or {@link COLLAPSIBLE_UNMEASURED} until it
23
+ * has reported. Read on the UI runtime only, by the height style — a JS-thread read
24
+ * can lag the panel's write. See {@link onMeasured}.
25
+ */
26
+ contentHeight: SharedValue<number>;
27
+ /**
28
+ * Told by the panel that it has measured itself for the first time.
29
+ *
30
+ * Internal machinery: the root is the only owner of the spring, and this flips
31
+ * its `isMeasured` state — the reason its effect re-runs once there is a height
32
+ * to travel against, and how it knows, never by reading `contentHeight`. The
33
+ * panel cannot start the spring itself — `onLayout` lands either side of
34
+ * React's effects, and a spring started there is sometimes cancelled by the
35
+ * cleanup of the effect it raced. `Accordion.Item`'s `onMeasured`, verbatim.
36
+ */
37
+ onMeasured: () => void;
38
+ };
39
+
40
+ const CollapsibleContext = createContext<CollapsibleContextValue | null>(null);
41
+
42
+ /**
43
+ * Supplies one collapsible's axes, state and shared values to its parts.
44
+ *
45
+ * Lives in its own module, importing nothing but React and types, so a part can
46
+ * read it without importing `./collapsible` — which would close a cycle.
47
+ */
48
+ export function CollapsibleProvider({
49
+ value,
50
+ children,
51
+ }: {
52
+ value: CollapsibleContextValue;
53
+ children: ReactNode;
54
+ }): ReactElement {
55
+ return <CollapsibleContext value={value}>{children}</CollapsibleContext>;
56
+ }
57
+ CollapsibleProvider.displayName = "DelacourUI.Collapsible.Provider";
58
+
59
+ /** The enclosing collapsible's state, or null outside a `<Collapsible>`. */
60
+ export function useCollapsibleContext(): CollapsibleContextValue | null {
61
+ return use(CollapsibleContext);
62
+ }
63
+
64
+ /**
65
+ * Reads the enclosing collapsible's state.
66
+ *
67
+ * What a custom child reaches for: `isOpen` to swap a glyph or a label,
68
+ * `progress` to animate against exactly the travel the panel is running, or
69
+ * `toggle` to open it from somewhere other than the trigger. Throws outside a
70
+ * `<Collapsible>` — use {@link useCollapsibleContext} where one is optional.
71
+ */
72
+ export function useCollapsible(): CollapsibleContextValue {
73
+ const context = useCollapsibleContext();
74
+ if (!context) {
75
+ throw new Error("useCollapsible must be called inside a <Collapsible>.");
76
+ }
77
+ return context;
78
+ }
79
+
80
+ /**
81
+ * The enclosing collapsible's state, for a compound part that cannot work without one.
82
+ *
83
+ * Internal: deliberately not re-exported from `index.ts`.
84
+ */
85
+ export function useCollapsiblePart(component: string): CollapsibleContextValue {
86
+ const context = useCollapsibleContext();
87
+ if (!context) {
88
+ throw new Error(`${component} must be rendered inside a <Collapsible>.`);
89
+ }
90
+ return context;
91
+ }