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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (493) hide show
  1. package/README.md +10 -6
  2. package/package.json +49 -5
  3. package/src/components/accordion/AGENTS.md +21 -9
  4. package/src/components/accordion/accordion-content.tsx +7 -4
  5. package/src/components/accordion/accordion-item.tsx +15 -13
  6. package/src/components/accordion/accordion.context.tsx +11 -5
  7. package/src/components/accordion/accordion.variants.test.ts +64 -3
  8. package/src/components/accordion/accordion.variants.ts +33 -9
  9. package/src/components/alert/AGENTS.md +102 -0
  10. package/src/components/alert/alert-action.tsx +17 -0
  11. package/src/components/alert/alert-close-button.tsx +50 -0
  12. package/src/components/alert/alert-content.tsx +15 -0
  13. package/src/components/alert/alert-description.tsx +16 -0
  14. package/src/components/alert/alert-glyphs.ts +25 -0
  15. package/src/components/alert/alert-indicator.tsx +40 -0
  16. package/src/components/alert/alert-title.tsx +18 -0
  17. package/src/components/alert/alert.context.tsx +69 -0
  18. package/src/components/alert/alert.tsx +160 -0
  19. package/src/components/alert/alert.types.ts +10 -0
  20. package/src/components/alert/alert.variants.test.ts +218 -0
  21. package/src/components/alert/alert.variants.ts +180 -0
  22. package/src/components/alert/index.ts +23 -0
  23. package/src/components/avatar/AGENTS.md +106 -0
  24. package/src/components/avatar/avatar-badge.tsx +51 -0
  25. package/src/components/avatar/avatar-group.tsx +139 -0
  26. package/src/components/avatar/avatar.context.tsx +93 -0
  27. package/src/components/avatar/avatar.tsx +201 -0
  28. package/src/components/avatar/avatar.utils.test.ts +174 -0
  29. package/src/components/avatar/avatar.utils.ts +141 -0
  30. package/src/components/avatar/avatar.variants.test.ts +251 -0
  31. package/src/components/avatar/avatar.variants.ts +270 -0
  32. package/src/components/avatar/index.ts +39 -0
  33. package/src/components/badge/AGENTS.md +10 -0
  34. package/src/components/badge/badge-close-button.tsx +2 -2
  35. package/src/components/badge/badge.context.tsx +2 -1
  36. package/src/components/badge/badge.tsx +13 -4
  37. package/src/components/badge/badge.variants.test.ts +26 -0
  38. package/src/components/badge/badge.variants.ts +21 -0
  39. package/src/components/bottom-sheet/AGENTS.md +186 -231
  40. package/src/components/bottom-sheet/bottom-sheet-background.tsx +31 -22
  41. package/src/components/bottom-sheet/bottom-sheet-close.tsx +23 -30
  42. package/src/components/bottom-sheet/bottom-sheet-container.tsx +54 -208
  43. package/src/components/bottom-sheet/bottom-sheet-content.tsx +25 -48
  44. package/src/components/bottom-sheet/bottom-sheet-description.tsx +8 -2
  45. package/src/components/bottom-sheet/bottom-sheet-flat-list.tsx +65 -0
  46. package/src/components/bottom-sheet/bottom-sheet-footer.tsx +53 -122
  47. package/src/components/bottom-sheet/bottom-sheet-handle.tsx +31 -26
  48. package/src/components/bottom-sheet/bottom-sheet-legend-list.tsx +69 -0
  49. package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +25 -147
  50. package/src/components/bottom-sheet/bottom-sheet-provider.tsx +42 -0
  51. package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +38 -62
  52. package/src/components/bottom-sheet/bottom-sheet-section-list.tsx +58 -0
  53. package/src/components/bottom-sheet/bottom-sheet-steps.tsx +88 -0
  54. package/src/components/bottom-sheet/bottom-sheet-text-input.tsx +44 -0
  55. package/src/components/bottom-sheet/bottom-sheet-title.tsx +12 -8
  56. package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +19 -37
  57. package/src/components/bottom-sheet/bottom-sheet.tsx +113 -60
  58. package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +47 -87
  59. package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -126
  60. package/src/components/bottom-sheet/index.ts +39 -17
  61. package/src/components/bottom-sheet/use-bottom-sheet-input.ts +16 -115
  62. package/src/components/button/AGENTS.md +51 -11
  63. package/src/components/button/button-group.tsx +44 -8
  64. package/src/components/button/button.context.tsx +10 -0
  65. package/src/components/button/button.tsx +7 -1
  66. package/src/components/button/button.variants.test.ts +190 -0
  67. package/src/components/button/button.variants.ts +87 -6
  68. package/src/components/button/index.ts +2 -0
  69. package/src/components/calendar/AGENTS.md +142 -0
  70. package/src/components/calendar/calendar-caption.tsx +116 -0
  71. package/src/components/calendar/calendar-day.tsx +103 -0
  72. package/src/components/calendar/calendar-grid.tsx +183 -0
  73. package/src/components/calendar/calendar-header.tsx +34 -0
  74. package/src/components/calendar/calendar-nav.tsx +51 -0
  75. package/src/components/calendar/calendar-picker.tsx +128 -0
  76. package/src/components/calendar/calendar-weekdays.tsx +48 -0
  77. package/src/components/calendar/calendar.context.tsx +108 -0
  78. package/src/components/calendar/calendar.date.test.ts +456 -0
  79. package/src/components/calendar/calendar.date.ts +584 -0
  80. package/src/components/calendar/calendar.tsx +389 -0
  81. package/src/components/calendar/calendar.types.ts +87 -0
  82. package/src/components/calendar/calendar.variants.test.ts +363 -0
  83. package/src/components/calendar/calendar.variants.ts +426 -0
  84. package/src/components/calendar/index.ts +96 -0
  85. package/src/components/card/AGENTS.md +92 -0
  86. package/src/components/card/card-action.tsx +16 -0
  87. package/src/components/card/card-content.tsx +12 -0
  88. package/src/components/card/card-description.tsx +12 -0
  89. package/src/components/card/card-footer.tsx +30 -0
  90. package/src/components/card/card-header.tsx +45 -0
  91. package/src/components/card/card-title.tsx +25 -0
  92. package/src/components/card/card.context.tsx +64 -0
  93. package/src/components/card/card.tsx +100 -0
  94. package/src/components/card/card.types.ts +13 -0
  95. package/src/components/card/card.variants.test.ts +240 -0
  96. package/src/components/card/card.variants.ts +177 -0
  97. package/src/components/card/index.ts +15 -0
  98. package/src/components/chart/AGENTS.md +8 -0
  99. package/src/components/chart/chart.tsx +8 -1
  100. package/src/components/chip/AGENTS.md +113 -0
  101. package/src/components/chip/chip-close-button.tsx +48 -0
  102. package/src/components/chip/chip-end-content.tsx +15 -0
  103. package/src/components/chip/chip-label.tsx +21 -0
  104. package/src/components/chip/chip-start-content.tsx +16 -0
  105. package/src/components/chip/chip.context.tsx +66 -0
  106. package/src/components/chip/chip.tsx +268 -0
  107. package/src/components/chip/chip.types.ts +10 -0
  108. package/src/components/chip/chip.variants.test.ts +335 -0
  109. package/src/components/chip/chip.variants.ts +321 -0
  110. package/src/components/chip/index.ts +24 -0
  111. package/src/components/collapsible/AGENTS.md +97 -0
  112. package/src/components/collapsible/collapsible-content.tsx +97 -0
  113. package/src/components/collapsible/collapsible-description.tsx +17 -0
  114. package/src/components/collapsible/collapsible-indicator.tsx +92 -0
  115. package/src/components/collapsible/collapsible-title.tsx +17 -0
  116. package/src/components/collapsible/collapsible-trigger.tsx +133 -0
  117. package/src/components/collapsible/collapsible.context.tsx +91 -0
  118. package/src/components/collapsible/collapsible.tsx +167 -0
  119. package/src/components/collapsible/collapsible.types.ts +9 -0
  120. package/src/components/collapsible/collapsible.variants.test.ts +338 -0
  121. package/src/components/collapsible/collapsible.variants.ts +228 -0
  122. package/src/components/collapsible/index.ts +32 -0
  123. package/src/components/context-menu/AGENTS.md +103 -0
  124. package/src/components/context-menu/context-menu-content.tsx +64 -0
  125. package/src/components/context-menu/context-menu-item.tsx +22 -0
  126. package/src/components/context-menu/context-menu-preview.tsx +60 -0
  127. package/src/components/context-menu/context-menu-trigger.tsx +160 -0
  128. package/src/components/context-menu/context-menu.context.tsx +91 -0
  129. package/src/components/context-menu/context-menu.tsx +90 -0
  130. package/src/components/context-menu/context-menu.variants.test.ts +266 -0
  131. package/src/components/context-menu/context-menu.variants.ts +147 -0
  132. package/src/components/context-menu/index.ts +18 -0
  133. package/src/components/dialog/AGENTS.md +97 -0
  134. package/src/components/dialog/dialog-body.tsx +21 -0
  135. package/src/components/dialog/dialog-close.tsx +82 -0
  136. package/src/components/dialog/dialog-content.tsx +153 -0
  137. package/src/components/dialog/dialog-description.tsx +23 -0
  138. package/src/components/dialog/dialog-footer.tsx +32 -0
  139. package/src/components/dialog/dialog-header.tsx +20 -0
  140. package/src/components/dialog/dialog-title.tsx +42 -0
  141. package/src/components/dialog/dialog-trigger.tsx +54 -0
  142. package/src/components/dialog/dialog.context.tsx +90 -0
  143. package/src/components/dialog/dialog.tsx +119 -0
  144. package/src/components/dialog/dialog.variants.test.ts +224 -0
  145. package/src/components/dialog/dialog.variants.ts +173 -0
  146. package/src/components/dialog/index.ts +30 -0
  147. package/src/components/drawer/AGENTS.md +123 -0
  148. package/src/components/drawer/drawer-body.tsx +46 -0
  149. package/src/components/drawer/drawer-close.tsx +76 -0
  150. package/src/components/drawer/drawer-content.tsx +186 -0
  151. package/src/components/drawer/drawer-description.tsx +23 -0
  152. package/src/components/drawer/drawer-footer.tsx +21 -0
  153. package/src/components/drawer/drawer-header.tsx +42 -0
  154. package/src/components/drawer/drawer-title.tsx +35 -0
  155. package/src/components/drawer/drawer-trigger.tsx +54 -0
  156. package/src/components/drawer/drawer.context.tsx +114 -0
  157. package/src/components/drawer/drawer.tsx +119 -0
  158. package/src/components/drawer/drawer.variants.test.ts +392 -0
  159. package/src/components/drawer/drawer.variants.ts +244 -0
  160. package/src/components/drawer/index.ts +41 -0
  161. package/src/components/drawer/use-drawer-pan.ts +108 -0
  162. package/src/components/empty-state/AGENTS.md +90 -0
  163. package/src/components/empty-state/empty-state-content.tsx +17 -0
  164. package/src/components/empty-state/empty-state-description.tsx +12 -0
  165. package/src/components/empty-state/empty-state-header.tsx +17 -0
  166. package/src/components/empty-state/empty-state-media.tsx +56 -0
  167. package/src/components/empty-state/empty-state-title.tsx +18 -0
  168. package/src/components/empty-state/empty-state.context.tsx +65 -0
  169. package/src/components/empty-state/empty-state.tsx +84 -0
  170. package/src/components/empty-state/empty-state.types.ts +14 -0
  171. package/src/components/empty-state/empty-state.variants.test.ts +245 -0
  172. package/src/components/empty-state/empty-state.variants.ts +132 -0
  173. package/src/components/empty-state/index.ts +20 -0
  174. package/src/components/fab/AGENTS.md +119 -0
  175. package/src/components/fab/fab-action.tsx +118 -0
  176. package/src/components/fab/fab-group.tsx +214 -0
  177. package/src/components/fab/fab-label.tsx +18 -0
  178. package/src/components/fab/fab.context.tsx +125 -0
  179. package/src/components/fab/fab.tsx +156 -0
  180. package/src/components/fab/fab.types.ts +17 -0
  181. package/src/components/fab/fab.variants.test.ts +274 -0
  182. package/src/components/fab/fab.variants.ts +234 -0
  183. package/src/components/fab/index.ts +37 -0
  184. package/src/components/feedback/AGENTS.md +95 -0
  185. package/src/components/feedback/feedback-action.tsx +16 -0
  186. package/src/components/feedback/feedback-cancel.tsx +34 -0
  187. package/src/components/feedback/feedback-close.tsx +21 -0
  188. package/src/components/feedback/feedback-content.tsx +24 -0
  189. package/src/components/feedback/feedback-field.tsx +70 -0
  190. package/src/components/feedback/feedback-footer.tsx +21 -0
  191. package/src/components/feedback/feedback-panel.tsx +54 -0
  192. package/src/components/feedback/feedback-submit.tsx +74 -0
  193. package/src/components/feedback/feedback-title.tsx +34 -0
  194. package/src/components/feedback/feedback-trigger.tsx +20 -0
  195. package/src/components/feedback/feedback.context.tsx +73 -0
  196. package/src/components/feedback/feedback.tsx +135 -0
  197. package/src/components/feedback/feedback.variants.test.ts +185 -0
  198. package/src/components/feedback/feedback.variants.ts +128 -0
  199. package/src/components/feedback/index.ts +24 -0
  200. package/src/components/field/AGENTS.md +19 -0
  201. package/src/components/field/field-label.tsx +19 -5
  202. package/src/components/field/field.context.tsx +17 -0
  203. package/src/components/field/field.tsx +15 -2
  204. package/src/components/field/field.variants.test.ts +24 -0
  205. package/src/components/field/field.variants.ts +20 -0
  206. package/src/components/field/index.ts +1 -0
  207. package/src/components/input/AGENTS.md +5 -0
  208. package/src/components/input/input.variants.test.ts +32 -0
  209. package/src/components/input/input.variants.ts +13 -1
  210. package/src/components/item/AGENTS.md +92 -0
  211. package/src/components/item/index.ts +25 -0
  212. package/src/components/item/item-actions.tsx +28 -0
  213. package/src/components/item/item-content.tsx +15 -0
  214. package/src/components/item/item-description.tsx +12 -0
  215. package/src/components/item/item-footer.tsx +13 -0
  216. package/src/components/item/item-group.tsx +32 -0
  217. package/src/components/item/item-header.tsx +13 -0
  218. package/src/components/item/item-media.tsx +37 -0
  219. package/src/components/item/item-separator.tsx +13 -0
  220. package/src/components/item/item-title.tsx +17 -0
  221. package/src/components/item/item.context.tsx +66 -0
  222. package/src/components/item/item.tsx +218 -0
  223. package/src/components/item/item.types.ts +12 -0
  224. package/src/components/item/item.variants.test.ts +263 -0
  225. package/src/components/item/item.variants.ts +200 -0
  226. package/src/components/kpi/AGENTS.md +125 -0
  227. package/src/components/kpi/index.ts +45 -0
  228. package/src/components/kpi/kpi-action.tsx +12 -0
  229. package/src/components/kpi/kpi-content.tsx +35 -0
  230. package/src/components/kpi/kpi-footer.tsx +52 -0
  231. package/src/components/kpi/kpi-group.tsx +70 -0
  232. package/src/components/kpi/kpi-header.tsx +25 -0
  233. package/src/components/kpi/kpi-icon.tsx +28 -0
  234. package/src/components/kpi/kpi-sparkline.tsx +145 -0
  235. package/src/components/kpi/kpi-stat.tsx +20 -0
  236. package/src/components/kpi/kpi-title.tsx +19 -0
  237. package/src/components/kpi/kpi-trend.tsx +105 -0
  238. package/src/components/kpi/kpi-value.tsx +32 -0
  239. package/src/components/kpi/kpi.context.tsx +111 -0
  240. package/src/components/kpi/kpi.tsx +154 -0
  241. package/src/components/kpi/kpi.types.ts +9 -0
  242. package/src/components/kpi/kpi.variants.test.ts +428 -0
  243. package/src/components/kpi/kpi.variants.ts +347 -0
  244. package/src/components/label/AGENTS.md +89 -0
  245. package/src/components/label/index.ts +10 -0
  246. package/src/components/label/label.tsx +97 -0
  247. package/src/components/label/label.variants.test.ts +176 -0
  248. package/src/components/label/label.variants.ts +123 -0
  249. package/src/components/menu/AGENTS.md +176 -0
  250. package/src/components/menu/index.ts +44 -0
  251. package/src/components/menu/menu-background.tsx +17 -0
  252. package/src/components/menu/menu-checkbox-item.tsx +57 -0
  253. package/src/components/menu/menu-content.tsx +303 -0
  254. package/src/components/menu/menu-item.tsx +162 -0
  255. package/src/components/menu/menu-label.tsx +14 -0
  256. package/src/components/menu/menu-radio-group.tsx +50 -0
  257. package/src/components/menu/menu-radio-item.tsx +57 -0
  258. package/src/components/menu/menu-separator.tsx +11 -0
  259. package/src/components/menu/menu-sub-content.tsx +72 -0
  260. package/src/components/menu/menu-sub-trigger.tsx +50 -0
  261. package/src/components/menu/menu-sub.tsx +75 -0
  262. package/src/components/menu/menu-trigger.tsx +57 -0
  263. package/src/components/menu/menu.context.tsx +214 -0
  264. package/src/components/menu/menu.tsx +88 -0
  265. package/src/components/menu/menu.types.ts +33 -0
  266. package/src/components/menu/menu.variants.test.ts +400 -0
  267. package/src/components/menu/menu.variants.ts +353 -0
  268. package/src/components/meter/AGENTS.md +135 -0
  269. package/src/components/meter/index.ts +43 -0
  270. package/src/components/meter/meter-fill.tsx +19 -0
  271. package/src/components/meter/meter-header.tsx +17 -0
  272. package/src/components/meter/meter-label.tsx +18 -0
  273. package/src/components/meter/meter-output.tsx +42 -0
  274. package/src/components/meter/meter-segments.tsx +83 -0
  275. package/src/components/meter/meter-track.tsx +16 -0
  276. package/src/components/meter/meter.context.tsx +62 -0
  277. package/src/components/meter/meter.tsx +286 -0
  278. package/src/components/meter/meter.types.ts +30 -0
  279. package/src/components/meter/meter.variants.test.ts +430 -0
  280. package/src/components/meter/meter.variants.ts +347 -0
  281. package/src/components/overlay/AGENTS.md +89 -0
  282. package/src/components/overlay/index.ts +27 -0
  283. package/src/components/overlay/overlay-portal.tsx +84 -0
  284. package/src/components/overlay/overlay-presence.test.ts +46 -0
  285. package/src/components/overlay/overlay-presence.ts +49 -0
  286. package/src/components/overlay/overlay-registry.test.ts +96 -0
  287. package/src/components/overlay/overlay-registry.ts +99 -0
  288. package/src/components/overlay/overlay-scrim.tsx +49 -0
  289. package/src/components/overlay/overlay.context.tsx +48 -0
  290. package/src/components/overlay/overlay.tsx +118 -0
  291. package/src/components/overlay/overlay.variants.test.ts +37 -0
  292. package/src/components/overlay/overlay.variants.ts +33 -0
  293. package/src/components/overlay/use-overlay-back-handler.ts +34 -0
  294. package/src/components/overlay/use-overlay-presence.ts +87 -0
  295. package/src/components/popover/AGENTS.md +138 -0
  296. package/src/components/popover/index.ts +48 -0
  297. package/src/components/popover/popover-anchor.tsx +49 -0
  298. package/src/components/popover/popover-arrow.tsx +89 -0
  299. package/src/components/popover/popover-close.tsx +73 -0
  300. package/src/components/popover/popover-content.tsx +235 -0
  301. package/src/components/popover/popover-description.tsx +18 -0
  302. package/src/components/popover/popover-title.tsx +33 -0
  303. package/src/components/popover/popover-trigger.tsx +56 -0
  304. package/src/components/popover/popover.context.tsx +71 -0
  305. package/src/components/popover/popover.position.test.ts +354 -0
  306. package/src/components/popover/popover.position.ts +286 -0
  307. package/src/components/popover/popover.tsx +123 -0
  308. package/src/components/popover/popover.variants.test.ts +135 -0
  309. package/src/components/popover/popover.variants.ts +85 -0
  310. package/src/components/popover/use-anchor-measure.ts +76 -0
  311. package/src/components/popover/use-anchored-content.ts +219 -0
  312. package/src/components/pressable/AGENTS.md +11 -1
  313. package/src/components/pressable/pressable.tsx +7 -2
  314. package/src/components/pressable/pressable.variants.test.ts +29 -0
  315. package/src/components/pressable/pressable.variants.ts +17 -0
  316. package/src/components/progress/AGENTS.md +120 -0
  317. package/src/components/progress/index.ts +40 -0
  318. package/src/components/progress/progress-fill.tsx +152 -0
  319. package/src/components/progress/progress-header.tsx +19 -0
  320. package/src/components/progress/progress-label.tsx +27 -0
  321. package/src/components/progress/progress-output.tsx +47 -0
  322. package/src/components/progress/progress-track.tsx +38 -0
  323. package/src/components/progress/progress.context.tsx +79 -0
  324. package/src/components/progress/progress.tsx +171 -0
  325. package/src/components/progress/progress.types.ts +23 -0
  326. package/src/components/progress/progress.variants.test.ts +335 -0
  327. package/src/components/progress/progress.variants.ts +284 -0
  328. package/src/components/progress-button/AGENTS.md +116 -0
  329. package/src/components/progress-button/index.ts +36 -0
  330. package/src/components/progress-button/progress-button-done.tsx +33 -0
  331. package/src/components/progress-button/progress-button-label.tsx +27 -0
  332. package/src/components/progress-button/progress-button.context.tsx +93 -0
  333. package/src/components/progress-button/progress-button.tsx +414 -0
  334. package/src/components/progress-button/progress-button.variants.test.ts +314 -0
  335. package/src/components/progress-button/progress-button.variants.ts +236 -0
  336. package/src/components/provider/AGENTS.md +38 -25
  337. package/src/components/provider/provider.tsx +38 -11
  338. package/src/components/radio/AGENTS.md +8 -10
  339. package/src/components/radio/radio.variants.test.ts +5 -6
  340. package/src/components/radio/radio.variants.ts +6 -7
  341. package/src/components/rating/AGENTS.md +143 -0
  342. package/src/components/rating/index.ts +43 -0
  343. package/src/components/rating/rating-output.tsx +39 -0
  344. package/src/components/rating/rating-star.tsx +70 -0
  345. package/src/components/rating/rating-stars.tsx +219 -0
  346. package/src/components/rating/rating.context.tsx +79 -0
  347. package/src/components/rating/rating.tsx +163 -0
  348. package/src/components/rating/rating.variants.test.ts +347 -0
  349. package/src/components/rating/rating.variants.ts +354 -0
  350. package/src/components/selection-mode/AGENTS.md +155 -0
  351. package/src/components/selection-mode/index.ts +43 -0
  352. package/src/components/selection-mode/selection-mode-action.tsx +65 -0
  353. package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
  354. package/src/components/selection-mode/selection-mode-group.tsx +123 -0
  355. package/src/components/selection-mode/selection-mode-header.tsx +85 -0
  356. package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
  357. package/src/components/selection-mode/selection-mode-item.tsx +182 -0
  358. package/src/components/selection-mode/selection-mode.context.tsx +89 -0
  359. package/src/components/selection-mode/selection-mode.tsx +207 -0
  360. package/src/components/selection-mode/selection-mode.types.ts +36 -0
  361. package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
  362. package/src/components/selection-mode/selection-mode.variants.ts +329 -0
  363. package/src/components/skeleton/AGENTS.md +120 -0
  364. package/src/components/skeleton/index.ts +15 -0
  365. package/src/components/skeleton/skeleton-clock.ts +48 -0
  366. package/src/components/skeleton/skeleton-group.tsx +64 -0
  367. package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
  368. package/src/components/skeleton/skeleton.context.tsx +45 -0
  369. package/src/components/skeleton/skeleton.tsx +227 -0
  370. package/src/components/skeleton/skeleton.variants.test.ts +245 -0
  371. package/src/components/skeleton/skeleton.variants.ts +222 -0
  372. package/src/components/slide-button/AGENTS.md +122 -0
  373. package/src/components/slide-button/index.ts +43 -0
  374. package/src/components/slide-button/slide-button-label.tsx +70 -0
  375. package/src/components/slide-button/slide-button-thumb.tsx +93 -0
  376. package/src/components/slide-button/slide-button.context.tsx +76 -0
  377. package/src/components/slide-button/slide-button.tsx +457 -0
  378. package/src/components/slide-button/slide-button.variants.test.ts +288 -0
  379. package/src/components/slide-button/slide-button.variants.ts +324 -0
  380. package/src/components/slider/index.ts +2 -0
  381. package/src/components/slider/slider-thumb.tsx +33 -1
  382. package/src/components/slider/slider.variants.test.ts +52 -0
  383. package/src/components/slider/slider.variants.ts +56 -2
  384. package/src/components/stack-card/AGENTS.md +169 -0
  385. package/src/components/stack-card/index.ts +37 -0
  386. package/src/components/stack-card/stack-card-action.tsx +66 -0
  387. package/src/components/stack-card/stack-card-actions.tsx +18 -0
  388. package/src/components/stack-card/stack-card-card.tsx +71 -0
  389. package/src/components/stack-card/stack-card-empty.tsx +30 -0
  390. package/src/components/stack-card/stack-card-slot.tsx +90 -0
  391. package/src/components/stack-card/stack-card-stamp.tsx +76 -0
  392. package/src/components/stack-card/stack-card.context.tsx +117 -0
  393. package/src/components/stack-card/stack-card.tsx +557 -0
  394. package/src/components/stack-card/stack-card.types.ts +26 -0
  395. package/src/components/stack-card/stack-card.variants.test.ts +355 -0
  396. package/src/components/stack-card/stack-card.variants.ts +360 -0
  397. package/src/components/steps/AGENTS.md +153 -0
  398. package/src/components/steps/index.ts +45 -0
  399. package/src/components/steps/steps-connector.tsx +31 -0
  400. package/src/components/steps/steps-description.tsx +26 -0
  401. package/src/components/steps/steps-indicator.tsx +106 -0
  402. package/src/components/steps/steps-item.tsx +225 -0
  403. package/src/components/steps/steps-panel.tsx +41 -0
  404. package/src/components/steps/steps-title.tsx +32 -0
  405. package/src/components/steps/steps-track.tsx +59 -0
  406. package/src/components/steps/steps.context.tsx +123 -0
  407. package/src/components/steps/steps.tsx +166 -0
  408. package/src/components/steps/steps.variants.test.ts +388 -0
  409. package/src/components/steps/steps.variants.ts +468 -0
  410. package/src/components/surface/AGENTS.md +82 -0
  411. package/src/components/surface/index.ts +15 -0
  412. package/src/components/surface/surface.context.tsx +56 -0
  413. package/src/components/surface/surface.tsx +87 -0
  414. package/src/components/surface/surface.variants.test.ts +227 -0
  415. package/src/components/surface/surface.variants.ts +165 -0
  416. package/src/components/swipe/AGENTS.md +140 -0
  417. package/src/components/swipe/index.ts +31 -0
  418. package/src/components/swipe/swipe-action.tsx +99 -0
  419. package/src/components/swipe/swipe-end.tsx +14 -0
  420. package/src/components/swipe/swipe-group.tsx +55 -0
  421. package/src/components/swipe/swipe-panel.tsx +55 -0
  422. package/src/components/swipe/swipe-start.tsx +15 -0
  423. package/src/components/swipe/swipe.context.tsx +135 -0
  424. package/src/components/swipe/swipe.tsx +393 -0
  425. package/src/components/swipe/swipe.types.ts +7 -0
  426. package/src/components/swipe/swipe.variants.test.ts +337 -0
  427. package/src/components/swipe/swipe.variants.ts +290 -0
  428. package/src/components/switch/switch.variants.ts +2 -2
  429. package/src/components/tabs/AGENTS.md +4 -3
  430. package/src/components/tabs/tabs.variants.test.ts +1 -1
  431. package/src/components/tabs/tabs.variants.ts +4 -6
  432. package/src/components/text/AGENTS.md +8 -2
  433. package/src/components/text/text.tsx +7 -0
  434. package/src/components/text/text.variants.test.ts +24 -1
  435. package/src/components/text/text.variants.ts +5 -0
  436. package/src/components/textarea/AGENTS.md +89 -0
  437. package/src/components/textarea/index.ts +16 -0
  438. package/src/components/textarea/textarea.tsx +157 -0
  439. package/src/components/textarea/textarea.variants.test.ts +219 -0
  440. package/src/components/textarea/textarea.variants.ts +171 -0
  441. package/src/components/toast/AGENTS.md +140 -0
  442. package/src/components/toast/index.ts +74 -0
  443. package/src/components/toast/toast-action.tsx +59 -0
  444. package/src/components/toast/toast-api.ts +32 -0
  445. package/src/components/toast/toast-close.tsx +44 -0
  446. package/src/components/toast/toast-content.tsx +11 -0
  447. package/src/components/toast/toast-description.tsx +11 -0
  448. package/src/components/toast/toast-indicator.tsx +34 -0
  449. package/src/components/toast/toast-item.tsx +308 -0
  450. package/src/components/toast/toast-title.tsx +13 -0
  451. package/src/components/toast/toast-viewport.tsx +135 -0
  452. package/src/components/toast/toast.context.tsx +69 -0
  453. package/src/components/toast/toast.store.test.ts +259 -0
  454. package/src/components/toast/toast.store.ts +338 -0
  455. package/src/components/toast/toast.timer.test.ts +64 -0
  456. package/src/components/toast/toast.timer.ts +51 -0
  457. package/src/components/toast/toast.tsx +132 -0
  458. package/src/components/toast/toast.variants.test.ts +393 -0
  459. package/src/components/toast/toast.variants.ts +304 -0
  460. package/src/components/toggle-button/AGENTS.md +106 -0
  461. package/src/components/toggle-button/index.ts +30 -0
  462. package/src/components/toggle-button/toggle-button-group.tsx +156 -0
  463. package/src/components/toggle-button/toggle-button-label.tsx +19 -0
  464. package/src/components/toggle-button/toggle-button.context.tsx +130 -0
  465. package/src/components/toggle-button/toggle-button.tsx +172 -0
  466. package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
  467. package/src/components/toggle-button/toggle-button.variants.ts +160 -0
  468. package/src/components/tooltip/AGENTS.md +133 -0
  469. package/src/components/tooltip/index.ts +28 -0
  470. package/src/components/tooltip/tooltip-arrow.tsx +40 -0
  471. package/src/components/tooltip/tooltip-content.tsx +179 -0
  472. package/src/components/tooltip/tooltip-description.tsx +19 -0
  473. package/src/components/tooltip/tooltip-text.tsx +21 -0
  474. package/src/components/tooltip/tooltip-title.tsx +20 -0
  475. package/src/components/tooltip/tooltip-trigger.tsx +103 -0
  476. package/src/components/tooltip/tooltip.context.tsx +68 -0
  477. package/src/components/tooltip/tooltip.tsx +213 -0
  478. package/src/components/tooltip/tooltip.variants.test.ts +231 -0
  479. package/src/components/tooltip/tooltip.variants.ts +177 -0
  480. package/src/hooks/use-calm-motion.tsx +31 -0
  481. package/src/lib/calm-motion.test.ts +17 -0
  482. package/src/lib/calm-motion.ts +23 -0
  483. package/src/lib/cn.parity.test.ts +62 -0
  484. package/src/lib/cn.test.ts +14 -0
  485. package/src/lib/cn.ts +11 -6
  486. package/src/no-gorhom.test.ts +54 -0
  487. package/src/styles/geometry.test.ts +1 -0
  488. package/src/styles/theme.css +4 -0
  489. package/src/styles/tokens.css +44 -0
  490. package/src/styles/tokens.test.ts +56 -0
  491. package/src/styles/tokens.ts +29 -1
  492. package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
  493. package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
@@ -0,0 +1,50 @@
1
+ import type { ReactElement } from "react";
2
+ import { IconCrossSmall } from "../../icons/central";
3
+ import { Icon } from "../icon";
4
+ import { Pressable, type PressableProps } from "../pressable";
5
+ import { useAlertPart } from "./alert.context";
6
+ import { alertVariants } from "./alert.variants";
7
+
8
+ export type AlertCloseButtonProps = Omit<PressableProps, "asChild" | "busy" | "children">;
9
+
10
+ /**
11
+ * The alert's trailing dismiss control.
12
+ *
13
+ * The root composes one in whenever `isDismissible` is set, so reach for this
14
+ * by hand only to place it somewhere else. Pressing it calls the alert's
15
+ * `dismiss` and then the caller's own `onPress`.
16
+ *
17
+ * The glyph is drawn in `muted-foreground` rather than the status colour: the
18
+ * control is about the alert, not part of what it says, and a red cross beside
19
+ * a red title reads as a second warning. `fade` rather than `scale` — a spring
20
+ * on a glyph this small reads as a jitter. `hitSlop` lifts the target to the
21
+ * 44-point minimum the glyph alone falls short of.
22
+ */
23
+ export function AlertCloseButton({
24
+ accessibilityLabel = "Dismiss",
25
+ className,
26
+ feedback = "fade",
27
+ hitSlop = 10,
28
+ onPress,
29
+ ...props
30
+ }: AlertCloseButtonProps): ReactElement {
31
+ const { size, dismiss } = useAlertPart("Alert.CloseButton");
32
+
33
+ return (
34
+ <Pressable
35
+ accessibilityLabel={accessibilityLabel}
36
+ accessibilityRole="button"
37
+ className={alertVariants({ size }).closeButton({ className })}
38
+ feedback={feedback}
39
+ hitSlop={hitSlop}
40
+ onPress={() => {
41
+ dismiss();
42
+ onPress?.();
43
+ }}
44
+ {...props}
45
+ >
46
+ <Icon color="muted-foreground" icon={IconCrossSmall} />
47
+ </Pressable>
48
+ );
49
+ }
50
+ AlertCloseButton.displayName = "DelacourUI.Alert.CloseButton";
@@ -0,0 +1,15 @@
1
+ import type { ReactElement } from "react";
2
+ import { View } from "react-native";
3
+ import type { AlertSlotProps } from "./alert.types";
4
+ import { alertVariants } from "./alert.variants";
5
+
6
+ /**
7
+ * The column holding the title, the description and any action.
8
+ *
9
+ * `flex-1` with `min-w-0` is what keeps a long description wrapping inside the
10
+ * alert instead of pushing the close control off its edge.
11
+ */
12
+ export function AlertContent({ className, ...props }: AlertSlotProps): ReactElement {
13
+ return <View className={alertVariants().content({ className })} {...props} />;
14
+ }
15
+ AlertContent.displayName = "DelacourUI.Alert.Content";
@@ -0,0 +1,16 @@
1
+ import type { ReactElement } from "react";
2
+ import { Text, type TextPresetProps } from "../text";
3
+ import { useAlertPart } from "./alert.context";
4
+ import { alertVariants } from "./alert.variants";
5
+
6
+ export type AlertDescriptionProps = TextPresetProps;
7
+
8
+ /**
9
+ * The alert's body text — always muted, whatever the status, so a long
10
+ * explanation under a red title never shouts.
11
+ */
12
+ export function AlertDescription({ className, ...props }: AlertDescriptionProps): ReactElement {
13
+ const { size } = useAlertPart("Alert.Description");
14
+ return <Text className={alertVariants({ size }).description({ className })} {...props} />;
15
+ }
16
+ AlertDescription.displayName = "DelacourUI.Alert.Description";
@@ -0,0 +1,25 @@
1
+ import { IconCircleCheck, IconCircleInfo, IconExclamationCircle, IconExclamationTriangle } from "../../icons/central";
2
+ import type { IconComponent } from "../icon";
3
+ import type { AlertStatus } from "./alert.variants";
4
+
5
+ /**
6
+ * The glyph each status draws.
7
+ *
8
+ * `warning` and `destructive` differ in shape as well as colour — a triangle
9
+ * against a circle — so the two stay distinguishable to anyone who cannot tell
10
+ * amber from red. `default` shares `info`'s glyph: a neutral note is still a
11
+ * note, and a status of its own would need a meaning it does not have.
12
+ *
13
+ * A leaf of its own rather than a constant in `alert-indicator.tsx`: `Toast`
14
+ * draws the same glyph per status, so the two read as one family, and it may
15
+ * import a leaf across folders where it may not import a part (package rule 3).
16
+ * Not in `alert.variants.ts` because the glyphs are React Native SVG components,
17
+ * and that file must stay importable from `bun test`.
18
+ */
19
+ export const ALERT_GLYPHS: Record<AlertStatus, IconComponent> = {
20
+ default: IconCircleInfo,
21
+ info: IconCircleInfo,
22
+ success: IconCircleCheck,
23
+ warning: IconExclamationTriangle,
24
+ destructive: IconExclamationCircle,
25
+ };
@@ -0,0 +1,40 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import { View, type ViewProps } from "react-native";
3
+ import { Icon } from "../icon";
4
+ import { useAlertPart } from "./alert.context";
5
+ import { alertVariants } from "./alert.variants";
6
+ import { ALERT_GLYPHS } from "./alert-glyphs";
7
+
8
+ export type AlertIndicatorProps = ViewProps & {
9
+ className?: string;
10
+ /** Replaces the status glyph — a `Spinner`, an `Icon` of your own. It inherits the alert's icon size and colour. */
11
+ children?: ReactNode;
12
+ };
13
+
14
+ /**
15
+ * The alert's leading glyph, picked from its status.
16
+ *
17
+ * Carries the title's line height as its own height and centres the glyph in
18
+ * it, so the glyph sits level with the title's first line however far the
19
+ * description wraps.
20
+ *
21
+ * Hidden from assistive technology. The title says what happened, and a screen
22
+ * reader announcing "image" before it adds nothing. Children replace the glyph
23
+ * and inherit the alert's icon size and colour, so `<Spinner />` or
24
+ * `<Icon icon={IconCloud} />` needs nothing else.
25
+ */
26
+ export function AlertIndicator({ className, children, ...props }: AlertIndicatorProps): ReactElement {
27
+ const { status, size } = useAlertPart("Alert.Indicator");
28
+
29
+ return (
30
+ <View
31
+ accessibilityElementsHidden
32
+ className={alertVariants({ size }).indicator({ className })}
33
+ importantForAccessibility="no-hide-descendants"
34
+ {...props}
35
+ >
36
+ {children ?? <Icon icon={ALERT_GLYPHS[status]} />}
37
+ </View>
38
+ );
39
+ }
40
+ AlertIndicator.displayName = "DelacourUI.Alert.Indicator";
@@ -0,0 +1,18 @@
1
+ import type { ReactElement } from "react";
2
+ import { Text, type TextPresetProps } from "../text";
3
+ import { useAlertPart } from "./alert.context";
4
+ import { alertVariants } from "./alert.variants";
5
+
6
+ export type AlertTitleProps = TextPresetProps;
7
+
8
+ /**
9
+ * The alert's heading, coloured by its status.
10
+ *
11
+ * The colour is the same token the indicator's glyph reads, so the two are
12
+ * always one shade — a test pins the pair.
13
+ */
14
+ export function AlertTitle({ className, ...props }: AlertTitleProps): ReactElement {
15
+ const { status, size } = useAlertPart("Alert.Title");
16
+ return <Text className={alertVariants({ size, status }).title({ className })} {...props} />;
17
+ }
18
+ AlertTitle.displayName = "DelacourUI.Alert.Title";
@@ -0,0 +1,69 @@
1
+ import { createContext, type ReactElement, type ReactNode, use } from "react";
2
+ import type { AlertSize, AlertStatus, AlertVariant } from "./alert.variants";
3
+
4
+ export type AlertContextValue = {
5
+ /** What the alert says about the thing it describes. */
6
+ status: AlertStatus;
7
+ /** How the alert's surface is painted. */
8
+ variant: AlertVariant;
9
+ /** Size of the alert. */
10
+ size: AlertSize;
11
+ /**
12
+ * Closes the alert — hides it when uncontrolled, and reports `false` through
13
+ * `onOpenChange` either way. Lets an action inside the alert ("Got it")
14
+ * dismiss it without the caller threading a setter down.
15
+ */
16
+ dismiss: () => void;
17
+ };
18
+
19
+ const AlertContext = createContext<AlertContextValue | null>(null);
20
+
21
+ /**
22
+ * Supplies the enclosing alert's status, variant, size and `dismiss` to its
23
+ * subtree.
24
+ *
25
+ * Lives in its own module, importing nothing but `alert.variants`, so a part
26
+ * can read it without importing `./alert`. That import would close a cycle, and
27
+ * Metro serves a partially initialised module for a cycle — leaving the context
28
+ * `undefined` at import time and red-boxing the app on a cold start.
29
+ */
30
+ export function AlertProvider({ value, children }: { value: AlertContextValue; children: ReactNode }): ReactElement {
31
+ return <AlertContext value={value}>{children}</AlertContext>;
32
+ }
33
+ AlertProvider.displayName = "DelacourUI.Alert.Provider";
34
+
35
+ /** The enclosing alert's context, or null outside an `<Alert>`. */
36
+ export function useAlertContext(): AlertContextValue | null {
37
+ return use(AlertContext);
38
+ }
39
+
40
+ /**
41
+ * Reads the enclosing alert's status, variant, size and `dismiss`.
42
+ *
43
+ * Lets a custom child match the alert — or close it — without the alert passing
44
+ * props down. Throws outside an `<Alert>`; use {@link useAlertContext} where the
45
+ * enclosing alert is optional.
46
+ */
47
+ export function useAlert(): AlertContextValue {
48
+ const context = useAlertContext();
49
+ if (!context) {
50
+ throw new Error("useAlert must be called inside an <Alert>.");
51
+ }
52
+ return context;
53
+ }
54
+
55
+ /**
56
+ * The enclosing alert's context, for a compound part that cannot work without
57
+ * one.
58
+ *
59
+ * Internal: deliberately not re-exported from `index.ts`. A caller outside the
60
+ * library wants {@link useAlert}, whose error message names the hook rather than
61
+ * a part.
62
+ */
63
+ export function useAlertPart(component: string): AlertContextValue {
64
+ const context = useAlertContext();
65
+ if (!context) {
66
+ throw new Error(`${component} must be rendered inside an <Alert>.`);
67
+ }
68
+ return context;
69
+ }
@@ -0,0 +1,160 @@
1
+ import { type ReactElement, type ReactNode, useCallback, useMemo } from "react";
2
+ import Animated, { FadeOut } from "react-native-reanimated";
3
+ import { useControllableState } from "../../hooks/use-controllable-state";
4
+ import { IconDefaultsProvider } from "../icon";
5
+ import { Surface, type SurfaceProps } from "../surface";
6
+ import { type AlertContextValue, AlertProvider } from "./alert.context";
7
+ import {
8
+ ALERT_FOREGROUND_TOKEN,
9
+ ALERT_SURFACE_PADDING,
10
+ type AlertSize,
11
+ type AlertStatus,
12
+ type AlertVariant,
13
+ alertVariants,
14
+ resolveAlertLiveRegion,
15
+ resolveAlertSurfaceVariant,
16
+ } from "./alert.variants";
17
+ import { AlertAction } from "./alert-action";
18
+ import { AlertCloseButton } from "./alert-close-button";
19
+ import { AlertContent } from "./alert-content";
20
+ import { AlertDescription } from "./alert-description";
21
+ import { AlertIndicator } from "./alert-indicator";
22
+ import { AlertTitle } from "./alert-title";
23
+
24
+ /** A dismissed alert fades rather than vanishing, so the layout below it has a beat to follow. */
25
+ const EXITING = FadeOut.duration(150);
26
+
27
+ export type AlertProps = Omit<SurfaceProps, "variant" | "padding"> & {
28
+ /** What the alert says: `default`, `info`, `success`, `warning` or `destructive`. Picks the glyph and the colour. */
29
+ status?: AlertStatus;
30
+ /** `soft` washes the surface in the status's colour; `surface` keeps the neutral fill and colours only the glyph and title. */
31
+ variant?: AlertVariant;
32
+ /** Padding, gap, type scale and glyph size together. */
33
+ size?: AlertSize;
34
+ /** Composes a trailing dismiss control in. */
35
+ isDismissible?: boolean;
36
+ /** Whether the alert is shown. Pass it to control the alert; omit it and the alert hides itself when dismissed. */
37
+ isOpen?: boolean;
38
+ /** Whether an uncontrolled alert starts shown. */
39
+ defaultOpen?: boolean;
40
+ /** Called with `false` when the alert is dismissed, controlled or not. */
41
+ onOpenChange?: (isOpen: boolean) => void;
42
+ /** Name a screen reader gives the dismiss control. Defaults to `Dismiss`. */
43
+ closeAccessibilityLabel?: string;
44
+ /** `testID` for the dismiss control, so a test or automation can press it. */
45
+ closeTestID?: string;
46
+ children?: ReactNode;
47
+ };
48
+
49
+ function AlertRoot({
50
+ status = "default",
51
+ variant = "soft",
52
+ size = "md",
53
+ isDismissible = false,
54
+ isOpen,
55
+ defaultOpen = true,
56
+ onOpenChange,
57
+ closeAccessibilityLabel,
58
+ closeTestID,
59
+ className,
60
+ children,
61
+ ...props
62
+ }: AlertProps): ReactElement | null {
63
+ const [open, setOpen] = useControllableState({ defaultValue: defaultOpen, onChange: onOpenChange, value: isOpen });
64
+ const dismiss = useCallback(() => setOpen(false), [setOpen]);
65
+
66
+ const context = useMemo<AlertContextValue>(
67
+ () => ({ dismiss, size, status, variant }),
68
+ [dismiss, size, status, variant]
69
+ );
70
+
71
+ const slots = alertVariants({ size, status, variant });
72
+
73
+ // A glyph composed anywhere inside — the indicator's own, a `Spinner`, an
74
+ // `Icon` in an action — adopts the alert's step and its status's colour.
75
+ const iconClassName = slots.icon();
76
+ const iconColor = ALERT_FOREGROUND_TOKEN[status];
77
+ const iconDefaults = useMemo(() => ({ className: iconClassName, color: iconColor }), [iconClassName, iconColor]);
78
+
79
+ if (!open) {
80
+ return null;
81
+ }
82
+
83
+ return (
84
+ <AlertProvider value={context}>
85
+ <Animated.View exiting={EXITING}>
86
+ <Surface
87
+ accessibilityLiveRegion={resolveAlertLiveRegion(status)}
88
+ accessibilityRole="alert"
89
+ className={slots.root({ className })}
90
+ padding={ALERT_SURFACE_PADDING[size]}
91
+ variant={resolveAlertSurfaceVariant({ status, variant })}
92
+ {...props}
93
+ >
94
+ <IconDefaultsProvider value={iconDefaults}>
95
+ {children}
96
+ {isDismissible ? (
97
+ <AlertCloseButton accessibilityLabel={closeAccessibilityLabel} testID={closeTestID} />
98
+ ) : null}
99
+ </IconDefaultsProvider>
100
+ </Surface>
101
+ </Animated.View>
102
+ </AlertProvider>
103
+ );
104
+ }
105
+
106
+ /**
107
+ * A status message — a glyph, a title, a description and, optionally, an
108
+ * action — drawn on a `Surface`.
109
+ *
110
+ * `status` says what the message means: `default`, `info`, `success`,
111
+ * `warning` or `destructive`. It picks the indicator's glyph and colours the
112
+ * glyph and the title from one token; the description stays muted whatever the
113
+ * status. `variant` says how loudly: `soft` washes the surface in the status's
114
+ * soft fill, `surface` keeps the neutral fill and lets the glyph and title carry
115
+ * the colour. A neutral or `surface` alert takes its fill from the surface
116
+ * ladder, so one inside a card steps off the card instead of vanishing into it.
117
+ *
118
+ * `isDismissible` composes a close control in. Uncontrolled, the alert hides
119
+ * itself when dismissed; pass `isOpen` and `onOpenChange` to own that state.
120
+ * `useAlert().dismiss` closes it from inside, for a "Got it" action.
121
+ *
122
+ * Announced as an `alert`, and as a live region on Android — assertive for a
123
+ * warning or a failure, polite for anything else.
124
+ *
125
+ * @example
126
+ * <Alert status="warning">
127
+ * <Alert.Indicator />
128
+ * <Alert.Content>
129
+ * <Alert.Title>Card expiring</Alert.Title>
130
+ * <Alert.Description>The card ending 4242 expires next month.</Alert.Description>
131
+ * </Alert.Content>
132
+ * </Alert>
133
+ *
134
+ * @example
135
+ * <Alert isDismissible status="destructive">
136
+ * <Alert.Indicator />
137
+ * <Alert.Content>
138
+ * <Alert.Title>Payment failed</Alert.Title>
139
+ * <Alert.Description>Your bank declined the charge.</Alert.Description>
140
+ * <Alert.Action>
141
+ * <Button onPress={retry} size="sm">Try again</Button>
142
+ * </Alert.Action>
143
+ * </Alert.Content>
144
+ * </Alert>
145
+ */
146
+ export const Alert = Object.assign(AlertRoot, {
147
+ /** The leading status glyph, picked from `status`. Children replace it. */
148
+ Indicator: AlertIndicator,
149
+ /** The column holding the title, description and action; takes the remaining width. */
150
+ Content: AlertContent,
151
+ /** The heading, coloured by the status. */
152
+ Title: AlertTitle,
153
+ /** The body text, always muted. */
154
+ Description: AlertDescription,
155
+ /** A wrapping row of buttons under the description. */
156
+ Action: AlertAction,
157
+ /** The trailing dismiss control. Composed in automatically whenever `isDismissible` is set. */
158
+ CloseButton: AlertCloseButton,
159
+ displayName: "DelacourUI.Alert",
160
+ });
@@ -0,0 +1,10 @@
1
+ import type { ViewProps } from "react-native";
2
+
3
+ /**
4
+ * The shape of an alert's layout parts.
5
+ *
6
+ * Shared by `Alert.Content` and `Alert.Action`, which are both a styled `View`
7
+ * and nothing else, so it lives in a leaf rather than in one of the two files
8
+ * arbitrarily.
9
+ */
10
+ export type AlertSlotProps = ViewProps & { className?: string };
@@ -0,0 +1,218 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import {
5
+ ALERT_FOREGROUND_TOKEN,
6
+ ALERT_SIZES,
7
+ ALERT_STATUSES,
8
+ ALERT_SURFACE_PADDING,
9
+ ALERT_TINTED_STATUSES,
10
+ ALERT_VARIANTS,
11
+ alertVariants,
12
+ resolveAlertLiveRegion,
13
+ resolveAlertSurfaceVariant,
14
+ resolveAlertTinted,
15
+ } from "./alert.variants";
16
+
17
+ /** Every root class string the variant function can produce, one per combination. */
18
+ function everyRoot(): string[] {
19
+ return ALERT_VARIANTS.flatMap((variant) =>
20
+ ALERT_STATUSES.flatMap((status) => ALERT_SIZES.map((size) => alertVariants({ size, status, variant }).root()))
21
+ );
22
+ }
23
+
24
+ describe("alertVariants root slot", () => {
25
+ test("lays the indicator, content and close control out in a row, aligned to the top", () => {
26
+ for (const cls of everyRoot()) {
27
+ expect(cls).toContain("flex-row");
28
+ expect(cls).toContain("items-start");
29
+ }
30
+ });
31
+
32
+ test("tints a soft alert with its status's soft fill", () => {
33
+ expect(alertVariants({ status: "info", variant: "soft" }).root()).toContain("bg-info-soft");
34
+ expect(alertVariants({ status: "success", variant: "soft" }).root()).toContain("bg-success-soft");
35
+ expect(alertVariants({ status: "warning", variant: "soft" }).root()).toContain("bg-warning-soft");
36
+ expect(alertVariants({ status: "destructive", variant: "soft" }).root()).toContain("bg-destructive-soft");
37
+ });
38
+
39
+ // A tinted fill is its own edge. A grey card hairline around a red wash
40
+ // reads as two components stacked, not one.
41
+ test("a tinted alert hides the surface's hairline", () => {
42
+ for (const status of ALERT_TINTED_STATUSES) {
43
+ expect(alertVariants({ status, variant: "soft" }).root()).toContain("border-transparent");
44
+ }
45
+ });
46
+
47
+ // The fill is the Surface's: a neutral alert, or any alert on the surface
48
+ // variant, names no background of its own, so the ladder decides it.
49
+ test("paints no fill of its own where the surface ladder decides", () => {
50
+ for (const status of ALERT_STATUSES) {
51
+ expect(alertVariants({ status, variant: "surface" }).root()).not.toMatch(/\bbg-/);
52
+ }
53
+ expect(alertVariants({ status: "default", variant: "soft" }).root()).not.toMatch(/\bbg-/);
54
+ });
55
+
56
+ test("gives every tinted status a distinct surface", () => {
57
+ const seen = new Set(ALERT_TINTED_STATUSES.map((status) => alertVariants({ status, variant: "soft" }).root()));
58
+ expect(seen.size).toBe(ALERT_TINTED_STATUSES.length);
59
+ });
60
+
61
+ test("steps its gap up with size", () => {
62
+ expect(alertVariants({ size: "sm" }).root()).toContain("gap-2");
63
+ expect(alertVariants({ size: "md" }).root()).toContain("gap-3");
64
+ expect(alertVariants({ size: "lg" }).root()).toContain("gap-3.5");
65
+ });
66
+
67
+ // Rule 1: a React Native View does not cascade colour to a Text descendant.
68
+ test("carries no text colour on the root", () => {
69
+ for (const cls of everyRoot()) {
70
+ expect(cls).not.toMatch(/\btext-/);
71
+ }
72
+ });
73
+
74
+ // Surface owns the padding; a second one here would fight it through the merge.
75
+ test("sets no padding of its own", () => {
76
+ for (const cls of everyRoot()) {
77
+ expect(cls).not.toMatch(/(^|\s)p[xy]?-/);
78
+ }
79
+ });
80
+
81
+ test("merges an incoming className last", () => {
82
+ expect(alertVariants().root({ className: "mt-4" })).toContain("mt-4");
83
+ expect(alertVariants({ status: "info" }).root({ className: "bg-card" })).not.toContain("bg-info-soft");
84
+ });
85
+ });
86
+
87
+ describe("alertVariants text slots", () => {
88
+ test("the title is coloured by the status, from the same token the icon reads", () => {
89
+ for (const status of ALERT_STATUSES) {
90
+ expect(alertVariants({ status }).title()).toContain(`text-${ALERT_FOREGROUND_TOKEN[status]}`);
91
+ }
92
+ });
93
+
94
+ test("the description is always muted", () => {
95
+ for (const status of ALERT_STATUSES) {
96
+ expect(alertVariants({ status }).description()).toContain("text-muted-foreground");
97
+ }
98
+ });
99
+
100
+ test("the title is heavier than the description", () => {
101
+ expect(alertVariants().title()).toContain("font-semibold");
102
+ expect(alertVariants().description()).not.toContain("font-semibold");
103
+ });
104
+
105
+ test("steps both text sizes up with the alert", () => {
106
+ expect(alertVariants({ size: "sm" }).title()).toContain("text-sm");
107
+ expect(alertVariants({ size: "md" }).title()).toContain("text-base");
108
+ expect(alertVariants({ size: "lg" }).title()).toContain("text-lg");
109
+ expect(alertVariants({ size: "sm" }).description()).toContain("text-xs");
110
+ expect(alertVariants({ size: "md" }).description()).toContain("text-sm");
111
+ expect(alertVariants({ size: "lg" }).description()).toContain("text-base");
112
+ });
113
+
114
+ // A fixed height clips wrapped text at a large accessibility step.
115
+ test("no slot carries a fixed height", () => {
116
+ for (const size of ALERT_SIZES) {
117
+ const slots = alertVariants({ size });
118
+ for (const cls of [slots.root(), slots.content(), slots.title(), slots.description(), slots.action()]) {
119
+ expect(cls).not.toMatch(/(^|\s)h-/);
120
+ }
121
+ }
122
+ });
123
+ });
124
+
125
+ describe("alertVariants layout slots", () => {
126
+ // Without flex-1 a long description pushes the close control off the edge.
127
+ test("the content takes the remaining width", () => {
128
+ expect(alertVariants().content()).toContain("flex-1");
129
+ });
130
+
131
+ test("the action row wraps rather than overflowing", () => {
132
+ expect(alertVariants().action()).toContain("flex-row");
133
+ expect(alertVariants().action()).toContain("flex-wrap");
134
+ });
135
+
136
+ test("the icon indexes the shared icon scale at every size", () => {
137
+ expect(alertVariants({ size: "sm" }).icon()).toBe("size-icon-sm");
138
+ expect(alertVariants({ size: "md" }).icon()).toBe("size-icon-md");
139
+ expect(alertVariants({ size: "lg" }).icon()).toBe("size-icon-lg");
140
+ });
141
+
142
+ // The glyph sits level with the title's first line rather than with the top
143
+ // of the box, so the indicator carries the line height as its own height.
144
+ test("the indicator is as tall as the title's first line", () => {
145
+ expect(alertVariants({ size: "sm" }).indicator()).toContain("h-5");
146
+ expect(alertVariants({ size: "md" }).indicator()).toContain("h-6");
147
+ expect(alertVariants({ size: "lg" }).indicator()).toContain("h-7");
148
+ });
149
+ });
150
+
151
+ describe("ALERT_FOREGROUND_TOKEN", () => {
152
+ test("a status names its soft foreground; the neutral alert names the page's", () => {
153
+ expect(ALERT_FOREGROUND_TOKEN).toEqual({
154
+ default: "foreground",
155
+ info: "info-soft-foreground",
156
+ success: "success-soft-foreground",
157
+ warning: "warning-soft-foreground",
158
+ destructive: "destructive-soft-foreground",
159
+ });
160
+ });
161
+
162
+ // A token no theme declares compiles to nothing and the icon falls back to
163
+ // whatever colour it inherited.
164
+ test("every token is declared in both themes and aliased", () => {
165
+ const css = readFileSync(join(import.meta.dir, "../../styles/theme.css"), "utf8");
166
+ for (const token of Object.values(ALERT_FOREGROUND_TOKEN)) {
167
+ expect(css).toContain(`--color-${token}: var(--${token})`);
168
+ expect(css.split(`--${token}:`).length - 1).toBeGreaterThanOrEqual(2);
169
+ }
170
+ });
171
+ });
172
+
173
+ describe("ALERT_SURFACE_PADDING", () => {
174
+ test("maps each size onto the surface's own padding step", () => {
175
+ expect(ALERT_SURFACE_PADDING).toEqual({ sm: "sm", md: "md", lg: "lg" });
176
+ });
177
+ });
178
+
179
+ describe("resolveAlertTinted", () => {
180
+ test("only a soft alert with a status is tinted", () => {
181
+ for (const status of ALERT_TINTED_STATUSES) {
182
+ expect(resolveAlertTinted({ status, variant: "soft" })).toBe(true);
183
+ expect(resolveAlertTinted({ status, variant: "surface" })).toBe(false);
184
+ }
185
+ expect(resolveAlertTinted({ status: "default", variant: "soft" })).toBe(false);
186
+ });
187
+ });
188
+
189
+ describe("resolveAlertSurfaceVariant", () => {
190
+ // A tinted alert paints over a card-shaped surface, so what nests in it
191
+ // steps from `default` like anything else on a card.
192
+ test("a tinted alert pins the surface to the card", () => {
193
+ for (const status of ALERT_TINTED_STATUSES) {
194
+ expect(resolveAlertSurfaceVariant({ status, variant: "soft" })).toBe("default");
195
+ }
196
+ });
197
+
198
+ // Left undefined, the surface steps from whatever it sits in, so an alert
199
+ // inside a card never vanishes into the card.
200
+ test("an untinted alert leaves the fill to the ladder", () => {
201
+ expect(resolveAlertSurfaceVariant({ status: "default", variant: "soft" })).toBeUndefined();
202
+ for (const status of ALERT_STATUSES) {
203
+ expect(resolveAlertSurfaceVariant({ status, variant: "surface" })).toBeUndefined();
204
+ }
205
+ });
206
+ });
207
+
208
+ describe("resolveAlertLiveRegion", () => {
209
+ // Android announces a live region when its content changes. A failure
210
+ // interrupts; news waits its turn.
211
+ test("a warning or a failure interrupts, anything else waits", () => {
212
+ expect(resolveAlertLiveRegion("destructive")).toBe("assertive");
213
+ expect(resolveAlertLiveRegion("warning")).toBe("assertive");
214
+ expect(resolveAlertLiveRegion("default")).toBe("polite");
215
+ expect(resolveAlertLiveRegion("info")).toBe("polite");
216
+ expect(resolveAlertLiveRegion("success")).toBe("polite");
217
+ });
218
+ });