@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
@@ -0,0 +1,335 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { declaredTokens } from "../../styles/theme-tokens.test";
3
+ import { ICON_SIZE_TOKENS } from "../../styles/tokens";
4
+ import { BADGE_COLORS, BADGE_FOREGROUND_TOKEN, BADGE_SIZES, badgeVariants } from "../badge/badge.variants";
5
+ import {
6
+ CHIP_CLOSE_HIT_SLOP,
7
+ CHIP_COLORS,
8
+ CHIP_FOREGROUND_TOKEN,
9
+ CHIP_HIT_SLOP,
10
+ CHIP_SIZES,
11
+ CHIP_SURFACES,
12
+ CHIP_VARIANTS,
13
+ chipVariants,
14
+ resolveChipCloseExposure,
15
+ resolveChipForegroundToken,
16
+ resolveChipMode,
17
+ resolveChipSurface,
18
+ } from "./chip.variants";
19
+
20
+ const LIGHT = declaredTokens("light");
21
+ const DARK = declaredTokens("dark");
22
+
23
+ /** Tailwind's spacing step a class string sets its horizontal padding from. */
24
+ function paddingX(cls: string): number {
25
+ return Number(cls.match(/\bpx-([\d.]+)\b/)?.[1]);
26
+ }
27
+
28
+ /** Tailwind's spacing step a class string sets its vertical padding from. */
29
+ function paddingY(cls: string): number {
30
+ return Number(cls.match(/\bpy-([\d.]+)\b/)?.[1]);
31
+ }
32
+
33
+ /** Tailwind's spacing step a class string sets its gap from. */
34
+ function gapOf(cls: string): number {
35
+ return Number(cls.match(/\bgap-([\d.]+)\b/)?.[1]);
36
+ }
37
+
38
+ /** Position of a label's font size on the type scale, largest last. */
39
+ const TEXT_SCALE = ["text-xs", "text-sm", "text-base", "text-lg"] as const;
40
+ function textStep(cls: string): number {
41
+ return TEXT_SCALE.findIndex((step) => new RegExp(`\\b${step}\\b`).test(cls));
42
+ }
43
+
44
+ /** Position of a slot's `size-icon-*` token on the shared icon scale. */
45
+ function iconStep(cls: string): number {
46
+ const token = cls.match(/\bsize-(icon-[\w-]+)\b/)?.[1];
47
+ return ICON_SIZE_TOKENS.indexOf(token as (typeof ICON_SIZE_TOKENS)[number]);
48
+ }
49
+
50
+ /** The `text-*` colour token a label class string resolves to, minus the prefix. */
51
+ function labelColorToken(cls: string): string | undefined {
52
+ return cls.match(/\btext-((?:[a-z]+-)*(?:foreground|background))\b/)?.[1];
53
+ }
54
+
55
+ /** The `bg-*` and `border-*` colour utilities of a root class string, sorted. */
56
+ function surfaceClasses(cls: string): string[] {
57
+ return cls
58
+ .split(/\s+/)
59
+ .filter((c) => /^bg-/.test(c) || (/^border-/.test(c) && c !== "border"))
60
+ .sort();
61
+ }
62
+
63
+ describe("the chip's axes", () => {
64
+ // A chip and a badge sitting side by side in one row mean the same thing by
65
+ // the same colour. A private colour list is how the two drift apart.
66
+ test("share Badge's colours and sizes", () => {
67
+ expect([...CHIP_COLORS]).toEqual([...BADGE_COLORS]);
68
+ expect([...CHIP_SIZES]).toEqual([...BADGE_SIZES]);
69
+ });
70
+
71
+ // `solid` is what selection paints, so a resting solid chip would be
72
+ // indistinguishable from a selected one.
73
+ test("rest on soft or outline only — solid is reserved for selected", () => {
74
+ expect([...CHIP_VARIANTS]).toEqual(["soft", "outline"]);
75
+ });
76
+ });
77
+
78
+ describe("chipVariants root slot", () => {
79
+ test("defaults to the soft default md chip", () => {
80
+ const cls = chipVariants().root();
81
+ expect(cls).toContain("bg-muted");
82
+ expect(cls).toContain("px-3");
83
+ });
84
+
85
+ test("never stretches and is a capsule, at every size", () => {
86
+ for (const size of CHIP_SIZES) {
87
+ const cls = chipVariants({ size }).root();
88
+ expect(cls).toContain("self-start");
89
+ expect(cls).toContain("rounded-full");
90
+ }
91
+ });
92
+
93
+ // Selecting a chip must not make it wider — in a wrapping row that would
94
+ // reflow every chip after it.
95
+ test("reserves the border in every state, so selection never resizes the box", () => {
96
+ for (const variant of CHIP_VARIANTS) {
97
+ for (const isSelected of [false, true]) {
98
+ expect(chipVariants({ variant, isSelected }).root()).toMatch(/\bborder\b/);
99
+ }
100
+ }
101
+ });
102
+
103
+ test("clips, so a pressed chip fades to the edge of its own box", () => {
104
+ expect(chipVariants().root()).toContain("overflow-hidden");
105
+ });
106
+
107
+ // Tone is shared with Badge: a resting chip is painted exactly as the
108
+ // badge of the same variant and colour is.
109
+ test("paints a resting chip as the matching badge", () => {
110
+ for (const variant of CHIP_VARIANTS) {
111
+ for (const color of CHIP_COLORS) {
112
+ const chip = chipVariants({ variant, color });
113
+ const badge = badgeVariants({ variant, color });
114
+ expect(surfaceClasses(chip.root())).toEqual(surfaceClasses(badge.root()));
115
+ expect(labelColorToken(chip.label())).toBe(labelColorToken(badge.label()));
116
+ }
117
+ }
118
+ });
119
+
120
+ test("paints a selected chip as a solid fill in its colour", () => {
121
+ for (const color of ["primary", "success", "warning", "destructive", "info"] as const) {
122
+ for (const variant of CHIP_VARIANTS) {
123
+ const slots = chipVariants({ variant, color, isSelected: true });
124
+ expect(slots.root()).toContain(`bg-${color}`);
125
+ expect(slots.root()).toContain(`border-${color}`);
126
+ expect(slots.label()).toContain(`text-${color}-foreground`);
127
+ }
128
+ }
129
+ });
130
+
131
+ // Badge's solid default is `secondary`, which this theme sets a hair from
132
+ // `muted` — a selected default chip would look unselected. It inverts
133
+ // instead.
134
+ test("inverts a selected default chip rather than tinting it", () => {
135
+ const slots = chipVariants({ color: "default", isSelected: true });
136
+ expect(slots.root()).toContain("bg-foreground");
137
+ expect(slots.label()).toContain("text-background");
138
+ });
139
+
140
+ test("reads the same when selected, whatever it rested on", () => {
141
+ for (const color of CHIP_COLORS) {
142
+ const soft = chipVariants({ variant: "soft", color, isSelected: true });
143
+ const outline = chipVariants({ variant: "outline", color, isSelected: true });
144
+ expect(surfaceClasses(soft.root())).toEqual(surfaceClasses(outline.root()));
145
+ expect(soft.label()).toBe(outline.label());
146
+ }
147
+ });
148
+
149
+ test("gives every surface and colour pair a distinct treatment", () => {
150
+ const seen = new Set<string>();
151
+ for (const surface of CHIP_SURFACES) {
152
+ for (const color of CHIP_COLORS) {
153
+ const slots =
154
+ surface === "selected"
155
+ ? chipVariants({ color, isSelected: true })
156
+ : chipVariants({ color, isSelected: false, variant: surface });
157
+ seen.add(`${surfaceClasses(slots.root()).join(" ")}|${labelColorToken(slots.label())}`);
158
+ }
159
+ }
160
+ expect(seen.size).toBe(CHIP_SURFACES.length * CHIP_COLORS.length);
161
+ });
162
+
163
+ test("adds the disabled treatment only when disabled", () => {
164
+ expect(chipVariants({ isDisabled: true }).root()).toContain("opacity-50");
165
+ expect(chipVariants({ isDisabled: false }).root()).not.toContain("opacity-50");
166
+ });
167
+
168
+ test("merges an incoming className last", () => {
169
+ expect(chipVariants().root({ className: "bg-info" })).toContain("bg-info");
170
+ expect(chipVariants().root({ className: "bg-info" })).not.toContain("bg-muted");
171
+ });
172
+ });
173
+
174
+ describe("chipVariants size axis", () => {
175
+ test("scales horizontal padding, vertical padding and gap together", () => {
176
+ const roots = CHIP_SIZES.map((size) => chipVariants({ size }).root());
177
+ for (const scale of [roots.map(paddingX), roots.map(paddingY), roots.map(gapOf)]) {
178
+ expect(scale.every(Number.isFinite)).toBe(true);
179
+ expect(new Set(scale).size).toBe(CHIP_SIZES.length);
180
+ expect([...scale]).toEqual([...scale].sort((a, b) => a - b));
181
+ }
182
+ });
183
+
184
+ // A chip is something to aim at, so it is roomier than the badge of the
185
+ // same size — never smaller.
186
+ test("pads at least as generously as the badge of the same size", () => {
187
+ for (const size of CHIP_SIZES) {
188
+ const chip = chipVariants({ size }).root();
189
+ const badge = badgeVariants({ size }).root();
190
+ expect(paddingX(chip)).toBeGreaterThanOrEqual(paddingX(badge));
191
+ expect(paddingY(chip)).toBeGreaterThanOrEqual(paddingY(badge));
192
+ }
193
+ });
194
+
195
+ test("takes no height, so the label grows with OS font scaling", () => {
196
+ for (const size of CHIP_SIZES) {
197
+ expect(chipVariants({ size }).root()).not.toMatch(/\bh-/);
198
+ }
199
+ });
200
+
201
+ test("scales label text with size, at medium weight", () => {
202
+ const labels = CHIP_SIZES.map((size) => chipVariants({ size }).label());
203
+ const steps = labels.map(textStep);
204
+ expect(steps.every((step) => step >= 0)).toBe(true);
205
+ expect([...steps]).toEqual([...steps].sort((a, b) => a - b));
206
+ for (const label of labels) expect(label).toContain("font-medium");
207
+ });
208
+
209
+ test("matches Badge's icon step at every size", () => {
210
+ for (const size of CHIP_SIZES) {
211
+ const step = iconStep(chipVariants({ size }).icon());
212
+ expect(step).toBeGreaterThanOrEqual(0);
213
+ expect(step).toBe(iconStep(badgeVariants({ size }).icon()));
214
+ }
215
+ });
216
+
217
+ test("the label slot carries the text colour, which the root must not", () => {
218
+ for (const variant of CHIP_VARIANTS) {
219
+ for (const color of CHIP_COLORS) {
220
+ for (const isSelected of [false, true]) {
221
+ const slots = chipVariants({ variant, color, isSelected });
222
+ expect(labelColorToken(slots.label())).toBeDefined();
223
+ expect(slots.root()).not.toMatch(/\btext-(?!center\b)/);
224
+ }
225
+ }
226
+ }
227
+ });
228
+ });
229
+
230
+ describe("resolveChipSurface", () => {
231
+ test("selection wins over the resting variant", () => {
232
+ expect(resolveChipSurface({ variant: "soft", isSelected: true })).toBe("selected");
233
+ expect(resolveChipSurface({ variant: "outline", isSelected: true })).toBe("selected");
234
+ });
235
+
236
+ test("an unselected chip rests on its variant", () => {
237
+ expect(resolveChipSurface({ variant: "soft", isSelected: false })).toBe("soft");
238
+ expect(resolveChipSurface({ variant: "outline", isSelected: false })).toBe("outline");
239
+ });
240
+ });
241
+
242
+ describe("CHIP_FOREGROUND_TOKEN", () => {
243
+ test("reuses Badge's tokens for the resting surfaces", () => {
244
+ expect(CHIP_FOREGROUND_TOKEN.soft).toEqual(BADGE_FOREGROUND_TOKEN.soft);
245
+ expect(CHIP_FOREGROUND_TOKEN.outline).toEqual(BADGE_FOREGROUND_TOKEN.outline);
246
+ });
247
+
248
+ // A composed Icon sits beside the label; two maps that can drift is how a
249
+ // chip ends up with a grey glyph beside white text.
250
+ test("matches the colour its own label slot resolves to, in every state", () => {
251
+ for (const variant of CHIP_VARIANTS) {
252
+ for (const color of CHIP_COLORS) {
253
+ for (const isSelected of [false, true]) {
254
+ const label = chipVariants({ variant, color, isSelected }).label();
255
+ expect(labelColorToken(label)).toBe(resolveChipForegroundToken({ variant, color, isSelected }));
256
+ }
257
+ }
258
+ }
259
+ });
260
+
261
+ test("names only tokens both themes declare", () => {
262
+ for (const surface of CHIP_SURFACES) {
263
+ for (const color of CHIP_COLORS) {
264
+ const token = CHIP_FOREGROUND_TOKEN[surface][color];
265
+ expect(LIGHT.has(token)).toBe(true);
266
+ expect(DARK.has(token)).toBe(true);
267
+ }
268
+ }
269
+ });
270
+ });
271
+
272
+ describe("resolveChipMode", () => {
273
+ const noop = () => {};
274
+
275
+ test("a chip with nothing to do stays a plain view", () => {
276
+ expect(resolveChipMode({})).toBe("static");
277
+ });
278
+
279
+ test("a press handler makes it a button", () => {
280
+ expect(resolveChipMode({ onPress: noop })).toBe("button");
281
+ expect(resolveChipMode({ onLongPress: noop })).toBe("button");
282
+ });
283
+
284
+ // Setting `isSelected` — even to false — says the chip has an on state, and
285
+ // a screen reader has to hear which one it is in.
286
+ test("any selection prop makes it a toggle, whatever else is set", () => {
287
+ expect(resolveChipMode({ isSelected: false })).toBe("toggle");
288
+ expect(resolveChipMode({ isSelected: true })).toBe("toggle");
289
+ expect(resolveChipMode({ defaultSelected: false })).toBe("toggle");
290
+ expect(resolveChipMode({ onSelectedChange: noop })).toBe("toggle");
291
+ expect(resolveChipMode({ isSelected: false, onPress: noop })).toBe("toggle");
292
+ });
293
+ });
294
+
295
+ describe("hit slop", () => {
296
+ // Chips wrap in rows eight points apart (`gap-2`). More than four points
297
+ // of vertical slop reaches into the next row and makes a tap ambiguous.
298
+ test("the root never reaches past half a gap-2 row gap", () => {
299
+ for (const size of CHIP_SIZES) {
300
+ const slop = CHIP_HIT_SLOP[size];
301
+ expect(slop.top).toBeLessThanOrEqual(4);
302
+ expect(slop.bottom).toBeLessThanOrEqual(4);
303
+ expect(slop.left).toBe(0);
304
+ expect(slop.right).toBe(0);
305
+ }
306
+ });
307
+
308
+ test("the close glyph grows its target with the chip", () => {
309
+ const slops = CHIP_SIZES.map((size) => CHIP_CLOSE_HIT_SLOP[size]);
310
+ expect(slops.every((slop) => slop > 0)).toBe(true);
311
+ expect([...slops]).toEqual([...slops].sort((a, b) => a - b));
312
+ });
313
+ });
314
+
315
+ describe("resolveChipCloseExposure", () => {
316
+ test("nothing to expose without onClose", () => {
317
+ for (const mode of ["static", "button", "toggle"] as const) {
318
+ expect(resolveChipCloseExposure({ mode, hasClose: false })).toBe("none");
319
+ }
320
+ });
321
+
322
+ // A static chip is not an accessibility element, so the close control is
323
+ // reachable on its own and needs nothing more.
324
+ test("a static chip leaves the close control as its own element", () => {
325
+ expect(resolveChipCloseExposure({ mode: "static", hasClose: true })).toBe("element");
326
+ });
327
+
328
+ // iOS folds every descendant of an accessible view into one element, so a
329
+ // close control inside a pressable chip is unreachable by swipe — it becomes
330
+ // an action on the chip instead.
331
+ test("a pressable chip offers removal as an accessibility action", () => {
332
+ expect(resolveChipCloseExposure({ mode: "button", hasClose: true })).toBe("action");
333
+ expect(resolveChipCloseExposure({ mode: "toggle", hasClose: true })).toBe("action");
334
+ });
335
+ });
@@ -0,0 +1,321 @@
1
+ import type { VariantProps } from "tailwind-variants";
2
+ import { tv } from "../../lib/tv";
3
+ import {
4
+ BADGE_COLORS,
5
+ BADGE_FOREGROUND_TOKEN,
6
+ BADGE_SIZES,
7
+ type BadgeColor,
8
+ type BadgeSize,
9
+ } from "../badge/badge.variants";
10
+
11
+ /**
12
+ * How an unselected chip is painted.
13
+ *
14
+ * Two of Badge's four, and deliberately not `solid`: a solid fill is what
15
+ * selection paints, so a resting solid chip would read as already selected.
16
+ * `ghost` is left out because a chip with no fill and no border has no edge to
17
+ * aim at.
18
+ */
19
+ export const CHIP_VARIANTS = ["soft", "outline"] as const;
20
+
21
+ /** What the chip means. Badge's list, so a chip and a badge agree on colour. */
22
+ export const CHIP_COLORS = BADGE_COLORS;
23
+
24
+ /** Badge's sizes, so a chip and a badge of one size carry the same glyph. */
25
+ export const CHIP_SIZES = BADGE_SIZES;
26
+
27
+ /**
28
+ * Every surface a chip can wear: its two resting variants, and selection.
29
+ *
30
+ * Selection is a surface rather than a modifier because it replaces the resting
31
+ * paint wholesale — a selected chip looks the same whichever variant it rests on.
32
+ */
33
+ export const CHIP_SURFACES = [...CHIP_VARIANTS, "selected"] as const;
34
+
35
+ export type ChipVariant = (typeof CHIP_VARIANTS)[number];
36
+ export type ChipColor = BadgeColor;
37
+ export type ChipSize = BadgeSize;
38
+ export type ChipSurface = (typeof CHIP_SURFACES)[number];
39
+
40
+ /**
41
+ * What a chip does when touched — decided by which props it was given.
42
+ *
43
+ * - `static` — a tag. A plain `View`, announced as text.
44
+ * - `button` — `onPress` or `onLongPress`, and no selection.
45
+ * - `toggle` — any selection prop. Announced with its selected state.
46
+ */
47
+ export type ChipMode = "static" | "button" | "toggle";
48
+
49
+ /**
50
+ * Theme token whose value gives an `Icon` composed into the chip its colour.
51
+ *
52
+ * The resting surfaces are Badge's own entries, not copies — a soft `success`
53
+ * chip and a soft `success` badge are the same shade by construction. Only
54
+ * `selected` is new.
55
+ *
56
+ * A selected `default` chip inverts to `foreground`/`background` rather than
57
+ * taking Badge's solid `secondary`: this theme sets `secondary` a hair from
58
+ * `muted`, so a selected default chip would look exactly like a resting one.
59
+ */
60
+ export const CHIP_FOREGROUND_TOKEN: Record<ChipSurface, Record<ChipColor, string>> = {
61
+ soft: BADGE_FOREGROUND_TOKEN.soft,
62
+ outline: BADGE_FOREGROUND_TOKEN.outline,
63
+ selected: {
64
+ default: "background",
65
+ primary: "primary-foreground",
66
+ success: "success-foreground",
67
+ warning: "warning-foreground",
68
+ destructive: "destructive-foreground",
69
+ info: "info-foreground",
70
+ },
71
+ };
72
+
73
+ /**
74
+ * Vertical slop that brings the root towards the 44pt minimum touch target.
75
+ *
76
+ * Capped at four points and never horizontal: chips wrap in rows with a
77
+ * `gap-2` between them, and slop past half that gap reaches into the
78
+ * neighbouring chip, making a tap between two of them ambiguous — worse than a
79
+ * target that is merely adequate.
80
+ */
81
+ export const CHIP_HIT_SLOP: Record<ChipSize, { top: number; bottom: number; left: number; right: number }> = {
82
+ sm: { top: 4, bottom: 4, left: 0, right: 0 },
83
+ md: { top: 4, bottom: 4, left: 0, right: 0 },
84
+ lg: { top: 2, bottom: 2, left: 0, right: 0 },
85
+ };
86
+
87
+ /**
88
+ * Slop around the close glyph, on every side.
89
+ *
90
+ * The glyph is 14–18 points; this is what makes it something a thumb can find.
91
+ * It reaches into the chip's own padding and past its edge — both are the
92
+ * chip's space, and the inner detector claims the tap before the root sees it.
93
+ */
94
+ export const CHIP_CLOSE_HIT_SLOP: Record<ChipSize, number> = { sm: 8, md: 10, lg: 12 };
95
+
96
+ /**
97
+ * Styling for every part of a chip.
98
+ *
99
+ * Shares Badge's two axes and its tones — a resting chip is painted exactly as
100
+ * the badge of the same variant and colour, a test pins every cell — and adds a
101
+ * third, `isSelected`, which replaces the resting paint with a solid fill in the
102
+ * chip's colour. The selected cells come **after** the resting ones, so
103
+ * tailwind-merge lets them win whatever the chip rests on.
104
+ *
105
+ * `border border-transparent` sits in the base for the reason it does in Badge,
106
+ * with more at stake: a chip toggles in place inside a wrapping row, and a box
107
+ * that grew two points on selection would reflow every chip after it.
108
+ *
109
+ * Padding is a step roomier than Badge at every size — a chip is aimed at, a
110
+ * badge is only read — and still never a height, so the label grows with OS
111
+ * font scaling. The icon step is Badge's, so a glyph moves between the two
112
+ * without changing size.
113
+ *
114
+ * Free of React Native imports so it stays unit-testable. See AGENTS.md.
115
+ */
116
+ export const chipVariants = tv({
117
+ slots: {
118
+ root: "flex-row items-center justify-center self-start overflow-hidden rounded-full border border-transparent",
119
+ label: "text-center font-medium",
120
+ startContent: "items-center justify-center",
121
+ endContent: "items-center justify-center",
122
+ closeButton: "items-center justify-center rounded-full",
123
+ /** Edge length an `Icon` composed into the chip inherits. */
124
+ icon: "",
125
+ },
126
+ variants: {
127
+ variant: {
128
+ soft: {},
129
+ outline: { root: "bg-transparent" },
130
+ },
131
+ color: {
132
+ default: {},
133
+ primary: {},
134
+ success: {},
135
+ warning: {},
136
+ destructive: {},
137
+ info: {},
138
+ },
139
+ size: {
140
+ sm: {
141
+ root: "gap-1 px-2.5 py-1",
142
+ label: "text-xs",
143
+ closeButton: "-mr-0.5",
144
+ icon: "size-icon-xs",
145
+ },
146
+ md: {
147
+ root: "gap-1.5 px-3 py-1.5",
148
+ label: "text-sm",
149
+ closeButton: "-mr-1",
150
+ icon: "size-icon-sm",
151
+ },
152
+ lg: {
153
+ root: "gap-2 px-4 py-2",
154
+ label: "text-base",
155
+ closeButton: "-mr-1.5",
156
+ icon: "size-icon-md",
157
+ },
158
+ },
159
+ // The empty `false` branches are load-bearing typing, not placeholders.
160
+ // See the note in button.variants.ts.
161
+ isSelected: { true: {}, false: {} },
162
+ isDisabled: { true: { root: "opacity-50" }, false: {} },
163
+ },
164
+ compoundVariants: [
165
+ { variant: "soft", color: "default", class: { root: "bg-muted", label: "text-muted-foreground" } },
166
+ { variant: "soft", color: "primary", class: { root: "bg-tertiary", label: "text-tertiary-foreground" } },
167
+ {
168
+ variant: "soft",
169
+ color: "success",
170
+ class: { root: "bg-success-soft", label: "text-success-soft-foreground" },
171
+ },
172
+ {
173
+ variant: "soft",
174
+ color: "warning",
175
+ class: { root: "bg-warning-soft", label: "text-warning-soft-foreground" },
176
+ },
177
+ {
178
+ variant: "soft",
179
+ color: "destructive",
180
+ class: { root: "bg-destructive-soft", label: "text-destructive-soft-foreground" },
181
+ },
182
+ { variant: "soft", color: "info", class: { root: "bg-info-soft", label: "text-info-soft-foreground" } },
183
+
184
+ { variant: "outline", color: "default", class: { root: "border-border", label: "text-muted-foreground" } },
185
+ { variant: "outline", color: "primary", class: { root: "border-primary", label: "text-foreground" } },
186
+ {
187
+ variant: "outline",
188
+ color: "success",
189
+ class: { root: "border-success", label: "text-success-soft-foreground" },
190
+ },
191
+ {
192
+ variant: "outline",
193
+ color: "warning",
194
+ class: { root: "border-warning", label: "text-warning-soft-foreground" },
195
+ },
196
+ {
197
+ variant: "outline",
198
+ color: "destructive",
199
+ class: { root: "border-destructive", label: "text-destructive-soft-foreground" },
200
+ },
201
+ { variant: "outline", color: "info", class: { root: "border-info", label: "text-info-soft-foreground" } },
202
+
203
+ {
204
+ isSelected: true,
205
+ color: "default",
206
+ class: { root: "border-foreground bg-foreground", label: "text-background" },
207
+ },
208
+ {
209
+ isSelected: true,
210
+ color: "primary",
211
+ class: { root: "border-primary bg-primary", label: "text-primary-foreground" },
212
+ },
213
+ {
214
+ isSelected: true,
215
+ color: "success",
216
+ class: { root: "border-success bg-success", label: "text-success-foreground" },
217
+ },
218
+ {
219
+ isSelected: true,
220
+ color: "warning",
221
+ class: { root: "border-warning bg-warning", label: "text-warning-foreground" },
222
+ },
223
+ {
224
+ isSelected: true,
225
+ color: "destructive",
226
+ class: { root: "border-destructive bg-destructive", label: "text-destructive-foreground" },
227
+ },
228
+ {
229
+ isSelected: true,
230
+ color: "info",
231
+ class: { root: "border-info bg-info", label: "text-info-foreground" },
232
+ },
233
+ ],
234
+ defaultVariants: {
235
+ variant: "soft",
236
+ color: "default",
237
+ size: "md",
238
+ isSelected: false,
239
+ isDisabled: false,
240
+ },
241
+ });
242
+
243
+ /**
244
+ * The surface a chip wears — its resting variant, or `selected`.
245
+ *
246
+ * Pure, so the icon colour and the label colour are chosen by one rule that
247
+ * `bun test` reaches. See AGENTS.md.
248
+ */
249
+ export function resolveChipSurface({
250
+ variant,
251
+ isSelected,
252
+ }: {
253
+ variant: ChipVariant;
254
+ isSelected: boolean;
255
+ }): ChipSurface {
256
+ return isSelected ? "selected" : variant;
257
+ }
258
+
259
+ /** The theme token an `Icon` composed into the chip takes its colour from. */
260
+ export function resolveChipForegroundToken({
261
+ variant,
262
+ color,
263
+ isSelected,
264
+ }: {
265
+ variant: ChipVariant;
266
+ color: ChipColor;
267
+ isSelected: boolean;
268
+ }): string {
269
+ return CHIP_FOREGROUND_TOKEN[resolveChipSurface({ isSelected, variant })][color];
270
+ }
271
+
272
+ /**
273
+ * What the root renders as — see {@link ChipMode}.
274
+ *
275
+ * A chip is content until it is given something to do, as a badge is. Any
276
+ * selection prop — `isSelected` even when `false`, `defaultSelected`,
277
+ * `onSelectedChange` — says the chip has an on state, and a screen reader must
278
+ * hear which one it is in, so selection outranks a bare press handler.
279
+ *
280
+ * `onClose` is deliberately not read: the dismiss control is its own
281
+ * pressable, so a chip that can only be removed still has an inert root.
282
+ */
283
+ export function resolveChipMode({
284
+ isSelected,
285
+ defaultSelected,
286
+ onSelectedChange,
287
+ onPress,
288
+ onLongPress,
289
+ }: {
290
+ isSelected?: boolean;
291
+ defaultSelected?: boolean;
292
+ onSelectedChange?: (isSelected: boolean) => void;
293
+ onPress?: () => void;
294
+ onLongPress?: () => void;
295
+ }): ChipMode {
296
+ if (isSelected !== undefined || defaultSelected !== undefined || onSelectedChange !== undefined) return "toggle";
297
+ if (onPress !== undefined || onLongPress !== undefined) return "button";
298
+ return "static";
299
+ }
300
+
301
+ /**
302
+ * How the remove control reaches assistive technology.
303
+ *
304
+ * - `none` — there is no `onClose`.
305
+ * - `element` — a static chip is not an accessibility element itself, so the
306
+ * close control is announced on its own, as a button.
307
+ * - `action` — a pressable chip is one accessibility element, and iOS folds its
308
+ * descendants into it: the close control is unreachable by swipe and its
309
+ * label is read as part of the chip's. It is hidden, and removal is offered as
310
+ * a `remove` accessibility action on the chip instead — VoiceOver's Actions
311
+ * rotor, TalkBack's actions menu.
312
+ */
313
+ export type ChipCloseExposure = "none" | "element" | "action";
314
+
315
+ /** See {@link ChipCloseExposure}. Pure, so `bun test` reaches it. */
316
+ export function resolveChipCloseExposure({ mode, hasClose }: { mode: ChipMode; hasClose: boolean }): ChipCloseExposure {
317
+ if (!hasClose) return "none";
318
+ return mode === "static" ? "element" : "action";
319
+ }
320
+
321
+ export type ChipVariantProps = VariantProps<typeof chipVariants>;
@@ -0,0 +1,24 @@
1
+ export { Chip, type ChipProps } from "./chip";
2
+ export { type ChipContextValue, ChipProvider, useChip, useChipContext } from "./chip.context";
3
+ export type { ChipSlotProps } from "./chip.types";
4
+ export {
5
+ CHIP_CLOSE_HIT_SLOP,
6
+ CHIP_COLORS,
7
+ CHIP_FOREGROUND_TOKEN,
8
+ CHIP_HIT_SLOP,
9
+ CHIP_SIZES,
10
+ CHIP_SURFACES,
11
+ CHIP_VARIANTS,
12
+ type ChipColor,
13
+ type ChipMode,
14
+ type ChipSize,
15
+ type ChipSurface,
16
+ type ChipVariant,
17
+ type ChipVariantProps,
18
+ chipVariants,
19
+ resolveChipForegroundToken,
20
+ resolveChipMode,
21
+ resolveChipSurface,
22
+ } from "./chip.variants";
23
+ export type { ChipCloseButtonProps } from "./chip-close-button";
24
+ export type { ChipLabelProps } from "./chip-label";