@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,167 @@
1
+ import { type ReactElement, type ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { View, type ViewProps } from "react-native";
3
+ import { cancelAnimation, useSharedValue, withSpring } from "react-native-reanimated";
4
+ import { useControllableState } from "../../hooks/use-controllable-state";
5
+ import { type CollapsibleContextValue, CollapsibleProvider } from "./collapsible.context";
6
+ import {
7
+ COLLAPSIBLE_DEFAULT_SIZE,
8
+ COLLAPSIBLE_DEFAULT_VARIANT,
9
+ COLLAPSIBLE_SPRING,
10
+ COLLAPSIBLE_UNMEASURED,
11
+ type CollapsibleSize,
12
+ type CollapsibleVariant,
13
+ collapsibleTravelTarget,
14
+ collapsibleVariants,
15
+ toggleCollapsibleOpen,
16
+ } from "./collapsible.variants";
17
+ import { CollapsibleContent } from "./collapsible-content";
18
+ import { CollapsibleDescription } from "./collapsible-description";
19
+ import { CollapsibleIndicator } from "./collapsible-indicator";
20
+ import { CollapsibleTitle } from "./collapsible-title";
21
+ import { CollapsibleTrigger } from "./collapsible-trigger";
22
+
23
+ export type CollapsibleProps = Omit<ViewProps, "children"> & {
24
+ variant?: CollapsibleVariant;
25
+ size?: CollapsibleSize;
26
+ /** Controlled open state. Leave it off and the collapsible holds its own. */
27
+ isOpen?: boolean;
28
+ /** Initial open state while uncontrolled. */
29
+ defaultOpen?: boolean;
30
+ /** Called with the next state whenever the trigger (or `toggle()`) opens or closes it. */
31
+ onOpenChange?: (isOpen: boolean) => void;
32
+ /**
33
+ * Stops the trigger opening or closing it, fades the surface and announces the
34
+ * trigger disabled. An open section stays open.
35
+ */
36
+ isDisabled?: boolean;
37
+ className?: string;
38
+ children?: ReactNode;
39
+ };
40
+
41
+ function CollapsibleRoot({
42
+ variant = COLLAPSIBLE_DEFAULT_VARIANT,
43
+ size = COLLAPSIBLE_DEFAULT_SIZE,
44
+ isOpen: isOpenProp,
45
+ defaultOpen = false,
46
+ onOpenChange,
47
+ isDisabled = false,
48
+ className,
49
+ children,
50
+ ...props
51
+ }: CollapsibleProps): ReactElement {
52
+ // A ref-backed trampoline, so a caller passing a fresh arrow every render does
53
+ // not change the context's identity and re-render every part.
54
+ const changeRef = useRef(onOpenChange);
55
+ changeRef.current = onOpenChange;
56
+ const handleChange = useCallback((next: boolean) => changeRef.current?.(next), []);
57
+
58
+ const [isOpen, setOpen] = useControllableState<boolean>({
59
+ defaultValue: defaultOpen,
60
+ onChange: handleChange,
61
+ value: isOpenProp,
62
+ });
63
+
64
+ // Read by `toggle` rather than closed over, so one stable callback serves the
65
+ // collapsible for its whole life.
66
+ const stateRef = useRef({ isDisabled, isOpen });
67
+ stateRef.current = { isDisabled, isOpen };
68
+
69
+ const toggle = useCallback(() => {
70
+ const current = stateRef.current;
71
+ const next = toggleCollapsibleOpen(current);
72
+ // Refused: a disabled tap must not report an `onOpenChange` for a change
73
+ // that did not happen.
74
+ if (next === current.isOpen) return;
75
+ setOpen(next);
76
+ }, [setOpen]);
77
+
78
+ // Seeded from the settled state, so a collapsible mounted open does not
79
+ // animate itself open on its first paint.
80
+ const progress = useSharedValue(isOpen ? 1 : 0);
81
+ const contentHeight = useSharedValue(COLLAPSIBLE_UNMEASURED);
82
+
83
+ // Whether the panel has ever reported a height — React state, never a JS-thread
84
+ // read of `contentHeight`, which can lag the write. See `collapsibleTravelTarget`
85
+ // and `onMeasured` on the context.
86
+ const [isMeasured, setMeasured] = useState(false);
87
+ const onMeasured = useCallback(() => setMeasured(true), []);
88
+
89
+ // The first open of a panel that has never mounted has no height to travel
90
+ // against, so that run waits and the measurement that follows brings it back.
91
+ useEffect(() => {
92
+ const target = collapsibleTravelTarget({ isMeasured, isOpen });
93
+ if (target === null) return;
94
+
95
+ progress.value = withSpring(target, COLLAPSIBLE_SPRING);
96
+
97
+ return () => cancelAnimation(progress);
98
+ }, [isMeasured, isOpen, progress]);
99
+
100
+ const context = useMemo<CollapsibleContextValue>(
101
+ () => ({ contentHeight, isDisabled, isOpen, onMeasured, progress, size, toggle, variant }),
102
+ [contentHeight, isDisabled, isOpen, onMeasured, progress, size, toggle, variant]
103
+ );
104
+
105
+ return (
106
+ <CollapsibleProvider value={context}>
107
+ <View className={collapsibleVariants({ isDisabled, size, variant }).root({ className })} {...props}>
108
+ {children}
109
+ </View>
110
+ </CollapsibleProvider>
111
+ );
112
+ }
113
+
114
+ /**
115
+ * One section of content, shown and hidden by its own trigger.
116
+ *
117
+ * The standalone form of an accordion item — the same row, the same measured
118
+ * panel, the same spring — for a single disclosure that belongs to nothing else:
119
+ * "Show details", an order summary, the advanced half of a form.
120
+ *
121
+ * **The panel's height is measured and animated**, so the animation lives on the
122
+ * panel's own node and whatever sits below follows it with nothing opted in. Its
123
+ * height, its fade and the indicator's rotation all run off one shared value, so
124
+ * they cannot drift by a frame, and a tap mid-travel reverses the spring rather
125
+ * than restarting it.
126
+ *
127
+ * **The panel mounts on first open and stays mounted**, so what was typed or
128
+ * scrolled inside it survives every later close. A closed panel is taken out of
129
+ * the accessibility tree and stops taking touches.
130
+ *
131
+ * **State works either way from one hook**: pass `isOpen` to control it, or
132
+ * `defaultOpen` (or nothing) and let it hold its own. `onOpenChange` hears both.
133
+ *
134
+ * `isDisabled` stops the trigger and fades the surface. It does not close a
135
+ * section that is already open — a disabled control is one that cannot be used,
136
+ * not one that undoes itself.
137
+ *
138
+ * @example
139
+ * <Collapsible>
140
+ * <Collapsible.Trigger>
141
+ * <Collapsible.Title>What is included</Collapsible.Title>
142
+ * <Collapsible.Description>Three items</Collapsible.Description>
143
+ * </Collapsible.Trigger>
144
+ * <Collapsible.Content>
145
+ * <Text.Paragraph>Unlimited projects, 100 GB of storage, email support.</Text.Paragraph>
146
+ * </Collapsible.Content>
147
+ * </Collapsible>
148
+ *
149
+ * @example
150
+ * <Collapsible isOpen={isOpen} onOpenChange={setOpen} variant="transparent">
151
+ * <Collapsible.Trigger>Advanced</Collapsible.Trigger>
152
+ * <Collapsible.Content>{fields}</Collapsible.Content>
153
+ * </Collapsible>
154
+ */
155
+ export const Collapsible = Object.assign(CollapsibleRoot, {
156
+ /** The row that opens the section. A `Pressable`, so it inherits the whole vocabulary. */
157
+ Trigger: CollapsibleTrigger,
158
+ /** The trigger's primary line. Bare string children become one automatically. */
159
+ Title: CollapsibleTitle,
160
+ /** The trigger's secondary line, stacked under the title. */
161
+ Description: CollapsibleDescription,
162
+ /** The glyph that turns as the panel opens. Composed in when a trigger holds none. */
163
+ Indicator: CollapsibleIndicator,
164
+ /** The measured, clipped panel. Mounts on first open and stays mounted. */
165
+ Content: CollapsibleContent,
166
+ displayName: "DelacourUI.Collapsible",
167
+ });
@@ -0,0 +1,9 @@
1
+ import type { TextProps } from "react-native";
2
+
3
+ /**
4
+ * The shape of the trigger's two text lines, `Title` and `Description`.
5
+ *
6
+ * Shared by both parts and by the trigger, which wraps bare string children in a
7
+ * `Collapsible.Title` — three modules, so it lives in a leaf.
8
+ */
9
+ export type CollapsibleTextProps = TextProps & { className?: string };
@@ -0,0 +1,338 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { declarationCount } from "../../styles/theme-tokens.test";
5
+ import {
6
+ ACCORDION_CONTENT_FADE,
7
+ ACCORDION_GLYPH_STEP,
8
+ ACCORDION_INDICATOR_ROTATION,
9
+ ACCORDION_SIZES,
10
+ ACCORDION_SPRING,
11
+ ACCORDION_VARIANTS,
12
+ accordionTravelTarget,
13
+ accordionVariants,
14
+ } from "../accordion/accordion.variants";
15
+ import { CONTENT_HEIGHT_READ, jsThreadSource } from "../accordion/accordion.variants.test";
16
+ import { ICON_SIZES } from "../icon/icon.variants";
17
+ import {
18
+ COLLAPSIBLE_CONTENT_FADE,
19
+ COLLAPSIBLE_DEFAULT_SIZE,
20
+ COLLAPSIBLE_DEFAULT_VARIANT,
21
+ COLLAPSIBLE_FOREGROUND_TOKEN,
22
+ COLLAPSIBLE_GLYPH_STEP,
23
+ COLLAPSIBLE_INDICATOR_ROTATION,
24
+ COLLAPSIBLE_INDICATOR_TOKEN,
25
+ COLLAPSIBLE_SIZES,
26
+ COLLAPSIBLE_SPRING,
27
+ COLLAPSIBLE_UNMEASURED,
28
+ COLLAPSIBLE_VARIANTS,
29
+ collapsibleTravelTarget,
30
+ collapsibleVariants,
31
+ resolveCollapsibleAccessibility,
32
+ toggleCollapsibleOpen,
33
+ } from "./collapsible.variants";
34
+
35
+ const TOKENS_CSS = readFileSync(join(import.meta.dirname, "../../styles/tokens.css"), "utf-8");
36
+
37
+ /** The minimum a touch target may be, in points. Apple's number, and Android's. */
38
+ const MINIMUM_TARGET = 44;
39
+
40
+ /** Tailwind's spacing step, in points — `min-h-12` is twelve of them. */
41
+ const SPACING_STEP = 4;
42
+
43
+ /** A `--spacing-*` token's value in points, read from `tokens.css`. */
44
+ function spacingPx(token: string): number {
45
+ const match = TOKENS_CSS.match(new RegExp(`--spacing-${token}:\\s*(\\d+)px`));
46
+ if (!match) throw new Error(`tokens.css declares no --spacing-${token}`);
47
+ return Number(match[1]);
48
+ }
49
+
50
+ /** A slot's class string, with `tv`'s empty-slot `undefined` flattened. */
51
+ function cls(value: string | undefined): string {
52
+ return value ?? "";
53
+ }
54
+
55
+ /** The points a `px-4` / `min-h-14` utility resolves to. */
56
+ function utilityPx(value: string, prefix: string): number {
57
+ const match = value.match(new RegExp(`\\b${prefix}-(\\d+(?:\\.\\d+)?)\\b`));
58
+ if (!match) throw new Error(`no \`${prefix}-*\` in "${value}"`);
59
+ return Number(match[1]) * SPACING_STEP;
60
+ }
61
+
62
+ type Size = (typeof COLLAPSIBLE_SIZES)[number];
63
+
64
+ /** Every slot for one size, at the default variant, enabled. */
65
+ function slotsFor(size: Size) {
66
+ return collapsibleVariants({ isDisabled: false, size, variant: COLLAPSIBLE_DEFAULT_VARIANT });
67
+ }
68
+
69
+ /** Every slot a `View` wears, for one size. A `Text` slot carries a treatment on purpose. */
70
+ function viewSlots(size: Size): string[] {
71
+ const slots = slotsFor(size);
72
+ return [
73
+ cls(slots.root()),
74
+ cls(slots.trigger()),
75
+ cls(slots.triggerContent()),
76
+ cls(slots.indicator()),
77
+ cls(slots.content()),
78
+ cls(slots.contentInner()),
79
+ ];
80
+ }
81
+
82
+ describe("toggleCollapsibleOpen", () => {
83
+ test("a tap flips the state", () => {
84
+ expect(toggleCollapsibleOpen({ isDisabled: false, isOpen: false })).toBe(true);
85
+ expect(toggleCollapsibleOpen({ isDisabled: false, isOpen: true })).toBe(false);
86
+ });
87
+
88
+ test("a disabled collapsible refuses the tap in both directions", () => {
89
+ // Disabled means the control cannot be used, not that it undoes itself: an
90
+ // open section stays open, and a closed one stays closed.
91
+ expect(toggleCollapsibleOpen({ isDisabled: true, isOpen: false })).toBe(false);
92
+ expect(toggleCollapsibleOpen({ isDisabled: true, isOpen: true })).toBe(true);
93
+ });
94
+ });
95
+
96
+ describe("resolveCollapsibleAccessibility", () => {
97
+ test("the trigger announces expanded and disabled from the settled state", () => {
98
+ expect(resolveCollapsibleAccessibility({ isDisabled: false, isOpen: true })).toEqual({
99
+ trigger: { disabled: false, expanded: true },
100
+ content: { accessibilityElementsHidden: false, importantForAccessibility: "auto" },
101
+ });
102
+ });
103
+
104
+ test("a closed panel is taken out of the accessibility tree", () => {
105
+ // The panel stays mounted after its first expand, so without this a screen
106
+ // reader would read out a section that is not on screen.
107
+ expect(resolveCollapsibleAccessibility({ isDisabled: false, isOpen: false }).content).toEqual({
108
+ accessibilityElementsHidden: true,
109
+ importantForAccessibility: "no-hide-descendants",
110
+ });
111
+ });
112
+
113
+ test("disabling a section does not hide it", () => {
114
+ // An open, disabled section is still content on screen.
115
+ const resolved = resolveCollapsibleAccessibility({ isDisabled: true, isOpen: true });
116
+ expect(resolved.trigger.disabled).toBe(true);
117
+ expect(resolved.content.accessibilityElementsHidden).toBe(false);
118
+ });
119
+ });
120
+
121
+ describe("collapsibleVariants defaults", () => {
122
+ test("the tv's defaults are the constants the component falls back to", () => {
123
+ expect(cls(collapsibleVariants({}).trigger())).toBe(cls(slotsFor(COLLAPSIBLE_DEFAULT_SIZE).trigger()));
124
+ expect(cls(collapsibleVariants({}).root())).toBe(
125
+ cls(collapsibleVariants({ size: COLLAPSIBLE_DEFAULT_SIZE, variant: COLLAPSIBLE_DEFAULT_VARIANT }).root())
126
+ );
127
+ expect(COLLAPSIBLE_SIZES).toContain(COLLAPSIBLE_DEFAULT_SIZE);
128
+ expect(COLLAPSIBLE_VARIANTS).toContain(COLLAPSIBLE_DEFAULT_VARIANT);
129
+ });
130
+ });
131
+
132
+ describe("collapsibleVariants geometry", () => {
133
+ test("the content is inset to the trigger's padding at every size", () => {
134
+ // A panel whose text started at a different margin than the title above it
135
+ // reads as two columns rather than one disclosure.
136
+ for (const size of COLLAPSIBLE_SIZES) {
137
+ const slots = slotsFor(size);
138
+ expect(utilityPx(cls(slots.contentInner()), "px")).toBe(utilityPx(cls(slots.trigger()), "px"));
139
+ }
140
+ });
141
+
142
+ test("a trigger is a real touch target at every size", () => {
143
+ for (const size of COLLAPSIBLE_SIZES) {
144
+ expect(utilityPx(cls(slotsFor(size).trigger()), "min-h")).toBeGreaterThanOrEqual(MINIMUM_TARGET);
145
+ }
146
+ });
147
+
148
+ test("the size ladder ascends", () => {
149
+ const heights = COLLAPSIBLE_SIZES.map((size) => utilityPx(cls(slotsFor(size).trigger()), "min-h"));
150
+ expect(heights).toEqual([...heights].sort((a, b) => a - b));
151
+ expect(new Set(heights).size).toBe(heights.length);
152
+ });
153
+
154
+ test("every glyph step is a size the icon scale has", () => {
155
+ for (const size of COLLAPSIBLE_SIZES) {
156
+ const step = COLLAPSIBLE_GLYPH_STEP[size];
157
+ expect(ICON_SIZES).toContain(step);
158
+ expect(cls(slotsFor(size).glyph())).toBe(`size-icon-${step}`);
159
+ expect(spacingPx(`icon-${step}`)).toBeGreaterThan(0);
160
+ }
161
+ });
162
+ });
163
+
164
+ describe("collapsibleVariants slots", () => {
165
+ test("no slot a View wears carries a text treatment", () => {
166
+ // A React Native View does not cascade colour to a Text descendant. Rule 1.
167
+ for (const size of COLLAPSIBLE_SIZES) {
168
+ for (const value of viewSlots(size)) {
169
+ expect(value).not.toMatch(/\b(text|font)-/);
170
+ }
171
+ }
172
+ });
173
+
174
+ test("the title and description carry the treatment instead", () => {
175
+ for (const size of COLLAPSIBLE_SIZES) {
176
+ const slots = slotsFor(size);
177
+ expect(cls(slots.title())).toMatch(/\btext-/);
178
+ expect(cls(slots.description())).toMatch(/\btext-/);
179
+ }
180
+ });
181
+
182
+ test("the root clips, so an expanding panel keeps the rounded corners", () => {
183
+ for (const size of COLLAPSIBLE_SIZES) {
184
+ expect(cls(slotsFor(size).root())).toMatch(/\boverflow-hidden\b/);
185
+ }
186
+ });
187
+
188
+ test("the content clips, because its height is animated below its content's", () => {
189
+ for (const size of COLLAPSIBLE_SIZES) {
190
+ expect(cls(slotsFor(size).content())).toMatch(/\boverflow-hidden\b/);
191
+ }
192
+ });
193
+
194
+ test("the measured layer is out of flow and spans the width", () => {
195
+ for (const size of COLLAPSIBLE_SIZES) {
196
+ const inner = cls(slotsFor(size).contentInner());
197
+ for (const utility of ["absolute", "top-0", "left-0", "right-0"]) {
198
+ expect(inner).toMatch(new RegExp(`\\b${utility}\\b`));
199
+ }
200
+ }
201
+ });
202
+
203
+ test("the trigger spans the surface and its text column can shrink", () => {
204
+ for (const size of COLLAPSIBLE_SIZES) {
205
+ const slots = slotsFor(size);
206
+ expect(cls(slots.trigger())).toMatch(/\bw-full\b/);
207
+ expect(cls(slots.triggerContent())).toMatch(/\bflex-1\b/);
208
+ expect(cls(slots.triggerContent())).toMatch(/\bmin-w-0\b/);
209
+ }
210
+ });
211
+
212
+ test("the disabled fade lands on the root, never on the trigger", () => {
213
+ // The trigger is a `Pressable`, whose root Animated.View writes `opacity`
214
+ // every frame — a class there is overwritten silently.
215
+ for (const size of COLLAPSIBLE_SIZES) {
216
+ const slots = collapsibleVariants({ isDisabled: true, size, variant: COLLAPSIBLE_DEFAULT_VARIANT });
217
+ expect(cls(slots.root())).toMatch(/\bopacity-50\b/);
218
+ expect(cls(slots.trigger())).not.toMatch(/\bopacity-/);
219
+ }
220
+ });
221
+
222
+ test("every variant paints a distinct root, and nothing else", () => {
223
+ const roots = COLLAPSIBLE_VARIANTS.map((variant) =>
224
+ cls(collapsibleVariants({ isDisabled: false, size: COLLAPSIBLE_DEFAULT_SIZE, variant }).root())
225
+ );
226
+ expect(new Set(roots).size).toBe(roots.length);
227
+ for (const variant of COLLAPSIBLE_VARIANTS) {
228
+ const slots = collapsibleVariants({ isDisabled: false, size: COLLAPSIBLE_DEFAULT_SIZE, variant });
229
+ expect(cls(slots.trigger())).toBe(cls(slotsFor(COLLAPSIBLE_DEFAULT_SIZE).trigger()));
230
+ expect(cls(slots.contentInner())).toBe(cls(slotsFor(COLLAPSIBLE_DEFAULT_SIZE).contentInner()));
231
+ }
232
+ });
233
+
234
+ test("nothing draws a shadow", () => {
235
+ for (const size of COLLAPSIBLE_SIZES) {
236
+ for (const value of viewSlots(size)) {
237
+ expect(value).not.toMatch(/\bshadow/);
238
+ }
239
+ }
240
+ });
241
+ });
242
+
243
+ describe("collapsible colour tokens", () => {
244
+ test("every token it names is declared in both themes", () => {
245
+ for (const token of [COLLAPSIBLE_FOREGROUND_TOKEN, COLLAPSIBLE_INDICATOR_TOKEN]) {
246
+ expect(declarationCount(token)).toBe(2);
247
+ }
248
+ });
249
+
250
+ test("the indicator is quieter than the title beside it", () => {
251
+ expect(COLLAPSIBLE_INDICATOR_TOKEN).not.toBe(COLLAPSIBLE_FOREGROUND_TOKEN);
252
+ });
253
+ });
254
+
255
+ describe("collapsible animation", () => {
256
+ test("the panel settles without overshooting its own measured height", () => {
257
+ const ratio = COLLAPSIBLE_SPRING.damping / (2 * Math.sqrt(COLLAPSIBLE_SPRING.stiffness * COLLAPSIBLE_SPRING.mass));
258
+ expect(ratio).toBeGreaterThanOrEqual(1);
259
+ expect(ratio).toBeLessThan(2);
260
+ });
261
+
262
+ test("unmeasured is a height no layout can report", () => {
263
+ expect(COLLAPSIBLE_UNMEASURED).toBeLessThan(0);
264
+ });
265
+
266
+ test("the fade runs ahead of the height, inside the travel", () => {
267
+ const { start, end } = COLLAPSIBLE_CONTENT_FADE;
268
+ expect(start).toBeGreaterThanOrEqual(0);
269
+ expect(end).toBeGreaterThan(start);
270
+ expect(end).toBeLessThan(1);
271
+ });
272
+ });
273
+
274
+ describe("collapsible and accordion move alike", () => {
275
+ // Restated rather than imported, so `delacour add collapsible` does not copy an
276
+ // accordion in with it — and pinned here, so the two disclosures on one screen
277
+ // cannot drift apart in feel, metrics or paint.
278
+ test("the same spring, fade and indicator travel", () => {
279
+ expect(COLLAPSIBLE_SPRING).toEqual(ACCORDION_SPRING);
280
+ expect(COLLAPSIBLE_CONTENT_FADE).toEqual(ACCORDION_CONTENT_FADE);
281
+ expect(COLLAPSIBLE_INDICATOR_ROTATION).toEqual(ACCORDION_INDICATOR_ROTATION);
282
+ });
283
+
284
+ test("the same axes", () => {
285
+ expect([...COLLAPSIBLE_VARIANTS]).toEqual([...ACCORDION_VARIANTS]);
286
+ expect([...COLLAPSIBLE_SIZES]).toEqual([...ACCORDION_SIZES]);
287
+ expect(COLLAPSIBLE_GLYPH_STEP).toEqual(ACCORDION_GLYPH_STEP);
288
+ });
289
+
290
+ test("the same trigger, panel and surface at every size and variant", () => {
291
+ for (const size of COLLAPSIBLE_SIZES) {
292
+ const own = slotsFor(size);
293
+ const accordion = accordionVariants({ isDisabled: false, size, variant: "default" });
294
+ expect(cls(own.trigger())).toBe(cls(accordion.trigger()));
295
+ expect(cls(own.contentInner())).toBe(cls(accordion.contentInner()));
296
+ expect(cls(own.title())).toBe(cls(accordion.title()));
297
+ expect(cls(own.description())).toBe(cls(accordion.description()));
298
+ }
299
+ for (const variant of COLLAPSIBLE_VARIANTS) {
300
+ const own = collapsibleVariants({ isDisabled: false, size: COLLAPSIBLE_DEFAULT_SIZE, variant });
301
+ const accordion = accordionVariants({ isDisabled: false, size: COLLAPSIBLE_DEFAULT_SIZE, variant });
302
+ expect(cls(own.root())).toBe(cls(accordion.root()));
303
+ }
304
+ });
305
+ });
306
+
307
+ describe("collapsibleTravelTarget", () => {
308
+ test("an open waits for the panel's first measurement", () => {
309
+ expect(collapsibleTravelTarget({ isOpen: true, isMeasured: false })).toBeNull();
310
+ });
311
+
312
+ test("an open travels to 1 once the panel has measured", () => {
313
+ expect(collapsibleTravelTarget({ isOpen: true, isMeasured: true })).toBe(1);
314
+ });
315
+
316
+ test("a close never waits, measured or not", () => {
317
+ expect(collapsibleTravelTarget({ isOpen: false, isMeasured: false })).toBe(0);
318
+ expect(collapsibleTravelTarget({ isOpen: false, isMeasured: true })).toBe(0);
319
+ });
320
+
321
+ test("agrees with the accordion's rule on every input", () => {
322
+ for (const isOpen of [false, true]) {
323
+ for (const isMeasured of [false, true]) {
324
+ expect(collapsibleTravelTarget({ isOpen, isMeasured })).toBe(
325
+ accordionTravelTarget({ isExpanded: isOpen, isMeasured })
326
+ );
327
+ }
328
+ }
329
+ });
330
+ });
331
+
332
+ describe("the measured height is never read on the JS thread", () => {
333
+ // `Accordion`'s Release-build bug, in the same two files: a JS-thread read straight after the
334
+ // panel's `onLayout` wrote the height can still see `COLLAPSIBLE_UNMEASURED`.
335
+ test.each(["collapsible.tsx", "collapsible-content.tsx"])("%s", (file) => {
336
+ expect(jsThreadSource(join(import.meta.dirname, file))).not.toMatch(CONTENT_HEIGHT_READ);
337
+ });
338
+ });