@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,166 @@
1
+ import { Children, isValidElement, type ReactElement, type ReactNode, useCallback, useMemo } from "react";
2
+ import { View, type ViewProps } from "react-native";
3
+ import { useControllableState } from "../../hooks/use-controllable-state";
4
+ import { type StepsContextValue, StepsProvider } from "./steps.context";
5
+ import {
6
+ resolveStepsReadOnly,
7
+ STEPS_DEFAULT_ORIENTATION,
8
+ STEPS_DEFAULT_SIZE,
9
+ STEPS_DEFAULT_VARIANT,
10
+ type StepsOrientation,
11
+ type StepsSize,
12
+ type StepsVariant,
13
+ shouldEmitStep,
14
+ stepsVariants,
15
+ } from "./steps.variants";
16
+ import { StepsDescription } from "./steps-description";
17
+ import { StepsIndicator } from "./steps-indicator";
18
+ import { StepsItem } from "./steps-item";
19
+ import { StepsPanel } from "./steps-panel";
20
+ import { StepsTitle } from "./steps-title";
21
+
22
+ export type StepsProps = ViewProps & {
23
+ /** The current step, zero-based. Controlled. */
24
+ value?: number;
25
+ /** The first current step while uncontrolled. */
26
+ defaultValue?: number;
27
+ /** Called with the pressed step. Never called for a re-press of the current one. */
28
+ onValueChange?: (step: number) => void;
29
+ /** `horizontal` runs the steps across with titles underneath; `vertical` stacks them. */
30
+ orientation?: StepsOrientation;
31
+ size?: StepsSize;
32
+ /** How the indicators are painted. */
33
+ variant?: StepsVariant;
34
+ /** Steps ahead of the value cannot be pressed — the flow's own button is the only way forward. */
35
+ isLinear?: boolean;
36
+ /**
37
+ * Steps render as plain views and take no presses. Defaults to true for a
38
+ * controlled `value` with no `onValueChange`, which is a progress display.
39
+ */
40
+ isReadOnly?: boolean;
41
+ /** Disables every step, overriding a step's own `isDisabled`. */
42
+ isDisabled?: boolean;
43
+ className?: string;
44
+ children?: ReactNode;
45
+ };
46
+
47
+ function StepsRoot({
48
+ value: valueProp,
49
+ defaultValue = 0,
50
+ onValueChange,
51
+ orientation = STEPS_DEFAULT_ORIENTATION,
52
+ size = STEPS_DEFAULT_SIZE,
53
+ variant = STEPS_DEFAULT_VARIANT,
54
+ isLinear = false,
55
+ isReadOnly,
56
+ isDisabled,
57
+ className,
58
+ children,
59
+ ...props
60
+ }: StepsProps): ReactElement {
61
+ const [value, setValue] = useControllableState<number>({
62
+ value: valueProp,
63
+ defaultValue,
64
+ onChange: onValueChange,
65
+ });
66
+
67
+ const select = useCallback(
68
+ (next: number) => {
69
+ if (shouldEmitStep(value, next)) setValue(next);
70
+ },
71
+ [setValue, value]
72
+ );
73
+
74
+ // Counted from the direct children, the way `Tabs` walks its panels: the
75
+ // count is what draws a line after every step but the last. A `.map()` is
76
+ // flattened by `Children.toArray` and counts; a step behind a wrapper does not.
77
+ const count = useMemo(
78
+ () => Children.toArray(children).filter((child) => isValidElement(child) && child.type === StepsItem).length,
79
+ [children]
80
+ );
81
+
82
+ if (process.env.NODE_ENV !== "production" && count === 0 && Children.count(children) > 0) {
83
+ console.warn(
84
+ "Steps: found no <Steps.Item> among the direct children, so no connectors can be drawn. " +
85
+ "Render the items directly, or from a .map(), rather than behind a wrapper component."
86
+ );
87
+ }
88
+
89
+ const resolvedIsReadOnly = resolveStepsReadOnly({
90
+ hasOnValueChange: onValueChange !== undefined,
91
+ isControlled: valueProp !== undefined,
92
+ isReadOnly,
93
+ });
94
+
95
+ const context = useMemo<StepsContextValue>(
96
+ () => ({
97
+ count,
98
+ isDisabled,
99
+ isLinear,
100
+ isReadOnly: resolvedIsReadOnly,
101
+ orientation,
102
+ select,
103
+ size,
104
+ value,
105
+ variant,
106
+ }),
107
+ [count, isDisabled, isLinear, resolvedIsReadOnly, orientation, select, size, value, variant]
108
+ );
109
+
110
+ return (
111
+ <StepsProvider value={context}>
112
+ <View className={stepsVariants({ orientation, size }).root({ className })} {...props}>
113
+ {children}
114
+ </View>
115
+ </StepsProvider>
116
+ );
117
+ }
118
+
119
+ /**
120
+ * A stepper for a multi-step flow: numbered indicators joined by a line, each
121
+ * step completed, current or upcoming against one zero-based `value`.
122
+ *
123
+ * **The connectors are drawn for you.** The root counts the `Steps.Item`s it
124
+ * holds and every one but the last draws a line to the next, filled once the
125
+ * value has passed it — so there is no separator to place, forget, or leave
126
+ * dangling after the last step.
127
+ *
128
+ * Steps take presses by default and move the value there. A controlled `value`
129
+ * with no `onValueChange` is read-only, a progress display whose steps are not
130
+ * announced as buttons; `isLinear` keeps the steps ahead of the value closed, so
131
+ * only the flow's own button moves it forward.
132
+ *
133
+ * Each step is one accessibility element named by its title, with a value such
134
+ * as "Step 2 of 3, completed".
135
+ *
136
+ * @example
137
+ * <Steps value={step} onValueChange={setStep}>
138
+ * <Steps.Item step={0}>Account</Steps.Item>
139
+ * <Steps.Item step={1}>Shipping</Steps.Item>
140
+ * <Steps.Item step={2}>Payment</Steps.Item>
141
+ * </Steps>
142
+ *
143
+ * @example
144
+ * <Steps orientation="vertical" defaultValue={1}>
145
+ * <Steps.Item step={0}>
146
+ * <Steps.Title>Order placed</Steps.Title>
147
+ * <Steps.Description>We have your order.</Steps.Description>
148
+ * </Steps.Item>
149
+ * <Steps.Item step={1} isLoading>
150
+ * <Steps.Title>Packing</Steps.Title>
151
+ * </Steps.Item>
152
+ * </Steps>
153
+ */
154
+ export const Steps = Object.assign(StepsRoot, {
155
+ /** One step — its indicator, connectors, title and description, as one tap target. */
156
+ Item: StepsItem,
157
+ /** The circle: number, check, cross or spinner. Composed in automatically. */
158
+ Indicator: StepsIndicator,
159
+ /** The step's name, and its accessible name. Bare text in an item becomes one. */
160
+ Title: StepsTitle,
161
+ /** An optional line of supporting text under the title. */
162
+ Description: StepsDescription,
163
+ /** Content below the title but outside the tap target — a form, a summary. */
164
+ Panel: StepsPanel,
165
+ displayName: "DelacourUI.Steps",
166
+ });
@@ -0,0 +1,388 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { TEXT_SIZES } from "../text/text.variants";
5
+ import {
6
+ isConnectorComplete,
7
+ resolveIndicatorForeground,
8
+ resolveIndicatorGlyph,
9
+ resolveStepAccessibilityValue,
10
+ resolveStepConnectors,
11
+ resolveStepInteraction,
12
+ resolveStepStatus,
13
+ resolveStepsReadOnly,
14
+ STEP_STATUSES,
15
+ STEPS_DEFAULT_ORIENTATION,
16
+ STEPS_DEFAULT_SIZE,
17
+ STEPS_DEFAULT_VARIANT,
18
+ STEPS_DESCRIPTION_TEXT_SIZE,
19
+ STEPS_ORIENTATIONS,
20
+ STEPS_SIZES,
21
+ STEPS_TITLE_TEXT_SIZE,
22
+ STEPS_VARIANTS,
23
+ shouldEmitStep,
24
+ stepsVariants,
25
+ } from "./steps.variants";
26
+
27
+ const TOKENS_CSS = readFileSync(join(import.meta.dirname, "../../styles/tokens.css"), "utf-8");
28
+ const THEME_CSS = readFileSync(join(import.meta.dirname, "../../styles/theme.css"), "utf-8");
29
+
30
+ /** Tailwind's own spacing scale, in points. `size-8` is thirty-two. */
31
+ const SPACING_STEP_PX = 4;
32
+
33
+ /** Tailwind v4's default line heights, in points — `tokens.css` overrides sizes, not leading. */
34
+ const LINE_HEIGHT_PX: Record<string, number> = { xs: 16, sm: 20, base: 24, lg: 28, xl: 28 };
35
+
36
+ /** The `text-*` utility each `Text` size step emits. */
37
+ const TEXT_UTILITY: Record<string, string> = { xs: "xs", sm: "sm", md: "base", lg: "lg", xl: "xl" };
38
+
39
+ /** A slot's class string, with `tv`'s empty-slot `undefined` flattened. */
40
+ function cls(value: string | undefined): string {
41
+ return value ?? "";
42
+ }
43
+
44
+ /** The step a `prefix-N` class sets — `size-8` with `size` yields 8. */
45
+ function step(value: string, prefix: string): number {
46
+ const found = value.match(new RegExp(`(?:^|\\s)${prefix}-(\\d+(?:\\.\\d+)?)(?:\\s|$)`))?.[1];
47
+ if (found === undefined) throw new Error(`no ${prefix}-* in "${value}"`);
48
+ return Number(found);
49
+ }
50
+
51
+ /** Every combination of the axes that paint an indicator, as `tv` props. */
52
+ function everyCell() {
53
+ const cells = [];
54
+ for (const variant of STEPS_VARIANTS) {
55
+ for (const status of STEP_STATUSES) {
56
+ for (const isInvalid of [false, true]) {
57
+ cells.push({ variant, status, isInvalid });
58
+ }
59
+ }
60
+ }
61
+ return cells;
62
+ }
63
+
64
+ describe("constants", () => {
65
+ test("the axes are the ones the docs name", () => {
66
+ expect(STEPS_VARIANTS).toEqual(["primary", "secondary"]);
67
+ expect(STEPS_SIZES).toEqual(["sm", "md", "lg"]);
68
+ expect(STEPS_ORIENTATIONS).toEqual(["horizontal", "vertical"]);
69
+ expect(STEP_STATUSES).toEqual(["completed", "current", "upcoming"]);
70
+ });
71
+
72
+ test("the named defaults match tv's defaultVariants", () => {
73
+ const fromDefaults = stepsVariants();
74
+ const named = stepsVariants({
75
+ orientation: STEPS_DEFAULT_ORIENTATION,
76
+ size: STEPS_DEFAULT_SIZE,
77
+ variant: STEPS_DEFAULT_VARIANT,
78
+ });
79
+ for (const slot of ["root", "item", "track", "indicator", "content", "title"] as const) {
80
+ expect(cls(fromDefaults[slot]())).toBe(cls(named[slot]()));
81
+ }
82
+ });
83
+
84
+ test("every text size a step hands its parts is one Text has", () => {
85
+ for (const size of STEPS_SIZES) {
86
+ expect(TEXT_SIZES).toContain(STEPS_TITLE_TEXT_SIZE[size]);
87
+ expect(TEXT_SIZES).toContain(STEPS_DESCRIPTION_TEXT_SIZE[size]);
88
+ }
89
+ });
90
+
91
+ test("tokens.css still defines the text steps the offset maths assumes", () => {
92
+ for (const size of STEPS_SIZES) {
93
+ expect(TOKENS_CSS).toContain(`--text-${TEXT_UTILITY[STEPS_TITLE_TEXT_SIZE[size]]}:`);
94
+ }
95
+ });
96
+ });
97
+
98
+ describe("resolveStepStatus", () => {
99
+ test("before the value is completed, at it is current, after it is upcoming", () => {
100
+ expect(resolveStepStatus({ step: 0, value: 1 })).toBe("completed");
101
+ expect(resolveStepStatus({ step: 1, value: 1 })).toBe("current");
102
+ expect(resolveStepStatus({ step: 2, value: 1 })).toBe("upcoming");
103
+ });
104
+
105
+ test("completed: true wins everywhere, including on the current step", () => {
106
+ expect(resolveStepStatus({ step: 3, value: 1, completed: true })).toBe("completed");
107
+ expect(resolveStepStatus({ step: 1, value: 1, completed: true })).toBe("completed");
108
+ });
109
+
110
+ test("completed: false un-completes a passed step but never demotes the current one", () => {
111
+ expect(resolveStepStatus({ step: 0, value: 2, completed: false })).toBe("upcoming");
112
+ expect(resolveStepStatus({ step: 2, value: 2, completed: false })).toBe("current");
113
+ });
114
+
115
+ test("a value past the end completes every step", () => {
116
+ for (const s of [0, 1, 2]) expect(resolveStepStatus({ step: s, value: 3 })).toBe("completed");
117
+ });
118
+ });
119
+
120
+ describe("isConnectorComplete", () => {
121
+ test("the line leaving a step fills once the value has moved past that step", () => {
122
+ expect(isConnectorComplete(0, 0)).toBe(false);
123
+ expect(isConnectorComplete(0, 1)).toBe(true);
124
+ expect(isConnectorComplete(1, 1)).toBe(false);
125
+ expect(isConnectorComplete(1, 3)).toBe(true);
126
+ });
127
+ });
128
+
129
+ describe("resolveStepConnectors", () => {
130
+ test("horizontal: every step draws both halves, the ends as spacers so indicators stay centred", () => {
131
+ expect(resolveStepConnectors({ step: 0, count: 3, orientation: "horizontal" })).toEqual({
132
+ leading: "spacer",
133
+ trailing: "line",
134
+ });
135
+ expect(resolveStepConnectors({ step: 1, count: 3, orientation: "horizontal" })).toEqual({
136
+ leading: "line",
137
+ trailing: "line",
138
+ });
139
+ expect(resolveStepConnectors({ step: 2, count: 3, orientation: "horizontal" })).toEqual({
140
+ leading: "line",
141
+ trailing: "spacer",
142
+ });
143
+ });
144
+
145
+ test("vertical: one trailing line per step, none after the last and none leading", () => {
146
+ expect(resolveStepConnectors({ step: 0, count: 3, orientation: "vertical" })).toEqual({
147
+ leading: "none",
148
+ trailing: "line",
149
+ });
150
+ expect(resolveStepConnectors({ step: 2, count: 3, orientation: "vertical" })).toEqual({
151
+ leading: "none",
152
+ trailing: "none",
153
+ });
154
+ });
155
+
156
+ test("a lone step draws no line in either orientation", () => {
157
+ expect(resolveStepConnectors({ step: 0, count: 1, orientation: "horizontal" })).toEqual({
158
+ leading: "spacer",
159
+ trailing: "spacer",
160
+ });
161
+ expect(resolveStepConnectors({ step: 0, count: 1, orientation: "vertical" })).toEqual({
162
+ leading: "none",
163
+ trailing: "none",
164
+ });
165
+ });
166
+ });
167
+
168
+ describe("resolveStepsReadOnly", () => {
169
+ test("a controlled value with no callback is read-only", () => {
170
+ expect(resolveStepsReadOnly({ isControlled: true, hasOnValueChange: false })).toBe(true);
171
+ });
172
+
173
+ test("uncontrolled, or controlled with a callback, is interactive", () => {
174
+ expect(resolveStepsReadOnly({ isControlled: false, hasOnValueChange: false })).toBe(false);
175
+ expect(resolveStepsReadOnly({ isControlled: true, hasOnValueChange: true })).toBe(false);
176
+ });
177
+
178
+ test("an explicit isReadOnly wins either way", () => {
179
+ expect(resolveStepsReadOnly({ isControlled: true, hasOnValueChange: true, isReadOnly: true })).toBe(true);
180
+ expect(resolveStepsReadOnly({ isControlled: true, hasOnValueChange: false, isReadOnly: false })).toBe(false);
181
+ });
182
+ });
183
+
184
+ describe("resolveStepInteraction", () => {
185
+ const base = { isReadOnly: false, isLinear: false, isDisabled: false, isLoading: false, step: 2, value: 1 };
186
+
187
+ test("read-only steps are static, whatever else is true", () => {
188
+ expect(resolveStepInteraction({ ...base, isReadOnly: true })).toBe("static");
189
+ expect(resolveStepInteraction({ ...base, isReadOnly: true, isDisabled: true })).toBe("static");
190
+ });
191
+
192
+ test("an interactive step is pressable", () => {
193
+ expect(resolveStepInteraction(base)).toBe("pressable");
194
+ });
195
+
196
+ test("disabled and loading steps are blocked", () => {
197
+ expect(resolveStepInteraction({ ...base, isDisabled: true })).toBe("blocked");
198
+ expect(resolveStepInteraction({ ...base, isLoading: true })).toBe("blocked");
199
+ });
200
+
201
+ test("linear blocks only the steps ahead of the value", () => {
202
+ expect(resolveStepInteraction({ ...base, isLinear: true, step: 2, value: 1 })).toBe("blocked");
203
+ expect(resolveStepInteraction({ ...base, isLinear: true, step: 1, value: 1 })).toBe("pressable");
204
+ expect(resolveStepInteraction({ ...base, isLinear: true, step: 0, value: 1 })).toBe("pressable");
205
+ });
206
+ });
207
+
208
+ describe("shouldEmitStep", () => {
209
+ test("re-pressing the current step is not a change", () => {
210
+ expect(shouldEmitStep(1, 1)).toBe(false);
211
+ expect(shouldEmitStep(1, 2)).toBe(true);
212
+ });
213
+ });
214
+
215
+ describe("resolveStepAccessibilityValue", () => {
216
+ test("announces position and status, one-based", () => {
217
+ expect(resolveStepAccessibilityValue({ step: 1, count: 3, status: "completed" })).toBe("Step 2 of 3, completed");
218
+ expect(resolveStepAccessibilityValue({ step: 0, count: 3, status: "current" })).toBe("Step 1 of 3, current");
219
+ expect(resolveStepAccessibilityValue({ step: 2, count: 3, status: "upcoming" })).toBe("Step 3 of 3, upcoming");
220
+ });
221
+
222
+ test("appends error and loading, in that order", () => {
223
+ expect(
224
+ resolveStepAccessibilityValue({ step: 0, count: 2, status: "current", isInvalid: true, isLoading: true })
225
+ ).toBe("Step 1 of 2, current, has an error, loading");
226
+ });
227
+
228
+ test("an unknown count drops the total rather than saying 'of 0'", () => {
229
+ expect(resolveStepAccessibilityValue({ step: 0, count: 0, status: "current" })).toBe("Step 1, current");
230
+ });
231
+ });
232
+
233
+ describe("resolveIndicatorGlyph", () => {
234
+ test("loading outranks invalid, invalid outranks completed, completed outranks the number", () => {
235
+ expect(resolveIndicatorGlyph({ step: 0, status: "completed", isInvalid: true, isLoading: true })).toEqual({
236
+ kind: "spinner",
237
+ });
238
+ expect(resolveIndicatorGlyph({ step: 0, status: "completed", isInvalid: true, isLoading: false })).toEqual({
239
+ kind: "alert",
240
+ });
241
+ expect(resolveIndicatorGlyph({ step: 0, status: "completed", isInvalid: false, isLoading: false })).toEqual({
242
+ kind: "check",
243
+ });
244
+ });
245
+
246
+ test("current and upcoming steps show their one-based number", () => {
247
+ expect(resolveIndicatorGlyph({ step: 0, status: "current", isInvalid: false, isLoading: false })).toEqual({
248
+ kind: "number",
249
+ label: "1",
250
+ });
251
+ expect(resolveIndicatorGlyph({ step: 4, status: "upcoming", isInvalid: false, isLoading: false })).toEqual({
252
+ kind: "number",
253
+ label: "5",
254
+ });
255
+ });
256
+ });
257
+
258
+ describe("stepsVariants", () => {
259
+ test("orientation lays the root and each item out along its own axis", () => {
260
+ expect(cls(stepsVariants({ orientation: "horizontal" }).root())).toMatch(/\bflex-row\b/);
261
+ expect(cls(stepsVariants({ orientation: "vertical" }).root())).toMatch(/\bflex-col\b/);
262
+ expect(cls(stepsVariants({ orientation: "horizontal" }).item())).toMatch(/\bflex-1\b/);
263
+ expect(cls(stepsVariants({ orientation: "horizontal" }).trigger())).toMatch(/\bitems-center\b/);
264
+ expect(cls(stepsVariants({ orientation: "vertical" }).trigger())).toMatch(/\bflex-row\b/);
265
+ });
266
+
267
+ test("horizontal titles centre under their indicator", () => {
268
+ expect(cls(stepsVariants({ orientation: "horizontal" }).content())).toMatch(/\bitems-center\b/);
269
+ expect(cls(stepsVariants({ orientation: "horizontal" }).title())).toMatch(/\btext-center\b/);
270
+ expect(cls(stepsVariants({ orientation: "vertical" }).title())).not.toMatch(/\btext-center\b/);
271
+ });
272
+
273
+ test("the connector is a line across the axis it joins", () => {
274
+ expect(cls(stepsVariants({ orientation: "horizontal" }).connector())).toMatch(/\bh-0\.5\b/);
275
+ expect(cls(stepsVariants({ orientation: "vertical" }).connector())).toMatch(/\bw-0\.5\b/);
276
+ });
277
+
278
+ test("a filled connector is primary, an empty one the border colour", () => {
279
+ expect(cls(stepsVariants({ isConnectorComplete: true }).connector())).toMatch(/\bbg-primary\b/);
280
+ expect(cls(stepsVariants({ isConnectorComplete: false }).connector())).toMatch(/\bbg-border\b/);
281
+ });
282
+
283
+ test("indicator sizes ascend", () => {
284
+ const edges = STEPS_SIZES.map((size) => step(cls(stepsVariants({ size }).indicator()), "size"));
285
+ expect(edges).toEqual([...edges].sort((a, b) => a - b));
286
+ expect(new Set(edges).size).toBe(edges.length);
287
+ });
288
+
289
+ test("a vertical title's first line is centred on its indicator", () => {
290
+ for (const size of STEPS_SIZES) {
291
+ const slots = stepsVariants({ orientation: "vertical", size });
292
+ const indicatorPx = step(cls(slots.indicator()), "size") * SPACING_STEP_PX;
293
+ const paddingPx = step(cls(slots.content()), "pt") * SPACING_STEP_PX;
294
+ const lineHeight = LINE_HEIGHT_PX[TEXT_UTILITY[STEPS_TITLE_TEXT_SIZE[size]]];
295
+ expect(paddingPx).toBe((indicatorPx - lineHeight) / 2);
296
+ }
297
+ });
298
+
299
+ test("every painted cell sets a border, a background and a foreground", () => {
300
+ for (const cell of everyCell()) {
301
+ const slots = stepsVariants(cell);
302
+ const indicator = cls(slots.indicator());
303
+ expect(indicator).toMatch(/\bborder-(primary|border|destructive|secondary|muted)\b/);
304
+ expect(indicator).toMatch(/\bbg-(primary|transparent|muted|secondary|destructive)\b/);
305
+ expect(cls(slots.indicatorLabel())).toMatch(/\btext-[a-z-]+-?foreground\b|\btext-foreground\b/);
306
+ }
307
+ });
308
+
309
+ test("invalid outranks every status in both variants", () => {
310
+ for (const variant of STEPS_VARIANTS) {
311
+ for (const status of STEP_STATUSES) {
312
+ const slots = stepsVariants({ variant, status, isInvalid: true });
313
+ expect(cls(slots.indicator())).toMatch(/\bborder-destructive\b/);
314
+ expect(cls(slots.indicator())).toMatch(/\bbg-destructive\b/);
315
+ expect(cls(slots.title())).toMatch(/\btext-destructive\b/);
316
+ }
317
+ }
318
+ });
319
+
320
+ test("the indicator label's colour class is the token the icon and spinner are handed", () => {
321
+ for (const cell of everyCell()) {
322
+ const token = resolveIndicatorForeground(cell);
323
+ expect(cls(stepsVariants(cell).indicatorLabel())).toMatch(new RegExp(`(^|\\s)text-${token}(\\s|$)`));
324
+ }
325
+ });
326
+
327
+ test("every foreground token is one theme.css declares", () => {
328
+ for (const cell of everyCell()) {
329
+ expect(THEME_CSS).toContain(`--${resolveIndicatorForeground(cell)}:`);
330
+ }
331
+ });
332
+
333
+ test("upcoming titles are muted; reached ones are not", () => {
334
+ expect(cls(stepsVariants({ status: "upcoming" }).title())).toMatch(/\btext-muted-foreground\b/);
335
+ expect(cls(stepsVariants({ status: "current" }).title())).toMatch(/\btext-foreground\b/);
336
+ expect(cls(stepsVariants({ status: "completed" }).title())).toMatch(/\btext-foreground\b/);
337
+ });
338
+
339
+ test("disabled fades the indicator and the content, never the pressable trigger", () => {
340
+ const slots = stepsVariants({ isDisabled: true });
341
+ expect(cls(slots.indicator())).toMatch(/\bopacity-50\b/);
342
+ expect(cls(slots.content())).toMatch(/\bopacity-50\b/);
343
+ expect(cls(slots.trigger())).not.toMatch(/\bopacity-/);
344
+ expect(cls(slots.item())).not.toMatch(/\bopacity-/);
345
+ });
346
+
347
+ test("a vertical panel's rail is exactly as wide as the indicator, so its line runs under the circle's centre", () => {
348
+ for (const size of STEPS_SIZES) {
349
+ const slots = stepsVariants({ hasPanel: true, orientation: "vertical", size });
350
+ expect(step(cls(slots.rail()), "w")).toBe(step(cls(slots.indicator()), "size"));
351
+ }
352
+ });
353
+
354
+ test("the panel row shares the trigger's gap, so the panel lines up under the title", () => {
355
+ for (const size of STEPS_SIZES) {
356
+ const slots = stepsVariants({ hasPanel: true, orientation: "vertical", size });
357
+ expect(step(cls(slots.panelRow()), "gap")).toBe(step(cls(slots.trigger()), "gap"));
358
+ }
359
+ });
360
+
361
+ test("with a panel the gap below a vertical step moves from the title to the panel", () => {
362
+ for (const size of STEPS_SIZES) {
363
+ const withPanel = stepsVariants({ hasPanel: true, isLast: false, orientation: "vertical", size });
364
+ const without = stepsVariants({ hasPanel: false, isLast: false, orientation: "vertical", size });
365
+ expect(cls(withPanel.content())).not.toMatch(/\bpb-[1-9]/);
366
+ expect(cls(withPanel.track())).not.toMatch(/\bpb-[1-9]/);
367
+ expect(step(cls(withPanel.panel()), "pb")).toBe(step(cls(without.content()), "pb"));
368
+ expect(step(cls(withPanel.rail()), "pb")).toBe(step(cls(without.track()), "pb"));
369
+ }
370
+ });
371
+
372
+ test("the last step's panel leaves no gap below it", () => {
373
+ const slots = stepsVariants({ hasPanel: true, isLast: true, orientation: "vertical" });
374
+ expect(cls(slots.panel())).not.toMatch(/\bpb-[1-9]/);
375
+ });
376
+
377
+ test("the last vertical step drops the gap its line would have filled", () => {
378
+ const middle = cls(stepsVariants({ orientation: "vertical", isLast: false }).content());
379
+ const last = cls(stepsVariants({ orientation: "vertical", isLast: true }).content());
380
+ expect(middle).toMatch(/\bpb-\d/);
381
+ expect(last).not.toMatch(/\bpb-[1-9]/);
382
+ });
383
+
384
+ test("a caller className is merged last", () => {
385
+ expect(cls(stepsVariants().root({ className: "flex-col" }))).toMatch(/\bflex-col\b/);
386
+ expect(cls(stepsVariants().root({ className: "flex-col" }))).not.toMatch(/\bflex-row\b/);
387
+ });
388
+ });