@delacour/react-native-ui 0.1.0-alpha.3 → 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 (482) hide show
  1. package/README.md +11 -7
  2. package/package.json +49 -5
  3. package/src/components/accordion/AGENTS.md +21 -9
  4. package/src/components/accordion/accordion-content.tsx +7 -4
  5. package/src/components/accordion/accordion-item.tsx +15 -13
  6. package/src/components/accordion/accordion.context.tsx +11 -5
  7. package/src/components/accordion/accordion.variants.test.ts +64 -3
  8. package/src/components/accordion/accordion.variants.ts +33 -9
  9. package/src/components/alert/AGENTS.md +102 -0
  10. package/src/components/alert/alert-action.tsx +17 -0
  11. package/src/components/alert/alert-close-button.tsx +50 -0
  12. package/src/components/alert/alert-content.tsx +15 -0
  13. package/src/components/alert/alert-description.tsx +16 -0
  14. package/src/components/alert/alert-glyphs.ts +25 -0
  15. package/src/components/alert/alert-indicator.tsx +40 -0
  16. package/src/components/alert/alert-title.tsx +18 -0
  17. package/src/components/alert/alert.context.tsx +69 -0
  18. package/src/components/alert/alert.tsx +160 -0
  19. package/src/components/alert/alert.types.ts +10 -0
  20. package/src/components/alert/alert.variants.test.ts +218 -0
  21. package/src/components/alert/alert.variants.ts +180 -0
  22. package/src/components/alert/index.ts +23 -0
  23. package/src/components/avatar/AGENTS.md +106 -0
  24. package/src/components/avatar/avatar-badge.tsx +51 -0
  25. package/src/components/avatar/avatar-group.tsx +139 -0
  26. package/src/components/avatar/avatar.context.tsx +93 -0
  27. package/src/components/avatar/avatar.tsx +201 -0
  28. package/src/components/avatar/avatar.utils.test.ts +174 -0
  29. package/src/components/avatar/avatar.utils.ts +141 -0
  30. package/src/components/avatar/avatar.variants.test.ts +251 -0
  31. package/src/components/avatar/avatar.variants.ts +270 -0
  32. package/src/components/avatar/index.ts +39 -0
  33. package/src/components/badge/AGENTS.md +5 -0
  34. package/src/components/badge/badge.tsx +6 -1
  35. package/src/components/bottom-sheet/AGENTS.md +186 -221
  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 -207
  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 -120
  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 -52
  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 -70
  55. package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -105
  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 +9 -1
  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/screen/screen-navbar-back-button.tsx +1 -1
  344. package/src/components/selection-mode/AGENTS.md +155 -0
  345. package/src/components/selection-mode/index.ts +43 -0
  346. package/src/components/selection-mode/selection-mode-action.tsx +65 -0
  347. package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
  348. package/src/components/selection-mode/selection-mode-group.tsx +123 -0
  349. package/src/components/selection-mode/selection-mode-header.tsx +85 -0
  350. package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
  351. package/src/components/selection-mode/selection-mode-item.tsx +182 -0
  352. package/src/components/selection-mode/selection-mode.context.tsx +89 -0
  353. package/src/components/selection-mode/selection-mode.tsx +207 -0
  354. package/src/components/selection-mode/selection-mode.types.ts +36 -0
  355. package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
  356. package/src/components/selection-mode/selection-mode.variants.ts +329 -0
  357. package/src/components/skeleton/AGENTS.md +120 -0
  358. package/src/components/skeleton/index.ts +15 -0
  359. package/src/components/skeleton/skeleton-clock.ts +48 -0
  360. package/src/components/skeleton/skeleton-group.tsx +64 -0
  361. package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
  362. package/src/components/skeleton/skeleton.context.tsx +45 -0
  363. package/src/components/skeleton/skeleton.tsx +227 -0
  364. package/src/components/skeleton/skeleton.variants.test.ts +245 -0
  365. package/src/components/skeleton/skeleton.variants.ts +222 -0
  366. package/src/components/slide-button/AGENTS.md +122 -0
  367. package/src/components/slide-button/index.ts +43 -0
  368. package/src/components/slide-button/slide-button-label.tsx +70 -0
  369. package/src/components/slide-button/slide-button-thumb.tsx +93 -0
  370. package/src/components/slide-button/slide-button.context.tsx +76 -0
  371. package/src/components/slide-button/slide-button.tsx +457 -0
  372. package/src/components/slide-button/slide-button.variants.test.ts +288 -0
  373. package/src/components/slide-button/slide-button.variants.ts +324 -0
  374. package/src/components/slider/index.ts +2 -0
  375. package/src/components/slider/slider-thumb.tsx +33 -1
  376. package/src/components/slider/slider.variants.test.ts +52 -0
  377. package/src/components/slider/slider.variants.ts +56 -2
  378. package/src/components/stack-card/AGENTS.md +169 -0
  379. package/src/components/stack-card/index.ts +37 -0
  380. package/src/components/stack-card/stack-card-action.tsx +66 -0
  381. package/src/components/stack-card/stack-card-actions.tsx +18 -0
  382. package/src/components/stack-card/stack-card-card.tsx +71 -0
  383. package/src/components/stack-card/stack-card-empty.tsx +30 -0
  384. package/src/components/stack-card/stack-card-slot.tsx +90 -0
  385. package/src/components/stack-card/stack-card-stamp.tsx +76 -0
  386. package/src/components/stack-card/stack-card.context.tsx +117 -0
  387. package/src/components/stack-card/stack-card.tsx +557 -0
  388. package/src/components/stack-card/stack-card.types.ts +26 -0
  389. package/src/components/stack-card/stack-card.variants.test.ts +355 -0
  390. package/src/components/stack-card/stack-card.variants.ts +360 -0
  391. package/src/components/steps/AGENTS.md +153 -0
  392. package/src/components/steps/index.ts +45 -0
  393. package/src/components/steps/steps-connector.tsx +31 -0
  394. package/src/components/steps/steps-description.tsx +26 -0
  395. package/src/components/steps/steps-indicator.tsx +106 -0
  396. package/src/components/steps/steps-item.tsx +225 -0
  397. package/src/components/steps/steps-panel.tsx +41 -0
  398. package/src/components/steps/steps-title.tsx +32 -0
  399. package/src/components/steps/steps-track.tsx +59 -0
  400. package/src/components/steps/steps.context.tsx +123 -0
  401. package/src/components/steps/steps.tsx +166 -0
  402. package/src/components/steps/steps.variants.test.ts +388 -0
  403. package/src/components/steps/steps.variants.ts +468 -0
  404. package/src/components/surface/AGENTS.md +72 -0
  405. package/src/components/surface/index.ts +15 -0
  406. package/src/components/surface/surface.context.tsx +54 -0
  407. package/src/components/surface/surface.tsx +70 -0
  408. package/src/components/surface/surface.variants.test.ts +180 -0
  409. package/src/components/surface/surface.variants.ts +133 -0
  410. package/src/components/swipe/AGENTS.md +140 -0
  411. package/src/components/swipe/index.ts +31 -0
  412. package/src/components/swipe/swipe-action.tsx +99 -0
  413. package/src/components/swipe/swipe-end.tsx +14 -0
  414. package/src/components/swipe/swipe-group.tsx +55 -0
  415. package/src/components/swipe/swipe-panel.tsx +55 -0
  416. package/src/components/swipe/swipe-start.tsx +15 -0
  417. package/src/components/swipe/swipe.context.tsx +135 -0
  418. package/src/components/swipe/swipe.tsx +393 -0
  419. package/src/components/swipe/swipe.types.ts +7 -0
  420. package/src/components/swipe/swipe.variants.test.ts +337 -0
  421. package/src/components/swipe/swipe.variants.ts +290 -0
  422. package/src/components/switch/switch.variants.ts +2 -2
  423. package/src/components/tabs/AGENTS.md +4 -3
  424. package/src/components/tabs/tabs.variants.test.ts +1 -1
  425. package/src/components/tabs/tabs.variants.ts +4 -6
  426. package/src/components/textarea/AGENTS.md +86 -0
  427. package/src/components/textarea/index.ts +16 -0
  428. package/src/components/textarea/textarea.tsx +157 -0
  429. package/src/components/textarea/textarea.variants.test.ts +219 -0
  430. package/src/components/textarea/textarea.variants.ts +171 -0
  431. package/src/components/toast/AGENTS.md +140 -0
  432. package/src/components/toast/index.ts +74 -0
  433. package/src/components/toast/toast-action.tsx +59 -0
  434. package/src/components/toast/toast-api.ts +32 -0
  435. package/src/components/toast/toast-close.tsx +44 -0
  436. package/src/components/toast/toast-content.tsx +11 -0
  437. package/src/components/toast/toast-description.tsx +11 -0
  438. package/src/components/toast/toast-indicator.tsx +34 -0
  439. package/src/components/toast/toast-item.tsx +308 -0
  440. package/src/components/toast/toast-title.tsx +13 -0
  441. package/src/components/toast/toast-viewport.tsx +135 -0
  442. package/src/components/toast/toast.context.tsx +69 -0
  443. package/src/components/toast/toast.store.test.ts +259 -0
  444. package/src/components/toast/toast.store.ts +338 -0
  445. package/src/components/toast/toast.timer.test.ts +64 -0
  446. package/src/components/toast/toast.timer.ts +51 -0
  447. package/src/components/toast/toast.tsx +132 -0
  448. package/src/components/toast/toast.variants.test.ts +393 -0
  449. package/src/components/toast/toast.variants.ts +304 -0
  450. package/src/components/toggle-button/AGENTS.md +106 -0
  451. package/src/components/toggle-button/index.ts +30 -0
  452. package/src/components/toggle-button/toggle-button-group.tsx +156 -0
  453. package/src/components/toggle-button/toggle-button-label.tsx +19 -0
  454. package/src/components/toggle-button/toggle-button.context.tsx +130 -0
  455. package/src/components/toggle-button/toggle-button.tsx +172 -0
  456. package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
  457. package/src/components/toggle-button/toggle-button.variants.ts +160 -0
  458. package/src/components/tooltip/AGENTS.md +133 -0
  459. package/src/components/tooltip/index.ts +28 -0
  460. package/src/components/tooltip/tooltip-arrow.tsx +40 -0
  461. package/src/components/tooltip/tooltip-content.tsx +179 -0
  462. package/src/components/tooltip/tooltip-description.tsx +19 -0
  463. package/src/components/tooltip/tooltip-text.tsx +21 -0
  464. package/src/components/tooltip/tooltip-title.tsx +20 -0
  465. package/src/components/tooltip/tooltip-trigger.tsx +103 -0
  466. package/src/components/tooltip/tooltip.context.tsx +68 -0
  467. package/src/components/tooltip/tooltip.tsx +213 -0
  468. package/src/components/tooltip/tooltip.variants.test.ts +231 -0
  469. package/src/components/tooltip/tooltip.variants.ts +177 -0
  470. package/src/hooks/use-calm-motion.tsx +31 -0
  471. package/src/lib/calm-motion.test.ts +17 -0
  472. package/src/lib/calm-motion.ts +23 -0
  473. package/src/lib/cn.test.ts +14 -0
  474. package/src/lib/cn.ts +2 -2
  475. package/src/no-gorhom.test.ts +54 -0
  476. package/src/styles/geometry.test.ts +1 -0
  477. package/src/styles/theme.css +4 -0
  478. package/src/styles/tokens.css +11 -0
  479. package/src/styles/tokens.test.ts +22 -0
  480. package/src/styles/tokens.ts +20 -0
  481. package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
  482. package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
@@ -1,9 +1,9 @@
1
- import { BottomSheetModalProvider } from "@gorhom/bottom-sheet";
2
1
  import type { ReactElement, ReactNode } from "react";
3
2
  import type { StyleProp, ViewStyle } from "react-native";
4
3
  import { GestureHandlerRootView } from "react-native-gesture-handler";
5
4
  import { KeyboardProvider } from "react-native-keyboard-controller";
6
5
  import { initialWindowMetrics, type Metrics, SafeAreaProvider } from "react-native-safe-area-context";
6
+ import { CalmMotionProvider } from "../../hooks/use-calm-motion";
7
7
  import { KeyboardStateSync } from "../../hooks/use-keyboard-state-sync";
8
8
 
9
9
  export type DelacourProviderProps = {
@@ -21,6 +21,16 @@ export type DelacourProviderProps = {
21
21
  * `null` is a value here rather than an absence.
22
22
  */
23
23
  initialMetrics?: Metrics | null;
24
+ /**
25
+ * Hold every decorative loop still — a skeleton's shimmer or pulse — whatever
26
+ * the OS reduce-motion setting says. Defaults to `false`.
27
+ *
28
+ * For an E2E build. A test runner that waits for the screen to stop moving
29
+ * before each gesture (Argent, Detox, Maestro) pays its whole timeout on any
30
+ * screen holding a loop that never ends. Motion that *is* the behaviour — a
31
+ * `Spinner`, an indeterminate `Progress` — keeps moving either way.
32
+ */
33
+ isMotionCalm?: boolean;
24
34
  /**
25
35
  * Style for the outermost `GestureHandlerRootView`.
26
36
  *
@@ -52,15 +62,23 @@ export type DelacourProviderProps = {
52
62
  * `will` events, so a keyboard that vanishes without one — an interactive
53
63
  * dismiss interrupted by navigation, a stack pop, an app suspend — leaves
54
64
  * every screen in the app believing it is still open.
55
- * 5. `BottomSheetModalProvider` — the host every `BottomSheet` portals into. It
56
- * goes innermost because it draws ABOVE the app and reads every layer over
57
- * it: the gesture root for the pan, the safe area for its insets, and the
58
- * keyboard values a sheet's footer rides. It wraps `{children}` and nothing
59
- * else moves, which is what a new layer here always looks like.
65
+ * 5. `CalmMotionProvider` — the app's `isMotionCalm`, for `useCalmMotion()`.
66
+ * Pure context, no native view, so it wraps `{children}` alone — innermost,
67
+ * where a new layer goes.
60
68
  *
61
- * `KeyboardStateSync` stays a SIBLING of it rather than a child. The repair is
62
- * global and has to run for the whole app's lifetime; inside a layer that can
63
- * remount it would be torn down with it.
69
+ * **`BottomSheetProvider` is not here, and the app mounts it.** The sheet's
70
+ * engine, `@delacour/react-native-bottom-sheet`, is an optional peer of this
71
+ * library: importing it from the recommended root would make every app resolve
72
+ * it, sheet or no sheet. So the provider is exported from
73
+ * `@delacour/react-native-ui/bottom-sheet` and goes innermost, inside this and
74
+ * around the navigator — it reads the gesture root for its pans, the safe area
75
+ * for its insets and the keyboard values a sheet's footer rides, and it draws
76
+ * above the app, so it wraps `{children}` and nothing else moves. That is what
77
+ * a new layer here always looks like, one package along.
78
+ *
79
+ * `KeyboardStateSync` is a sibling of `{children}` rather than a wrapper. The
80
+ * repair is global and has to run for the whole app's lifetime; inside a layer
81
+ * that can remount it would be torn down with it.
64
82
  *
65
83
  * There are no per-layer escape hatches and no layer-named props on purpose.
66
84
  * An app that needs a different stack composes the providers by hand; they are
@@ -69,13 +87,16 @@ export type DelacourProviderProps = {
69
87
  * @example
70
88
  * // expo-router root layout. The css import must stay the first statement.
71
89
  * import "../styles/global.css";
90
+ * import { BottomSheetProvider } from "@delacour/react-native-ui/bottom-sheet";
72
91
  * import { DelacourProvider } from "@delacour/react-native-ui/provider";
73
92
  * import { Stack } from "expo-router";
74
93
  *
75
94
  * export default function RootLayout() {
76
95
  * return (
77
96
  * <DelacourProvider>
78
- * <Stack screenOptions={{ headerShown: false }} />
97
+ * <BottomSheetProvider>
98
+ * <Stack screenOptions={{ headerShown: false }} />
99
+ * </BottomSheetProvider>
79
100
  * </DelacourProvider>
80
101
  * );
81
102
  * }
@@ -85,10 +106,16 @@ export type DelacourProviderProps = {
85
106
  * // app that launches into a rotated or split-screen window and cannot
86
107
  * // tolerate one stale frame.
87
108
  * <DelacourProvider initialMetrics={null}>{children}</DelacourProvider>
109
+ *
110
+ * @example
111
+ * // An E2E build: decorative loops hold still so the test runner never waits
112
+ * // out a shimmer. `IS_E2E` is the app's own build flag.
113
+ * <DelacourProvider isMotionCalm={IS_E2E}>{children}</DelacourProvider>
88
114
  */
89
115
  export function DelacourProvider({
90
116
  children,
91
117
  initialMetrics = initialWindowMetrics,
118
+ isMotionCalm = false,
92
119
  style,
93
120
  }: DelacourProviderProps): ReactElement {
94
121
  return (
@@ -96,7 +123,7 @@ export function DelacourProvider({
96
123
  <SafeAreaProvider initialMetrics={initialMetrics}>
97
124
  <KeyboardProvider>
98
125
  <KeyboardStateSync />
99
- <BottomSheetModalProvider>{children}</BottomSheetModalProvider>
126
+ <CalmMotionProvider isMotionCalm={isMotionCalm}>{children}</CalmMotionProvider>
100
127
  </KeyboardProvider>
101
128
  </SafeAreaProvider>
102
129
  </GestureHandlerRootView>
@@ -78,16 +78,14 @@ sit in `radio.variants.ts` beside the pure resolver that reads them. See
78
78
  A test asserts the `dot` slot claims neither `opacity-*` nor `scale-*` in any
79
79
  cell: a class fighting a `useAnimatedStyle` for the same property is a dot that
80
80
  never appears, with no error anywhere.
81
- - **The disabled fade lands on the ring and the label, never on the root.** The
82
- `root` slot is worn by `Pressable`'s own `Animated.View`, whose
83
- `useAnimatedStyle` writes `opacity` on every frame — at rest, 1. An
84
- `opacity-50` class on that node is overwritten before it is ever drawn,
85
- silently, so the row would stay at full contrast while behaving as disabled.
86
- The ring and the label are ordinary descendants, so their opacity multiplies
87
- with the root's instead of fighting it. The group must not fade either, or a
88
- disabled group would compound `opacity-50` with each of its rows and land at a
89
- quarter opacity. Tests pin both halves. This is the failure `Switch`, `Tabs`
90
- and `Accordion` all cite back here.
81
+ - **The disabled fade lands on the ring and the label, never on the root.** It
82
+ was placed there because [`Pressable`](../pressable/AGENTS.md)'s animated
83
+ style used to overwrite an `opacity-50` on the root; `Pressable` now
84
+ multiplies a className's opacity into its press, so the root *could* fade,
85
+ but the fade stays on the parts: an `opacity-50` on the root as well would
86
+ compound with them and land at a quarter. The group must not fade either, for
87
+ the same reason. Tests pin both halves. `Switch`, `Tabs` and `Accordion` all
88
+ cite back here.
91
89
  - **The ring is drawn from `View`s rather than a Central Icon.** Rule 5 governs
92
90
  icons and [Spinner](../spinner/AGENTS.md)'s arc is the precedent for
93
91
  primitives; here the set has no ring-with-a-centred-dot glyph at all. Two
@@ -192,10 +192,9 @@ describe("the root slot", () => {
192
192
  expect([...gaps]).toEqual([...gaps].sort((a, b) => a - b));
193
193
  });
194
194
 
195
- // `root` is worn by Pressable's own Animated.View, whose useAnimatedStyle
196
- // writes `opacity` every frame. A class here is overwritten before it is
197
- // drawn — silently — so the fade has to live on the children instead.
198
- test("never carries an opacity, which Pressable's animated style would overwrite", () => {
195
+ // The fade lives on the ring and the label; an `opacity-50` on the root as
196
+ // well would compound with them and land at a quarter.
197
+ test("never carries an opacity, which would compound with its children's", () => {
199
198
  for (const cell of CELLS) {
200
199
  expect(radioVariants(cell).root()).not.toMatch(/\bopacity-/);
201
200
  }
@@ -397,8 +396,8 @@ describe("a trailing indicator", () => {
397
396
  });
398
397
 
399
398
  describe("a disabled radio", () => {
400
- // The ring and the label are ordinary descendants of the pressable, so their
401
- // opacity multiplies with the root's rather than being overwritten by it.
399
+ // The ring and the label carry the fade; the root stays clear so the two
400
+ // never compound.
402
401
  test("fades its ring and its label", () => {
403
402
  expect(radioVariants({ isDisabled: true }).indicator()).toContain("opacity-50");
404
403
  expect(cls(radioVariants({ isDisabled: true }).label())).toContain("opacity-50");
@@ -119,13 +119,12 @@ export const radioVariants = tv({
119
119
  // caller's `justify-between` because the root already knows where the
120
120
  // indicator landed — see {@link resolveIndicatorPlacement}.
121
121
  isIndicatorTrailing: { true: { root: "justify-between" }, false: {} },
122
- // The fade lands on the children, never on the `root`, and that is not a
123
- // style choice. `root` is worn by `Pressable`'s own `Animated.View`, whose
124
- // `useAnimatedStyle` writes `opacity` on every frame — at rest, 1. An
125
- // `opacity-50` class on that node is overwritten before it is ever drawn,
126
- // silently, so the row stays at full contrast while behaving as disabled.
127
- // The ring and the label are ordinary descendants, so their opacity
128
- // multiplies with the root's instead of fighting it.
122
+ // The fade lands on the children, never on the `root`. `root` is worn by
123
+ // `Pressable`'s own `Animated.View`, which now multiplies a className's
124
+ // opacity into its press — but the ring and the label fading on their own
125
+ // keeps the row's hit area at full contrast and lets each part be tuned,
126
+ // so it stays here. Putting `opacity-50` on the root as well would
127
+ // compound with these and land at a quarter.
129
128
  //
130
129
  // The group must not fade either, or a disabled group would compound
131
130
  // `opacity-50` with each of its rows and land at a quarter opacity.
@@ -0,0 +1,143 @@
1
+ # Rating
2
+
3
+ A row of stars that reads or sets a score. Compound root plus `Rating.Stars` and
4
+ `Rating.Output`. Read-only or interactive, in whole or fractional steps, set by a
5
+ tap or a drag along the row.
6
+
7
+ `import { Rating } from "@delacour/react-native-ui/rating";`
8
+
9
+ ## Files
10
+
11
+ | File | What it holds |
12
+ | --- | --- |
13
+ | `index.ts` | → `@delacour/react-native-ui/rating` |
14
+ | `rating.tsx` | Root + the `Object.assign` compound surface |
15
+ | `rating-stars.tsx` | `Rating.Stars` — owns the pan, the measurement, the haptic and the accessibility surface |
16
+ | `rating-star.tsx` | One star: an empty glyph and a clipped filled one. Internal |
17
+ | `rating-output.tsx` | `Rating.Output`, the `Text.Label` readout |
18
+ | `rating.context.tsx` | `RatingContext`, `useRating()`, `useRatingContext()`, `useRatingPart()`, `RatingRenderProps` |
19
+ | `rating.variants.ts` | Pure `tv()` slots, the value maths and the paint resolver, no RN imports |
20
+ | `rating.variants.test.ts` | |
21
+
22
+ There is no `rating.types.ts`: `RatingRenderProps` is the one shared type and it
23
+ is part of the context's own shape, so it lives beside it.
24
+
25
+ ## Design
26
+
27
+ - **Colours**: `default`, `primary`, `success`, `warning`, `destructive`, `info` —
28
+ Slider's set. The default is **`warning`**, not `default`: a star is read as
29
+ amber before it is read as a star, and a row of ink stars reads as a set of
30
+ icons rather than a score. **Sizes**: `sm`, `md`, `lg`, which are `icon-lg`,
31
+ `icon-xl` and `icon-2xl` on the icon scale — a star is a mark, the way a
32
+ checkbox's tick is.
33
+ - **The anatomy is written out**, `Rating.Stars` and an optional
34
+ `Rating.Output`, rather than a `showValue` boolean. A readout beside the stars,
35
+ under them, or formatted as "4.3 · 128 reviews" is three layouts one boolean
36
+ could not express; the root is a column and a `flex-row` class puts it inline.
37
+ - **`maxValue` is the star count and `step` the precision**, the names `Slider`
38
+ uses for the same two ideas. A fraction of a count rounds *down*; anything
39
+ that is not a count falls back to five. A step must divide one star — `0.5`
40
+ and `0.25` do, `0.3` does not — and falls back to whole stars otherwise,
41
+ because a rating is usually fed a number from a server rather than a literal.
42
+
43
+ ## Value
44
+
45
+ - **Any value is drawn; only a touch is snapped.** `clampRating` holds the value
46
+ inside zero and the count and does not snap, so a read-only average of 3.7
47
+ fills seven tenths of the fourth star. `step` constrains what a finger can set.
48
+ A non-finite value draws as zero — a star whose width came out of `NaN` is a
49
+ row that lies about its score.
50
+ - **A touch rounds up to the next stop.** Any touch inside a star's cell takes
51
+ that star, and with half stars its left half is the half. A touch exactly on a
52
+ cell's edge belongs to the star before it; the epsilon in `ratingFromOffset` is
53
+ what stops `Math.ceil` and binary floating point disagreeing there.
54
+ - **A drag past the first star holds the lowest stop; it does not clear.** A drag
55
+ that overshoots the start is someone rating one star, not someone withdrawing
56
+ a rating. Clearing is its own gesture.
57
+ - **`allowClear` is a tap on the value already held**, decided in `onFinalize` by
58
+ `shouldClearRating`. It reads the travel since touch-down, so a drag that
59
+ wanders off and comes back to the same star is a drag rather than a clear, and
60
+ `RATING_TAP_SLOP` is the line between them.
61
+ - **The fills sum back to the value.** `starFillOf` gives every star before the
62
+ value a full fill, every star after it none and the one it lands in the
63
+ remainder, and a test sums them.
64
+
65
+ ## Drawing
66
+
67
+ - **The star is an SVG path drawn here, not an icon.** Central Icons is an
68
+ outlined set: a filled star, and the clipped half of one, has no glyph there.
69
+ The path is ten vertices, stroked in its own fill colour with round joins, which
70
+ softens the points to sit beside the rounded icon set.
71
+ - **A partial star is two layers and a clip, not a gradient.** The empty glyph
72
+ sits underneath and the filled one is inside a `View` whose width is the fill as
73
+ a percentage, with `overflow-hidden`. The filled glyph keeps the full glyph's
74
+ size — the window narrows, never the star. A hard-stop gradient would draw the
75
+ same thing, but its stops are paint props re-parsed on every change.
76
+ - **The glyph's size is a class on a `View`, and the `Svg` fills it at `100%`.**
77
+ That keeps the icon scale the one source of the size without wrapping `Svg` in
78
+ `withUniwind`, which rule 7 forbids.
79
+ - **The colour is a token read with `useThemeColor`, not a class.** A path's
80
+ `fill` is a paint prop no `className` reaches. `RATING_FILL_TOKEN` names the
81
+ token per colour and `resolveRatingPaint` settles it, so the whole paint matrix
82
+ is reachable from `bun test`, which also checks every token it names exists in
83
+ both variants of `theme.css`.
84
+ - **An empty star is `muted-foreground` at 40% — the same chrome at every colour.**
85
+ No token sits where an empty star needs to: `border` and `input` vanish against
86
+ a light card, and `muted-foreground` at full strength reads as a disabled
87
+ filled star. The opacity is on the whole `Svg` view, not on the path's
88
+ `fillOpacity` and `strokeOpacity` — half the stroke overlaps the fill, and two
89
+ translucent paints draw a darker ring round every empty star.
90
+ - **Invalid outranks the colour on the empty stars too.** A required rating left
91
+ at zero has no filled star to turn red, so the outlines carry the signal or
92
+ there is none.
93
+
94
+ ## Gesture
95
+
96
+ - **Neither the row nor a star is a `Pressable`**, for the reasons
97
+ [Slider](../slider/AGENTS.md) gives for its track: a tap-to-set would fire
98
+ `onPress` every time, and a `Pressable` per star would nest five taps inside the
99
+ row's pan. One `Gesture.Pan()` on the row reads the star under the finger. What
100
+ is inherited is `playHaptic`, the one haptic switch.
101
+ - **The value is written in `onBegin`**, because a pan activates on the first
102
+ movement and a stationary tap would never reach `onUpdate`. `minDistance(0)`
103
+ wins the touch from an enclosing scroll view and `shouldCancelWhenOutside(false)`
104
+ keeps a drag past the last star tracking — both exactly as `Slider.Track`.
105
+ - **Each star's cell carries the gap as padding**, so every cell is the same width
106
+ and a star's half is the half of its cell. A `gap-*` on the row would leave dead
107
+ space between cells that belongs to no star.
108
+ - **The row is padded on the cross axis only**, to 44pt at every size, and a test
109
+ asserts the sum. The main axis is not padded: the value is read straight off
110
+ the touch's offset along the row, and a gutter there would shift every value.
111
+ - **The stars render from React state; the gesture reads a shared value.** Unlike
112
+ a slider, a rating crosses a handful of stops in a whole drag, so a commit per
113
+ crossing is a handful of commits and there is no UI-thread mirror to pay for.
114
+ The shared value exists so the worklet can tell a crossing from a repeat
115
+ without waiting on a render. It is re-synced from React on every gesture's end,
116
+ through a token that exists only to re-run the effect — without it a controlled
117
+ parent that rejects a value leaves the shared value holding it.
118
+ - **The haptic ticks on grab, on every star crossed and on a clear.** There is no
119
+ distance rate limit as there is on `Slider`: the stops are a star apart, so a
120
+ flick across ten of them is ten ticks a hand can feel.
121
+
122
+ ## Accessibility
123
+
124
+ - **The row is one `adjustable` control, not five buttons.** Its value is spoken
125
+ as "3.5 out of 5" and a swipe up or down calls `stepRating`, which lands a value
126
+ between stops on the *next* stop in the direction asked rather than a whole
127
+ step past it. Five buttons would make a screen-reader user visit every star to
128
+ learn one number.
129
+ - **A read-only rating is an `image` with the same value** — something to read,
130
+ not something to set, and no actions.
131
+ - **`Rating.Output` is hidden from assistive technology**, because the row already
132
+ speaks the value and a second, terser reading one swipe later is noise.
133
+ - **Pass `accessibilityLabel` to `Rating.Stars`**, not the root. It defaults to
134
+ "Rating", which is correct and says nothing about *what* is being rated.
135
+
136
+ ## Axes
137
+
138
+ - **The ladder is two rungs**, the rating's own props then an enclosing
139
+ [Field](../field/AGENTS.md), as on `Slider`. A `Field` reaches the two *state*
140
+ axes only, and a test pins that it cannot acquire a paint axis.
141
+ - **`isReadOnly` is the rating's own and does not fade.** A score on a review card
142
+ is content, not a control that happens to be off, so it takes no touch and no
143
+ assistive actions but keeps full strength. `isDisabled` is the one that fades.
@@ -0,0 +1,43 @@
1
+ export { Rating, type RatingProps } from "./rating";
2
+ export {
3
+ type RatingContextValue,
4
+ RatingProvider,
5
+ type RatingRenderProps,
6
+ useRating,
7
+ useRatingContext,
8
+ } from "./rating.context";
9
+ export {
10
+ clampRating,
11
+ describeRating,
12
+ formatRatingValue,
13
+ normalizeRatingCount,
14
+ normalizeRatingStep,
15
+ RATING_COLORS,
16
+ RATING_COUNT,
17
+ RATING_DEFAULT_COLOR,
18
+ RATING_DEFAULT_SIZE,
19
+ RATING_EMPTY_OPACITY,
20
+ RATING_FILL_TOKEN,
21
+ RATING_OUTPUT_TEXT_SIZE,
22
+ RATING_SIZES,
23
+ RATING_STAR_PATH,
24
+ RATING_STAR_STROKE,
25
+ RATING_STEP,
26
+ RATING_TAP_SLOP,
27
+ type RatingAxes,
28
+ type RatingColor,
29
+ type RatingFieldAxes,
30
+ type RatingOwnAxes,
31
+ type RatingPaint,
32
+ type RatingSize,
33
+ type RatingVariantProps,
34
+ ratingFromOffset,
35
+ ratingVariants,
36
+ resolveRatingAxes,
37
+ resolveRatingPaint,
38
+ shouldClearRating,
39
+ starFillOf,
40
+ stepRating,
41
+ } from "./rating.variants";
42
+ export type { RatingOutputProps } from "./rating-output";
43
+ export type { RatingStarsProps } from "./rating-stars";
@@ -0,0 +1,39 @@
1
+ import type { ReactElement, ReactNode } from "react";
2
+ import { Text, type TextPresetProps } from "../text";
3
+ import { type RatingRenderProps, useRatingPart } from "./rating.context";
4
+ import { formatRatingValue, RATING_OUTPUT_TEXT_SIZE, ratingVariants } from "./rating.variants";
5
+
6
+ export type RatingOutputProps = Omit<TextPresetProps, "children"> & {
7
+ /** Custom content, or a function called with the rating's settled state. */
8
+ children?: ReactNode | ((props: RatingRenderProps) => ReactNode);
9
+ };
10
+
11
+ /**
12
+ * The value, formatted — `4`, or `3.5`.
13
+ *
14
+ * Renders `Text.Label` and names a size step, never a scale of its own, the rule
15
+ * `Slider.Output` follows: the preset owns the weight and the colour, and the
16
+ * readout still tracks the rating's size.
17
+ *
18
+ * It is hidden from assistive technology. `Rating.Stars` already speaks the
19
+ * value as "3.5 out of 5", and a second, terser reading of the same number one
20
+ * swipe later is noise.
21
+ */
22
+ export function RatingOutput({ children, className, size, ...props }: RatingOutputProps): ReactElement {
23
+ const context = useRatingPart("Rating.Output");
24
+ const content =
25
+ typeof children === "function" ? children(context.renderProps) : (children ?? formatRatingValue(context.value));
26
+
27
+ return (
28
+ <Text.Label
29
+ accessibilityElementsHidden
30
+ className={ratingVariants({ size: context.size }).output({ className })}
31
+ importantForAccessibility="no-hide-descendants"
32
+ size={size ?? RATING_OUTPUT_TEXT_SIZE[context.size]}
33
+ {...props}
34
+ >
35
+ {content}
36
+ </Text.Label>
37
+ );
38
+ }
39
+ RatingOutput.displayName = "DelacourUI.Rating.Output";
@@ -0,0 +1,70 @@
1
+ import type { ReactElement } from "react";
2
+ import { View } from "react-native";
3
+ import Svg, { Path } from "react-native-svg";
4
+ import { RATING_EMPTY_OPACITY, RATING_STAR_PATH, RATING_STAR_STROKE } from "./rating.variants";
5
+
6
+ export type RatingStarProps = {
7
+ /** How much of the star is filled, 0–1. */
8
+ fill: number;
9
+ /** The resolved colour of the filled part. */
10
+ fillColor: string | undefined;
11
+ /** The resolved colour of the empty star, drawn at {@link RATING_EMPTY_OPACITY}. */
12
+ emptyColor: string | undefined;
13
+ /** The `size-icon-*` box the glyph fills. */
14
+ glyphClassName: string;
15
+ /** The clip window's class. Its width is set here, from `fill`. */
16
+ clipClassName: string;
17
+ };
18
+
19
+ /**
20
+ * One star: an empty glyph, and a filled one clipped to `fill` of its width.
21
+ *
22
+ * **Two layers rather than a gradient.** A hard-stop linear gradient on one path
23
+ * would draw the half star too, but its stops are paint props that React Native
24
+ * SVG re-parses on every change, and a clip is a `View` width the layout engine
25
+ * already animates cheaply. The filled glyph inside the clip keeps the full
26
+ * glyph's size — it is the *window* that narrows, never the star.
27
+ *
28
+ * Both glyphs are sized by a `size-icon-*` class on a `View` and fill it at
29
+ * `100%`, which keeps the icon scale the single source of the size without
30
+ * wrapping `Svg` in `withUniwind` (rule 7).
31
+ *
32
+ * Internal: `Rating.Stars` renders one per count, and nothing outside reaches it.
33
+ */
34
+ export function RatingStar({
35
+ fill,
36
+ fillColor,
37
+ emptyColor,
38
+ glyphClassName,
39
+ clipClassName,
40
+ }: RatingStarProps): ReactElement {
41
+ return (
42
+ <View className={glyphClassName}>
43
+ <StarGlyph color={emptyColor} opacity={RATING_EMPTY_OPACITY} />
44
+ {fill > 0 ? (
45
+ <View className={clipClassName} style={{ width: `${fill * 100}%` }}>
46
+ <View className={glyphClassName}>
47
+ <StarGlyph color={fillColor} opacity={1} />
48
+ </View>
49
+ </View>
50
+ ) : null}
51
+ </View>
52
+ );
53
+ }
54
+ RatingStar.displayName = "DelacourUI.Rating.Stars.Star";
55
+
56
+ /**
57
+ * The path itself, filled and stroked in one colour so the points come out rounded.
58
+ *
59
+ * The opacity is the whole view's, not the path's `fillOpacity` and
60
+ * `strokeOpacity`: half the stroke overlaps the fill, and two translucent paints
61
+ * over one another draw a darker ring round every empty star.
62
+ */
63
+ function StarGlyph({ color, opacity }: { color: string | undefined; opacity: number }): ReactElement {
64
+ return (
65
+ <Svg height="100%" style={{ opacity }} viewBox="0 0 24 24" width="100%">
66
+ <Path d={RATING_STAR_PATH} fill={color} stroke={color} strokeLinejoin="round" strokeWidth={RATING_STAR_STROKE} />
67
+ </Svg>
68
+ );
69
+ }
70
+ StarGlyph.displayName = "DelacourUI.Rating.Stars.Star.Glyph";