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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (493) hide show
  1. package/README.md +10 -6
  2. package/package.json +49 -5
  3. package/src/components/accordion/AGENTS.md +21 -9
  4. package/src/components/accordion/accordion-content.tsx +7 -4
  5. package/src/components/accordion/accordion-item.tsx +15 -13
  6. package/src/components/accordion/accordion.context.tsx +11 -5
  7. package/src/components/accordion/accordion.variants.test.ts +64 -3
  8. package/src/components/accordion/accordion.variants.ts +33 -9
  9. package/src/components/alert/AGENTS.md +102 -0
  10. package/src/components/alert/alert-action.tsx +17 -0
  11. package/src/components/alert/alert-close-button.tsx +50 -0
  12. package/src/components/alert/alert-content.tsx +15 -0
  13. package/src/components/alert/alert-description.tsx +16 -0
  14. package/src/components/alert/alert-glyphs.ts +25 -0
  15. package/src/components/alert/alert-indicator.tsx +40 -0
  16. package/src/components/alert/alert-title.tsx +18 -0
  17. package/src/components/alert/alert.context.tsx +69 -0
  18. package/src/components/alert/alert.tsx +160 -0
  19. package/src/components/alert/alert.types.ts +10 -0
  20. package/src/components/alert/alert.variants.test.ts +218 -0
  21. package/src/components/alert/alert.variants.ts +180 -0
  22. package/src/components/alert/index.ts +23 -0
  23. package/src/components/avatar/AGENTS.md +106 -0
  24. package/src/components/avatar/avatar-badge.tsx +51 -0
  25. package/src/components/avatar/avatar-group.tsx +139 -0
  26. package/src/components/avatar/avatar.context.tsx +93 -0
  27. package/src/components/avatar/avatar.tsx +201 -0
  28. package/src/components/avatar/avatar.utils.test.ts +174 -0
  29. package/src/components/avatar/avatar.utils.ts +141 -0
  30. package/src/components/avatar/avatar.variants.test.ts +251 -0
  31. package/src/components/avatar/avatar.variants.ts +270 -0
  32. package/src/components/avatar/index.ts +39 -0
  33. package/src/components/badge/AGENTS.md +10 -0
  34. package/src/components/badge/badge-close-button.tsx +2 -2
  35. package/src/components/badge/badge.context.tsx +2 -1
  36. package/src/components/badge/badge.tsx +13 -4
  37. package/src/components/badge/badge.variants.test.ts +26 -0
  38. package/src/components/badge/badge.variants.ts +21 -0
  39. package/src/components/bottom-sheet/AGENTS.md +186 -231
  40. package/src/components/bottom-sheet/bottom-sheet-background.tsx +31 -22
  41. package/src/components/bottom-sheet/bottom-sheet-close.tsx +23 -30
  42. package/src/components/bottom-sheet/bottom-sheet-container.tsx +54 -208
  43. package/src/components/bottom-sheet/bottom-sheet-content.tsx +25 -48
  44. package/src/components/bottom-sheet/bottom-sheet-description.tsx +8 -2
  45. package/src/components/bottom-sheet/bottom-sheet-flat-list.tsx +65 -0
  46. package/src/components/bottom-sheet/bottom-sheet-footer.tsx +53 -122
  47. package/src/components/bottom-sheet/bottom-sheet-handle.tsx +31 -26
  48. package/src/components/bottom-sheet/bottom-sheet-legend-list.tsx +69 -0
  49. package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +25 -147
  50. package/src/components/bottom-sheet/bottom-sheet-provider.tsx +42 -0
  51. package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +38 -62
  52. package/src/components/bottom-sheet/bottom-sheet-section-list.tsx +58 -0
  53. package/src/components/bottom-sheet/bottom-sheet-steps.tsx +88 -0
  54. package/src/components/bottom-sheet/bottom-sheet-text-input.tsx +44 -0
  55. package/src/components/bottom-sheet/bottom-sheet-title.tsx +12 -8
  56. package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +19 -37
  57. package/src/components/bottom-sheet/bottom-sheet.tsx +113 -60
  58. package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +47 -87
  59. package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -126
  60. package/src/components/bottom-sheet/index.ts +39 -17
  61. package/src/components/bottom-sheet/use-bottom-sheet-input.ts +16 -115
  62. package/src/components/button/AGENTS.md +51 -11
  63. package/src/components/button/button-group.tsx +44 -8
  64. package/src/components/button/button.context.tsx +10 -0
  65. package/src/components/button/button.tsx +7 -1
  66. package/src/components/button/button.variants.test.ts +190 -0
  67. package/src/components/button/button.variants.ts +87 -6
  68. package/src/components/button/index.ts +2 -0
  69. package/src/components/calendar/AGENTS.md +142 -0
  70. package/src/components/calendar/calendar-caption.tsx +116 -0
  71. package/src/components/calendar/calendar-day.tsx +103 -0
  72. package/src/components/calendar/calendar-grid.tsx +183 -0
  73. package/src/components/calendar/calendar-header.tsx +34 -0
  74. package/src/components/calendar/calendar-nav.tsx +51 -0
  75. package/src/components/calendar/calendar-picker.tsx +128 -0
  76. package/src/components/calendar/calendar-weekdays.tsx +48 -0
  77. package/src/components/calendar/calendar.context.tsx +108 -0
  78. package/src/components/calendar/calendar.date.test.ts +456 -0
  79. package/src/components/calendar/calendar.date.ts +584 -0
  80. package/src/components/calendar/calendar.tsx +389 -0
  81. package/src/components/calendar/calendar.types.ts +87 -0
  82. package/src/components/calendar/calendar.variants.test.ts +363 -0
  83. package/src/components/calendar/calendar.variants.ts +426 -0
  84. package/src/components/calendar/index.ts +96 -0
  85. package/src/components/card/AGENTS.md +92 -0
  86. package/src/components/card/card-action.tsx +16 -0
  87. package/src/components/card/card-content.tsx +12 -0
  88. package/src/components/card/card-description.tsx +12 -0
  89. package/src/components/card/card-footer.tsx +30 -0
  90. package/src/components/card/card-header.tsx +45 -0
  91. package/src/components/card/card-title.tsx +25 -0
  92. package/src/components/card/card.context.tsx +64 -0
  93. package/src/components/card/card.tsx +100 -0
  94. package/src/components/card/card.types.ts +13 -0
  95. package/src/components/card/card.variants.test.ts +240 -0
  96. package/src/components/card/card.variants.ts +177 -0
  97. package/src/components/card/index.ts +15 -0
  98. package/src/components/chart/AGENTS.md +8 -0
  99. package/src/components/chart/chart.tsx +8 -1
  100. package/src/components/chip/AGENTS.md +113 -0
  101. package/src/components/chip/chip-close-button.tsx +48 -0
  102. package/src/components/chip/chip-end-content.tsx +15 -0
  103. package/src/components/chip/chip-label.tsx +21 -0
  104. package/src/components/chip/chip-start-content.tsx +16 -0
  105. package/src/components/chip/chip.context.tsx +66 -0
  106. package/src/components/chip/chip.tsx +268 -0
  107. package/src/components/chip/chip.types.ts +10 -0
  108. package/src/components/chip/chip.variants.test.ts +335 -0
  109. package/src/components/chip/chip.variants.ts +321 -0
  110. package/src/components/chip/index.ts +24 -0
  111. package/src/components/collapsible/AGENTS.md +97 -0
  112. package/src/components/collapsible/collapsible-content.tsx +97 -0
  113. package/src/components/collapsible/collapsible-description.tsx +17 -0
  114. package/src/components/collapsible/collapsible-indicator.tsx +92 -0
  115. package/src/components/collapsible/collapsible-title.tsx +17 -0
  116. package/src/components/collapsible/collapsible-trigger.tsx +133 -0
  117. package/src/components/collapsible/collapsible.context.tsx +91 -0
  118. package/src/components/collapsible/collapsible.tsx +167 -0
  119. package/src/components/collapsible/collapsible.types.ts +9 -0
  120. package/src/components/collapsible/collapsible.variants.test.ts +338 -0
  121. package/src/components/collapsible/collapsible.variants.ts +228 -0
  122. package/src/components/collapsible/index.ts +32 -0
  123. package/src/components/context-menu/AGENTS.md +103 -0
  124. package/src/components/context-menu/context-menu-content.tsx +64 -0
  125. package/src/components/context-menu/context-menu-item.tsx +22 -0
  126. package/src/components/context-menu/context-menu-preview.tsx +60 -0
  127. package/src/components/context-menu/context-menu-trigger.tsx +160 -0
  128. package/src/components/context-menu/context-menu.context.tsx +91 -0
  129. package/src/components/context-menu/context-menu.tsx +90 -0
  130. package/src/components/context-menu/context-menu.variants.test.ts +266 -0
  131. package/src/components/context-menu/context-menu.variants.ts +147 -0
  132. package/src/components/context-menu/index.ts +18 -0
  133. package/src/components/dialog/AGENTS.md +97 -0
  134. package/src/components/dialog/dialog-body.tsx +21 -0
  135. package/src/components/dialog/dialog-close.tsx +82 -0
  136. package/src/components/dialog/dialog-content.tsx +153 -0
  137. package/src/components/dialog/dialog-description.tsx +23 -0
  138. package/src/components/dialog/dialog-footer.tsx +32 -0
  139. package/src/components/dialog/dialog-header.tsx +20 -0
  140. package/src/components/dialog/dialog-title.tsx +42 -0
  141. package/src/components/dialog/dialog-trigger.tsx +54 -0
  142. package/src/components/dialog/dialog.context.tsx +90 -0
  143. package/src/components/dialog/dialog.tsx +119 -0
  144. package/src/components/dialog/dialog.variants.test.ts +224 -0
  145. package/src/components/dialog/dialog.variants.ts +173 -0
  146. package/src/components/dialog/index.ts +30 -0
  147. package/src/components/drawer/AGENTS.md +123 -0
  148. package/src/components/drawer/drawer-body.tsx +46 -0
  149. package/src/components/drawer/drawer-close.tsx +76 -0
  150. package/src/components/drawer/drawer-content.tsx +186 -0
  151. package/src/components/drawer/drawer-description.tsx +23 -0
  152. package/src/components/drawer/drawer-footer.tsx +21 -0
  153. package/src/components/drawer/drawer-header.tsx +42 -0
  154. package/src/components/drawer/drawer-title.tsx +35 -0
  155. package/src/components/drawer/drawer-trigger.tsx +54 -0
  156. package/src/components/drawer/drawer.context.tsx +114 -0
  157. package/src/components/drawer/drawer.tsx +119 -0
  158. package/src/components/drawer/drawer.variants.test.ts +392 -0
  159. package/src/components/drawer/drawer.variants.ts +244 -0
  160. package/src/components/drawer/index.ts +41 -0
  161. package/src/components/drawer/use-drawer-pan.ts +108 -0
  162. package/src/components/empty-state/AGENTS.md +90 -0
  163. package/src/components/empty-state/empty-state-content.tsx +17 -0
  164. package/src/components/empty-state/empty-state-description.tsx +12 -0
  165. package/src/components/empty-state/empty-state-header.tsx +17 -0
  166. package/src/components/empty-state/empty-state-media.tsx +56 -0
  167. package/src/components/empty-state/empty-state-title.tsx +18 -0
  168. package/src/components/empty-state/empty-state.context.tsx +65 -0
  169. package/src/components/empty-state/empty-state.tsx +84 -0
  170. package/src/components/empty-state/empty-state.types.ts +14 -0
  171. package/src/components/empty-state/empty-state.variants.test.ts +245 -0
  172. package/src/components/empty-state/empty-state.variants.ts +132 -0
  173. package/src/components/empty-state/index.ts +20 -0
  174. package/src/components/fab/AGENTS.md +119 -0
  175. package/src/components/fab/fab-action.tsx +118 -0
  176. package/src/components/fab/fab-group.tsx +214 -0
  177. package/src/components/fab/fab-label.tsx +18 -0
  178. package/src/components/fab/fab.context.tsx +125 -0
  179. package/src/components/fab/fab.tsx +156 -0
  180. package/src/components/fab/fab.types.ts +17 -0
  181. package/src/components/fab/fab.variants.test.ts +274 -0
  182. package/src/components/fab/fab.variants.ts +234 -0
  183. package/src/components/fab/index.ts +37 -0
  184. package/src/components/feedback/AGENTS.md +95 -0
  185. package/src/components/feedback/feedback-action.tsx +16 -0
  186. package/src/components/feedback/feedback-cancel.tsx +34 -0
  187. package/src/components/feedback/feedback-close.tsx +21 -0
  188. package/src/components/feedback/feedback-content.tsx +24 -0
  189. package/src/components/feedback/feedback-field.tsx +70 -0
  190. package/src/components/feedback/feedback-footer.tsx +21 -0
  191. package/src/components/feedback/feedback-panel.tsx +54 -0
  192. package/src/components/feedback/feedback-submit.tsx +74 -0
  193. package/src/components/feedback/feedback-title.tsx +34 -0
  194. package/src/components/feedback/feedback-trigger.tsx +20 -0
  195. package/src/components/feedback/feedback.context.tsx +73 -0
  196. package/src/components/feedback/feedback.tsx +135 -0
  197. package/src/components/feedback/feedback.variants.test.ts +185 -0
  198. package/src/components/feedback/feedback.variants.ts +128 -0
  199. package/src/components/feedback/index.ts +24 -0
  200. package/src/components/field/AGENTS.md +19 -0
  201. package/src/components/field/field-label.tsx +19 -5
  202. package/src/components/field/field.context.tsx +17 -0
  203. package/src/components/field/field.tsx +15 -2
  204. package/src/components/field/field.variants.test.ts +24 -0
  205. package/src/components/field/field.variants.ts +20 -0
  206. package/src/components/field/index.ts +1 -0
  207. package/src/components/input/AGENTS.md +5 -0
  208. package/src/components/input/input.variants.test.ts +32 -0
  209. package/src/components/input/input.variants.ts +13 -1
  210. package/src/components/item/AGENTS.md +92 -0
  211. package/src/components/item/index.ts +25 -0
  212. package/src/components/item/item-actions.tsx +28 -0
  213. package/src/components/item/item-content.tsx +15 -0
  214. package/src/components/item/item-description.tsx +12 -0
  215. package/src/components/item/item-footer.tsx +13 -0
  216. package/src/components/item/item-group.tsx +32 -0
  217. package/src/components/item/item-header.tsx +13 -0
  218. package/src/components/item/item-media.tsx +37 -0
  219. package/src/components/item/item-separator.tsx +13 -0
  220. package/src/components/item/item-title.tsx +17 -0
  221. package/src/components/item/item.context.tsx +66 -0
  222. package/src/components/item/item.tsx +218 -0
  223. package/src/components/item/item.types.ts +12 -0
  224. package/src/components/item/item.variants.test.ts +263 -0
  225. package/src/components/item/item.variants.ts +200 -0
  226. package/src/components/kpi/AGENTS.md +125 -0
  227. package/src/components/kpi/index.ts +45 -0
  228. package/src/components/kpi/kpi-action.tsx +12 -0
  229. package/src/components/kpi/kpi-content.tsx +35 -0
  230. package/src/components/kpi/kpi-footer.tsx +52 -0
  231. package/src/components/kpi/kpi-group.tsx +70 -0
  232. package/src/components/kpi/kpi-header.tsx +25 -0
  233. package/src/components/kpi/kpi-icon.tsx +28 -0
  234. package/src/components/kpi/kpi-sparkline.tsx +145 -0
  235. package/src/components/kpi/kpi-stat.tsx +20 -0
  236. package/src/components/kpi/kpi-title.tsx +19 -0
  237. package/src/components/kpi/kpi-trend.tsx +105 -0
  238. package/src/components/kpi/kpi-value.tsx +32 -0
  239. package/src/components/kpi/kpi.context.tsx +111 -0
  240. package/src/components/kpi/kpi.tsx +154 -0
  241. package/src/components/kpi/kpi.types.ts +9 -0
  242. package/src/components/kpi/kpi.variants.test.ts +428 -0
  243. package/src/components/kpi/kpi.variants.ts +347 -0
  244. package/src/components/label/AGENTS.md +89 -0
  245. package/src/components/label/index.ts +10 -0
  246. package/src/components/label/label.tsx +97 -0
  247. package/src/components/label/label.variants.test.ts +176 -0
  248. package/src/components/label/label.variants.ts +123 -0
  249. package/src/components/menu/AGENTS.md +176 -0
  250. package/src/components/menu/index.ts +44 -0
  251. package/src/components/menu/menu-background.tsx +17 -0
  252. package/src/components/menu/menu-checkbox-item.tsx +57 -0
  253. package/src/components/menu/menu-content.tsx +303 -0
  254. package/src/components/menu/menu-item.tsx +162 -0
  255. package/src/components/menu/menu-label.tsx +14 -0
  256. package/src/components/menu/menu-radio-group.tsx +50 -0
  257. package/src/components/menu/menu-radio-item.tsx +57 -0
  258. package/src/components/menu/menu-separator.tsx +11 -0
  259. package/src/components/menu/menu-sub-content.tsx +72 -0
  260. package/src/components/menu/menu-sub-trigger.tsx +50 -0
  261. package/src/components/menu/menu-sub.tsx +75 -0
  262. package/src/components/menu/menu-trigger.tsx +57 -0
  263. package/src/components/menu/menu.context.tsx +214 -0
  264. package/src/components/menu/menu.tsx +88 -0
  265. package/src/components/menu/menu.types.ts +33 -0
  266. package/src/components/menu/menu.variants.test.ts +400 -0
  267. package/src/components/menu/menu.variants.ts +353 -0
  268. package/src/components/meter/AGENTS.md +135 -0
  269. package/src/components/meter/index.ts +43 -0
  270. package/src/components/meter/meter-fill.tsx +19 -0
  271. package/src/components/meter/meter-header.tsx +17 -0
  272. package/src/components/meter/meter-label.tsx +18 -0
  273. package/src/components/meter/meter-output.tsx +42 -0
  274. package/src/components/meter/meter-segments.tsx +83 -0
  275. package/src/components/meter/meter-track.tsx +16 -0
  276. package/src/components/meter/meter.context.tsx +62 -0
  277. package/src/components/meter/meter.tsx +286 -0
  278. package/src/components/meter/meter.types.ts +30 -0
  279. package/src/components/meter/meter.variants.test.ts +430 -0
  280. package/src/components/meter/meter.variants.ts +347 -0
  281. package/src/components/overlay/AGENTS.md +89 -0
  282. package/src/components/overlay/index.ts +27 -0
  283. package/src/components/overlay/overlay-portal.tsx +84 -0
  284. package/src/components/overlay/overlay-presence.test.ts +46 -0
  285. package/src/components/overlay/overlay-presence.ts +49 -0
  286. package/src/components/overlay/overlay-registry.test.ts +96 -0
  287. package/src/components/overlay/overlay-registry.ts +99 -0
  288. package/src/components/overlay/overlay-scrim.tsx +49 -0
  289. package/src/components/overlay/overlay.context.tsx +48 -0
  290. package/src/components/overlay/overlay.tsx +118 -0
  291. package/src/components/overlay/overlay.variants.test.ts +37 -0
  292. package/src/components/overlay/overlay.variants.ts +33 -0
  293. package/src/components/overlay/use-overlay-back-handler.ts +34 -0
  294. package/src/components/overlay/use-overlay-presence.ts +87 -0
  295. package/src/components/popover/AGENTS.md +138 -0
  296. package/src/components/popover/index.ts +48 -0
  297. package/src/components/popover/popover-anchor.tsx +49 -0
  298. package/src/components/popover/popover-arrow.tsx +89 -0
  299. package/src/components/popover/popover-close.tsx +73 -0
  300. package/src/components/popover/popover-content.tsx +235 -0
  301. package/src/components/popover/popover-description.tsx +18 -0
  302. package/src/components/popover/popover-title.tsx +33 -0
  303. package/src/components/popover/popover-trigger.tsx +56 -0
  304. package/src/components/popover/popover.context.tsx +71 -0
  305. package/src/components/popover/popover.position.test.ts +354 -0
  306. package/src/components/popover/popover.position.ts +286 -0
  307. package/src/components/popover/popover.tsx +123 -0
  308. package/src/components/popover/popover.variants.test.ts +135 -0
  309. package/src/components/popover/popover.variants.ts +85 -0
  310. package/src/components/popover/use-anchor-measure.ts +76 -0
  311. package/src/components/popover/use-anchored-content.ts +219 -0
  312. package/src/components/pressable/AGENTS.md +11 -1
  313. package/src/components/pressable/pressable.tsx +7 -2
  314. package/src/components/pressable/pressable.variants.test.ts +29 -0
  315. package/src/components/pressable/pressable.variants.ts +17 -0
  316. package/src/components/progress/AGENTS.md +120 -0
  317. package/src/components/progress/index.ts +40 -0
  318. package/src/components/progress/progress-fill.tsx +152 -0
  319. package/src/components/progress/progress-header.tsx +19 -0
  320. package/src/components/progress/progress-label.tsx +27 -0
  321. package/src/components/progress/progress-output.tsx +47 -0
  322. package/src/components/progress/progress-track.tsx +38 -0
  323. package/src/components/progress/progress.context.tsx +79 -0
  324. package/src/components/progress/progress.tsx +171 -0
  325. package/src/components/progress/progress.types.ts +23 -0
  326. package/src/components/progress/progress.variants.test.ts +335 -0
  327. package/src/components/progress/progress.variants.ts +284 -0
  328. package/src/components/progress-button/AGENTS.md +116 -0
  329. package/src/components/progress-button/index.ts +36 -0
  330. package/src/components/progress-button/progress-button-done.tsx +33 -0
  331. package/src/components/progress-button/progress-button-label.tsx +27 -0
  332. package/src/components/progress-button/progress-button.context.tsx +93 -0
  333. package/src/components/progress-button/progress-button.tsx +414 -0
  334. package/src/components/progress-button/progress-button.variants.test.ts +314 -0
  335. package/src/components/progress-button/progress-button.variants.ts +236 -0
  336. package/src/components/provider/AGENTS.md +38 -25
  337. package/src/components/provider/provider.tsx +38 -11
  338. package/src/components/radio/AGENTS.md +8 -10
  339. package/src/components/radio/radio.variants.test.ts +5 -6
  340. package/src/components/radio/radio.variants.ts +6 -7
  341. package/src/components/rating/AGENTS.md +143 -0
  342. package/src/components/rating/index.ts +43 -0
  343. package/src/components/rating/rating-output.tsx +39 -0
  344. package/src/components/rating/rating-star.tsx +70 -0
  345. package/src/components/rating/rating-stars.tsx +219 -0
  346. package/src/components/rating/rating.context.tsx +79 -0
  347. package/src/components/rating/rating.tsx +163 -0
  348. package/src/components/rating/rating.variants.test.ts +347 -0
  349. package/src/components/rating/rating.variants.ts +354 -0
  350. package/src/components/selection-mode/AGENTS.md +155 -0
  351. package/src/components/selection-mode/index.ts +43 -0
  352. package/src/components/selection-mode/selection-mode-action.tsx +65 -0
  353. package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
  354. package/src/components/selection-mode/selection-mode-group.tsx +123 -0
  355. package/src/components/selection-mode/selection-mode-header.tsx +85 -0
  356. package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
  357. package/src/components/selection-mode/selection-mode-item.tsx +182 -0
  358. package/src/components/selection-mode/selection-mode.context.tsx +89 -0
  359. package/src/components/selection-mode/selection-mode.tsx +207 -0
  360. package/src/components/selection-mode/selection-mode.types.ts +36 -0
  361. package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
  362. package/src/components/selection-mode/selection-mode.variants.ts +329 -0
  363. package/src/components/skeleton/AGENTS.md +120 -0
  364. package/src/components/skeleton/index.ts +15 -0
  365. package/src/components/skeleton/skeleton-clock.ts +48 -0
  366. package/src/components/skeleton/skeleton-group.tsx +64 -0
  367. package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
  368. package/src/components/skeleton/skeleton.context.tsx +45 -0
  369. package/src/components/skeleton/skeleton.tsx +227 -0
  370. package/src/components/skeleton/skeleton.variants.test.ts +245 -0
  371. package/src/components/skeleton/skeleton.variants.ts +222 -0
  372. package/src/components/slide-button/AGENTS.md +122 -0
  373. package/src/components/slide-button/index.ts +43 -0
  374. package/src/components/slide-button/slide-button-label.tsx +70 -0
  375. package/src/components/slide-button/slide-button-thumb.tsx +93 -0
  376. package/src/components/slide-button/slide-button.context.tsx +76 -0
  377. package/src/components/slide-button/slide-button.tsx +457 -0
  378. package/src/components/slide-button/slide-button.variants.test.ts +288 -0
  379. package/src/components/slide-button/slide-button.variants.ts +324 -0
  380. package/src/components/slider/index.ts +2 -0
  381. package/src/components/slider/slider-thumb.tsx +33 -1
  382. package/src/components/slider/slider.variants.test.ts +52 -0
  383. package/src/components/slider/slider.variants.ts +56 -2
  384. package/src/components/stack-card/AGENTS.md +169 -0
  385. package/src/components/stack-card/index.ts +37 -0
  386. package/src/components/stack-card/stack-card-action.tsx +66 -0
  387. package/src/components/stack-card/stack-card-actions.tsx +18 -0
  388. package/src/components/stack-card/stack-card-card.tsx +71 -0
  389. package/src/components/stack-card/stack-card-empty.tsx +30 -0
  390. package/src/components/stack-card/stack-card-slot.tsx +90 -0
  391. package/src/components/stack-card/stack-card-stamp.tsx +76 -0
  392. package/src/components/stack-card/stack-card.context.tsx +117 -0
  393. package/src/components/stack-card/stack-card.tsx +557 -0
  394. package/src/components/stack-card/stack-card.types.ts +26 -0
  395. package/src/components/stack-card/stack-card.variants.test.ts +355 -0
  396. package/src/components/stack-card/stack-card.variants.ts +360 -0
  397. package/src/components/steps/AGENTS.md +153 -0
  398. package/src/components/steps/index.ts +45 -0
  399. package/src/components/steps/steps-connector.tsx +31 -0
  400. package/src/components/steps/steps-description.tsx +26 -0
  401. package/src/components/steps/steps-indicator.tsx +106 -0
  402. package/src/components/steps/steps-item.tsx +225 -0
  403. package/src/components/steps/steps-panel.tsx +41 -0
  404. package/src/components/steps/steps-title.tsx +32 -0
  405. package/src/components/steps/steps-track.tsx +59 -0
  406. package/src/components/steps/steps.context.tsx +123 -0
  407. package/src/components/steps/steps.tsx +166 -0
  408. package/src/components/steps/steps.variants.test.ts +388 -0
  409. package/src/components/steps/steps.variants.ts +468 -0
  410. package/src/components/surface/AGENTS.md +82 -0
  411. package/src/components/surface/index.ts +15 -0
  412. package/src/components/surface/surface.context.tsx +56 -0
  413. package/src/components/surface/surface.tsx +87 -0
  414. package/src/components/surface/surface.variants.test.ts +227 -0
  415. package/src/components/surface/surface.variants.ts +165 -0
  416. package/src/components/swipe/AGENTS.md +140 -0
  417. package/src/components/swipe/index.ts +31 -0
  418. package/src/components/swipe/swipe-action.tsx +99 -0
  419. package/src/components/swipe/swipe-end.tsx +14 -0
  420. package/src/components/swipe/swipe-group.tsx +55 -0
  421. package/src/components/swipe/swipe-panel.tsx +55 -0
  422. package/src/components/swipe/swipe-start.tsx +15 -0
  423. package/src/components/swipe/swipe.context.tsx +135 -0
  424. package/src/components/swipe/swipe.tsx +393 -0
  425. package/src/components/swipe/swipe.types.ts +7 -0
  426. package/src/components/swipe/swipe.variants.test.ts +337 -0
  427. package/src/components/swipe/swipe.variants.ts +290 -0
  428. package/src/components/switch/switch.variants.ts +2 -2
  429. package/src/components/tabs/AGENTS.md +4 -3
  430. package/src/components/tabs/tabs.variants.test.ts +1 -1
  431. package/src/components/tabs/tabs.variants.ts +4 -6
  432. package/src/components/text/AGENTS.md +8 -2
  433. package/src/components/text/text.tsx +7 -0
  434. package/src/components/text/text.variants.test.ts +24 -1
  435. package/src/components/text/text.variants.ts +5 -0
  436. package/src/components/textarea/AGENTS.md +89 -0
  437. package/src/components/textarea/index.ts +16 -0
  438. package/src/components/textarea/textarea.tsx +157 -0
  439. package/src/components/textarea/textarea.variants.test.ts +219 -0
  440. package/src/components/textarea/textarea.variants.ts +171 -0
  441. package/src/components/toast/AGENTS.md +140 -0
  442. package/src/components/toast/index.ts +74 -0
  443. package/src/components/toast/toast-action.tsx +59 -0
  444. package/src/components/toast/toast-api.ts +32 -0
  445. package/src/components/toast/toast-close.tsx +44 -0
  446. package/src/components/toast/toast-content.tsx +11 -0
  447. package/src/components/toast/toast-description.tsx +11 -0
  448. package/src/components/toast/toast-indicator.tsx +34 -0
  449. package/src/components/toast/toast-item.tsx +308 -0
  450. package/src/components/toast/toast-title.tsx +13 -0
  451. package/src/components/toast/toast-viewport.tsx +135 -0
  452. package/src/components/toast/toast.context.tsx +69 -0
  453. package/src/components/toast/toast.store.test.ts +259 -0
  454. package/src/components/toast/toast.store.ts +338 -0
  455. package/src/components/toast/toast.timer.test.ts +64 -0
  456. package/src/components/toast/toast.timer.ts +51 -0
  457. package/src/components/toast/toast.tsx +132 -0
  458. package/src/components/toast/toast.variants.test.ts +393 -0
  459. package/src/components/toast/toast.variants.ts +304 -0
  460. package/src/components/toggle-button/AGENTS.md +106 -0
  461. package/src/components/toggle-button/index.ts +30 -0
  462. package/src/components/toggle-button/toggle-button-group.tsx +156 -0
  463. package/src/components/toggle-button/toggle-button-label.tsx +19 -0
  464. package/src/components/toggle-button/toggle-button.context.tsx +130 -0
  465. package/src/components/toggle-button/toggle-button.tsx +172 -0
  466. package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
  467. package/src/components/toggle-button/toggle-button.variants.ts +160 -0
  468. package/src/components/tooltip/AGENTS.md +133 -0
  469. package/src/components/tooltip/index.ts +28 -0
  470. package/src/components/tooltip/tooltip-arrow.tsx +40 -0
  471. package/src/components/tooltip/tooltip-content.tsx +179 -0
  472. package/src/components/tooltip/tooltip-description.tsx +19 -0
  473. package/src/components/tooltip/tooltip-text.tsx +21 -0
  474. package/src/components/tooltip/tooltip-title.tsx +20 -0
  475. package/src/components/tooltip/tooltip-trigger.tsx +103 -0
  476. package/src/components/tooltip/tooltip.context.tsx +68 -0
  477. package/src/components/tooltip/tooltip.tsx +213 -0
  478. package/src/components/tooltip/tooltip.variants.test.ts +231 -0
  479. package/src/components/tooltip/tooltip.variants.ts +177 -0
  480. package/src/hooks/use-calm-motion.tsx +31 -0
  481. package/src/lib/calm-motion.test.ts +17 -0
  482. package/src/lib/calm-motion.ts +23 -0
  483. package/src/lib/cn.parity.test.ts +62 -0
  484. package/src/lib/cn.test.ts +14 -0
  485. package/src/lib/cn.ts +11 -6
  486. package/src/no-gorhom.test.ts +54 -0
  487. package/src/styles/geometry.test.ts +1 -0
  488. package/src/styles/theme.css +4 -0
  489. package/src/styles/tokens.css +44 -0
  490. package/src/styles/tokens.test.ts +56 -0
  491. package/src/styles/tokens.ts +29 -1
  492. package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
  493. package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
@@ -0,0 +1,360 @@
1
+ import { Children, type ReactNode } from "react";
2
+ import type { VariantProps } from "tailwind-variants";
3
+ import { tv } from "../../lib/tv";
4
+
5
+ /** The four ways a card can leave the pile. */
6
+ export const STACK_CARD_DIRECTIONS = ["left", "right", "up", "down"] as const;
7
+
8
+ /**
9
+ * How the cards behind the top one are drawn. `stack` steps each one down and
10
+ * smaller, `fan` rotates them alternately about the bottom centre, `flat` hides
11
+ * them so only the top card shows.
12
+ */
13
+ export const STACK_CARD_LAYOUTS = ["stack", "fan", "flat"] as const;
14
+
15
+ /** The state colours a stamp may take — each a token declared in both themes. */
16
+ export const STACK_CARD_STAMP_COLORS = ["primary", "success", "warning", "info", "destructive"] as const;
17
+
18
+ export type StackCardDirection = (typeof STACK_CARD_DIRECTIONS)[number];
19
+ export type StackCardLayout = (typeof STACK_CARD_LAYOUTS)[number];
20
+ export type StackCardStampColor = (typeof STACK_CARD_STAMP_COLORS)[number];
21
+
22
+ /** The directions a deck allows when it names none: a horizontal yes-or-no. */
23
+ export const STACK_CARD_DEFAULT_DIRECTIONS: readonly StackCardDirection[] = ["left", "right"];
24
+
25
+ /** What a screen reader calls each direction when the caller names none. */
26
+ export const STACK_CARD_DEFAULT_DIRECTION_LABELS: Record<StackCardDirection, string> = {
27
+ down: "Swipe down",
28
+ left: "Swipe left",
29
+ right: "Swipe right",
30
+ up: "Swipe up",
31
+ };
32
+
33
+ /** The fraction of the card's size a drag must cover to throw it. */
34
+ export const STACK_CARD_DEFAULT_THRESHOLD = 0.3;
35
+
36
+ /** Degrees of tilt a card takes for every card-width it is dragged. */
37
+ export const STACK_CARD_DRAG_ROTATION = 12;
38
+
39
+ /** The tilt each stamp carries, by the direction it answers for. */
40
+ export const STACK_CARD_STAMP_ROTATION: Record<StackCardDirection, number> = {
41
+ down: 0,
42
+ left: 12,
43
+ right: -12,
44
+ up: 0,
45
+ };
46
+
47
+ /**
48
+ * One slotted `tv()` for every part.
49
+ *
50
+ * `card` is a card's look — `bg-card border border-border rounded-lg`, the
51
+ * package's card-shaped surface (see the package AGENTS.md on `--radius`) —
52
+ * absolutely filling the pile, so every card in the deck occupies one box and a
53
+ * transform is the only thing that tells them apart. `stamp` is placed on the
54
+ * side it answers for: a right-hand "yes" sits top-start, where a card tilting
55
+ * right lifts it into view. The text colour lives on `stampLabel`, never on the
56
+ * stamp (rule 1).
57
+ *
58
+ * Free of React Native imports so it stays unit-testable. See AGENTS.md.
59
+ */
60
+ export const stackCardVariants = tv({
61
+ slots: {
62
+ root: "gap-4",
63
+ pile: "relative flex-1",
64
+ card: "absolute inset-0 overflow-hidden rounded-lg border border-border bg-card",
65
+ stamp: "absolute rounded-md border-4 px-3 py-1",
66
+ stampLabel: "font-bold text-2xl uppercase tracking-widest",
67
+ empty: "absolute inset-0 items-center justify-center",
68
+ actions: "flex-row items-center justify-center gap-4",
69
+ },
70
+ variants: {
71
+ color: {
72
+ primary: { stamp: "border-primary", stampLabel: "text-primary" },
73
+ success: { stamp: "border-success", stampLabel: "text-success" },
74
+ warning: { stamp: "border-warning", stampLabel: "text-warning" },
75
+ info: { stamp: "border-info", stampLabel: "text-info" },
76
+ destructive: { stamp: "border-destructive", stampLabel: "text-destructive" },
77
+ },
78
+ direction: {
79
+ right: { stamp: "top-8 left-6" },
80
+ left: { stamp: "top-8 right-6" },
81
+ up: { stamp: "bottom-10 self-center" },
82
+ down: { stamp: "top-10 self-center" },
83
+ },
84
+ },
85
+ defaultVariants: {
86
+ color: "primary",
87
+ direction: "right",
88
+ },
89
+ });
90
+
91
+ export type StackCardVariantProps = VariantProps<typeof stackCardVariants>;
92
+
93
+ /** What `partitionStackChildren` sorts a child into. */
94
+ export type StackCardChildKind = "stamp" | "card" | "empty" | "actions";
95
+
96
+ /**
97
+ * Sorts the root's children by part: stamps, cards in order, the empty state
98
+ * and the actions row.
99
+ *
100
+ * **Anything that is not a stamp, an empty state or an actions row is a card.**
101
+ * A caller who wraps `StackCard.Card` in a component of their own still gets a
102
+ * card, rather than a child silently dropped because its type is not the part's.
103
+ * The first `Empty` and the first `Actions` win; a second of either is ignored.
104
+ *
105
+ * `kindOf` is passed in rather than imported so this stays free of the parts it
106
+ * looks for, and of React Native with them.
107
+ */
108
+ export function partitionStackChildren(
109
+ children: ReactNode,
110
+ kindOf: (node: ReactNode) => StackCardChildKind
111
+ ): { stamps: ReactNode[]; cards: ReactNode[]; empty: ReactNode | null; actions: ReactNode | null } {
112
+ const stamps: ReactNode[] = [];
113
+ const cards: ReactNode[] = [];
114
+ let empty: ReactNode | null = null;
115
+ let actions: ReactNode | null = null;
116
+
117
+ for (const child of Children.toArray(children)) {
118
+ const kind = kindOf(child);
119
+ if (kind === "stamp") stamps.push(child);
120
+ else if (kind === "empty") empty ??= child;
121
+ else if (kind === "actions") actions ??= child;
122
+ else cards.push(child);
123
+ }
124
+
125
+ return { actions, cards, empty, stamps };
126
+ }
127
+
128
+ /**
129
+ * Where a released card goes: a direction to throw it, or `null` to spring back.
130
+ *
131
+ * Momentum counts — the position is projected 0.15 s along the release velocity —
132
+ * so a flick throws from a short drag and a flick back toward the centre cancels
133
+ * a long one. Each axis is measured as a fraction of the card's own size along
134
+ * it, the larger fraction is the dominant axis, and that axis alone decides.
135
+ * A direction the deck does not allow gives `null`, as does a card not yet
136
+ * measured.
137
+ *
138
+ * A worklet, self-contained, so the pan's `onEnd` calls it on the UI thread.
139
+ */
140
+ export function resolveStackRelease({
141
+ x,
142
+ y,
143
+ vx,
144
+ vy,
145
+ width,
146
+ height,
147
+ threshold,
148
+ directions,
149
+ }: {
150
+ x: number;
151
+ y: number;
152
+ vx: number;
153
+ vy: number;
154
+ width: number;
155
+ height: number;
156
+ threshold: number;
157
+ directions: readonly StackCardDirection[];
158
+ }): StackCardDirection | null {
159
+ "worklet";
160
+ if (width <= 0 || height <= 0) return null;
161
+ const px = x + vx * 0.15;
162
+ const py = y + vy * 0.15;
163
+ const fx = Math.abs(px) / width;
164
+ const fy = Math.abs(py) / height;
165
+ const isHorizontal = fx >= fy;
166
+ const fraction = isHorizontal ? fx : fy;
167
+ if (fraction < threshold) return null;
168
+ const direction: StackCardDirection = isHorizontal ? (px > 0 ? "right" : "left") : py > 0 ? "down" : "up";
169
+ return directions.includes(direction) ? direction : null;
170
+ }
171
+
172
+ /**
173
+ * The card's offset for a finger's translation.
174
+ *
175
+ * Toward an allowed direction it follows one to one. **Toward a disallowed one
176
+ * it still gives a quarter**, and springs back on release: a card that does not
177
+ * budge at all reads as frozen, where one that gives a little and returns reads
178
+ * as "not that way".
179
+ *
180
+ * A worklet, self-contained, so the pan's `onUpdate` calls it on the UI thread.
181
+ */
182
+ export function resolveDragOffset({
183
+ translationX,
184
+ translationY,
185
+ directions,
186
+ }: {
187
+ translationX: number;
188
+ translationY: number;
189
+ directions: readonly StackCardDirection[];
190
+ }): { x: number; y: number } {
191
+ "worklet";
192
+ const xDirection: StackCardDirection = translationX > 0 ? "right" : "left";
193
+ const yDirection: StackCardDirection = translationY > 0 ? "down" : "up";
194
+ const x = directions.includes(xDirection) ? translationX : translationX * 0.25;
195
+ const y = directions.includes(yDirection) ? translationY : translationY * 0.25;
196
+ return { x, y };
197
+ }
198
+
199
+ /**
200
+ * How far the top card is toward leaving, 0 at rest and 1 at the threshold.
201
+ *
202
+ * The furthest axis, as a fraction of the card's size along it, clamped. The
203
+ * cards behind interpolate on this, which is what puts the next card exactly in
204
+ * place by the time a release can throw the top one.
205
+ *
206
+ * A worklet, self-contained.
207
+ */
208
+ export function resolveDragProgress({
209
+ x,
210
+ y,
211
+ width,
212
+ height,
213
+ threshold,
214
+ }: {
215
+ x: number;
216
+ y: number;
217
+ width: number;
218
+ height: number;
219
+ threshold: number;
220
+ }): number {
221
+ "worklet";
222
+ if (width <= 0 || height <= 0 || threshold <= 0) return 0;
223
+ const progress = Math.max(Math.abs(x) / (width * threshold), Math.abs(y) / (height * threshold));
224
+ return Math.min(1, progress);
225
+ }
226
+
227
+ /**
228
+ * A stamp's opacity: its progress toward its own direction, 0..1.
229
+ *
230
+ * Reaches 1 at the threshold, so a stamp at full strength means "let go and this
231
+ * is the answer". A stamp stays dark while the other axis is the dominant one,
232
+ * so a diagonal drag in a four-way deck shows one answer, not two — the same
233
+ * rule {@link resolveStackRelease} decides by.
234
+ *
235
+ * A worklet, self-contained.
236
+ */
237
+ export function resolveStampOpacity({
238
+ x,
239
+ y,
240
+ width,
241
+ height,
242
+ direction,
243
+ threshold = 0.3,
244
+ }: {
245
+ x: number;
246
+ y: number;
247
+ width: number;
248
+ height: number;
249
+ direction: StackCardDirection;
250
+ threshold?: number;
251
+ }): number {
252
+ "worklet";
253
+ if (width <= 0 || height <= 0 || threshold <= 0) return 0;
254
+ const fx = x / width;
255
+ const fy = y / height;
256
+ const isHorizontal = direction === "left" || direction === "right";
257
+ if (isHorizontal ? Math.abs(fy) > Math.abs(fx) : Math.abs(fx) > Math.abs(fy)) return 0;
258
+ const along = direction === "right" ? fx : direction === "left" ? -fx : direction === "down" ? fy : -fy;
259
+ return Math.min(1, Math.max(0, along / threshold));
260
+ }
261
+
262
+ /** A card's resting transform — rotation in degrees. */
263
+ export type StackCardTransform = { translateY: number; scale: number; rotate: number; opacity: number };
264
+
265
+ /**
266
+ * Where a card behind the top one sits, interpolated toward the slot ahead of it.
267
+ *
268
+ * `position` is how far behind the top it is (0 is the top), `progress` how far
269
+ * the top card is toward leaving. At `progress` 1 a card is exactly where the one
270
+ * ahead of it was at 0, so when the top card goes the next one is already in
271
+ * place and advancing moves nothing.
272
+ *
273
+ * - `stack`: `translateY = 8·i`, `scale = 1 − 0.04·i`.
274
+ * - `fan`: `rotate = ±3°·i`, alternating sign, about the bottom centre.
275
+ * - `flat`: every card behind is hidden.
276
+ *
277
+ * Positions `1..depth` are opaque; the one at `depth + 1` waits hidden and fades
278
+ * in as it moves up, so the deck never pops a card into view.
279
+ *
280
+ * A worklet, self-contained.
281
+ */
282
+ export function resolveBehindTransform({
283
+ layout,
284
+ position,
285
+ progress,
286
+ depth,
287
+ }: {
288
+ layout: StackCardLayout;
289
+ position: number;
290
+ progress: number;
291
+ depth: number;
292
+ }): StackCardTransform {
293
+ "worklet";
294
+ const t = Math.min(1, Math.max(0, progress));
295
+ const at = (p: number): StackCardTransform => {
296
+ if (p <= 0) return { opacity: 1, rotate: 0, scale: 1, translateY: 0 };
297
+ const opacity = layout === "flat" || p > depth ? 0 : 1;
298
+ if (layout === "stack") return { opacity, rotate: 0, scale: 1 - 0.04 * p, translateY: 8 * p };
299
+ if (layout === "fan") return { opacity, rotate: (p % 2 === 1 ? 3 : -3) * p, scale: 1, translateY: 0 };
300
+ return { opacity, rotate: 0, scale: 1, translateY: 0 };
301
+ };
302
+ const from = at(position);
303
+ if (t === 0) return from;
304
+ const to = at(position - 1);
305
+ return {
306
+ opacity: from.opacity + (to.opacity - from.opacity) * t,
307
+ rotate: from.rotate + (to.rotate - from.rotate) * t,
308
+ scale: from.scale + (to.scale - from.scale) * t,
309
+ translateY: from.translateY + (to.translateY - from.translateY) * t,
310
+ };
311
+ }
312
+
313
+ /**
314
+ * Where a thrown card ends up: one and a half of its own size off in `direction`,
315
+ * which clears the pile at any tilt.
316
+ *
317
+ * A worklet, self-contained — the throw, an undo's fly-in and a declined card's
318
+ * return all start or end here.
319
+ */
320
+ export function resolveExitOffset({
321
+ direction,
322
+ width,
323
+ height,
324
+ }: {
325
+ direction: StackCardDirection;
326
+ width: number;
327
+ height: number;
328
+ }): { x: number; y: number } {
329
+ "worklet";
330
+ if (direction === "right") return { x: width * 1.5, y: 0 };
331
+ if (direction === "left") return { x: -width * 1.5, y: 0 };
332
+ if (direction === "down") return { x: 0, y: height * 1.5 };
333
+ return { x: 0, y: -height * 1.5 };
334
+ }
335
+
336
+ /**
337
+ * The cards actually mounted, as `[from, to)`: one behind the top for undo, the
338
+ * top, the visible depth, and one beyond it so the next card can fade in.
339
+ *
340
+ * A deck of 500 therefore mounts what a deck of five does.
341
+ */
342
+ export function resolveMountedWindow({
343
+ index,
344
+ count,
345
+ depth,
346
+ }: {
347
+ index: number;
348
+ count: number;
349
+ depth: number;
350
+ }): [number, number] {
351
+ const to = Math.max(0, Math.min(count, index + depth + 2));
352
+ const from = Math.max(0, Math.min(index - 1, to));
353
+ return [from, to];
354
+ }
355
+
356
+ /** The number of cards drawn behind the top: 2 by default, a whole number in 0..4. */
357
+ export function resolveStackDepth(depth?: number): number {
358
+ if (depth === undefined || Number.isNaN(depth)) return 2;
359
+ return Math.min(4, Math.max(0, Math.round(depth)));
360
+ }
@@ -0,0 +1,153 @@
1
+ # Steps
2
+
3
+ A stepper for a multi-step flow. Compound root plus `Steps.Item`, `Steps.Indicator`,
4
+ `Steps.Title`, `Steps.Description` and `Steps.Panel`; the connectors between steps are
5
+ internal and drawn by the items themselves.
6
+
7
+ `import { Steps } from "@delacour/react-native-ui/steps";`
8
+
9
+ ## Files
10
+
11
+ | File | What it holds |
12
+ | --- | --- |
13
+ | `index.ts` | → `@delacour/react-native-ui/steps` |
14
+ | `steps.tsx` | Root — the value, the item count, and the `Object.assign` |
15
+ | `steps-item.tsx` | `Steps.Item` — the outer box, the trigger (a `Pressable` or a `View`), and `splitChildren` |
16
+ | `steps-track.tsx` | The track `[half] [circle] [half]`, and the rail beside a vertical panel — internal |
17
+ | `steps-panel.tsx` | `Steps.Panel` — content outside the tap target |
18
+ | `steps-indicator.tsx` | `Steps.Indicator` — the circle and its number, check, cross or spinner |
19
+ | `steps-title.tsx` | `Steps.Title` |
20
+ | `steps-description.tsx` | `Steps.Description` |
21
+ | `steps-connector.tsx` | One half of the line between two steps — internal |
22
+ | `steps.context.tsx` | The stepper's and the step's contexts, with their hooks |
23
+ | `steps.variants.ts` | Pure `tv()` slots + every resolver, no RN imports |
24
+ | `steps.variants.test.ts` | |
25
+
26
+ ## Design
27
+
28
+ - **Variants**: `primary` (a completed step is solid, the current one a ring, the
29
+ steps ahead hollow) and `secondary` (every indicator filled — completed soft,
30
+ current solid, upcoming muted). **Sizes**: `sm`, `md`, `lg` — the circle's edge
31
+ (24/32/40), the number's type step, the glyph's icon step, the title's `Text`
32
+ step and the gaps, on one axis. **Orientations**: `horizontal` (titles centred
33
+ under the circles, equal-width columns) and `vertical` (titles beside, the line
34
+ running down).
35
+ - **Three statuses, off one number.** `resolveStepStatus` reads the step's index
36
+ against the zero-based `value`: before it `completed`, at it `current`, after it
37
+ `upcoming`. A step's own `completed` overrides that — `true` wins everywhere
38
+ (the last step of a finished flow), `false` un-completes a passed step (a
39
+ skipped optional one) but never demotes the current step, which would leave a
40
+ stepper with nowhere the user is. `isCurrent` is kept beside the status for the
41
+ same reason: a completed current step is still where the user is, and it still
42
+ announces `selected`.
43
+ - **Invalid and loading are flags, not statuses.** A step can be completed *and*
44
+ invalid (the payment failed on review), or current *and* loading. Folding them
45
+ into the status would make the status a list of every pair.
46
+ `resolveIndicatorGlyph` ranks what the circle draws: spinner over cross over
47
+ check over number — loading outranks invalid because a failed step being
48
+ retried is retrying.
49
+
50
+ ## The connectors are laid out, not measured
51
+
52
+ - **The root counts its items and each item draws its own halves.** A horizontal
53
+ item is an equal `flex-1` column whose track is `[half] [circle] [half]`; the
54
+ line between two steps is one item's right half meeting the next item's left
55
+ half at the column boundary. The outer halves of the first and last steps are
56
+ transparent spacers, so every circle stays centred. A vertical item's track is
57
+ `[circle] [line]`, the line `flex-1` down whatever height the content gives it,
58
+ and the last step draws none. `resolveStepConnectors` is that whole decision.
59
+ Nothing calls `onLayout`, so a title that wraps to three lines moves nothing a
60
+ frame late — the failure a measured connector has on every first paint.
61
+ - **Both halves of one line fill off the value, never off a step's status.**
62
+ `isConnectorComplete(step, value)` is `step < value`. Each half lives in a
63
+ different item and can see only its own step's `completed` override, so a line
64
+ coloured by status could be half filled. The value is the one thing both ends
65
+ agree on.
66
+ - **The halves are square-ended.** Rounded ends meeting at the column boundary
67
+ pinch a two-point line visibly at every joint.
68
+ - **Counting walks the direct children**, the way [`Tabs`](../tabs/AGENTS.md)
69
+ walks its panels. A `.map()` is flattened by `Children.toArray` and counts; an
70
+ item behind a wrapper component does not, and the root warns in development.
71
+ With no count every item looks like the last and no line is drawn, which is the
72
+ visible failure the warning names. An item also warns when its `step` falls
73
+ outside the count — the usual cause is one-based indices.
74
+ - **A vertical title is padded down to sit level with its circle** — by half the
75
+ circle's edge less the title's line height. It is the one number derived from
76
+ another, and `steps.variants.test.ts` recomputes it from the slot classes, so a
77
+ retuned circle or title step fails the suite instead of drifting half a line.
78
+
79
+ ## Interaction
80
+
81
+ - **Read-only is inferred from the props React already has.** A controlled
82
+ `value` with no `onValueChange` cannot move — a press would be thrown away —
83
+ so the steps render as plain `View`s and are not announced as buttons.
84
+ `isReadOnly` states it outright either way. `resolveStepsReadOnly` is the rule.
85
+ - **`resolveStepInteraction` returns `static`, `pressable` or `blocked`.**
86
+ Blocked is still a `Pressable`, announced disabled: the step is disabled,
87
+ loading, or ahead of the value under `isLinear`, where the flow's own Continue
88
+ button is the only way forward and the steps behind stay open for going back.
89
+ A loading step is passed to `Pressable` as `busy` rather than `disabled`, so it
90
+ is announced busy, not dimmed.
91
+ - **Re-pressing the current step does not call `onValueChange`** —
92
+ `shouldEmitStep`, the rule `Radio.Group` keeps.
93
+ - **The fade lands on the circle and the content, never on the trigger.** The
94
+ trigger is `Pressable`'s own `Animated.View`, whose animated style writes `opacity`
95
+ every frame and silently overwrites an `opacity-50` class — see
96
+ [`Radio`](../radio/AGENTS.md). A root `isDisabled` wins over a step's own, the
97
+ radio group's ladder.
98
+
99
+ ## Accessibility
100
+
101
+ - **One element per step.** The trigger is `accessible`, so the circle, title and
102
+ description merge into one stop. Its name is the title (the text it holds) and
103
+ its `accessibilityValue.text` is `resolveStepAccessibilityValue` — "Step 2 of 3,
104
+ completed", with ", has an error" and ", loading" appended. VoiceOver reads it
105
+ as "Payment, Step 2 of 3, completed". The current step is `selected`.
106
+ - **The number does not scale with the OS text size.** The circle is a fixed
107
+ edge, so a scaled numeral would spill out of it, and the position it shows is
108
+ already in the accessibility value. The title and description scale as usual.
109
+ - **The connectors are hidden from assistive technology**, like `Separator` —
110
+ they restate the status every step already announces.
111
+
112
+ ## Colour
113
+
114
+ - **The number is a `Text` and the glyphs are SVG, and one test holds them
115
+ together.** The number takes its colour as a class from the `indicatorLabel`
116
+ slot (rule 1); the check, cross and spinner take theirs as a token through one
117
+ `IconDefaultsProvider`. `resolveIndicatorForeground` names the token and the
118
+ suite asserts, for every cell, that the slot emits `text-<that token>` and that
119
+ `theme.css` declares it — a drift would be a white numeral beside a black check.
120
+ - **Every colour cell is a compound, invalid last.** `tv` emits compounds after
121
+ plain variants, so a plain `isInvalid` branch would lose to the status cells —
122
+ `radioVariants`' reason.
123
+ - **The title follows the step; the description does not.** Upcoming titles are
124
+ muted, an invalid title is destructive. The description keeps `Text.Caption`'s
125
+ muted colour in every state: the circle and the title already say where the step
126
+ stands.
127
+ - **The check and cross land with a short `ZoomIn`**, keyed by glyph so a change
128
+ of glyph replays it. Reduce motion is left at Reanimated's `System`, which snaps
129
+ it in — the state is the glyph's presence, not its motion.
130
+
131
+ ## The panel
132
+
133
+ - **`Steps.Panel` exists because the trigger is one accessibility element.** A
134
+ form written inside the trigger was unreachable: `accessible` merges every
135
+ descendant into one VoiceOver stop, so its text field and its Continue button
136
+ could not be focused, and its button's tap gesture sat inside the step's own.
137
+ The item therefore splits its children — the indicator into the track, a panel
138
+ after the trigger, everything else into the text column — and a panel's
139
+ controls are ordinary elements that never move the stepper.
140
+ - **A vertical panel sits beside a rail as wide as the circle**, which carries the
141
+ trailing line on past it, so the panel reads as part of its step and the line
142
+ still reaches the next circle. The gap before the next step moves from under
143
+ the title to under the panel; the suite pins both halves of that move.
144
+ - **There is no "only while current".** A finished step's summary is as common a
145
+ panel as the current step's form, so the caller gates it on `value`.
146
+ - **`className` styles the item's outer box; every other prop reaches the
147
+ trigger.** `testID`, `onPress`, `haptic` and the accessibility props all belong
148
+ to the thing that is pressed and announced.
149
+
150
+ ## What it does not do
151
+
152
+ - **No `Steps.Separator`.** The connectors are derived from the count; exposing
153
+ them would reintroduce the dangling-line failure the count exists to prevent.
@@ -0,0 +1,45 @@
1
+ export { Steps, type StepsProps } from "./steps";
2
+ export {
3
+ type StepsContextValue,
4
+ type StepsItemContextValue,
5
+ StepsItemProvider,
6
+ StepsProvider,
7
+ useSteps,
8
+ useStepsContext,
9
+ useStepsItem,
10
+ useStepsItemContext,
11
+ } from "./steps.context";
12
+ export {
13
+ isConnectorComplete,
14
+ resolveIndicatorForeground,
15
+ resolveIndicatorGlyph,
16
+ resolveStepAccessibilityValue,
17
+ resolveStepConnectors,
18
+ resolveStepInteraction,
19
+ resolveStepStatus,
20
+ resolveStepsReadOnly,
21
+ STEP_STATUSES,
22
+ STEPS_DEFAULT_ORIENTATION,
23
+ STEPS_DEFAULT_SIZE,
24
+ STEPS_DEFAULT_VARIANT,
25
+ STEPS_DESCRIPTION_TEXT_SIZE,
26
+ STEPS_ORIENTATIONS,
27
+ STEPS_SIZES,
28
+ STEPS_TITLE_TEXT_SIZE,
29
+ STEPS_VARIANTS,
30
+ type StepConnectorSlot,
31
+ type StepIndicatorGlyph,
32
+ type StepInteraction,
33
+ type StepStatus,
34
+ type StepsOrientation,
35
+ type StepsSize,
36
+ type StepsVariant,
37
+ type StepsVariantProps,
38
+ shouldEmitStep,
39
+ stepsVariants,
40
+ } from "./steps.variants";
41
+ export type { StepsDescriptionProps } from "./steps-description";
42
+ export type { StepsIndicatorChildrenProps, StepsIndicatorProps } from "./steps-indicator";
43
+ export type { StepsItemProps } from "./steps-item";
44
+ export type { StepsPanelProps } from "./steps-panel";
45
+ export type { StepsTitleProps } from "./steps-title";
@@ -0,0 +1,31 @@
1
+ import type { ReactElement } from "react";
2
+ import { View } from "react-native";
3
+ import type { StepConnectorSlot, StepsOrientation } from "./steps.variants";
4
+ import { stepsVariants } from "./steps.variants";
5
+
6
+ export type StepsConnectorProps = {
7
+ slot: Exclude<StepConnectorSlot, "none">;
8
+ orientation: StepsOrientation;
9
+ isComplete: boolean;
10
+ };
11
+
12
+ /**
13
+ * One half of the line between two steps, or the invisible half that keeps an
14
+ * end indicator centred. Internal — nobody places one; see
15
+ * `resolveStepConnectors`.
16
+ *
17
+ * Hidden from assistive technology, like `Separator`: the line restates the
18
+ * status every step already announces.
19
+ */
20
+ export function StepsConnector({ slot, orientation, isComplete }: StepsConnectorProps): ReactElement {
21
+ const slots = stepsVariants({ isConnectorComplete: isComplete, orientation });
22
+
23
+ return (
24
+ <View
25
+ accessibilityElementsHidden
26
+ className={slot === "spacer" ? slots.spacer() : slots.connector()}
27
+ importantForAccessibility="no-hide-descendants"
28
+ />
29
+ );
30
+ }
31
+ StepsConnector.displayName = "DelacourUI.Steps.Connector";
@@ -0,0 +1,26 @@
1
+ import type { ReactElement } from "react";
2
+ import { Text, type TextPresetProps } from "../text";
3
+ import { useStepsItemPart } from "./steps.context";
4
+ import { STEPS_DESCRIPTION_TEXT_SIZE, stepsVariants } from "./steps.variants";
5
+
6
+ export type StepsDescriptionProps = TextPresetProps;
7
+
8
+ /**
9
+ * A line of supporting text under the title. Optional.
10
+ *
11
+ * Renders `Text.Caption` one size step below the title and keeps the preset's
12
+ * muted colour in every state — the indicator and the title already say where
13
+ * the step stands, and a third voice saying it would only be louder.
14
+ */
15
+ export function StepsDescription({ className, size, ...props }: StepsDescriptionProps): ReactElement {
16
+ const item = useStepsItemPart("Steps.Description");
17
+
18
+ return (
19
+ <Text.Caption
20
+ className={stepsVariants({ orientation: item.orientation }).description({ className })}
21
+ size={size ?? STEPS_DESCRIPTION_TEXT_SIZE[item.size]}
22
+ {...props}
23
+ />
24
+ );
25
+ }
26
+ StepsDescription.displayName = "DelacourUI.Steps.Description";