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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (481) hide show
  1. package/README.md +10 -6
  2. package/package.json +48 -4
  3. package/src/components/accordion/AGENTS.md +21 -9
  4. package/src/components/accordion/accordion-content.tsx +7 -4
  5. package/src/components/accordion/accordion-item.tsx +15 -13
  6. package/src/components/accordion/accordion.context.tsx +11 -5
  7. package/src/components/accordion/accordion.variants.test.ts +64 -3
  8. package/src/components/accordion/accordion.variants.ts +33 -9
  9. package/src/components/alert/AGENTS.md +102 -0
  10. package/src/components/alert/alert-action.tsx +17 -0
  11. package/src/components/alert/alert-close-button.tsx +50 -0
  12. package/src/components/alert/alert-content.tsx +15 -0
  13. package/src/components/alert/alert-description.tsx +16 -0
  14. package/src/components/alert/alert-glyphs.ts +25 -0
  15. package/src/components/alert/alert-indicator.tsx +40 -0
  16. package/src/components/alert/alert-title.tsx +18 -0
  17. package/src/components/alert/alert.context.tsx +69 -0
  18. package/src/components/alert/alert.tsx +160 -0
  19. package/src/components/alert/alert.types.ts +10 -0
  20. package/src/components/alert/alert.variants.test.ts +218 -0
  21. package/src/components/alert/alert.variants.ts +180 -0
  22. package/src/components/alert/index.ts +23 -0
  23. package/src/components/avatar/AGENTS.md +106 -0
  24. package/src/components/avatar/avatar-badge.tsx +51 -0
  25. package/src/components/avatar/avatar-group.tsx +139 -0
  26. package/src/components/avatar/avatar.context.tsx +93 -0
  27. package/src/components/avatar/avatar.tsx +201 -0
  28. package/src/components/avatar/avatar.utils.test.ts +174 -0
  29. package/src/components/avatar/avatar.utils.ts +141 -0
  30. package/src/components/avatar/avatar.variants.test.ts +251 -0
  31. package/src/components/avatar/avatar.variants.ts +270 -0
  32. package/src/components/avatar/index.ts +39 -0
  33. package/src/components/badge/AGENTS.md +5 -0
  34. package/src/components/badge/badge.tsx +6 -1
  35. package/src/components/bottom-sheet/AGENTS.md +186 -231
  36. package/src/components/bottom-sheet/bottom-sheet-background.tsx +31 -22
  37. package/src/components/bottom-sheet/bottom-sheet-close.tsx +23 -30
  38. package/src/components/bottom-sheet/bottom-sheet-container.tsx +54 -208
  39. package/src/components/bottom-sheet/bottom-sheet-content.tsx +25 -48
  40. package/src/components/bottom-sheet/bottom-sheet-description.tsx +8 -2
  41. package/src/components/bottom-sheet/bottom-sheet-flat-list.tsx +65 -0
  42. package/src/components/bottom-sheet/bottom-sheet-footer.tsx +53 -122
  43. package/src/components/bottom-sheet/bottom-sheet-handle.tsx +31 -26
  44. package/src/components/bottom-sheet/bottom-sheet-legend-list.tsx +69 -0
  45. package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +25 -147
  46. package/src/components/bottom-sheet/bottom-sheet-provider.tsx +42 -0
  47. package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +38 -62
  48. package/src/components/bottom-sheet/bottom-sheet-section-list.tsx +58 -0
  49. package/src/components/bottom-sheet/bottom-sheet-steps.tsx +88 -0
  50. package/src/components/bottom-sheet/bottom-sheet-text-input.tsx +44 -0
  51. package/src/components/bottom-sheet/bottom-sheet-title.tsx +12 -8
  52. package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +19 -37
  53. package/src/components/bottom-sheet/bottom-sheet.tsx +113 -60
  54. package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +47 -87
  55. package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -126
  56. package/src/components/bottom-sheet/index.ts +39 -17
  57. package/src/components/bottom-sheet/use-bottom-sheet-input.ts +16 -115
  58. package/src/components/button/AGENTS.md +45 -11
  59. package/src/components/button/button-group.tsx +44 -8
  60. package/src/components/button/button.context.tsx +10 -0
  61. package/src/components/button/button.tsx +2 -1
  62. package/src/components/button/button.variants.test.ts +154 -0
  63. package/src/components/button/button.variants.ts +71 -6
  64. package/src/components/button/index.ts +2 -0
  65. package/src/components/calendar/AGENTS.md +142 -0
  66. package/src/components/calendar/calendar-caption.tsx +116 -0
  67. package/src/components/calendar/calendar-day.tsx +103 -0
  68. package/src/components/calendar/calendar-grid.tsx +183 -0
  69. package/src/components/calendar/calendar-header.tsx +34 -0
  70. package/src/components/calendar/calendar-nav.tsx +51 -0
  71. package/src/components/calendar/calendar-picker.tsx +128 -0
  72. package/src/components/calendar/calendar-weekdays.tsx +48 -0
  73. package/src/components/calendar/calendar.context.tsx +108 -0
  74. package/src/components/calendar/calendar.date.test.ts +456 -0
  75. package/src/components/calendar/calendar.date.ts +584 -0
  76. package/src/components/calendar/calendar.tsx +389 -0
  77. package/src/components/calendar/calendar.types.ts +87 -0
  78. package/src/components/calendar/calendar.variants.test.ts +363 -0
  79. package/src/components/calendar/calendar.variants.ts +426 -0
  80. package/src/components/calendar/index.ts +96 -0
  81. package/src/components/card/AGENTS.md +88 -0
  82. package/src/components/card/card-action.tsx +16 -0
  83. package/src/components/card/card-content.tsx +12 -0
  84. package/src/components/card/card-description.tsx +12 -0
  85. package/src/components/card/card-footer.tsx +30 -0
  86. package/src/components/card/card-header.tsx +45 -0
  87. package/src/components/card/card-title.tsx +25 -0
  88. package/src/components/card/card.context.tsx +64 -0
  89. package/src/components/card/card.tsx +94 -0
  90. package/src/components/card/card.types.ts +13 -0
  91. package/src/components/card/card.variants.test.ts +240 -0
  92. package/src/components/card/card.variants.ts +177 -0
  93. package/src/components/card/index.ts +15 -0
  94. package/src/components/chart/AGENTS.md +8 -0
  95. package/src/components/chart/chart.tsx +8 -1
  96. package/src/components/chip/AGENTS.md +113 -0
  97. package/src/components/chip/chip-close-button.tsx +48 -0
  98. package/src/components/chip/chip-end-content.tsx +15 -0
  99. package/src/components/chip/chip-label.tsx +21 -0
  100. package/src/components/chip/chip-start-content.tsx +16 -0
  101. package/src/components/chip/chip.context.tsx +66 -0
  102. package/src/components/chip/chip.tsx +268 -0
  103. package/src/components/chip/chip.types.ts +10 -0
  104. package/src/components/chip/chip.variants.test.ts +335 -0
  105. package/src/components/chip/chip.variants.ts +321 -0
  106. package/src/components/chip/index.ts +24 -0
  107. package/src/components/collapsible/AGENTS.md +97 -0
  108. package/src/components/collapsible/collapsible-content.tsx +97 -0
  109. package/src/components/collapsible/collapsible-description.tsx +17 -0
  110. package/src/components/collapsible/collapsible-indicator.tsx +92 -0
  111. package/src/components/collapsible/collapsible-title.tsx +17 -0
  112. package/src/components/collapsible/collapsible-trigger.tsx +133 -0
  113. package/src/components/collapsible/collapsible.context.tsx +91 -0
  114. package/src/components/collapsible/collapsible.tsx +167 -0
  115. package/src/components/collapsible/collapsible.types.ts +9 -0
  116. package/src/components/collapsible/collapsible.variants.test.ts +338 -0
  117. package/src/components/collapsible/collapsible.variants.ts +228 -0
  118. package/src/components/collapsible/index.ts +32 -0
  119. package/src/components/context-menu/AGENTS.md +103 -0
  120. package/src/components/context-menu/context-menu-content.tsx +64 -0
  121. package/src/components/context-menu/context-menu-item.tsx +22 -0
  122. package/src/components/context-menu/context-menu-preview.tsx +60 -0
  123. package/src/components/context-menu/context-menu-trigger.tsx +160 -0
  124. package/src/components/context-menu/context-menu.context.tsx +91 -0
  125. package/src/components/context-menu/context-menu.tsx +90 -0
  126. package/src/components/context-menu/context-menu.variants.test.ts +266 -0
  127. package/src/components/context-menu/context-menu.variants.ts +147 -0
  128. package/src/components/context-menu/index.ts +18 -0
  129. package/src/components/dialog/AGENTS.md +97 -0
  130. package/src/components/dialog/dialog-body.tsx +21 -0
  131. package/src/components/dialog/dialog-close.tsx +82 -0
  132. package/src/components/dialog/dialog-content.tsx +153 -0
  133. package/src/components/dialog/dialog-description.tsx +23 -0
  134. package/src/components/dialog/dialog-footer.tsx +32 -0
  135. package/src/components/dialog/dialog-header.tsx +20 -0
  136. package/src/components/dialog/dialog-title.tsx +42 -0
  137. package/src/components/dialog/dialog-trigger.tsx +54 -0
  138. package/src/components/dialog/dialog.context.tsx +90 -0
  139. package/src/components/dialog/dialog.tsx +119 -0
  140. package/src/components/dialog/dialog.variants.test.ts +224 -0
  141. package/src/components/dialog/dialog.variants.ts +173 -0
  142. package/src/components/dialog/index.ts +30 -0
  143. package/src/components/drawer/AGENTS.md +123 -0
  144. package/src/components/drawer/drawer-body.tsx +46 -0
  145. package/src/components/drawer/drawer-close.tsx +76 -0
  146. package/src/components/drawer/drawer-content.tsx +186 -0
  147. package/src/components/drawer/drawer-description.tsx +23 -0
  148. package/src/components/drawer/drawer-footer.tsx +21 -0
  149. package/src/components/drawer/drawer-header.tsx +42 -0
  150. package/src/components/drawer/drawer-title.tsx +35 -0
  151. package/src/components/drawer/drawer-trigger.tsx +54 -0
  152. package/src/components/drawer/drawer.context.tsx +114 -0
  153. package/src/components/drawer/drawer.tsx +119 -0
  154. package/src/components/drawer/drawer.variants.test.ts +392 -0
  155. package/src/components/drawer/drawer.variants.ts +244 -0
  156. package/src/components/drawer/index.ts +41 -0
  157. package/src/components/drawer/use-drawer-pan.ts +108 -0
  158. package/src/components/empty-state/AGENTS.md +90 -0
  159. package/src/components/empty-state/empty-state-content.tsx +17 -0
  160. package/src/components/empty-state/empty-state-description.tsx +12 -0
  161. package/src/components/empty-state/empty-state-header.tsx +17 -0
  162. package/src/components/empty-state/empty-state-media.tsx +56 -0
  163. package/src/components/empty-state/empty-state-title.tsx +18 -0
  164. package/src/components/empty-state/empty-state.context.tsx +65 -0
  165. package/src/components/empty-state/empty-state.tsx +84 -0
  166. package/src/components/empty-state/empty-state.types.ts +14 -0
  167. package/src/components/empty-state/empty-state.variants.test.ts +245 -0
  168. package/src/components/empty-state/empty-state.variants.ts +132 -0
  169. package/src/components/empty-state/index.ts +20 -0
  170. package/src/components/fab/AGENTS.md +119 -0
  171. package/src/components/fab/fab-action.tsx +118 -0
  172. package/src/components/fab/fab-group.tsx +214 -0
  173. package/src/components/fab/fab-label.tsx +18 -0
  174. package/src/components/fab/fab.context.tsx +125 -0
  175. package/src/components/fab/fab.tsx +156 -0
  176. package/src/components/fab/fab.types.ts +17 -0
  177. package/src/components/fab/fab.variants.test.ts +274 -0
  178. package/src/components/fab/fab.variants.ts +234 -0
  179. package/src/components/fab/index.ts +37 -0
  180. package/src/components/feedback/AGENTS.md +95 -0
  181. package/src/components/feedback/feedback-action.tsx +16 -0
  182. package/src/components/feedback/feedback-cancel.tsx +34 -0
  183. package/src/components/feedback/feedback-close.tsx +21 -0
  184. package/src/components/feedback/feedback-content.tsx +24 -0
  185. package/src/components/feedback/feedback-field.tsx +70 -0
  186. package/src/components/feedback/feedback-footer.tsx +21 -0
  187. package/src/components/feedback/feedback-panel.tsx +54 -0
  188. package/src/components/feedback/feedback-submit.tsx +74 -0
  189. package/src/components/feedback/feedback-title.tsx +34 -0
  190. package/src/components/feedback/feedback-trigger.tsx +20 -0
  191. package/src/components/feedback/feedback.context.tsx +73 -0
  192. package/src/components/feedback/feedback.tsx +135 -0
  193. package/src/components/feedback/feedback.variants.test.ts +185 -0
  194. package/src/components/feedback/feedback.variants.ts +128 -0
  195. package/src/components/feedback/index.ts +24 -0
  196. package/src/components/field/AGENTS.md +19 -0
  197. package/src/components/field/field-label.tsx +19 -5
  198. package/src/components/field/field.context.tsx +17 -0
  199. package/src/components/field/field.tsx +15 -2
  200. package/src/components/field/field.variants.test.ts +24 -0
  201. package/src/components/field/field.variants.ts +20 -0
  202. package/src/components/field/index.ts +1 -0
  203. package/src/components/item/AGENTS.md +92 -0
  204. package/src/components/item/index.ts +25 -0
  205. package/src/components/item/item-actions.tsx +28 -0
  206. package/src/components/item/item-content.tsx +15 -0
  207. package/src/components/item/item-description.tsx +12 -0
  208. package/src/components/item/item-footer.tsx +13 -0
  209. package/src/components/item/item-group.tsx +32 -0
  210. package/src/components/item/item-header.tsx +13 -0
  211. package/src/components/item/item-media.tsx +37 -0
  212. package/src/components/item/item-separator.tsx +13 -0
  213. package/src/components/item/item-title.tsx +17 -0
  214. package/src/components/item/item.context.tsx +66 -0
  215. package/src/components/item/item.tsx +218 -0
  216. package/src/components/item/item.types.ts +12 -0
  217. package/src/components/item/item.variants.test.ts +263 -0
  218. package/src/components/item/item.variants.ts +200 -0
  219. package/src/components/kpi/AGENTS.md +125 -0
  220. package/src/components/kpi/index.ts +45 -0
  221. package/src/components/kpi/kpi-action.tsx +12 -0
  222. package/src/components/kpi/kpi-content.tsx +35 -0
  223. package/src/components/kpi/kpi-footer.tsx +52 -0
  224. package/src/components/kpi/kpi-group.tsx +70 -0
  225. package/src/components/kpi/kpi-header.tsx +25 -0
  226. package/src/components/kpi/kpi-icon.tsx +28 -0
  227. package/src/components/kpi/kpi-sparkline.tsx +145 -0
  228. package/src/components/kpi/kpi-stat.tsx +20 -0
  229. package/src/components/kpi/kpi-title.tsx +19 -0
  230. package/src/components/kpi/kpi-trend.tsx +105 -0
  231. package/src/components/kpi/kpi-value.tsx +32 -0
  232. package/src/components/kpi/kpi.context.tsx +111 -0
  233. package/src/components/kpi/kpi.tsx +154 -0
  234. package/src/components/kpi/kpi.types.ts +9 -0
  235. package/src/components/kpi/kpi.variants.test.ts +428 -0
  236. package/src/components/kpi/kpi.variants.ts +347 -0
  237. package/src/components/label/AGENTS.md +89 -0
  238. package/src/components/label/index.ts +10 -0
  239. package/src/components/label/label.tsx +97 -0
  240. package/src/components/label/label.variants.test.ts +176 -0
  241. package/src/components/label/label.variants.ts +123 -0
  242. package/src/components/menu/AGENTS.md +176 -0
  243. package/src/components/menu/index.ts +44 -0
  244. package/src/components/menu/menu-background.tsx +17 -0
  245. package/src/components/menu/menu-checkbox-item.tsx +57 -0
  246. package/src/components/menu/menu-content.tsx +303 -0
  247. package/src/components/menu/menu-item.tsx +162 -0
  248. package/src/components/menu/menu-label.tsx +14 -0
  249. package/src/components/menu/menu-radio-group.tsx +50 -0
  250. package/src/components/menu/menu-radio-item.tsx +57 -0
  251. package/src/components/menu/menu-separator.tsx +11 -0
  252. package/src/components/menu/menu-sub-content.tsx +72 -0
  253. package/src/components/menu/menu-sub-trigger.tsx +50 -0
  254. package/src/components/menu/menu-sub.tsx +75 -0
  255. package/src/components/menu/menu-trigger.tsx +57 -0
  256. package/src/components/menu/menu.context.tsx +214 -0
  257. package/src/components/menu/menu.tsx +88 -0
  258. package/src/components/menu/menu.types.ts +33 -0
  259. package/src/components/menu/menu.variants.test.ts +400 -0
  260. package/src/components/menu/menu.variants.ts +353 -0
  261. package/src/components/meter/AGENTS.md +135 -0
  262. package/src/components/meter/index.ts +43 -0
  263. package/src/components/meter/meter-fill.tsx +19 -0
  264. package/src/components/meter/meter-header.tsx +17 -0
  265. package/src/components/meter/meter-label.tsx +18 -0
  266. package/src/components/meter/meter-output.tsx +42 -0
  267. package/src/components/meter/meter-segments.tsx +83 -0
  268. package/src/components/meter/meter-track.tsx +16 -0
  269. package/src/components/meter/meter.context.tsx +62 -0
  270. package/src/components/meter/meter.tsx +286 -0
  271. package/src/components/meter/meter.types.ts +30 -0
  272. package/src/components/meter/meter.variants.test.ts +430 -0
  273. package/src/components/meter/meter.variants.ts +347 -0
  274. package/src/components/overlay/AGENTS.md +89 -0
  275. package/src/components/overlay/index.ts +27 -0
  276. package/src/components/overlay/overlay-portal.tsx +84 -0
  277. package/src/components/overlay/overlay-presence.test.ts +46 -0
  278. package/src/components/overlay/overlay-presence.ts +49 -0
  279. package/src/components/overlay/overlay-registry.test.ts +96 -0
  280. package/src/components/overlay/overlay-registry.ts +99 -0
  281. package/src/components/overlay/overlay-scrim.tsx +49 -0
  282. package/src/components/overlay/overlay.context.tsx +48 -0
  283. package/src/components/overlay/overlay.tsx +118 -0
  284. package/src/components/overlay/overlay.variants.test.ts +37 -0
  285. package/src/components/overlay/overlay.variants.ts +33 -0
  286. package/src/components/overlay/use-overlay-back-handler.ts +34 -0
  287. package/src/components/overlay/use-overlay-presence.ts +87 -0
  288. package/src/components/popover/AGENTS.md +138 -0
  289. package/src/components/popover/index.ts +48 -0
  290. package/src/components/popover/popover-anchor.tsx +49 -0
  291. package/src/components/popover/popover-arrow.tsx +89 -0
  292. package/src/components/popover/popover-close.tsx +73 -0
  293. package/src/components/popover/popover-content.tsx +235 -0
  294. package/src/components/popover/popover-description.tsx +18 -0
  295. package/src/components/popover/popover-title.tsx +33 -0
  296. package/src/components/popover/popover-trigger.tsx +56 -0
  297. package/src/components/popover/popover.context.tsx +71 -0
  298. package/src/components/popover/popover.position.test.ts +354 -0
  299. package/src/components/popover/popover.position.ts +286 -0
  300. package/src/components/popover/popover.tsx +123 -0
  301. package/src/components/popover/popover.variants.test.ts +135 -0
  302. package/src/components/popover/popover.variants.ts +85 -0
  303. package/src/components/popover/use-anchor-measure.ts +76 -0
  304. package/src/components/popover/use-anchored-content.ts +219 -0
  305. package/src/components/pressable/AGENTS.md +11 -1
  306. package/src/components/pressable/pressable.tsx +7 -2
  307. package/src/components/pressable/pressable.variants.test.ts +29 -0
  308. package/src/components/pressable/pressable.variants.ts +17 -0
  309. package/src/components/progress/AGENTS.md +120 -0
  310. package/src/components/progress/index.ts +40 -0
  311. package/src/components/progress/progress-fill.tsx +152 -0
  312. package/src/components/progress/progress-header.tsx +19 -0
  313. package/src/components/progress/progress-label.tsx +27 -0
  314. package/src/components/progress/progress-output.tsx +47 -0
  315. package/src/components/progress/progress-track.tsx +38 -0
  316. package/src/components/progress/progress.context.tsx +79 -0
  317. package/src/components/progress/progress.tsx +171 -0
  318. package/src/components/progress/progress.types.ts +23 -0
  319. package/src/components/progress/progress.variants.test.ts +335 -0
  320. package/src/components/progress/progress.variants.ts +284 -0
  321. package/src/components/progress-button/AGENTS.md +116 -0
  322. package/src/components/progress-button/index.ts +36 -0
  323. package/src/components/progress-button/progress-button-done.tsx +33 -0
  324. package/src/components/progress-button/progress-button-label.tsx +27 -0
  325. package/src/components/progress-button/progress-button.context.tsx +93 -0
  326. package/src/components/progress-button/progress-button.tsx +414 -0
  327. package/src/components/progress-button/progress-button.variants.test.ts +314 -0
  328. package/src/components/progress-button/progress-button.variants.ts +236 -0
  329. package/src/components/provider/AGENTS.md +38 -25
  330. package/src/components/provider/provider.tsx +38 -11
  331. package/src/components/radio/AGENTS.md +8 -10
  332. package/src/components/radio/radio.variants.test.ts +5 -6
  333. package/src/components/radio/radio.variants.ts +6 -7
  334. package/src/components/rating/AGENTS.md +143 -0
  335. package/src/components/rating/index.ts +43 -0
  336. package/src/components/rating/rating-output.tsx +39 -0
  337. package/src/components/rating/rating-star.tsx +70 -0
  338. package/src/components/rating/rating-stars.tsx +219 -0
  339. package/src/components/rating/rating.context.tsx +79 -0
  340. package/src/components/rating/rating.tsx +163 -0
  341. package/src/components/rating/rating.variants.test.ts +347 -0
  342. package/src/components/rating/rating.variants.ts +354 -0
  343. package/src/components/selection-mode/AGENTS.md +155 -0
  344. package/src/components/selection-mode/index.ts +43 -0
  345. package/src/components/selection-mode/selection-mode-action.tsx +65 -0
  346. package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
  347. package/src/components/selection-mode/selection-mode-group.tsx +123 -0
  348. package/src/components/selection-mode/selection-mode-header.tsx +85 -0
  349. package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
  350. package/src/components/selection-mode/selection-mode-item.tsx +182 -0
  351. package/src/components/selection-mode/selection-mode.context.tsx +89 -0
  352. package/src/components/selection-mode/selection-mode.tsx +207 -0
  353. package/src/components/selection-mode/selection-mode.types.ts +36 -0
  354. package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
  355. package/src/components/selection-mode/selection-mode.variants.ts +329 -0
  356. package/src/components/skeleton/AGENTS.md +120 -0
  357. package/src/components/skeleton/index.ts +15 -0
  358. package/src/components/skeleton/skeleton-clock.ts +48 -0
  359. package/src/components/skeleton/skeleton-group.tsx +64 -0
  360. package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
  361. package/src/components/skeleton/skeleton.context.tsx +45 -0
  362. package/src/components/skeleton/skeleton.tsx +227 -0
  363. package/src/components/skeleton/skeleton.variants.test.ts +245 -0
  364. package/src/components/skeleton/skeleton.variants.ts +222 -0
  365. package/src/components/slide-button/AGENTS.md +122 -0
  366. package/src/components/slide-button/index.ts +43 -0
  367. package/src/components/slide-button/slide-button-label.tsx +70 -0
  368. package/src/components/slide-button/slide-button-thumb.tsx +93 -0
  369. package/src/components/slide-button/slide-button.context.tsx +76 -0
  370. package/src/components/slide-button/slide-button.tsx +457 -0
  371. package/src/components/slide-button/slide-button.variants.test.ts +288 -0
  372. package/src/components/slide-button/slide-button.variants.ts +324 -0
  373. package/src/components/slider/index.ts +2 -0
  374. package/src/components/slider/slider-thumb.tsx +33 -1
  375. package/src/components/slider/slider.variants.test.ts +52 -0
  376. package/src/components/slider/slider.variants.ts +56 -2
  377. package/src/components/stack-card/AGENTS.md +169 -0
  378. package/src/components/stack-card/index.ts +37 -0
  379. package/src/components/stack-card/stack-card-action.tsx +66 -0
  380. package/src/components/stack-card/stack-card-actions.tsx +18 -0
  381. package/src/components/stack-card/stack-card-card.tsx +71 -0
  382. package/src/components/stack-card/stack-card-empty.tsx +30 -0
  383. package/src/components/stack-card/stack-card-slot.tsx +90 -0
  384. package/src/components/stack-card/stack-card-stamp.tsx +76 -0
  385. package/src/components/stack-card/stack-card.context.tsx +117 -0
  386. package/src/components/stack-card/stack-card.tsx +557 -0
  387. package/src/components/stack-card/stack-card.types.ts +26 -0
  388. package/src/components/stack-card/stack-card.variants.test.ts +355 -0
  389. package/src/components/stack-card/stack-card.variants.ts +360 -0
  390. package/src/components/steps/AGENTS.md +153 -0
  391. package/src/components/steps/index.ts +45 -0
  392. package/src/components/steps/steps-connector.tsx +31 -0
  393. package/src/components/steps/steps-description.tsx +26 -0
  394. package/src/components/steps/steps-indicator.tsx +106 -0
  395. package/src/components/steps/steps-item.tsx +225 -0
  396. package/src/components/steps/steps-panel.tsx +41 -0
  397. package/src/components/steps/steps-title.tsx +32 -0
  398. package/src/components/steps/steps-track.tsx +59 -0
  399. package/src/components/steps/steps.context.tsx +123 -0
  400. package/src/components/steps/steps.tsx +166 -0
  401. package/src/components/steps/steps.variants.test.ts +388 -0
  402. package/src/components/steps/steps.variants.ts +468 -0
  403. package/src/components/surface/AGENTS.md +72 -0
  404. package/src/components/surface/index.ts +15 -0
  405. package/src/components/surface/surface.context.tsx +54 -0
  406. package/src/components/surface/surface.tsx +70 -0
  407. package/src/components/surface/surface.variants.test.ts +180 -0
  408. package/src/components/surface/surface.variants.ts +133 -0
  409. package/src/components/swipe/AGENTS.md +140 -0
  410. package/src/components/swipe/index.ts +31 -0
  411. package/src/components/swipe/swipe-action.tsx +99 -0
  412. package/src/components/swipe/swipe-end.tsx +14 -0
  413. package/src/components/swipe/swipe-group.tsx +55 -0
  414. package/src/components/swipe/swipe-panel.tsx +55 -0
  415. package/src/components/swipe/swipe-start.tsx +15 -0
  416. package/src/components/swipe/swipe.context.tsx +135 -0
  417. package/src/components/swipe/swipe.tsx +393 -0
  418. package/src/components/swipe/swipe.types.ts +7 -0
  419. package/src/components/swipe/swipe.variants.test.ts +337 -0
  420. package/src/components/swipe/swipe.variants.ts +290 -0
  421. package/src/components/switch/switch.variants.ts +2 -2
  422. package/src/components/tabs/AGENTS.md +4 -3
  423. package/src/components/tabs/tabs.variants.test.ts +1 -1
  424. package/src/components/tabs/tabs.variants.ts +4 -6
  425. package/src/components/textarea/AGENTS.md +86 -0
  426. package/src/components/textarea/index.ts +16 -0
  427. package/src/components/textarea/textarea.tsx +157 -0
  428. package/src/components/textarea/textarea.variants.test.ts +219 -0
  429. package/src/components/textarea/textarea.variants.ts +171 -0
  430. package/src/components/toast/AGENTS.md +140 -0
  431. package/src/components/toast/index.ts +74 -0
  432. package/src/components/toast/toast-action.tsx +59 -0
  433. package/src/components/toast/toast-api.ts +32 -0
  434. package/src/components/toast/toast-close.tsx +44 -0
  435. package/src/components/toast/toast-content.tsx +11 -0
  436. package/src/components/toast/toast-description.tsx +11 -0
  437. package/src/components/toast/toast-indicator.tsx +34 -0
  438. package/src/components/toast/toast-item.tsx +308 -0
  439. package/src/components/toast/toast-title.tsx +13 -0
  440. package/src/components/toast/toast-viewport.tsx +135 -0
  441. package/src/components/toast/toast.context.tsx +69 -0
  442. package/src/components/toast/toast.store.test.ts +259 -0
  443. package/src/components/toast/toast.store.ts +338 -0
  444. package/src/components/toast/toast.timer.test.ts +64 -0
  445. package/src/components/toast/toast.timer.ts +51 -0
  446. package/src/components/toast/toast.tsx +132 -0
  447. package/src/components/toast/toast.variants.test.ts +393 -0
  448. package/src/components/toast/toast.variants.ts +304 -0
  449. package/src/components/toggle-button/AGENTS.md +106 -0
  450. package/src/components/toggle-button/index.ts +30 -0
  451. package/src/components/toggle-button/toggle-button-group.tsx +156 -0
  452. package/src/components/toggle-button/toggle-button-label.tsx +19 -0
  453. package/src/components/toggle-button/toggle-button.context.tsx +130 -0
  454. package/src/components/toggle-button/toggle-button.tsx +172 -0
  455. package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
  456. package/src/components/toggle-button/toggle-button.variants.ts +160 -0
  457. package/src/components/tooltip/AGENTS.md +133 -0
  458. package/src/components/tooltip/index.ts +28 -0
  459. package/src/components/tooltip/tooltip-arrow.tsx +40 -0
  460. package/src/components/tooltip/tooltip-content.tsx +179 -0
  461. package/src/components/tooltip/tooltip-description.tsx +19 -0
  462. package/src/components/tooltip/tooltip-text.tsx +21 -0
  463. package/src/components/tooltip/tooltip-title.tsx +20 -0
  464. package/src/components/tooltip/tooltip-trigger.tsx +103 -0
  465. package/src/components/tooltip/tooltip.context.tsx +68 -0
  466. package/src/components/tooltip/tooltip.tsx +213 -0
  467. package/src/components/tooltip/tooltip.variants.test.ts +231 -0
  468. package/src/components/tooltip/tooltip.variants.ts +177 -0
  469. package/src/hooks/use-calm-motion.tsx +31 -0
  470. package/src/lib/calm-motion.test.ts +17 -0
  471. package/src/lib/calm-motion.ts +23 -0
  472. package/src/lib/cn.test.ts +14 -0
  473. package/src/lib/cn.ts +2 -2
  474. package/src/no-gorhom.test.ts +54 -0
  475. package/src/styles/geometry.test.ts +1 -0
  476. package/src/styles/theme.css +4 -0
  477. package/src/styles/tokens.css +11 -0
  478. package/src/styles/tokens.test.ts +22 -0
  479. package/src/styles/tokens.ts +20 -0
  480. package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
  481. package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
@@ -0,0 +1,468 @@
1
+ import type { VariantProps } from "tailwind-variants";
2
+ import { tv } from "../../lib/tv";
3
+ import type { TextSize } from "../text/text.variants";
4
+
5
+ /**
6
+ * How an indicator is painted. `primary` rings the current step and leaves the
7
+ * steps ahead hollow; `secondary` fills every indicator, the current one solid
8
+ * and the rest soft.
9
+ */
10
+ export const STEPS_VARIANTS = ["primary", "secondary"] as const;
11
+
12
+ export const STEPS_SIZES = ["sm", "md", "lg"] as const;
13
+
14
+ /** Which way the steps run. `horizontal` puts titles under the indicators; `vertical` beside them. */
15
+ export const STEPS_ORIENTATIONS = ["horizontal", "vertical"] as const;
16
+
17
+ /** Where a step stands relative to the value. See {@link resolveStepStatus}. */
18
+ export const STEP_STATUSES = ["completed", "current", "upcoming"] as const;
19
+
20
+ export type StepsVariant = (typeof STEPS_VARIANTS)[number];
21
+ export type StepsSize = (typeof STEPS_SIZES)[number];
22
+ export type StepsOrientation = (typeof STEPS_ORIENTATIONS)[number];
23
+ export type StepStatus = (typeof STEP_STATUSES)[number];
24
+
25
+ /**
26
+ * The axes a stepper falls back to when the call site names none.
27
+ *
28
+ * Named once because two places read them — `defaultVariants` below and the
29
+ * root's destructure, which settles them before `tv` is ever called and
30
+ * publishes them through context. A test pins the pair.
31
+ */
32
+ export const STEPS_DEFAULT_VARIANT: StepsVariant = "primary";
33
+ export const STEPS_DEFAULT_SIZE: StepsSize = "md";
34
+ export const STEPS_DEFAULT_ORIENTATION: StepsOrientation = "horizontal";
35
+
36
+ /**
37
+ * The `Text` size step each stepper size hands `Steps.Title`.
38
+ *
39
+ * The title renders `Text.Label` and names this step rather than restating a
40
+ * type scale in a slot — the rule `Radio.Label` follows. The vertical layout's
41
+ * top padding is derived from the line height of this step, and a test holds
42
+ * the two together: move one without the other and the title's first line
43
+ * stops sitting level with its indicator.
44
+ */
45
+ export const STEPS_TITLE_TEXT_SIZE: Record<StepsSize, TextSize> = { sm: "sm", md: "md", lg: "lg" };
46
+
47
+ /** The `Text` size step each stepper size hands `Steps.Description`. One below the title. */
48
+ export const STEPS_DESCRIPTION_TEXT_SIZE: Record<StepsSize, TextSize> = { sm: "xs", md: "sm", lg: "md" };
49
+
50
+ /**
51
+ * Styling for every part of a stepper.
52
+ *
53
+ * One slotted `tv()` rather than a call per part, because the item, indicator,
54
+ * title and description files cannot import the root without closing a cycle
55
+ * (AGENTS.md rule 3) yet all of them read the same `orientation`, `size` and
56
+ * `status`.
57
+ *
58
+ * **The connectors are laid out, never measured.** A horizontal item is a
59
+ * column of equal `flex-1` width with its indicator centred in a track of
60
+ * `[half-line] [indicator] [half-line]`; the line between two steps is the
61
+ * right half of one and the left half of the next, meeting at the item
62
+ * boundary. A vertical trigger is a row whose track is `[indicator] [line]`, the
63
+ * line `flex-1` down the height the content gives it. Neither needs an
64
+ * `onLayout`, so a title that wraps to three lines moves nothing a frame late.
65
+ *
66
+ * **A vertical title is padded down to sit level with its indicator**, by half
67
+ * the difference between the indicator's edge and the title's line height —
68
+ * the one number here derived from another, and pinned by a test.
69
+ *
70
+ * **The fade lands on the indicator and the content, never the trigger.** The
71
+ * trigger is worn by `Pressable`'s own `Animated.View`, whose animated style writes
72
+ * `opacity` every frame and would silently overwrite an `opacity-50` class —
73
+ * the trap `radioVariants` documents.
74
+ *
75
+ * Every colour cell lives in `compoundVariants`, invalid last, because `tv`
76
+ * emits the compounds after the plain variants: a plain `isInvalid` branch
77
+ * would lose to the status cells.
78
+ *
79
+ * Free of React Native imports so it stays unit-testable. See AGENTS.md.
80
+ */
81
+ export const stepsVariants = tv({
82
+ slots: {
83
+ /** The root's box. */
84
+ root: "w-full",
85
+ /** One step's box: the trigger, then its panel. Takes the caller's `className`. */
86
+ item: "",
87
+ /** The pressable — indicator, title and description. Deliberately free of opacity; see above. */
88
+ trigger: "",
89
+ /** The indicator and the connector halves around it. */
90
+ track: "",
91
+ /**
92
+ * One half of the line between two steps. Square-ended: two rounded
93
+ * halves meeting at the item boundary would pinch the line there.
94
+ */
95
+ connector: "",
96
+ /** An invisible half that keeps an end indicator centred in its column. */
97
+ spacer: "flex-1",
98
+ /** The circle. */
99
+ indicator: "items-center justify-center rounded-full border-2",
100
+ /** The step number inside the circle — a `Text`, so it owns its colour (rule 1). */
101
+ indicatorLabel: "font-semibold",
102
+ /** The check, alert or spinner inside the circle. */
103
+ glyph: "",
104
+ /** The title, description and anything else composed into the step. */
105
+ content: "min-w-0 gap-0.5",
106
+ /** Handed to a `Text.Label`. */
107
+ title: "",
108
+ /** Handed to a `Text.Caption`. */
109
+ description: "",
110
+ /** `Steps.Panel`'s row: a rail carrying the line on, and the panel beside it. */
111
+ panelRow: "",
112
+ /** The column under the indicator that carries a vertical line past a panel. */
113
+ rail: "items-center",
114
+ /** `Steps.Panel` itself — outside the tap target. */
115
+ panel: "min-w-0",
116
+ },
117
+ variants: {
118
+ orientation: {
119
+ horizontal: {
120
+ root: "flex-row items-start",
121
+ item: "flex-1",
122
+ trigger: "items-center",
123
+ track: "flex-row items-center self-stretch",
124
+ connector: "h-0.5 flex-1",
125
+ content: "items-center px-1",
126
+ panel: "px-1 pt-2",
127
+ title: "text-center",
128
+ description: "text-center",
129
+ },
130
+ vertical: {
131
+ root: "flex-col",
132
+ trigger: "flex-row",
133
+ track: "items-center",
134
+ panelRow: "flex-row",
135
+ panel: "flex-1",
136
+ connector: "min-h-4 w-0.5 flex-1",
137
+ content: "flex-1",
138
+ },
139
+ },
140
+ size: {
141
+ sm: {
142
+ trigger: "gap-2",
143
+ panelRow: "gap-2",
144
+ rail: "w-6",
145
+ track: "gap-1",
146
+ indicator: "size-6",
147
+ indicatorLabel: "text-xs",
148
+ glyph: "size-icon-sm",
149
+ },
150
+ md: {
151
+ trigger: "gap-2.5",
152
+ panelRow: "gap-2.5",
153
+ rail: "w-8",
154
+ track: "gap-1.5",
155
+ indicator: "size-8",
156
+ indicatorLabel: "text-sm",
157
+ glyph: "size-icon-lg",
158
+ },
159
+ lg: {
160
+ trigger: "gap-3",
161
+ panelRow: "gap-3",
162
+ rail: "w-10",
163
+ track: "gap-2",
164
+ indicator: "size-10",
165
+ indicatorLabel: "text-base",
166
+ glyph: "size-icon-xl",
167
+ },
168
+ },
169
+ // The empty branches are load-bearing typing, not placeholders — `tv`
170
+ // derives the prop type from the declared keys. See button.variants.ts.
171
+ variant: { primary: {}, secondary: {} },
172
+ status: {
173
+ completed: { title: "text-foreground" },
174
+ current: { title: "text-foreground" },
175
+ upcoming: { title: "text-muted-foreground" },
176
+ },
177
+ isInvalid: { true: {}, false: {} },
178
+ isConnectorComplete: { true: { connector: "bg-primary" }, false: { connector: "bg-border" } },
179
+ isDisabled: { true: { indicator: "opacity-50", content: "opacity-50" }, false: {} },
180
+ isLast: { true: {}, false: {} },
181
+ hasPanel: { true: {}, false: {} },
182
+ },
183
+ compoundVariants: [
184
+ // A vertical step's content: level with the indicator at the top, and
185
+ // room below for the line to run down to the next step.
186
+ { orientation: "vertical", size: "sm", class: { content: "pt-0.5" } },
187
+ { orientation: "vertical", size: "md", class: { content: "pt-1" } },
188
+ { orientation: "vertical", size: "lg", class: { content: "pt-1.5" } },
189
+ { orientation: "vertical", isLast: false, size: "sm", class: { content: "pb-4" } },
190
+ { orientation: "vertical", isLast: false, size: "md", class: { content: "pb-6" } },
191
+ { orientation: "vertical", isLast: false, size: "lg", class: { content: "pb-8" } },
192
+ { orientation: "vertical", isLast: true, class: { content: "pb-0" } },
193
+ // The line stops short of the next indicator by the same gap it leaves
194
+ // below its own, so it floats between the two rather than touching one.
195
+ { orientation: "vertical", size: "sm", class: { track: "pb-1" } },
196
+ { orientation: "vertical", size: "md", class: { track: "pb-1.5" } },
197
+ { orientation: "vertical", size: "lg", class: { track: "pb-2" } },
198
+ // With a panel both gaps move down a row: the trigger's line runs to its
199
+ // bottom edge and on through the rail, and the space before the next step
200
+ // opens below the panel instead of below the title.
201
+ { orientation: "vertical", hasPanel: true, class: { content: "pb-0", track: "pb-0" } },
202
+ { orientation: "vertical", hasPanel: true, isLast: false, size: "sm", class: { panel: "pb-4", rail: "pb-1" } },
203
+ { orientation: "vertical", hasPanel: true, isLast: false, size: "md", class: { panel: "pb-6", rail: "pb-1.5" } },
204
+ { orientation: "vertical", hasPanel: true, isLast: false, size: "lg", class: { panel: "pb-8", rail: "pb-2" } },
205
+
206
+ {
207
+ variant: "primary",
208
+ status: "completed",
209
+ class: { indicator: "border-primary bg-primary", indicatorLabel: "text-primary-foreground" },
210
+ },
211
+ {
212
+ variant: "primary",
213
+ status: "current",
214
+ class: { indicator: "border-primary bg-transparent", indicatorLabel: "text-foreground" },
215
+ },
216
+ {
217
+ variant: "primary",
218
+ status: "upcoming",
219
+ class: { indicator: "border-border bg-transparent", indicatorLabel: "text-muted-foreground" },
220
+ },
221
+ {
222
+ variant: "secondary",
223
+ status: "completed",
224
+ class: { indicator: "border-secondary bg-secondary", indicatorLabel: "text-secondary-foreground" },
225
+ },
226
+ {
227
+ variant: "secondary",
228
+ status: "current",
229
+ class: { indicator: "border-primary bg-primary", indicatorLabel: "text-primary-foreground" },
230
+ },
231
+ {
232
+ variant: "secondary",
233
+ status: "upcoming",
234
+ class: { indicator: "border-muted bg-muted", indicatorLabel: "text-muted-foreground" },
235
+ },
236
+ {
237
+ isInvalid: true,
238
+ class: {
239
+ indicator: "border-destructive bg-destructive",
240
+ indicatorLabel: "text-destructive-foreground",
241
+ title: "text-destructive",
242
+ },
243
+ },
244
+ ],
245
+ defaultVariants: {
246
+ orientation: STEPS_DEFAULT_ORIENTATION,
247
+ size: STEPS_DEFAULT_SIZE,
248
+ variant: STEPS_DEFAULT_VARIANT,
249
+ status: "upcoming",
250
+ isInvalid: false,
251
+ isConnectorComplete: false,
252
+ isDisabled: false,
253
+ isLast: false,
254
+ hasPanel: false,
255
+ },
256
+ });
257
+
258
+ export type StepsVariantProps = VariantProps<typeof stepsVariants>;
259
+
260
+ /**
261
+ * The theme token an indicator's contents are drawn in.
262
+ *
263
+ * The number is a `Text` and takes its colour as a class from the
264
+ * `indicatorLabel` slot; the check, the alert and the spinner are SVG and take
265
+ * theirs as a token through `IconDefaultsProvider`. Two mechanisms for one
266
+ * decision, so a test asserts every cell of this map is the class the slot
267
+ * emits — a drift would be a white number beside a black check.
268
+ */
269
+ export function resolveIndicatorForeground({
270
+ variant,
271
+ status,
272
+ isInvalid,
273
+ }: {
274
+ variant: StepsVariant;
275
+ status: StepStatus;
276
+ isInvalid: boolean;
277
+ }): string {
278
+ if (isInvalid) return "destructive-foreground";
279
+ if (status === "upcoming") return "muted-foreground";
280
+ if (variant === "primary") return status === "completed" ? "primary-foreground" : "foreground";
281
+ return status === "completed" ? "secondary-foreground" : "primary-foreground";
282
+ }
283
+
284
+ /**
285
+ * Where a step stands, from its index, the stepper's value and its own override.
286
+ *
287
+ * Before the value is `completed`, at it is `current`, after it `upcoming`.
288
+ * `completed: true` wins everywhere — a finished flow marks its last step done
289
+ * while still sitting on it. `completed: false` un-completes a step the value
290
+ * has passed (a skipped optional step) but never demotes the current one, which
291
+ * would leave a stepper with nowhere the user is.
292
+ *
293
+ * Pure, so the whole matrix is reachable from `bun test`.
294
+ */
295
+ export function resolveStepStatus({
296
+ step,
297
+ value,
298
+ completed,
299
+ }: {
300
+ step: number;
301
+ value: number;
302
+ completed?: boolean;
303
+ }): StepStatus {
304
+ if (completed === true) return "completed";
305
+ if (step === value) return "current";
306
+ if (completed === false) return "upcoming";
307
+ return step < value ? "completed" : "upcoming";
308
+ }
309
+
310
+ /**
311
+ * Whether the line leaving `step` is filled.
312
+ *
313
+ * Read off the value alone, never off either step's status: the line is drawn
314
+ * as two halves in two different items, and each half can see only its own
315
+ * step's overrides. The value is the one thing both ends agree on.
316
+ */
317
+ export function isConnectorComplete(step: number, value: number): boolean {
318
+ return step < value;
319
+ }
320
+
321
+ /** What one side of an indicator's track holds. */
322
+ export type StepConnectorSlot = "line" | "spacer" | "none";
323
+
324
+ /**
325
+ * Which connector halves a step draws.
326
+ *
327
+ * Horizontal steps always draw both sides so every indicator stays centred in
328
+ * its equal-width column — the outer ends become transparent spacers rather
329
+ * than disappearing. Vertical steps draw one line down from the indicator, and
330
+ * none from the last.
331
+ *
332
+ * This is how the connectors are "drawn for you": the root counts its items,
333
+ * so no caller places a separator, and none can be left dangling after the
334
+ * last step.
335
+ */
336
+ export function resolveStepConnectors({
337
+ step,
338
+ count,
339
+ orientation,
340
+ }: {
341
+ step: number;
342
+ count: number;
343
+ orientation: StepsOrientation;
344
+ }): { leading: StepConnectorSlot; trailing: StepConnectorSlot } {
345
+ const isFirst = step <= 0;
346
+ const isLast = step >= count - 1;
347
+ if (orientation === "vertical") return { leading: "none", trailing: isLast ? "none" : "line" };
348
+ return { leading: isFirst ? "spacer" : "line", trailing: isLast ? "spacer" : "line" };
349
+ }
350
+
351
+ /**
352
+ * Whether the stepper takes presses at all.
353
+ *
354
+ * A controlled `value` with no `onValueChange` is a progress display: nothing a
355
+ * press could do would stick, so its steps render as plain views and are not
356
+ * announced as buttons. `isReadOnly` states it outright either way.
357
+ */
358
+ export function resolveStepsReadOnly({
359
+ isControlled,
360
+ hasOnValueChange,
361
+ isReadOnly,
362
+ }: {
363
+ isControlled: boolean;
364
+ hasOnValueChange: boolean;
365
+ isReadOnly?: boolean;
366
+ }): boolean {
367
+ return isReadOnly ?? (isControlled && !hasOnValueChange);
368
+ }
369
+
370
+ /**
371
+ * How one step responds to touch.
372
+ *
373
+ * `static` renders a plain view — the stepper is read-only. `pressable` moves the
374
+ * value there. `blocked` is still a pressable, announced disabled: the step is
375
+ * disabled, loading, or ahead of the value in a linear flow, where the only way
376
+ * forward is the flow's own Continue button.
377
+ */
378
+ export type StepInteraction = "static" | "pressable" | "blocked";
379
+
380
+ export function resolveStepInteraction({
381
+ isReadOnly,
382
+ isLinear,
383
+ isDisabled,
384
+ isLoading,
385
+ step,
386
+ value,
387
+ }: {
388
+ isReadOnly: boolean;
389
+ isLinear: boolean;
390
+ isDisabled: boolean;
391
+ isLoading: boolean;
392
+ step: number;
393
+ value: number;
394
+ }): StepInteraction {
395
+ if (isReadOnly) return "static";
396
+ if (isDisabled || isLoading) return "blocked";
397
+ if (isLinear && step > value) return "blocked";
398
+ return "pressable";
399
+ }
400
+
401
+ /**
402
+ * Whether a press should write a new value. Re-pressing the current step is not
403
+ * a change, so the caller is never re-notified with the value it already holds.
404
+ */
405
+ export function shouldEmitStep(current: number, next: number): boolean {
406
+ return current !== next;
407
+ }
408
+
409
+ const STATUS_WORD: Record<StepStatus, string> = { completed: "completed", current: "current", upcoming: "upcoming" };
410
+
411
+ /**
412
+ * What VoiceOver and TalkBack read after a step's title.
413
+ *
414
+ * Handed to `accessibilityValue.text`, so the title stays the accessible name
415
+ * and this follows it: "Payment, Step 2 of 3, completed". One-based, because
416
+ * that is what the indicator draws. A count of zero means the root found no
417
+ * items to count — they sit behind a wrapper — and "of 0" would be a lie.
418
+ */
419
+ export function resolveStepAccessibilityValue({
420
+ step,
421
+ count,
422
+ status,
423
+ isInvalid = false,
424
+ isLoading = false,
425
+ }: {
426
+ step: number;
427
+ count: number;
428
+ status: StepStatus;
429
+ isInvalid?: boolean;
430
+ isLoading?: boolean;
431
+ }): string {
432
+ const position = count > 0 ? `Step ${step + 1} of ${count}` : `Step ${step + 1}`;
433
+ const parts = [position, STATUS_WORD[status]];
434
+ if (isInvalid) parts.push("has an error");
435
+ if (isLoading) parts.push("loading");
436
+ return parts.join(", ");
437
+ }
438
+
439
+ /** What the indicator draws inside its circle. */
440
+ export type StepIndicatorGlyph =
441
+ | { kind: "spinner" }
442
+ | { kind: "alert" }
443
+ | { kind: "check" }
444
+ | { kind: "number"; label: string };
445
+
446
+ /**
447
+ * What an indicator draws: a spinner while loading, an alert while invalid, a
448
+ * check once completed, and otherwise the step's one-based number.
449
+ *
450
+ * Loading outranks invalid because it is the more recent fact — a step that
451
+ * failed and is being retried is retrying.
452
+ */
453
+ export function resolveIndicatorGlyph({
454
+ step,
455
+ status,
456
+ isInvalid,
457
+ isLoading,
458
+ }: {
459
+ step: number;
460
+ status: StepStatus;
461
+ isInvalid: boolean;
462
+ isLoading: boolean;
463
+ }): StepIndicatorGlyph {
464
+ if (isLoading) return { kind: "spinner" };
465
+ if (isInvalid) return { kind: "alert" };
466
+ if (status === "completed") return { kind: "check" };
467
+ return { kind: "number", label: String(step + 1) };
468
+ }
@@ -0,0 +1,72 @@
1
+ # Surface
2
+
3
+ A rounded container on one of the theme's surface fills — the plane a card, an
4
+ alert or a settings panel is drawn on. A single styled `View` with a context, so
5
+ components built on it (`Card`, `Alert`) can read the plane they sit on.
6
+
7
+ `import { Surface } from "@delacour/react-native-ui/surface";`
8
+
9
+ ## Files
10
+
11
+ | File | What it holds |
12
+ | --- | --- |
13
+ | `index.ts` | → `@delacour/react-native-ui/surface` |
14
+ | `surface.tsx` | `Surface` |
15
+ | `surface.context.tsx` | `SurfaceProvider`, `useSurface()`, `useSurfaceContext()` |
16
+ | `surface.variants.ts` | The `tv()`, `SURFACE_FOREGROUND_TOKENS`, and the two pure resolvers — no RN imports |
17
+ | `surface.variants.test.ts` | |
18
+
19
+ ## Design
20
+
21
+ - **The API is `variant`, `padding`, `className` and `children`, and it stays
22
+ that small.** `Card` and `Alert` are built on this; every prop added here is
23
+ one they inherit and have to document. Anything else a caller needs — a
24
+ margin, a tinted border, a fixed height — is a class, merged last through the
25
+ `tv()`.
26
+ - **Variants**: `default`, `secondary`, `tertiary`, `transparent`.
27
+ **Padding**: `none`, `sm`, `md`, `lg` — `p-0`, `p-3`, `p-4`, `p-6`. `md`
28
+ rather than `default` for the middle step, so one step name means "the
29
+ default step" across the library.
30
+ - **The variants are a ladder, not a palette.** `default` is `bg-card` with a
31
+ `border-border` hairline; `secondary` and `tertiary` are the fills beneath it;
32
+ `transparent` keeps the padding and corner and paints nothing. Only `default`
33
+ colours its border: in light `--card` sits a percent above `--background`, and
34
+ without the hairline a card on a page is white on near-white. Every variant
35
+ still reserves the same one-point `border`, so changing variant never moves
36
+ the content.
37
+ - **A surface that names no variant steps from the one it sits in.**
38
+ `resolveSurfaceVariant` makes a top-level surface `default`, and a nested one
39
+ `default → secondary → tertiary → secondary`. `tertiary` is *quieter* than
40
+ `secondary` — half a step back toward the page, see `theme.css` — so a strict
41
+ ascent would run out of fills at the fourth level; what the resolver
42
+ guarantees, and the tests assert, is that a surface never resolves to the fill
43
+ it sits on. An explicit `variant` always wins.
44
+ - **A transparent surface passes its parent's plane through.**
45
+ `resolveSurfacePlane` publishes the plane a surface's children sit on, and for
46
+ `transparent` that is whatever it sits on itself. Without it, a surface inside
47
+ a transparent wrapper inside a card would restart at `default` — the card's
48
+ own fill — and disappear into it.
49
+ - **The corner is `rounded-lg`, with `border-continuous`.** `rounded-lg` is the
50
+ step `--radius` names, which is the card corner — see the corner-scale note in
51
+ [the package AGENTS.md](../../../AGENTS.md). `border-continuous` is Uniwind's
52
+ utility for iOS's `borderCurve: "continuous"`, the squircle rather than a
53
+ circular arc; Android ignores it. It is registered in `TW_MERGE_CONFIG` as a
54
+ class group of its own — unregistered, tailwind-merge read it as a border
55
+ *colour* and dropped it against `border-border`.
56
+ - **`padding="none"` clips, and nothing else does.** It is the step for content
57
+ bled to the edge — an image, a chart — and that content has to take the
58
+ corner with it. Clipping at every padding would crop anything a child draws
59
+ past its own box.
60
+ - **No shadow.** Nothing in this package casts one — React Native's shadow
61
+ props disagree between platforms — so depth here is carried by fill, and the
62
+ tests assert no variant emits one. A caller who wants one writes `shadow-md`
63
+ and gets the theme's `--shadow-md`.
64
+ - **No text treatment.** The root carries no `text-*` (rule 1), and the surface
65
+ publishes no `TextClassProvider` either: `Text`'s presets set their own
66
+ colour, so the cascade would reach only a bare `Text` — and a provider marks
67
+ every `Text` beneath it as nested, which strips `Text.Code` of its chip.
68
+ `SURFACE_FOREGROUND_TOKENS` maps each fill to its `X-foreground` token for a
69
+ caller that wants it, as a class or through `useThemeColor`.
70
+ - **No role.** A surface is layout, not a landmark; it adds nothing a screen
71
+ reader could announce. `ViewProps` pass through, so a caller that does mean
72
+ one — `accessibilityRole="summary"` — sets it.
@@ -0,0 +1,15 @@
1
+ export { Surface, type SurfaceProps } from "./surface";
2
+ export { type SurfaceContextValue, SurfaceProvider, useSurface, useSurfaceContext } from "./surface.context";
3
+ export {
4
+ resolveSurfacePlane,
5
+ resolveSurfaceVariant,
6
+ SURFACE_FILLED_VARIANTS,
7
+ SURFACE_FOREGROUND_TOKENS,
8
+ SURFACE_PADDINGS,
9
+ SURFACE_VARIANTS,
10
+ type SurfacePadding,
11
+ type SurfacePlane,
12
+ type SurfaceVariant,
13
+ type SurfaceVariantProps,
14
+ surfaceVariants,
15
+ } from "./surface.variants";
@@ -0,0 +1,54 @@
1
+ import { createContext, type ReactElement, type ReactNode, use } from "react";
2
+ import type { SurfacePlane, SurfaceVariant } from "./surface.variants";
3
+
4
+ export type SurfaceContextValue = {
5
+ /** The fill the enclosing surface resolved to — explicit, or stepped from its own parent. */
6
+ variant: SurfaceVariant;
7
+ /**
8
+ * The fill content inside the enclosing surface sits on. The same as
9
+ * `variant` unless that is `transparent`, which paints nothing and passes the
10
+ * plane beneath it through — `null` when there is none.
11
+ */
12
+ plane: SurfacePlane | null;
13
+ };
14
+
15
+ const SurfaceContext = createContext<SurfaceContextValue | null>(null);
16
+
17
+ /**
18
+ * Supplies the enclosing surface's fill to its subtree.
19
+ *
20
+ * Lives in its own module, importing nothing but `surface.variants`, so a
21
+ * component built on a surface can read it without importing `../surface` and
22
+ * closing a cycle through `surface.tsx`. See AGENTS.md rule 3.
23
+ */
24
+ export function SurfaceProvider({
25
+ value,
26
+ children,
27
+ }: {
28
+ value: SurfaceContextValue;
29
+ children: ReactNode;
30
+ }): ReactElement {
31
+ return <SurfaceContext value={value}>{children}</SurfaceContext>;
32
+ }
33
+ SurfaceProvider.displayName = "DelacourUI.Surface.Provider";
34
+
35
+ /** The enclosing surface's context, or null outside a `<Surface>`. */
36
+ export function useSurfaceContext(): SurfaceContextValue | null {
37
+ return use(SurfaceContext);
38
+ }
39
+
40
+ /**
41
+ * Reads the enclosing surface's fill.
42
+ *
43
+ * Lets a custom child match the plane it sits on — pick an icon colour from
44
+ * `SURFACE_FOREGROUND_TOKENS[plane]`, or a divider that reads against it —
45
+ * without the surface passing props down. Throws outside a `<Surface>`; use
46
+ * {@link useSurfaceContext} where the enclosing surface is optional.
47
+ */
48
+ export function useSurface(): SurfaceContextValue {
49
+ const context = useSurfaceContext();
50
+ if (!context) {
51
+ throw new Error("useSurface must be called inside a <Surface>.");
52
+ }
53
+ return context;
54
+ }
@@ -0,0 +1,70 @@
1
+ import { type ReactElement, type ReactNode, useMemo } from "react";
2
+ import { View, type ViewProps } from "react-native";
3
+ import { type SurfaceContextValue, SurfaceProvider, useSurfaceContext } from "./surface.context";
4
+ import {
5
+ resolveSurfacePlane,
6
+ resolveSurfaceVariant,
7
+ type SurfacePadding,
8
+ type SurfaceVariant,
9
+ surfaceVariants,
10
+ } from "./surface.variants";
11
+
12
+ export type SurfaceProps = ViewProps & {
13
+ /**
14
+ * The fill. Omitted, a top-level surface is the `default` card and a nested
15
+ * one steps to the next fill, so it never vanishes into the surface it sits on.
16
+ */
17
+ variant?: SurfaceVariant;
18
+ /** Inner spacing. `none` also clips, for content bled to the corners. */
19
+ padding?: SurfacePadding;
20
+ className?: string;
21
+ children?: ReactNode;
22
+ };
23
+
24
+ /**
25
+ * A rounded container on one of the theme's surface fills — the plane a card,
26
+ * an alert or a settings panel is drawn on.
27
+ *
28
+ * The variants are a ladder rather than a palette. `default` is the card,
29
+ * hairlined so it holds its edge on the page; `secondary` and `tertiary` are
30
+ * the fills beneath it; `transparent` keeps the padding and corner and paints
31
+ * nothing. A surface that names no variant reads the one it sits in and steps
32
+ * to the next fill, so nesting builds depth without naming a colour.
33
+ *
34
+ * It publishes no text treatment. `Text`'s presets already carry their own
35
+ * colour, so a class cascade would reach only a bare `Text` — and
36
+ * would mark every `Text` beneath it as nested, which strips `Text.Code` of its
37
+ * chip. A caller that wants the fill's own token reads
38
+ * `SURFACE_FOREGROUND_TOKENS[useSurface().plane]`.
39
+ *
40
+ * @example
41
+ * <Surface>
42
+ * <Text.Label>Account</Text.Label>
43
+ * <Surface className="mt-3">
44
+ * <Text color="muted">Signed in as sam@example.com</Text>
45
+ * </Surface>
46
+ * </Surface>
47
+ *
48
+ * @example
49
+ * <Surface padding="none">
50
+ * <Image className="h-40 w-full" source={cover} />
51
+ * </Surface>
52
+ */
53
+ export function Surface({ variant, padding = "md", className, children, ...props }: SurfaceProps): ReactElement {
54
+ const parent = useSurfaceContext();
55
+ const parentPlane = parent?.plane ?? null;
56
+
57
+ const resolved = resolveSurfaceVariant({ parentPlane, variant });
58
+ const plane = resolveSurfacePlane({ parentPlane, variant: resolved });
59
+
60
+ const context = useMemo<SurfaceContextValue>(() => ({ plane, variant: resolved }), [plane, resolved]);
61
+
62
+ return (
63
+ <SurfaceProvider value={context}>
64
+ <View className={surfaceVariants({ padding, variant: resolved }).root({ className })} {...props}>
65
+ {children}
66
+ </View>
67
+ </SurfaceProvider>
68
+ );
69
+ }
70
+ Surface.displayName = "DelacourUI.Surface";