@delacour/react-native-ui 0.1.0-alpha.4 → 0.2.0-alpha.20261010061731

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (493) hide show
  1. package/README.md +10 -6
  2. package/package.json +49 -5
  3. package/src/components/accordion/AGENTS.md +21 -9
  4. package/src/components/accordion/accordion-content.tsx +7 -4
  5. package/src/components/accordion/accordion-item.tsx +15 -13
  6. package/src/components/accordion/accordion.context.tsx +11 -5
  7. package/src/components/accordion/accordion.variants.test.ts +64 -3
  8. package/src/components/accordion/accordion.variants.ts +33 -9
  9. package/src/components/alert/AGENTS.md +102 -0
  10. package/src/components/alert/alert-action.tsx +17 -0
  11. package/src/components/alert/alert-close-button.tsx +50 -0
  12. package/src/components/alert/alert-content.tsx +15 -0
  13. package/src/components/alert/alert-description.tsx +16 -0
  14. package/src/components/alert/alert-glyphs.ts +25 -0
  15. package/src/components/alert/alert-indicator.tsx +40 -0
  16. package/src/components/alert/alert-title.tsx +18 -0
  17. package/src/components/alert/alert.context.tsx +69 -0
  18. package/src/components/alert/alert.tsx +160 -0
  19. package/src/components/alert/alert.types.ts +10 -0
  20. package/src/components/alert/alert.variants.test.ts +218 -0
  21. package/src/components/alert/alert.variants.ts +180 -0
  22. package/src/components/alert/index.ts +23 -0
  23. package/src/components/avatar/AGENTS.md +106 -0
  24. package/src/components/avatar/avatar-badge.tsx +51 -0
  25. package/src/components/avatar/avatar-group.tsx +139 -0
  26. package/src/components/avatar/avatar.context.tsx +93 -0
  27. package/src/components/avatar/avatar.tsx +201 -0
  28. package/src/components/avatar/avatar.utils.test.ts +174 -0
  29. package/src/components/avatar/avatar.utils.ts +141 -0
  30. package/src/components/avatar/avatar.variants.test.ts +251 -0
  31. package/src/components/avatar/avatar.variants.ts +270 -0
  32. package/src/components/avatar/index.ts +39 -0
  33. package/src/components/badge/AGENTS.md +10 -0
  34. package/src/components/badge/badge-close-button.tsx +2 -2
  35. package/src/components/badge/badge.context.tsx +2 -1
  36. package/src/components/badge/badge.tsx +13 -4
  37. package/src/components/badge/badge.variants.test.ts +26 -0
  38. package/src/components/badge/badge.variants.ts +21 -0
  39. package/src/components/bottom-sheet/AGENTS.md +186 -231
  40. package/src/components/bottom-sheet/bottom-sheet-background.tsx +31 -22
  41. package/src/components/bottom-sheet/bottom-sheet-close.tsx +23 -30
  42. package/src/components/bottom-sheet/bottom-sheet-container.tsx +54 -208
  43. package/src/components/bottom-sheet/bottom-sheet-content.tsx +25 -48
  44. package/src/components/bottom-sheet/bottom-sheet-description.tsx +8 -2
  45. package/src/components/bottom-sheet/bottom-sheet-flat-list.tsx +65 -0
  46. package/src/components/bottom-sheet/bottom-sheet-footer.tsx +53 -122
  47. package/src/components/bottom-sheet/bottom-sheet-handle.tsx +31 -26
  48. package/src/components/bottom-sheet/bottom-sheet-legend-list.tsx +69 -0
  49. package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +25 -147
  50. package/src/components/bottom-sheet/bottom-sheet-provider.tsx +42 -0
  51. package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +38 -62
  52. package/src/components/bottom-sheet/bottom-sheet-section-list.tsx +58 -0
  53. package/src/components/bottom-sheet/bottom-sheet-steps.tsx +88 -0
  54. package/src/components/bottom-sheet/bottom-sheet-text-input.tsx +44 -0
  55. package/src/components/bottom-sheet/bottom-sheet-title.tsx +12 -8
  56. package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +19 -37
  57. package/src/components/bottom-sheet/bottom-sheet.tsx +113 -60
  58. package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +47 -87
  59. package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -126
  60. package/src/components/bottom-sheet/index.ts +39 -17
  61. package/src/components/bottom-sheet/use-bottom-sheet-input.ts +16 -115
  62. package/src/components/button/AGENTS.md +51 -11
  63. package/src/components/button/button-group.tsx +44 -8
  64. package/src/components/button/button.context.tsx +10 -0
  65. package/src/components/button/button.tsx +7 -1
  66. package/src/components/button/button.variants.test.ts +190 -0
  67. package/src/components/button/button.variants.ts +87 -6
  68. package/src/components/button/index.ts +2 -0
  69. package/src/components/calendar/AGENTS.md +142 -0
  70. package/src/components/calendar/calendar-caption.tsx +116 -0
  71. package/src/components/calendar/calendar-day.tsx +103 -0
  72. package/src/components/calendar/calendar-grid.tsx +183 -0
  73. package/src/components/calendar/calendar-header.tsx +34 -0
  74. package/src/components/calendar/calendar-nav.tsx +51 -0
  75. package/src/components/calendar/calendar-picker.tsx +128 -0
  76. package/src/components/calendar/calendar-weekdays.tsx +48 -0
  77. package/src/components/calendar/calendar.context.tsx +108 -0
  78. package/src/components/calendar/calendar.date.test.ts +456 -0
  79. package/src/components/calendar/calendar.date.ts +584 -0
  80. package/src/components/calendar/calendar.tsx +389 -0
  81. package/src/components/calendar/calendar.types.ts +87 -0
  82. package/src/components/calendar/calendar.variants.test.ts +363 -0
  83. package/src/components/calendar/calendar.variants.ts +426 -0
  84. package/src/components/calendar/index.ts +96 -0
  85. package/src/components/card/AGENTS.md +92 -0
  86. package/src/components/card/card-action.tsx +16 -0
  87. package/src/components/card/card-content.tsx +12 -0
  88. package/src/components/card/card-description.tsx +12 -0
  89. package/src/components/card/card-footer.tsx +30 -0
  90. package/src/components/card/card-header.tsx +45 -0
  91. package/src/components/card/card-title.tsx +25 -0
  92. package/src/components/card/card.context.tsx +64 -0
  93. package/src/components/card/card.tsx +100 -0
  94. package/src/components/card/card.types.ts +13 -0
  95. package/src/components/card/card.variants.test.ts +240 -0
  96. package/src/components/card/card.variants.ts +177 -0
  97. package/src/components/card/index.ts +15 -0
  98. package/src/components/chart/AGENTS.md +8 -0
  99. package/src/components/chart/chart.tsx +8 -1
  100. package/src/components/chip/AGENTS.md +113 -0
  101. package/src/components/chip/chip-close-button.tsx +48 -0
  102. package/src/components/chip/chip-end-content.tsx +15 -0
  103. package/src/components/chip/chip-label.tsx +21 -0
  104. package/src/components/chip/chip-start-content.tsx +16 -0
  105. package/src/components/chip/chip.context.tsx +66 -0
  106. package/src/components/chip/chip.tsx +268 -0
  107. package/src/components/chip/chip.types.ts +10 -0
  108. package/src/components/chip/chip.variants.test.ts +335 -0
  109. package/src/components/chip/chip.variants.ts +321 -0
  110. package/src/components/chip/index.ts +24 -0
  111. package/src/components/collapsible/AGENTS.md +97 -0
  112. package/src/components/collapsible/collapsible-content.tsx +97 -0
  113. package/src/components/collapsible/collapsible-description.tsx +17 -0
  114. package/src/components/collapsible/collapsible-indicator.tsx +92 -0
  115. package/src/components/collapsible/collapsible-title.tsx +17 -0
  116. package/src/components/collapsible/collapsible-trigger.tsx +133 -0
  117. package/src/components/collapsible/collapsible.context.tsx +91 -0
  118. package/src/components/collapsible/collapsible.tsx +167 -0
  119. package/src/components/collapsible/collapsible.types.ts +9 -0
  120. package/src/components/collapsible/collapsible.variants.test.ts +338 -0
  121. package/src/components/collapsible/collapsible.variants.ts +228 -0
  122. package/src/components/collapsible/index.ts +32 -0
  123. package/src/components/context-menu/AGENTS.md +103 -0
  124. package/src/components/context-menu/context-menu-content.tsx +64 -0
  125. package/src/components/context-menu/context-menu-item.tsx +22 -0
  126. package/src/components/context-menu/context-menu-preview.tsx +60 -0
  127. package/src/components/context-menu/context-menu-trigger.tsx +160 -0
  128. package/src/components/context-menu/context-menu.context.tsx +91 -0
  129. package/src/components/context-menu/context-menu.tsx +90 -0
  130. package/src/components/context-menu/context-menu.variants.test.ts +266 -0
  131. package/src/components/context-menu/context-menu.variants.ts +147 -0
  132. package/src/components/context-menu/index.ts +18 -0
  133. package/src/components/dialog/AGENTS.md +97 -0
  134. package/src/components/dialog/dialog-body.tsx +21 -0
  135. package/src/components/dialog/dialog-close.tsx +82 -0
  136. package/src/components/dialog/dialog-content.tsx +153 -0
  137. package/src/components/dialog/dialog-description.tsx +23 -0
  138. package/src/components/dialog/dialog-footer.tsx +32 -0
  139. package/src/components/dialog/dialog-header.tsx +20 -0
  140. package/src/components/dialog/dialog-title.tsx +42 -0
  141. package/src/components/dialog/dialog-trigger.tsx +54 -0
  142. package/src/components/dialog/dialog.context.tsx +90 -0
  143. package/src/components/dialog/dialog.tsx +119 -0
  144. package/src/components/dialog/dialog.variants.test.ts +224 -0
  145. package/src/components/dialog/dialog.variants.ts +173 -0
  146. package/src/components/dialog/index.ts +30 -0
  147. package/src/components/drawer/AGENTS.md +123 -0
  148. package/src/components/drawer/drawer-body.tsx +46 -0
  149. package/src/components/drawer/drawer-close.tsx +76 -0
  150. package/src/components/drawer/drawer-content.tsx +186 -0
  151. package/src/components/drawer/drawer-description.tsx +23 -0
  152. package/src/components/drawer/drawer-footer.tsx +21 -0
  153. package/src/components/drawer/drawer-header.tsx +42 -0
  154. package/src/components/drawer/drawer-title.tsx +35 -0
  155. package/src/components/drawer/drawer-trigger.tsx +54 -0
  156. package/src/components/drawer/drawer.context.tsx +114 -0
  157. package/src/components/drawer/drawer.tsx +119 -0
  158. package/src/components/drawer/drawer.variants.test.ts +392 -0
  159. package/src/components/drawer/drawer.variants.ts +244 -0
  160. package/src/components/drawer/index.ts +41 -0
  161. package/src/components/drawer/use-drawer-pan.ts +108 -0
  162. package/src/components/empty-state/AGENTS.md +90 -0
  163. package/src/components/empty-state/empty-state-content.tsx +17 -0
  164. package/src/components/empty-state/empty-state-description.tsx +12 -0
  165. package/src/components/empty-state/empty-state-header.tsx +17 -0
  166. package/src/components/empty-state/empty-state-media.tsx +56 -0
  167. package/src/components/empty-state/empty-state-title.tsx +18 -0
  168. package/src/components/empty-state/empty-state.context.tsx +65 -0
  169. package/src/components/empty-state/empty-state.tsx +84 -0
  170. package/src/components/empty-state/empty-state.types.ts +14 -0
  171. package/src/components/empty-state/empty-state.variants.test.ts +245 -0
  172. package/src/components/empty-state/empty-state.variants.ts +132 -0
  173. package/src/components/empty-state/index.ts +20 -0
  174. package/src/components/fab/AGENTS.md +119 -0
  175. package/src/components/fab/fab-action.tsx +118 -0
  176. package/src/components/fab/fab-group.tsx +214 -0
  177. package/src/components/fab/fab-label.tsx +18 -0
  178. package/src/components/fab/fab.context.tsx +125 -0
  179. package/src/components/fab/fab.tsx +156 -0
  180. package/src/components/fab/fab.types.ts +17 -0
  181. package/src/components/fab/fab.variants.test.ts +274 -0
  182. package/src/components/fab/fab.variants.ts +234 -0
  183. package/src/components/fab/index.ts +37 -0
  184. package/src/components/feedback/AGENTS.md +95 -0
  185. package/src/components/feedback/feedback-action.tsx +16 -0
  186. package/src/components/feedback/feedback-cancel.tsx +34 -0
  187. package/src/components/feedback/feedback-close.tsx +21 -0
  188. package/src/components/feedback/feedback-content.tsx +24 -0
  189. package/src/components/feedback/feedback-field.tsx +70 -0
  190. package/src/components/feedback/feedback-footer.tsx +21 -0
  191. package/src/components/feedback/feedback-panel.tsx +54 -0
  192. package/src/components/feedback/feedback-submit.tsx +74 -0
  193. package/src/components/feedback/feedback-title.tsx +34 -0
  194. package/src/components/feedback/feedback-trigger.tsx +20 -0
  195. package/src/components/feedback/feedback.context.tsx +73 -0
  196. package/src/components/feedback/feedback.tsx +135 -0
  197. package/src/components/feedback/feedback.variants.test.ts +185 -0
  198. package/src/components/feedback/feedback.variants.ts +128 -0
  199. package/src/components/feedback/index.ts +24 -0
  200. package/src/components/field/AGENTS.md +19 -0
  201. package/src/components/field/field-label.tsx +19 -5
  202. package/src/components/field/field.context.tsx +17 -0
  203. package/src/components/field/field.tsx +15 -2
  204. package/src/components/field/field.variants.test.ts +24 -0
  205. package/src/components/field/field.variants.ts +20 -0
  206. package/src/components/field/index.ts +1 -0
  207. package/src/components/input/AGENTS.md +5 -0
  208. package/src/components/input/input.variants.test.ts +32 -0
  209. package/src/components/input/input.variants.ts +13 -1
  210. package/src/components/item/AGENTS.md +92 -0
  211. package/src/components/item/index.ts +25 -0
  212. package/src/components/item/item-actions.tsx +28 -0
  213. package/src/components/item/item-content.tsx +15 -0
  214. package/src/components/item/item-description.tsx +12 -0
  215. package/src/components/item/item-footer.tsx +13 -0
  216. package/src/components/item/item-group.tsx +32 -0
  217. package/src/components/item/item-header.tsx +13 -0
  218. package/src/components/item/item-media.tsx +37 -0
  219. package/src/components/item/item-separator.tsx +13 -0
  220. package/src/components/item/item-title.tsx +17 -0
  221. package/src/components/item/item.context.tsx +66 -0
  222. package/src/components/item/item.tsx +218 -0
  223. package/src/components/item/item.types.ts +12 -0
  224. package/src/components/item/item.variants.test.ts +263 -0
  225. package/src/components/item/item.variants.ts +200 -0
  226. package/src/components/kpi/AGENTS.md +125 -0
  227. package/src/components/kpi/index.ts +45 -0
  228. package/src/components/kpi/kpi-action.tsx +12 -0
  229. package/src/components/kpi/kpi-content.tsx +35 -0
  230. package/src/components/kpi/kpi-footer.tsx +52 -0
  231. package/src/components/kpi/kpi-group.tsx +70 -0
  232. package/src/components/kpi/kpi-header.tsx +25 -0
  233. package/src/components/kpi/kpi-icon.tsx +28 -0
  234. package/src/components/kpi/kpi-sparkline.tsx +145 -0
  235. package/src/components/kpi/kpi-stat.tsx +20 -0
  236. package/src/components/kpi/kpi-title.tsx +19 -0
  237. package/src/components/kpi/kpi-trend.tsx +105 -0
  238. package/src/components/kpi/kpi-value.tsx +32 -0
  239. package/src/components/kpi/kpi.context.tsx +111 -0
  240. package/src/components/kpi/kpi.tsx +154 -0
  241. package/src/components/kpi/kpi.types.ts +9 -0
  242. package/src/components/kpi/kpi.variants.test.ts +428 -0
  243. package/src/components/kpi/kpi.variants.ts +347 -0
  244. package/src/components/label/AGENTS.md +89 -0
  245. package/src/components/label/index.ts +10 -0
  246. package/src/components/label/label.tsx +97 -0
  247. package/src/components/label/label.variants.test.ts +176 -0
  248. package/src/components/label/label.variants.ts +123 -0
  249. package/src/components/menu/AGENTS.md +176 -0
  250. package/src/components/menu/index.ts +44 -0
  251. package/src/components/menu/menu-background.tsx +17 -0
  252. package/src/components/menu/menu-checkbox-item.tsx +57 -0
  253. package/src/components/menu/menu-content.tsx +303 -0
  254. package/src/components/menu/menu-item.tsx +162 -0
  255. package/src/components/menu/menu-label.tsx +14 -0
  256. package/src/components/menu/menu-radio-group.tsx +50 -0
  257. package/src/components/menu/menu-radio-item.tsx +57 -0
  258. package/src/components/menu/menu-separator.tsx +11 -0
  259. package/src/components/menu/menu-sub-content.tsx +72 -0
  260. package/src/components/menu/menu-sub-trigger.tsx +50 -0
  261. package/src/components/menu/menu-sub.tsx +75 -0
  262. package/src/components/menu/menu-trigger.tsx +57 -0
  263. package/src/components/menu/menu.context.tsx +214 -0
  264. package/src/components/menu/menu.tsx +88 -0
  265. package/src/components/menu/menu.types.ts +33 -0
  266. package/src/components/menu/menu.variants.test.ts +400 -0
  267. package/src/components/menu/menu.variants.ts +353 -0
  268. package/src/components/meter/AGENTS.md +135 -0
  269. package/src/components/meter/index.ts +43 -0
  270. package/src/components/meter/meter-fill.tsx +19 -0
  271. package/src/components/meter/meter-header.tsx +17 -0
  272. package/src/components/meter/meter-label.tsx +18 -0
  273. package/src/components/meter/meter-output.tsx +42 -0
  274. package/src/components/meter/meter-segments.tsx +83 -0
  275. package/src/components/meter/meter-track.tsx +16 -0
  276. package/src/components/meter/meter.context.tsx +62 -0
  277. package/src/components/meter/meter.tsx +286 -0
  278. package/src/components/meter/meter.types.ts +30 -0
  279. package/src/components/meter/meter.variants.test.ts +430 -0
  280. package/src/components/meter/meter.variants.ts +347 -0
  281. package/src/components/overlay/AGENTS.md +89 -0
  282. package/src/components/overlay/index.ts +27 -0
  283. package/src/components/overlay/overlay-portal.tsx +84 -0
  284. package/src/components/overlay/overlay-presence.test.ts +46 -0
  285. package/src/components/overlay/overlay-presence.ts +49 -0
  286. package/src/components/overlay/overlay-registry.test.ts +96 -0
  287. package/src/components/overlay/overlay-registry.ts +99 -0
  288. package/src/components/overlay/overlay-scrim.tsx +49 -0
  289. package/src/components/overlay/overlay.context.tsx +48 -0
  290. package/src/components/overlay/overlay.tsx +118 -0
  291. package/src/components/overlay/overlay.variants.test.ts +37 -0
  292. package/src/components/overlay/overlay.variants.ts +33 -0
  293. package/src/components/overlay/use-overlay-back-handler.ts +34 -0
  294. package/src/components/overlay/use-overlay-presence.ts +87 -0
  295. package/src/components/popover/AGENTS.md +138 -0
  296. package/src/components/popover/index.ts +48 -0
  297. package/src/components/popover/popover-anchor.tsx +49 -0
  298. package/src/components/popover/popover-arrow.tsx +89 -0
  299. package/src/components/popover/popover-close.tsx +73 -0
  300. package/src/components/popover/popover-content.tsx +235 -0
  301. package/src/components/popover/popover-description.tsx +18 -0
  302. package/src/components/popover/popover-title.tsx +33 -0
  303. package/src/components/popover/popover-trigger.tsx +56 -0
  304. package/src/components/popover/popover.context.tsx +71 -0
  305. package/src/components/popover/popover.position.test.ts +354 -0
  306. package/src/components/popover/popover.position.ts +286 -0
  307. package/src/components/popover/popover.tsx +123 -0
  308. package/src/components/popover/popover.variants.test.ts +135 -0
  309. package/src/components/popover/popover.variants.ts +85 -0
  310. package/src/components/popover/use-anchor-measure.ts +76 -0
  311. package/src/components/popover/use-anchored-content.ts +219 -0
  312. package/src/components/pressable/AGENTS.md +11 -1
  313. package/src/components/pressable/pressable.tsx +7 -2
  314. package/src/components/pressable/pressable.variants.test.ts +29 -0
  315. package/src/components/pressable/pressable.variants.ts +17 -0
  316. package/src/components/progress/AGENTS.md +120 -0
  317. package/src/components/progress/index.ts +40 -0
  318. package/src/components/progress/progress-fill.tsx +152 -0
  319. package/src/components/progress/progress-header.tsx +19 -0
  320. package/src/components/progress/progress-label.tsx +27 -0
  321. package/src/components/progress/progress-output.tsx +47 -0
  322. package/src/components/progress/progress-track.tsx +38 -0
  323. package/src/components/progress/progress.context.tsx +79 -0
  324. package/src/components/progress/progress.tsx +171 -0
  325. package/src/components/progress/progress.types.ts +23 -0
  326. package/src/components/progress/progress.variants.test.ts +335 -0
  327. package/src/components/progress/progress.variants.ts +284 -0
  328. package/src/components/progress-button/AGENTS.md +116 -0
  329. package/src/components/progress-button/index.ts +36 -0
  330. package/src/components/progress-button/progress-button-done.tsx +33 -0
  331. package/src/components/progress-button/progress-button-label.tsx +27 -0
  332. package/src/components/progress-button/progress-button.context.tsx +93 -0
  333. package/src/components/progress-button/progress-button.tsx +414 -0
  334. package/src/components/progress-button/progress-button.variants.test.ts +314 -0
  335. package/src/components/progress-button/progress-button.variants.ts +236 -0
  336. package/src/components/provider/AGENTS.md +38 -25
  337. package/src/components/provider/provider.tsx +38 -11
  338. package/src/components/radio/AGENTS.md +8 -10
  339. package/src/components/radio/radio.variants.test.ts +5 -6
  340. package/src/components/radio/radio.variants.ts +6 -7
  341. package/src/components/rating/AGENTS.md +143 -0
  342. package/src/components/rating/index.ts +43 -0
  343. package/src/components/rating/rating-output.tsx +39 -0
  344. package/src/components/rating/rating-star.tsx +70 -0
  345. package/src/components/rating/rating-stars.tsx +219 -0
  346. package/src/components/rating/rating.context.tsx +79 -0
  347. package/src/components/rating/rating.tsx +163 -0
  348. package/src/components/rating/rating.variants.test.ts +347 -0
  349. package/src/components/rating/rating.variants.ts +354 -0
  350. package/src/components/selection-mode/AGENTS.md +155 -0
  351. package/src/components/selection-mode/index.ts +43 -0
  352. package/src/components/selection-mode/selection-mode-action.tsx +65 -0
  353. package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
  354. package/src/components/selection-mode/selection-mode-group.tsx +123 -0
  355. package/src/components/selection-mode/selection-mode-header.tsx +85 -0
  356. package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
  357. package/src/components/selection-mode/selection-mode-item.tsx +182 -0
  358. package/src/components/selection-mode/selection-mode.context.tsx +89 -0
  359. package/src/components/selection-mode/selection-mode.tsx +207 -0
  360. package/src/components/selection-mode/selection-mode.types.ts +36 -0
  361. package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
  362. package/src/components/selection-mode/selection-mode.variants.ts +329 -0
  363. package/src/components/skeleton/AGENTS.md +120 -0
  364. package/src/components/skeleton/index.ts +15 -0
  365. package/src/components/skeleton/skeleton-clock.ts +48 -0
  366. package/src/components/skeleton/skeleton-group.tsx +64 -0
  367. package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
  368. package/src/components/skeleton/skeleton.context.tsx +45 -0
  369. package/src/components/skeleton/skeleton.tsx +227 -0
  370. package/src/components/skeleton/skeleton.variants.test.ts +245 -0
  371. package/src/components/skeleton/skeleton.variants.ts +222 -0
  372. package/src/components/slide-button/AGENTS.md +122 -0
  373. package/src/components/slide-button/index.ts +43 -0
  374. package/src/components/slide-button/slide-button-label.tsx +70 -0
  375. package/src/components/slide-button/slide-button-thumb.tsx +93 -0
  376. package/src/components/slide-button/slide-button.context.tsx +76 -0
  377. package/src/components/slide-button/slide-button.tsx +457 -0
  378. package/src/components/slide-button/slide-button.variants.test.ts +288 -0
  379. package/src/components/slide-button/slide-button.variants.ts +324 -0
  380. package/src/components/slider/index.ts +2 -0
  381. package/src/components/slider/slider-thumb.tsx +33 -1
  382. package/src/components/slider/slider.variants.test.ts +52 -0
  383. package/src/components/slider/slider.variants.ts +56 -2
  384. package/src/components/stack-card/AGENTS.md +169 -0
  385. package/src/components/stack-card/index.ts +37 -0
  386. package/src/components/stack-card/stack-card-action.tsx +66 -0
  387. package/src/components/stack-card/stack-card-actions.tsx +18 -0
  388. package/src/components/stack-card/stack-card-card.tsx +71 -0
  389. package/src/components/stack-card/stack-card-empty.tsx +30 -0
  390. package/src/components/stack-card/stack-card-slot.tsx +90 -0
  391. package/src/components/stack-card/stack-card-stamp.tsx +76 -0
  392. package/src/components/stack-card/stack-card.context.tsx +117 -0
  393. package/src/components/stack-card/stack-card.tsx +557 -0
  394. package/src/components/stack-card/stack-card.types.ts +26 -0
  395. package/src/components/stack-card/stack-card.variants.test.ts +355 -0
  396. package/src/components/stack-card/stack-card.variants.ts +360 -0
  397. package/src/components/steps/AGENTS.md +153 -0
  398. package/src/components/steps/index.ts +45 -0
  399. package/src/components/steps/steps-connector.tsx +31 -0
  400. package/src/components/steps/steps-description.tsx +26 -0
  401. package/src/components/steps/steps-indicator.tsx +106 -0
  402. package/src/components/steps/steps-item.tsx +225 -0
  403. package/src/components/steps/steps-panel.tsx +41 -0
  404. package/src/components/steps/steps-title.tsx +32 -0
  405. package/src/components/steps/steps-track.tsx +59 -0
  406. package/src/components/steps/steps.context.tsx +123 -0
  407. package/src/components/steps/steps.tsx +166 -0
  408. package/src/components/steps/steps.variants.test.ts +388 -0
  409. package/src/components/steps/steps.variants.ts +468 -0
  410. package/src/components/surface/AGENTS.md +82 -0
  411. package/src/components/surface/index.ts +15 -0
  412. package/src/components/surface/surface.context.tsx +56 -0
  413. package/src/components/surface/surface.tsx +87 -0
  414. package/src/components/surface/surface.variants.test.ts +227 -0
  415. package/src/components/surface/surface.variants.ts +165 -0
  416. package/src/components/swipe/AGENTS.md +140 -0
  417. package/src/components/swipe/index.ts +31 -0
  418. package/src/components/swipe/swipe-action.tsx +99 -0
  419. package/src/components/swipe/swipe-end.tsx +14 -0
  420. package/src/components/swipe/swipe-group.tsx +55 -0
  421. package/src/components/swipe/swipe-panel.tsx +55 -0
  422. package/src/components/swipe/swipe-start.tsx +15 -0
  423. package/src/components/swipe/swipe.context.tsx +135 -0
  424. package/src/components/swipe/swipe.tsx +393 -0
  425. package/src/components/swipe/swipe.types.ts +7 -0
  426. package/src/components/swipe/swipe.variants.test.ts +337 -0
  427. package/src/components/swipe/swipe.variants.ts +290 -0
  428. package/src/components/switch/switch.variants.ts +2 -2
  429. package/src/components/tabs/AGENTS.md +4 -3
  430. package/src/components/tabs/tabs.variants.test.ts +1 -1
  431. package/src/components/tabs/tabs.variants.ts +4 -6
  432. package/src/components/text/AGENTS.md +8 -2
  433. package/src/components/text/text.tsx +7 -0
  434. package/src/components/text/text.variants.test.ts +24 -1
  435. package/src/components/text/text.variants.ts +5 -0
  436. package/src/components/textarea/AGENTS.md +89 -0
  437. package/src/components/textarea/index.ts +16 -0
  438. package/src/components/textarea/textarea.tsx +157 -0
  439. package/src/components/textarea/textarea.variants.test.ts +219 -0
  440. package/src/components/textarea/textarea.variants.ts +171 -0
  441. package/src/components/toast/AGENTS.md +140 -0
  442. package/src/components/toast/index.ts +74 -0
  443. package/src/components/toast/toast-action.tsx +59 -0
  444. package/src/components/toast/toast-api.ts +32 -0
  445. package/src/components/toast/toast-close.tsx +44 -0
  446. package/src/components/toast/toast-content.tsx +11 -0
  447. package/src/components/toast/toast-description.tsx +11 -0
  448. package/src/components/toast/toast-indicator.tsx +34 -0
  449. package/src/components/toast/toast-item.tsx +308 -0
  450. package/src/components/toast/toast-title.tsx +13 -0
  451. package/src/components/toast/toast-viewport.tsx +135 -0
  452. package/src/components/toast/toast.context.tsx +69 -0
  453. package/src/components/toast/toast.store.test.ts +259 -0
  454. package/src/components/toast/toast.store.ts +338 -0
  455. package/src/components/toast/toast.timer.test.ts +64 -0
  456. package/src/components/toast/toast.timer.ts +51 -0
  457. package/src/components/toast/toast.tsx +132 -0
  458. package/src/components/toast/toast.variants.test.ts +393 -0
  459. package/src/components/toast/toast.variants.ts +304 -0
  460. package/src/components/toggle-button/AGENTS.md +106 -0
  461. package/src/components/toggle-button/index.ts +30 -0
  462. package/src/components/toggle-button/toggle-button-group.tsx +156 -0
  463. package/src/components/toggle-button/toggle-button-label.tsx +19 -0
  464. package/src/components/toggle-button/toggle-button.context.tsx +130 -0
  465. package/src/components/toggle-button/toggle-button.tsx +172 -0
  466. package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
  467. package/src/components/toggle-button/toggle-button.variants.ts +160 -0
  468. package/src/components/tooltip/AGENTS.md +133 -0
  469. package/src/components/tooltip/index.ts +28 -0
  470. package/src/components/tooltip/tooltip-arrow.tsx +40 -0
  471. package/src/components/tooltip/tooltip-content.tsx +179 -0
  472. package/src/components/tooltip/tooltip-description.tsx +19 -0
  473. package/src/components/tooltip/tooltip-text.tsx +21 -0
  474. package/src/components/tooltip/tooltip-title.tsx +20 -0
  475. package/src/components/tooltip/tooltip-trigger.tsx +103 -0
  476. package/src/components/tooltip/tooltip.context.tsx +68 -0
  477. package/src/components/tooltip/tooltip.tsx +213 -0
  478. package/src/components/tooltip/tooltip.variants.test.ts +231 -0
  479. package/src/components/tooltip/tooltip.variants.ts +177 -0
  480. package/src/hooks/use-calm-motion.tsx +31 -0
  481. package/src/lib/calm-motion.test.ts +17 -0
  482. package/src/lib/calm-motion.ts +23 -0
  483. package/src/lib/cn.parity.test.ts +62 -0
  484. package/src/lib/cn.test.ts +14 -0
  485. package/src/lib/cn.ts +11 -6
  486. package/src/no-gorhom.test.ts +54 -0
  487. package/src/styles/geometry.test.ts +1 -0
  488. package/src/styles/theme.css +4 -0
  489. package/src/styles/tokens.css +44 -0
  490. package/src/styles/tokens.test.ts +56 -0
  491. package/src/styles/tokens.ts +29 -1
  492. package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
  493. package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
@@ -267,3 +267,29 @@ describe("resolveBadgeInteractive", () => {
267
267
  expect(resolveBadgeInteractive({ onPress: noop, onLongPress: noop })).toBe(true);
268
268
  });
269
269
  });
270
+
271
+ describe("the etched badge material", () => {
272
+ test("flat keeps the capsule", () => {
273
+ expect(badgeVariants({}).root()).toContain("rounded-full");
274
+ });
275
+
276
+ test("etched squares the corner to the small step", () => {
277
+ const cls = badgeVariants({ material: "etched" }).root();
278
+ expect(cls).toContain("rounded-sm");
279
+ expect(cls).not.toContain("rounded-full");
280
+ expect(badgeVariants({ material: "etched" }).closeButton()).toContain("rounded-sm");
281
+ });
282
+
283
+ test("etched soft status fills are alpha tints of the status colour", () => {
284
+ for (const [color, fill] of [
285
+ ["success", "bg-success/8"],
286
+ ["warning", "bg-warning/8"],
287
+ ["destructive", "bg-destructive/8"],
288
+ ["info", "bg-info/8"],
289
+ ] as const) {
290
+ const cls = badgeVariants({ color, material: "etched", variant: "soft" }).root();
291
+ expect(cls).toContain(fill);
292
+ expect(cls).toContain(`dark:${fill.replace("/8", "/16")}`);
293
+ }
294
+ });
295
+ });
@@ -7,10 +7,18 @@ export const BADGE_VARIANTS = ["solid", "soft", "outline", "ghost"] as const;
7
7
  /** What the surface means. Orthogonal to {@link BADGE_VARIANTS}. */
8
8
  export const BADGE_COLORS = ["default", "primary", "success", "warning", "destructive", "info"] as const;
9
9
 
10
+ /**
11
+ * `flat` is the library default, a capsule. `etched` squares it to the small
12
+ * corner and tints the status fills as a low alpha of the status colour, so a
13
+ * badge sits quietly on any surface it lands on.
14
+ */
15
+ export const BADGE_MATERIALS = ["flat", "etched"] as const;
16
+
10
17
  export const BADGE_SIZES = ["sm", "md", "lg"] as const;
11
18
 
12
19
  export type BadgeVariant = (typeof BADGE_VARIANTS)[number];
13
20
  export type BadgeColor = (typeof BADGE_COLORS)[number];
21
+ export type BadgeMaterial = (typeof BADGE_MATERIALS)[number];
14
22
  export type BadgeSize = (typeof BADGE_SIZES)[number];
15
23
 
16
24
  /**
@@ -148,6 +156,7 @@ export const badgeVariants = tv({
148
156
  icon: "size-icon-md",
149
157
  },
150
158
  },
159
+ material: { flat: {}, etched: {} },
151
160
  // The empty `false` branch is load-bearing typing, not a placeholder.
152
161
  // See the note in button.variants.ts.
153
162
  isDisabled: { true: { root: "opacity-50" }, false: {} },
@@ -204,6 +213,18 @@ export const badgeVariants = tv({
204
213
  { variant: "ghost", color: "warning", class: { label: "text-warning-soft-foreground" } },
205
214
  { variant: "ghost", color: "destructive", class: { label: "text-destructive-soft-foreground" } },
206
215
  { variant: "ghost", color: "info", class: { label: "text-info-soft-foreground" } },
216
+
217
+ // Written out per colour: Tailwind's scanner cannot see `bg-${color}/8`.
218
+ { material: "etched", class: { root: "rounded-sm", closeButton: "rounded-sm" } },
219
+ { material: "etched", variant: "soft", color: "success", class: { root: "bg-success/8 dark:bg-success/16" } },
220
+ { material: "etched", variant: "soft", color: "warning", class: { root: "bg-warning/8 dark:bg-warning/16" } },
221
+ {
222
+ material: "etched",
223
+ variant: "soft",
224
+ color: "destructive",
225
+ class: { root: "bg-destructive/8 dark:bg-destructive/16" },
226
+ },
227
+ { material: "etched", variant: "soft", color: "info", class: { root: "bg-info/8 dark:bg-info/16" } },
207
228
  ],
208
229
  defaultVariants: {
209
230
  variant: "solid",
@@ -1,242 +1,197 @@
1
1
  # BottomSheet
2
2
 
3
- A panel that slides up over everything, on `@gorhom/bottom-sheet`'s modal. The
4
- library's first overlay. Compound root plus `BottomSheet.Trigger`,
5
- `BottomSheet.Portal`, `BottomSheet.Overlay`, `BottomSheet.Container`,
6
- `BottomSheet.Content`, `BottomSheet.ScrollView`, `BottomSheet.Close`,
7
- `BottomSheet.Title`, `BottomSheet.Description` and `BottomSheet.Footer`.
3
+ A panel that slides up from the bottom of the screen, over everything — a
4
+ Uniwind skin on `@delacour/react-native-bottom-sheet`, this repository's own
5
+ headless engine.
8
6
 
9
- `import { BottomSheet } from "@delacour/react-native-ui/bottom-sheet";`
7
+ `import { BottomSheet, BottomSheetProvider } from "@delacour/react-native-ui/bottom-sheet";`
8
+
9
+ The engine is an **optional peer**, like `@delacour/react-native-charts` is
10
+ for `Chart`: an app that never imports this subpath never resolves it, and
11
+ never has to build its native peer `react-native-teleport`. The consequence is
12
+ the one thing a consumer has to do that no other component asks of them —
13
+ **mount `BottomSheetProvider` once**, inside `DelacourProvider` and around the
14
+ navigator. `DelacourProvider` cannot mount it without importing the engine. See
15
+ [DelacourProvider](../provider/AGENTS.md).
10
16
 
11
17
  ## Files
12
18
 
13
19
  | File | What it holds |
14
20
  | --- | --- |
15
- | `index.ts` | → `@delacour/react-native-ui/bottom-sheet` |
16
- | `bottom-sheet.tsx` | Root + the `Object.assign` compound surface |
17
- | `bottom-sheet-trigger.tsx` | `BottomSheet.Trigger` |
18
- | `bottom-sheet-portal.tsx` | `BottomSheet.Portal`, and the overlay hoist |
19
- | `bottom-sheet-overlay.tsx` | `BottomSheet.Overlay` — one of the two `withUniwind` wrappers |
20
- | `bottom-sheet-container.tsx` | `BottomSheet.Container`, the modal, and the footer hoist |
21
- | `bottom-sheet-content.tsx` | `BottomSheet.Content` |
22
- | `bottom-sheet-scroll-view.tsx` | `BottomSheet.ScrollView` — the other wrapper |
23
- | `bottom-sheet-close.tsx` | `BottomSheet.Close` |
24
- | `bottom-sheet-title.tsx` | `BottomSheet.Title` |
25
- | `bottom-sheet-description.tsx` | `BottomSheet.Description` |
26
- | `bottom-sheet-footer.tsx` | `BottomSheet.Footer`, its sticky and inline branches |
27
- | `bottom-sheet-background.tsx` | The sheet's surface — internal |
28
- | `bottom-sheet-handle.tsx` | The grabber — internal |
29
- | `bottom-sheet.context.tsx` | Three contexts — the sheet, the portal, the container |
30
- | `bottom-sheet.variants.ts` | Pure `tv()` slots + two resolvers, no RN imports |
31
- | `bottom-sheet.variants.test.ts` | |
32
- | `use-bottom-sheet-input.ts` | What an `Input` needs to be a field the sheet owns |
33
-
34
- ## Design
35
-
36
- - **Two parts are not drawn where they are written, and that is the component.**
37
- gorhom takes the scrim and a pinned footer as *render props* on the modal —
38
- `backdropComponent` and `footerComponent` — not as children. `BottomSheet.Portal`
39
- lifts the `Overlay` out of its own children and publishes it on context;
40
- `BottomSheet.Container` lifts a `Footer sticky` out of its own and clones both
41
- into those slots. The alternative was a `backdrop` prop and a `footer` prop on
42
- the container, which would put two parts of the anatomy somewhere the anatomy
43
- cannot see them. `hoistOverlay` and `hoistFooter` live with the part that lifts
44
- its own children — the rule `withIndicator` follows in [`radio.tsx`](../radio/AGENTS.md) — and the
45
- decision inside the second, `resolveFooterPlacement`, is pure so `bun test`
46
- reaches the matrix.
47
- - **The scrim's press area stops at the top of the sheet, and rebuilding it that
48
- way is the only reason `Input` works in here.** gorhom's backdrop puts a
49
- full-screen `Gesture.Tap()` behind the sheet. Gesture Handler resolves a touch
50
- between competing GESTURES, so a `Button` in the sheet has one of its own and
51
- wins — but a `TextInput` has none, the backdrop takes the tap, and the sheet
52
- CLOSES instead of the field focusing. That is what gorhom's own
53
- `BottomSheetTextInput` exists to fix, and taking that route would have left
54
- this package's `Input` — its variants, its `Input.Group`, its `Field`
55
- cascade — unusable inside a sheet. So `BottomSheet.Overlay` passes
56
- `pressBehavior="none"` (no gesture anywhere) and lays a `Pressable` over the
57
- band ABOVE the sheet, sized from the sheet's own `animatedPosition`. It cannot
58
- overlap the sheet, so there is nothing for it to take, and the part of the
59
- scrim anyone can actually see still closes. Confirmed both ways on a
60
- simulator — with gorhom's backdrop the field could not be focused at all.
61
- - **Never call `dismiss()` on a modal that was never presented.** gorhom's modal
62
- starts at status `INITIAL`; `dismiss()` reads that as a live sheet closing and
63
- moves it to `DISMISSING`, where it stays. The next `present()` mounts the
64
- portal and renders the sheet — and it never animates in, never lays out its
65
- content and never fires `onChange`. Nothing throws, nothing is logged, and the
66
- screen simply does not change. `BottomSheet.Container` keeps a
67
- `hasPresentedRef` so the effect that mirrors `isOpen` only ever dismisses
68
- something it opened. This cost an afternoon; do not "simplify" the guard away.
69
- - **A portal breaks context, so the container re-publishes its own.** The sheet's
70
- children are rendered by `@gorhom/portal` into a host inside
71
- `BottomSheetModalProvider` — a different place in the React tree from where
72
- they were written — so the `BottomSheetContext` the root provides does not
73
- reach them, and `BottomSheet.Close` throws "must be rendered inside a
74
- `<BottomSheet>`". `BottomSheet.Container` wraps the modal's children in the
75
- same provider again. `BottomSheet.Overlay` cannot be covered that way — gorhom
76
- renders the backdrop outside those children — so its press area reads gorhom's
77
- `useBottomSheet()` instead, which lands on `onDismiss` and therefore on
78
- `onOpenChange` anyway.
79
- - **There is no size and no colour axis.** A sheet has one shape and one surface;
80
- a second way to paint it would be a second thing to keep in step for no
81
- question anyone is asking. `bottomSheetVariants` is slots and nothing else.
82
-
83
- ## Footer and keyboard
84
-
85
- - **`BottomSheet.Footer` is inline by default, and `sticky` pins it.** Same word,
86
- same default as [`Screen.Footer`](../screen/AGENTS.md), so the two cannot be read as opposites. The
87
- two branches deliberately look different: an inline footer is in the flow and
88
- inherits the sheet's surface, while a pinned one draws OVER the content and so
89
- brings a background and a top hairline of its own. Without them the content
90
- scrolls straight through it and its buttons are legible only where they happen
91
- to overlap blank space — `Screen.Footer`'s rule about its backing, one
92
- component along. A footer written `sticky` but rendered outside a container
93
- falls back to the inline branch rather than throwing: there is no footer
94
- position to ride, and a row of buttons in the flow is the useful failure.
95
- - **A pinned footer is TALLER by the safe-area band; it is not moved up by it.**
96
- gorhom's `bottomInset` translates the whole footer up instead, which leaves a
97
- band the height of the home indicator between the footer and the bottom of the
98
- sheet — with the content scrolling through it in plain view. So `bottomInset`
99
- stays 0 and the band is part of the footer's own `paddingBottom`, putting its
100
- surface against the bottom of the screen.
101
- - **The keyboard takes the band back, and ONLY the band.**
102
- `BOTTOM_SHEET_FOOTER_PADDING` is always there and the band collapses into it as
103
- `useReanimatedKeyboardAnimation()`'s `progress` runs 0 → 1, so the controls
104
- never end up flush against the keyboard. Sliding the whole footer down by the
105
- band instead — the first thing tried — parks its padding behind the keyboard
106
- along with it, and the buttons sit on the keys. It also has to be the footer
107
- genuinely getting *shorter* rather than moving, because that is what lets the
108
- body's reserve shrink with it instead of holding back the height of a home
109
- indicator that is no longer on screen. Both numbers are constants rather than
110
- classes, for `SCREEN_FOOTER_PADDING`'s reason: an animated padding cannot be a
111
- class, and the reserve maths has to read the same value.
112
- - **The footer publishes its measured height and the body reserves it with an
113
- animated spacer.** Not gorhom's `enableFooterMarginAdjustment`, which turns that
114
- height into content-container padding through React state — and since the
115
- footer changes height on every frame of the keyboard animation, that is a
116
- commit per frame. A shared value read by a spacer's animated style costs
117
- nothing on the JS thread; [`Screen`](../screen/AGENTS.md) reserves its own chrome exactly this way.
118
- The spacer sits OUTSIDE the classed content box, or the content's own `gap`
119
- lands in front of it and silently doubles `BOTTOM_SHEET_FOOTER_GAP`. The
120
- container owns the shared value because the footer and the body are siblings in
121
- gorhom's tree and neither can see the other.
122
- - **`BottomSheet.ScrollView` gives the footer's height up as its own frame, not
123
- as a spacer.** A spacer inside the content leaves the scroll view running the
124
- sheet's full height, so its indicator and its overscroll bounce carry on
125
- underneath the footer and the bottom of the track is hidden behind the buttons.
126
- So the scroll view takes the same shared value as an animated `marginBottom`,
127
- ends at the footer's hairline, and its content keeps only the gap —
128
- `resolveSheetScrollEndPadding`. `BottomSheet.Content` keeps the spacer: it does
129
- not scroll, so there is no indicator to stop. Confirmed on a simulator by
130
- `measureInWindow` — the scroll view's bottom and the footer's top land on the
131
- same point.
132
- - **Do not put a `KeyboardStickyView` inside a sheet.** This is the one keyboard
133
- rule that differs from [`Screen`](../screen/AGENTS.md). gorhom's `animatedFooterPosition` already
134
- carries a pinned footer clear of the keyboard and the translate above gives up
135
- the safe-area band on top of that; a third mechanism moving the same view would
136
- fight both for prop ownership every frame.
137
- - **Exactly one thing in the sheet pays for the home indicator.** With a pinned
138
- footer that is the footer, whose own padding carries the band and whose height
139
- the body already reserves; the content asking for it too would count it twice.
140
- With no pinned footer the content is the bottom-most thing and takes it.
141
- `resolveSheetBottomInset` is that decision and it is pure, so `bun test` reaches
142
- it. As everywhere else the band comes from `useSafeAreaInsets()` and never from
143
- `pb-safe`.
144
-
145
- ## Styling
146
-
147
- - **`BottomSheet.ScrollView` puts its classes on an inner `View`, not on gorhom's
148
- content container**, and that is not tidiness. Uniwind compiles a
149
- `contentContainerClassName` into an **array** alongside any
150
- `contentContainerStyle`; gorhom's footer-margin adjustment then reads
151
- `paddingBottom` off a flattened style, an array flattens to an array, the band
152
- reads as zero and the last row hides behind a pinned footer. One writer for
153
- that style, and it is the inset. A scrollable sheet also needs
154
- `enableDynamicSizing={false}` and explicit `snapPoints`, or the sheet grows to
155
- the content and there is nothing to scroll within.
156
- - **Two `withUniwind` wrappers, and only two.** `BottomSheetBackdrop` and
157
- `BottomSheetScrollView` are leaves that take a `style` and no `className`, so
158
- each is wrapped once, at module scope, in its own file (rule 7).
159
- `BottomSheetView` and `BottomSheetFooter` are **not** wrapped: both take
160
- children, so an ordinary `View` nested inside carries the classes instead —
161
- the move [`Screen.Footer`](../screen/AGENTS.md) already makes with `KeyboardStickyView`. Reach for a
162
- nested view before a wrapper whenever the third-party component has room for
163
- one. The scroll view's wrapper also needs its signature restated, the way
164
- `Screen.LegendList`'s does — `withUniwind`'s mapped return type collapses a
165
- scrollable's animated props to a single entry.
166
- - **`BottomSheet.Container` takes no `className`.** gorhom's modal wears an
167
- animated style its own position writes every frame, so a class would reach
168
- nothing `backgroundClassName` does not reach better. The background and the
169
- handle are supplied as `backgroundComponent` and `handleComponent` — this
170
- package's own views, so they take classes — and a `backgroundStyle`,
171
- `handleStyle` or `handleIndicatorStyle` still arrives, forwarded as the `style`
172
- each is handed. **Forwarding the background's `style` is load-bearing:** gorhom
173
- passes `[StyleSheet.absoluteFill, backgroundStyle]`, and dropping it renders the
174
- sheet transparent over the app.
175
-
176
- ## Behaviour and defaults
177
-
178
- - **One callback for every way a sheet closes.** A swipe down, a press on the
179
- scrim, `BottomSheet.Close` and a controlled `isOpen` all arrive at
180
- `onOpenChange`, because presenting is driven by state and gorhom's `onDismiss`
181
- is consumed rather than forwarded. gorhom's own `onClose` fires for the gesture
182
- alone, which is the shape that makes a caller wire three handlers and still
183
- miss one. `useBottomSheet().sheetRef` remains for the imperative calls with no
184
- declarative form — `snapToIndex`, `expand`, `collapse`.
185
- - **`accessible` defaults to `false`, and it is the least obvious line in the
186
- component.** gorhom marks the sheet's content container `accessible` with the
187
- label "Bottom Sheet" and the `adjustable` role. On iOS an `accessible`
188
- container collapses its whole subtree into ONE element, so every field, button
189
- and line of copy inside becomes unreachable to VoiceOver — the form gallery
190
- route was a single "Bottom Sheet" element until this default was flipped. It is
191
- [`Field`](../field/AGENTS.md)'s rule one component along: the row is `accessible={false}` so the
192
- control stays the element a screen reader sees. Verified with
193
- `native-describe-screen` on a simulator, not assumed.
194
- - **Three defaults differ from gorhom's, and each is a modal-sheet decision.**
195
- `enablePanDownToClose` is on, because a modal sheet has no collapsed resting
196
- state to pan down to. The backdrop appears on index `0` and disappears on `-1`
197
- rather than `1` and `0`, for the same reason — gorhom's defaults suit a
198
- persistent sheet that dims the app only once expanded. And the keyboard trio
199
- is `interactive` / `restore` / `adjustResize`: leaving Android on gorhom's
200
- `adjustPan` slides the whole window up instead of resizing it and puts the
201
- footer off-screen, where `resize` is already what Expo configures and what
202
- `KeyboardProvider` requires. All are ordinary props; `BOTTOM_SHEET_KEYBOARD_DEFAULTS`
203
- is a const so a test pins the trio rather than three inline literals.
204
- - **`--overlay` is a token, not a `bg-black/50`.** A pure-black scrim over
205
- a near-black dark theme is nearly invisible, so the two variants carry
206
- different alphas — and because the alpha lives in the token, the backdrop's
207
- `opacity` defaults to **1** rather than gorhom's 0.5, which would multiply the
208
- two and land the scrim at a fifth of what the theme asked for. It takes no
209
- `-foreground`: nothing is drawn on a scrim, and the sheet in front brings its
210
- own. A test pins the token against both `@variant` blocks and pins the opacity
211
- against the alpha being there.
212
-
213
- ## Input
214
-
215
- - **`useBottomSheetInput()` is why `Input` still works in here.** A sheet only
216
- grows for the keyboard when it knows the focused field is one of its own, and
217
- gorhom's answer is to use its `BottomSheetTextInput` — which would leave this
218
- package's `Input`, its variants, its `Input.Group` and its `Field` cascade
219
- unusable inside a sheet. The registration is exposed as `{ onFocus, onBlur, ref }`
220
- instead, so `Input` imports nothing from `@gorhom/bottom-sheet` and the hook is
221
- inert outside a sheet. The `ref` is not optional polish: without it, moving
222
- focus between two fields in one sheet reads as the keyboard closing and
223
- reopening and the sheet resizes twice on the way. It covers the keyboard half only:
224
- a *drag* across a field can still be claimed by the sheet's content pan, since
225
- gorhom's input is built on Gesture Handler's `TextInput` and this one is not.
226
- If that ever bites, the fix is `enableContentPanningGesture={false}` on the
227
- container. The *tap* half is the scrim's problem and is solved above.
21
+ | `index.ts` | → `@delacour/react-native-ui/bottom-sheet`. The compound, the variants, the provider and the hooks re-exported from the engine, and the two compat aliases below |
22
+ | `bottom-sheet.tsx` | `BottomSheet` — the engine's root with this library's defaults: `bottomInset` from the safe area, the two haptics, the stale-keyboard guard. The `Object.assign` names every part |
23
+ | `bottom-sheet.variants.ts` | The slotted `tv()` — `overlay`, `background` (with a `detached` variant), `handle`, `handleIndicator`, `content`, `scrollContent`, `steps`, `step`, `footer`, `stickyFooter`, `close`, `title` — and the numeric constants the engine's props take |
24
+ | `bottom-sheet.variants.test.ts` | Every token the slots name exists in both themes; the rules below, pinned |
25
+ | `bottom-sheet-trigger.tsx` | `BottomSheet.Trigger` — this library's `Pressable`, or `asChild` to donate the press |
26
+ | `bottom-sheet-overlay.tsx` | `BottomSheet.Overlay` — the engine's scrim in `bg-overlay`, opacity 1 |
27
+ | `bottom-sheet-container.tsx` | `BottomSheet.Container` — the panel; writes `Background` and `Handle` when the caller writes neither |
28
+ | `bottom-sheet-background.tsx` | `BottomSheet.Background` — `bg-popover`, top corners rounded, every corner when detached |
29
+ | `bottom-sheet-handle.tsx` | `BottomSheet.Handle` — the engine's row with the pill inside it |
30
+ | `bottom-sheet-content.tsx` | `BottomSheet.Content` — the static body, gutter and gap on the measured box |
31
+ | `bottom-sheet-scroll-view.tsx` | `BottomSheet.ScrollView` — classes on an inner `View` |
32
+ | `bottom-sheet-flat-list.tsx` | `BottomSheet.FlatList` — classes on the content container |
33
+ | `bottom-sheet-section-list.tsx` | `BottomSheet.SectionList` — same |
34
+ | `bottom-sheet-legend-list.tsx` | `BottomSheet.LegendList` — built here with `createBottomSheetScrollable`, because `@legendapp/list` is this library's optional peer and not the engine's |
35
+ | `bottom-sheet-footer.tsx` | `BottomSheet.Footer` — inline by default, `sticky` to pin |
36
+ | `bottom-sheet-close.tsx` | `BottomSheet.Close` — the engine's `Close asChild` around `Pressable feedback="fade"` and an `IconCrossSmall` |
37
+ | `bottom-sheet-title.tsx` | `BottomSheet.Title` — the engine's `Title asChild` around `Text.Header` |
38
+ | `bottom-sheet-description.tsx` | `BottomSheet.Description` — `Description asChild` around a muted `Text.Paragraph` |
39
+ | `bottom-sheet-text-input.tsx` | `BottomSheet.TextInput` — `Input` spread with the engine's registration |
40
+ | `bottom-sheet-steps.tsx` | `BottomSheet.Steps` and `BottomSheet.Step` — the multi-step body, gutter on the stack and vertical padding on each step |
41
+ | `use-bottom-sheet-input.ts` | `useBottomSheetInput()` — the engine's `useBottomSheetTextInput` under the name this library shipped |
42
+
43
+ `Portal`, `Host` and `Provider` are the engine's own, re-exported unchanged:
44
+ none of them draws anything a class could reach.
45
+
46
+ ## Anatomy
47
+
48
+ ```tsx
49
+ <BottomSheet snapPoints? dynamicSizing? maxDynamicContentSize? detached? keyboardBehavior? …>
50
+ <BottomSheet.Trigger asChild><Button /></BottomSheet.Trigger>
51
+ <BottomSheet.Portal inline? hostName?>
52
+ <BottomSheet.Overlay pressBehavior? />
53
+ <BottomSheet.Container backgroundClassName? handleClassName? handleIndicatorClassName?>
54
+ <BottomSheet.Background /> <BottomSheet.Handle /> // written for you unless you write them
55
+ <BottomSheet.Content> | <BottomSheet.ScrollView> | <BottomSheet.FlatList> | <BottomSheet.SectionList> | <BottomSheet.LegendList> | <BottomSheet.Steps controller>
56
+ <BottomSheet.Close /> <BottomSheet.Title /> <BottomSheet.Description /> <BottomSheet.TextInput />
57
+ <BottomSheet.Footer sticky? />
58
+ </BottomSheet.Container>
59
+ </BottomSheet.Portal>
60
+ </BottomSheet>
61
+
62
+ <BottomSheetProvider /> // once, in the app root
63
+ <BottomSheet.Host name="modal" /> // last in a native modal screen
64
+ ```
65
+
66
+ `Container` is the panel that moves; `Content` is the static body inside it.
67
+ Sizing and behaviour — `snapPoints`, `dynamicSizing`, `maxDynamicContentSize`,
68
+ `keyboardBehavior`, `detached`, the haptics — are the **root's** props. The
69
+ engine's names win everywhere; this skin aliases nothing.
70
+
71
+ ## What the engine owns
72
+
73
+ Everything that is not a class. The open state and the intent queue; the
74
+ height-space geometry and the snap points; the handle and content pans and how a
75
+ list shares a finger with them; the keyboard — who owns it, `interactive` /
76
+ `extend` / `fillParent` / `none`, the lift and the restore; the sticky footer's
77
+ line and the safe-area band under it; the teleport portal, the hosts, the
78
+ registry that stacks sheets and answers Android's back button; the detached
79
+ card's frame; the step machine and the height that glides between steps; and
80
+ the accessibility — the panel is a modal view labelled by the title, the
81
+ handle is the one adjustable element. Its `AGENTS.md` and the `AGENTS.md` in
82
+ each of its `src/*` folders carry the reasoning and the device verification;
83
+ read those before touching behaviour, because behaviour is not here.
84
+
85
+ ## What this skin owns
86
+
87
+ - **Classes, from one `tv()`.** Every part is the engine's part with a slot
88
+ from `bottomSheetVariants` on it. The engine's parts take `style`, not
89
+ `className`, so each is wrapped in `withUniwind` **once, at module scope** —
90
+ in render the wrapper would mint a new component type every pass, remount
91
+ the panel and re-attach the pans. Rule 7's carve-out for third-party
92
+ components applies: the engine is another package.
93
+ - **Where the classes land.** `Content`, `Footer` and `Steps` hand `style` to
94
+ the engine's *measured* inner view, so a class there is height the dynamic
95
+ snap point counts. `ScrollView` puts its content classes on an inner `View`
96
+ rather than the content container, so the one style this file writes there —
97
+ the gap above a pinned footer — has a single writer. The three virtualised
98
+ lists have no inner box, so theirs go on `contentContainerClassName`; the
99
+ engine flattens the resulting array to one object before the list measures
100
+ it, which is what makes that safe where it was not with the library this
101
+ replaces.
102
+ - **`Container` writes `Background` and `Handle` for you** when the children
103
+ hold neither, detected by element type. Write either yourself and it writes
104
+ nothing: a custom handle with no surface is a decision. The three
105
+ `*ClassName` props reach only the parts it writes.
106
+ - **`Title` and `Description` are `Text` presets**, through the engine's
107
+ `asChild`. The engine contributes the `nativeID` the panel is labelled by and
108
+ the heading role; the preset contributes the type. The `title` slot carries
109
+ `pr-8` for the close control and nothing else — a `text-lg` there would be a
110
+ second definition of `Text.Header` — and the test asserts it.
111
+ - **`Close` and `Trigger` are this library's `Pressable`**, through `asChild`.
112
+ The engine's press handler is donated to the pressable rather than wrapping
113
+ it, because two tap gestures in an ancestor/descendant pair give the touch to
114
+ the descendant and a wrapping trigger never fires. `Close` presses with
115
+ `fade` and an 8pt slop, for `Badge.CloseButton`'s and `Checkbox`'s reasons.
116
+ - **The root fills in three defaults.** `bottomInset` is `useSafeAreaInsets().bottom`,
117
+ so the engine reserves the home-indicator band and nobody pads for it by
118
+ hand. `onSnapPointHaptic` is `Presets.System.selection` and `onCloseHaptic` is
119
+ `Presets.System.impactLight` — pulsar's presets are worklets, so they are
120
+ passed as they are; a JS function there is `undefined is not a function` on
121
+ the UI thread. And `useKeyboardAnimationGuard()` runs on mount, the same
122
+ stale-keyboard repair `Screen.Footer` runs. `topInset` stays at the engine's
123
+ zero, so a `%` snap point is the fraction of the window it always was.
124
+ - **`Footer.sticky` defaults to `false`**, matching `Screen.Footer`, and this is
125
+ the one engine default the skin overrides. A pinned footer takes
126
+ `padding={BOTTOM_SHEET_FOOTER_PADDING}` through the engine's prop rather
127
+ than a class, so the padding is inside the box the snap point measures; the
128
+ `stickyFooter` slot carries the gutter, the surface and the hairline, and
129
+ the test asserts it carries no vertical padding.
130
+ - **The overlay's opacity is 1**, because `--overlay` carries its own alpha and
131
+ the two theme variants carry different ones. `BOTTOM_SHEET_OVERLAY_OPACITY`
132
+ and `BOTTOM_SHEET_BACKDROP_INDICES` restate the engine's defaults so a test
133
+ can pin them against the theme.
134
+ - **`LegendList` is built here.** The engine exports
135
+ `createBottomSheetScrollable` and ships `ScrollView`, `FlatList` and
136
+ `SectionList`; `@legendapp/list` is this library's optional peer, so its
137
+ body is made here, once at module scope, and registered as a flat list.
138
+
139
+ ## Compat
140
+
141
+ Two names survive from the previous implementation for callers that imported
142
+ them: `useBottomSheetInput()` is the engine's `useBottomSheetTextInput()`, and
143
+ `BottomSheetHandle` is the engine's `BottomSheetRef`, marked deprecated.
144
+ `resolveSheetBottomInset` and `resolveSheetScrollEndPadding` moved into the
145
+ engine's `./core` and are re-exported from here. `useBottomSheetContext()` is
146
+ the engine's `useOptionalBottomSheet()`.
147
+
148
+ Gone: `BOTTOM_SHEET_KEYBOARD_DEFAULTS` (the engine's `keyboardBehavior` and
149
+ `keyboardBlurBehavior` defaults are the same values, and there is no Android
150
+ input-mode prop), `resolveFooterPlacement` (nothing is hoisted any more), and
151
+ the container and portal contexts (the engine provides its contexts once, at
152
+ the root, and teleport keeps the React tree in place so they reach every part).
153
+
154
+ ## No longer needed
155
+
156
+ The previous implementation, on a third-party modal, documented sixteen
157
+ workarounds. Every one of them is either the engine's job now or simply not a
158
+ problem it has:
159
+
160
+ - dismiss-before-present deadlock — state opens, intents move; closing a closed
161
+ sheet is nothing
162
+ - the backdrop stealing taps from a `TextInput` — the overlay is a `Pressable`
163
+ written before the panel, resolved by view order
164
+ - context lost across the portal — teleport moves the native view, not the tree
165
+ - `accessible` collapsing the panel into one element — the panel is a modal
166
+ view, never `accessible`
167
+ - the footer's `bottomInset` band — the geometry's footer line holds still
168
+ through the keyboard; the band is a spacer it owns
169
+ - `enableFooterMarginAdjustment` committing a render per frame — the body's
170
+ spacer is an animated style off `footerHeight`
171
+ - `android_keyboardInputMode` — keyboard-controller owns the window
172
+ - stale keyboard values — the root runs the guard; the engine runs the same
173
+ reconcile on every presentation
174
+ - an input's drag captured by the sheet — the content pan fails on horizontal
175
+ movement and yields to the field
176
+ - `snapPoints` identity re-deriving under an open sheet — normalised once per
177
+ change, on the UI thread
178
+ - nesting a sheet in a sheet — the registry stacks them; `stackBehavior` and
179
+ `dismissAll` are for exactly that
180
+ - `ScrollView` needing `dynamicSizing={false}` and `snapPoints` — a list's
181
+ content size is the dynamic snap point
182
+ - lifting `Overlay` and a sticky `Footer` out of the tree as render props —
183
+ every part renders where it is written
184
+ - a `Content` inside a `Container` paying the safe-area band by hand — the
185
+ geometry reserves it
186
+ - a sheet mounted while the keyboard was up floating a keyboard-height off the
187
+ bottom — ownership is decided per keyboard, sticky through a transition
188
+ - two footers, one inline and one sticky, needing a placement rule — a footer
189
+ is where you write it
228
190
 
229
191
  ## Structure
230
192
 
231
- - **There is no `bottom-sheet.types.ts`.** Nothing is shared by two modules that
232
- is not already a type somewhere better: the text parts take `TextPresetProps`,
233
- which `Text` already exports, and the container reads its footer's props from
234
- `bottom-sheet-footer.tsx` directly — no cycle, since a part never imports the
235
- container. A types leaf holding one alias would be a file with nothing in it.
236
- - **There is no `FullWindowOverlay`, so nothing imports `react-native-screens`.**
237
- `BottomSheetModal` hosts itself in the `BottomSheetModalProvider` that
238
- `DelacourProvider` mounts, which covers everything short of drawing over a
239
- *native* stack modal. gorhom's `containerComponent` is the escape hatch if a
240
- call site ever needs that; it is not worth the optional peer, the iOS element
241
- inspector and the keyboard caveats before then.
242
-
193
+ Pattern B, compound and context — the contexts are the engine's. The parts
194
+ never import `./bottom-sheet` or `./index` (rule 3); they import the engine and
195
+ `./bottom-sheet.variants`. `bun test` reaches the variants and the constants;
196
+ the behaviour is the engine's and is verified there, and the skin on a
197
+ simulator through `apps/playground`'s Bottom sheet galleries.