@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,353 @@
1
+ import type { VariantProps } from "tailwind-variants";
2
+ import { tv } from "../../lib/tv";
3
+
4
+ export const MENU_PLACEMENTS = ["top", "bottom"] as const;
5
+ export const MENU_ALIGNS = ["start", "center", "end"] as const;
6
+ export const MENU_ITEM_VARIANTS = ["default", "destructive"] as const;
7
+ export const MENU_RADIO_INDICATORS = ["check", "dot"] as const;
8
+
9
+ export type MenuPlacement = (typeof MENU_PLACEMENTS)[number];
10
+ export type MenuAlign = (typeof MENU_ALIGNS)[number];
11
+ export type MenuItemVariant = (typeof MENU_ITEM_VARIANTS)[number];
12
+ export type MenuRadioIndicator = (typeof MENU_RADIO_INDICATORS)[number];
13
+
14
+ /** Where the panel is anchored, in window coordinates. A zero-size rect is a point. */
15
+ export type MenuAnchorRect = { x: number; y: number; width: number; height: number };
16
+
17
+ /** A screen edge's safe-area inset, in points. */
18
+ export type MenuInsets = { top: number; right: number; bottom: number; left: number };
19
+
20
+ /** `align` with RTL already applied — the edge the panel lines up with on screen. */
21
+ export type MenuPhysicalAlign = "left" | "center" | "right";
22
+
23
+ /** Gap between the anchor and the panel, in points. */
24
+ export const MENU_DEFAULT_OFFSET = 6;
25
+
26
+ /** What the panel keeps clear of the safe area on every side, in points. */
27
+ export const MENU_EDGE_MARGIN = 8;
28
+
29
+ /** The narrowest a panel resolves to when nothing names a width. */
30
+ export const MENU_DEFAULT_MIN_WIDTH = 200;
31
+
32
+ /** The scale the panel grows from on enter and shrinks to on exit. */
33
+ export const MENU_ENTER_SCALE = 0.96;
34
+
35
+ /** How long the panel takes to appear and to leave, in milliseconds. */
36
+ export const MENU_MOTION = { enterMs: 160, exitMs: 120 } as const;
37
+
38
+ /**
39
+ * A row's press: the fill fades in fast and out slower, and the row dips to
40
+ * `scale`. Both read one shared value.
41
+ */
42
+ export const MENU_PRESS = { inMs: 90, outMs: 160, scale: 0.98 } as const;
43
+
44
+ /** How far a tap may travel before it stops being a tap — a scroll, in a long menu. */
45
+ export const MENU_TAP_MAX_DISTANCE = 10;
46
+
47
+ /** Where the sub-trigger's chevron points at each end of the travel, in degrees. */
48
+ export const MENU_SUB_ROTATION = { collapsed: 0, expanded: 90 } as const;
49
+
50
+ /**
51
+ * The submenu's spring — `Collapsible`'s, restated rather than imported so
52
+ * `delacour add menu` copies one folder. Critically damped, because an
53
+ * overshoot in height flashes the panel behind the rows.
54
+ */
55
+ export const MENU_SUB_SPRING = { damping: 26, mass: 0.4, stiffness: 400 } as const;
56
+
57
+ /** Theme token a row's icon is drawn in, per variant. */
58
+ export const MENU_ICON_TOKEN: Record<MenuItemVariant, string> = {
59
+ default: "popover-foreground",
60
+ destructive: "destructive",
61
+ };
62
+
63
+ /** Theme token an indicator (check, chevron) is drawn in. */
64
+ export const MENU_INDICATOR_TOKEN = "popover-foreground";
65
+
66
+ /** The accessibility hint a trigger carries. */
67
+ export const MENU_TRIGGER_HINT = "Opens a menu";
68
+
69
+ /** `align` as a screen edge: start and end swap under RTL, center does not. */
70
+ export function resolveMenuPhysicalAlign(align: MenuAlign, isRTL: boolean): MenuPhysicalAlign {
71
+ if (align === "center") return "center";
72
+ const isStart = align === "start";
73
+ return isStart !== isRTL ? "left" : "right";
74
+ }
75
+
76
+ export type MenuPlacementInput = {
77
+ anchor: MenuAnchorRect;
78
+ /** The panel's size — its resolved width and its measured (or estimated) height. */
79
+ content: { width: number; height: number };
80
+ window: { width: number; height: number };
81
+ insets: MenuInsets;
82
+ placement: MenuPlacement;
83
+ align: MenuAlign;
84
+ offset: number;
85
+ isRTL: boolean;
86
+ };
87
+
88
+ export type MenuPlacementResult = {
89
+ /** The panel's top edge, for a panel below the anchor. */
90
+ top: number;
91
+ /**
92
+ * The distance from the window's bottom to the panel's bottom edge — what a
93
+ * panel above the anchor is positioned by, so it grows away from the anchor
94
+ * when its content does.
95
+ */
96
+ bottom: number;
97
+ left: number;
98
+ /** The side it actually opened on, after any flip. */
99
+ placement: MenuPlacement;
100
+ /** The room on that side, inside the safe area and the margin. Never negative. */
101
+ maxHeight: number;
102
+ };
103
+
104
+ /**
105
+ * Where the panel goes.
106
+ *
107
+ * **Vertical.** The requested side wins when the content fits there; otherwise it
108
+ * flips to the other side if the content fits there; otherwise it takes the
109
+ * larger side and `maxHeight` caps it, so the rows scroll. Room is measured
110
+ * inside the safe area plus {@link MENU_EDGE_MARGIN}.
111
+ *
112
+ * **Horizontal.** `align` lines an edge (or the centre) up with the anchor's —
113
+ * start and end swap under RTL — and the result is clamped inside the safe area
114
+ * plus the margin. A panel wider than the room pins to the left edge.
115
+ *
116
+ * A zero-size anchor (a long-press point) runs through the same arithmetic with
117
+ * no special case: its edges coincide.
118
+ *
119
+ * Pure, and the one place the geometry lives — when Popover lands, Menu.Content
120
+ * moves onto it and this resolver moves with it.
121
+ */
122
+ export function resolveMenuPlacement({
123
+ anchor,
124
+ content,
125
+ window,
126
+ insets,
127
+ placement,
128
+ align,
129
+ offset,
130
+ isRTL,
131
+ }: MenuPlacementInput): MenuPlacementResult {
132
+ const topBound = insets.top + MENU_EDGE_MARGIN;
133
+ const bottomBound = window.height - insets.bottom - MENU_EDGE_MARGIN;
134
+ const belowStart = anchor.y + anchor.height + offset;
135
+ const aboveEnd = anchor.y - offset;
136
+
137
+ const room: Record<MenuPlacement, number> = {
138
+ bottom: Math.max(0, bottomBound - belowStart),
139
+ top: Math.max(0, aboveEnd - topBound),
140
+ };
141
+ const other: MenuPlacement = placement === "bottom" ? "top" : "bottom";
142
+
143
+ let side: MenuPlacement;
144
+ if (content.height <= room[placement]) side = placement;
145
+ else if (content.height <= room[other]) side = other;
146
+ else side = room[placement] >= room[other] ? placement : other;
147
+
148
+ const maxHeight = room[side];
149
+ const height = Math.min(content.height, maxHeight);
150
+ const top = side === "bottom" ? belowStart : aboveEnd - height;
151
+ const bottom = window.height - (top + height);
152
+
153
+ const physical = resolveMenuPhysicalAlign(align, isRTL);
154
+ let left: number;
155
+ if (physical === "left") left = anchor.x;
156
+ else if (physical === "right") left = anchor.x + anchor.width - content.width;
157
+ else left = anchor.x + anchor.width / 2 - content.width / 2;
158
+
159
+ const minLeft = insets.left + MENU_EDGE_MARGIN;
160
+ const maxLeft = window.width - insets.right - MENU_EDGE_MARGIN - content.width;
161
+ left = maxLeft < minLeft ? minLeft : Math.min(Math.max(left, minLeft), maxLeft);
162
+
163
+ return { bottom, left, maxHeight, placement: side, top };
164
+ }
165
+
166
+ /**
167
+ * The translate that makes a centre-origin scale look like it grows out of the
168
+ * anchor's side, at the panel's smallest. The worklet multiplies it by
169
+ * `1 - progress`, so it is zero once the panel has arrived.
170
+ *
171
+ * React Native has no `transformOrigin` that Reanimated animates reliably on
172
+ * both platforms, so the origin is approximated: a panel below grows from its
173
+ * top edge, a panel above from its bottom, and a start-aligned one from its
174
+ * leading edge.
175
+ */
176
+ export function resolveMenuOrigin({
177
+ placement,
178
+ align,
179
+ width,
180
+ height,
181
+ }: {
182
+ placement: MenuPlacement;
183
+ align: MenuPhysicalAlign;
184
+ width: number;
185
+ height: number;
186
+ }): { x: number; y: number } {
187
+ const shrink = 1 - MENU_ENTER_SCALE;
188
+ const y = ((placement === "bottom" ? -1 : 1) * shrink * height) / 2;
189
+ const x = align === "center" ? 0 : ((align === "left" ? -1 : 1) * shrink * width) / 2;
190
+ return { x, y };
191
+ }
192
+
193
+ /**
194
+ * The panel's width: the caller's `width`, or the trigger's floored at
195
+ * {@link MENU_DEFAULT_MIN_WIDTH}; then floored by `minWidth`; then capped by
196
+ * `maxWidth` — the room between the safe-area edges — so it never runs off screen.
197
+ */
198
+ export function resolveMenuWidth({
199
+ width,
200
+ minWidth,
201
+ maxWidth,
202
+ triggerWidth,
203
+ }: {
204
+ width?: number;
205
+ minWidth?: number;
206
+ maxWidth?: number;
207
+ triggerWidth: number;
208
+ }): number {
209
+ const base = width ?? Math.max(triggerWidth, MENU_DEFAULT_MIN_WIDTH);
210
+ const floored = Math.max(base, minWidth ?? 0);
211
+ return maxWidth === undefined ? floored : Math.min(floored, maxWidth);
212
+ }
213
+
214
+ /** The group's next value when a radio row is chosen. A radio never clears, so re-choosing keeps it. */
215
+ export function resolveRadioNext(_current: string | undefined, value: string): string {
216
+ return value;
217
+ }
218
+
219
+ /** The kinds of row a menu has. */
220
+ export type MenuRowKind = "item" | "checkbox" | "radio" | "sub-trigger";
221
+
222
+ const CLOSE_ON_SELECT: Record<MenuRowKind, boolean> = {
223
+ checkbox: false,
224
+ item: true,
225
+ radio: true,
226
+ "sub-trigger": false,
227
+ };
228
+
229
+ /**
230
+ * Whether choosing a row closes the menu: an item and a radio do, a checkbox does
231
+ * not (several are usually toggled in one visit), and a sub-trigger only expands.
232
+ * An explicit `isClosedOnSelect` wins.
233
+ */
234
+ export function resolveCloseOnSelect(kind: MenuRowKind, explicit?: boolean): boolean {
235
+ return explicit ?? CLOSE_ON_SELECT[kind];
236
+ }
237
+
238
+ export type MenuItemAccessibilityInput =
239
+ | { kind: "item"; isDisabled: boolean }
240
+ | { kind: "checkbox"; isDisabled: boolean; isChecked: boolean }
241
+ | { kind: "radio"; isDisabled: boolean; isSelected: boolean }
242
+ | { kind: "sub-trigger"; isDisabled: boolean; isExpanded: boolean };
243
+
244
+ export type MenuItemAccessibilityState = {
245
+ disabled: boolean;
246
+ checked?: boolean;
247
+ selected?: boolean;
248
+ expanded?: boolean;
249
+ };
250
+
251
+ /** The `accessibilityState` a row announces — one source, so the matrix is testable. */
252
+ export function resolveMenuItemAccessibility(input: MenuItemAccessibilityInput): MenuItemAccessibilityState {
253
+ switch (input.kind) {
254
+ case "item":
255
+ return { disabled: input.isDisabled };
256
+ case "checkbox":
257
+ return { checked: input.isChecked, disabled: input.isDisabled };
258
+ case "radio":
259
+ return { disabled: input.isDisabled, selected: input.isSelected };
260
+ case "sub-trigger":
261
+ return { disabled: input.isDisabled, expanded: input.isExpanded };
262
+ default:
263
+ return { disabled: false };
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Styling for every part of a menu.
269
+ *
270
+ * One slotted `tv()`, because the parts cannot import each other without closing
271
+ * a cycle (AGENTS.md rule 3) and all of them read the same row axes.
272
+ *
273
+ * **`background` is `bg-popover`, never `bg-overlay`.** `overlay` is the
274
+ * translucent scrim colour, and a panel painted in it is a see-through card.
275
+ * The scrim slot wears it instead.
276
+ *
277
+ * **`itemFill` is the row's pressed background**, an absolute layer whose
278
+ * opacity the press drives. There is no wash on top: the fill is the feedback.
279
+ *
280
+ * **The disabled fade lands on `item`**, a plain `Animated.View` that owns only
281
+ * `transform`, so a class opacity there is not overwritten.
282
+ *
283
+ * No slot worn by a `View` carries `text-*` (rule 1). Free of React Native
284
+ * imports so it stays reachable from `bun test`.
285
+ */
286
+ export const menuVariants = tv({
287
+ slots: {
288
+ /**
289
+ * The positioned, animated layer. Casts the panel's shadow, so it carries no
290
+ * `overflow-hidden` — iOS clips a shadow to a clipping view's own bounds.
291
+ */
292
+ panel: "absolute shadow-lg",
293
+ /** The panel's frame: corner, hairline and clip. Its surface is `background`. */
294
+ content: "overflow-hidden rounded-lg border border-border",
295
+ /** The panel's surface, drawn behind the scroller. */
296
+ background: "absolute inset-0 bg-popover",
297
+ /** The full-screen tint behind the panel, when `hasScrim`. */
298
+ scrim: "absolute inset-0 bg-overlay",
299
+ /** What `backdrop` is drawn in — full-screen, over the scrim, behind the panel. */
300
+ backdrop: "absolute inset-0",
301
+ /** The scroller's padding, around the rows. */
302
+ scroll: "p-1",
303
+ /** One row. */
304
+ item: "min-h-11 flex-row items-center gap-3 rounded-md px-3 py-2",
305
+ /** The row's pressed fill. Its opacity is an animated style. */
306
+ itemFill: "absolute inset-0 rounded-md bg-accent",
307
+ /** The label-and-description column. */
308
+ itemContent: "min-w-0 flex-1 justify-center gap-0.5",
309
+ itemLabel: "text-popover-foreground text-sm",
310
+ itemDescription: "text-muted-foreground text-xs",
311
+ itemShortcut: "ms-auto text-muted-foreground text-xs",
312
+ /** Edge length of a row's glyph — 18pt, the reserved icon column. */
313
+ itemIcon: "size-icon-md",
314
+ /** The section heading. */
315
+ label: "px-3 pt-2 pb-1 font-medium text-muted-foreground text-xs",
316
+ /** The box an indicator (check, dot, chevron) sits in — the icon column's footprint. */
317
+ indicator: "size-icon-md items-center justify-center",
318
+ /** The radio `dot` indicator. */
319
+ indicatorDot: "size-1.5 rounded-full bg-popover-foreground",
320
+ /** The rows a submenu discloses, stepped in from the trigger. */
321
+ subContent: "ps-3",
322
+ /** The submenu's measured clip. Its height is an animated style. */
323
+ subClip: "overflow-hidden",
324
+ /** The submenu's measured inner layer — out of flow so it measures its content alone. */
325
+ subInner: "absolute top-0 right-0 left-0",
326
+ /** The separator's spacing. */
327
+ separator: "my-1",
328
+ },
329
+ variants: {
330
+ variant: {
331
+ default: {},
332
+ destructive: {
333
+ itemFill: "bg-destructive-soft",
334
+ itemLabel: "text-destructive",
335
+ },
336
+ },
337
+ isInset: {
338
+ true: { label: "ps-[42px]" },
339
+ false: {},
340
+ },
341
+ isDisabled: {
342
+ true: { item: "opacity-50" },
343
+ false: {},
344
+ },
345
+ },
346
+ defaultVariants: {
347
+ variant: "default",
348
+ isInset: false,
349
+ isDisabled: false,
350
+ },
351
+ });
352
+
353
+ export type MenuVariantProps = VariantProps<typeof menuVariants>;
@@ -0,0 +1,135 @@
1
+ # Meter
2
+
3
+ A measurement on a fixed scale, coloured by where it falls — storage used, a
4
+ battery's charge, a password's strength. Built on `Progress`: the same track, fill,
5
+ header and label, with a root that clamps the reading, judges it, and paints the
6
+ fill that judgement's colour. Adds `Meter.Segments` for a scale drawn as whole
7
+ blocks.
8
+
9
+ `import { Meter } from "@delacour/react-native-ui/meter";`
10
+
11
+ ## Files
12
+
13
+ | File | What it holds |
14
+ | --- | --- |
15
+ | `index.ts` | → `@delacour/react-native-ui/meter` |
16
+ | `meter.tsx` | Root + the `Object.assign` compound surface; publishes both contexts |
17
+ | `meter-header.tsx` | `Meter.Header` — `Progress.Header`, named for the meter |
18
+ | `meter-label.tsx` | `Meter.Label` — `Progress.Label`, named for the meter |
19
+ | `meter-output.tsx` | `Meter.Output`, the readout, whose function child is handed the region |
20
+ | `meter-track.tsx` | `Meter.Track` — `Progress.Track`, named for the meter |
21
+ | `meter-fill.tsx` | `Meter.Fill` — `Progress.Fill`, named for the meter |
22
+ | `meter-segments.tsx` | `Meter.Segments`, the blocks, and the internal `Meter.Segments.Segment` |
23
+ | `meter.context.tsx` | `MeterContext`, `useMeter()`, `useMeterContext()`, `useMeterPart()` |
24
+ | `meter.types.ts` | `MeterRenderProps`, shared by the context and the readout |
25
+ | `meter.variants.ts` | The blocks' `tv()` slots + the scale, region, threshold and segment resolvers |
26
+ | `meter.variants.test.ts` | |
27
+
28
+ ## Built on Progress
29
+
30
+ - **The root publishes `ProgressContextValue` as well as its own context.**
31
+ `Progress.Track` and `Progress.Fill` read only `ratio`, `isIndeterminate`,
32
+ `color`, `size` and `trackSize` — the progress bar was written to be reused
33
+ this way — so `Meter.Track` and `Meter.Fill` *are* those parts, and the fill's
34
+ UI-thread transform, its seeded first frame and its hide-until-measured come
35
+ along without a line restated. `isIndeterminate` is always `false`.
36
+ - **Each reused part is a thin wrapper, not the progress part assigned
37
+ directly.** The wrapper calls `useMeterPart` first, so a `Meter.Fill` outside
38
+ a meter throws naming `Meter.Fill` rather than `Progress.Fill`, and DevTools
39
+ shows `DelacourUI.Meter.Fill` (rule 12).
40
+ - **The parts come through `../progress`, the index; the resolvers through
41
+ `../progress/progress.variants`, the leaf** (rule 3). Progress imports nothing
42
+ from here, so there is no cycle, and the registry records `progress` as a
43
+ dependency of `meter`.
44
+ - **Colours and sizes are the progress bar's tuples, re-exported, not
45
+ restated.** A test pins them equal, and another pins a block's height and
46
+ paint to the progress track's and fill's, so a segmented meter and a
47
+ continuous one in one card read as one instrument.
48
+
49
+ ## Judging the reading
50
+
51
+ - **Two ways, never both, and the prop type says so.** `MeterProps` is a union:
52
+ `low` / `high` / `optimum` *or* `thresholds`. Passing both is a type error
53
+ rather than a precedence rule. `resolveMeterScale` turns the props into a
54
+ `MeterScale` — `plain`, `regions` or `thresholds` — and `resolveMeterColor`
55
+ switches on its `kind`.
56
+ - **Regions follow a gauge's three-part model.** `low` and `high` default to the
57
+ scale's ends and `optimum` to its middle; each is clamped into the scale and
58
+ `high` is never below `low`, so a boundary written the wrong way round is
59
+ pulled into order rather than inverting every region. `optimum` below `low`
60
+ makes the bottom good (a disk), above `high` the top (a battery), between them
61
+ the band (a temperature). The regions paint `success`, `warning`,
62
+ `destructive` — `METER_REGION_COLORS`, fixed, so a region reads the same in
63
+ every meter in an app — and `color` is ignored.
64
+ - **Thresholds: the highest `from` reached wins, in any listed order.** `from`
65
+ is in the reading's units, not a percentage — a threshold on a 0–256 GB meter
66
+ is written in gigabytes. A non-finite `from` is ignored. Below every threshold
67
+ `color` applies.
68
+ - **The clamped reading is what is judged.** A disk over its capacity is
69
+ `critical`, not off the scale.
70
+ - **Colour is never the only signal, and `valueLabel` takes a function for it.**
71
+ Nothing speaks the region on its own — the words are domain-specific ("Almost
72
+ full", "Weak") — so `valueLabel` may be a function handed the judged
73
+ `MeterReading`, and `resolveMeterValueLabel` calls it. A `Meter.Output`
74
+ function reading `region` is **not** enough: the output is hidden from
75
+ assistive technology, so its words are seen and never heard. The simulator
76
+ pass caught exactly that — the regions demo drew "Filling up · 82%" while
77
+ VoiceOver's value was "82%".
78
+
79
+ ## The scale
80
+
81
+ - **`meterRange` makes the scale finite and ordered.** A non-finite bound takes
82
+ 0 or 100; an inverted or empty scale collapses at its floor and reads empty.
83
+ `progressRatio` would already return 0 for it, but the accessibility value
84
+ needs `min ≤ now ≤ max` too.
85
+ - **`meterValue` clamps to the ends; `NaN` reads at the floor and an infinity at
86
+ the end it points to.** `progressRatio` alone returns 0 for `+Infinity`,
87
+ which would draw a full disk as empty.
88
+ - **The formatted readout uses the clamped reading**, so a non-percent format
89
+ never prints `300 GB` on a 256 GB scale.
90
+
91
+ ## Segments
92
+
93
+ - **Whole blocks only.** `litSegments` floors, so four blocks say "three out of
94
+ four" where a bar says "about seventy percent" — a password is not seventy
95
+ percent strong. A small epsilon absorbs floating-point error, so `0.7 × 10`
96
+ lights seven.
97
+ - **Any reading above the floor lights at least one block.** Rounding down would
98
+ leave the first quarter of a four-block meter dark, and "a little" looking like
99
+ "none" is the reading a meter can least afford to get wrong.
100
+ - **`meterSegmentCount` rounds a fractional count down, clamps above
101
+ `METER_MAX_SEGMENTS` (100), and falls back to the continuous bar** for a count
102
+ under one or not finite, rather than drawing nothing.
103
+ - **A block's lit colour is a layer that fades its opacity**, not the block's own
104
+ background: an opacity animates on the UI thread, where a background would need
105
+ two token values interpolated in JavaScript. Each layer is seeded at its
106
+ current state, so a list of meters does not replay the lighting as rows mount,
107
+ and the timing keeps Reanimated's default reduce-motion policy — with the system
108
+ setting on, a block lights without a fade. Unlike the progress bar's loop,
109
+ nothing here has to keep moving to be read.
110
+ - **`segments` lives on the root, not on `Meter.Segments`.** The lit count is
111
+ part of the render props a readout reads, so the root has to know it.
112
+ `Meter.Segments` without a count renders nothing; `Meter.Track` with one still
113
+ draws the continuous bar.
114
+
115
+ ## What it refuses
116
+
117
+ - **No `isDisabled`, `isInvalid`, `Field` or indeterminate state.** A meter
118
+ reports a reading and accepts no input; `resolveMeterAxes` takes one argument
119
+ and a test pins that. A reading that is not known yet is `Progress`'s
120
+ indeterminate loop, not a meter.
121
+ - **No `label` / `showValueLabel` props.** The anatomy is written out, as
122
+ `Progress`'s is — `Meter.Header` with a `Meter.Label` and a `Meter.Output` — and
123
+ a bare `<Meter value={68} />` still draws the scale, because a compound root
124
+ with no default children renders an empty `View` with no error.
125
+
126
+ ## Accessibility
127
+
128
+ - **The root is the one accessible element**, `accessibilityRole="progressbar"`
129
+ — React Native has no meter role, and this is the one that publishes a range
130
+ and a value on both platforms. `resolveProgressAccessibility` decides the
131
+ spoken text: the percentage over 0–100, `168 of 256` otherwise, or
132
+ `valueLabel`. Thresholds, regions and segments never change it.
133
+ - **`Meter.Label` is the name, `Meter.Output` is hidden** — the root already
134
+ speaks the value, so a visible readout would be read twice. A meter with no
135
+ label needs an `accessibilityLabel`.
@@ -0,0 +1,43 @@
1
+ export { Meter, type MeterProps } from "./meter";
2
+ export { type MeterContextValue, MeterProvider, useMeter, useMeterContext } from "./meter.context";
3
+ export type { MeterRenderChildren, MeterRenderProps } from "./meter.types";
4
+ export {
5
+ litSegments,
6
+ METER_COLORS,
7
+ METER_DEFAULT_COLOR,
8
+ METER_DEFAULT_SIZE,
9
+ METER_MAX_SEGMENTS,
10
+ METER_MAX_VALUE,
11
+ METER_MIN_VALUE,
12
+ METER_REGION_COLORS,
13
+ METER_REGIONS,
14
+ METER_SEGMENT_FADE_MS,
15
+ METER_SIZES,
16
+ type MeterAxes,
17
+ type MeterColor,
18
+ type MeterOwnAxes,
19
+ type MeterReading,
20
+ type MeterRegion,
21
+ type MeterScale,
22
+ type MeterScaleInput,
23
+ type MeterSize,
24
+ type MeterThreshold,
25
+ type MeterValueLabel,
26
+ type MeterVariantProps,
27
+ meterRange,
28
+ meterSegmentCount,
29
+ meterValue,
30
+ meterVariants,
31
+ resolveMeterAxes,
32
+ resolveMeterColor,
33
+ resolveMeterRegion,
34
+ resolveMeterScale,
35
+ resolveMeterValueLabel,
36
+ thresholdColor,
37
+ } from "./meter.variants";
38
+ export type { MeterFillProps } from "./meter-fill";
39
+ export type { MeterHeaderProps } from "./meter-header";
40
+ export type { MeterLabelProps } from "./meter-label";
41
+ export type { MeterOutputProps } from "./meter-output";
42
+ export type { MeterSegmentsProps } from "./meter-segments";
43
+ export type { MeterTrackProps } from "./meter-track";
@@ -0,0 +1,19 @@
1
+ import type { ReactElement } from "react";
2
+ import { Progress, type ProgressFillProps } from "../progress";
3
+ import { useMeterPart } from "./meter.context";
4
+
5
+ export type MeterFillProps = ProgressFillProps;
6
+
7
+ /**
8
+ * The painted bar, in the colour the reading was judged to be.
9
+ *
10
+ * The progress bar's own fill: a transform on the UI thread, one timing per new
11
+ * reading, landing on the value without travel under the system's reduce-motion
12
+ * setting. A change of colour is a class, so it lands on the render that judged
13
+ * it.
14
+ */
15
+ export function MeterFill(props: MeterFillProps): ReactElement {
16
+ useMeterPart("Meter.Fill");
17
+ return <Progress.Fill {...props} />;
18
+ }
19
+ MeterFill.displayName = "DelacourUI.Meter.Fill";
@@ -0,0 +1,17 @@
1
+ import type { ReactElement } from "react";
2
+ import { Progress, type ProgressHeaderProps } from "../progress";
3
+ import { useMeterPart } from "./meter.context";
4
+
5
+ export type MeterHeaderProps = ProgressHeaderProps;
6
+
7
+ /**
8
+ * The row above the track: a label at the start, a readout at the end.
9
+ *
10
+ * The progress bar's own header — the meter publishes that bar's context, so the
11
+ * row, its gap and its type step are shared rather than restated.
12
+ */
13
+ export function MeterHeader(props: MeterHeaderProps): ReactElement {
14
+ useMeterPart("Meter.Header");
15
+ return <Progress.Header {...props} />;
16
+ }
17
+ MeterHeader.displayName = "DelacourUI.Meter.Header";
@@ -0,0 +1,18 @@
1
+ import type { ReactElement } from "react";
2
+ import { Progress, type ProgressLabelProps } from "../progress";
3
+ import { useMeterPart } from "./meter.context";
4
+
5
+ export type MeterLabelProps = ProgressLabelProps;
6
+
7
+ /**
8
+ * What is being measured — "Storage", "Battery".
9
+ *
10
+ * Left visible to assistive technology: the root is one accessible element, and
11
+ * a label inside it is what VoiceOver and TalkBack read as that element's name.
12
+ * A meter without one announces a number with nothing attached to it.
13
+ */
14
+ export function MeterLabel(props: MeterLabelProps): ReactElement {
15
+ useMeterPart("Meter.Label");
16
+ return <Progress.Label {...props} />;
17
+ }
18
+ MeterLabel.displayName = "DelacourUI.Meter.Label";
@@ -0,0 +1,42 @@
1
+ import type { ReactElement } from "react";
2
+ import { PROGRESS_OUTPUT_TEXT_SIZE, progressVariants } from "../progress/progress.variants";
3
+ import { Text, type TextPresetProps } from "../text";
4
+ import { useMeterPart } from "./meter.context";
5
+ import type { MeterRenderChildren } from "./meter.types";
6
+
7
+ export type MeterOutputProps = Omit<TextPresetProps, "children"> & {
8
+ /** Custom content, or a function called with the meter's settled state. */
9
+ children?: MeterRenderChildren;
10
+ };
11
+
12
+ /**
13
+ * The reading, formatted — `68%` by default, or `valueLabel` when one is given.
14
+ *
15
+ * A function child is handed the meter's state, including the `region` it was
16
+ * judged to be in. Words it draws are seen but not heard — see below — so a
17
+ * judgement a screen reader must hear belongs in a `valueLabel` function.
18
+ *
19
+ * **Hidden from assistive technology on purpose.** The root already publishes
20
+ * the reading as `accessibilityValue`, so a readout left visible inside it would
21
+ * be read twice.
22
+ */
23
+ export function MeterOutput({ children, className, size, ...props }: MeterOutputProps): ReactElement | null {
24
+ const { size: meterSize, renderProps } = useMeterPart("Meter.Output");
25
+
26
+ const content = typeof children === "function" ? children(renderProps) : (children ?? renderProps.formatted);
27
+
28
+ if (content === null || content === undefined) return null;
29
+
30
+ return (
31
+ <Text.Label
32
+ accessibilityElementsHidden
33
+ className={progressVariants({ size: meterSize }).output({ className })}
34
+ importantForAccessibility="no-hide-descendants"
35
+ size={size ?? PROGRESS_OUTPUT_TEXT_SIZE[meterSize]}
36
+ {...props}
37
+ >
38
+ {content}
39
+ </Text.Label>
40
+ );
41
+ }
42
+ MeterOutput.displayName = "DelacourUI.Meter.Output";