@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,51 @@
1
+ /**
2
+ * A toast's auto-hide clock, as plain data.
3
+ *
4
+ * `startedAt` is `null` while paused; `remaining` is what was left when it last
5
+ * stopped. Every function returns a new value and reads the time it is given,
6
+ * so pause and resume are exact and the whole clock is reachable from
7
+ * `bun test`. The viewport turns `toastTimerRemaining` into one `setTimeout`
8
+ * per run and clears it on every pause.
9
+ *
10
+ * A duration of `0` means "until hidden" and is stored as an infinite
11
+ * `remaining`, which no arithmetic here ever brings down to zero.
12
+ */
13
+ export type ToastTimer = {
14
+ remaining: number;
15
+ startedAt: number | null;
16
+ };
17
+
18
+ /** A paused clock holding `duration` ms. `0` never expires. */
19
+ export function createToastTimer(duration: number): ToastTimer {
20
+ return { remaining: duration > 0 ? duration : Number.POSITIVE_INFINITY, startedAt: null };
21
+ }
22
+
23
+ export function isToastTimerRunning(timer: ToastTimer): boolean {
24
+ return timer.startedAt !== null;
25
+ }
26
+
27
+ /** Starts the clock at `now`. A running clock is returned as it is. */
28
+ export function startToastTimer(timer: ToastTimer, now: number): ToastTimer {
29
+ return timer.startedAt === null ? { remaining: timer.remaining, startedAt: now } : timer;
30
+ }
31
+
32
+ /** Stops the clock at `now`, keeping what is left. A paused clock is returned as it is. */
33
+ export function pauseToastTimer(timer: ToastTimer, now: number): ToastTimer {
34
+ if (timer.startedAt === null) return timer;
35
+ return { remaining: Math.max(0, timer.remaining - (now - timer.startedAt)), startedAt: null };
36
+ }
37
+
38
+ /** Carries on from where `pauseToastTimer` left it. */
39
+ export function resumeToastTimer(timer: ToastTimer, now: number): ToastTimer {
40
+ return startToastTimer(timer, now);
41
+ }
42
+
43
+ /** Milliseconds left at `now` — `Infinity` for a toast that stays until hidden. */
44
+ export function toastTimerRemaining(timer: ToastTimer, now: number): number {
45
+ if (timer.startedAt === null) return timer.remaining;
46
+ return Math.max(0, timer.remaining - (now - timer.startedAt));
47
+ }
48
+
49
+ export function isToastTimerExpired(timer: ToastTimer, now: number): boolean {
50
+ return toastTimerRemaining(timer, now) <= 0;
51
+ }
@@ -0,0 +1,132 @@
1
+ import { type ReactElement, type ReactNode, useCallback, useMemo, useState } from "react";
2
+ import { type AccessibilityActionEvent, View, type ViewProps } from "react-native";
3
+ import { IconDefaultsProvider } from "../icon";
4
+ import {
5
+ type ToastContextValue,
6
+ ToastProvider,
7
+ type ToastRegisteredAction,
8
+ useToastItemContext,
9
+ } from "./toast.context";
10
+ import type { ToastStatus } from "./toast.store";
11
+ import { resolveToastRole, TOAST_FOREGROUND_TOKEN, toastVariants } from "./toast.variants";
12
+ import { ToastAction } from "./toast-action";
13
+ import { ToastClose } from "./toast-close";
14
+ import { ToastContent } from "./toast-content";
15
+ import { ToastDescription } from "./toast-description";
16
+ import { ToastIndicator } from "./toast-indicator";
17
+ import { ToastTitle } from "./toast-title";
18
+
19
+ export type ToastProps = ViewProps & {
20
+ className?: string;
21
+ /** What the toast says: `default`, `info`, `success`, `warning` or `destructive`. Picks the glyph and the title's colour. */
22
+ status?: ToastStatus;
23
+ /** Hides the toast — the close control, the action and a screen reader's dismiss call it. Inside the viewport it defaults to hiding the toast being drawn. */
24
+ onHide?: () => void;
25
+ children?: ReactNode;
26
+ };
27
+
28
+ function noop(): void {}
29
+
30
+ function ToastRoot({
31
+ status = "default",
32
+ onHide,
33
+ className,
34
+ children,
35
+ accessibilityActions,
36
+ onAccessibilityAction,
37
+ ...props
38
+ }: ToastProps): ReactElement {
39
+ const item = useToastItemContext();
40
+ const hide = onHide ?? item?.hide ?? noop;
41
+ const [action, setAction] = useState<ToastRegisteredAction | null>(null);
42
+
43
+ const context = useMemo<ToastContextValue>(() => ({ status, hide, registerAction: setAction }), [status, hide]);
44
+
45
+ const slots = toastVariants({ status });
46
+ const iconClassName = slots.icon();
47
+ const iconColor = TOAST_FOREGROUND_TOKEN[status];
48
+ const iconDefaults = useMemo(() => ({ className: iconClassName, color: iconColor }), [iconClassName, iconColor]);
49
+
50
+ const actions = useMemo(
51
+ () => [
52
+ ...(action ? [{ name: "activate", label: action.label }] : []),
53
+ { name: "escape" },
54
+ { name: "dismiss", label: "Dismiss" },
55
+ ...(accessibilityActions ?? []),
56
+ ],
57
+ [action, accessibilityActions]
58
+ );
59
+
60
+ const handleAccessibilityAction = useCallback(
61
+ (event: AccessibilityActionEvent) => {
62
+ const { actionName } = event.nativeEvent;
63
+ if (actionName === "activate" && action) action.run();
64
+ else if (actionName === "escape" || actionName === "dismiss") hide();
65
+ onAccessibilityAction?.(event);
66
+ },
67
+ [action, hide, onAccessibilityAction]
68
+ );
69
+
70
+ // One accessible element: VoiceOver and TalkBack read the title and the
71
+ // description as one message, and the action and ✕ are its actions rather
72
+ // than two more stops. It never takes focus — it is announced instead.
73
+ return (
74
+ <ToastProvider value={context}>
75
+ <View
76
+ accessibilityActions={actions}
77
+ accessibilityLiveRegion="polite"
78
+ accessibilityRole={resolveToastRole(status)}
79
+ accessible
80
+ className={slots.root({ className })}
81
+ onAccessibilityAction={handleAccessibilityAction}
82
+ {...props}
83
+ >
84
+ <IconDefaultsProvider value={iconDefaults}>{children}</IconDefaultsProvider>
85
+ </View>
86
+ </ToastProvider>
87
+ );
88
+ }
89
+
90
+ /**
91
+ * The toast card — a glyph, a title, a description, an action and a ✕ — on a
92
+ * `popover` surface with a hairline.
93
+ *
94
+ * The viewport draws one of these for every `toast()`; compose it yourself
95
+ * only inside a custom `render`, where it keeps the house look and hides the
96
+ * toast it is drawn in with no `onHide` of its own. `status` is Alert's
97
+ * vocabulary and colours the glyph and the title from Alert's token, so a toast
98
+ * and an alert saying the same thing read as one family.
99
+ *
100
+ * One accessible element: the title and the description are read as one
101
+ * message, the action is its `activate` and the ✕ its `escape` and "Dismiss".
102
+ * `role="alert"` for a failure; a polite live region on Android.
103
+ *
104
+ * @example
105
+ * toast.show({
106
+ * render: ({ hide }) => (
107
+ * <Toast status="success">
108
+ * <Toast.Indicator />
109
+ * <Toast.Content>
110
+ * <Toast.Title>Invite sent</Toast.Title>
111
+ * <Toast.Description>priya@example.com can join now.</Toast.Description>
112
+ * </Toast.Content>
113
+ * <Toast.Close />
114
+ * </Toast>
115
+ * ),
116
+ * });
117
+ */
118
+ export const Toast = Object.assign(ToastRoot, {
119
+ /** The leading status glyph — Alert's for the same status. Children replace it. */
120
+ Indicator: ToastIndicator,
121
+ /** The column holding the title and the description. */
122
+ Content: ToastContent,
123
+ /** What happened, in the status's colour. */
124
+ Title: ToastTitle,
125
+ /** A muted line under the title. */
126
+ Description: ToastDescription,
127
+ /** One compact text button; pressing it also hides the toast. */
128
+ Action: ToastAction,
129
+ /** A trailing ✕ that hides the toast. */
130
+ Close: ToastClose,
131
+ displayName: "DelacourUI.Toast",
132
+ });
@@ -0,0 +1,393 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { declaredTokens } from "../../styles/theme-tokens.test";
3
+ import { ALERT_FOREGROUND_TOKEN, ALERT_STATUSES } from "../alert/alert.variants";
4
+ import {
5
+ resolveToastAnnouncement,
6
+ resolveToastDepthStyle,
7
+ resolveToastDrag,
8
+ resolveToastDuration,
9
+ resolveToastEnterDelay,
10
+ resolveToastHaptic,
11
+ resolveToastInterrupts,
12
+ resolveToastRelease,
13
+ resolveToastRole,
14
+ resolveToastStack,
15
+ resolveToastStackedHeight,
16
+ TOAST_DURATION,
17
+ TOAST_ENTER_DISTANCE,
18
+ TOAST_FOREGROUND_TOKEN,
19
+ TOAST_PLACEMENTS,
20
+ TOAST_RUBBER_BAND,
21
+ TOAST_STACK,
22
+ TOAST_STAGGER_MS,
23
+ TOAST_STATUSES,
24
+ TOAST_SWIPE,
25
+ type ToastStackInput,
26
+ toastVariants,
27
+ } from "./toast.variants";
28
+
29
+ const LIGHT = declaredTokens("light");
30
+ const DARK = declaredTokens("dark");
31
+
32
+ /** `border-t` and friends set a width, not a colour, and name no token. */
33
+ const STRUCTURAL_BORDER_SUFFIXES = new Set(["t", "b", "l", "r", "x", "y", "s", "e"]);
34
+
35
+ /** Tailwind's own size steps share the `text-` prefix with colours and name no token. */
36
+ const TEXT_SIZES = new Set(["xs", "sm", "base", "lg", "xl", "2xl", "3xl", "4xl"]);
37
+
38
+ /** Every theme token a class string paints with, with any `/alpha` suffix dropped. */
39
+ function colorTokens(cls: string): string[] {
40
+ const tokens: string[] = [];
41
+ for (const [, utility, token] of cls.matchAll(/\b(bg|border|text)-([a-z][\w-]*)(?:\/\d+)?\b/g)) {
42
+ if (utility === "border" && STRUCTURAL_BORDER_SUFFIXES.has(token)) continue;
43
+ if (utility === "text" && TEXT_SIZES.has(token)) continue;
44
+ tokens.push(token);
45
+ }
46
+ return tokens;
47
+ }
48
+
49
+ /** The slots this component declares, pinned so a new one has to be added before the sweeps can miss it. */
50
+ const SLOT_NAMES = [
51
+ "viewport",
52
+ "stack",
53
+ "item",
54
+ "root",
55
+ "indicator",
56
+ "content",
57
+ "title",
58
+ "description",
59
+ "action",
60
+ "actionLabel",
61
+ "close",
62
+ "icon",
63
+ ] as const;
64
+
65
+ describe("toastVariants", () => {
66
+ test("declares every slot", () => {
67
+ const slots = toastVariants();
68
+ for (const name of SLOT_NAMES) expect(typeof slots[name]).toBe("function");
69
+ });
70
+
71
+ test("every token a slot paints with exists in both themes, for every status", () => {
72
+ for (const status of TOAST_STATUSES) {
73
+ const slots = toastVariants({ status });
74
+ for (const name of SLOT_NAMES) {
75
+ for (const token of colorTokens(slots[name]() ?? "")) {
76
+ expect(LIGHT.has(token)).toBe(true);
77
+ expect(DARK.has(token)).toBe(true);
78
+ }
79
+ }
80
+ }
81
+ });
82
+
83
+ test("the statuses are Alert's", () => {
84
+ expect(TOAST_STATUSES).toBe(ALERT_STATUSES);
85
+ expect(TOAST_FOREGROUND_TOKEN).toBe(ALERT_FOREGROUND_TOKEN);
86
+ });
87
+
88
+ test("the title takes Alert's foreground token per status", () => {
89
+ for (const status of TOAST_STATUSES) {
90
+ expect(toastVariants({ status }).title()).toContain(`text-${ALERT_FOREGROUND_TOKEN[status]}`);
91
+ }
92
+ });
93
+
94
+ test("the card is a popover surface with the card corner and a hairline, and no shadow", () => {
95
+ const root = toastVariants().root();
96
+ expect(root).toContain("bg-popover");
97
+ expect(root).toMatch(/\bborder\b/);
98
+ expect(root).toContain("border-border");
99
+ expect(root).toContain("rounded-lg");
100
+ expect(root).toContain("flex-row");
101
+ expect(root).toContain("max-w-[560px]");
102
+ expect(root).not.toMatch(/\bshadow/);
103
+ });
104
+
105
+ test("the description is muted whatever the status", () => {
106
+ for (const status of TOAST_STATUSES) {
107
+ expect(toastVariants({ status }).description()).toContain("text-muted-foreground");
108
+ }
109
+ });
110
+
111
+ test("the action's label is the primary colour, on the label and not the pressable (rule 1)", () => {
112
+ expect(toastVariants().actionLabel()).toContain("text-primary");
113
+ expect(toastVariants().action()).not.toMatch(/\btext-(?!xs|sm|base)/);
114
+ });
115
+
116
+ test("the root holds no text colour (rule 1)", () => {
117
+ expect(toastVariants().root()).not.toMatch(/\btext-/);
118
+ });
119
+
120
+ test("a caller's className wins on the card", () => {
121
+ const root = toastVariants().root({ className: "px-6" });
122
+ expect(root).toContain("px-6");
123
+ expect(root).not.toMatch(/\bpx-4\b/);
124
+ });
125
+ });
126
+
127
+ describe("resolveToastDuration", () => {
128
+ const quiet = { isScreenReaderEnabled: false };
129
+ const reader = { isScreenReaderEnabled: true };
130
+
131
+ test("defaults to 4000, and 6000 with an action", () => {
132
+ expect(resolveToastDuration({}, quiet)).toBe(TOAST_DURATION.default);
133
+ expect(resolveToastDuration({ hasAction: true }, quiet)).toBe(TOAST_DURATION.withAction);
134
+ expect(TOAST_DURATION).toMatchObject({ default: 4000, withAction: 6000 });
135
+ });
136
+
137
+ test("an explicit duration wins, and 0 stays until hidden", () => {
138
+ expect(resolveToastDuration({ duration: 1500 }, quiet)).toBe(1500);
139
+ expect(resolveToastDuration({ duration: 0, hasAction: true }, quiet)).toBe(0);
140
+ });
141
+
142
+ test("a loading toast stays until updated unless it says otherwise", () => {
143
+ expect(resolveToastDuration({ isLoading: true }, quiet)).toBe(0);
144
+ expect(resolveToastDuration({ isLoading: true, duration: 2000 }, quiet)).toBe(2000);
145
+ });
146
+
147
+ test("nonsense falls back to the default", () => {
148
+ expect(resolveToastDuration({ duration: -1 }, quiet)).toBe(TOAST_DURATION.default);
149
+ expect(resolveToastDuration({ duration: Number.NaN }, quiet)).toBe(TOAST_DURATION.default);
150
+ });
151
+
152
+ test("under a screen reader every timed toast stays at least 10 seconds (WCAG 2.2.1)", () => {
153
+ expect(TOAST_DURATION.screenReaderMinimum).toBe(10_000);
154
+ expect(resolveToastDuration({}, reader)).toBe(10_000);
155
+ expect(resolveToastDuration({ duration: 1500 }, reader)).toBe(10_000);
156
+ expect(resolveToastDuration({ duration: 20_000 }, reader)).toBe(20_000);
157
+ expect(resolveToastDuration({ duration: 0 }, reader)).toBe(0);
158
+ });
159
+ });
160
+
161
+ function stackItem(id: string, overrides: Partial<ToastStackInput> = {}): ToastStackInput {
162
+ return { id, placement: "bottom", isExiting: false, ...overrides };
163
+ }
164
+
165
+ describe("resolveToastStack", () => {
166
+ test("newest in front, and at most three drawn", () => {
167
+ const items = ["a", "b", "c", "d", "e"].map((id) => stackItem(id));
168
+ expect(resolveToastStack(items, "bottom")).toEqual([
169
+ { id: "e", depth: 0, isVisible: true, isExiting: false },
170
+ { id: "d", depth: 1, isVisible: true, isExiting: false },
171
+ { id: "c", depth: 2, isVisible: true, isExiting: false },
172
+ { id: "b", depth: 3, isVisible: false, isExiting: false },
173
+ { id: "a", depth: 4, isVisible: false, isExiting: false },
174
+ ]);
175
+ expect(TOAST_STACK.maxVisible).toBe(3);
176
+ });
177
+
178
+ test("only the asked placement", () => {
179
+ const items = [stackItem("a", { placement: "top" }), stackItem("b"), stackItem("c", { placement: "top" })];
180
+ expect(resolveToastStack(items, "top").map((entry) => entry.id)).toEqual(["c", "a"]);
181
+ });
182
+
183
+ test("an exiting toast keeps its slot while it leaves and frees it for the queue", () => {
184
+ const items = [stackItem("a"), stackItem("b"), stackItem("c"), stackItem("d", { isExiting: true })];
185
+ expect(resolveToastStack(items, "bottom")).toEqual([
186
+ { id: "d", depth: 0, isVisible: true, isExiting: true },
187
+ { id: "c", depth: 0, isVisible: true, isExiting: false },
188
+ { id: "b", depth: 1, isVisible: true, isExiting: false },
189
+ { id: "a", depth: 2, isVisible: true, isExiting: false },
190
+ ]);
191
+ });
192
+
193
+ test("an exiting toast that was never drawn is not drawn on its way out", () => {
194
+ const items = [stackItem("a", { isExiting: true }), stackItem("b"), stackItem("c"), stackItem("d")];
195
+ expect(resolveToastStack(items, "bottom")[3]).toEqual({ id: "a", depth: 3, isVisible: false, isExiting: true });
196
+ });
197
+ });
198
+
199
+ describe("resolveToastDepthStyle", () => {
200
+ test("the front toast is untouched", () => {
201
+ expect(resolveToastDepthStyle(0, "bottom")).toEqual({ translateY: 0, scale: 1, opacity: 1 });
202
+ });
203
+
204
+ test("each older toast sits 8pt further toward the entry edge, smaller and fainter", () => {
205
+ expect(resolveToastDepthStyle(1, "bottom")).toEqual({ translateY: 8, scale: 0.95, opacity: 0.75 });
206
+ const top = resolveToastDepthStyle(2, "top");
207
+ expect(top.translateY).toBe(-16);
208
+ expect(top.scale).toBeCloseTo(0.9);
209
+ expect(top.opacity).toBeCloseTo(0.5);
210
+ });
211
+ });
212
+
213
+ describe("resolveToastStackedHeight", () => {
214
+ test("the front toast keeps its own height", () => {
215
+ expect(resolveToastStackedHeight({ depth: 0, natural: 90, front: 60 })).toBe(90);
216
+ });
217
+
218
+ test("a toast behind takes the front one's height, so a taller one cannot show over it", () => {
219
+ expect(resolveToastStackedHeight({ depth: 1, natural: 90, front: 60 })).toBe(60);
220
+ expect(resolveToastStackedHeight({ depth: 2, natural: 40, front: 60 })).toBe(60);
221
+ });
222
+
223
+ test("moving to the front, it eases from the front's height back to its own", () => {
224
+ expect(resolveToastStackedHeight({ depth: 0.5, natural: 90, front: 60 })).toBe(75);
225
+ });
226
+
227
+ test("unmeasured, it is left alone", () => {
228
+ expect(resolveToastStackedHeight({ depth: 1, natural: 0, front: 60 })).toBeNull();
229
+ expect(resolveToastStackedHeight({ depth: 1, natural: 90, front: 0 })).toBeNull();
230
+ });
231
+ });
232
+
233
+ describe("resolveToastEnterDelay", () => {
234
+ test("toasts shown in one tick enter 220ms apart", () => {
235
+ expect(TOAST_STAGGER_MS).toBe(220);
236
+ expect(resolveToastEnterDelay({ createdAt: 0, batchIndex: 0, now: 0 })).toBe(0);
237
+ expect(resolveToastEnterDelay({ createdAt: 0, batchIndex: 2, now: 0 })).toBe(440);
238
+ });
239
+
240
+ test("a queued toast drawn later owes no delay", () => {
241
+ expect(resolveToastEnterDelay({ createdAt: 0, batchIndex: 4, now: 5000 })).toBe(0);
242
+ });
243
+ });
244
+
245
+ describe("resolveToastDrag", () => {
246
+ test("sideways and toward the entry edge follow the finger", () => {
247
+ expect(resolveToastDrag({ placement: "bottom", translationX: -50, translationY: 30 })).toEqual({ x: -50, y: 30 });
248
+ expect(resolveToastDrag({ placement: "top", translationX: 0, translationY: -30 })).toEqual({ x: 0, y: -30 });
249
+ });
250
+
251
+ test("toward the centre rubber-bands, never past the limit", () => {
252
+ const pulled = resolveToastDrag({ placement: "bottom", translationX: 0, translationY: -400 });
253
+ expect(pulled.y).toBeLessThan(0);
254
+ expect(pulled.y).toBeGreaterThan(-TOAST_RUBBER_BAND);
255
+ const small = resolveToastDrag({ placement: "top", translationX: 0, translationY: 10 });
256
+ expect(small.y).toBeGreaterThan(0);
257
+ expect(small.y).toBeLessThan(10);
258
+ });
259
+ });
260
+
261
+ describe("resolveToastRelease", () => {
262
+ const size = { width: 300, height: 60 };
263
+ const still = { velocityX: 0, velocityY: 0 };
264
+
265
+ test("a short drag settles back", () => {
266
+ expect(resolveToastRelease({ placement: "bottom", translationX: 20, translationY: 10, ...still, ...size })).toEqual(
267
+ { kind: "settle" }
268
+ );
269
+ });
270
+
271
+ test("past 40% sideways dismisses that way", () => {
272
+ expect(TOAST_SWIPE.distanceRatio).toBe(0.4);
273
+ expect(resolveToastRelease({ placement: "bottom", translationX: 130, translationY: 0, ...still, ...size })).toEqual(
274
+ { kind: "dismiss", axis: "x", direction: 1 }
275
+ );
276
+ expect(resolveToastRelease({ placement: "top", translationX: -130, translationY: 0, ...still, ...size })).toEqual({
277
+ kind: "dismiss",
278
+ axis: "x",
279
+ direction: -1,
280
+ });
281
+ });
282
+
283
+ test("past 40% of its height toward the entry edge dismisses", () => {
284
+ expect(resolveToastRelease({ placement: "bottom", translationX: 0, translationY: 30, ...still, ...size })).toEqual({
285
+ kind: "dismiss",
286
+ axis: "y",
287
+ direction: 1,
288
+ });
289
+ expect(resolveToastRelease({ placement: "top", translationX: 0, translationY: -30, ...still, ...size })).toEqual({
290
+ kind: "dismiss",
291
+ axis: "y",
292
+ direction: -1,
293
+ });
294
+ });
295
+
296
+ test("toward the centre never dismisses, however far", () => {
297
+ expect(
298
+ resolveToastRelease({ placement: "bottom", translationX: 0, translationY: -200, ...still, ...size })
299
+ ).toEqual({ kind: "settle" });
300
+ expect(
301
+ resolveToastRelease({
302
+ placement: "top",
303
+ translationX: 0,
304
+ translationY: 200,
305
+ velocityX: 0,
306
+ velocityY: 2000,
307
+ ...size,
308
+ })
309
+ ).toEqual({ kind: "settle" });
310
+ });
311
+
312
+ test("a fling over 800pt/s dismisses however short", () => {
313
+ expect(TOAST_SWIPE.velocity).toBe(800);
314
+ expect(
315
+ resolveToastRelease({
316
+ placement: "bottom",
317
+ translationX: 10,
318
+ translationY: 0,
319
+ velocityX: 900,
320
+ velocityY: 0,
321
+ ...size,
322
+ })
323
+ ).toEqual({ kind: "dismiss", axis: "x", direction: 1 });
324
+ expect(
325
+ resolveToastRelease({
326
+ placement: "top",
327
+ translationX: 0,
328
+ translationY: -5,
329
+ velocityX: 0,
330
+ velocityY: -900,
331
+ ...size,
332
+ })
333
+ ).toEqual({ kind: "dismiss", axis: "y", direction: -1 });
334
+ });
335
+
336
+ test("the dominant axis wins when both would dismiss", () => {
337
+ expect(
338
+ resolveToastRelease({
339
+ placement: "bottom",
340
+ translationX: 200,
341
+ translationY: 40,
342
+ velocityX: 0,
343
+ velocityY: 0,
344
+ ...size,
345
+ })
346
+ ).toEqual({ kind: "dismiss", axis: "x", direction: 1 });
347
+ });
348
+ });
349
+
350
+ describe("resolveToastHaptic", () => {
351
+ test("success, warning and destructive play their notification; the rest are silent", () => {
352
+ expect(resolveToastHaptic("success", undefined)).toBe("success");
353
+ expect(resolveToastHaptic("warning", undefined)).toBe("warning");
354
+ expect(resolveToastHaptic("destructive", undefined)).toBe("error");
355
+ expect(resolveToastHaptic("info", undefined)).toBe(false);
356
+ expect(resolveToastHaptic("default", undefined)).toBe(false);
357
+ });
358
+
359
+ test("the caller's choice wins, including false", () => {
360
+ expect(resolveToastHaptic("success", false)).toBe(false);
361
+ expect(resolveToastHaptic("default", "light")).toBe("light");
362
+ });
363
+ });
364
+
365
+ describe("accessibility", () => {
366
+ test("the announcement is the title, then the description", () => {
367
+ expect(resolveToastAnnouncement({ title: "Saved" })).toBe("Saved");
368
+ expect(resolveToastAnnouncement({ title: "Upload failed", description: "Try again" })).toBe(
369
+ "Upload failed. Try again"
370
+ );
371
+ });
372
+
373
+ test("warnings and failures interrupt; the rest queue", () => {
374
+ expect(resolveToastInterrupts("destructive")).toBe(true);
375
+ expect(resolveToastInterrupts("warning")).toBe(true);
376
+ expect(resolveToastInterrupts("success")).toBe(false);
377
+ expect(resolveToastInterrupts("default")).toBe(false);
378
+ });
379
+
380
+ test("only a failure is an alert", () => {
381
+ expect(resolveToastRole("destructive")).toBe("alert");
382
+ expect(resolveToastRole("warning")).toBe("none");
383
+ expect(resolveToastRole("success")).toBe("none");
384
+ });
385
+ });
386
+
387
+ describe("constants", () => {
388
+ test("placements, entrance and stack steps", () => {
389
+ expect(TOAST_PLACEMENTS).toEqual(["top", "bottom"]);
390
+ expect(TOAST_ENTER_DISTANCE).toBe(16);
391
+ expect(TOAST_STACK).toMatchObject({ depthOffset: 8, depthScale: 0.05, depthOpacity: 0.25 });
392
+ });
393
+ });