@delacour/react-native-ui 0.1.0-alpha.4 → 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 (481) hide show
  1. package/README.md +10 -6
  2. package/package.json +48 -4
  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 -231
  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 -208
  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 -122
  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 -62
  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 -87
  55. package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -126
  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 +8 -0
  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/selection-mode/AGENTS.md +155 -0
  344. package/src/components/selection-mode/index.ts +43 -0
  345. package/src/components/selection-mode/selection-mode-action.tsx +65 -0
  346. package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
  347. package/src/components/selection-mode/selection-mode-group.tsx +123 -0
  348. package/src/components/selection-mode/selection-mode-header.tsx +85 -0
  349. package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
  350. package/src/components/selection-mode/selection-mode-item.tsx +182 -0
  351. package/src/components/selection-mode/selection-mode.context.tsx +89 -0
  352. package/src/components/selection-mode/selection-mode.tsx +207 -0
  353. package/src/components/selection-mode/selection-mode.types.ts +36 -0
  354. package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
  355. package/src/components/selection-mode/selection-mode.variants.ts +329 -0
  356. package/src/components/skeleton/AGENTS.md +120 -0
  357. package/src/components/skeleton/index.ts +15 -0
  358. package/src/components/skeleton/skeleton-clock.ts +48 -0
  359. package/src/components/skeleton/skeleton-group.tsx +64 -0
  360. package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
  361. package/src/components/skeleton/skeleton.context.tsx +45 -0
  362. package/src/components/skeleton/skeleton.tsx +227 -0
  363. package/src/components/skeleton/skeleton.variants.test.ts +245 -0
  364. package/src/components/skeleton/skeleton.variants.ts +222 -0
  365. package/src/components/slide-button/AGENTS.md +122 -0
  366. package/src/components/slide-button/index.ts +43 -0
  367. package/src/components/slide-button/slide-button-label.tsx +70 -0
  368. package/src/components/slide-button/slide-button-thumb.tsx +93 -0
  369. package/src/components/slide-button/slide-button.context.tsx +76 -0
  370. package/src/components/slide-button/slide-button.tsx +457 -0
  371. package/src/components/slide-button/slide-button.variants.test.ts +288 -0
  372. package/src/components/slide-button/slide-button.variants.ts +324 -0
  373. package/src/components/slider/index.ts +2 -0
  374. package/src/components/slider/slider-thumb.tsx +33 -1
  375. package/src/components/slider/slider.variants.test.ts +52 -0
  376. package/src/components/slider/slider.variants.ts +56 -2
  377. package/src/components/stack-card/AGENTS.md +169 -0
  378. package/src/components/stack-card/index.ts +37 -0
  379. package/src/components/stack-card/stack-card-action.tsx +66 -0
  380. package/src/components/stack-card/stack-card-actions.tsx +18 -0
  381. package/src/components/stack-card/stack-card-card.tsx +71 -0
  382. package/src/components/stack-card/stack-card-empty.tsx +30 -0
  383. package/src/components/stack-card/stack-card-slot.tsx +90 -0
  384. package/src/components/stack-card/stack-card-stamp.tsx +76 -0
  385. package/src/components/stack-card/stack-card.context.tsx +117 -0
  386. package/src/components/stack-card/stack-card.tsx +557 -0
  387. package/src/components/stack-card/stack-card.types.ts +26 -0
  388. package/src/components/stack-card/stack-card.variants.test.ts +355 -0
  389. package/src/components/stack-card/stack-card.variants.ts +360 -0
  390. package/src/components/steps/AGENTS.md +153 -0
  391. package/src/components/steps/index.ts +45 -0
  392. package/src/components/steps/steps-connector.tsx +31 -0
  393. package/src/components/steps/steps-description.tsx +26 -0
  394. package/src/components/steps/steps-indicator.tsx +106 -0
  395. package/src/components/steps/steps-item.tsx +225 -0
  396. package/src/components/steps/steps-panel.tsx +41 -0
  397. package/src/components/steps/steps-title.tsx +32 -0
  398. package/src/components/steps/steps-track.tsx +59 -0
  399. package/src/components/steps/steps.context.tsx +123 -0
  400. package/src/components/steps/steps.tsx +166 -0
  401. package/src/components/steps/steps.variants.test.ts +388 -0
  402. package/src/components/steps/steps.variants.ts +468 -0
  403. package/src/components/surface/AGENTS.md +72 -0
  404. package/src/components/surface/index.ts +15 -0
  405. package/src/components/surface/surface.context.tsx +54 -0
  406. package/src/components/surface/surface.tsx +70 -0
  407. package/src/components/surface/surface.variants.test.ts +180 -0
  408. package/src/components/surface/surface.variants.ts +133 -0
  409. package/src/components/swipe/AGENTS.md +140 -0
  410. package/src/components/swipe/index.ts +31 -0
  411. package/src/components/swipe/swipe-action.tsx +99 -0
  412. package/src/components/swipe/swipe-end.tsx +14 -0
  413. package/src/components/swipe/swipe-group.tsx +55 -0
  414. package/src/components/swipe/swipe-panel.tsx +55 -0
  415. package/src/components/swipe/swipe-start.tsx +15 -0
  416. package/src/components/swipe/swipe.context.tsx +135 -0
  417. package/src/components/swipe/swipe.tsx +393 -0
  418. package/src/components/swipe/swipe.types.ts +7 -0
  419. package/src/components/swipe/swipe.variants.test.ts +337 -0
  420. package/src/components/swipe/swipe.variants.ts +290 -0
  421. package/src/components/switch/switch.variants.ts +2 -2
  422. package/src/components/tabs/AGENTS.md +4 -3
  423. package/src/components/tabs/tabs.variants.test.ts +1 -1
  424. package/src/components/tabs/tabs.variants.ts +4 -6
  425. package/src/components/textarea/AGENTS.md +86 -0
  426. package/src/components/textarea/index.ts +16 -0
  427. package/src/components/textarea/textarea.tsx +157 -0
  428. package/src/components/textarea/textarea.variants.test.ts +219 -0
  429. package/src/components/textarea/textarea.variants.ts +171 -0
  430. package/src/components/toast/AGENTS.md +140 -0
  431. package/src/components/toast/index.ts +74 -0
  432. package/src/components/toast/toast-action.tsx +59 -0
  433. package/src/components/toast/toast-api.ts +32 -0
  434. package/src/components/toast/toast-close.tsx +44 -0
  435. package/src/components/toast/toast-content.tsx +11 -0
  436. package/src/components/toast/toast-description.tsx +11 -0
  437. package/src/components/toast/toast-indicator.tsx +34 -0
  438. package/src/components/toast/toast-item.tsx +308 -0
  439. package/src/components/toast/toast-title.tsx +13 -0
  440. package/src/components/toast/toast-viewport.tsx +135 -0
  441. package/src/components/toast/toast.context.tsx +69 -0
  442. package/src/components/toast/toast.store.test.ts +259 -0
  443. package/src/components/toast/toast.store.ts +338 -0
  444. package/src/components/toast/toast.timer.test.ts +64 -0
  445. package/src/components/toast/toast.timer.ts +51 -0
  446. package/src/components/toast/toast.tsx +132 -0
  447. package/src/components/toast/toast.variants.test.ts +393 -0
  448. package/src/components/toast/toast.variants.ts +304 -0
  449. package/src/components/toggle-button/AGENTS.md +106 -0
  450. package/src/components/toggle-button/index.ts +30 -0
  451. package/src/components/toggle-button/toggle-button-group.tsx +156 -0
  452. package/src/components/toggle-button/toggle-button-label.tsx +19 -0
  453. package/src/components/toggle-button/toggle-button.context.tsx +130 -0
  454. package/src/components/toggle-button/toggle-button.tsx +172 -0
  455. package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
  456. package/src/components/toggle-button/toggle-button.variants.ts +160 -0
  457. package/src/components/tooltip/AGENTS.md +133 -0
  458. package/src/components/tooltip/index.ts +28 -0
  459. package/src/components/tooltip/tooltip-arrow.tsx +40 -0
  460. package/src/components/tooltip/tooltip-content.tsx +179 -0
  461. package/src/components/tooltip/tooltip-description.tsx +19 -0
  462. package/src/components/tooltip/tooltip-text.tsx +21 -0
  463. package/src/components/tooltip/tooltip-title.tsx +20 -0
  464. package/src/components/tooltip/tooltip-trigger.tsx +103 -0
  465. package/src/components/tooltip/tooltip.context.tsx +68 -0
  466. package/src/components/tooltip/tooltip.tsx +213 -0
  467. package/src/components/tooltip/tooltip.variants.test.ts +231 -0
  468. package/src/components/tooltip/tooltip.variants.ts +177 -0
  469. package/src/hooks/use-calm-motion.tsx +31 -0
  470. package/src/lib/calm-motion.test.ts +17 -0
  471. package/src/lib/calm-motion.ts +23 -0
  472. package/src/lib/cn.test.ts +14 -0
  473. package/src/lib/cn.ts +2 -2
  474. package/src/no-gorhom.test.ts +54 -0
  475. package/src/styles/geometry.test.ts +1 -0
  476. package/src/styles/theme.css +4 -0
  477. package/src/styles/tokens.css +11 -0
  478. package/src/styles/tokens.test.ts +22 -0
  479. package/src/styles/tokens.ts +20 -0
  480. package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
  481. package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
@@ -0,0 +1,55 @@
1
+ import type { ReactElement } from "react";
2
+ import Animated, { useAnimatedStyle } from "react-native-reanimated";
3
+ import { SwipeTileProvider, useSwipePart } from "./swipe.context";
4
+ import { resolveOutermostIndex, type SwipeSide, type SwipeTileProps, swipeVariants } from "./swipe.variants";
5
+
6
+ type SwipePanelLayerProps = {
7
+ side: SwipeSide;
8
+ /** The `Swipe.Action` elements lifted out of the side's marker. */
9
+ tiles: ReactElement<SwipeTileProps>[];
10
+ /** Hidden from assistive technology while the row is shut. */
11
+ isHidden: boolean;
12
+ };
13
+
14
+ const START_EDGE = { start: 0 } as const;
15
+ const END_EDGE = { end: 0 } as const;
16
+
17
+ /**
18
+ * The layer behind one edge of the row. Internal: the root renders one per side
19
+ * that has tiles.
20
+ *
21
+ * **Exactly as wide as the gap the row has left**, animated off the same offset
22
+ * the row moves on — never wider, so it cannot paint over a row that has no
23
+ * background of its own, and never narrower, so there is no hole. Its own
24
+ * colour is the outermost tile's, so the overshoot that tile grows into reads
25
+ * as the same action.
26
+ *
27
+ * Pinned by logical `start` / `end` rather than `left` / `right`, so the layout
28
+ * mirrors under right to left with no sign of its own; only the row's
29
+ * translation has to flip.
30
+ */
31
+ export function SwipePanelLayer({ side, tiles, isHidden }: SwipePanelLayerProps): ReactElement {
32
+ const { offset } = useSwipePart("Swipe.Panel");
33
+ const outermost = tiles[resolveOutermostIndex(side, tiles.length)];
34
+ const slots = swipeVariants({ color: outermost?.props.color ?? "default", side });
35
+
36
+ const panelStyle = useAnimatedStyle(() => ({
37
+ width: Math.max(0, side === "start" ? offset.value : -offset.value),
38
+ }));
39
+
40
+ return (
41
+ <Animated.View
42
+ accessibilityElementsHidden={isHidden}
43
+ className={slots.panel()}
44
+ importantForAccessibility={isHidden ? "no-hide-descendants" : "auto"}
45
+ style={[side === "start" ? START_EDGE : END_EDGE, panelStyle]}
46
+ >
47
+ {tiles.map((tile, index) => (
48
+ <SwipeTileProvider key={tile.key ?? index} value={{ count: tiles.length, index, side }}>
49
+ {tile}
50
+ </SwipeTileProvider>
51
+ ))}
52
+ </Animated.View>
53
+ );
54
+ }
55
+ SwipePanelLayer.displayName = "DelacourUI.Swipe.Panel";
@@ -0,0 +1,15 @@
1
+ import type { ReactElement } from "react";
2
+ import type { SwipePanelProps } from "./swipe.types";
3
+
4
+ /**
5
+ * Marks the tiles behind the row's start edge — revealed by sliding the row
6
+ * toward `end`.
7
+ *
8
+ * A marker, never rendered: the root lifts its children out by element type and
9
+ * lays them out itself, because only the root knows how wide the gap is. On its
10
+ * own it renders nothing.
11
+ */
12
+ export function SwipeStart(_props: SwipePanelProps): ReactElement | null {
13
+ return null;
14
+ }
15
+ SwipeStart.displayName = "DelacourUI.Swipe.Start";
@@ -0,0 +1,135 @@
1
+ import { createContext, type ReactElement, type ReactNode, use } from "react";
2
+ import type { SharedValue } from "react-native-reanimated";
3
+ import type { SwipeOpenSide, SwipeSide } from "./swipe.variants";
4
+
5
+ export type SwipeContextValue = {
6
+ /** The side that is settled open, or null. Changes on a settle, never during a drag. */
7
+ openSide: SwipeOpenSide;
8
+ /** Slides the row aside to reveal `side`. A side with no panel stays shut. */
9
+ open: (side: SwipeSide) => void;
10
+ /** Slides the row back over its panels. */
11
+ close: () => void;
12
+ /** The row's logical offset — positive reveals `start`. Internal to the parts. */
13
+ offset: SharedValue<number>;
14
+ /** Whether the layout runs right to left, read once from `I18nManager`. */
15
+ isRTL: boolean;
16
+ /** Runs a tile's action, then closes the row unless it asks to stay open. */
17
+ runAction: (onPress: () => void, isKeptOpen: boolean) => void;
18
+ };
19
+
20
+ const SwipeContext = createContext<SwipeContextValue | null>(null);
21
+
22
+ /**
23
+ * Supplies a row's state to its tiles.
24
+ *
25
+ * In a leaf of its own, importing nothing but types, so a part can read it
26
+ * without importing `./swipe` and closing a cycle (package AGENTS.md rule 3).
27
+ */
28
+ export function SwipeProvider({ value, children }: { value: SwipeContextValue; children: ReactNode }): ReactElement {
29
+ return <SwipeContext value={value}>{children}</SwipeContext>;
30
+ }
31
+ SwipeProvider.displayName = "DelacourUI.Swipe.Provider";
32
+
33
+ /** The enclosing row, or null outside a `<Swipe>`. */
34
+ export function useSwipeContext(): SwipeContextValue | null {
35
+ return use(SwipeContext);
36
+ }
37
+
38
+ /**
39
+ * Reads the enclosing row: which side is open, and `open` / `close`.
40
+ *
41
+ * For a custom child of the row that has to close it — a button inside the row
42
+ * that acts and then tucks the actions away. Throws outside a `<Swipe>`.
43
+ */
44
+ export function useSwipe(): SwipeContextValue {
45
+ const context = useSwipeContext();
46
+ if (!context) throw new Error("useSwipe must be called inside a <Swipe>.");
47
+ return context;
48
+ }
49
+
50
+ /**
51
+ * The enclosing row, for a part that cannot work without it.
52
+ *
53
+ * Internal: deliberately not re-exported from `index.ts`.
54
+ */
55
+ export function useSwipePart(component: string): SwipeContextValue {
56
+ const context = useSwipeContext();
57
+ if (!context) throw new Error(`${component} must be rendered inside a <Swipe>.`);
58
+ return context;
59
+ }
60
+
61
+ export type SwipeTileContextValue = {
62
+ side: SwipeSide;
63
+ /** Source order within its panel. */
64
+ index: number;
65
+ /** How many tiles share the panel. */
66
+ count: number;
67
+ };
68
+
69
+ const SwipeTileContext = createContext<SwipeTileContextValue | null>(null);
70
+
71
+ /** Where a tile sits in its panel. The root wraps every tile it lifts out in one. */
72
+ export function SwipeTileProvider({
73
+ value,
74
+ children,
75
+ }: {
76
+ value: SwipeTileContextValue;
77
+ children: ReactNode;
78
+ }): ReactElement {
79
+ return <SwipeTileContext value={value}>{children}</SwipeTileContext>;
80
+ }
81
+ SwipeTileProvider.displayName = "DelacourUI.Swipe.TileProvider";
82
+
83
+ /**
84
+ * A tile's place in its panel. Internal.
85
+ *
86
+ * Throws when a `Swipe.Action` was rendered anywhere but inside `Swipe.Start`
87
+ * or `Swipe.End` — the root lifts tiles out of those two markers only.
88
+ */
89
+ export function useSwipeTilePart(component: string): SwipeTileContextValue {
90
+ const context = use(SwipeTileContext);
91
+ if (!context) throw new Error(`${component} must be written inside <Swipe.Start> or <Swipe.End>.`);
92
+ return context;
93
+ }
94
+
95
+ export type SwipeGroupContextValue = {
96
+ /** Adds a row's close to the group. Returns the matching unregister. */
97
+ register: (id: string, close: () => void) => () => void;
98
+ /** Tells the group `id` has opened, so an exclusive group closes the rest. */
99
+ notifyOpen: (id: string) => void;
100
+ /** Closes every row in the group. */
101
+ closeAll: () => void;
102
+ };
103
+
104
+ const SwipeGroupContext = createContext<SwipeGroupContextValue | null>(null);
105
+
106
+ /** Supplies a group's registry to the rows beneath it, however deeply they are nested. */
107
+ export function SwipeGroupProvider({
108
+ value,
109
+ children,
110
+ }: {
111
+ value: SwipeGroupContextValue;
112
+ children: ReactNode;
113
+ }): ReactElement {
114
+ return <SwipeGroupContext value={value}>{children}</SwipeGroupContext>;
115
+ }
116
+ SwipeGroupProvider.displayName = "DelacourUI.Swipe.Group.Provider";
117
+
118
+ /** The enclosing group, or null for a row standing on its own. */
119
+ export function useSwipeGroupContext(): SwipeGroupContextValue | null {
120
+ return use(SwipeGroupContext);
121
+ }
122
+
123
+ const NO_GROUP = { closeAll: () => {} } as const;
124
+
125
+ /**
126
+ * Closes every row in the enclosing `Swipe.Group` — before a list scrolls away,
127
+ * or when a screen loses focus.
128
+ *
129
+ * Outside a group it returns a no-op rather than throwing, so a screen can call
130
+ * it whether or not its rows are grouped.
131
+ */
132
+ export function useSwipeGroup(): { closeAll: () => void } {
133
+ const group = useSwipeGroupContext();
134
+ return group ? { closeAll: group.closeAll } : NO_GROUP;
135
+ }
@@ -0,0 +1,393 @@
1
+ import {
2
+ Children,
3
+ type ReactElement,
4
+ type ReactNode,
5
+ type Ref,
6
+ useCallback,
7
+ useEffect,
8
+ useId,
9
+ useImperativeHandle,
10
+ useMemo,
11
+ useRef,
12
+ useState,
13
+ } from "react";
14
+ import { type AccessibilityActionEvent, I18nManager, type LayoutChangeEvent, View, type ViewProps } from "react-native";
15
+ import { Gesture, GestureDetector } from "react-native-gesture-handler";
16
+ import Animated, {
17
+ useAnimatedStyle,
18
+ useReducedMotion,
19
+ useSharedValue,
20
+ withDelay,
21
+ withSpring,
22
+ withTiming,
23
+ } from "react-native-reanimated";
24
+ import { scheduleOnRN } from "react-native-worklets";
25
+ import { type HapticFeedback, playHaptic } from "../pressable/pressable";
26
+ import { type SwipeContextValue, SwipeProvider, useSwipeGroupContext } from "./swipe.context";
27
+ import {
28
+ partitionSwipeChildren,
29
+ resolveOutermostIndex,
30
+ SWIPE_FLY_OFF_DURATION,
31
+ SWIPE_FULL_FRACTION,
32
+ SWIPE_PROJECTION,
33
+ SWIPE_REDUCED_DURATION,
34
+ SWIPE_RUBBER_BAND,
35
+ SWIPE_SPRING,
36
+ SWIPE_TILE_WIDTH,
37
+ type SwipeOpenSide,
38
+ type SwipeSide,
39
+ swipeVariants,
40
+ } from "./swipe.variants";
41
+ import { SwipeAction } from "./swipe-action";
42
+ import { SwipeEnd } from "./swipe-end";
43
+ import { SwipeGroup } from "./swipe-group";
44
+ import { SwipePanelLayer } from "./swipe-panel";
45
+ import { SwipeStart } from "./swipe-start";
46
+
47
+ export type SwipeHandle = {
48
+ /** Slides the row aside to reveal `side`. A side with no tiles stays shut. */
49
+ open: (side: SwipeSide) => void;
50
+ close: () => void;
51
+ };
52
+
53
+ export type SwipeProps = Omit<ViewProps, "children"> & {
54
+ /** `Swipe.Start`, `Swipe.End` and the row — anything that is not a panel. Order does not matter. */
55
+ children: ReactNode;
56
+ className?: string;
57
+ /** The moving row. */
58
+ contentClassName?: string;
59
+ /** A drag far past the panel fires the outermost action. Default `true`. */
60
+ isFullSwipe?: boolean;
61
+ /** Turns the gesture off. Tiles already open stay tappable. */
62
+ isDisabled?: boolean;
63
+ /** Ticks once as a drag crosses the full-swipe point. Off by default. */
64
+ haptic?: false | HapticFeedback;
65
+ /** The side that settled open, or `null` once the row is shut. */
66
+ onOpenChange?: (side: SwipeOpenSide) => void;
67
+ ref?: Ref<SwipeHandle>;
68
+ };
69
+
70
+ /**
71
+ * Read once: a direction change needs an app reload in React Native, so there
72
+ * is no live value to follow.
73
+ */
74
+ const IS_RTL = I18nManager.isRTL;
75
+
76
+ function SwipeRoot({
77
+ children,
78
+ className,
79
+ contentClassName,
80
+ isFullSwipe = true,
81
+ isDisabled = false,
82
+ haptic = false,
83
+ onOpenChange,
84
+ onLayout,
85
+ accessibilityLabel,
86
+ accessibilityHint,
87
+ ref,
88
+ ...props
89
+ }: SwipeProps): ReactElement {
90
+ const { start, end, row } = useMemo(() => partitionSwipeChildren(Children.toArray(children)), [children]);
91
+ const startWidth = start.length * SWIPE_TILE_WIDTH;
92
+ const endWidth = end.length * SWIPE_TILE_WIDTH;
93
+ const isReducedMotion = useReducedMotion();
94
+
95
+ // Logical points: positive reveals `start`. The row's translation flips it
96
+ // under right to left; nothing else has to.
97
+ const offset = useSharedValue(0);
98
+ const grabbed = useSharedValue(0);
99
+ const rowWidth = useSharedValue(0);
100
+ const rowOpacity = useSharedValue(1);
101
+ const isActive = useSharedValue(false);
102
+ const isPastFull = useSharedValue(false);
103
+
104
+ const [openSide, setOpenSide] = useState<SwipeOpenSide>(null);
105
+ const openSideRef = useRef<SwipeOpenSide>(null);
106
+ const group = useSwipeGroupContext();
107
+ const id = useId();
108
+
109
+ const onOpenChangeRef = useRef(onOpenChange);
110
+ onOpenChangeRef.current = onOpenChange;
111
+
112
+ const settle = useCallback(
113
+ (side: SwipeOpenSide) => {
114
+ if (openSideRef.current === side) return;
115
+ openSideRef.current = side;
116
+ setOpenSide(side);
117
+ onOpenChangeRef.current?.(side);
118
+ if (side !== null) group?.notifyOpen(id);
119
+ },
120
+ [group, id]
121
+ );
122
+
123
+ const animateTo = useCallback(
124
+ (target: number) => {
125
+ offset.value = isReducedMotion
126
+ ? withTiming(target, { duration: SWIPE_REDUCED_DURATION })
127
+ : withSpring(target, SWIPE_SPRING);
128
+ },
129
+ [isReducedMotion, offset]
130
+ );
131
+
132
+ const close = useCallback(() => {
133
+ animateTo(0);
134
+ settle(null);
135
+ }, [animateTo, settle]);
136
+
137
+ const open = useCallback(
138
+ (side: SwipeSide) => {
139
+ const width = side === "start" ? startWidth : endWidth;
140
+ if (width <= 0) return;
141
+ animateTo(side === "start" ? width : -width);
142
+ settle(side);
143
+ },
144
+ [animateTo, endWidth, settle, startWidth]
145
+ );
146
+
147
+ useImperativeHandle(ref, () => ({ close, open }), [close, open]);
148
+
149
+ // A group closes the others through this, so a row already shut is left
150
+ // alone — a drag under way on it is not the group's to cancel.
151
+ const closeFromGroup = useCallback(() => {
152
+ if (openSideRef.current !== null) close();
153
+ }, [close]);
154
+
155
+ useEffect(() => group?.register(id, closeFromGroup), [closeFromGroup, group, id]);
156
+
157
+ const runAction = useCallback(
158
+ (onPress: () => void, isKeptOpen: boolean) => {
159
+ onPress();
160
+ if (!isKeptOpen) close();
161
+ },
162
+ [close]
163
+ );
164
+
165
+ const tilesRef = useRef({ end, start });
166
+ tilesRef.current = { end, start };
167
+
168
+ // Runs once the fly-off has carried the row away. The way back is delayed a
169
+ // beat so an action that removes the row unmounts it before it slides in.
170
+ const completeFullSwipe = useCallback(
171
+ (side: SwipeSide) => {
172
+ const tiles = tilesRef.current[side];
173
+ const outermost = tiles[resolveOutermostIndex(side, tiles.length)];
174
+ outermost?.props.onPress?.();
175
+
176
+ openSideRef.current = null;
177
+ setOpenSide(null);
178
+ onOpenChangeRef.current?.(null);
179
+
180
+ if (isReducedMotion) {
181
+ offset.value = 0;
182
+ rowOpacity.value = withDelay(SWIPE_REDUCED_DURATION, withTiming(1, { duration: SWIPE_REDUCED_DURATION }));
183
+ } else {
184
+ offset.value = withDelay(SWIPE_FLY_OFF_DURATION, withSpring(0, SWIPE_SPRING));
185
+ }
186
+ },
187
+ [isReducedMotion, offset, rowOpacity]
188
+ );
189
+
190
+ const pan = useMemo(
191
+ () =>
192
+ Gesture.Pan()
193
+ .enabled(!isDisabled)
194
+ .activeOffsetX([-10, 10])
195
+ .failOffsetY([-8, 8])
196
+ .onStart(() => {
197
+ "worklet";
198
+ isActive.value = true;
199
+ grabbed.value = offset.value;
200
+ isPastFull.value = false;
201
+ })
202
+ .onUpdate((event) => {
203
+ "worklet";
204
+ const raw = grabbed.value + (IS_RTL ? -event.translationX : event.translationX);
205
+ const isUnbacked = (raw > 0 && startWidth <= 0) || (raw < 0 && endWidth <= 0);
206
+ const next = isUnbacked ? raw * SWIPE_RUBBER_BAND : raw;
207
+ offset.value = next;
208
+
209
+ if (!isFullSwipe || haptic === false || rowWidth.value <= 0) return;
210
+ const width = next > 0 ? startWidth : endWidth;
211
+ const isPast = width > 0 && Math.abs(next) > width + SWIPE_FULL_FRACTION * rowWidth.value;
212
+ if (isPast === isPastFull.value) return;
213
+ isPastFull.value = isPast;
214
+ if (isPast) playHaptic(haptic);
215
+ })
216
+ .onFinalize((event) => {
217
+ "worklet";
218
+ if (!isActive.value) return;
219
+ isActive.value = false;
220
+
221
+ // `resolveSwipeRelease`, restated: a worklet body stays self-contained.
222
+ const current = offset.value;
223
+ const velocity = IS_RTL ? -event.velocityX : event.velocityX;
224
+ const projected = current + velocity * SWIPE_PROJECTION;
225
+ const isCrossing = current !== 0 && Math.sign(projected) !== Math.sign(current);
226
+ const side: SwipeSide = projected > 0 ? "start" : "end";
227
+ const width = side === "start" ? startWidth : endWidth;
228
+ const fullAt = width + SWIPE_FULL_FRACTION * rowWidth.value;
229
+ const isClosing = projected === 0 || isCrossing || width <= 0;
230
+ const isFull =
231
+ !isClosing &&
232
+ isFullSwipe &&
233
+ rowWidth.value > 0 &&
234
+ Math.abs(current) > fullAt &&
235
+ Math.abs(projected) > fullAt;
236
+ const isOpening = !isClosing && !isFull && Math.abs(projected) > width / 2;
237
+ const direction = side === "start" ? 1 : -1;
238
+
239
+ if (isFull) {
240
+ if (isReducedMotion) {
241
+ rowOpacity.value = withTiming(0, { duration: SWIPE_REDUCED_DURATION }, (finished) => {
242
+ if (finished) scheduleOnRN(completeFullSwipe, side);
243
+ });
244
+ } else {
245
+ offset.value = withTiming(
246
+ direction * rowWidth.value,
247
+ { duration: SWIPE_FLY_OFF_DURATION },
248
+ (finished) => {
249
+ if (finished) scheduleOnRN(completeFullSwipe, side);
250
+ }
251
+ );
252
+ }
253
+ return;
254
+ }
255
+
256
+ const target = isOpening ? direction * width : 0;
257
+ offset.value = isReducedMotion
258
+ ? withTiming(target, { duration: SWIPE_REDUCED_DURATION })
259
+ : withSpring(target, { ...SWIPE_SPRING, velocity });
260
+ scheduleOnRN(settle, isOpening ? side : null);
261
+ }),
262
+ [
263
+ completeFullSwipe,
264
+ endWidth,
265
+ grabbed,
266
+ haptic,
267
+ isActive,
268
+ isDisabled,
269
+ isFullSwipe,
270
+ isPastFull,
271
+ isReducedMotion,
272
+ offset,
273
+ rowOpacity,
274
+ rowWidth,
275
+ settle,
276
+ startWidth,
277
+ ]
278
+ );
279
+
280
+ const shieldTap = useMemo(
281
+ () =>
282
+ Gesture.Tap().onEnd(() => {
283
+ "worklet";
284
+ scheduleOnRN(close);
285
+ }),
286
+ [close]
287
+ );
288
+
289
+ const rowStyle = useAnimatedStyle(() => ({
290
+ opacity: rowOpacity.value,
291
+ transform: [{ translateX: IS_RTL ? -offset.value : offset.value }],
292
+ }));
293
+
294
+ const handleLayout = useCallback(
295
+ (event: LayoutChangeEvent) => {
296
+ rowWidth.value = event.nativeEvent.layout.width;
297
+ onLayout?.(event);
298
+ },
299
+ [onLayout, rowWidth]
300
+ );
301
+
302
+ const actions = useMemo(() => [...start, ...end].map((tile) => tile.props), [end, start]);
303
+ const accessibilityActions = useMemo(
304
+ () =>
305
+ actions.flatMap((action) => (action.label === undefined ? [] : [{ label: action.label, name: action.label }])),
306
+ [actions]
307
+ );
308
+ const handleAccessibilityAction = useCallback(
309
+ (event: AccessibilityActionEvent) => {
310
+ const action = actions.find((entry) => entry.label === event.nativeEvent.actionName);
311
+ action?.onPress?.();
312
+ },
313
+ [actions]
314
+ );
315
+
316
+ const context = useMemo<SwipeContextValue>(
317
+ () => ({ close, isRTL: IS_RTL, offset, open, openSide, runAction }),
318
+ [close, offset, open, openSide, runAction]
319
+ );
320
+
321
+ const slots = swipeVariants();
322
+ const isShut = openSide === null;
323
+
324
+ return (
325
+ <SwipeProvider value={context}>
326
+ <View className={slots.root({ className })} onLayout={handleLayout} {...props}>
327
+ {start.length > 0 ? <SwipePanelLayer isHidden={isShut} side="start" tiles={start} /> : null}
328
+ {end.length > 0 ? <SwipePanelLayer isHidden={isShut} side="end" tiles={end} /> : null}
329
+ <GestureDetector gesture={pan}>
330
+ <Animated.View
331
+ accessibilityActions={accessibilityActions}
332
+ accessibilityHint={accessibilityHint}
333
+ accessibilityLabel={accessibilityLabel}
334
+ accessible
335
+ className={slots.row({ className: contentClassName })}
336
+ onAccessibilityAction={handleAccessibilityAction}
337
+ style={rowStyle}
338
+ >
339
+ {row}
340
+ {isShut ? null : (
341
+ <GestureDetector gesture={shieldTap}>
342
+ <View accessible={false} className="absolute inset-0" />
343
+ </GestureDetector>
344
+ )}
345
+ </Animated.View>
346
+ </GestureDetector>
347
+ </View>
348
+ </SwipeProvider>
349
+ );
350
+ }
351
+
352
+ /**
353
+ * A row that slides aside to reveal actions behind it — delete, archive,
354
+ * snooze, mark done.
355
+ *
356
+ * The tiles sit **behind** the row rather than beside it, so nothing reflows
357
+ * when one opens, and only the row moves, on the UI thread. A drag that is
358
+ * clearly sideways claims the touch and one that commits vertically gives it
359
+ * up, so a list of rows scrolls normally. A release opens a side or closes the
360
+ * row by where it would come to rest; a drag far past the panel fires the
361
+ * outermost action (`isFullSwipe`, on by default).
362
+ *
363
+ * `Swipe.Start` and `Swipe.End` are markers: their `Swipe.Action`s are lifted
364
+ * out and laid out by the root. Everything else is the row. Every action is
365
+ * also published as an accessibility action on the row, since a swipe is
366
+ * invisible to a screen reader.
367
+ *
368
+ * Wrap rows in `Swipe.Group` to keep one open at a time; `useSwipeGroup()`
369
+ * closes them all. A ref exposes `open(side)` and `close()`.
370
+ *
371
+ * @example
372
+ * <Swipe haptic="selection">
373
+ * <Swipe.Start>
374
+ * <Swipe.Action color="success" icon={IconCheckmark2} label="Done" onPress={complete} />
375
+ * </Swipe.Start>
376
+ * <Swipe.End>
377
+ * <Swipe.Action color="warning" icon={IconBell} label="Snooze" onPress={snooze} />
378
+ * <Swipe.Action color="destructive" icon={IconTrashCan} label="Delete" onPress={remove} />
379
+ * </Swipe.End>
380
+ * <Item>…</Item>
381
+ * </Swipe>
382
+ */
383
+ export const Swipe = Object.assign(SwipeRoot, {
384
+ /** Tiles behind the start edge. A marker: its children are lifted out. */
385
+ Start: SwipeStart,
386
+ /** Tiles behind the end edge — the edge text runs toward. A marker. */
387
+ End: SwipeEnd,
388
+ /** One tile: a glyph above a caption, on its colour. */
389
+ Action: SwipeAction,
390
+ /** Keeps one row open at a time, however deeply its rows are nested. */
391
+ Group: SwipeGroup,
392
+ displayName: "DelacourUI.Swipe",
393
+ });
@@ -0,0 +1,7 @@
1
+ import type { ReactNode } from "react";
2
+
3
+ /** `Swipe.Start` and `Swipe.End` — markers whose children are the panel's tiles. */
4
+ export type SwipePanelProps = {
5
+ /** One or more `Swipe.Action`s, in visual order. */
6
+ children: ReactNode;
7
+ };