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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (482) hide show
  1. package/README.md +11 -7
  2. package/package.json +49 -5
  3. package/src/components/accordion/AGENTS.md +21 -9
  4. package/src/components/accordion/accordion-content.tsx +7 -4
  5. package/src/components/accordion/accordion-item.tsx +15 -13
  6. package/src/components/accordion/accordion.context.tsx +11 -5
  7. package/src/components/accordion/accordion.variants.test.ts +64 -3
  8. package/src/components/accordion/accordion.variants.ts +33 -9
  9. package/src/components/alert/AGENTS.md +102 -0
  10. package/src/components/alert/alert-action.tsx +17 -0
  11. package/src/components/alert/alert-close-button.tsx +50 -0
  12. package/src/components/alert/alert-content.tsx +15 -0
  13. package/src/components/alert/alert-description.tsx +16 -0
  14. package/src/components/alert/alert-glyphs.ts +25 -0
  15. package/src/components/alert/alert-indicator.tsx +40 -0
  16. package/src/components/alert/alert-title.tsx +18 -0
  17. package/src/components/alert/alert.context.tsx +69 -0
  18. package/src/components/alert/alert.tsx +160 -0
  19. package/src/components/alert/alert.types.ts +10 -0
  20. package/src/components/alert/alert.variants.test.ts +218 -0
  21. package/src/components/alert/alert.variants.ts +180 -0
  22. package/src/components/alert/index.ts +23 -0
  23. package/src/components/avatar/AGENTS.md +106 -0
  24. package/src/components/avatar/avatar-badge.tsx +51 -0
  25. package/src/components/avatar/avatar-group.tsx +139 -0
  26. package/src/components/avatar/avatar.context.tsx +93 -0
  27. package/src/components/avatar/avatar.tsx +201 -0
  28. package/src/components/avatar/avatar.utils.test.ts +174 -0
  29. package/src/components/avatar/avatar.utils.ts +141 -0
  30. package/src/components/avatar/avatar.variants.test.ts +251 -0
  31. package/src/components/avatar/avatar.variants.ts +270 -0
  32. package/src/components/avatar/index.ts +39 -0
  33. package/src/components/badge/AGENTS.md +5 -0
  34. package/src/components/badge/badge.tsx +6 -1
  35. package/src/components/bottom-sheet/AGENTS.md +186 -221
  36. package/src/components/bottom-sheet/bottom-sheet-background.tsx +31 -22
  37. package/src/components/bottom-sheet/bottom-sheet-close.tsx +23 -30
  38. package/src/components/bottom-sheet/bottom-sheet-container.tsx +54 -207
  39. package/src/components/bottom-sheet/bottom-sheet-content.tsx +25 -48
  40. package/src/components/bottom-sheet/bottom-sheet-description.tsx +8 -2
  41. package/src/components/bottom-sheet/bottom-sheet-flat-list.tsx +65 -0
  42. package/src/components/bottom-sheet/bottom-sheet-footer.tsx +53 -120
  43. package/src/components/bottom-sheet/bottom-sheet-handle.tsx +31 -26
  44. package/src/components/bottom-sheet/bottom-sheet-legend-list.tsx +69 -0
  45. package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +25 -147
  46. package/src/components/bottom-sheet/bottom-sheet-provider.tsx +42 -0
  47. package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +38 -52
  48. package/src/components/bottom-sheet/bottom-sheet-section-list.tsx +58 -0
  49. package/src/components/bottom-sheet/bottom-sheet-steps.tsx +88 -0
  50. package/src/components/bottom-sheet/bottom-sheet-text-input.tsx +44 -0
  51. package/src/components/bottom-sheet/bottom-sheet-title.tsx +12 -8
  52. package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +19 -37
  53. package/src/components/bottom-sheet/bottom-sheet.tsx +113 -60
  54. package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +47 -70
  55. package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -105
  56. package/src/components/bottom-sheet/index.ts +39 -17
  57. package/src/components/bottom-sheet/use-bottom-sheet-input.ts +16 -115
  58. package/src/components/button/AGENTS.md +45 -11
  59. package/src/components/button/button-group.tsx +44 -8
  60. package/src/components/button/button.context.tsx +10 -0
  61. package/src/components/button/button.tsx +2 -1
  62. package/src/components/button/button.variants.test.ts +154 -0
  63. package/src/components/button/button.variants.ts +71 -6
  64. package/src/components/button/index.ts +2 -0
  65. package/src/components/calendar/AGENTS.md +142 -0
  66. package/src/components/calendar/calendar-caption.tsx +116 -0
  67. package/src/components/calendar/calendar-day.tsx +103 -0
  68. package/src/components/calendar/calendar-grid.tsx +183 -0
  69. package/src/components/calendar/calendar-header.tsx +34 -0
  70. package/src/components/calendar/calendar-nav.tsx +51 -0
  71. package/src/components/calendar/calendar-picker.tsx +128 -0
  72. package/src/components/calendar/calendar-weekdays.tsx +48 -0
  73. package/src/components/calendar/calendar.context.tsx +108 -0
  74. package/src/components/calendar/calendar.date.test.ts +456 -0
  75. package/src/components/calendar/calendar.date.ts +584 -0
  76. package/src/components/calendar/calendar.tsx +389 -0
  77. package/src/components/calendar/calendar.types.ts +87 -0
  78. package/src/components/calendar/calendar.variants.test.ts +363 -0
  79. package/src/components/calendar/calendar.variants.ts +426 -0
  80. package/src/components/calendar/index.ts +96 -0
  81. package/src/components/card/AGENTS.md +88 -0
  82. package/src/components/card/card-action.tsx +16 -0
  83. package/src/components/card/card-content.tsx +12 -0
  84. package/src/components/card/card-description.tsx +12 -0
  85. package/src/components/card/card-footer.tsx +30 -0
  86. package/src/components/card/card-header.tsx +45 -0
  87. package/src/components/card/card-title.tsx +25 -0
  88. package/src/components/card/card.context.tsx +64 -0
  89. package/src/components/card/card.tsx +94 -0
  90. package/src/components/card/card.types.ts +13 -0
  91. package/src/components/card/card.variants.test.ts +240 -0
  92. package/src/components/card/card.variants.ts +177 -0
  93. package/src/components/card/index.ts +15 -0
  94. package/src/components/chart/AGENTS.md +9 -1
  95. package/src/components/chart/chart.tsx +8 -1
  96. package/src/components/chip/AGENTS.md +113 -0
  97. package/src/components/chip/chip-close-button.tsx +48 -0
  98. package/src/components/chip/chip-end-content.tsx +15 -0
  99. package/src/components/chip/chip-label.tsx +21 -0
  100. package/src/components/chip/chip-start-content.tsx +16 -0
  101. package/src/components/chip/chip.context.tsx +66 -0
  102. package/src/components/chip/chip.tsx +268 -0
  103. package/src/components/chip/chip.types.ts +10 -0
  104. package/src/components/chip/chip.variants.test.ts +335 -0
  105. package/src/components/chip/chip.variants.ts +321 -0
  106. package/src/components/chip/index.ts +24 -0
  107. package/src/components/collapsible/AGENTS.md +97 -0
  108. package/src/components/collapsible/collapsible-content.tsx +97 -0
  109. package/src/components/collapsible/collapsible-description.tsx +17 -0
  110. package/src/components/collapsible/collapsible-indicator.tsx +92 -0
  111. package/src/components/collapsible/collapsible-title.tsx +17 -0
  112. package/src/components/collapsible/collapsible-trigger.tsx +133 -0
  113. package/src/components/collapsible/collapsible.context.tsx +91 -0
  114. package/src/components/collapsible/collapsible.tsx +167 -0
  115. package/src/components/collapsible/collapsible.types.ts +9 -0
  116. package/src/components/collapsible/collapsible.variants.test.ts +338 -0
  117. package/src/components/collapsible/collapsible.variants.ts +228 -0
  118. package/src/components/collapsible/index.ts +32 -0
  119. package/src/components/context-menu/AGENTS.md +103 -0
  120. package/src/components/context-menu/context-menu-content.tsx +64 -0
  121. package/src/components/context-menu/context-menu-item.tsx +22 -0
  122. package/src/components/context-menu/context-menu-preview.tsx +60 -0
  123. package/src/components/context-menu/context-menu-trigger.tsx +160 -0
  124. package/src/components/context-menu/context-menu.context.tsx +91 -0
  125. package/src/components/context-menu/context-menu.tsx +90 -0
  126. package/src/components/context-menu/context-menu.variants.test.ts +266 -0
  127. package/src/components/context-menu/context-menu.variants.ts +147 -0
  128. package/src/components/context-menu/index.ts +18 -0
  129. package/src/components/dialog/AGENTS.md +97 -0
  130. package/src/components/dialog/dialog-body.tsx +21 -0
  131. package/src/components/dialog/dialog-close.tsx +82 -0
  132. package/src/components/dialog/dialog-content.tsx +153 -0
  133. package/src/components/dialog/dialog-description.tsx +23 -0
  134. package/src/components/dialog/dialog-footer.tsx +32 -0
  135. package/src/components/dialog/dialog-header.tsx +20 -0
  136. package/src/components/dialog/dialog-title.tsx +42 -0
  137. package/src/components/dialog/dialog-trigger.tsx +54 -0
  138. package/src/components/dialog/dialog.context.tsx +90 -0
  139. package/src/components/dialog/dialog.tsx +119 -0
  140. package/src/components/dialog/dialog.variants.test.ts +224 -0
  141. package/src/components/dialog/dialog.variants.ts +173 -0
  142. package/src/components/dialog/index.ts +30 -0
  143. package/src/components/drawer/AGENTS.md +123 -0
  144. package/src/components/drawer/drawer-body.tsx +46 -0
  145. package/src/components/drawer/drawer-close.tsx +76 -0
  146. package/src/components/drawer/drawer-content.tsx +186 -0
  147. package/src/components/drawer/drawer-description.tsx +23 -0
  148. package/src/components/drawer/drawer-footer.tsx +21 -0
  149. package/src/components/drawer/drawer-header.tsx +42 -0
  150. package/src/components/drawer/drawer-title.tsx +35 -0
  151. package/src/components/drawer/drawer-trigger.tsx +54 -0
  152. package/src/components/drawer/drawer.context.tsx +114 -0
  153. package/src/components/drawer/drawer.tsx +119 -0
  154. package/src/components/drawer/drawer.variants.test.ts +392 -0
  155. package/src/components/drawer/drawer.variants.ts +244 -0
  156. package/src/components/drawer/index.ts +41 -0
  157. package/src/components/drawer/use-drawer-pan.ts +108 -0
  158. package/src/components/empty-state/AGENTS.md +90 -0
  159. package/src/components/empty-state/empty-state-content.tsx +17 -0
  160. package/src/components/empty-state/empty-state-description.tsx +12 -0
  161. package/src/components/empty-state/empty-state-header.tsx +17 -0
  162. package/src/components/empty-state/empty-state-media.tsx +56 -0
  163. package/src/components/empty-state/empty-state-title.tsx +18 -0
  164. package/src/components/empty-state/empty-state.context.tsx +65 -0
  165. package/src/components/empty-state/empty-state.tsx +84 -0
  166. package/src/components/empty-state/empty-state.types.ts +14 -0
  167. package/src/components/empty-state/empty-state.variants.test.ts +245 -0
  168. package/src/components/empty-state/empty-state.variants.ts +132 -0
  169. package/src/components/empty-state/index.ts +20 -0
  170. package/src/components/fab/AGENTS.md +119 -0
  171. package/src/components/fab/fab-action.tsx +118 -0
  172. package/src/components/fab/fab-group.tsx +214 -0
  173. package/src/components/fab/fab-label.tsx +18 -0
  174. package/src/components/fab/fab.context.tsx +125 -0
  175. package/src/components/fab/fab.tsx +156 -0
  176. package/src/components/fab/fab.types.ts +17 -0
  177. package/src/components/fab/fab.variants.test.ts +274 -0
  178. package/src/components/fab/fab.variants.ts +234 -0
  179. package/src/components/fab/index.ts +37 -0
  180. package/src/components/feedback/AGENTS.md +95 -0
  181. package/src/components/feedback/feedback-action.tsx +16 -0
  182. package/src/components/feedback/feedback-cancel.tsx +34 -0
  183. package/src/components/feedback/feedback-close.tsx +21 -0
  184. package/src/components/feedback/feedback-content.tsx +24 -0
  185. package/src/components/feedback/feedback-field.tsx +70 -0
  186. package/src/components/feedback/feedback-footer.tsx +21 -0
  187. package/src/components/feedback/feedback-panel.tsx +54 -0
  188. package/src/components/feedback/feedback-submit.tsx +74 -0
  189. package/src/components/feedback/feedback-title.tsx +34 -0
  190. package/src/components/feedback/feedback-trigger.tsx +20 -0
  191. package/src/components/feedback/feedback.context.tsx +73 -0
  192. package/src/components/feedback/feedback.tsx +135 -0
  193. package/src/components/feedback/feedback.variants.test.ts +185 -0
  194. package/src/components/feedback/feedback.variants.ts +128 -0
  195. package/src/components/feedback/index.ts +24 -0
  196. package/src/components/field/AGENTS.md +19 -0
  197. package/src/components/field/field-label.tsx +19 -5
  198. package/src/components/field/field.context.tsx +17 -0
  199. package/src/components/field/field.tsx +15 -2
  200. package/src/components/field/field.variants.test.ts +24 -0
  201. package/src/components/field/field.variants.ts +20 -0
  202. package/src/components/field/index.ts +1 -0
  203. package/src/components/item/AGENTS.md +92 -0
  204. package/src/components/item/index.ts +25 -0
  205. package/src/components/item/item-actions.tsx +28 -0
  206. package/src/components/item/item-content.tsx +15 -0
  207. package/src/components/item/item-description.tsx +12 -0
  208. package/src/components/item/item-footer.tsx +13 -0
  209. package/src/components/item/item-group.tsx +32 -0
  210. package/src/components/item/item-header.tsx +13 -0
  211. package/src/components/item/item-media.tsx +37 -0
  212. package/src/components/item/item-separator.tsx +13 -0
  213. package/src/components/item/item-title.tsx +17 -0
  214. package/src/components/item/item.context.tsx +66 -0
  215. package/src/components/item/item.tsx +218 -0
  216. package/src/components/item/item.types.ts +12 -0
  217. package/src/components/item/item.variants.test.ts +263 -0
  218. package/src/components/item/item.variants.ts +200 -0
  219. package/src/components/kpi/AGENTS.md +125 -0
  220. package/src/components/kpi/index.ts +45 -0
  221. package/src/components/kpi/kpi-action.tsx +12 -0
  222. package/src/components/kpi/kpi-content.tsx +35 -0
  223. package/src/components/kpi/kpi-footer.tsx +52 -0
  224. package/src/components/kpi/kpi-group.tsx +70 -0
  225. package/src/components/kpi/kpi-header.tsx +25 -0
  226. package/src/components/kpi/kpi-icon.tsx +28 -0
  227. package/src/components/kpi/kpi-sparkline.tsx +145 -0
  228. package/src/components/kpi/kpi-stat.tsx +20 -0
  229. package/src/components/kpi/kpi-title.tsx +19 -0
  230. package/src/components/kpi/kpi-trend.tsx +105 -0
  231. package/src/components/kpi/kpi-value.tsx +32 -0
  232. package/src/components/kpi/kpi.context.tsx +111 -0
  233. package/src/components/kpi/kpi.tsx +154 -0
  234. package/src/components/kpi/kpi.types.ts +9 -0
  235. package/src/components/kpi/kpi.variants.test.ts +428 -0
  236. package/src/components/kpi/kpi.variants.ts +347 -0
  237. package/src/components/label/AGENTS.md +89 -0
  238. package/src/components/label/index.ts +10 -0
  239. package/src/components/label/label.tsx +97 -0
  240. package/src/components/label/label.variants.test.ts +176 -0
  241. package/src/components/label/label.variants.ts +123 -0
  242. package/src/components/menu/AGENTS.md +176 -0
  243. package/src/components/menu/index.ts +44 -0
  244. package/src/components/menu/menu-background.tsx +17 -0
  245. package/src/components/menu/menu-checkbox-item.tsx +57 -0
  246. package/src/components/menu/menu-content.tsx +303 -0
  247. package/src/components/menu/menu-item.tsx +162 -0
  248. package/src/components/menu/menu-label.tsx +14 -0
  249. package/src/components/menu/menu-radio-group.tsx +50 -0
  250. package/src/components/menu/menu-radio-item.tsx +57 -0
  251. package/src/components/menu/menu-separator.tsx +11 -0
  252. package/src/components/menu/menu-sub-content.tsx +72 -0
  253. package/src/components/menu/menu-sub-trigger.tsx +50 -0
  254. package/src/components/menu/menu-sub.tsx +75 -0
  255. package/src/components/menu/menu-trigger.tsx +57 -0
  256. package/src/components/menu/menu.context.tsx +214 -0
  257. package/src/components/menu/menu.tsx +88 -0
  258. package/src/components/menu/menu.types.ts +33 -0
  259. package/src/components/menu/menu.variants.test.ts +400 -0
  260. package/src/components/menu/menu.variants.ts +353 -0
  261. package/src/components/meter/AGENTS.md +135 -0
  262. package/src/components/meter/index.ts +43 -0
  263. package/src/components/meter/meter-fill.tsx +19 -0
  264. package/src/components/meter/meter-header.tsx +17 -0
  265. package/src/components/meter/meter-label.tsx +18 -0
  266. package/src/components/meter/meter-output.tsx +42 -0
  267. package/src/components/meter/meter-segments.tsx +83 -0
  268. package/src/components/meter/meter-track.tsx +16 -0
  269. package/src/components/meter/meter.context.tsx +62 -0
  270. package/src/components/meter/meter.tsx +286 -0
  271. package/src/components/meter/meter.types.ts +30 -0
  272. package/src/components/meter/meter.variants.test.ts +430 -0
  273. package/src/components/meter/meter.variants.ts +347 -0
  274. package/src/components/overlay/AGENTS.md +89 -0
  275. package/src/components/overlay/index.ts +27 -0
  276. package/src/components/overlay/overlay-portal.tsx +84 -0
  277. package/src/components/overlay/overlay-presence.test.ts +46 -0
  278. package/src/components/overlay/overlay-presence.ts +49 -0
  279. package/src/components/overlay/overlay-registry.test.ts +96 -0
  280. package/src/components/overlay/overlay-registry.ts +99 -0
  281. package/src/components/overlay/overlay-scrim.tsx +49 -0
  282. package/src/components/overlay/overlay.context.tsx +48 -0
  283. package/src/components/overlay/overlay.tsx +118 -0
  284. package/src/components/overlay/overlay.variants.test.ts +37 -0
  285. package/src/components/overlay/overlay.variants.ts +33 -0
  286. package/src/components/overlay/use-overlay-back-handler.ts +34 -0
  287. package/src/components/overlay/use-overlay-presence.ts +87 -0
  288. package/src/components/popover/AGENTS.md +138 -0
  289. package/src/components/popover/index.ts +48 -0
  290. package/src/components/popover/popover-anchor.tsx +49 -0
  291. package/src/components/popover/popover-arrow.tsx +89 -0
  292. package/src/components/popover/popover-close.tsx +73 -0
  293. package/src/components/popover/popover-content.tsx +235 -0
  294. package/src/components/popover/popover-description.tsx +18 -0
  295. package/src/components/popover/popover-title.tsx +33 -0
  296. package/src/components/popover/popover-trigger.tsx +56 -0
  297. package/src/components/popover/popover.context.tsx +71 -0
  298. package/src/components/popover/popover.position.test.ts +354 -0
  299. package/src/components/popover/popover.position.ts +286 -0
  300. package/src/components/popover/popover.tsx +123 -0
  301. package/src/components/popover/popover.variants.test.ts +135 -0
  302. package/src/components/popover/popover.variants.ts +85 -0
  303. package/src/components/popover/use-anchor-measure.ts +76 -0
  304. package/src/components/popover/use-anchored-content.ts +219 -0
  305. package/src/components/pressable/AGENTS.md +11 -1
  306. package/src/components/pressable/pressable.tsx +7 -2
  307. package/src/components/pressable/pressable.variants.test.ts +29 -0
  308. package/src/components/pressable/pressable.variants.ts +17 -0
  309. package/src/components/progress/AGENTS.md +120 -0
  310. package/src/components/progress/index.ts +40 -0
  311. package/src/components/progress/progress-fill.tsx +152 -0
  312. package/src/components/progress/progress-header.tsx +19 -0
  313. package/src/components/progress/progress-label.tsx +27 -0
  314. package/src/components/progress/progress-output.tsx +47 -0
  315. package/src/components/progress/progress-track.tsx +38 -0
  316. package/src/components/progress/progress.context.tsx +79 -0
  317. package/src/components/progress/progress.tsx +171 -0
  318. package/src/components/progress/progress.types.ts +23 -0
  319. package/src/components/progress/progress.variants.test.ts +335 -0
  320. package/src/components/progress/progress.variants.ts +284 -0
  321. package/src/components/progress-button/AGENTS.md +116 -0
  322. package/src/components/progress-button/index.ts +36 -0
  323. package/src/components/progress-button/progress-button-done.tsx +33 -0
  324. package/src/components/progress-button/progress-button-label.tsx +27 -0
  325. package/src/components/progress-button/progress-button.context.tsx +93 -0
  326. package/src/components/progress-button/progress-button.tsx +414 -0
  327. package/src/components/progress-button/progress-button.variants.test.ts +314 -0
  328. package/src/components/progress-button/progress-button.variants.ts +236 -0
  329. package/src/components/provider/AGENTS.md +38 -25
  330. package/src/components/provider/provider.tsx +38 -11
  331. package/src/components/radio/AGENTS.md +8 -10
  332. package/src/components/radio/radio.variants.test.ts +5 -6
  333. package/src/components/radio/radio.variants.ts +6 -7
  334. package/src/components/rating/AGENTS.md +143 -0
  335. package/src/components/rating/index.ts +43 -0
  336. package/src/components/rating/rating-output.tsx +39 -0
  337. package/src/components/rating/rating-star.tsx +70 -0
  338. package/src/components/rating/rating-stars.tsx +219 -0
  339. package/src/components/rating/rating.context.tsx +79 -0
  340. package/src/components/rating/rating.tsx +163 -0
  341. package/src/components/rating/rating.variants.test.ts +347 -0
  342. package/src/components/rating/rating.variants.ts +354 -0
  343. package/src/components/screen/screen-navbar-back-button.tsx +1 -1
  344. package/src/components/selection-mode/AGENTS.md +155 -0
  345. package/src/components/selection-mode/index.ts +43 -0
  346. package/src/components/selection-mode/selection-mode-action.tsx +65 -0
  347. package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
  348. package/src/components/selection-mode/selection-mode-group.tsx +123 -0
  349. package/src/components/selection-mode/selection-mode-header.tsx +85 -0
  350. package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
  351. package/src/components/selection-mode/selection-mode-item.tsx +182 -0
  352. package/src/components/selection-mode/selection-mode.context.tsx +89 -0
  353. package/src/components/selection-mode/selection-mode.tsx +207 -0
  354. package/src/components/selection-mode/selection-mode.types.ts +36 -0
  355. package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
  356. package/src/components/selection-mode/selection-mode.variants.ts +329 -0
  357. package/src/components/skeleton/AGENTS.md +120 -0
  358. package/src/components/skeleton/index.ts +15 -0
  359. package/src/components/skeleton/skeleton-clock.ts +48 -0
  360. package/src/components/skeleton/skeleton-group.tsx +64 -0
  361. package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
  362. package/src/components/skeleton/skeleton.context.tsx +45 -0
  363. package/src/components/skeleton/skeleton.tsx +227 -0
  364. package/src/components/skeleton/skeleton.variants.test.ts +245 -0
  365. package/src/components/skeleton/skeleton.variants.ts +222 -0
  366. package/src/components/slide-button/AGENTS.md +122 -0
  367. package/src/components/slide-button/index.ts +43 -0
  368. package/src/components/slide-button/slide-button-label.tsx +70 -0
  369. package/src/components/slide-button/slide-button-thumb.tsx +93 -0
  370. package/src/components/slide-button/slide-button.context.tsx +76 -0
  371. package/src/components/slide-button/slide-button.tsx +457 -0
  372. package/src/components/slide-button/slide-button.variants.test.ts +288 -0
  373. package/src/components/slide-button/slide-button.variants.ts +324 -0
  374. package/src/components/slider/index.ts +2 -0
  375. package/src/components/slider/slider-thumb.tsx +33 -1
  376. package/src/components/slider/slider.variants.test.ts +52 -0
  377. package/src/components/slider/slider.variants.ts +56 -2
  378. package/src/components/stack-card/AGENTS.md +169 -0
  379. package/src/components/stack-card/index.ts +37 -0
  380. package/src/components/stack-card/stack-card-action.tsx +66 -0
  381. package/src/components/stack-card/stack-card-actions.tsx +18 -0
  382. package/src/components/stack-card/stack-card-card.tsx +71 -0
  383. package/src/components/stack-card/stack-card-empty.tsx +30 -0
  384. package/src/components/stack-card/stack-card-slot.tsx +90 -0
  385. package/src/components/stack-card/stack-card-stamp.tsx +76 -0
  386. package/src/components/stack-card/stack-card.context.tsx +117 -0
  387. package/src/components/stack-card/stack-card.tsx +557 -0
  388. package/src/components/stack-card/stack-card.types.ts +26 -0
  389. package/src/components/stack-card/stack-card.variants.test.ts +355 -0
  390. package/src/components/stack-card/stack-card.variants.ts +360 -0
  391. package/src/components/steps/AGENTS.md +153 -0
  392. package/src/components/steps/index.ts +45 -0
  393. package/src/components/steps/steps-connector.tsx +31 -0
  394. package/src/components/steps/steps-description.tsx +26 -0
  395. package/src/components/steps/steps-indicator.tsx +106 -0
  396. package/src/components/steps/steps-item.tsx +225 -0
  397. package/src/components/steps/steps-panel.tsx +41 -0
  398. package/src/components/steps/steps-title.tsx +32 -0
  399. package/src/components/steps/steps-track.tsx +59 -0
  400. package/src/components/steps/steps.context.tsx +123 -0
  401. package/src/components/steps/steps.tsx +166 -0
  402. package/src/components/steps/steps.variants.test.ts +388 -0
  403. package/src/components/steps/steps.variants.ts +468 -0
  404. package/src/components/surface/AGENTS.md +72 -0
  405. package/src/components/surface/index.ts +15 -0
  406. package/src/components/surface/surface.context.tsx +54 -0
  407. package/src/components/surface/surface.tsx +70 -0
  408. package/src/components/surface/surface.variants.test.ts +180 -0
  409. package/src/components/surface/surface.variants.ts +133 -0
  410. package/src/components/swipe/AGENTS.md +140 -0
  411. package/src/components/swipe/index.ts +31 -0
  412. package/src/components/swipe/swipe-action.tsx +99 -0
  413. package/src/components/swipe/swipe-end.tsx +14 -0
  414. package/src/components/swipe/swipe-group.tsx +55 -0
  415. package/src/components/swipe/swipe-panel.tsx +55 -0
  416. package/src/components/swipe/swipe-start.tsx +15 -0
  417. package/src/components/swipe/swipe.context.tsx +135 -0
  418. package/src/components/swipe/swipe.tsx +393 -0
  419. package/src/components/swipe/swipe.types.ts +7 -0
  420. package/src/components/swipe/swipe.variants.test.ts +337 -0
  421. package/src/components/swipe/swipe.variants.ts +290 -0
  422. package/src/components/switch/switch.variants.ts +2 -2
  423. package/src/components/tabs/AGENTS.md +4 -3
  424. package/src/components/tabs/tabs.variants.test.ts +1 -1
  425. package/src/components/tabs/tabs.variants.ts +4 -6
  426. package/src/components/textarea/AGENTS.md +86 -0
  427. package/src/components/textarea/index.ts +16 -0
  428. package/src/components/textarea/textarea.tsx +157 -0
  429. package/src/components/textarea/textarea.variants.test.ts +219 -0
  430. package/src/components/textarea/textarea.variants.ts +171 -0
  431. package/src/components/toast/AGENTS.md +140 -0
  432. package/src/components/toast/index.ts +74 -0
  433. package/src/components/toast/toast-action.tsx +59 -0
  434. package/src/components/toast/toast-api.ts +32 -0
  435. package/src/components/toast/toast-close.tsx +44 -0
  436. package/src/components/toast/toast-content.tsx +11 -0
  437. package/src/components/toast/toast-description.tsx +11 -0
  438. package/src/components/toast/toast-indicator.tsx +34 -0
  439. package/src/components/toast/toast-item.tsx +308 -0
  440. package/src/components/toast/toast-title.tsx +13 -0
  441. package/src/components/toast/toast-viewport.tsx +135 -0
  442. package/src/components/toast/toast.context.tsx +69 -0
  443. package/src/components/toast/toast.store.test.ts +259 -0
  444. package/src/components/toast/toast.store.ts +338 -0
  445. package/src/components/toast/toast.timer.test.ts +64 -0
  446. package/src/components/toast/toast.timer.ts +51 -0
  447. package/src/components/toast/toast.tsx +132 -0
  448. package/src/components/toast/toast.variants.test.ts +393 -0
  449. package/src/components/toast/toast.variants.ts +304 -0
  450. package/src/components/toggle-button/AGENTS.md +106 -0
  451. package/src/components/toggle-button/index.ts +30 -0
  452. package/src/components/toggle-button/toggle-button-group.tsx +156 -0
  453. package/src/components/toggle-button/toggle-button-label.tsx +19 -0
  454. package/src/components/toggle-button/toggle-button.context.tsx +130 -0
  455. package/src/components/toggle-button/toggle-button.tsx +172 -0
  456. package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
  457. package/src/components/toggle-button/toggle-button.variants.ts +160 -0
  458. package/src/components/tooltip/AGENTS.md +133 -0
  459. package/src/components/tooltip/index.ts +28 -0
  460. package/src/components/tooltip/tooltip-arrow.tsx +40 -0
  461. package/src/components/tooltip/tooltip-content.tsx +179 -0
  462. package/src/components/tooltip/tooltip-description.tsx +19 -0
  463. package/src/components/tooltip/tooltip-text.tsx +21 -0
  464. package/src/components/tooltip/tooltip-title.tsx +20 -0
  465. package/src/components/tooltip/tooltip-trigger.tsx +103 -0
  466. package/src/components/tooltip/tooltip.context.tsx +68 -0
  467. package/src/components/tooltip/tooltip.tsx +213 -0
  468. package/src/components/tooltip/tooltip.variants.test.ts +231 -0
  469. package/src/components/tooltip/tooltip.variants.ts +177 -0
  470. package/src/hooks/use-calm-motion.tsx +31 -0
  471. package/src/lib/calm-motion.test.ts +17 -0
  472. package/src/lib/calm-motion.ts +23 -0
  473. package/src/lib/cn.test.ts +14 -0
  474. package/src/lib/cn.ts +2 -2
  475. package/src/no-gorhom.test.ts +54 -0
  476. package/src/styles/geometry.test.ts +1 -0
  477. package/src/styles/theme.css +4 -0
  478. package/src/styles/tokens.css +11 -0
  479. package/src/styles/tokens.test.ts +22 -0
  480. package/src/styles/tokens.ts +20 -0
  481. package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
  482. package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
@@ -0,0 +1,180 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import {
5
+ resolveSurfacePlane,
6
+ resolveSurfaceVariant,
7
+ SURFACE_FILLED_VARIANTS,
8
+ SURFACE_FOREGROUND_TOKENS,
9
+ SURFACE_PADDINGS,
10
+ SURFACE_VARIANTS,
11
+ surfaceVariants,
12
+ } from "./surface.variants";
13
+
14
+ /** Every class string the variant function can produce, one per combination. */
15
+ function everyRoot(): string[] {
16
+ return SURFACE_VARIANTS.flatMap((variant) =>
17
+ SURFACE_PADDINGS.map((padding) => surfaceVariants({ padding, variant }).root())
18
+ );
19
+ }
20
+
21
+ describe("surfaceVariants root slot", () => {
22
+ test("defaults to the card surface with md padding", () => {
23
+ const cls = surfaceVariants().root();
24
+ expect(cls).toContain("bg-card");
25
+ expect(cls).toContain("border-border");
26
+ expect(cls).toContain("p-4");
27
+ });
28
+
29
+ test("maps each variant to its fill", () => {
30
+ expect(surfaceVariants({ variant: "default" }).root()).toContain("bg-card");
31
+ expect(surfaceVariants({ variant: "secondary" }).root()).toContain("bg-secondary");
32
+ expect(surfaceVariants({ variant: "tertiary" }).root()).toContain("bg-tertiary");
33
+ expect(surfaceVariants({ variant: "transparent" }).root()).toContain("bg-transparent");
34
+ });
35
+
36
+ test("gives every variant a distinct surface", () => {
37
+ const seen = new Set(SURFACE_VARIANTS.map((variant) => surfaceVariants({ variant }).root()));
38
+ expect(seen.size).toBe(SURFACE_VARIANTS.length);
39
+ });
40
+
41
+ // A card on a page is white on near-white in light. The hairline is what
42
+ // holds its edge; the filled rungs below it are separated by their fill.
43
+ test("only the default variant draws a visible border", () => {
44
+ expect(surfaceVariants({ variant: "default" }).root()).toContain("border-border");
45
+ for (const variant of SURFACE_VARIANTS.filter((name) => name !== "default")) {
46
+ expect(surfaceVariants({ variant }).root()).not.toContain("border-border");
47
+ }
48
+ });
49
+
50
+ // Every variant carries the same one-point border box, so switching variant
51
+ // never moves the content by a pixel.
52
+ test("every variant reserves the same border width", () => {
53
+ for (const cls of everyRoot()) {
54
+ expect(cls).toMatch(/(^|\s)border(\s|$)/);
55
+ }
56
+ });
57
+
58
+ test("is card-shaped, with a continuous corner, at every variant and padding", () => {
59
+ for (const cls of everyRoot()) {
60
+ expect(cls).toContain("rounded-lg");
61
+ expect(cls).toContain("border-continuous");
62
+ }
63
+ });
64
+
65
+ test("maps each padding to its step", () => {
66
+ expect(surfaceVariants({ padding: "none" }).root()).toContain("p-0");
67
+ expect(surfaceVariants({ padding: "sm" }).root()).toContain("p-3");
68
+ expect(surfaceVariants({ padding: "md" }).root()).toContain("p-4");
69
+ expect(surfaceVariants({ padding: "lg" }).root()).toContain("p-6");
70
+ });
71
+
72
+ // `none` is for content that bleeds to the edge — an image, a chart — and
73
+ // that content has to take the corner with it.
74
+ test("clips only when its content touches the edge", () => {
75
+ expect(surfaceVariants({ padding: "none" }).root()).toContain("overflow-hidden");
76
+ for (const padding of SURFACE_PADDINGS.filter((name) => name !== "none")) {
77
+ expect(surfaceVariants({ padding }).root()).not.toContain("overflow-hidden");
78
+ }
79
+ });
80
+
81
+ // Rule 1: a React Native View does not cascade colour to a Text descendant.
82
+ test("carries no text treatment on the root", () => {
83
+ for (const cls of everyRoot()) {
84
+ expect(cls).not.toMatch(/\btext-/);
85
+ }
86
+ });
87
+
88
+ test("draws no shadow anywhere", () => {
89
+ for (const cls of everyRoot()) {
90
+ expect(cls).not.toMatch(/shadow/);
91
+ }
92
+ });
93
+
94
+ test("merges an incoming className last", () => {
95
+ expect(surfaceVariants().root({ className: "mt-3" })).toContain("mt-3");
96
+ expect(surfaceVariants().root({ className: "bg-popover" })).not.toContain("bg-card");
97
+ expect(surfaceVariants().root({ className: "p-8" })).not.toMatch(/\bp-4\b/);
98
+ });
99
+
100
+ // `border-continuous` is Uniwind's, and a merger that read it as a colour
101
+ // would drop it the moment a caller tinted the border.
102
+ test("keeps its corner curve when a caller recolours the border", () => {
103
+ expect(surfaceVariants().root({ className: "border-destructive" })).toContain("border-continuous");
104
+ });
105
+ });
106
+
107
+ describe("SURFACE_FOREGROUND_TOKENS", () => {
108
+ test("names the X-foreground token for each fill", () => {
109
+ expect(SURFACE_FOREGROUND_TOKENS).toEqual({
110
+ default: "card-foreground",
111
+ secondary: "secondary-foreground",
112
+ tertiary: "tertiary-foreground",
113
+ });
114
+ });
115
+
116
+ // Every token named here has to exist, or the class compiles to nothing and
117
+ // the text keeps whatever colour it inherited.
118
+ test("every token is declared in theme.css", () => {
119
+ const css = readFileSync(join(import.meta.dir, "../../styles/theme.css"), "utf8");
120
+ for (const token of Object.values(SURFACE_FOREGROUND_TOKENS)) {
121
+ expect(css).toContain(`--color-${token}: var(--${token})`);
122
+ }
123
+ });
124
+ });
125
+
126
+ describe("resolveSurfaceVariant", () => {
127
+ test("an explicit variant always wins", () => {
128
+ for (const variant of SURFACE_VARIANTS) {
129
+ expect(resolveSurfaceVariant({ parentPlane: null, variant })).toBe(variant);
130
+ expect(resolveSurfaceVariant({ parentPlane: "secondary", variant })).toBe(variant);
131
+ }
132
+ });
133
+
134
+ test("a top-level surface is the default card", () => {
135
+ expect(resolveSurfaceVariant({ parentPlane: null })).toBe("default");
136
+ });
137
+
138
+ test("a nested surface steps to the next fill", () => {
139
+ expect(resolveSurfaceVariant({ parentPlane: "default" })).toBe("secondary");
140
+ expect(resolveSurfaceVariant({ parentPlane: "secondary" })).toBe("tertiary");
141
+ expect(resolveSurfaceVariant({ parentPlane: "tertiary" })).toBe("secondary");
142
+ });
143
+
144
+ // The point of the ladder: whatever depth it is nested at, a surface never
145
+ // resolves to the fill it is sitting on, or it would vanish into it.
146
+ test("never resolves to the fill of the plane it sits on", () => {
147
+ for (const parentPlane of SURFACE_FILLED_VARIANTS) {
148
+ expect(resolveSurfaceVariant({ parentPlane })).not.toBe(parentPlane);
149
+ }
150
+ });
151
+
152
+ test("never resolves to transparent on its own", () => {
153
+ for (const parentPlane of [null, ...SURFACE_FILLED_VARIANTS]) {
154
+ expect(resolveSurfaceVariant({ parentPlane })).not.toBe("transparent");
155
+ }
156
+ });
157
+ });
158
+
159
+ describe("resolveSurfacePlane", () => {
160
+ test("a filled surface is the plane its children sit on", () => {
161
+ for (const variant of SURFACE_FILLED_VARIANTS) {
162
+ expect(resolveSurfacePlane({ parentPlane: null, variant })).toBe(variant);
163
+ expect(resolveSurfacePlane({ parentPlane: "tertiary", variant })).toBe(variant);
164
+ }
165
+ });
166
+
167
+ // A transparent surface paints nothing, so what its children sit on is
168
+ // still whatever it sits on.
169
+ test("a transparent surface passes its parent's plane through", () => {
170
+ expect(resolveSurfacePlane({ parentPlane: null, variant: "transparent" })).toBeNull();
171
+ for (const parentPlane of SURFACE_FILLED_VARIANTS) {
172
+ expect(resolveSurfacePlane({ parentPlane, variant: "transparent" })).toBe(parentPlane);
173
+ }
174
+ });
175
+
176
+ test("a surface inside a transparent one steps from the plane beneath both", () => {
177
+ const through = resolveSurfacePlane({ parentPlane: "default", variant: "transparent" });
178
+ expect(resolveSurfaceVariant({ parentPlane: through })).toBe("secondary");
179
+ });
180
+ });
@@ -0,0 +1,133 @@
1
+ import type { VariantProps } from "tailwind-variants";
2
+ import { tv } from "../../lib/tv";
3
+
4
+ export const SURFACE_VARIANTS = ["default", "secondary", "tertiary", "transparent"] as const;
5
+
6
+ export const SURFACE_PADDINGS = ["none", "sm", "md", "lg"] as const;
7
+
8
+ export type SurfaceVariant = (typeof SURFACE_VARIANTS)[number];
9
+ export type SurfacePadding = (typeof SURFACE_PADDINGS)[number];
10
+
11
+ /** The variants that paint a fill — every one but `transparent`. */
12
+ export const SURFACE_FILLED_VARIANTS = ["default", "secondary", "tertiary"] as const;
13
+
14
+ /**
15
+ * A fill something can sit on. `transparent` is not one: it paints nothing, so
16
+ * whatever is inside it still sits on the plane beneath.
17
+ */
18
+ export type SurfacePlane = (typeof SURFACE_FILLED_VARIANTS)[number];
19
+
20
+ /**
21
+ * Theme token for content drawn on each fill — `X-foreground` on an `X`
22
+ * surface, per the package's token rule. A class is `text-${token}`; a value,
23
+ * for an icon or a gradient stop, is `useThemeColor(token)`.
24
+ */
25
+ export const SURFACE_FOREGROUND_TOKENS = {
26
+ default: "card-foreground",
27
+ secondary: "secondary-foreground",
28
+ tertiary: "tertiary-foreground",
29
+ } as const satisfies Record<SurfacePlane, string>;
30
+
31
+ /**
32
+ * The fill a nested surface takes when it names none, keyed by the plane it
33
+ * sits on.
34
+ *
35
+ * `tertiary` is quieter than `secondary` — half a step back toward the page —
36
+ * so a third level returns to `secondary` rather than stopping. What matters is
37
+ * that each surface differs from its immediate parent; a strictly ascending
38
+ * ladder would run out of fills at the fourth level and start vanishing into
39
+ * its own parent.
40
+ */
41
+ const NEXT_PLANE = {
42
+ default: "secondary",
43
+ secondary: "tertiary",
44
+ tertiary: "secondary",
45
+ } as const satisfies Record<SurfacePlane, SurfacePlane>;
46
+
47
+ /**
48
+ * Styling for a surface.
49
+ *
50
+ * The root is card-shaped — `rounded-lg`, the corner `--radius` names, see the
51
+ * corner-scale note in the package AGENTS.md — with `border-continuous`, which
52
+ * gives iOS's continuous corner rather than a circular arc. Android ignores it.
53
+ *
54
+ * Every variant reserves the same one-point `border`, transparent unless the
55
+ * variant colours it, so changing variant never moves the content. Only
56
+ * `default` colours it: `--card` sits a percent above `--background` in light,
57
+ * and without the hairline a card on a page is white on near-white. The filled
58
+ * rungs below it are told apart by their fill.
59
+ *
60
+ * `padding: "none"` clips, and nothing else does. It is the one step whose
61
+ * content touches the edge — an image or a chart bled to the corners — and that
62
+ * content has to take the corner with it. Clipping everywhere would crop
63
+ * anything a child draws past its own box.
64
+ *
65
+ * No shadow, on purpose. Nothing in this package casts one — React Native's
66
+ * shadow props disagree between platforms — so depth here is carried by fill.
67
+ *
68
+ * The root carries no `text-*`: a React Native `View` does not cascade colour
69
+ * to a `Text` descendant, so a class here would reach nothing.
70
+ *
71
+ * Free of React Native imports so it stays unit-testable — `bun test` cannot
72
+ * parse React Native's Flow-typed source. See AGENTS.md.
73
+ */
74
+ export const surfaceVariants = tv({
75
+ slots: {
76
+ root: "rounded-lg border border-continuous border-transparent",
77
+ },
78
+ variants: {
79
+ variant: {
80
+ default: { root: "border-border bg-card" },
81
+ secondary: { root: "bg-secondary" },
82
+ tertiary: { root: "bg-tertiary" },
83
+ transparent: { root: "bg-transparent" },
84
+ },
85
+ padding: {
86
+ none: { root: "overflow-hidden p-0" },
87
+ sm: { root: "p-3" },
88
+ md: { root: "p-4" },
89
+ lg: { root: "p-6" },
90
+ },
91
+ },
92
+ defaultVariants: {
93
+ variant: "default",
94
+ padding: "md",
95
+ },
96
+ });
97
+
98
+ export type SurfaceVariantProps = VariantProps<typeof surfaceVariants>;
99
+
100
+ /**
101
+ * The fill a surface draws.
102
+ *
103
+ * An explicit `variant` always wins. Without one, a surface at the top of the
104
+ * tree is the `default` card, and a surface nested in another steps to the next
105
+ * fill, so a panel inside a card is told apart from the card with nothing said
106
+ * at the call site — and a `Card` built on this gets the same for free.
107
+ */
108
+ export function resolveSurfaceVariant({
109
+ parentPlane,
110
+ variant,
111
+ }: {
112
+ parentPlane: SurfacePlane | null;
113
+ variant?: SurfaceVariant;
114
+ }): SurfaceVariant {
115
+ if (variant) {
116
+ return variant;
117
+ }
118
+ return parentPlane ? NEXT_PLANE[parentPlane] : "default";
119
+ }
120
+
121
+ /**
122
+ * The plane a surface's children sit on: its own fill, or — for a transparent
123
+ * surface, which paints none — the plane it sits on itself.
124
+ */
125
+ export function resolveSurfacePlane({
126
+ parentPlane,
127
+ variant,
128
+ }: {
129
+ parentPlane: SurfacePlane | null;
130
+ variant: SurfaceVariant;
131
+ }): SurfacePlane | null {
132
+ return variant === "transparent" ? parentPlane : variant;
133
+ }
@@ -0,0 +1,140 @@
1
+ # Swipe
2
+
3
+ A row that slides aside to reveal actions behind it — delete, archive, snooze, mark done. Compound
4
+ root plus `Swipe.Start`, `Swipe.End`, `Swipe.Action` and `Swipe.Group`, with `useSwipe()` and
5
+ `useSwipeGroup()`. It acts on one row; for many rows at once, a selection mode is the pattern.
6
+
7
+ `import { Swipe } from "@delacour/react-native-ui/swipe";`
8
+
9
+ ## Files
10
+
11
+ | File | What it holds |
12
+ | --- | --- |
13
+ | `index.ts` | → `@delacour/react-native-ui/swipe` |
14
+ | `swipe.tsx` | Root — the partition, the pan, the release, the imperative handle and the accessibility actions |
15
+ | `swipe-start.tsx` | `Swipe.Start`, a marker that renders nothing |
16
+ | `swipe-end.tsx` | `Swipe.End`, a marker that renders nothing |
17
+ | `swipe-action.tsx` | `Swipe.Action`, one tile, positioned by its own animated style |
18
+ | `swipe-panel.tsx` | The layer behind one edge — internal, one per side that has tiles |
19
+ | `swipe-group.tsx` | `Swipe.Group`, the registry that keeps one row open |
20
+ | `swipe.context.tsx` | Row, tile and group contexts, `useSwipe()`, `useSwipeGroup()` |
21
+ | `swipe.types.ts` | `SwipePanelProps`, shared by the two markers |
22
+ | `swipe.variants.ts` | Pure `tv()` slots + the partition, release and tile-layout resolvers, no RN imports |
23
+ | `swipe.variants.test.ts` | |
24
+
25
+ ## Design
26
+
27
+ - **Colours**: `default`, `primary`, `success`, `warning`, `info`, `destructive` — Badge's set. A
28
+ tile is `bg-{color}` with its glyph and label in `{color}-foreground`; `default` is `bg-muted`
29
+ with `text-foreground`, since there is no `muted` surface ink stronger than that.
30
+ - **Tiles are 72pt, all of them.** A panel's width is a count times `SWIPE_TILE_WIDTH`, so nothing
31
+ is measured and the release can be judged on the UI thread the moment the finger lifts. The
32
+ `tileContent` slot spells the same number as `w-18`, and a test pins the pair.
33
+ - **The panel is exactly as wide as the gap the row has left.** Never wider, so it cannot paint
34
+ over a row that has no background of its own; never narrower, so no hole opens. The row slot
35
+ sets no `bg-*` and a test holds it to that — the two never overlap, so the row needs none.
36
+ - **Past the tiles' total the outermost tile grows, and the panel wears its colour.** An overshoot
37
+ then reads as *more of that action* rather than as an empty strip. The outermost is the last
38
+ tile on `end` and the first on `start` — `resolveOutermostIndex`.
39
+ - **Tiles emerge from under the row.** Short of fully open each is overlapped by the one outside
40
+ it, spread in proportion to the gap; they are whole and edge to edge exactly when the gap
41
+ reaches their total. `resolveTileLayout` is the rule, and a test sweeps it to prove the tiles
42
+ always cover the gap and never reach past it once open.
43
+ - **A tile's content box is pinned to its inner edge**, so when the outermost grows its glyph
44
+ stays beside its neighbours rather than drifting to the middle of a widening slab.
45
+
46
+ ## Anatomy
47
+
48
+ - **`Swipe.Start` and `Swipe.End` are markers, lifted out by element type.** Only the root knows
49
+ the gap, so only the root can lay the tiles out; a marker that rendered its own children would
50
+ need the offset passed back down to it anyway. Everything that is not a marker is the row, and
51
+ order does not matter.
52
+ - **The match is on `displayName`, not on function identity.** The package ships raw source, so
53
+ React Compiler or a second module instance can hand the root an element whose `type` is a
54
+ different object standing for the same component — the failure
55
+ [Switch](../switch/AGENTS.md)'s thumb detection records. `partitionSwipeChildren` reads only
56
+ `type.displayName` and `props.children`, which is also what lets the test feed it plain objects.
57
+ - **A tile has to be written directly inside its marker.** `Swipe.Action` reads a tile context the
58
+ root supplies as it lays each tile out, and throws by name if it is rendered anywhere else.
59
+ Wrapping actions in a custom component inside the marker therefore does not work — the wrapper
60
+ would be the tile. This is the trade the lift-out makes.
61
+ - **The icon is a component prop, and that bends the house rule on purpose.** Icons are composed
62
+ everywhere else, but a tile has to size its glyph to `size-icon-md` and tint it to its
63
+ foreground. It takes the same `IconComponent` type `Icon`'s `icon` prop takes and renders
64
+ `<Icon icon={…} />` inside an `IconDefaultsProvider`, so the glyph still flows through the one
65
+ icon path the package has.
66
+
67
+ ## Gesture
68
+
69
+ - **`activeOffsetX([-10, 10])` under `failOffsetY([-8, 8])`**, [Tabs](../tabs/AGENTS.md)' pairing.
70
+ The pan waits for clearly sideways intent and fails the moment the finger commits vertically, so
71
+ a list of rows scrolls normally. This is the opposite of [Slider](../slider/AGENTS.md)'s
72
+ `minDistance(0)`: a slider owns every touch on its track, a row owns only the sideways ones.
73
+ - **A side with no panel rubber-bands at 0.2 and returns.** The row still answers the finger, so
74
+ the touch does not feel dead, but it never promises an action that is not there.
75
+ - **The release is judged on a projection**: `offset + velocity × 0.1` against half the panel. A
76
+ projection that crosses the rest position closes rather than flinging the row open on the other
77
+ side.
78
+ - **A full swipe needs the finger itself past the point.** `|offset| > panel + 0.35 × row`, with
79
+ the projection still beyond it. A flick alone never fires the outermost action, because that
80
+ action is routinely Delete; and pulling back from past the point before letting go cancels it.
81
+ This is the one reading of "or a projected equivalent" that cannot delete by accident. The
82
+ haptic ticks once at exactly that crossing and re-arms if the finger goes back, so the person
83
+ letting go knows what will happen.
84
+ - **The fly-off fires the action when it lands, then brings the row back a beat later.** An action
85
+ that removes the row from state unmounts it during that beat, so it never visibly slides back
86
+ in; one that does not (Done, Archive into the same list) sees its row return closed.
87
+ - **Tapping the open row closes it and swallows the tap.** A transparent shield covers the row
88
+ only while a side is open, behind its own `Gesture.Tap()`, so the row's own `Pressable`s never
89
+ see that tap.
90
+ - **The release runs in `onFinalize`, gated on the pan having started.** `onFinalize` also fires
91
+ for a touch that never activated; without the gate a stray tap would re-judge, and stomp on, an
92
+ imperative `open()` still springing.
93
+ - **Nothing re-renders during a drag.** The offset, the grab, the full-swipe latch and the measured
94
+ width are shared values; React state changes only when a release settles a side.
95
+ - **Every worklet body is self-contained.** `resolveSwipeRelease`, `resolveSwipeDrag` and
96
+ `resolveTileLayout` are restated inside the gesture and the animated styles rather than called,
97
+ and the tests pin the pure versions. A module-scope worklet calling a sibling is the crash
98
+ [Slider](../slider/AGENTS.md) documents.
99
+
100
+ ## Group
101
+
102
+ - **Rows register through context, never by the group walking its children.** A row nested in a
103
+ `ListGroup`, a `.map()` or a custom component still belongs. The registry is a ref, so joining
104
+ or leaving it re-renders nothing.
105
+ - **The group closes only rows that are open.** A close aimed at a shut row would restart its
106
+ spring and cancel a drag already under way on it.
107
+ - **`useSwipeGroup()` outside a group is a no-op, not a throw**, so a screen can call
108
+ `closeAll()` whether or not its rows are grouped.
109
+
110
+ ## Motion
111
+
112
+ - **Under reduced motion every spring becomes a 160ms timing, and the fly-off becomes a fade.**
113
+ The row still follows the finger — direct manipulation is not decorative — and the fade keeps
114
+ the outcome visible rather than cutting.
115
+
116
+ ## RTL
117
+
118
+ - **`I18nManager.isRTL` flips one sign — the row's translation and the pan's reading of it.** The
119
+ offset is held in logical points (positive reveals `start`), and the panels and tiles are pinned
120
+ with Yoga's logical `start` / `end`, which mirror on their own. `end` is always the edge text
121
+ runs toward.
122
+
123
+ ## Accessibility
124
+
125
+ - **Every action is published on the row as an accessibility action**, named and labelled by its
126
+ `label`, and dispatched by name. A swipe is invisible to a screen reader, so without this the
127
+ actions would not exist for one.
128
+ - **The row is a single accessibility element** (`accessible`), the shape a list row with custom
129
+ actions takes; `accessibilityLabel` and `accessibilityHint` passed to `Swipe` land on it. A row
130
+ that needs its own separately focusable controls is not a swipe row.
131
+ - **Tiles are `button`s, hidden while the row is shut** (`accessibilityElementsHidden` and
132
+ `importantForAccessibility="no-hide-descendants"` on each panel), so a screen reader does not
133
+ walk into actions nobody can see.
134
+
135
+ ## Out of scope
136
+
137
+ - A controlled `openSide` prop. The imperative handle and `onOpenChange` cover the cases that came
138
+ up; a controlled pair can follow through `useControllableState` if one appears.
139
+ - Wrapped tiles (see Anatomy), per-tile widths, and vertical swipes.
140
+ - Undo. A demo keeps local state; a toast belongs to the overlays.
@@ -0,0 +1,31 @@
1
+ export { Swipe, type SwipeHandle, type SwipeProps } from "./swipe";
2
+ export {
3
+ type SwipeContextValue,
4
+ type SwipeGroupContextValue,
5
+ useSwipe,
6
+ useSwipeContext,
7
+ useSwipeGroup,
8
+ } from "./swipe.context";
9
+ export type { SwipePanelProps } from "./swipe.types";
10
+ export {
11
+ partitionSwipeChildren,
12
+ resolveOutermostIndex,
13
+ resolveSwipeActionForegroundToken,
14
+ resolveSwipeDrag,
15
+ resolveSwipeRelease,
16
+ resolveTileLayout,
17
+ SWIPE_ACTION_COLORS,
18
+ SWIPE_ACTION_FOREGROUND_TOKEN,
19
+ SWIPE_SIDES,
20
+ SWIPE_TILE_WIDTH,
21
+ type SwipeActionColor,
22
+ type SwipeChildren,
23
+ type SwipeOpenSide,
24
+ type SwipeRelease,
25
+ type SwipeSide,
26
+ type SwipeVariantProps,
27
+ swipeVariants,
28
+ toPhysicalOffset,
29
+ } from "./swipe.variants";
30
+ export type { SwipeActionProps } from "./swipe-action";
31
+ export type { SwipeGroupProps } from "./swipe-group";
@@ -0,0 +1,99 @@
1
+ import { type ReactElement, useCallback, useMemo } from "react";
2
+ import { View } from "react-native";
3
+ import Animated, { useAnimatedStyle } from "react-native-reanimated";
4
+ import { Icon, type IconComponent } from "../icon/icon";
5
+ import { IconDefaultsProvider } from "../icon/icon.context";
6
+ import { Pressable } from "../pressable/pressable";
7
+ import { Text } from "../text/text";
8
+ import { useSwipePart, useSwipeTilePart } from "./swipe.context";
9
+ import {
10
+ resolveSwipeActionForegroundToken,
11
+ SWIPE_TILE_WIDTH,
12
+ type SwipeActionColor,
13
+ swipeVariants,
14
+ } from "./swipe.variants";
15
+
16
+ export type SwipeActionProps = {
17
+ /** The tile's caption, and the name a screen reader offers the action under. Required. */
18
+ label: string;
19
+ /** A Central Icon component, drawn above the label at the tile's size and ink. */
20
+ icon?: IconComponent;
21
+ color?: SwipeActionColor;
22
+ onPress: () => void;
23
+ /** Leave the row open after the action runs — for a toggle such as a bookmark. */
24
+ isKeptOpen?: boolean;
25
+ className?: string;
26
+ labelClassName?: string;
27
+ /** Forwarded to the tile's pressable, for an end-to-end test or a capture flow to find it. */
28
+ testID?: string;
29
+ };
30
+
31
+ /**
32
+ * One tile behind the row.
33
+ *
34
+ * Positioned by its own animated style off the row's offset: short of fully
35
+ * open it is overlapped by the tile outside it, so the set emerges from under
36
+ * the row; past fully open the outermost tile grows into the overshoot. The
37
+ * worklet restates `resolveTileLayout`, which the tests pin.
38
+ *
39
+ * Takes the icon as a **component** rather than composed children, because the
40
+ * tile has to size and tint the glyph itself; it renders it through
41
+ * `IconDefaultsProvider`, the same inheritance a `Button` uses.
42
+ */
43
+ export function SwipeAction({
44
+ label,
45
+ icon,
46
+ color = "default",
47
+ onPress,
48
+ isKeptOpen = false,
49
+ className,
50
+ labelClassName,
51
+ testID,
52
+ }: SwipeActionProps): ReactElement {
53
+ const swipe = useSwipePart("Swipe.Action");
54
+ const { side, index, count } = useSwipeTilePart("Swipe.Action");
55
+ const { offset, runAction } = swipe;
56
+ const slots = swipeVariants({ color, side });
57
+ const order = side === "end" ? index : count - 1 - index;
58
+
59
+ const tileStyle = useAnimatedStyle(() => {
60
+ const tileWidth = SWIPE_TILE_WIDTH;
61
+ const reveal = Math.max(0, side === "start" ? offset.value : -offset.value);
62
+ const total = count * tileWidth;
63
+ const isPacked = reveal <= total;
64
+ const x = isPacked ? (count > 0 ? (order * reveal) / count : 0) : order * tileWidth;
65
+ const width = !isPacked && order === count - 1 ? reveal - order * tileWidth : tileWidth;
66
+ return side === "end" ? { start: x, width } : { end: x, width };
67
+ });
68
+
69
+ const handlePress = useCallback(() => runAction(onPress, isKeptOpen), [isKeptOpen, onPress, runAction]);
70
+ const iconDefaults = useMemo(
71
+ () => ({ className: "size-icon-md", color: resolveSwipeActionForegroundToken(color) }),
72
+ [color]
73
+ );
74
+
75
+ return (
76
+ <Animated.View className={slots.tile({ className })} style={[{ zIndex: order }, tileStyle]}>
77
+ <Pressable
78
+ accessibilityLabel={label}
79
+ accessibilityRole="button"
80
+ className={slots.tilePressable()}
81
+ feedback="fade"
82
+ onPress={handlePress}
83
+ testID={testID}
84
+ >
85
+ <View className={slots.tileContent()}>
86
+ {icon ? (
87
+ <IconDefaultsProvider value={iconDefaults}>
88
+ <Icon icon={icon} />
89
+ </IconDefaultsProvider>
90
+ ) : null}
91
+ <Text className={slots.tileLabel({ className: labelClassName })} numberOfLines={1}>
92
+ {label}
93
+ </Text>
94
+ </View>
95
+ </Pressable>
96
+ </Animated.View>
97
+ );
98
+ }
99
+ SwipeAction.displayName = "DelacourUI.Swipe.Action";
@@ -0,0 +1,14 @@
1
+ import type { ReactElement } from "react";
2
+ import type { SwipePanelProps } from "./swipe.types";
3
+
4
+ /**
5
+ * Marks the tiles behind the row's end edge — the edge text runs toward —
6
+ * revealed by sliding the row toward `start`.
7
+ *
8
+ * A marker, never rendered: the root lifts its children out by element type and
9
+ * lays them out itself. On its own it renders nothing.
10
+ */
11
+ export function SwipeEnd(_props: SwipePanelProps): ReactElement | null {
12
+ return null;
13
+ }
14
+ SwipeEnd.displayName = "DelacourUI.Swipe.End";
@@ -0,0 +1,55 @@
1
+ import { type ReactElement, useCallback, useMemo, useRef } from "react";
2
+ import { View, type ViewProps } from "react-native";
3
+ import { cn } from "../../lib/cn";
4
+ import { type SwipeGroupContextValue, SwipeGroupProvider } from "./swipe.context";
5
+
6
+ export type SwipeGroupProps = ViewProps & {
7
+ className?: string;
8
+ /** One row open at a time: opening one closes the rest. Default `true`. */
9
+ isExclusive?: boolean;
10
+ };
11
+
12
+ /**
13
+ * Coordinates the rows beneath it. Renders a plain `View`.
14
+ *
15
+ * **Rows register themselves through context**, rather than the group walking
16
+ * its children, so a row nested in a `ListGroup`, a `.map()` or a custom
17
+ * component still belongs. The registry is a ref — joining or leaving it
18
+ * re-renders nothing.
19
+ */
20
+ export function SwipeGroup({ className, isExclusive = true, children, ...props }: SwipeGroupProps): ReactElement {
21
+ const rows = useRef(new Map<string, () => void>());
22
+
23
+ const register = useCallback((id: string, close: () => void) => {
24
+ rows.current.set(id, close);
25
+ return () => {
26
+ if (rows.current.get(id) === close) rows.current.delete(id);
27
+ };
28
+ }, []);
29
+
30
+ const notifyOpen = useCallback(
31
+ (id: string) => {
32
+ if (!isExclusive) return;
33
+ for (const [other, close] of rows.current) if (other !== id) close();
34
+ },
35
+ [isExclusive]
36
+ );
37
+
38
+ const closeAll = useCallback(() => {
39
+ for (const close of rows.current.values()) close();
40
+ }, []);
41
+
42
+ const value = useMemo<SwipeGroupContextValue>(
43
+ () => ({ closeAll, notifyOpen, register }),
44
+ [closeAll, notifyOpen, register]
45
+ );
46
+
47
+ return (
48
+ <SwipeGroupProvider value={value}>
49
+ <View className={cn(className)} {...props}>
50
+ {children}
51
+ </View>
52
+ </SwipeGroupProvider>
53
+ );
54
+ }
55
+ SwipeGroup.displayName = "DelacourUI.Swipe.Group";