@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
@@ -1,73 +1,106 @@
1
- import { type ReactElement, type ReactNode, useCallback, useMemo, useRef } from "react";
2
- import { useControllableState } from "../../hooks/use-controllable-state";
3
- import { type BottomSheetContextValue, type BottomSheetHandle, BottomSheetProvider } from "./bottom-sheet.context";
1
+ import {
2
+ BottomSheetHost,
3
+ BottomSheet as Headless,
4
+ type BottomSheetProps as HeadlessBottomSheetProps,
5
+ } from "@delacour/react-native-bottom-sheet";
6
+ import type { ReactElement } from "react";
7
+ import { Presets } from "react-native-pulsar";
8
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
9
+ import { useKeyboardAnimationGuard } from "../../hooks/use-keyboard-state-sync";
10
+ import { BottomSheetBackground } from "./bottom-sheet-background";
4
11
  import { BottomSheetClose } from "./bottom-sheet-close";
5
12
  import { BottomSheetContainer } from "./bottom-sheet-container";
6
13
  import { BottomSheetContent } from "./bottom-sheet-content";
7
14
  import { BottomSheetDescription } from "./bottom-sheet-description";
15
+ import { BottomSheetFlatList } from "./bottom-sheet-flat-list";
8
16
  import { BottomSheetFooter } from "./bottom-sheet-footer";
17
+ import { BottomSheetHandle } from "./bottom-sheet-handle";
18
+ import { BottomSheetLegendList } from "./bottom-sheet-legend-list";
9
19
  import { BottomSheetOverlay } from "./bottom-sheet-overlay";
10
- import { BottomSheetPortal } from "./bottom-sheet-portal";
20
+ import { BottomSheetProvider } from "./bottom-sheet-provider";
11
21
  import { BottomSheetScrollView } from "./bottom-sheet-scroll-view";
22
+ import { BottomSheetSectionList } from "./bottom-sheet-section-list";
23
+ import { BottomSheetStep, BottomSheetSteps } from "./bottom-sheet-steps";
24
+ import { BottomSheetTextInput } from "./bottom-sheet-text-input";
12
25
  import { BottomSheetTitle } from "./bottom-sheet-title";
13
26
  import { BottomSheetTrigger } from "./bottom-sheet-trigger";
14
27
 
15
- export type BottomSheetProps = {
16
- children?: ReactNode;
17
- /** Controlled open state. Leave it off and the sheet holds its own. */
18
- isOpen?: boolean;
19
- /** Initial open state while uncontrolled. */
20
- defaultOpen?: boolean;
21
- /**
22
- * Called whenever the sheet opens or closes — by a trigger, a swipe down, a
23
- * press on the backdrop, `BottomSheet.Close`, or a controlled `isOpen`.
24
- *
25
- * One callback for every path, deliberately. gorhom's own `onClose` fires for
26
- * the gesture alone, which is the shape that makes a caller wire three
27
- * handlers and still miss one.
28
- */
29
- onOpenChange?: (isOpen: boolean) => void;
30
- };
28
+ /**
29
+ * Every prop the engine's root takes, with two defaults this skin fills in:
30
+ * `bottomInset` is the device's safe-area bottom unless a caller says
31
+ * otherwise, and the two haptics are the library's own.
32
+ */
33
+ export type BottomSheetProps = HeadlessBottomSheetProps;
31
34
 
32
- function BottomSheetRoot({ children, defaultOpen = false, isOpen, onOpenChange }: BottomSheetProps): ReactElement {
33
- const sheetRef = useRef<BottomSheetHandle | null>(null);
34
- const [isSheetOpen, setOpen] = useControllableState<boolean>({
35
- defaultValue: defaultOpen,
36
- onChange: onOpenChange,
37
- value: isOpen,
38
- });
35
+ /**
36
+ * The haptics the sheet plays, as module-scope worklets.
37
+ *
38
+ * Both run on the UI thread from inside the engine's pan, so they are
39
+ * `Presets.System.*` — themselves worklets — and nothing else: a JS function
40
+ * here would be `undefined is not a function` at the moment a finger crosses a
41
+ * snap point. `selection` on a snap point because that is what a picker's tick feels
42
+ * like, and `impactLight` on a close because letting go of a sheet is a
43
+ * heavier event than passing a stop, but not by much.
44
+ */
45
+ const snapPointHaptic = Presets.System.selection;
46
+ const closeHaptic = Presets.System.impactLight;
39
47
 
40
- const open = useCallback(() => setOpen(true), [setOpen]);
41
- const close = useCallback(() => setOpen(false), [setOpen]);
48
+ /**
49
+ * The engine's root with this library's defaults on it. It renders no view of
50
+ * its own.
51
+ *
52
+ * Three things happen here and nowhere else in the skin:
53
+ *
54
+ * - **`bottomInset` defaults to the safe-area bottom**, read from
55
+ * `useSafeAreaInsets()`. The engine reserves that band under the body or the
56
+ * sticky footer and gives it back to the keyboard as it arrives; a caller
57
+ * never pads for the home indicator by hand.
58
+ * - **The haptics are on by default.** Pass `onSnapPointHaptic={undefined}` to
59
+ * turn one off, or a worklet of your own to change it.
60
+ * - **The stale-keyboard guard runs on mount.** A sheet mounted while
61
+ * `KeyboardProvider`'s shared values are pinned open by a keyboard that
62
+ * vanished without a `will` event would lift for a keyboard that is not
63
+ * there; `Screen.Footer` runs the same repair for the same reason.
64
+ *
65
+ * `topInset` is left at the engine's zero, so a `%` snap point is a fraction of the
66
+ * whole window — the same fraction it was before this rewrite.
67
+ */
68
+ function BottomSheetRoot({
69
+ bottomInset,
70
+ onSnapPointHaptic = snapPointHaptic,
71
+ onCloseHaptic = closeHaptic,
72
+ ...props
73
+ }: BottomSheetProps): ReactElement {
74
+ const insets = useSafeAreaInsets();
75
+ useKeyboardAnimationGuard();
42
76
 
43
- const context = useMemo<BottomSheetContextValue>(
44
- () => ({ close, isOpen: isSheetOpen, open, setOpen, sheetRef }),
45
- [close, isSheetOpen, open, setOpen]
77
+ return (
78
+ <Headless
79
+ bottomInset={bottomInset ?? insets.bottom}
80
+ onCloseHaptic={onCloseHaptic}
81
+ onSnapPointHaptic={onSnapPointHaptic}
82
+ {...props}
83
+ />
46
84
  );
47
-
48
- return <BottomSheetProvider value={context}>{children}</BottomSheetProvider>;
49
85
  }
50
86
 
51
87
  /**
52
88
  * A panel that slides up from the bottom of the screen, over everything.
53
89
  *
54
- * The library's first overlay, built on `@gorhom/bottom-sheet`'s modal. It
55
- * renders no view of its own — it owns the open state and hands it to the parts
56
- * through context, so a trigger anywhere inside can open a sheet declared
57
- * anywhere else in the same tree.
90
+ * A thin Uniwind skin over `@delacour/react-native-bottom-sheet`: every part
91
+ * here is the engine's part with this library's classes, `Text` presets and
92
+ * `Pressable` on it. The engine owns the state, the geometry, the gestures,
93
+ * the keyboard, the portal and the accessibility; this file owns what it looks
94
+ * like.
58
95
  *
59
- * **The app must mount `DelacourProvider` at its root.** The sheet hosts itself in
60
- * the `BottomSheetModalProvider` that provider's innermost layer supplies, and it
61
- * needs the gesture root above it for the pan.
62
- *
63
- * **Two parts are not drawn where they are written.** `BottomSheet.Overlay` and a
64
- * `BottomSheet.Footer sticky` are lifted out of the tree and handed to gorhom as
65
- * render props — see `BottomSheet.Portal` and `BottomSheet.Container` for why the
66
- * anatomy is worth that. Everything else renders in place.
96
+ * **The app mounts `BottomSheetProvider` once**, inside `DelacourProvider` and
97
+ * around its navigator. `DelacourProvider` cannot do it — the engine is an
98
+ * optional peer of this library — so the provider is exported from this
99
+ * subpath for the app to place. Without it a `Portal` renders where it is
100
+ * written, which is the inline sheet.
67
101
  *
68
102
  * Controlled or not, from one hook: pass `isOpen` and `onOpenChange` to own the
69
- * state, or nothing at all and let the sheet hold it. Which mode is in play is
70
- * locked in on first render.
103
+ * state, or nothing at all and let the sheet hold it.
71
104
  *
72
105
  * @example
73
106
  * <BottomSheet>
@@ -90,12 +123,12 @@ function BottomSheetRoot({ children, defaultOpen = false, isOpen, onOpenChange }
90
123
  * </BottomSheet>
91
124
  *
92
125
  * @example
93
- * // Controlled, and opened from somewhere other than a trigger.
94
- * <BottomSheet isOpen={isOpen} onOpenChange={setOpen}>
126
+ * // Controlled, snapped, and scrolling.
127
+ * <BottomSheet dynamicSizing={false} isOpen={isOpen} onOpenChange={setOpen} snapPoints={["50%", "90%"]}>
95
128
  * <BottomSheet.Portal>
96
129
  * <BottomSheet.Overlay />
97
130
  * <BottomSheet.Container>
98
- * <BottomSheet.Content>{…}</BottomSheet.Content>
131
+ * <BottomSheet.ScrollView>{rows}</BottomSheet.ScrollView>
99
132
  * </BottomSheet.Container>
100
133
  * </BottomSheet.Portal>
101
134
  * </BottomSheet>
@@ -103,23 +136,43 @@ function BottomSheetRoot({ children, defaultOpen = false, isOpen, onOpenChange }
103
136
  export const BottomSheet = Object.assign(BottomSheetRoot, {
104
137
  /** The control that opens the sheet. `asChild` to make a `Button` the trigger. */
105
138
  Trigger: BottomSheetTrigger,
106
- /** Everything drawn above the app. Lifts the overlay out to the container. */
107
- Portal: BottomSheetPortal,
108
- /** The scrim. Written beside the container, drawn as its backdrop. */
139
+ /** Everything drawn above the app, teleported to the nearest host. `inline` to render in place. */
140
+ Portal: Headless.Portal,
141
+ /** The scrim. Omit it and the sheet has none. */
109
142
  Overlay: BottomSheetOverlay,
110
- /** The sheet itself. Every gorhom prop passes through it. */
143
+ /** The panel that moves. Brings its own `Background` and `Handle` unless you write them. */
111
144
  Container: BottomSheetContainer,
112
- /** The sheet's body, sized to itself under `enableDynamicSizing`. */
145
+ /** The panel's surface — `bg-popover`, top corners rounded, every corner when `detached`. */
146
+ Background: BottomSheetBackground,
147
+ /** The grabber's row, and the sheet's one adjustable accessibility element. */
148
+ Handle: BottomSheetHandle,
149
+ /** The static body. A sheet sized to its content is sized to this. */
113
150
  Content: BottomSheetContent,
114
- /** A scrolling body. Needs `enableDynamicSizing={false}` and snap points. */
151
+ /** A scrolling body. Sizes to its rows, capped by `maxDynamicContentSize`, or fills explicit `snapPoints`. */
115
152
  ScrollView: BottomSheetScrollView,
153
+ /** A virtualised body. */
154
+ FlatList: BottomSheetFlatList,
155
+ /** A virtualised body with sticky section headers. */
156
+ SectionList: BottomSheetSectionList,
157
+ /** A `LegendList` body. Needs the optional `@legendapp/list` peer. */
158
+ LegendList: BottomSheetLegendList,
159
+ /** Controls at the bottom. `sticky` pins them over the body and rides the keyboard. */
160
+ Footer: BottomSheetFooter,
116
161
  /** The dismiss control, positioned out of the content's flow. */
117
162
  Close: BottomSheetClose,
118
- /** The sheet's heading — a `Text.Header` with the close control's clearance. */
163
+ /** The sheet's heading — a `Text.Header` that labels the panel for a screen reader. */
119
164
  Title: BottomSheetTitle,
120
165
  /** Supporting copy under the title — a muted `Text.Paragraph`. */
121
166
  Description: BottomSheetDescription,
122
- /** Controls at the bottom. `sticky` pins them above the keyboard. */
123
- Footer: BottomSheetFooter,
167
+ /** An `Input` registered with the sheet, so the keyboard it raises is the sheet's. */
168
+ TextInput: BottomSheetTextInput,
169
+ /** A body whose contents follow a step machine and whose height glides between them. */
170
+ Steps: BottomSheetSteps,
171
+ /** One step of a `Steps` body. */
172
+ Step: BottomSheetStep,
173
+ /** A place for sheets to teleport to other than the root — the recipe for a native modal. */
174
+ Host: BottomSheetHost,
175
+ /** The engine's provider, sharing `OverlayProvider`'s teleport host. Mount it once, inside `DelacourProvider`. */
176
+ Provider: BottomSheetProvider,
124
177
  displayName: "DelacourUI.BottomSheet",
125
178
  });
@@ -1,16 +1,14 @@
1
1
  import { describe, expect, test } from "bun:test";
2
+ import { resolveSheetBottomInset, resolveSheetScrollEndPadding } from "@delacour/react-native-bottom-sheet/core";
2
3
  import { declaredTokens, tokenValue } from "../../styles/theme-tokens.test";
3
4
  import {
4
5
  BOTTOM_SHEET_BACKDROP_INDICES,
5
6
  BOTTOM_SHEET_CLOSE_HIT_SLOP,
6
7
  BOTTOM_SHEET_FOOTER_GAP,
7
8
  BOTTOM_SHEET_FOOTER_PADDING,
8
- BOTTOM_SHEET_KEYBOARD_DEFAULTS,
9
9
  BOTTOM_SHEET_OVERLAY_OPACITY,
10
10
  BOTTOM_SHEET_OVERLAY_TOKEN,
11
11
  bottomSheetVariants,
12
- resolveFooterPlacement,
13
- resolveSheetBottomInset,
14
12
  } from "./bottom-sheet.variants";
15
13
 
16
14
  const LIGHT = declaredTokens("light");
@@ -32,6 +30,7 @@ function colorTokens(cls: string): string[] {
32
30
  }
33
31
 
34
32
  const SLOTS = bottomSheetVariants();
33
+ const DETACHED = bottomSheetVariants({ detached: true });
35
34
 
36
35
  /**
37
36
  * The slots this component declares, pinned rather than derived.
@@ -47,6 +46,8 @@ const SLOT_NAMES = [
47
46
  "handleIndicator",
48
47
  "content",
49
48
  "scrollContent",
49
+ "steps",
50
+ "step",
50
51
  "footer",
51
52
  "stickyFooter",
52
53
  "close",
@@ -81,8 +82,8 @@ describe("the overlay token", () => {
81
82
  expect(alpha).toBeLessThan(1);
82
83
  }
83
84
 
84
- // Leaving gorhom's 0.5 in place would multiply against that alpha and land
85
- // the scrim at roughly a fifth of what the theme asked for.
85
+ // Any other opacity would multiply against that alpha and land the scrim
86
+ // somewhere the theme did not ask for.
86
87
  expect(BOTTOM_SHEET_OVERLAY_OPACITY).toBe(1);
87
88
  });
88
89
 
@@ -112,74 +113,28 @@ describe("every token the slots name", () => {
112
113
 
113
114
  describe("the backdrop indices", () => {
114
115
  test("show the scrim from the first snap point and hide it only when closed", () => {
115
- // gorhom's own defaults (1 / 0) suit a persistent sheet resting collapsed
116
- // on screen. A modal sheet has no resting state.
116
+ // A modal sheet has no resting state: presented or gone.
117
117
  expect(BOTTOM_SHEET_BACKDROP_INDICES.appearsOnIndex).toBe(0);
118
118
  expect(BOTTOM_SHEET_BACKDROP_INDICES.disappearsOnIndex).toBe(-1);
119
119
  expect(BOTTOM_SHEET_BACKDROP_INDICES.disappearsOnIndex).toBeLessThan(BOTTOM_SHEET_BACKDROP_INDICES.appearsOnIndex);
120
120
  });
121
121
  });
122
122
 
123
- describe("the keyboard defaults", () => {
124
- test("follow the keyboard, restore on blur, and resize the Android window", () => {
125
- expect(BOTTOM_SHEET_KEYBOARD_DEFAULTS).toEqual({
126
- keyboardBehavior: "interactive",
127
- keyboardBlurBehavior: "restore",
128
- android_keyboardInputMode: "adjustResize",
129
- });
130
- });
131
-
132
- test("does not leave Android on gorhom's adjustPan", () => {
133
- // `adjustPan` slides the whole window up instead of resizing it, which puts
134
- // a sheet's footer off-screen. `resize` is what Expo configures and what
135
- // `KeyboardProvider` requires, so this is the value that matches reality.
136
- expect(BOTTOM_SHEET_KEYBOARD_DEFAULTS.android_keyboardInputMode).not.toBe("adjustPan");
137
- });
138
- });
139
-
140
- describe("resolveFooterPlacement", () => {
141
- const footer = (isSticky: boolean) => ({ isFooter: true, isSticky });
142
- const other = { isFooter: false, isSticky: false };
143
-
144
- test("reports none for children holding no footer", () => {
145
- expect(resolveFooterPlacement([])).toBe("none");
146
- expect(resolveFooterPlacement([other, other])).toBe("none");
147
- });
148
-
149
- test("reports inline for a footer that did not ask to be pinned", () => {
150
- expect(resolveFooterPlacement([other, footer(false)])).toBe("inline");
151
- });
152
-
153
- test("reports sticky for one that did, wherever it sits", () => {
154
- expect(resolveFooterPlacement([footer(true), other])).toBe("sticky");
155
- expect(resolveFooterPlacement([other, footer(true)])).toBe("sticky");
156
- });
157
-
158
- test("lets a single sticky footer win over an inline one", () => {
159
- // Two footers is not a state worth expressing, and the pinned one is the
160
- // one a caller meant.
161
- expect(resolveFooterPlacement([footer(false), footer(true)])).toBe("sticky");
162
- expect(resolveFooterPlacement([footer(true), footer(false)])).toBe("sticky");
163
- });
164
-
165
- test("never reads a sticky flag off a child that is not a footer", () => {
166
- expect(resolveFooterPlacement([{ isFooter: false, isSticky: true }])).toBe("none");
167
- });
168
- });
169
-
170
- describe("resolveSheetBottomInset", () => {
171
- test("gives the safe-area band to the content when nothing is pinned below it", () => {
123
+ describe("the inset helpers the engine's core exports", () => {
124
+ // They used to live here. They moved to the engine so the engine's own footer
125
+ // and body could share them; the skin re-exports them for the callers that
126
+ // imported them from `@delacour/react-native-ui/bottom-sheet`.
127
+ test("give the safe-area band to static content when nothing is pinned below it", () => {
172
128
  expect(resolveSheetBottomInset({ bottom: 34, hasStickyFooter: false })).toBe(34);
173
129
  });
174
130
 
175
- test("withholds it once a pinned footer's own box is carrying it", () => {
176
- // gorhom adds the footer's whole measured height — band included — to the
177
- // content's reserve, so asking for it here too counts it twice.
131
+ test("hold content off a pinned footer by the gap alone, never the band", () => {
132
+ // The footer's own box carries the band; the content asking for it too
133
+ // would count it twice.
178
134
  expect(resolveSheetBottomInset({ bottom: 34, hasStickyFooter: true })).toBe(0);
179
- });
180
-
181
- test("is a no-op on a device with no bottom inset", () => {
182
- expect(resolveSheetBottomInset({ bottom: 0, hasStickyFooter: false })).toBe(0);
135
+ expect(
136
+ resolveSheetScrollEndPadding({ bottom: 34, footerGap: BOTTOM_SHEET_FOOTER_GAP, hasStickyFooter: true })
137
+ ).toBe(BOTTOM_SHEET_FOOTER_GAP);
183
138
  });
184
139
  });
185
140
 
@@ -211,7 +166,7 @@ describe("bottomSheetVariants slots", () => {
211
166
  expect(declared.sort()).toEqual([...SLOT_NAMES].sort());
212
167
  });
213
168
 
214
- test("the sheet's surface rounds its top corners only", () => {
169
+ test("an attached sheet's surface rounds its top corners only", () => {
215
170
  const background = SLOTS.background();
216
171
  expect(background).toMatch(/\brounded-t-/);
217
172
  expect(background).not.toMatch(/\brounded-b-/);
@@ -220,6 +175,17 @@ describe("bottomSheetVariants slots", () => {
220
175
  expect(background).not.toMatch(/\brounded-(?:none|xs|sm|md|lg|xl|2xl|3xl|full)\b/);
221
176
  });
222
177
 
178
+ test("a detached card's surface rounds every corner, and only once", () => {
179
+ const background = DETACHED.background();
180
+ // The card floats, so its bottom edge is on screen and needs the radius.
181
+ expect(background).toMatch(/\brounded-2xl\b/);
182
+ // tailwind-merge has to have replaced the top-only radius, not stacked
183
+ // beside it — two radius utilities on one view is order-dependent.
184
+ expect(background).not.toMatch(/\brounded-t-/);
185
+ // The surface itself is unchanged.
186
+ expect(colorTokens(background)).toEqual(colorTokens(SLOTS.background()));
187
+ });
188
+
223
189
  test("a pinned footer brings a surface and a line where an inline one does not", () => {
224
190
  // It draws OVER the content, so without them the content scrolls straight
225
191
  // through it. An inline footer is in the flow and needs neither.
@@ -229,10 +195,10 @@ describe("bottomSheetVariants slots", () => {
229
195
  expect(SLOTS.footer()).not.toMatch(/\bborder-t\b/);
230
196
  });
231
197
 
232
- test("a pinned footer writes its vertical padding as a style, never a class", () => {
233
- // The bottom padding is animated — the safe-area band collapses into it as
234
- // the keyboard arrives — and a class cannot carry an animated value. The
235
- // top is written the same way so the two cannot drift.
198
+ test("a pinned footer writes its vertical padding as the engine's prop, never a class", () => {
199
+ // The engine measures the footer's inner box into the sheet's height, and
200
+ // padding handed to its `padding` prop lands on that box. A class on the
201
+ // outer view would be height the snap point never counts.
236
202
  expect(SLOTS.stickyFooter()).not.toMatch(/\bp[tby]?-[\d.]+\b/);
237
203
  expect(BOTTOM_SHEET_FOOTER_PADDING).toBeGreaterThan(0);
238
204
  });
@@ -256,9 +222,20 @@ describe("bottomSheetVariants slots", () => {
256
222
  expect(SLOTS.scrollContent()).toMatch(/\bpx-screen-gutter\b/);
257
223
  });
258
224
 
225
+ test("a multi-step body keeps the gutter on the stack and the vertical padding on each step", () => {
226
+ // The engine measures each `Step`, not the box around the stack, so
227
+ // vertical padding on `steps` would be height the sheet never counts.
228
+ expect(SLOTS.steps()).toMatch(/\bpx-screen-gutter\b/);
229
+ expect(SLOTS.steps()).not.toMatch(/\bp[tby]-[\d.]+\b/);
230
+ expect(SLOTS.step()).not.toMatch(/\bpx-/);
231
+ // A step reads like static content: same gap, same top padding.
232
+ expect(SLOTS.step().match(/\bgap-[\d.]+\b/)?.[0]).toBe(SLOTS.content().match(/\bgap-[\d.]+\b/)?.[0]);
233
+ expect(SLOTS.step().match(/\bpt-[\d.]+\b/)?.[0]).toBe(SLOTS.content().match(/\bpt-[\d.]+\b/)?.[0]);
234
+ });
235
+
259
236
  test("the overlay sets no opacity of its own", () => {
260
- // gorhom animates the scrim's opacity off the sheet's position; a class here
261
- // would be a second writer of the same style property.
237
+ // The engine animates the scrim's opacity off the sheet's index; a class
238
+ // here would be a second writer of the same style property.
262
239
  expect(SLOTS.overlay()).not.toMatch(/\bopacity-\d/);
263
240
  });
264
241
 
@@ -13,45 +13,23 @@ export const BOTTOM_SHEET_OVERLAY_TOKEN = "overlay";
13
13
  /**
14
14
  * The backdrop's opacity at full appearance.
15
15
  *
16
- * **1, not gorhom's 0.5.** The alpha lives in `--overlay`, so leaving the
17
- * library default in place would multiply the two and land the scrim at roughly
18
- * a fifth of what the theme asked for. One source for the alpha, and it is the
19
- * token — which is also what lets the two theme variants differ.
16
+ * **1.** The alpha lives in `--overlay`, so any other number here would multiply
17
+ * against it and land the scrim somewhere the theme did not ask for. One source
18
+ * for the alpha, and it is the token — which is also what lets the two theme
19
+ * variants differ.
20
20
  */
21
21
  export const BOTTOM_SHEET_OVERLAY_OPACITY = 1;
22
22
 
23
23
  /**
24
- * The snap indices the scrim appears and disappears on.
24
+ * The snap point indices the scrim appears and disappears on.
25
25
  *
26
- * gorhom defaults to `{ appearsOnIndex: 1, disappearsOnIndex: 0 }`, which suits
27
- * a persistent sheet that rests collapsed on the screen and only dims the app
28
- * once it is expanded. A modal sheet has no resting state: it is either
29
- * presented or gone, so the scrim belongs from the FIRST snap point (`0`) and
30
- * is only absent when the sheet is closed (`-1`).
26
+ * A modal sheet has no resting state: it is either presented or gone, so the
27
+ * scrim belongs from the FIRST snap point (`0`) and is only absent when the sheet
28
+ * is closed (`-1`). These are the engine's own defaults too; they are named
29
+ * here so the test that pins them has something to read.
31
30
  */
32
31
  export const BOTTOM_SHEET_BACKDROP_INDICES = { appearsOnIndex: 0, disappearsOnIndex: -1 } as const;
33
32
 
34
- /**
35
- * Keyboard behaviour every `BottomSheet.Container` starts from.
36
- *
37
- * A const rather than three defaults spelled inline, so `bun test` pins them and
38
- * a retune is one edit rather than three.
39
- *
40
- * - `interactive` — the sheet grows to keep its content above the keyboard,
41
- * following it frame by frame rather than snapping once it has settled.
42
- * - `restore` — blurring returns the sheet to the snap point it was on. Without
43
- * it a sheet that grew for the keyboard stays grown over empty screen.
44
- * - `adjustResize` — gorhom's own default is `adjustPan`, which leaves the
45
- * Android window height alone and slides the whole view up, so a sheet's
46
- * footer lands off-screen. `resize` is already what Expo configures and what
47
- * `KeyboardProvider` requires, so this is the value that matches the window.
48
- */
49
- export const BOTTOM_SHEET_KEYBOARD_DEFAULTS = {
50
- keyboardBehavior: "interactive",
51
- keyboardBlurBehavior: "restore",
52
- android_keyboardInputMode: "adjustResize",
53
- } as const;
54
-
55
33
  /**
56
34
  * Slop around `BottomSheet.Close`.
57
35
  *
@@ -62,22 +40,23 @@ export const BOTTOM_SHEET_KEYBOARD_DEFAULTS = {
62
40
  export const BOTTOM_SHEET_CLOSE_HIT_SLOP = 8;
63
41
 
64
42
  /**
65
- * The padding a pinned footer keeps whatever the keyboard is doing.
43
+ * The padding a pinned footer's measured box carries.
66
44
  *
67
- * A number rather than a `p-4`, and for `SCREEN_FOOTER_PADDING`'s reason: the
68
- * footer's bottom padding is an animated value — the safe-area band collapses
69
- * into it as the keyboard arrives — and a class cannot carry one. Its top
70
- * padding is written the same way so the two cannot drift.
45
+ * A number rather than a `p-4`, because it is handed to the engine's `padding`
46
+ * prop: the engine measures the footer's inner box into the dynamic snap point,
47
+ * and padding written there is counted where a class on the outer view would
48
+ * not be. The horizontal gutter still comes from the `stickyFooter` slot, which
49
+ * is merged after it.
71
50
  */
72
51
  export const BOTTOM_SHEET_FOOTER_PADDING = 16;
73
52
 
74
53
  /**
75
54
  * The gap between the last of the content and a pinned footer.
76
55
  *
77
- * Also a number, because the reserve it belongs to is computed at runtime from
78
- * the footer's measured height. Without it the last row of a list sits flush
79
- * against the footer's hairline, which reads as content clipped rather than
80
- * content ended.
56
+ * Also a number, because it is the engine's `footerGap` — added to the spacer it
57
+ * reserves under the body from the footer's measured height. Without it the last
58
+ * row of a list sits flush against the footer's hairline, which reads as content
59
+ * clipped rather than content ended.
81
60
  */
82
61
  export const BOTTOM_SHEET_FOOTER_GAP = 16;
83
62
 
@@ -85,7 +64,7 @@ export const bottomSheetVariants = tv({
85
64
  slots: {
86
65
  /**
87
66
  * The scrim. Carries the colour and nothing else — the fade is an animated
88
- * opacity gorhom drives off the sheet's own position.
67
+ * opacity the engine drives off the sheet's own index.
89
68
  */
90
69
  overlay: "bg-overlay",
91
70
  /**
@@ -94,7 +73,8 @@ export const bottomSheetVariants = tv({
94
73
  * surface token nothing else in the package had claimed.
95
74
  *
96
75
  * Only the top corners round. The bottom edge runs off the screen, and a
97
- * radius there shows as two notches of the app behind it.
76
+ * radius there shows as two notches of the app behind it. The `detached`
77
+ * variant is the exception — a floating card has a visible bottom edge.
98
78
  */
99
79
  background: "rounded-t-2xl bg-popover",
100
80
  handle: "items-center justify-center pt-3 pb-1",
@@ -102,6 +82,14 @@ export const bottomSheetVariants = tv({
102
82
  content: "gap-4 px-screen-gutter pt-2",
103
83
  /** A scrollable body's content container. Same treatment as `content`. */
104
84
  scrollContent: "gap-4 px-screen-gutter pt-2",
85
+ /**
86
+ * A multi-step body. The gutter only: the engine measures each `Step`, not
87
+ * the box around the stack, so vertical padding here would be height the
88
+ * sheet never counts.
89
+ */
90
+ steps: "px-screen-gutter",
91
+ /** One step of a multi-step body — `content` less the gutter `steps` already carries. */
92
+ step: "gap-4 pt-2",
105
93
  footer: "gap-3 px-screen-gutter pt-4",
106
94
  /**
107
95
  * A pinned footer draws OVER the content, so unlike the inline one it has
@@ -110,9 +98,8 @@ export const bottomSheetVariants = tv({
110
98
  * to overlap blank space. The same reason `Screen.Footer`'s backing lives
111
99
  * inside its sticky view.
112
100
  *
113
- * It carries no vertical padding: that is an animated style, because the
114
- * safe-area band inside it collapses as the keyboard arrives. See
115
- * {@link BOTTOM_SHEET_FOOTER_PADDING}.
101
+ * It carries no vertical padding: that is the engine's `padding` prop, so
102
+ * the measured box counts it. See {@link BOTTOM_SHEET_FOOTER_PADDING}.
116
103
  */
117
104
  stickyFooter: "gap-3 border-border border-t bg-popover px-screen-gutter",
118
105
  close: "absolute top-4 right-4 z-10",
@@ -131,69 +118,27 @@ export const bottomSheetVariants = tv({
131
118
  */
132
119
  title: "pr-8",
133
120
  },
121
+ variants: {
122
+ /**
123
+ * A floating card. Every corner is on screen, so every corner rounds;
124
+ * `rounded-2xl` replaces `rounded-t-2xl` through tailwind-merge rather
125
+ * than stacking beside it.
126
+ */
127
+ detached: {
128
+ true: {
129
+ background: "rounded-2xl",
130
+ },
131
+ },
132
+ },
134
133
  });
135
134
 
136
135
  /**
137
- * There is no `description` or `scrollView` slot.
136
+ * There is no `description`, `panel`, `portal` or `scrollView` slot.
138
137
  *
139
- * Neither has any layout of its own — a description is a `Text.Paragraph` in a
140
- * gap column and a scroll view fills whatever it is given — and `tv` emits
141
- * `undefined` for a slot whose class string is empty, so a slot that says
142
- * nothing is a slot no test can assert against. Those two parts merge the
143
- * caller's `className` with `cn()` instead.
138
+ * None has any layout of its own — a description is a `Text.Paragraph` in a
139
+ * gap column, the panel is a transparent frame the engine positions, a scroll
140
+ * view fills whatever it is given — and `tv` emits `undefined` for a slot whose
141
+ * class string is empty, so a slot that says nothing is a slot no test can
142
+ * assert against. Those parts merge the caller's `className` with `cn()` instead.
144
143
  */
145
144
  export type BottomSheetVariantProps = VariantProps<typeof bottomSheetVariants>;
146
-
147
- /** Where a `BottomSheet.Footer` among a container's children has to be rendered. */
148
- export type BottomSheetFooterPlacement = "sticky" | "inline" | "none";
149
-
150
- /** One child of a `BottomSheet.Container`, reduced to what the placement turns on. */
151
- export type BottomSheetFooterFlag = {
152
- isFooter: boolean;
153
- isSticky: boolean;
154
- };
155
-
156
- /**
157
- * Whether a container's children hold a footer, and where it has to go.
158
- *
159
- * gorhom takes a footer as a `footerComponent` render prop rather than as a
160
- * child, so a sticky one has to be lifted out of the tree it was written in and
161
- * handed over; an inline one simply stays put. This is the decision, kept pure
162
- * so `bun test` reaches the whole matrix — the walk over `Children.toArray` that
163
- * produces the flags lives with its caller, the way `withIndicator` does in
164
- * `radio.tsx`.
165
- *
166
- * A single sticky footer anywhere in the children wins, because two footers is
167
- * not a state worth expressing and the pinned one is the one a caller meant.
168
- */
169
- export function resolveFooterPlacement(flags: readonly BottomSheetFooterFlag[]): BottomSheetFooterPlacement {
170
- let hasFooter = false;
171
-
172
- for (const flag of flags) {
173
- if (!flag.isFooter) continue;
174
- if (flag.isSticky) return "sticky";
175
- hasFooter = true;
176
- }
177
-
178
- return hasFooter ? "inline" : "none";
179
- }
180
-
181
- /**
182
- * The safe-area band `BottomSheet.Content` and `BottomSheet.ScrollView` reserve.
183
- *
184
- * Exactly one thing in the sheet pays for the home indicator. A pinned footer is
185
- * the bottom-most thing there is, and its own box carries the band so its surface
186
- * reaches the bottom of the screen — gorhom then adds that whole measured height
187
- * to the content's reserve, so the content asking for the band as well would
188
- * count it twice and leave a gap the height of the indicator above the footer.
189
- * With no pinned footer the content IS the bottom-most thing and takes it.
190
- */
191
- export function resolveSheetBottomInset({
192
- hasStickyFooter,
193
- bottom,
194
- }: {
195
- hasStickyFooter: boolean;
196
- bottom: number;
197
- }): number {
198
- return hasStickyFooter ? 0 : bottom;
199
- }