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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (481) hide show
  1. package/README.md +10 -6
  2. package/package.json +48 -4
  3. package/src/components/accordion/AGENTS.md +21 -9
  4. package/src/components/accordion/accordion-content.tsx +7 -4
  5. package/src/components/accordion/accordion-item.tsx +15 -13
  6. package/src/components/accordion/accordion.context.tsx +11 -5
  7. package/src/components/accordion/accordion.variants.test.ts +64 -3
  8. package/src/components/accordion/accordion.variants.ts +33 -9
  9. package/src/components/alert/AGENTS.md +102 -0
  10. package/src/components/alert/alert-action.tsx +17 -0
  11. package/src/components/alert/alert-close-button.tsx +50 -0
  12. package/src/components/alert/alert-content.tsx +15 -0
  13. package/src/components/alert/alert-description.tsx +16 -0
  14. package/src/components/alert/alert-glyphs.ts +25 -0
  15. package/src/components/alert/alert-indicator.tsx +40 -0
  16. package/src/components/alert/alert-title.tsx +18 -0
  17. package/src/components/alert/alert.context.tsx +69 -0
  18. package/src/components/alert/alert.tsx +160 -0
  19. package/src/components/alert/alert.types.ts +10 -0
  20. package/src/components/alert/alert.variants.test.ts +218 -0
  21. package/src/components/alert/alert.variants.ts +180 -0
  22. package/src/components/alert/index.ts +23 -0
  23. package/src/components/avatar/AGENTS.md +106 -0
  24. package/src/components/avatar/avatar-badge.tsx +51 -0
  25. package/src/components/avatar/avatar-group.tsx +139 -0
  26. package/src/components/avatar/avatar.context.tsx +93 -0
  27. package/src/components/avatar/avatar.tsx +201 -0
  28. package/src/components/avatar/avatar.utils.test.ts +174 -0
  29. package/src/components/avatar/avatar.utils.ts +141 -0
  30. package/src/components/avatar/avatar.variants.test.ts +251 -0
  31. package/src/components/avatar/avatar.variants.ts +270 -0
  32. package/src/components/avatar/index.ts +39 -0
  33. package/src/components/badge/AGENTS.md +5 -0
  34. package/src/components/badge/badge.tsx +6 -1
  35. package/src/components/bottom-sheet/AGENTS.md +186 -231
  36. package/src/components/bottom-sheet/bottom-sheet-background.tsx +31 -22
  37. package/src/components/bottom-sheet/bottom-sheet-close.tsx +23 -30
  38. package/src/components/bottom-sheet/bottom-sheet-container.tsx +54 -208
  39. package/src/components/bottom-sheet/bottom-sheet-content.tsx +25 -48
  40. package/src/components/bottom-sheet/bottom-sheet-description.tsx +8 -2
  41. package/src/components/bottom-sheet/bottom-sheet-flat-list.tsx +65 -0
  42. package/src/components/bottom-sheet/bottom-sheet-footer.tsx +53 -122
  43. package/src/components/bottom-sheet/bottom-sheet-handle.tsx +31 -26
  44. package/src/components/bottom-sheet/bottom-sheet-legend-list.tsx +69 -0
  45. package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +25 -147
  46. package/src/components/bottom-sheet/bottom-sheet-provider.tsx +42 -0
  47. package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +38 -62
  48. package/src/components/bottom-sheet/bottom-sheet-section-list.tsx +58 -0
  49. package/src/components/bottom-sheet/bottom-sheet-steps.tsx +88 -0
  50. package/src/components/bottom-sheet/bottom-sheet-text-input.tsx +44 -0
  51. package/src/components/bottom-sheet/bottom-sheet-title.tsx +12 -8
  52. package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +19 -37
  53. package/src/components/bottom-sheet/bottom-sheet.tsx +113 -60
  54. package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +47 -87
  55. package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -126
  56. package/src/components/bottom-sheet/index.ts +39 -17
  57. package/src/components/bottom-sheet/use-bottom-sheet-input.ts +16 -115
  58. package/src/components/button/AGENTS.md +45 -11
  59. package/src/components/button/button-group.tsx +44 -8
  60. package/src/components/button/button.context.tsx +10 -0
  61. package/src/components/button/button.tsx +2 -1
  62. package/src/components/button/button.variants.test.ts +154 -0
  63. package/src/components/button/button.variants.ts +71 -6
  64. package/src/components/button/index.ts +2 -0
  65. package/src/components/calendar/AGENTS.md +142 -0
  66. package/src/components/calendar/calendar-caption.tsx +116 -0
  67. package/src/components/calendar/calendar-day.tsx +103 -0
  68. package/src/components/calendar/calendar-grid.tsx +183 -0
  69. package/src/components/calendar/calendar-header.tsx +34 -0
  70. package/src/components/calendar/calendar-nav.tsx +51 -0
  71. package/src/components/calendar/calendar-picker.tsx +128 -0
  72. package/src/components/calendar/calendar-weekdays.tsx +48 -0
  73. package/src/components/calendar/calendar.context.tsx +108 -0
  74. package/src/components/calendar/calendar.date.test.ts +456 -0
  75. package/src/components/calendar/calendar.date.ts +584 -0
  76. package/src/components/calendar/calendar.tsx +389 -0
  77. package/src/components/calendar/calendar.types.ts +87 -0
  78. package/src/components/calendar/calendar.variants.test.ts +363 -0
  79. package/src/components/calendar/calendar.variants.ts +426 -0
  80. package/src/components/calendar/index.ts +96 -0
  81. package/src/components/card/AGENTS.md +88 -0
  82. package/src/components/card/card-action.tsx +16 -0
  83. package/src/components/card/card-content.tsx +12 -0
  84. package/src/components/card/card-description.tsx +12 -0
  85. package/src/components/card/card-footer.tsx +30 -0
  86. package/src/components/card/card-header.tsx +45 -0
  87. package/src/components/card/card-title.tsx +25 -0
  88. package/src/components/card/card.context.tsx +64 -0
  89. package/src/components/card/card.tsx +94 -0
  90. package/src/components/card/card.types.ts +13 -0
  91. package/src/components/card/card.variants.test.ts +240 -0
  92. package/src/components/card/card.variants.ts +177 -0
  93. package/src/components/card/index.ts +15 -0
  94. package/src/components/chart/AGENTS.md +8 -0
  95. package/src/components/chart/chart.tsx +8 -1
  96. package/src/components/chip/AGENTS.md +113 -0
  97. package/src/components/chip/chip-close-button.tsx +48 -0
  98. package/src/components/chip/chip-end-content.tsx +15 -0
  99. package/src/components/chip/chip-label.tsx +21 -0
  100. package/src/components/chip/chip-start-content.tsx +16 -0
  101. package/src/components/chip/chip.context.tsx +66 -0
  102. package/src/components/chip/chip.tsx +268 -0
  103. package/src/components/chip/chip.types.ts +10 -0
  104. package/src/components/chip/chip.variants.test.ts +335 -0
  105. package/src/components/chip/chip.variants.ts +321 -0
  106. package/src/components/chip/index.ts +24 -0
  107. package/src/components/collapsible/AGENTS.md +97 -0
  108. package/src/components/collapsible/collapsible-content.tsx +97 -0
  109. package/src/components/collapsible/collapsible-description.tsx +17 -0
  110. package/src/components/collapsible/collapsible-indicator.tsx +92 -0
  111. package/src/components/collapsible/collapsible-title.tsx +17 -0
  112. package/src/components/collapsible/collapsible-trigger.tsx +133 -0
  113. package/src/components/collapsible/collapsible.context.tsx +91 -0
  114. package/src/components/collapsible/collapsible.tsx +167 -0
  115. package/src/components/collapsible/collapsible.types.ts +9 -0
  116. package/src/components/collapsible/collapsible.variants.test.ts +338 -0
  117. package/src/components/collapsible/collapsible.variants.ts +228 -0
  118. package/src/components/collapsible/index.ts +32 -0
  119. package/src/components/context-menu/AGENTS.md +103 -0
  120. package/src/components/context-menu/context-menu-content.tsx +64 -0
  121. package/src/components/context-menu/context-menu-item.tsx +22 -0
  122. package/src/components/context-menu/context-menu-preview.tsx +60 -0
  123. package/src/components/context-menu/context-menu-trigger.tsx +160 -0
  124. package/src/components/context-menu/context-menu.context.tsx +91 -0
  125. package/src/components/context-menu/context-menu.tsx +90 -0
  126. package/src/components/context-menu/context-menu.variants.test.ts +266 -0
  127. package/src/components/context-menu/context-menu.variants.ts +147 -0
  128. package/src/components/context-menu/index.ts +18 -0
  129. package/src/components/dialog/AGENTS.md +97 -0
  130. package/src/components/dialog/dialog-body.tsx +21 -0
  131. package/src/components/dialog/dialog-close.tsx +82 -0
  132. package/src/components/dialog/dialog-content.tsx +153 -0
  133. package/src/components/dialog/dialog-description.tsx +23 -0
  134. package/src/components/dialog/dialog-footer.tsx +32 -0
  135. package/src/components/dialog/dialog-header.tsx +20 -0
  136. package/src/components/dialog/dialog-title.tsx +42 -0
  137. package/src/components/dialog/dialog-trigger.tsx +54 -0
  138. package/src/components/dialog/dialog.context.tsx +90 -0
  139. package/src/components/dialog/dialog.tsx +119 -0
  140. package/src/components/dialog/dialog.variants.test.ts +224 -0
  141. package/src/components/dialog/dialog.variants.ts +173 -0
  142. package/src/components/dialog/index.ts +30 -0
  143. package/src/components/drawer/AGENTS.md +123 -0
  144. package/src/components/drawer/drawer-body.tsx +46 -0
  145. package/src/components/drawer/drawer-close.tsx +76 -0
  146. package/src/components/drawer/drawer-content.tsx +186 -0
  147. package/src/components/drawer/drawer-description.tsx +23 -0
  148. package/src/components/drawer/drawer-footer.tsx +21 -0
  149. package/src/components/drawer/drawer-header.tsx +42 -0
  150. package/src/components/drawer/drawer-title.tsx +35 -0
  151. package/src/components/drawer/drawer-trigger.tsx +54 -0
  152. package/src/components/drawer/drawer.context.tsx +114 -0
  153. package/src/components/drawer/drawer.tsx +119 -0
  154. package/src/components/drawer/drawer.variants.test.ts +392 -0
  155. package/src/components/drawer/drawer.variants.ts +244 -0
  156. package/src/components/drawer/index.ts +41 -0
  157. package/src/components/drawer/use-drawer-pan.ts +108 -0
  158. package/src/components/empty-state/AGENTS.md +90 -0
  159. package/src/components/empty-state/empty-state-content.tsx +17 -0
  160. package/src/components/empty-state/empty-state-description.tsx +12 -0
  161. package/src/components/empty-state/empty-state-header.tsx +17 -0
  162. package/src/components/empty-state/empty-state-media.tsx +56 -0
  163. package/src/components/empty-state/empty-state-title.tsx +18 -0
  164. package/src/components/empty-state/empty-state.context.tsx +65 -0
  165. package/src/components/empty-state/empty-state.tsx +84 -0
  166. package/src/components/empty-state/empty-state.types.ts +14 -0
  167. package/src/components/empty-state/empty-state.variants.test.ts +245 -0
  168. package/src/components/empty-state/empty-state.variants.ts +132 -0
  169. package/src/components/empty-state/index.ts +20 -0
  170. package/src/components/fab/AGENTS.md +119 -0
  171. package/src/components/fab/fab-action.tsx +118 -0
  172. package/src/components/fab/fab-group.tsx +214 -0
  173. package/src/components/fab/fab-label.tsx +18 -0
  174. package/src/components/fab/fab.context.tsx +125 -0
  175. package/src/components/fab/fab.tsx +156 -0
  176. package/src/components/fab/fab.types.ts +17 -0
  177. package/src/components/fab/fab.variants.test.ts +274 -0
  178. package/src/components/fab/fab.variants.ts +234 -0
  179. package/src/components/fab/index.ts +37 -0
  180. package/src/components/feedback/AGENTS.md +95 -0
  181. package/src/components/feedback/feedback-action.tsx +16 -0
  182. package/src/components/feedback/feedback-cancel.tsx +34 -0
  183. package/src/components/feedback/feedback-close.tsx +21 -0
  184. package/src/components/feedback/feedback-content.tsx +24 -0
  185. package/src/components/feedback/feedback-field.tsx +70 -0
  186. package/src/components/feedback/feedback-footer.tsx +21 -0
  187. package/src/components/feedback/feedback-panel.tsx +54 -0
  188. package/src/components/feedback/feedback-submit.tsx +74 -0
  189. package/src/components/feedback/feedback-title.tsx +34 -0
  190. package/src/components/feedback/feedback-trigger.tsx +20 -0
  191. package/src/components/feedback/feedback.context.tsx +73 -0
  192. package/src/components/feedback/feedback.tsx +135 -0
  193. package/src/components/feedback/feedback.variants.test.ts +185 -0
  194. package/src/components/feedback/feedback.variants.ts +128 -0
  195. package/src/components/feedback/index.ts +24 -0
  196. package/src/components/field/AGENTS.md +19 -0
  197. package/src/components/field/field-label.tsx +19 -5
  198. package/src/components/field/field.context.tsx +17 -0
  199. package/src/components/field/field.tsx +15 -2
  200. package/src/components/field/field.variants.test.ts +24 -0
  201. package/src/components/field/field.variants.ts +20 -0
  202. package/src/components/field/index.ts +1 -0
  203. package/src/components/item/AGENTS.md +92 -0
  204. package/src/components/item/index.ts +25 -0
  205. package/src/components/item/item-actions.tsx +28 -0
  206. package/src/components/item/item-content.tsx +15 -0
  207. package/src/components/item/item-description.tsx +12 -0
  208. package/src/components/item/item-footer.tsx +13 -0
  209. package/src/components/item/item-group.tsx +32 -0
  210. package/src/components/item/item-header.tsx +13 -0
  211. package/src/components/item/item-media.tsx +37 -0
  212. package/src/components/item/item-separator.tsx +13 -0
  213. package/src/components/item/item-title.tsx +17 -0
  214. package/src/components/item/item.context.tsx +66 -0
  215. package/src/components/item/item.tsx +218 -0
  216. package/src/components/item/item.types.ts +12 -0
  217. package/src/components/item/item.variants.test.ts +263 -0
  218. package/src/components/item/item.variants.ts +200 -0
  219. package/src/components/kpi/AGENTS.md +125 -0
  220. package/src/components/kpi/index.ts +45 -0
  221. package/src/components/kpi/kpi-action.tsx +12 -0
  222. package/src/components/kpi/kpi-content.tsx +35 -0
  223. package/src/components/kpi/kpi-footer.tsx +52 -0
  224. package/src/components/kpi/kpi-group.tsx +70 -0
  225. package/src/components/kpi/kpi-header.tsx +25 -0
  226. package/src/components/kpi/kpi-icon.tsx +28 -0
  227. package/src/components/kpi/kpi-sparkline.tsx +145 -0
  228. package/src/components/kpi/kpi-stat.tsx +20 -0
  229. package/src/components/kpi/kpi-title.tsx +19 -0
  230. package/src/components/kpi/kpi-trend.tsx +105 -0
  231. package/src/components/kpi/kpi-value.tsx +32 -0
  232. package/src/components/kpi/kpi.context.tsx +111 -0
  233. package/src/components/kpi/kpi.tsx +154 -0
  234. package/src/components/kpi/kpi.types.ts +9 -0
  235. package/src/components/kpi/kpi.variants.test.ts +428 -0
  236. package/src/components/kpi/kpi.variants.ts +347 -0
  237. package/src/components/label/AGENTS.md +89 -0
  238. package/src/components/label/index.ts +10 -0
  239. package/src/components/label/label.tsx +97 -0
  240. package/src/components/label/label.variants.test.ts +176 -0
  241. package/src/components/label/label.variants.ts +123 -0
  242. package/src/components/menu/AGENTS.md +176 -0
  243. package/src/components/menu/index.ts +44 -0
  244. package/src/components/menu/menu-background.tsx +17 -0
  245. package/src/components/menu/menu-checkbox-item.tsx +57 -0
  246. package/src/components/menu/menu-content.tsx +303 -0
  247. package/src/components/menu/menu-item.tsx +162 -0
  248. package/src/components/menu/menu-label.tsx +14 -0
  249. package/src/components/menu/menu-radio-group.tsx +50 -0
  250. package/src/components/menu/menu-radio-item.tsx +57 -0
  251. package/src/components/menu/menu-separator.tsx +11 -0
  252. package/src/components/menu/menu-sub-content.tsx +72 -0
  253. package/src/components/menu/menu-sub-trigger.tsx +50 -0
  254. package/src/components/menu/menu-sub.tsx +75 -0
  255. package/src/components/menu/menu-trigger.tsx +57 -0
  256. package/src/components/menu/menu.context.tsx +214 -0
  257. package/src/components/menu/menu.tsx +88 -0
  258. package/src/components/menu/menu.types.ts +33 -0
  259. package/src/components/menu/menu.variants.test.ts +400 -0
  260. package/src/components/menu/menu.variants.ts +353 -0
  261. package/src/components/meter/AGENTS.md +135 -0
  262. package/src/components/meter/index.ts +43 -0
  263. package/src/components/meter/meter-fill.tsx +19 -0
  264. package/src/components/meter/meter-header.tsx +17 -0
  265. package/src/components/meter/meter-label.tsx +18 -0
  266. package/src/components/meter/meter-output.tsx +42 -0
  267. package/src/components/meter/meter-segments.tsx +83 -0
  268. package/src/components/meter/meter-track.tsx +16 -0
  269. package/src/components/meter/meter.context.tsx +62 -0
  270. package/src/components/meter/meter.tsx +286 -0
  271. package/src/components/meter/meter.types.ts +30 -0
  272. package/src/components/meter/meter.variants.test.ts +430 -0
  273. package/src/components/meter/meter.variants.ts +347 -0
  274. package/src/components/overlay/AGENTS.md +89 -0
  275. package/src/components/overlay/index.ts +27 -0
  276. package/src/components/overlay/overlay-portal.tsx +84 -0
  277. package/src/components/overlay/overlay-presence.test.ts +46 -0
  278. package/src/components/overlay/overlay-presence.ts +49 -0
  279. package/src/components/overlay/overlay-registry.test.ts +96 -0
  280. package/src/components/overlay/overlay-registry.ts +99 -0
  281. package/src/components/overlay/overlay-scrim.tsx +49 -0
  282. package/src/components/overlay/overlay.context.tsx +48 -0
  283. package/src/components/overlay/overlay.tsx +118 -0
  284. package/src/components/overlay/overlay.variants.test.ts +37 -0
  285. package/src/components/overlay/overlay.variants.ts +33 -0
  286. package/src/components/overlay/use-overlay-back-handler.ts +34 -0
  287. package/src/components/overlay/use-overlay-presence.ts +87 -0
  288. package/src/components/popover/AGENTS.md +138 -0
  289. package/src/components/popover/index.ts +48 -0
  290. package/src/components/popover/popover-anchor.tsx +49 -0
  291. package/src/components/popover/popover-arrow.tsx +89 -0
  292. package/src/components/popover/popover-close.tsx +73 -0
  293. package/src/components/popover/popover-content.tsx +235 -0
  294. package/src/components/popover/popover-description.tsx +18 -0
  295. package/src/components/popover/popover-title.tsx +33 -0
  296. package/src/components/popover/popover-trigger.tsx +56 -0
  297. package/src/components/popover/popover.context.tsx +71 -0
  298. package/src/components/popover/popover.position.test.ts +354 -0
  299. package/src/components/popover/popover.position.ts +286 -0
  300. package/src/components/popover/popover.tsx +123 -0
  301. package/src/components/popover/popover.variants.test.ts +135 -0
  302. package/src/components/popover/popover.variants.ts +85 -0
  303. package/src/components/popover/use-anchor-measure.ts +76 -0
  304. package/src/components/popover/use-anchored-content.ts +219 -0
  305. package/src/components/pressable/AGENTS.md +11 -1
  306. package/src/components/pressable/pressable.tsx +7 -2
  307. package/src/components/pressable/pressable.variants.test.ts +29 -0
  308. package/src/components/pressable/pressable.variants.ts +17 -0
  309. package/src/components/progress/AGENTS.md +120 -0
  310. package/src/components/progress/index.ts +40 -0
  311. package/src/components/progress/progress-fill.tsx +152 -0
  312. package/src/components/progress/progress-header.tsx +19 -0
  313. package/src/components/progress/progress-label.tsx +27 -0
  314. package/src/components/progress/progress-output.tsx +47 -0
  315. package/src/components/progress/progress-track.tsx +38 -0
  316. package/src/components/progress/progress.context.tsx +79 -0
  317. package/src/components/progress/progress.tsx +171 -0
  318. package/src/components/progress/progress.types.ts +23 -0
  319. package/src/components/progress/progress.variants.test.ts +335 -0
  320. package/src/components/progress/progress.variants.ts +284 -0
  321. package/src/components/progress-button/AGENTS.md +116 -0
  322. package/src/components/progress-button/index.ts +36 -0
  323. package/src/components/progress-button/progress-button-done.tsx +33 -0
  324. package/src/components/progress-button/progress-button-label.tsx +27 -0
  325. package/src/components/progress-button/progress-button.context.tsx +93 -0
  326. package/src/components/progress-button/progress-button.tsx +414 -0
  327. package/src/components/progress-button/progress-button.variants.test.ts +314 -0
  328. package/src/components/progress-button/progress-button.variants.ts +236 -0
  329. package/src/components/provider/AGENTS.md +38 -25
  330. package/src/components/provider/provider.tsx +38 -11
  331. package/src/components/radio/AGENTS.md +8 -10
  332. package/src/components/radio/radio.variants.test.ts +5 -6
  333. package/src/components/radio/radio.variants.ts +6 -7
  334. package/src/components/rating/AGENTS.md +143 -0
  335. package/src/components/rating/index.ts +43 -0
  336. package/src/components/rating/rating-output.tsx +39 -0
  337. package/src/components/rating/rating-star.tsx +70 -0
  338. package/src/components/rating/rating-stars.tsx +219 -0
  339. package/src/components/rating/rating.context.tsx +79 -0
  340. package/src/components/rating/rating.tsx +163 -0
  341. package/src/components/rating/rating.variants.test.ts +347 -0
  342. package/src/components/rating/rating.variants.ts +354 -0
  343. package/src/components/selection-mode/AGENTS.md +155 -0
  344. package/src/components/selection-mode/index.ts +43 -0
  345. package/src/components/selection-mode/selection-mode-action.tsx +65 -0
  346. package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
  347. package/src/components/selection-mode/selection-mode-group.tsx +123 -0
  348. package/src/components/selection-mode/selection-mode-header.tsx +85 -0
  349. package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
  350. package/src/components/selection-mode/selection-mode-item.tsx +182 -0
  351. package/src/components/selection-mode/selection-mode.context.tsx +89 -0
  352. package/src/components/selection-mode/selection-mode.tsx +207 -0
  353. package/src/components/selection-mode/selection-mode.types.ts +36 -0
  354. package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
  355. package/src/components/selection-mode/selection-mode.variants.ts +329 -0
  356. package/src/components/skeleton/AGENTS.md +120 -0
  357. package/src/components/skeleton/index.ts +15 -0
  358. package/src/components/skeleton/skeleton-clock.ts +48 -0
  359. package/src/components/skeleton/skeleton-group.tsx +64 -0
  360. package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
  361. package/src/components/skeleton/skeleton.context.tsx +45 -0
  362. package/src/components/skeleton/skeleton.tsx +227 -0
  363. package/src/components/skeleton/skeleton.variants.test.ts +245 -0
  364. package/src/components/skeleton/skeleton.variants.ts +222 -0
  365. package/src/components/slide-button/AGENTS.md +122 -0
  366. package/src/components/slide-button/index.ts +43 -0
  367. package/src/components/slide-button/slide-button-label.tsx +70 -0
  368. package/src/components/slide-button/slide-button-thumb.tsx +93 -0
  369. package/src/components/slide-button/slide-button.context.tsx +76 -0
  370. package/src/components/slide-button/slide-button.tsx +457 -0
  371. package/src/components/slide-button/slide-button.variants.test.ts +288 -0
  372. package/src/components/slide-button/slide-button.variants.ts +324 -0
  373. package/src/components/slider/index.ts +2 -0
  374. package/src/components/slider/slider-thumb.tsx +33 -1
  375. package/src/components/slider/slider.variants.test.ts +52 -0
  376. package/src/components/slider/slider.variants.ts +56 -2
  377. package/src/components/stack-card/AGENTS.md +169 -0
  378. package/src/components/stack-card/index.ts +37 -0
  379. package/src/components/stack-card/stack-card-action.tsx +66 -0
  380. package/src/components/stack-card/stack-card-actions.tsx +18 -0
  381. package/src/components/stack-card/stack-card-card.tsx +71 -0
  382. package/src/components/stack-card/stack-card-empty.tsx +30 -0
  383. package/src/components/stack-card/stack-card-slot.tsx +90 -0
  384. package/src/components/stack-card/stack-card-stamp.tsx +76 -0
  385. package/src/components/stack-card/stack-card.context.tsx +117 -0
  386. package/src/components/stack-card/stack-card.tsx +557 -0
  387. package/src/components/stack-card/stack-card.types.ts +26 -0
  388. package/src/components/stack-card/stack-card.variants.test.ts +355 -0
  389. package/src/components/stack-card/stack-card.variants.ts +360 -0
  390. package/src/components/steps/AGENTS.md +153 -0
  391. package/src/components/steps/index.ts +45 -0
  392. package/src/components/steps/steps-connector.tsx +31 -0
  393. package/src/components/steps/steps-description.tsx +26 -0
  394. package/src/components/steps/steps-indicator.tsx +106 -0
  395. package/src/components/steps/steps-item.tsx +225 -0
  396. package/src/components/steps/steps-panel.tsx +41 -0
  397. package/src/components/steps/steps-title.tsx +32 -0
  398. package/src/components/steps/steps-track.tsx +59 -0
  399. package/src/components/steps/steps.context.tsx +123 -0
  400. package/src/components/steps/steps.tsx +166 -0
  401. package/src/components/steps/steps.variants.test.ts +388 -0
  402. package/src/components/steps/steps.variants.ts +468 -0
  403. package/src/components/surface/AGENTS.md +72 -0
  404. package/src/components/surface/index.ts +15 -0
  405. package/src/components/surface/surface.context.tsx +54 -0
  406. package/src/components/surface/surface.tsx +70 -0
  407. package/src/components/surface/surface.variants.test.ts +180 -0
  408. package/src/components/surface/surface.variants.ts +133 -0
  409. package/src/components/swipe/AGENTS.md +140 -0
  410. package/src/components/swipe/index.ts +31 -0
  411. package/src/components/swipe/swipe-action.tsx +99 -0
  412. package/src/components/swipe/swipe-end.tsx +14 -0
  413. package/src/components/swipe/swipe-group.tsx +55 -0
  414. package/src/components/swipe/swipe-panel.tsx +55 -0
  415. package/src/components/swipe/swipe-start.tsx +15 -0
  416. package/src/components/swipe/swipe.context.tsx +135 -0
  417. package/src/components/swipe/swipe.tsx +393 -0
  418. package/src/components/swipe/swipe.types.ts +7 -0
  419. package/src/components/swipe/swipe.variants.test.ts +337 -0
  420. package/src/components/swipe/swipe.variants.ts +290 -0
  421. package/src/components/switch/switch.variants.ts +2 -2
  422. package/src/components/tabs/AGENTS.md +4 -3
  423. package/src/components/tabs/tabs.variants.test.ts +1 -1
  424. package/src/components/tabs/tabs.variants.ts +4 -6
  425. package/src/components/textarea/AGENTS.md +86 -0
  426. package/src/components/textarea/index.ts +16 -0
  427. package/src/components/textarea/textarea.tsx +157 -0
  428. package/src/components/textarea/textarea.variants.test.ts +219 -0
  429. package/src/components/textarea/textarea.variants.ts +171 -0
  430. package/src/components/toast/AGENTS.md +140 -0
  431. package/src/components/toast/index.ts +74 -0
  432. package/src/components/toast/toast-action.tsx +59 -0
  433. package/src/components/toast/toast-api.ts +32 -0
  434. package/src/components/toast/toast-close.tsx +44 -0
  435. package/src/components/toast/toast-content.tsx +11 -0
  436. package/src/components/toast/toast-description.tsx +11 -0
  437. package/src/components/toast/toast-indicator.tsx +34 -0
  438. package/src/components/toast/toast-item.tsx +308 -0
  439. package/src/components/toast/toast-title.tsx +13 -0
  440. package/src/components/toast/toast-viewport.tsx +135 -0
  441. package/src/components/toast/toast.context.tsx +69 -0
  442. package/src/components/toast/toast.store.test.ts +259 -0
  443. package/src/components/toast/toast.store.ts +338 -0
  444. package/src/components/toast/toast.timer.test.ts +64 -0
  445. package/src/components/toast/toast.timer.ts +51 -0
  446. package/src/components/toast/toast.tsx +132 -0
  447. package/src/components/toast/toast.variants.test.ts +393 -0
  448. package/src/components/toast/toast.variants.ts +304 -0
  449. package/src/components/toggle-button/AGENTS.md +106 -0
  450. package/src/components/toggle-button/index.ts +30 -0
  451. package/src/components/toggle-button/toggle-button-group.tsx +156 -0
  452. package/src/components/toggle-button/toggle-button-label.tsx +19 -0
  453. package/src/components/toggle-button/toggle-button.context.tsx +130 -0
  454. package/src/components/toggle-button/toggle-button.tsx +172 -0
  455. package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
  456. package/src/components/toggle-button/toggle-button.variants.ts +160 -0
  457. package/src/components/tooltip/AGENTS.md +133 -0
  458. package/src/components/tooltip/index.ts +28 -0
  459. package/src/components/tooltip/tooltip-arrow.tsx +40 -0
  460. package/src/components/tooltip/tooltip-content.tsx +179 -0
  461. package/src/components/tooltip/tooltip-description.tsx +19 -0
  462. package/src/components/tooltip/tooltip-text.tsx +21 -0
  463. package/src/components/tooltip/tooltip-title.tsx +20 -0
  464. package/src/components/tooltip/tooltip-trigger.tsx +103 -0
  465. package/src/components/tooltip/tooltip.context.tsx +68 -0
  466. package/src/components/tooltip/tooltip.tsx +213 -0
  467. package/src/components/tooltip/tooltip.variants.test.ts +231 -0
  468. package/src/components/tooltip/tooltip.variants.ts +177 -0
  469. package/src/hooks/use-calm-motion.tsx +31 -0
  470. package/src/lib/calm-motion.test.ts +17 -0
  471. package/src/lib/calm-motion.ts +23 -0
  472. package/src/lib/cn.test.ts +14 -0
  473. package/src/lib/cn.ts +2 -2
  474. package/src/no-gorhom.test.ts +54 -0
  475. package/src/styles/geometry.test.ts +1 -0
  476. package/src/styles/theme.css +4 -0
  477. package/src/styles/tokens.css +11 -0
  478. package/src/styles/tokens.test.ts +22 -0
  479. package/src/styles/tokens.ts +20 -0
  480. package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
  481. package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
@@ -0,0 +1,133 @@
1
+ # Tooltip
2
+
3
+ A short label naming the control under the finger — what an icon-only button does, a shortcut, a
4
+ one-line hint. It is not interactive: anything with a button in it is a [Popover](../popover/AGENTS.md).
5
+
6
+ `import { Tooltip } from "@delacour/react-native-ui/tooltip";`
7
+
8
+ It draws through the overlay foundation, so it needs `OverlayProvider` at the app root and
9
+ `react-native-teleport` installed — see [Overlay](../overlay/AGENTS.md). The provider is also what
10
+ hears an outside tap; without it the tooltip renders inline, may be clipped, and closes only on its
11
+ timer or the trigger.
12
+
13
+ ## Anatomy
14
+
15
+ ```tsx
16
+ <Tooltip isOpen? defaultOpen? onOpenChange? openOn? duration? label?>
17
+ <Tooltip.Trigger asChild><Button size="icon-md" variant="ghost" /></Tooltip.Trigger>
18
+ <Tooltip.Content placement? align? offset? alignOffset? variant? width? minWidth? maxHeight? isScrollable?>
19
+ <Tooltip.Arrow />
20
+ <Tooltip.Text /> // inverted: the one line
21
+ <Tooltip.Title /> <Tooltip.Description /> // surface: a heading and a line
22
+ </Tooltip.Content>
23
+ </Tooltip>
24
+ ```
25
+
26
+ `useTooltip()` returns `{ isOpen, setOpen, close }`.
27
+
28
+ ## Files
29
+
30
+ | File | What it holds |
31
+ | --- | --- |
32
+ | `index.ts` | → `@delacour/react-native-ui/tooltip` |
33
+ | `tooltip.tsx` | `Tooltip` — open state, the screen-reader check, activation, the outside-tap subscription, the one-open-at-a-time rule; the `Object.assign` names every part |
34
+ | `tooltip.context.tsx` | **Leaf.** `TooltipContext`, `useTooltip`, and the content context the arrow and text read |
35
+ | `tooltip.variants.ts` | The slotted `tv()` — `content`, `arrow`, `text`, `title`, `description` per variant — the defaults, and the pure `resolveTooltipDuration`, `shouldTooltipActivate`, `resolveTooltipAccessibility`, `readableTextOf` |
36
+ | `tooltip.variants.test.ts` | Both variants and their tokens in both themes, the three resolvers, the defaults |
37
+ | `tooltip-trigger.tsx` | `Tooltip.Trigger` — `Pressable`, or `asChild` to donate the long press or the press; the anchor; the label or hint |
38
+ | `tooltip-content.tsx` | `Tooltip.Content` — the portal, the panel, the timer |
39
+ | `tooltip-arrow.tsx` | `Tooltip.Arrow` — Popover's `AnchoredArrow` in the variant's paint |
40
+ | `tooltip-text.tsx` | `Tooltip.Text` — `Text.Caption`, inked for the variant |
41
+ | `tooltip-title.tsx` | `Tooltip.Title` — `Text.Label`, for the surface variant |
42
+ | `tooltip-description.tsx` | `Tooltip.Description` — `Text.Caption`, for the surface variant |
43
+
44
+ ## Builds on Popover's leaves
45
+
46
+ `popover/popover.position.ts`, `popover/use-anchor-measure.ts`, `popover/use-anchored-content.ts`,
47
+ `popover/popover-arrow.tsx` and `popover/popover.variants.ts` are imported **directly**, never
48
+ `../popover` — package rule 3's leaf exception, written down in Popover's own doc. So the tooltip
49
+ measures, flips, shifts, clamps and aims its arrow exactly as a popover does, and none of it is
50
+ re-tested here. It keeps Popover's collision padding and arrow inset; the inset is sized for the
51
+ card corner, which is larger than the inverted chip's `rounded-md`, so it clears both.
52
+
53
+ ## Design
54
+
55
+ - **A long press opens it, so the control keeps its tap.** Mobile has no hover. A tooltip that
56
+ opened on a tap would take the tap from the button it names, so the default `openOn` is
57
+ `"longPress"`, with a `selection` haptic — fired from the UI thread through `playHaptic`, because
58
+ `Pressable`'s `haptic` plays on press-in, which is every tap. The trigger's own `onLongPress`
59
+ still runs, after the tooltip's. A tap shorter than the long-press delay still presses the
60
+ button; one held past it fails as a tap, so a long press never also presses it. `openOn="press"`
61
+ is for a trigger with no tap of its own — an info glyph.
62
+ - **`asChild` donates the gesture.** For `Popover.Trigger`'s reason: a `Button` inside a pressable
63
+ trigger would win the touch and the tooltip would never open. The trigger hands its handler to
64
+ the child as `onLongPress` or `onPress`, chained ahead of the child's own by `mergeProps`, and
65
+ composes the measuring ref onto the child's — so the child has to be built on `Pressable`.
66
+ - **An outside tap closes it and still lands.** Popover swallows an outside tap behind an invisible
67
+ catcher, which is right for a panel with things to press in it and wrong for a label: a tooltip
68
+ in the way of the next tap would make the app feel stuck. So there is no catcher. The panel is
69
+ `pointerEvents="none"` — a tap on it falls through too — and the tooltip subscribes, while open,
70
+ to `OverlayProvider`'s `subscribeTouchStart`, which hears every touch starting anywhere beneath
71
+ the provider without claiming it, and closes. Only the provider can do this: nothing else is an
72
+ ancestor of a view the tooltip knows nothing about. See [Overlay](../overlay/AGENTS.md).
73
+ - **The trigger again closes it, and that rides on bubbling order.** A touch on the trigger reaches
74
+ the trigger's `onTouchStart` before it bubbles to the provider, so the trigger records whether
75
+ the tooltip was open when this touch began; then the provider closes it; then the long press or
76
+ press arrives and, seeing it was open, leaves it closed rather than re-opening what the touch
77
+ just shut. In long-press mode a plain tap on the trigger closes it too — the user has moved on to
78
+ the button.
79
+ - **It hides itself.** `duration` ms (1500 by default) after the entrance settles, so a slow
80
+ entrance never eats into the time the words are on screen. `0` keeps it until an outside tap or
81
+ the trigger. A negative or non-finite duration is a mistake, not a request to vanish, and takes
82
+ the default (`resolveTooltipDuration`).
83
+ - **One at a time.** A module-level slot holds the open tooltip's id and close; opening another
84
+ closes it. Two labels pointing at two controls at once say nothing about either.
85
+ - **`inverted` is the default look.** The foreground colour as the fill and the background colour
86
+ as the ink reads over anything in either theme, which is what a label floating over arbitrary
87
+ content needs; a compact `rounded-md` chip sized for one caption-sized line. `surface` is the
88
+ popover card — fill, hairline, card corner — with room for `Tooltip.Title` over
89
+ `Tooltip.Description`. The text parts read the variant from the panel, so neither needs a class
90
+ at the call site.
91
+ - **The arrow is Popover's, repainted.** `AnchoredArrow` with `isUnstyled`, so Popover's paint is
92
+ stripped and the variant's `arrow` slot is all there is: fill alone on the borderless chip, fill
93
+ plus the border on its two outer edges on the card.
94
+ - **Motion is a fade and 4pt.** From `useAnchoredContent`: an invisible measure frame, then a fade
95
+ and a 4pt slide from the resolved side, with no scale — a label appears, it does not grow.
96
+ Under reduce motion only the fade runs.
97
+
98
+ ## Accessibility
99
+
100
+ - **The trigger carries the words.** `resolveTooltipAccessibility`: `label` becomes the trigger's
101
+ `accessibilityLabel` when it has none — an icon button — or its `accessibilityHint` when it does;
102
+ a label identical to the trigger's is dropped rather than read twice. An `asChild` trigger's
103
+ label is read off the child's props, and visible text counts as a label (`readableTextOf`): a
104
+ `<Button>Sync now</Button>` is already named, and an early build replaced "Sync now" with the
105
+ tooltip's words — found on a simulator with the `surface` demo. So VoiceOver says the tooltip's words without anything
106
+ opening.
107
+ - **The panel is never read and never takes focus.** `accessibilityElementsHidden` and
108
+ `importantForAccessibility="no-hide-descendants"` on the positioner; non-modal overlays never move
109
+ focus (the overlay plan's rule). Reading it as well would say everything twice.
110
+ - **With a screen reader on, a long press does not open it** (`shouldTooltipActivate`) — the words
111
+ already reached the trigger, and double-tap-and-hold is an action. A press tooltip still opens,
112
+ for someone using zoom alongside VoiceOver, and then never times out
113
+ (`resolveTooltipDuration`): they read at their own pace.
114
+ - **Android back closes it** while it is the top overlay, so a tooltip opened over a popover does
115
+ not leave back doing nothing.
116
+
117
+ ## Out of scope
118
+
119
+ - Following a trigger that scrolls while open — the panel stays where it opened, as Popover's does,
120
+ and the timer clears it soon after.
121
+ - Interactive content. A button in a tooltip cannot be pressed; that is a `Popover`.
122
+
123
+ ## Testing
124
+
125
+ `bun test` reaches the variants and the three resolvers. Positioning is Popover's and tested there.
126
+ Everything else — the long press and its haptic, the button's tap surviving it, the timer, an
127
+ outside tap closing it and still landing, one-at-a-time, VoiceOver reading `label` — is verified on
128
+ a simulator through `apps/playground`'s `/tooltip` gallery.
129
+
130
+ Preview media is not captured yet (see the overlay plan); the flows are in
131
+ `.argent/flows/previews/tooltip/`. When a capture tool is back, mark these `capture`:
132
+ `icon-buttons` `{ flow: "tooltip/icon-buttons", frame: "device", hero: true }`, and `press`,
133
+ `placements`, `surface` `{ flow: "tooltip/<id>", frame: "device" }`.
@@ -0,0 +1,28 @@
1
+ export { Tooltip, type TooltipProps } from "./tooltip";
2
+ export {
3
+ TooltipContext,
4
+ type TooltipContextValue,
5
+ useTooltip,
6
+ } from "./tooltip.context";
7
+ export {
8
+ readableTextOf,
9
+ resolveTooltipAccessibility,
10
+ resolveTooltipDuration,
11
+ shouldTooltipActivate,
12
+ TOOLTIP_DEFAULTS,
13
+ TOOLTIP_DURATION,
14
+ TOOLTIP_ENTER_DISTANCE,
15
+ TOOLTIP_VARIANTS,
16
+ type TooltipAccessibility,
17
+ type TooltipGesture,
18
+ type TooltipOpenOn,
19
+ type TooltipVariant,
20
+ type TooltipVariantProps,
21
+ tooltipVariants,
22
+ } from "./tooltip.variants";
23
+ export type { TooltipArrowProps } from "./tooltip-arrow";
24
+ export type { TooltipContentProps } from "./tooltip-content";
25
+ export type { TooltipDescriptionProps } from "./tooltip-description";
26
+ export type { TooltipTextProps } from "./tooltip-text";
27
+ export type { TooltipTitleProps } from "./tooltip-title";
28
+ export type { TooltipTriggerProps } from "./tooltip-trigger";
@@ -0,0 +1,40 @@
1
+ import type { ReactElement } from "react";
2
+ import { AnchoredArrow } from "../popover/popover-arrow";
3
+ import { useOptionalTooltipContent } from "./tooltip.context";
4
+ import { tooltipVariants } from "./tooltip.variants";
5
+
6
+ export type TooltipArrowProps = {
7
+ className?: string;
8
+ };
9
+
10
+ /**
11
+ * The panel's arrow — Popover's `AnchoredArrow`, wearing the tooltip's paint.
12
+ *
13
+ * Write it anywhere inside `Tooltip.Content`: it reads the resolved placement
14
+ * and offset from the panel, follows a flip, points at the trigger's centre
15
+ * even after the panel shifted along an edge, and is lifted out of a
16
+ * scrollable body. Popover's own paint is stripped and the variant's slot
17
+ * supplies it, so an inverted chip's arrow is the foreground fill with no
18
+ * border and a surface card's is the popover fill with its hairline.
19
+ *
20
+ * @example
21
+ * <Tooltip.Content>
22
+ * <Tooltip.Arrow />
23
+ * <Tooltip.Text>Share</Tooltip.Text>
24
+ * </Tooltip.Content>
25
+ */
26
+ export function TooltipArrow({ className }: TooltipArrowProps): ReactElement | null {
27
+ const content = useOptionalTooltipContent();
28
+ if (content === null) return null;
29
+
30
+ return (
31
+ <AnchoredArrow
32
+ arrowOffset={content.arrowOffset}
33
+ className={tooltipVariants({ variant: content.variant }).arrow({ className })}
34
+ isUnstyled
35
+ placement={content.placement}
36
+ size={content.size}
37
+ />
38
+ );
39
+ }
40
+ TooltipArrow.displayName = "DelacourUI.Tooltip.Arrow";
@@ -0,0 +1,179 @@
1
+ import {
2
+ Children,
3
+ isValidElement,
4
+ type ReactElement,
5
+ type ReactNode,
6
+ useCallback,
7
+ useEffect,
8
+ useId,
9
+ useMemo,
10
+ useRef,
11
+ } from "react";
12
+ import { ScrollView, View, type ViewProps } from "react-native";
13
+ import Animated from "react-native-reanimated";
14
+ import { Overlay, useOverlayBackHandler } from "../overlay";
15
+ import type { PopoverAlign, PopoverPlacement, PopoverWidth } from "../popover/popover.position";
16
+ import { POPOVER_ARROW_INSET, POPOVER_COLLISION_PADDING } from "../popover/popover.variants";
17
+ import { useAnchoredContent } from "../popover/use-anchored-content";
18
+ import { TooltipContentContext, type TooltipContentContextValue, useTooltipContext } from "./tooltip.context";
19
+ import { TOOLTIP_DEFAULTS, TOOLTIP_ENTER_DISTANCE, type TooltipVariant, tooltipVariants } from "./tooltip.variants";
20
+ import { TooltipArrow } from "./tooltip-arrow";
21
+
22
+ export type TooltipContentProps = ViewProps & {
23
+ className?: string;
24
+ /** The side of the trigger the panel prefers. It flips when that side lacks room. Default `"top"`. */
25
+ placement?: PopoverPlacement;
26
+ /** Which edges line up along the cross axis; logical under RTL. Default `"center"`. */
27
+ align?: PopoverAlign;
28
+ /** The gap between trigger and panel, in points. Default 6. */
29
+ offset?: number;
30
+ /** A nudge along the cross axis, inward from the aligned edge. Default 0. */
31
+ alignOffset?: number;
32
+ /** `"inverted"` — a dark chip for one line; `"surface"` — a popover card for a title and a description. Default `"inverted"`. */
33
+ variant?: TooltipVariant;
34
+ /** A number, the trigger's width, the content's own, or the safe span. Default `"content-fit"`. */
35
+ width?: PopoverWidth;
36
+ minWidth?: number;
37
+ /** Clamped to the room on the resolved side. */
38
+ maxHeight?: number;
39
+ /** Scroll the body when it is taller than `maxHeight` — the one case the panel takes a touch. Default false. */
40
+ isScrollable?: boolean;
41
+ };
42
+
43
+ /** Pulls `Tooltip.Arrow` out of the body, so a scrolling body never scrolls or clips it. */
44
+ function partitionArrow(children: ReactNode): { arrows: ReactNode[]; body: ReactNode[] } {
45
+ const arrows: ReactNode[] = [];
46
+ const body: ReactNode[] = [];
47
+ Children.forEach(children, (child) => {
48
+ if (isValidElement(child) && child.type === TooltipArrow) arrows.push(child);
49
+ else body.push(child);
50
+ });
51
+ return { arrows, body };
52
+ }
53
+
54
+ /**
55
+ * The panel, drawn over the app and anchored to the trigger.
56
+ *
57
+ * Renders nothing while closed. On open it mounts in the `anchored` band of
58
+ * the overlay z-order, measures itself invisibly, resolves where it fits —
59
+ * Popover's resolver, so it flips and shifts the same way — and fades in from
60
+ * 4pt toward its resolved side; under reduce motion it only fades. `duration`
61
+ * ms after that entrance settles it hides itself.
62
+ *
63
+ * It takes no touch: there is no catcher under it and the panel is
64
+ * `pointerEvents="none"`, so a tap anywhere — the panel included — reaches
65
+ * what is under it, and the provider closes the tooltip on the way. Only an
66
+ * `isScrollable` body takes a touch, to scroll. It is hidden from assistive
67
+ * technology and never takes focus: its words already reached the trigger.
68
+ * Android back closes it while it is the top overlay.
69
+ *
70
+ * @example
71
+ * <Tooltip.Content placement="bottom">
72
+ * <Tooltip.Arrow />
73
+ * <Tooltip.Text>Copy link</Tooltip.Text>
74
+ * </Tooltip.Content>
75
+ */
76
+ export function TooltipContent({
77
+ children,
78
+ className,
79
+ placement = TOOLTIP_DEFAULTS.placement,
80
+ align = TOOLTIP_DEFAULTS.align,
81
+ offset = TOOLTIP_DEFAULTS.offset,
82
+ alignOffset = TOOLTIP_DEFAULTS.alignOffset,
83
+ variant = TOOLTIP_DEFAULTS.variant,
84
+ width = TOOLTIP_DEFAULTS.width,
85
+ minWidth,
86
+ maxHeight,
87
+ isScrollable = false,
88
+ style,
89
+ onTouchStart,
90
+ ...props
91
+ }: TooltipContentProps): ReactElement | null {
92
+ const { isOpen, close, duration, anchorRect, onContentTouchStart } = useTooltipContext();
93
+ const overlayId = useId();
94
+ const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
95
+
96
+ const clearTimer = useCallback(() => {
97
+ if (timer.current === null) return;
98
+ clearTimeout(timer.current);
99
+ timer.current = null;
100
+ }, []);
101
+
102
+ const startTimer = useCallback(() => {
103
+ clearTimer();
104
+ if (duration > 0) timer.current = setTimeout(close, duration);
105
+ }, [clearTimer, close, duration]);
106
+
107
+ useEffect(() => {
108
+ if (!isOpen) clearTimer();
109
+ }, [isOpen, clearTimer]);
110
+ useEffect(() => clearTimer, [clearTimer]);
111
+
112
+ const anchored = useAnchoredContent({
113
+ isOpen,
114
+ anchor: anchorRect,
115
+ placement,
116
+ align,
117
+ offset,
118
+ alignOffset,
119
+ collisionPadding: POPOVER_COLLISION_PADDING,
120
+ arrowInset: POPOVER_ARROW_INSET,
121
+ width,
122
+ minWidth,
123
+ maxHeight,
124
+ enterDistance: TOOLTIP_ENTER_DISTANCE,
125
+ enterScale: 1,
126
+ onEntered: startTimer,
127
+ });
128
+
129
+ useOverlayBackHandler({ id: overlayId, isEnabled: anchored.isMounted, onBack: close });
130
+
131
+ const contentContext = useMemo<TooltipContentContextValue>(
132
+ () => ({
133
+ variant,
134
+ placement: anchored.position?.placement ?? placement,
135
+ arrowOffset: anchored.position?.arrowOffset ?? 0,
136
+ size: anchored.size ?? { width: 0, height: 0 },
137
+ }),
138
+ [variant, anchored.position?.placement, placement, anchored.position?.arrowOffset, anchored.size]
139
+ );
140
+
141
+ if (!anchored.isMounted) return null;
142
+
143
+ const { arrows, body } = partitionArrow(children);
144
+
145
+ return (
146
+ <Overlay.Portal id={overlayId} layer="anchored">
147
+ <Animated.View
148
+ accessibilityElementsHidden
149
+ importantForAccessibility="no-hide-descendants"
150
+ onLayout={anchored.onLayout}
151
+ pointerEvents={isScrollable ? "box-none" : "none"}
152
+ style={[anchored.positionerStyle, anchored.animatedStyle]}
153
+ >
154
+ <View
155
+ accessible={false}
156
+ className={tooltipVariants({ variant }).content({ className })}
157
+ onTouchStart={(event) => {
158
+ onContentTouchStart();
159
+ onTouchStart?.(event);
160
+ }}
161
+ style={[anchored.frameStyle, style]}
162
+ {...props}
163
+ >
164
+ <TooltipContentContext.Provider value={contentContext}>
165
+ {isScrollable ? (
166
+ <ScrollView className="shrink grow-0" contentContainerClassName="gap-1">
167
+ {body}
168
+ </ScrollView>
169
+ ) : (
170
+ body
171
+ )}
172
+ {arrows}
173
+ </TooltipContentContext.Provider>
174
+ </View>
175
+ </Animated.View>
176
+ </Overlay.Portal>
177
+ );
178
+ }
179
+ TooltipContent.displayName = "DelacourUI.Tooltip.Content";
@@ -0,0 +1,19 @@
1
+ import type { ReactElement } from "react";
2
+ import { Text, type TextPresetProps } from "../text";
3
+ import { useOptionalTooltipContent } from "./tooltip.context";
4
+ import { TOOLTIP_DEFAULTS, tooltipVariants } from "./tooltip.variants";
5
+
6
+ export type TooltipDescriptionProps = TextPresetProps;
7
+
8
+ /**
9
+ * Supporting copy under the title — a `Text.Caption`, muted on a surface card
10
+ * and the background colour at 80% on an inverted chip.
11
+ *
12
+ * @example
13
+ * <Tooltip.Description>Changes reach your other devices within a minute.</Tooltip.Description>
14
+ */
15
+ export function TooltipDescription({ className, ...props }: TooltipDescriptionProps): ReactElement {
16
+ const variant = useOptionalTooltipContent()?.variant ?? TOOLTIP_DEFAULTS.variant;
17
+ return <Text.Caption className={tooltipVariants({ variant }).description({ className })} {...props} />;
18
+ }
19
+ TooltipDescription.displayName = "DelacourUI.Tooltip.Description";
@@ -0,0 +1,21 @@
1
+ import type { ReactElement } from "react";
2
+ import { Text, type TextPresetProps } from "../text";
3
+ import { useOptionalTooltipContent } from "./tooltip.context";
4
+ import { TOOLTIP_DEFAULTS, tooltipVariants } from "./tooltip.variants";
5
+
6
+ export type TooltipTextProps = TextPresetProps;
7
+
8
+ /**
9
+ * The one-line label — a `Text.Caption`, inked for the panel it sits on: the
10
+ * background colour on an inverted chip, the popover foreground on a surface
11
+ * card. Caption rather than label because a tooltip is an aside to its
12
+ * control, not a second control.
13
+ *
14
+ * @example
15
+ * <Tooltip.Text>Share</Tooltip.Text>
16
+ */
17
+ export function TooltipText({ className, ...props }: TooltipTextProps): ReactElement {
18
+ const variant = useOptionalTooltipContent()?.variant ?? TOOLTIP_DEFAULTS.variant;
19
+ return <Text.Caption className={tooltipVariants({ variant }).text({ className })} {...props} />;
20
+ }
21
+ TooltipText.displayName = "DelacourUI.Tooltip.Text";
@@ -0,0 +1,20 @@
1
+ import type { ReactElement } from "react";
2
+ import { Text, type TextPresetProps } from "../text";
3
+ import { useOptionalTooltipContent } from "./tooltip.context";
4
+ import { TOOLTIP_DEFAULTS, tooltipVariants } from "./tooltip.variants";
5
+
6
+ export type TooltipTitleProps = TextPresetProps;
7
+
8
+ /**
9
+ * A heading over a description, for the surface variant — a `Text.Label`, the
10
+ * size a popover's title takes. Not announced as a header: the panel is hidden
11
+ * from assistive technology, and its words reach the trigger through `label`.
12
+ *
13
+ * @example
14
+ * <Tooltip.Title>Sync</Tooltip.Title>
15
+ */
16
+ export function TooltipTitle({ className, ...props }: TooltipTitleProps): ReactElement {
17
+ const variant = useOptionalTooltipContent()?.variant ?? TOOLTIP_DEFAULTS.variant;
18
+ return <Text.Label className={tooltipVariants({ variant }).title({ className })} {...props} />;
19
+ }
20
+ TooltipTitle.displayName = "DelacourUI.Tooltip.Title";
@@ -0,0 +1,103 @@
1
+ import { isValidElement, type ReactElement } from "react";
2
+ import type { GestureResponderEvent } from "react-native";
3
+ import { Slot } from "../../lib/slot";
4
+ import { Pressable, type PressableProps } from "../pressable";
5
+ import { useTooltipContext } from "./tooltip.context";
6
+ import { readableTextOf, resolveTooltipAccessibility } from "./tooltip.variants";
7
+
8
+ export type TooltipTriggerProps = PressableProps;
9
+
10
+ /** The `accessibilityLabel` an `asChild` trigger's child already carries, if any. */
11
+ function childLabel(children: PressableProps["children"]): string | undefined {
12
+ if (!isValidElement<{ accessibilityLabel?: unknown }>(children)) return undefined;
13
+ const label = children.props.accessibilityLabel;
14
+ return typeof label === "string" ? label : undefined;
15
+ }
16
+
17
+ /**
18
+ * What the trigger is already called: its own label, the child's when it is
19
+ * donated to, or else its visible text — which is what a screen reader names
20
+ * a control by when nothing else does.
21
+ */
22
+ function triggerName(
23
+ accessibilityLabel: string | undefined,
24
+ asChild: boolean,
25
+ children: PressableProps["children"]
26
+ ): string | undefined {
27
+ return accessibilityLabel ?? (asChild ? childLabel(children) : undefined) ?? readableTextOf(children);
28
+ }
29
+
30
+ /**
31
+ * The control the tooltip names, and the view the panel is anchored to.
32
+ *
33
+ * On its own it is this library's `Pressable`. **`asChild` donates the
34
+ * gesture** rather than wrapping the child, for `Popover.Trigger`'s reason —
35
+ * a `Button` inside a pressable trigger would win the touch. With the default
36
+ * `openOn="longPress"` the trigger hands the child an `onLongPress`; with
37
+ * `"press"`, an `onPress`. Either is chained ahead of the child's own, so the
38
+ * child's handler still runs, and a long press never costs the child its tap:
39
+ * a tap held past the long-press delay fails, so `onPress` does not fire for
40
+ * it. The measuring ref is composed onto the child's own, so the child has to
41
+ * be built on `Pressable`.
42
+ *
43
+ * The tooltip's `label` becomes the trigger's accessibility label when it has
44
+ * none, or its hint when it does — and visible text counts as a name, so a
45
+ * `<Button>Sync now</Button>` keeps saying "Sync now".
46
+ *
47
+ * @example
48
+ * <Tooltip.Trigger asChild>
49
+ * <Button size="icon-md" variant="ghost"><Icon icon={IconShare} /></Button>
50
+ * </Tooltip.Trigger>
51
+ */
52
+ export function TooltipTrigger({
53
+ asChild = false,
54
+ accessibilityLabel,
55
+ children,
56
+ onPress,
57
+ onLongPress,
58
+ onTouchStart,
59
+ ...props
60
+ }: TooltipTriggerProps): ReactElement {
61
+ const { openOn, label, activate, onTriggerTouchStart, triggerRef } = useTooltipContext();
62
+
63
+ const accessibility = resolveTooltipAccessibility({
64
+ label,
65
+ triggerLabel: triggerName(accessibilityLabel, asChild, children),
66
+ });
67
+
68
+ const handlers = {
69
+ onTouchStart: (event: GestureResponderEvent) => {
70
+ onTriggerTouchStart();
71
+ onTouchStart?.(event);
72
+ },
73
+ onPress:
74
+ openOn === "press"
75
+ ? () => {
76
+ activate("press");
77
+ onPress?.();
78
+ }
79
+ : onPress,
80
+ onLongPress:
81
+ openOn === "longPress"
82
+ ? () => {
83
+ activate("longPress");
84
+ onLongPress?.();
85
+ }
86
+ : onLongPress,
87
+ };
88
+
89
+ if (asChild) {
90
+ return (
91
+ <Slot {...props} accessibilityLabel={accessibilityLabel} {...accessibility} {...handlers} ref={triggerRef}>
92
+ {children}
93
+ </Slot>
94
+ );
95
+ }
96
+
97
+ return (
98
+ <Pressable {...props} accessibilityLabel={accessibilityLabel} {...accessibility} {...handlers} ref={triggerRef}>
99
+ {children}
100
+ </Pressable>
101
+ );
102
+ }
103
+ TooltipTrigger.displayName = "DelacourUI.Tooltip.Trigger";
@@ -0,0 +1,68 @@
1
+ import { createContext, useContext } from "react";
2
+ import type { AnchoredSize, AnchorRect, PopoverPlacement } from "../popover/popover.position";
3
+ import type { MeasurableNode } from "../popover/use-anchor-measure";
4
+ import type { TooltipGesture, TooltipOpenOn, TooltipVariant } from "./tooltip.variants";
5
+
6
+ /** What `Tooltip` shares with its parts. */
7
+ export type TooltipContextValue = {
8
+ isOpen: boolean;
9
+ setOpen: (isOpen: boolean) => void;
10
+ close: () => void;
11
+ openOn: TooltipOpenOn;
12
+ /** The tooltip's words, for the trigger's label or hint. */
13
+ label: string | undefined;
14
+ /** ms after the entrance settles before it hides; 0 for never. Already resolved against the screen reader. */
15
+ duration: number;
16
+ /** The trigger reports a gesture; the root decides whether it toggles. */
17
+ activate: (gesture: TooltipGesture) => void;
18
+ /** The trigger reports that a touch started on it — before the provider hears the same touch. */
19
+ onTriggerTouchStart: () => void;
20
+ /** The panel reports that a touch started on it, so a scrollable body is not an outside tap. */
21
+ onContentTouchStart: () => void;
22
+ /** The trigger's frame in window coordinates, `null` until it is measured on open. */
23
+ anchorRect: AnchorRect | null;
24
+ /** The callback ref `Tooltip.Trigger` measures through. */
25
+ triggerRef: (node: MeasurableNode | null) => void;
26
+ };
27
+
28
+ export const TooltipContext = createContext<TooltipContextValue | null>(null);
29
+ TooltipContext.displayName = "DelacourUI.Tooltip.Context";
30
+
31
+ /**
32
+ * The tooltip a part sits in — open state and the close. Throws outside a
33
+ * `<Tooltip>`.
34
+ *
35
+ * @example
36
+ * function Hint() {
37
+ * const { isOpen } = useTooltip();
38
+ * return isOpen ? <Text.Caption>Shown</Text.Caption> : null;
39
+ * }
40
+ */
41
+ export function useTooltip(): Pick<TooltipContextValue, "isOpen" | "setOpen" | "close"> {
42
+ const context = useContext(TooltipContext);
43
+ if (context === null) throw new Error("useTooltip must be used inside a <Tooltip>.");
44
+ return context;
45
+ }
46
+
47
+ /** The full context, for the parts. Throws outside a `<Tooltip>`. */
48
+ export function useTooltipContext(): TooltipContextValue {
49
+ const context = useContext(TooltipContext);
50
+ if (context === null) throw new Error("Tooltip parts must be used inside a <Tooltip>.");
51
+ return context;
52
+ }
53
+
54
+ /** What `Tooltip.Content` shares with the parts drawn inside the panel. */
55
+ export type TooltipContentContextValue = {
56
+ variant: TooltipVariant;
57
+ placement: PopoverPlacement;
58
+ arrowOffset: number;
59
+ size: AnchoredSize;
60
+ };
61
+
62
+ export const TooltipContentContext = createContext<TooltipContentContextValue | null>(null);
63
+ TooltipContentContext.displayName = "DelacourUI.Tooltip.ContentContext";
64
+
65
+ /** The panel's variant and placement, or `null` outside `Tooltip.Content`. */
66
+ export function useOptionalTooltipContent(): TooltipContentContextValue | null {
67
+ return useContext(TooltipContentContext);
68
+ }