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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (493) hide show
  1. package/README.md +10 -6
  2. package/package.json +49 -5
  3. package/src/components/accordion/AGENTS.md +21 -9
  4. package/src/components/accordion/accordion-content.tsx +7 -4
  5. package/src/components/accordion/accordion-item.tsx +15 -13
  6. package/src/components/accordion/accordion.context.tsx +11 -5
  7. package/src/components/accordion/accordion.variants.test.ts +64 -3
  8. package/src/components/accordion/accordion.variants.ts +33 -9
  9. package/src/components/alert/AGENTS.md +102 -0
  10. package/src/components/alert/alert-action.tsx +17 -0
  11. package/src/components/alert/alert-close-button.tsx +50 -0
  12. package/src/components/alert/alert-content.tsx +15 -0
  13. package/src/components/alert/alert-description.tsx +16 -0
  14. package/src/components/alert/alert-glyphs.ts +25 -0
  15. package/src/components/alert/alert-indicator.tsx +40 -0
  16. package/src/components/alert/alert-title.tsx +18 -0
  17. package/src/components/alert/alert.context.tsx +69 -0
  18. package/src/components/alert/alert.tsx +160 -0
  19. package/src/components/alert/alert.types.ts +10 -0
  20. package/src/components/alert/alert.variants.test.ts +218 -0
  21. package/src/components/alert/alert.variants.ts +180 -0
  22. package/src/components/alert/index.ts +23 -0
  23. package/src/components/avatar/AGENTS.md +106 -0
  24. package/src/components/avatar/avatar-badge.tsx +51 -0
  25. package/src/components/avatar/avatar-group.tsx +139 -0
  26. package/src/components/avatar/avatar.context.tsx +93 -0
  27. package/src/components/avatar/avatar.tsx +201 -0
  28. package/src/components/avatar/avatar.utils.test.ts +174 -0
  29. package/src/components/avatar/avatar.utils.ts +141 -0
  30. package/src/components/avatar/avatar.variants.test.ts +251 -0
  31. package/src/components/avatar/avatar.variants.ts +270 -0
  32. package/src/components/avatar/index.ts +39 -0
  33. package/src/components/badge/AGENTS.md +10 -0
  34. package/src/components/badge/badge-close-button.tsx +2 -2
  35. package/src/components/badge/badge.context.tsx +2 -1
  36. package/src/components/badge/badge.tsx +13 -4
  37. package/src/components/badge/badge.variants.test.ts +26 -0
  38. package/src/components/badge/badge.variants.ts +21 -0
  39. package/src/components/bottom-sheet/AGENTS.md +186 -231
  40. package/src/components/bottom-sheet/bottom-sheet-background.tsx +31 -22
  41. package/src/components/bottom-sheet/bottom-sheet-close.tsx +23 -30
  42. package/src/components/bottom-sheet/bottom-sheet-container.tsx +54 -208
  43. package/src/components/bottom-sheet/bottom-sheet-content.tsx +25 -48
  44. package/src/components/bottom-sheet/bottom-sheet-description.tsx +8 -2
  45. package/src/components/bottom-sheet/bottom-sheet-flat-list.tsx +65 -0
  46. package/src/components/bottom-sheet/bottom-sheet-footer.tsx +53 -122
  47. package/src/components/bottom-sheet/bottom-sheet-handle.tsx +31 -26
  48. package/src/components/bottom-sheet/bottom-sheet-legend-list.tsx +69 -0
  49. package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +25 -147
  50. package/src/components/bottom-sheet/bottom-sheet-provider.tsx +42 -0
  51. package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +38 -62
  52. package/src/components/bottom-sheet/bottom-sheet-section-list.tsx +58 -0
  53. package/src/components/bottom-sheet/bottom-sheet-steps.tsx +88 -0
  54. package/src/components/bottom-sheet/bottom-sheet-text-input.tsx +44 -0
  55. package/src/components/bottom-sheet/bottom-sheet-title.tsx +12 -8
  56. package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +19 -37
  57. package/src/components/bottom-sheet/bottom-sheet.tsx +113 -60
  58. package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +47 -87
  59. package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -126
  60. package/src/components/bottom-sheet/index.ts +39 -17
  61. package/src/components/bottom-sheet/use-bottom-sheet-input.ts +16 -115
  62. package/src/components/button/AGENTS.md +51 -11
  63. package/src/components/button/button-group.tsx +44 -8
  64. package/src/components/button/button.context.tsx +10 -0
  65. package/src/components/button/button.tsx +7 -1
  66. package/src/components/button/button.variants.test.ts +190 -0
  67. package/src/components/button/button.variants.ts +87 -6
  68. package/src/components/button/index.ts +2 -0
  69. package/src/components/calendar/AGENTS.md +142 -0
  70. package/src/components/calendar/calendar-caption.tsx +116 -0
  71. package/src/components/calendar/calendar-day.tsx +103 -0
  72. package/src/components/calendar/calendar-grid.tsx +183 -0
  73. package/src/components/calendar/calendar-header.tsx +34 -0
  74. package/src/components/calendar/calendar-nav.tsx +51 -0
  75. package/src/components/calendar/calendar-picker.tsx +128 -0
  76. package/src/components/calendar/calendar-weekdays.tsx +48 -0
  77. package/src/components/calendar/calendar.context.tsx +108 -0
  78. package/src/components/calendar/calendar.date.test.ts +456 -0
  79. package/src/components/calendar/calendar.date.ts +584 -0
  80. package/src/components/calendar/calendar.tsx +389 -0
  81. package/src/components/calendar/calendar.types.ts +87 -0
  82. package/src/components/calendar/calendar.variants.test.ts +363 -0
  83. package/src/components/calendar/calendar.variants.ts +426 -0
  84. package/src/components/calendar/index.ts +96 -0
  85. package/src/components/card/AGENTS.md +92 -0
  86. package/src/components/card/card-action.tsx +16 -0
  87. package/src/components/card/card-content.tsx +12 -0
  88. package/src/components/card/card-description.tsx +12 -0
  89. package/src/components/card/card-footer.tsx +30 -0
  90. package/src/components/card/card-header.tsx +45 -0
  91. package/src/components/card/card-title.tsx +25 -0
  92. package/src/components/card/card.context.tsx +64 -0
  93. package/src/components/card/card.tsx +100 -0
  94. package/src/components/card/card.types.ts +13 -0
  95. package/src/components/card/card.variants.test.ts +240 -0
  96. package/src/components/card/card.variants.ts +177 -0
  97. package/src/components/card/index.ts +15 -0
  98. package/src/components/chart/AGENTS.md +8 -0
  99. package/src/components/chart/chart.tsx +8 -1
  100. package/src/components/chip/AGENTS.md +113 -0
  101. package/src/components/chip/chip-close-button.tsx +48 -0
  102. package/src/components/chip/chip-end-content.tsx +15 -0
  103. package/src/components/chip/chip-label.tsx +21 -0
  104. package/src/components/chip/chip-start-content.tsx +16 -0
  105. package/src/components/chip/chip.context.tsx +66 -0
  106. package/src/components/chip/chip.tsx +268 -0
  107. package/src/components/chip/chip.types.ts +10 -0
  108. package/src/components/chip/chip.variants.test.ts +335 -0
  109. package/src/components/chip/chip.variants.ts +321 -0
  110. package/src/components/chip/index.ts +24 -0
  111. package/src/components/collapsible/AGENTS.md +97 -0
  112. package/src/components/collapsible/collapsible-content.tsx +97 -0
  113. package/src/components/collapsible/collapsible-description.tsx +17 -0
  114. package/src/components/collapsible/collapsible-indicator.tsx +92 -0
  115. package/src/components/collapsible/collapsible-title.tsx +17 -0
  116. package/src/components/collapsible/collapsible-trigger.tsx +133 -0
  117. package/src/components/collapsible/collapsible.context.tsx +91 -0
  118. package/src/components/collapsible/collapsible.tsx +167 -0
  119. package/src/components/collapsible/collapsible.types.ts +9 -0
  120. package/src/components/collapsible/collapsible.variants.test.ts +338 -0
  121. package/src/components/collapsible/collapsible.variants.ts +228 -0
  122. package/src/components/collapsible/index.ts +32 -0
  123. package/src/components/context-menu/AGENTS.md +103 -0
  124. package/src/components/context-menu/context-menu-content.tsx +64 -0
  125. package/src/components/context-menu/context-menu-item.tsx +22 -0
  126. package/src/components/context-menu/context-menu-preview.tsx +60 -0
  127. package/src/components/context-menu/context-menu-trigger.tsx +160 -0
  128. package/src/components/context-menu/context-menu.context.tsx +91 -0
  129. package/src/components/context-menu/context-menu.tsx +90 -0
  130. package/src/components/context-menu/context-menu.variants.test.ts +266 -0
  131. package/src/components/context-menu/context-menu.variants.ts +147 -0
  132. package/src/components/context-menu/index.ts +18 -0
  133. package/src/components/dialog/AGENTS.md +97 -0
  134. package/src/components/dialog/dialog-body.tsx +21 -0
  135. package/src/components/dialog/dialog-close.tsx +82 -0
  136. package/src/components/dialog/dialog-content.tsx +153 -0
  137. package/src/components/dialog/dialog-description.tsx +23 -0
  138. package/src/components/dialog/dialog-footer.tsx +32 -0
  139. package/src/components/dialog/dialog-header.tsx +20 -0
  140. package/src/components/dialog/dialog-title.tsx +42 -0
  141. package/src/components/dialog/dialog-trigger.tsx +54 -0
  142. package/src/components/dialog/dialog.context.tsx +90 -0
  143. package/src/components/dialog/dialog.tsx +119 -0
  144. package/src/components/dialog/dialog.variants.test.ts +224 -0
  145. package/src/components/dialog/dialog.variants.ts +173 -0
  146. package/src/components/dialog/index.ts +30 -0
  147. package/src/components/drawer/AGENTS.md +123 -0
  148. package/src/components/drawer/drawer-body.tsx +46 -0
  149. package/src/components/drawer/drawer-close.tsx +76 -0
  150. package/src/components/drawer/drawer-content.tsx +186 -0
  151. package/src/components/drawer/drawer-description.tsx +23 -0
  152. package/src/components/drawer/drawer-footer.tsx +21 -0
  153. package/src/components/drawer/drawer-header.tsx +42 -0
  154. package/src/components/drawer/drawer-title.tsx +35 -0
  155. package/src/components/drawer/drawer-trigger.tsx +54 -0
  156. package/src/components/drawer/drawer.context.tsx +114 -0
  157. package/src/components/drawer/drawer.tsx +119 -0
  158. package/src/components/drawer/drawer.variants.test.ts +392 -0
  159. package/src/components/drawer/drawer.variants.ts +244 -0
  160. package/src/components/drawer/index.ts +41 -0
  161. package/src/components/drawer/use-drawer-pan.ts +108 -0
  162. package/src/components/empty-state/AGENTS.md +90 -0
  163. package/src/components/empty-state/empty-state-content.tsx +17 -0
  164. package/src/components/empty-state/empty-state-description.tsx +12 -0
  165. package/src/components/empty-state/empty-state-header.tsx +17 -0
  166. package/src/components/empty-state/empty-state-media.tsx +56 -0
  167. package/src/components/empty-state/empty-state-title.tsx +18 -0
  168. package/src/components/empty-state/empty-state.context.tsx +65 -0
  169. package/src/components/empty-state/empty-state.tsx +84 -0
  170. package/src/components/empty-state/empty-state.types.ts +14 -0
  171. package/src/components/empty-state/empty-state.variants.test.ts +245 -0
  172. package/src/components/empty-state/empty-state.variants.ts +132 -0
  173. package/src/components/empty-state/index.ts +20 -0
  174. package/src/components/fab/AGENTS.md +119 -0
  175. package/src/components/fab/fab-action.tsx +118 -0
  176. package/src/components/fab/fab-group.tsx +214 -0
  177. package/src/components/fab/fab-label.tsx +18 -0
  178. package/src/components/fab/fab.context.tsx +125 -0
  179. package/src/components/fab/fab.tsx +156 -0
  180. package/src/components/fab/fab.types.ts +17 -0
  181. package/src/components/fab/fab.variants.test.ts +274 -0
  182. package/src/components/fab/fab.variants.ts +234 -0
  183. package/src/components/fab/index.ts +37 -0
  184. package/src/components/feedback/AGENTS.md +95 -0
  185. package/src/components/feedback/feedback-action.tsx +16 -0
  186. package/src/components/feedback/feedback-cancel.tsx +34 -0
  187. package/src/components/feedback/feedback-close.tsx +21 -0
  188. package/src/components/feedback/feedback-content.tsx +24 -0
  189. package/src/components/feedback/feedback-field.tsx +70 -0
  190. package/src/components/feedback/feedback-footer.tsx +21 -0
  191. package/src/components/feedback/feedback-panel.tsx +54 -0
  192. package/src/components/feedback/feedback-submit.tsx +74 -0
  193. package/src/components/feedback/feedback-title.tsx +34 -0
  194. package/src/components/feedback/feedback-trigger.tsx +20 -0
  195. package/src/components/feedback/feedback.context.tsx +73 -0
  196. package/src/components/feedback/feedback.tsx +135 -0
  197. package/src/components/feedback/feedback.variants.test.ts +185 -0
  198. package/src/components/feedback/feedback.variants.ts +128 -0
  199. package/src/components/feedback/index.ts +24 -0
  200. package/src/components/field/AGENTS.md +19 -0
  201. package/src/components/field/field-label.tsx +19 -5
  202. package/src/components/field/field.context.tsx +17 -0
  203. package/src/components/field/field.tsx +15 -2
  204. package/src/components/field/field.variants.test.ts +24 -0
  205. package/src/components/field/field.variants.ts +20 -0
  206. package/src/components/field/index.ts +1 -0
  207. package/src/components/input/AGENTS.md +5 -0
  208. package/src/components/input/input.variants.test.ts +32 -0
  209. package/src/components/input/input.variants.ts +13 -1
  210. package/src/components/item/AGENTS.md +92 -0
  211. package/src/components/item/index.ts +25 -0
  212. package/src/components/item/item-actions.tsx +28 -0
  213. package/src/components/item/item-content.tsx +15 -0
  214. package/src/components/item/item-description.tsx +12 -0
  215. package/src/components/item/item-footer.tsx +13 -0
  216. package/src/components/item/item-group.tsx +32 -0
  217. package/src/components/item/item-header.tsx +13 -0
  218. package/src/components/item/item-media.tsx +37 -0
  219. package/src/components/item/item-separator.tsx +13 -0
  220. package/src/components/item/item-title.tsx +17 -0
  221. package/src/components/item/item.context.tsx +66 -0
  222. package/src/components/item/item.tsx +218 -0
  223. package/src/components/item/item.types.ts +12 -0
  224. package/src/components/item/item.variants.test.ts +263 -0
  225. package/src/components/item/item.variants.ts +200 -0
  226. package/src/components/kpi/AGENTS.md +125 -0
  227. package/src/components/kpi/index.ts +45 -0
  228. package/src/components/kpi/kpi-action.tsx +12 -0
  229. package/src/components/kpi/kpi-content.tsx +35 -0
  230. package/src/components/kpi/kpi-footer.tsx +52 -0
  231. package/src/components/kpi/kpi-group.tsx +70 -0
  232. package/src/components/kpi/kpi-header.tsx +25 -0
  233. package/src/components/kpi/kpi-icon.tsx +28 -0
  234. package/src/components/kpi/kpi-sparkline.tsx +145 -0
  235. package/src/components/kpi/kpi-stat.tsx +20 -0
  236. package/src/components/kpi/kpi-title.tsx +19 -0
  237. package/src/components/kpi/kpi-trend.tsx +105 -0
  238. package/src/components/kpi/kpi-value.tsx +32 -0
  239. package/src/components/kpi/kpi.context.tsx +111 -0
  240. package/src/components/kpi/kpi.tsx +154 -0
  241. package/src/components/kpi/kpi.types.ts +9 -0
  242. package/src/components/kpi/kpi.variants.test.ts +428 -0
  243. package/src/components/kpi/kpi.variants.ts +347 -0
  244. package/src/components/label/AGENTS.md +89 -0
  245. package/src/components/label/index.ts +10 -0
  246. package/src/components/label/label.tsx +97 -0
  247. package/src/components/label/label.variants.test.ts +176 -0
  248. package/src/components/label/label.variants.ts +123 -0
  249. package/src/components/menu/AGENTS.md +176 -0
  250. package/src/components/menu/index.ts +44 -0
  251. package/src/components/menu/menu-background.tsx +17 -0
  252. package/src/components/menu/menu-checkbox-item.tsx +57 -0
  253. package/src/components/menu/menu-content.tsx +303 -0
  254. package/src/components/menu/menu-item.tsx +162 -0
  255. package/src/components/menu/menu-label.tsx +14 -0
  256. package/src/components/menu/menu-radio-group.tsx +50 -0
  257. package/src/components/menu/menu-radio-item.tsx +57 -0
  258. package/src/components/menu/menu-separator.tsx +11 -0
  259. package/src/components/menu/menu-sub-content.tsx +72 -0
  260. package/src/components/menu/menu-sub-trigger.tsx +50 -0
  261. package/src/components/menu/menu-sub.tsx +75 -0
  262. package/src/components/menu/menu-trigger.tsx +57 -0
  263. package/src/components/menu/menu.context.tsx +214 -0
  264. package/src/components/menu/menu.tsx +88 -0
  265. package/src/components/menu/menu.types.ts +33 -0
  266. package/src/components/menu/menu.variants.test.ts +400 -0
  267. package/src/components/menu/menu.variants.ts +353 -0
  268. package/src/components/meter/AGENTS.md +135 -0
  269. package/src/components/meter/index.ts +43 -0
  270. package/src/components/meter/meter-fill.tsx +19 -0
  271. package/src/components/meter/meter-header.tsx +17 -0
  272. package/src/components/meter/meter-label.tsx +18 -0
  273. package/src/components/meter/meter-output.tsx +42 -0
  274. package/src/components/meter/meter-segments.tsx +83 -0
  275. package/src/components/meter/meter-track.tsx +16 -0
  276. package/src/components/meter/meter.context.tsx +62 -0
  277. package/src/components/meter/meter.tsx +286 -0
  278. package/src/components/meter/meter.types.ts +30 -0
  279. package/src/components/meter/meter.variants.test.ts +430 -0
  280. package/src/components/meter/meter.variants.ts +347 -0
  281. package/src/components/overlay/AGENTS.md +89 -0
  282. package/src/components/overlay/index.ts +27 -0
  283. package/src/components/overlay/overlay-portal.tsx +84 -0
  284. package/src/components/overlay/overlay-presence.test.ts +46 -0
  285. package/src/components/overlay/overlay-presence.ts +49 -0
  286. package/src/components/overlay/overlay-registry.test.ts +96 -0
  287. package/src/components/overlay/overlay-registry.ts +99 -0
  288. package/src/components/overlay/overlay-scrim.tsx +49 -0
  289. package/src/components/overlay/overlay.context.tsx +48 -0
  290. package/src/components/overlay/overlay.tsx +118 -0
  291. package/src/components/overlay/overlay.variants.test.ts +37 -0
  292. package/src/components/overlay/overlay.variants.ts +33 -0
  293. package/src/components/overlay/use-overlay-back-handler.ts +34 -0
  294. package/src/components/overlay/use-overlay-presence.ts +87 -0
  295. package/src/components/popover/AGENTS.md +138 -0
  296. package/src/components/popover/index.ts +48 -0
  297. package/src/components/popover/popover-anchor.tsx +49 -0
  298. package/src/components/popover/popover-arrow.tsx +89 -0
  299. package/src/components/popover/popover-close.tsx +73 -0
  300. package/src/components/popover/popover-content.tsx +235 -0
  301. package/src/components/popover/popover-description.tsx +18 -0
  302. package/src/components/popover/popover-title.tsx +33 -0
  303. package/src/components/popover/popover-trigger.tsx +56 -0
  304. package/src/components/popover/popover.context.tsx +71 -0
  305. package/src/components/popover/popover.position.test.ts +354 -0
  306. package/src/components/popover/popover.position.ts +286 -0
  307. package/src/components/popover/popover.tsx +123 -0
  308. package/src/components/popover/popover.variants.test.ts +135 -0
  309. package/src/components/popover/popover.variants.ts +85 -0
  310. package/src/components/popover/use-anchor-measure.ts +76 -0
  311. package/src/components/popover/use-anchored-content.ts +219 -0
  312. package/src/components/pressable/AGENTS.md +11 -1
  313. package/src/components/pressable/pressable.tsx +7 -2
  314. package/src/components/pressable/pressable.variants.test.ts +29 -0
  315. package/src/components/pressable/pressable.variants.ts +17 -0
  316. package/src/components/progress/AGENTS.md +120 -0
  317. package/src/components/progress/index.ts +40 -0
  318. package/src/components/progress/progress-fill.tsx +152 -0
  319. package/src/components/progress/progress-header.tsx +19 -0
  320. package/src/components/progress/progress-label.tsx +27 -0
  321. package/src/components/progress/progress-output.tsx +47 -0
  322. package/src/components/progress/progress-track.tsx +38 -0
  323. package/src/components/progress/progress.context.tsx +79 -0
  324. package/src/components/progress/progress.tsx +171 -0
  325. package/src/components/progress/progress.types.ts +23 -0
  326. package/src/components/progress/progress.variants.test.ts +335 -0
  327. package/src/components/progress/progress.variants.ts +284 -0
  328. package/src/components/progress-button/AGENTS.md +116 -0
  329. package/src/components/progress-button/index.ts +36 -0
  330. package/src/components/progress-button/progress-button-done.tsx +33 -0
  331. package/src/components/progress-button/progress-button-label.tsx +27 -0
  332. package/src/components/progress-button/progress-button.context.tsx +93 -0
  333. package/src/components/progress-button/progress-button.tsx +414 -0
  334. package/src/components/progress-button/progress-button.variants.test.ts +314 -0
  335. package/src/components/progress-button/progress-button.variants.ts +236 -0
  336. package/src/components/provider/AGENTS.md +38 -25
  337. package/src/components/provider/provider.tsx +38 -11
  338. package/src/components/radio/AGENTS.md +8 -10
  339. package/src/components/radio/radio.variants.test.ts +5 -6
  340. package/src/components/radio/radio.variants.ts +6 -7
  341. package/src/components/rating/AGENTS.md +143 -0
  342. package/src/components/rating/index.ts +43 -0
  343. package/src/components/rating/rating-output.tsx +39 -0
  344. package/src/components/rating/rating-star.tsx +70 -0
  345. package/src/components/rating/rating-stars.tsx +219 -0
  346. package/src/components/rating/rating.context.tsx +79 -0
  347. package/src/components/rating/rating.tsx +163 -0
  348. package/src/components/rating/rating.variants.test.ts +347 -0
  349. package/src/components/rating/rating.variants.ts +354 -0
  350. package/src/components/selection-mode/AGENTS.md +155 -0
  351. package/src/components/selection-mode/index.ts +43 -0
  352. package/src/components/selection-mode/selection-mode-action.tsx +65 -0
  353. package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
  354. package/src/components/selection-mode/selection-mode-group.tsx +123 -0
  355. package/src/components/selection-mode/selection-mode-header.tsx +85 -0
  356. package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
  357. package/src/components/selection-mode/selection-mode-item.tsx +182 -0
  358. package/src/components/selection-mode/selection-mode.context.tsx +89 -0
  359. package/src/components/selection-mode/selection-mode.tsx +207 -0
  360. package/src/components/selection-mode/selection-mode.types.ts +36 -0
  361. package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
  362. package/src/components/selection-mode/selection-mode.variants.ts +329 -0
  363. package/src/components/skeleton/AGENTS.md +120 -0
  364. package/src/components/skeleton/index.ts +15 -0
  365. package/src/components/skeleton/skeleton-clock.ts +48 -0
  366. package/src/components/skeleton/skeleton-group.tsx +64 -0
  367. package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
  368. package/src/components/skeleton/skeleton.context.tsx +45 -0
  369. package/src/components/skeleton/skeleton.tsx +227 -0
  370. package/src/components/skeleton/skeleton.variants.test.ts +245 -0
  371. package/src/components/skeleton/skeleton.variants.ts +222 -0
  372. package/src/components/slide-button/AGENTS.md +122 -0
  373. package/src/components/slide-button/index.ts +43 -0
  374. package/src/components/slide-button/slide-button-label.tsx +70 -0
  375. package/src/components/slide-button/slide-button-thumb.tsx +93 -0
  376. package/src/components/slide-button/slide-button.context.tsx +76 -0
  377. package/src/components/slide-button/slide-button.tsx +457 -0
  378. package/src/components/slide-button/slide-button.variants.test.ts +288 -0
  379. package/src/components/slide-button/slide-button.variants.ts +324 -0
  380. package/src/components/slider/index.ts +2 -0
  381. package/src/components/slider/slider-thumb.tsx +33 -1
  382. package/src/components/slider/slider.variants.test.ts +52 -0
  383. package/src/components/slider/slider.variants.ts +56 -2
  384. package/src/components/stack-card/AGENTS.md +169 -0
  385. package/src/components/stack-card/index.ts +37 -0
  386. package/src/components/stack-card/stack-card-action.tsx +66 -0
  387. package/src/components/stack-card/stack-card-actions.tsx +18 -0
  388. package/src/components/stack-card/stack-card-card.tsx +71 -0
  389. package/src/components/stack-card/stack-card-empty.tsx +30 -0
  390. package/src/components/stack-card/stack-card-slot.tsx +90 -0
  391. package/src/components/stack-card/stack-card-stamp.tsx +76 -0
  392. package/src/components/stack-card/stack-card.context.tsx +117 -0
  393. package/src/components/stack-card/stack-card.tsx +557 -0
  394. package/src/components/stack-card/stack-card.types.ts +26 -0
  395. package/src/components/stack-card/stack-card.variants.test.ts +355 -0
  396. package/src/components/stack-card/stack-card.variants.ts +360 -0
  397. package/src/components/steps/AGENTS.md +153 -0
  398. package/src/components/steps/index.ts +45 -0
  399. package/src/components/steps/steps-connector.tsx +31 -0
  400. package/src/components/steps/steps-description.tsx +26 -0
  401. package/src/components/steps/steps-indicator.tsx +106 -0
  402. package/src/components/steps/steps-item.tsx +225 -0
  403. package/src/components/steps/steps-panel.tsx +41 -0
  404. package/src/components/steps/steps-title.tsx +32 -0
  405. package/src/components/steps/steps-track.tsx +59 -0
  406. package/src/components/steps/steps.context.tsx +123 -0
  407. package/src/components/steps/steps.tsx +166 -0
  408. package/src/components/steps/steps.variants.test.ts +388 -0
  409. package/src/components/steps/steps.variants.ts +468 -0
  410. package/src/components/surface/AGENTS.md +82 -0
  411. package/src/components/surface/index.ts +15 -0
  412. package/src/components/surface/surface.context.tsx +56 -0
  413. package/src/components/surface/surface.tsx +87 -0
  414. package/src/components/surface/surface.variants.test.ts +227 -0
  415. package/src/components/surface/surface.variants.ts +165 -0
  416. package/src/components/swipe/AGENTS.md +140 -0
  417. package/src/components/swipe/index.ts +31 -0
  418. package/src/components/swipe/swipe-action.tsx +99 -0
  419. package/src/components/swipe/swipe-end.tsx +14 -0
  420. package/src/components/swipe/swipe-group.tsx +55 -0
  421. package/src/components/swipe/swipe-panel.tsx +55 -0
  422. package/src/components/swipe/swipe-start.tsx +15 -0
  423. package/src/components/swipe/swipe.context.tsx +135 -0
  424. package/src/components/swipe/swipe.tsx +393 -0
  425. package/src/components/swipe/swipe.types.ts +7 -0
  426. package/src/components/swipe/swipe.variants.test.ts +337 -0
  427. package/src/components/swipe/swipe.variants.ts +290 -0
  428. package/src/components/switch/switch.variants.ts +2 -2
  429. package/src/components/tabs/AGENTS.md +4 -3
  430. package/src/components/tabs/tabs.variants.test.ts +1 -1
  431. package/src/components/tabs/tabs.variants.ts +4 -6
  432. package/src/components/text/AGENTS.md +8 -2
  433. package/src/components/text/text.tsx +7 -0
  434. package/src/components/text/text.variants.test.ts +24 -1
  435. package/src/components/text/text.variants.ts +5 -0
  436. package/src/components/textarea/AGENTS.md +89 -0
  437. package/src/components/textarea/index.ts +16 -0
  438. package/src/components/textarea/textarea.tsx +157 -0
  439. package/src/components/textarea/textarea.variants.test.ts +219 -0
  440. package/src/components/textarea/textarea.variants.ts +171 -0
  441. package/src/components/toast/AGENTS.md +140 -0
  442. package/src/components/toast/index.ts +74 -0
  443. package/src/components/toast/toast-action.tsx +59 -0
  444. package/src/components/toast/toast-api.ts +32 -0
  445. package/src/components/toast/toast-close.tsx +44 -0
  446. package/src/components/toast/toast-content.tsx +11 -0
  447. package/src/components/toast/toast-description.tsx +11 -0
  448. package/src/components/toast/toast-indicator.tsx +34 -0
  449. package/src/components/toast/toast-item.tsx +308 -0
  450. package/src/components/toast/toast-title.tsx +13 -0
  451. package/src/components/toast/toast-viewport.tsx +135 -0
  452. package/src/components/toast/toast.context.tsx +69 -0
  453. package/src/components/toast/toast.store.test.ts +259 -0
  454. package/src/components/toast/toast.store.ts +338 -0
  455. package/src/components/toast/toast.timer.test.ts +64 -0
  456. package/src/components/toast/toast.timer.ts +51 -0
  457. package/src/components/toast/toast.tsx +132 -0
  458. package/src/components/toast/toast.variants.test.ts +393 -0
  459. package/src/components/toast/toast.variants.ts +304 -0
  460. package/src/components/toggle-button/AGENTS.md +106 -0
  461. package/src/components/toggle-button/index.ts +30 -0
  462. package/src/components/toggle-button/toggle-button-group.tsx +156 -0
  463. package/src/components/toggle-button/toggle-button-label.tsx +19 -0
  464. package/src/components/toggle-button/toggle-button.context.tsx +130 -0
  465. package/src/components/toggle-button/toggle-button.tsx +172 -0
  466. package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
  467. package/src/components/toggle-button/toggle-button.variants.ts +160 -0
  468. package/src/components/tooltip/AGENTS.md +133 -0
  469. package/src/components/tooltip/index.ts +28 -0
  470. package/src/components/tooltip/tooltip-arrow.tsx +40 -0
  471. package/src/components/tooltip/tooltip-content.tsx +179 -0
  472. package/src/components/tooltip/tooltip-description.tsx +19 -0
  473. package/src/components/tooltip/tooltip-text.tsx +21 -0
  474. package/src/components/tooltip/tooltip-title.tsx +20 -0
  475. package/src/components/tooltip/tooltip-trigger.tsx +103 -0
  476. package/src/components/tooltip/tooltip.context.tsx +68 -0
  477. package/src/components/tooltip/tooltip.tsx +213 -0
  478. package/src/components/tooltip/tooltip.variants.test.ts +231 -0
  479. package/src/components/tooltip/tooltip.variants.ts +177 -0
  480. package/src/hooks/use-calm-motion.tsx +31 -0
  481. package/src/lib/calm-motion.test.ts +17 -0
  482. package/src/lib/calm-motion.ts +23 -0
  483. package/src/lib/cn.parity.test.ts +62 -0
  484. package/src/lib/cn.test.ts +14 -0
  485. package/src/lib/cn.ts +11 -6
  486. package/src/no-gorhom.test.ts +54 -0
  487. package/src/styles/geometry.test.ts +1 -0
  488. package/src/styles/theme.css +4 -0
  489. package/src/styles/tokens.css +44 -0
  490. package/src/styles/tokens.test.ts +56 -0
  491. package/src/styles/tokens.ts +29 -1
  492. package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
  493. package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
@@ -87,6 +87,14 @@ knows nothing about tokens.
87
87
  tailwind-merge is what lets a caller's `className="aspect-video h-auto"`
88
88
  cleanly win when they want that instead.
89
89
 
90
+ - **`frameClassName` resizes the frame, not the root.** The canvas fills the
91
+ frame, and the frame carries the size's height, so a `className` on the root
92
+ cannot shorten the plot — the frame would overflow it. `frameClassName` is
93
+ merged after the size's `h-chart-*`, and the tokens are registered with
94
+ tailwind-merge, so `h-16` cleanly replaces it. `Kpi.Sparkline` is the reason
95
+ it exists: a sparkline is a chart with no axes a third the height of the
96
+ smallest one.
97
+
90
98
  - **The tooltip is a React Native view even though it floats over the canvas.**
91
99
  It wants `popover`, `border`, the radius scale and the type scale, none of
92
100
  which exist in Skia, and it is the part a caller most wants to restyle —
@@ -72,6 +72,12 @@ export type ChartProps = {
72
72
  /** How the scrub coexists with a scrolling parent. Defaults to holding. */
73
73
  scrubConfig?: ScrubConfig;
74
74
  className?: string;
75
+ /**
76
+ * Merged onto the frame — the view the canvas fills — after the size's
77
+ * height. `frameClassName="h-16"` is a sparkline; the three `size` heights
78
+ * are for a chart someone reads with axes.
79
+ */
80
+ frameClassName?: string;
75
81
  /** Named on the frame, so a capture flow or a test can find the plot. */
76
82
  testID?: string;
77
83
  children?: ReactNode;
@@ -106,6 +112,7 @@ function ChartRoot({
106
112
  orientation = "vertical",
107
113
  scrubConfig,
108
114
  className,
115
+ frameClassName,
109
116
  testID,
110
117
  children,
111
118
  }: ChartProps): ReactElement {
@@ -231,7 +238,7 @@ function ChartRoot({
231
238
  return (
232
239
  <ChartProvider value={value}>
233
240
  <View className={slots.root({ className })}>
234
- <View className={slots.frame()} onLayout={onLayout} testID={testID}>
241
+ <View className={slots.frame({ className: frameClassName })} onLayout={onLayout} testID={testID}>
235
242
  <CartesianChart
236
243
  curve={curve}
237
244
  data={data}
@@ -0,0 +1,113 @@
1
+ # Chip
2
+
3
+ An interactive pill — a filter that is on or off, a tag, or a token that can be
4
+ removed. Compound root plus `Chip.Label`, `Chip.StartContent`, `Chip.EndContent`
5
+ and `Chip.CloseButton`.
6
+
7
+ `import { Chip } from "@delacour/react-native-ui/chip";`
8
+
9
+ ## Files
10
+
11
+ | File | What it holds |
12
+ | --- | --- |
13
+ | `index.ts` | → `@delacour/react-native-ui/chip` |
14
+ | `chip.tsx` | Root + the `Object.assign` compound surface |
15
+ | `chip-label.tsx` | `Chip.Label` |
16
+ | `chip-start-content.tsx` | `Chip.StartContent` |
17
+ | `chip-end-content.tsx` | `Chip.EndContent` |
18
+ | `chip-close-button.tsx` | `Chip.CloseButton`, the remove pressable |
19
+ | `chip.context.tsx` | `ChipContext`, `useChip()`, `useChipContext()`, `useChipPart()` |
20
+ | `chip.types.ts` | Prop types shared by two or more parts |
21
+ | `chip.variants.ts` | Pure `tv()` slots, the foreground map, hit slops, `resolveChipMode` / `resolveChipSurface` / `resolveChipForegroundToken` — no RN imports |
22
+ | `chip.variants.test.ts` | |
23
+
24
+ ## Design
25
+
26
+ - **What a chip does is read from its props, never from a `type` flag.**
27
+ `resolveChipMode` returns `static`, `button` or `toggle`. No handler is a tag,
28
+ rendered as a plain `View` — a detector under every tag in a list of fifty
29
+ would announce each as a button with no action, the same reason
30
+ [`Badge`](../badge/AGENTS.md) stays inert. `onPress` or `onLongPress` is a
31
+ button. **Any** selection prop — `isSelected` even when `false`,
32
+ `defaultSelected`, `onSelectedChange` — is a toggle, and it outranks a bare
33
+ press handler, because a chip with an on state that is announced as a plain
34
+ button leaves a screen-reader user unable to tell which chips are on. A
35
+ toggle still calls `onPress` after flipping.
36
+ - **Selection is announced as `selected`, with the `button` role.** That is the
37
+ native idiom for a filter on both platforms — VoiceOver reads "Selected",
38
+ TalkBack "selected" — and it matches what a system segmented filter reports.
39
+ `checkbox` would promise a box that is not drawn. A caller's own
40
+ `accessibilityState` is merged under it, and `Pressable` merges `disabled` on
41
+ top.
42
+ - **Controlled or uncontrolled, through `useControllableState`**, the same hook
43
+ and the same prop shape as [`Checkbox`](../checkbox/AGENTS.md):
44
+ `isSelected` / `defaultSelected` / `onSelectedChange`. A toggle ticks a
45
+ `selection` haptic by default; a button plays none, as a badge does. Pass
46
+ `haptic` to override either.
47
+ - **Tone is Badge's, not a copy of it.** `CHIP_COLORS` and `CHIP_SIZES` *are*
48
+ `BADGE_COLORS` and `BADGE_SIZES`, and the resting entries of
49
+ `CHIP_FOREGROUND_TOKEN` are Badge's own objects. The class strings in
50
+ `chipVariants` are restated (Tailwind's scanner is static, so a class has to
51
+ appear literally somewhere), and a test pins every resting cell's `bg-*`,
52
+ `border-*` and label colour to the badge of the same variant and colour. A
53
+ soft `success` chip beside a soft `success` badge is one shade by
54
+ construction.
55
+ - **Two resting variants, `soft` and `outline`.** `solid` is what selection
56
+ paints, so a resting solid chip would read as already on; `ghost` has neither
57
+ fill nor border, so there is no edge to aim at. `soft` is the default.
58
+ - **Selection is a surface, not a modifier.** `CHIP_SURFACES` is the two resting
59
+ variants plus `selected`, and a selected chip looks the same whichever it rests
60
+ on — a solid fill and matching border in its colour. The `isSelected`
61
+ compound variants sit after the resting ones, so tailwind-merge lets them win.
62
+ - **A selected `default` chip inverts** to `bg-foreground` / `text-background`.
63
+ Badge's solid default is `secondary`, which this theme sets a hair from
64
+ `muted` — the soft default — so a selected default chip would look unselected.
65
+ A test asserts all eighteen surface-and-colour cells are distinct.
66
+ - **The border is reserved in every state.** A chip toggles in place inside a
67
+ wrapping row; a box that grew two points on selection would reflow every chip
68
+ after it. `overflow-hidden` keeps the pressed fade inside the capsule.
69
+ - **Roomier than a badge, and still never a height.** A chip is aimed at and a
70
+ badge is only read, so each size pads at least as generously as the badge of
71
+ that size — a test asserts it. There is no `h-*`, so the label grows with OS
72
+ font scaling. The icon step is Badge's, so a glyph moves between the two
73
+ without changing size.
74
+ - **Hit slop is vertical and capped at four points.** Chips wrap with `gap-2`;
75
+ slop past half that gap reaches into the neighbouring row and makes a tap
76
+ between two chips ambiguous. `CHIP_HIT_SLOP` is never horizontal for the same
77
+ reason. The close glyph gets `CHIP_CLOSE_HIT_SLOP` on every side instead — it
78
+ is 14–18 points, and its slop only reaches into its own chip's padding.
79
+ - **`onClose` is its own pressable, never a mode of the root.** The remove
80
+ control is a `Chip.CloseButton` composed in last, so its tap is claimed by the
81
+ inner detector and never also toggles the chip. It presses with `fade` —
82
+ a spring on a glyph that small reads as a jitter — and inherits the current
83
+ surface's colour, so it turns with the label on selection.
84
+ - **On a pressable chip, removal is an accessibility action, not a second
85
+ element.** iOS folds every descendant of an accessible view into one element,
86
+ so a close control inside a button or toggle chip was unreachable by swipe and
87
+ its label was read as part of the chip's — "React Native, Remove React
88
+ Native". `resolveChipCloseExposure` decides: a static chip leaves the close
89
+ control as its own element; a pressable one hides it
90
+ (`accessibilityElementsHidden`, `no-hide-descendants`) and adds a `remove`
91
+ action, labelled by `closeAccessibilityLabel`, that calls `onClose` —
92
+ VoiceOver's Actions rotor, TalkBack's actions menu. A caller's own
93
+ `accessibilityActions` are kept beside it.
94
+ - **`closeTestID` reaches the composed close control.** The root's own `testID`
95
+ names the chip; the close part is a separate pressable with no id of its own
96
+ unless the root forwards one, so a flow or test could only find it by its label.
97
+ `closeTestID` is named like `closeAccessibilityLabel`, the other prop the root
98
+ passes through to it.
99
+ - **`self-start` is Badge's, and beside a taller sibling in a row it top-aligns
100
+ the chip.** It is what keeps a chip content-sized in a column, which is the
101
+ common case; in a `flex-row items-center` holding something taller — a
102
+ `Switch` — pass `className="self-center"`, which tailwind-merge lets win. The
103
+ playground's `controlled-and-uncontrolled` demo does exactly this.
104
+ - **Icons are composed, never passed as props.** The root wraps its subtree in an
105
+ `IconDefaultsProvider` and a `TextClassProvider` resolved from the *current*
106
+ surface, so a bare `<Icon>` or `<Text>` follows the chip into and out of
107
+ selection. `useChip()` exposes `isSelected` for a custom child that needs more.
108
+ - **String children** are wrapped in a `Chip.Label` automatically, consecutive
109
+ strings collapsing into one — the same rule as [`Badge`](../badge/AGENTS.md).
110
+ - **No group, yet.** Single- and multi-select filter rows are composed from
111
+ controlled chips (the playground's `filter-row` demo). A `Chip.Group` owning
112
+ the value, as `Checkbox.Group` does, is the obvious next step once a second
113
+ caller needs it.
@@ -0,0 +1,48 @@
1
+ import type { ReactElement } from "react";
2
+ import { IconCrossSmall } from "../../icons/central";
3
+ import { Icon } from "../icon";
4
+ import { Pressable, type PressableProps } from "../pressable";
5
+ import { useChipPart } from "./chip.context";
6
+ import { CHIP_CLOSE_HIT_SLOP, chipVariants } from "./chip.variants";
7
+
8
+ export type ChipCloseButtonProps = Omit<PressableProps, "asChild" | "busy" | "children" | "disabled">;
9
+
10
+ /**
11
+ * The chip's trailing remove control.
12
+ *
13
+ * A pressable of its own rather than a mode of the root, which is what keeps a
14
+ * removal from also toggling the chip or firing its `onPress`: the two gestures
15
+ * belong to two detectors, and the inner one claims the tap.
16
+ *
17
+ * The root composes one in whenever `onClose` is set, so reach for this by hand
18
+ * only to place it somewhere other than last.
19
+ *
20
+ * The glyph is left bare: it inherits the chip's icon size and its current
21
+ * surface's colour from the root's `IconDefaultsProvider`, so it turns with the
22
+ * label when the chip is selected. `CHIP_CLOSE_HIT_SLOP` grows a 14–18 point
23
+ * glyph into something a thumb can find.
24
+ */
25
+ export function ChipCloseButton({
26
+ accessibilityLabel = "Remove",
27
+ className,
28
+ feedback = "fade",
29
+ hitSlop,
30
+ ...props
31
+ }: ChipCloseButtonProps): ReactElement {
32
+ const { size, isDisabled } = useChipPart("Chip.CloseButton");
33
+
34
+ return (
35
+ <Pressable
36
+ accessibilityLabel={accessibilityLabel}
37
+ accessibilityRole="button"
38
+ className={chipVariants({ size }).closeButton({ className })}
39
+ disabled={isDisabled}
40
+ feedback={feedback}
41
+ hitSlop={hitSlop ?? CHIP_CLOSE_HIT_SLOP[size]}
42
+ {...props}
43
+ >
44
+ <Icon icon={IconCrossSmall} />
45
+ </Pressable>
46
+ );
47
+ }
48
+ ChipCloseButton.displayName = "DelacourUI.Chip.CloseButton";
@@ -0,0 +1,15 @@
1
+ import type { ReactElement } from "react";
2
+ import { View } from "react-native";
3
+ import type { ChipSlotProps } from "./chip.types";
4
+ import { chipVariants } from "./chip.variants";
5
+
6
+ /**
7
+ * A centred wrapper for trailing content that is not an `Icon` — a count.
8
+ *
9
+ * An `Icon` needs no wrapper: it inherits the chip's icon size and its
10
+ * surface's colour from the root's `IconDefaultsProvider`.
11
+ */
12
+ export function ChipEndContent({ className, ...props }: ChipSlotProps): ReactElement {
13
+ return <View className={chipVariants().endContent({ className })} {...props} />;
14
+ }
15
+ ChipEndContent.displayName = "DelacourUI.Chip.EndContent";
@@ -0,0 +1,21 @@
1
+ import type { ReactElement } from "react";
2
+ import type { TextProps } from "react-native";
3
+ import { Text } from "../text";
4
+ import { useChipPart } from "./chip.context";
5
+ import { chipVariants } from "./chip.variants";
6
+
7
+ export type ChipLabelProps = TextProps & { className?: string };
8
+
9
+ /**
10
+ * The chip's text.
11
+ *
12
+ * Reads the chip's variant, colour and selected state itself, so an
13
+ * icon-plus-text chip changes colour on selection without the caller threading
14
+ * anything through: a React Native `View` does not cascade colour to a `Text`
15
+ * descendant, so a colour set on the root would be lost.
16
+ */
17
+ export function ChipLabel({ className, ...props }: ChipLabelProps): ReactElement {
18
+ const { variant, color, size, isSelected } = useChipPart("Chip.Label");
19
+ return <Text className={chipVariants({ color, isSelected, size, variant }).label({ className })} {...props} />;
20
+ }
21
+ ChipLabel.displayName = "DelacourUI.Chip.Label";
@@ -0,0 +1,16 @@
1
+ import type { ReactElement } from "react";
2
+ import { View } from "react-native";
3
+ import type { ChipSlotProps } from "./chip.types";
4
+ import { chipVariants } from "./chip.variants";
5
+
6
+ /**
7
+ * A centred wrapper for leading content that is not an `Icon` — an avatar, a
8
+ * status dot.
9
+ *
10
+ * An `Icon` needs no wrapper: it inherits the chip's icon size and its
11
+ * surface's colour from the root's `IconDefaultsProvider`.
12
+ */
13
+ export function ChipStartContent({ className, ...props }: ChipSlotProps): ReactElement {
14
+ return <View className={chipVariants().startContent({ className })} {...props} />;
15
+ }
16
+ ChipStartContent.displayName = "DelacourUI.Chip.StartContent";
@@ -0,0 +1,66 @@
1
+ import { createContext, type ReactElement, type ReactNode, use } from "react";
2
+ import type { ChipColor, ChipSize, ChipVariant } from "./chip.variants";
3
+
4
+ export type ChipContextValue = {
5
+ /** Size of the chip. */
6
+ size: ChipSize;
7
+ /** How the chip is painted while unselected. */
8
+ variant: ChipVariant;
9
+ /** What the chip means. */
10
+ color: ChipColor;
11
+ /** Whether the chip is selected. Always `false` for a chip that cannot be. */
12
+ isSelected: boolean;
13
+ /** Whether the chip is disabled. */
14
+ isDisabled: boolean;
15
+ };
16
+
17
+ const ChipContext = createContext<ChipContextValue | null>(null);
18
+
19
+ /**
20
+ * Supplies the enclosing chip's variant, colour, size and state to its subtree.
21
+ *
22
+ * Lives in its own module, importing nothing but `chip.variants`, so a part can
23
+ * read it without importing `./chip`. That import would close a cycle, and Metro
24
+ * serves a partially initialised module for a cycle — leaving the context
25
+ * `undefined` at import time and red-boxing the app on a cold start.
26
+ */
27
+ export function ChipProvider({ value, children }: { value: ChipContextValue; children: ReactNode }): ReactElement {
28
+ return <ChipContext value={value}>{children}</ChipContext>;
29
+ }
30
+ ChipProvider.displayName = "DelacourUI.Chip.Provider";
31
+
32
+ /** The enclosing chip's context, or null outside a `<Chip>`. */
33
+ export function useChipContext(): ChipContextValue | null {
34
+ return use(ChipContext);
35
+ }
36
+
37
+ /**
38
+ * Reads the enclosing chip's variant, colour, size and state.
39
+ *
40
+ * Lets a custom child restyle itself when the chip is selected without the chip
41
+ * passing props down through every slot. Throws outside a `<Chip>` — use
42
+ * {@link useChipContext} where the enclosing chip is optional.
43
+ */
44
+ export function useChip(): ChipContextValue {
45
+ const context = useChipContext();
46
+ if (!context) {
47
+ throw new Error("useChip must be called inside a <Chip>.");
48
+ }
49
+ return context;
50
+ }
51
+
52
+ /**
53
+ * The enclosing chip's context, for a compound part that cannot work without
54
+ * one.
55
+ *
56
+ * Internal: deliberately not re-exported from `index.ts`. A caller outside the
57
+ * library wants {@link useChip}, whose error message names the hook rather than
58
+ * a part.
59
+ */
60
+ export function useChipPart(component: string): ChipContextValue {
61
+ const context = useChipContext();
62
+ if (!context) {
63
+ throw new Error(`${component} must be rendered inside a <Chip>.`);
64
+ }
65
+ return context;
66
+ }
@@ -0,0 +1,268 @@
1
+ import { Children, type ReactElement, type ReactNode, useCallback, useMemo } from "react";
2
+ import { type AccessibilityActionEvent, View } from "react-native";
3
+ import { useControllableState } from "../../hooks/use-controllable-state";
4
+ import { IconDefaultsProvider } from "../icon";
5
+ import { Pressable, type PressableProps } from "../pressable";
6
+ import { TextClassProvider } from "../text/text.context";
7
+ import { type ChipContextValue, ChipProvider } from "./chip.context";
8
+ import {
9
+ CHIP_HIT_SLOP,
10
+ type ChipColor,
11
+ type ChipSize,
12
+ type ChipVariant,
13
+ chipVariants,
14
+ resolveChipCloseExposure,
15
+ resolveChipForegroundToken,
16
+ resolveChipMode,
17
+ } from "./chip.variants";
18
+ import { ChipCloseButton } from "./chip-close-button";
19
+ import { ChipEndContent } from "./chip-end-content";
20
+ import { ChipLabel } from "./chip-label";
21
+ import { ChipStartContent } from "./chip-start-content";
22
+
23
+ export type ChipProps = Omit<PressableProps, "asChild" | "busy" | "children" | "disabled"> & {
24
+ /** How the chip is painted while unselected. */
25
+ variant?: ChipVariant;
26
+ /** What the chip means. Also the fill it takes when selected. */
27
+ color?: ChipColor;
28
+ size?: ChipSize;
29
+ /**
30
+ * Controlled selected state. Setting it — even to `false` — makes the chip a
31
+ * toggle, announced with its state; a tap reports the flip through
32
+ * `onSelectedChange`.
33
+ */
34
+ isSelected?: boolean;
35
+ /** Starting selected state while uncontrolled. Also makes the chip a toggle. */
36
+ defaultSelected?: boolean;
37
+ onSelectedChange?: (isSelected: boolean) => void;
38
+ isDisabled?: boolean;
39
+ /** Composes a trailing remove control in. Its press never toggles the chip or reaches `onPress`. */
40
+ onClose?: () => void;
41
+ /** Name a screen reader gives the remove control. Defaults to `Remove`. */
42
+ closeAccessibilityLabel?: string;
43
+ /** `testID` for the remove control, so a test or automation can press it. */
44
+ closeTestID?: string;
45
+ children?: ReactNode;
46
+ };
47
+
48
+ function ChipRoot({
49
+ variant = "soft",
50
+ color = "default",
51
+ size = "md",
52
+ isSelected,
53
+ defaultSelected,
54
+ onSelectedChange,
55
+ isDisabled = false,
56
+ onClose,
57
+ closeAccessibilityLabel,
58
+ closeTestID,
59
+ onPress,
60
+ onLongPress,
61
+ haptic,
62
+ feedback = "scale",
63
+ pressedScale,
64
+ pressedOpacity,
65
+ accessibilityState,
66
+ accessibilityActions,
67
+ onAccessibilityAction,
68
+ className,
69
+ children,
70
+ ...props
71
+ }: ChipProps): ReactElement {
72
+ const mode = resolveChipMode({ defaultSelected, isSelected, onLongPress, onPress, onSelectedChange });
73
+ const closeExposure = resolveChipCloseExposure({ hasClose: onClose !== undefined, mode });
74
+
75
+ const [ownSelected, setSelected] = useControllableState({
76
+ defaultValue: defaultSelected ?? false,
77
+ onChange: onSelectedChange,
78
+ value: isSelected,
79
+ });
80
+ const selected = mode === "toggle" && ownSelected;
81
+
82
+ const handlePress = useCallback(() => {
83
+ if (mode === "toggle") setSelected(!selected);
84
+ onPress?.();
85
+ }, [mode, onPress, selected, setSelected]);
86
+
87
+ const context = useMemo<ChipContextValue>(
88
+ () => ({ color, isDisabled, isSelected: selected, size, variant }),
89
+ [color, isDisabled, selected, size, variant]
90
+ );
91
+
92
+ const slots = chipVariants({ color, isDisabled, isSelected: selected, size, variant });
93
+
94
+ // Icons composed into the chip adopt these unless told otherwise, and turn
95
+ // with the label when the chip is selected.
96
+ const iconClassName = slots.icon();
97
+ const foregroundToken = resolveChipForegroundToken({ color, isSelected: selected, variant });
98
+ const iconDefaults = useMemo(
99
+ () => ({ className: iconClassName, color: foregroundToken }),
100
+ [iconClassName, foregroundToken]
101
+ );
102
+
103
+ const content = useMemo(() => wrapTextChildren(children), [children]);
104
+ const rootClassName = slots.root({ className });
105
+
106
+ // Inside a pressable chip the close control is folded into the chip's own
107
+ // accessibility element, so it is hidden there and offered as the `remove`
108
+ // action below instead — see `resolveChipCloseExposure`.
109
+ const closeHidden = closeExposure === "action";
110
+ const inner = (
111
+ <IconDefaultsProvider value={iconDefaults}>
112
+ <TextClassProvider value={slots.label()}>
113
+ {content}
114
+ {onClose ? (
115
+ <ChipCloseButton
116
+ accessibilityElementsHidden={closeHidden}
117
+ accessible={!closeHidden}
118
+ accessibilityLabel={closeAccessibilityLabel}
119
+ importantForAccessibility={closeHidden ? "no-hide-descendants" : "auto"}
120
+ onPress={onClose}
121
+ testID={closeTestID}
122
+ />
123
+ ) : null}
124
+ </TextClassProvider>
125
+ </IconDefaultsProvider>
126
+ );
127
+
128
+ const actions =
129
+ closeExposure === "action"
130
+ ? [...(accessibilityActions ?? []), { label: closeAccessibilityLabel ?? "Remove", name: "remove" }]
131
+ : accessibilityActions;
132
+
133
+ const handleAccessibilityAction = (event: AccessibilityActionEvent) => {
134
+ if (closeExposure === "action" && event.nativeEvent.actionName === "remove" && !isDisabled) {
135
+ onClose?.();
136
+ return;
137
+ }
138
+ onAccessibilityAction?.(event);
139
+ };
140
+
141
+ // A chip with nothing to do stays a plain box — see `resolveChipMode`.
142
+ if (mode === "static") {
143
+ return (
144
+ <ChipProvider value={context}>
145
+ <View
146
+ accessibilityActions={accessibilityActions}
147
+ className={rootClassName}
148
+ onAccessibilityAction={onAccessibilityAction}
149
+ {...props}
150
+ >
151
+ {inner}
152
+ </View>
153
+ </ChipProvider>
154
+ );
155
+ }
156
+
157
+ // A toggle reports `selected`, which VoiceOver reads as "Selected" and
158
+ // TalkBack as "selected" — the native idiom for a filter — and a plain
159
+ // button reports nothing extra. Pressable merges `disabled` in on top.
160
+ const state = mode === "toggle" ? { ...accessibilityState, selected } : accessibilityState;
161
+
162
+ return (
163
+ <ChipProvider value={context}>
164
+ <Pressable
165
+ accessibilityActions={actions}
166
+ accessibilityRole="button"
167
+ accessibilityState={state}
168
+ className={rootClassName}
169
+ disabled={isDisabled}
170
+ feedback={feedback}
171
+ haptic={haptic ?? (mode === "toggle" ? "selection" : false)}
172
+ hitSlop={CHIP_HIT_SLOP[size]}
173
+ onAccessibilityAction={handleAccessibilityAction}
174
+ onLongPress={onLongPress}
175
+ onPress={handlePress}
176
+ pressedOpacity={pressedOpacity}
177
+ pressedScale={pressedScale}
178
+ {...props}
179
+ >
180
+ {inner}
181
+ </Pressable>
182
+ </ChipProvider>
183
+ );
184
+ }
185
+
186
+ /**
187
+ * Wraps bare text children in a `Chip.Label`.
188
+ *
189
+ * React Native cannot render a string outside a `<Text>`, so `<Chip>Design</Chip>`
190
+ * would otherwise crash. Consecutive strings and numbers are collected into a
191
+ * single label rather than one each — `{count} open` is one piece of text, and
192
+ * wrapping the parts separately would space them apart by the chip's gap.
193
+ */
194
+ function wrapTextChildren(children: ReactNode): ReactNode {
195
+ const items = Children.toArray(children);
196
+ const output: ReactNode[] = [];
197
+ let run: (string | number)[] = [];
198
+
199
+ const flushRun = () => {
200
+ if (run.length === 0) return;
201
+ output.push(<ChipLabel key={`label-${output.length}`}>{run.join("")}</ChipLabel>);
202
+ run = [];
203
+ };
204
+
205
+ for (const child of items) {
206
+ if (typeof child === "string" || typeof child === "number") {
207
+ run.push(child);
208
+ continue;
209
+ }
210
+ flushRun();
211
+ output.push(child);
212
+ }
213
+ flushRun();
214
+
215
+ return output;
216
+ }
217
+
218
+ /**
219
+ * An interactive pill — a filter that is on or off, a tag, or a token that can
220
+ * be removed.
221
+ *
222
+ * What it does is decided by the props it is given, never by a `type` flag:
223
+ *
224
+ * - **Nothing** — a tag. A plain `View`, not announced as a control.
225
+ * - **`onPress` / `onLongPress`** — a button.
226
+ * - **`isSelected`, `defaultSelected` or `onSelectedChange`** — a filter. A tap
227
+ * flips the state, a `selection` haptic ticks, and a screen reader hears
228
+ * whether it is selected. Controlled or uncontrolled, like `Checkbox`.
229
+ * - **`onClose`** — adds a trailing remove control, a pressable of its own, so
230
+ * removing a chip never also toggles it. Combines with any of the above; on
231
+ * a pressable chip a screen reader reaches it as a `remove` action.
232
+ *
233
+ * Shares Badge's `color` and `size` axes and its tones: an unselected chip is
234
+ * painted exactly as the badge of the same `variant` and `color`. Selection
235
+ * replaces that with a solid fill in the chip's colour, and a selected
236
+ * `default` chip inverts. The border is reserved in every state, so selecting a
237
+ * chip never reflows the row it sits in.
238
+ *
239
+ * Icons are composed in, not passed as props. Anything in the subtree inherits
240
+ * the chip's icon size and its current surface's colour, and follows it when
241
+ * the chip is selected.
242
+ *
243
+ * @example
244
+ * <Chip color="success">Shipped</Chip>
245
+ *
246
+ * @example
247
+ * const [open, setOpen] = useState(false);
248
+ * <Chip isSelected={open} onSelectedChange={setOpen}>
249
+ * <Icon icon={IconFilter} />
250
+ * <Chip.Label>Open only</Chip.Label>
251
+ * </Chip>
252
+ *
253
+ * @example
254
+ * <Chip onClose={() => remove(tag)} variant="outline">
255
+ * {tag}
256
+ * </Chip>
257
+ */
258
+ export const Chip = Object.assign(ChipRoot, {
259
+ /** The chip's text. Picks its colour and type scale from the chip's variant, colour, size and selection. */
260
+ Label: ChipLabel,
261
+ /** A centred wrapper for leading content that is not an `Icon` — an avatar, a status dot. */
262
+ StartContent: ChipStartContent,
263
+ /** A centred wrapper for trailing content that is not an `Icon` — a count. */
264
+ EndContent: ChipEndContent,
265
+ /** The trailing remove control. Composed in automatically whenever `onClose` is set. */
266
+ CloseButton: ChipCloseButton,
267
+ displayName: "DelacourUI.Chip",
268
+ });
@@ -0,0 +1,10 @@
1
+ import type { ViewProps } from "react-native";
2
+
3
+ /**
4
+ * The shape of a chip's leading and trailing slots.
5
+ *
6
+ * Shared by `Chip.StartContent` and `Chip.EndContent`, which are identical apart
7
+ * from where the root places them, so it lives in a leaf rather than in one of
8
+ * the two files arbitrarily.
9
+ */
10
+ export type ChipSlotProps = ViewProps & { className?: string };