@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
@@ -0,0 +1,62 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { readdirSync, readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { extendTailwindMerge } from "tailwind-merge";
5
+ import { type CustomClassGroupId, TW_MERGE_CONFIG } from "../styles/tokens";
6
+ import { cn } from "./cn";
7
+
8
+ /**
9
+ * `cn` against the tailwind-merge it replaced, on this library's own classes.
10
+ *
11
+ * `cn` promises tailwind-merge's output for every input; this holds it to that
12
+ * on the strings that matter here — every class literal in a `*.variants.ts`,
13
+ * merged pairwise with a caller-style override, through the same
14
+ * `TW_MERGE_CONFIG`. tailwind-merge stays installed as `tailwind-variants`'
15
+ * peer, which is what makes the reference available.
16
+ */
17
+ const reference = extendTailwindMerge<CustomClassGroupId>(TW_MERGE_CONFIG);
18
+
19
+ function variantFiles(dir: string): string[] {
20
+ return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
21
+ const path = join(dir, entry.name);
22
+ if (entry.isDirectory()) return variantFiles(path);
23
+ return entry.name.endsWith(".variants.ts") ? [path] : [];
24
+ });
25
+ }
26
+
27
+ const literals = [
28
+ ...new Set(
29
+ variantFiles(join(import.meta.dir, "../components")).flatMap((file) =>
30
+ [...readFileSync(file, "utf8").matchAll(/"([a-z0-9!:[\]/.\-_% ]+)"/g)]
31
+ .map((match) => match[1] ?? "")
32
+ .filter((value) => value.includes("-"))
33
+ )
34
+ ),
35
+ ];
36
+
37
+ const OVERRIDES = [
38
+ "p-4",
39
+ "h-12",
40
+ "rounded-none",
41
+ "bg-destructive",
42
+ "text-sm",
43
+ "text-primary",
44
+ "opacity-50",
45
+ "h-button-sm",
46
+ ];
47
+
48
+ describe("cn matches tailwind-merge on the library's classes", () => {
49
+ test("found a real corpus", () => {
50
+ expect(literals.length).toBeGreaterThan(100);
51
+ });
52
+
53
+ test("every literal alone", () => {
54
+ for (const value of literals) expect(cn(value)).toBe(reference(value));
55
+ });
56
+
57
+ test("every literal under a caller override", () => {
58
+ for (const value of literals) {
59
+ for (const override of OVERRIDES) expect(cn(value, override)).toBe(reference(value, override));
60
+ }
61
+ });
62
+ });
@@ -134,3 +134,17 @@ describe("cn with the semantic size tokens", () => {
134
134
  expect(cn("border-input", "text-input-md")).toBe("border-input text-input-md");
135
135
  });
136
136
  });
137
+
138
+ describe("cn with Uniwind's corner-curve utilities", () => {
139
+ // `border-continuous` is a Uniwind utility, not a Tailwind one, and a bare
140
+ // tailwind-merge files it under border *colour* — so it and `border-border`
141
+ // were judged to conflict and one of them was silently dropped.
142
+ test("a corner curve and a border colour both survive", () => {
143
+ expect(cn("border-continuous", "border-border")).toBe("border-continuous border-border");
144
+ expect(cn("border-border", "border-continuous")).toBe("border-border border-continuous");
145
+ });
146
+
147
+ test("one corner curve replaces the other", () => {
148
+ expect(cn("border-continuous", "border-circular")).toBe("border-circular");
149
+ });
150
+ });
package/src/lib/cn.ts CHANGED
@@ -1,11 +1,16 @@
1
- import { type ClassValue, clsx } from "clsx";
2
- import { extendTailwindMerge } from "tailwind-merge";
1
+ import type { ClassValue } from "cn";
2
+ import { createCn } from "cn/config";
3
3
  import { TW_MERGE_CONFIG } from "../styles/tokens";
4
4
 
5
5
  /**
6
- * tailwind-merge, taught the semantic size tokens from `tokens.css`.
6
+ * Class joining and conflict resolution, taught the semantic size tokens from `tokens.css`.
7
7
  *
8
- * Registering them is load-bearing rather than tidiness. tailwind-merge only
8
+ * Built on `cn` (shadcn-ui/cn), which replaces `clsx` + `tailwind-merge` with one
9
+ * engine: the same join semantics, the same merge output, and much cheaper on the
10
+ * repeated calls a render loop makes. `createCn` takes tailwind-merge's own
11
+ * `{ extend }` shape, so `TW_MERGE_CONFIG` is passed unchanged.
12
+ *
13
+ * Registering the tokens is load-bearing rather than tidiness. The merger only
9
14
  * treats two classes as conflicting when it recognises both as members of the
10
15
  * same group, and `button-md` is not a value it knows. Left unregistered,
11
16
  * `cn("h-button-md", "h-12")` returns *both* classes: they each resolve to a
@@ -15,7 +20,7 @@ import { TW_MERGE_CONFIG } from "../styles/tokens";
15
20
  *
16
21
  * `tv()` needs the same treatment for its own merger — see `lib/tv.ts`.
17
22
  */
18
- const twMerge = extendTailwindMerge(TW_MERGE_CONFIG);
23
+ const merge = createCn(TW_MERGE_CONFIG);
19
24
 
20
25
  /**
21
26
  * Merges class names and resolves Tailwind conflicts so the last utility wins.
@@ -25,5 +30,5 @@ const twMerge = extendTailwindMerge(TW_MERGE_CONFIG);
25
30
  * `className` through this before handing it to a component.
26
31
  */
27
32
  export function cn(...inputs: ClassValue[]): string {
28
- return twMerge(clsx(inputs));
33
+ return merge(...inputs);
29
34
  }
@@ -0,0 +1,54 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { readdirSync, readFileSync } from "node:fs";
3
+ import { join, relative } from "node:path";
4
+
5
+ const SRC = import.meta.dirname;
6
+ const PACKAGE_JSON = join(SRC, "..", "package.json");
7
+
8
+ /** The sheet library `BottomSheet` used to wrap. */
9
+ const FORBIDDEN = "@gorhom/";
10
+
11
+ /**
12
+ * Nothing in this package names the sheet library it used to wrap.
13
+ *
14
+ * `BottomSheet` is built on `@delacour/react-native-bottom-sheet` now, and the
15
+ * old library left as a peer with it. A stray import would make every consumer
16
+ * install a package nobody uses; a stray mention in a doc would send a reader
17
+ * to a workaround that no longer applies. Both read the same to `grep`, so this
18
+ * reads the whole tree as text, source and docs alike.
19
+ *
20
+ * Like `docs.test.ts`, this imports nothing from the tree — `bun test` cannot
21
+ * parse React Native's Flow-typed source, and it does not need to.
22
+ */
23
+ function sourceFiles(dir: string): string[] {
24
+ return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
25
+ const path = join(dir, entry.name);
26
+ if (entry.isDirectory()) return sourceFiles(path);
27
+ return /\.(tsx?|md|css)$/.test(entry.name) && entry.name !== "no-gorhom.test.ts" ? [path] : [];
28
+ });
29
+ }
30
+
31
+ const FILES = sourceFiles(SRC);
32
+
33
+ describe("no gorhom", () => {
34
+ // An empty walk would let the assertion below pass on nothing.
35
+ test("finds the source tree", () => {
36
+ expect(FILES.length).toBeGreaterThan(50);
37
+ });
38
+
39
+ test("no source file or doc mentions the old sheet library", () => {
40
+ const offenders = FILES.filter((path) => readFileSync(path, "utf-8").includes(FORBIDDEN)).map((path) =>
41
+ relative(SRC, path)
42
+ );
43
+ expect(offenders).toEqual([]);
44
+ });
45
+
46
+ test("it is not a dependency or peer of the package", () => {
47
+ const pkg = JSON.parse(readFileSync(PACKAGE_JSON, "utf-8")) as Record<string, Record<string, string> | undefined>;
48
+ const declared = ["dependencies", "peerDependencies", "devDependencies", "optionalDependencies"].flatMap((field) =>
49
+ Object.keys(pkg[field] ?? {})
50
+ );
51
+ expect(declared.filter((name) => name.startsWith(FORBIDDEN))).toEqual([]);
52
+ expect(Object.keys(pkg.peerDependenciesMeta ?? {}).filter((name) => name.startsWith(FORBIDDEN))).toEqual([]);
53
+ });
54
+ });
@@ -83,6 +83,7 @@ describe("the geometry block", () => {
83
83
  "--spacing-navbar-row",
84
84
  "--spacing-screen-gutter",
85
85
  ...(["sm", "md", "lg"] as const).map((step) => `--spacing-chart-${step}`),
86
+ ...(["sm", "md", "lg"] as const).map((step) => `--spacing-fab-${step}`),
86
87
  ];
87
88
 
88
89
  for (const name of required) {
@@ -350,6 +350,10 @@
350
350
  --spacing-chart-sm: 160px;
351
351
  --spacing-chart-md: 220px;
352
352
  --spacing-chart-lg: 280px;
353
+
354
+ --spacing-fab-sm: 44px;
355
+ --spacing-fab-md: 56px;
356
+ --spacing-fab-lg: 64px;
353
357
  }
354
358
  }
355
359
  }
@@ -106,6 +106,50 @@
106
106
  --spacing-chart-sm: 160px;
107
107
  --spacing-chart-md: 220px;
108
108
  --spacing-chart-lg: 280px;
109
+
110
+ /*
111
+ * The kicker: the tiny uppercase mono label that sits over a group, a
112
+ * screen or a field. Ten points at 0.2em tracking, named as a pair so
113
+ * `Text variant="kicker"` and a hand-written `text-kicker tracking-kicker`
114
+ * agree. Nothing reads it unless asked, so the default look is unchanged.
115
+ */
116
+ --text-kicker: 10px;
117
+ --tracking-kicker: 0.2em;
118
+
119
+ /*
120
+ * The etched material: a one-pixel highlight set into a surface's edge plus
121
+ * a very soft drop. Light puts the highlight on the bottom edge as a
122
+ * darkening, dark puts it on the top edge as a lightening, so a card reads
123
+ * as lit from above in both. They are two tokens because one `shadow-*`
124
+ * utility sets one `box-shadow` and a scheme flip needs `dark:`.
125
+ *
126
+ * `etched-primary` is the primary button's own: a white inset top edge at
127
+ * 16% that is the same in both schemes, because the fill under it is the
128
+ * dark one in light and invisible against the pale one in dark.
129
+ *
130
+ * `focus` is the three-point halo an etched field draws round its ring
131
+ * border. A neutral alpha rather than the ring colour: `color-mix` inside a
132
+ * `box-shadow` does not survive Uniwind's compile and draws nothing, and
133
+ * the ring is a neutral in every base ramp anyway.
134
+ *
135
+ * Opt-in only: no default component reads any of these.
136
+ */
137
+ --shadow-etched: inset 0 -1px 0 0 rgb(0 0 0 / 4%), 0 1px 2px 0 rgb(0 0 0 / 5%);
138
+ --shadow-etched-dark: inset 0 1px 0 0 rgb(255 255 255 / 6%), 0 1px 2px 0 rgb(0 0 0 / 30%);
139
+ --shadow-etched-primary: inset 0 1px 0 0 rgb(255 255 255 / 16%), 0 1px 2px 0 rgb(0 0 0 / 5%);
140
+ --shadow-focus: 0 0 0 3px rgb(0 0 0 / 10%);
141
+ --shadow-focus-dark: 0 0 0 3px rgb(255 255 255 / 14%);
142
+
143
+ /*
144
+ * A floating action button's footprint — the circle's edge, and the
145
+ * height of an extended one. A scale of its own rather than
146
+ * `--spacing-button-*`: a fab sits over content rather than in a row of
147
+ * controls, so it is a step larger, and retuning a toolbar's density must
148
+ * not move the one thing floating over the list.
149
+ */
150
+ --spacing-fab-sm: 44px;
151
+ --spacing-fab-md: 56px;
152
+ --spacing-fab-lg: 64px;
109
153
  }
110
154
 
111
155
  /*
@@ -6,10 +6,14 @@ import {
6
6
  BUTTON_SIZE_TOKENS,
7
7
  BUTTON_TEXT_TOKENS,
8
8
  CHART_SIZE_TOKENS,
9
+ ETCHED_SHADOW_TOKENS,
10
+ FAB_SIZE_TOKENS,
9
11
  ICON_SIZE_TOKENS,
10
12
  INPUT_SIZE_TOKENS,
11
13
  INPUT_TEXT_TOKENS,
14
+ KICKER_TOKEN,
12
15
  SCREEN_SIZE_TOKENS,
16
+ TW_MERGE_CONFIG,
13
17
  } from "./tokens";
14
18
 
15
19
  const CSS = readFileSync(join(import.meta.dirname, "tokens.css"), "utf-8");
@@ -44,6 +48,7 @@ describe("tokens.ts and tokens.css", () => {
44
48
  const registered = [
45
49
  ...BUTTON_SIZE_TOKENS,
46
50
  ...CHART_SIZE_TOKENS,
51
+ ...FAB_SIZE_TOKENS,
47
52
  ...ICON_SIZE_TOKENS,
48
53
  ...INPUT_SIZE_TOKENS,
49
54
  ...SCREEN_SIZE_TOKENS,
@@ -179,3 +184,54 @@ describe("the input scale", () => {
179
184
  }
180
185
  });
181
186
  });
187
+
188
+ describe("the etched material", () => {
189
+ test("declares every shadow the registry names, and no other", () => {
190
+ const declared = [...CSS.matchAll(/--shadow-([\w-]+):/g)].map((match) => match[1]).sort();
191
+ expect(declared).toEqual([...ETCHED_SHADOW_TOKENS].sort());
192
+ });
193
+
194
+ // An inset layer is what makes it a highlight rather than a drop shadow, and
195
+ // the soft outer layer is what lifts the edge off the page.
196
+ test("light and dark edges sit on opposite sides of the surface", () => {
197
+ expect(CSS).toMatch(/--shadow-etched:\s*inset 0 -1px/);
198
+ expect(CSS).toMatch(/--shadow-etched-dark:\s*inset 0 1px/);
199
+ expect(CSS).toMatch(/--shadow-etched-primary:\s*inset 0 1px 0 0 rgb\(255 255 255 \/ 16%\)/);
200
+ });
201
+
202
+ test("registers its shadows so a caller's shadow-* can replace them", () => {
203
+ expect(TW_MERGE_CONFIG.extend.theme.shadow).toEqual([...ETCHED_SHADOW_TOKENS]);
204
+ });
205
+ });
206
+
207
+ describe("the kicker type style", () => {
208
+ test("is ten points, tracked at 0.2em", () => {
209
+ expect(px("text-kicker")).toBe(10);
210
+ expect(CSS).toMatch(/--tracking-kicker:\s*0\.2em;/);
211
+ });
212
+
213
+ test("registers both tokens so text-kicker is read as a size, not a colour", () => {
214
+ expect(TW_MERGE_CONFIG.extend.theme.text).toContain(KICKER_TOKEN);
215
+ expect(TW_MERGE_CONFIG.extend.theme.tracking).toEqual([KICKER_TOKEN]);
216
+ });
217
+ });
218
+
219
+ describe("the fab scale", () => {
220
+ test("ascends in the order the registry lists it", () => {
221
+ const values = FAB_SIZE_TOKENS.map((token) => px(`spacing-${token}`));
222
+ expect(values).toEqual([...values].sort((a, b) => a - b));
223
+ expect(new Set(values).size).toBe(FAB_SIZE_TOKENS.length);
224
+ });
225
+
226
+ // The smallest fab is still a touch target, which the platform guidelines
227
+ // put at 44pt — the same floor the medium button sits on.
228
+ test("never drops below a touch target", () => {
229
+ expect(px("spacing-fab-sm")).toBeGreaterThanOrEqual(44);
230
+ });
231
+
232
+ // A fab's glyph indexes the shared icon scale: sm → icon-md, md and lg → icon-lg.
233
+ test("holds its glyph with room around it", () => {
234
+ expect(px("spacing-icon-md")).toBeLessThan(px("spacing-fab-sm") / 2);
235
+ expect(px("spacing-icon-lg")).toBeLessThan(px("spacing-fab-md") / 2);
236
+ });
237
+ });
@@ -59,6 +59,15 @@ export const INPUT_TEXT_TOKENS = ["input-sm", "input-md", "input-lg"] as const;
59
59
  */
60
60
  export const CHART_SIZE_TOKENS = ["chart-sm", "chart-md", "chart-lg"] as const;
61
61
 
62
+ /**
63
+ * Suffixes in the `--spacing-*` namespace: `size-fab-md`, `h-fab-md`.
64
+ *
65
+ * A floating action button's footprint — the circle's edge, or an extended
66
+ * one's height. Its own scale rather than the button's, so a fab can be
67
+ * retuned without moving every control in a form.
68
+ */
69
+ export const FAB_SIZE_TOKENS = ["fab-sm", "fab-md", "fab-lg"] as const;
70
+
62
71
  /**
63
72
  * The tailwind-merge extension both mergers in this package are built from.
64
73
  *
@@ -68,19 +77,38 @@ export const CHART_SIZE_TOKENS = ["chart-sm", "chart-md", "chart-lg"] as const;
68
77
  * `text-button-md` as an unrecognised utility — which is how a size token ends
69
78
  * up colliding with a colour and silently dropping it. One config, read by
70
79
  * `lib/cn.ts` and `lib/tv.ts`, so they cannot drift apart.
80
+ *
81
+ * `border-curve` is Uniwind's, not Tailwind's: `border-continuous` sets iOS's
82
+ * `borderCurve`. Unregistered, tailwind-merge reads it as a border *colour*,
83
+ * and `border-continuous border-border` loses one of the two.
71
84
  */
85
+ /** Names of the shadows the etched material declares in `tokens.css`. */
86
+ export const ETCHED_SHADOW_TOKENS = ["etched", "etched-dark", "etched-primary", "focus", "focus-dark"] as const;
87
+
88
+ /** The size and tracking token behind `Text variant="kicker"`; one name for both. */
89
+ export const KICKER_TOKEN = "kicker";
90
+
72
91
  export const TW_MERGE_CONFIG = {
73
92
  extend: {
93
+ classGroups: {
94
+ "border-curve": ["border-continuous", "border-circular"],
95
+ },
74
96
  theme: {
75
97
  radius: [...BUTTON_RADIUS_TOKENS],
76
98
  spacing: [
77
99
  ...BUTTON_SIZE_TOKENS,
78
100
  ...CHART_SIZE_TOKENS,
101
+ ...FAB_SIZE_TOKENS,
79
102
  ...ICON_SIZE_TOKENS,
80
103
  ...INPUT_SIZE_TOKENS,
81
104
  ...SCREEN_SIZE_TOKENS,
82
105
  ],
83
- text: [...BUTTON_TEXT_TOKENS, ...INPUT_TEXT_TOKENS],
106
+ shadow: [...ETCHED_SHADOW_TOKENS],
107
+ text: [...BUTTON_TEXT_TOKENS, ...INPUT_TEXT_TOKENS, KICKER_TOKEN],
108
+ tracking: [KICKER_TOKEN],
84
109
  },
85
110
  },
86
111
  } as const;
112
+
113
+ /** Class groups `TW_MERGE_CONFIG` adds, which tailwind-merge's extension has to be told about by name. */
114
+ export type CustomClassGroupId = keyof typeof TW_MERGE_CONFIG.extend.classGroups;
@@ -1,72 +0,0 @@
1
- import { Children, isValidElement, type ReactElement, type ReactNode, useMemo } from "react";
2
- import { BottomSheetPortalProvider } from "./bottom-sheet.context";
3
- import { BottomSheetOverlay } from "./bottom-sheet-overlay";
4
-
5
- export type BottomSheetPortalProps = {
6
- children?: ReactNode;
7
- };
8
-
9
- /**
10
- * Everything that draws above the app once the sheet is open.
11
- *
12
- * It renders no view of its own, and it does not portal anything either —
13
- * `BottomSheetModal` already hosts itself in the `BottomSheetModalProvider` that
14
- * `DelacourProvider` mounts innermost. What this part does is one thing:
15
- * **it lifts the `BottomSheet.Overlay` out of its own children** and publishes it
16
- * on context, so `BottomSheet.Container` can clone it into gorhom's
17
- * `backdropComponent` slot.
18
- *
19
- * That indirection buys the anatomy. The scrim and the sheet are siblings on
20
- * screen, so they read best as siblings in the tree — but gorhom takes the scrim
21
- * as a render prop, and a component cannot hand a prop to a sibling. This is the
22
- * seam where the two shapes are reconciled, and it is why the part exists at all
23
- * rather than the container simply taking a `backdrop` prop.
24
- *
25
- * @example
26
- * <BottomSheet.Portal>
27
- * <BottomSheet.Overlay />
28
- * <BottomSheet.Container>{…}</BottomSheet.Container>
29
- * </BottomSheet.Portal>
30
- */
31
- export function BottomSheetPortal({ children }: BottomSheetPortalProps): ReactElement {
32
- const { content, overlay } = useMemo(() => hoistOverlay(children), [children]);
33
- const value = useMemo(() => ({ overlay }), [overlay]);
34
-
35
- return <BottomSheetPortalProvider value={value}>{content}</BottomSheetPortalProvider>;
36
- }
37
- BottomSheetPortal.displayName = "DelacourUI.BottomSheet.Portal";
38
-
39
- /**
40
- * Splits a portal's children into the scrim and everything else.
41
- *
42
- * Lives here rather than with the parts: it is the portal that lifts its own
43
- * children, and importing it from `bottom-sheet-overlay.tsx` would close a cycle.
44
- * The same rule that keeps `withIndicator` in `radio.tsx`. See AGENTS.md.
45
- *
46
- * A second overlay is dropped rather than rendered — there is one
47
- * `backdropComponent` slot to clone into, and silently drawing two scrims at
48
- * different opacities is worse than the warning.
49
- */
50
- function hoistOverlay(children: ReactNode): { content: ReactNode; overlay: ReactElement | null } {
51
- const items = Children.toArray(children);
52
- const content: ReactNode[] = [];
53
- let overlay: ReactElement | null = null;
54
- let seen = 0;
55
-
56
- for (const child of items) {
57
- if (isValidElement(child) && child.type === BottomSheetOverlay) {
58
- seen += 1;
59
- overlay ??= child;
60
- continue;
61
- }
62
- content.push(child);
63
- }
64
-
65
- if (process.env.NODE_ENV !== "production" && seen > 1) {
66
- console.warn(
67
- `BottomSheet.Portal: ${seen} <BottomSheet.Overlay> elements were written; only the first is drawn. A sheet has one backdrop.`
68
- );
69
- }
70
-
71
- return { content, overlay };
72
- }
@@ -1,135 +0,0 @@
1
- import type { BottomSheetModal } from "@gorhom/bottom-sheet";
2
- import { type ComponentRef, createContext, type ReactElement, type ReactNode, type RefObject, use } from "react";
3
- import type { SharedValue } from "react-native-reanimated";
4
-
5
- /**
6
- * The imperative handle a `BottomSheetModal` exposes.
7
- *
8
- * Derived from the component rather than imported by name: gorhom re-exports the
9
- * modal's *value* from its package root but not the `BottomSheetModalMethods`
10
- * type behind its ref, and reaching into `lib/typescript/` for one would pin this
11
- * package to a path that is not part of that package's public surface.
12
- */
13
- export type BottomSheetHandle = ComponentRef<typeof BottomSheetModal>;
14
-
15
- export type BottomSheetContextValue = {
16
- isOpen: boolean;
17
- /** The controllable-state setter. Reports upward whether controlled or not. */
18
- setOpen: (isOpen: boolean) => void;
19
- open: () => void;
20
- close: () => void;
21
- /**
22
- * The modal's handle, for the imperative calls that have no declarative form —
23
- * `snapToIndex`, `expand`, `collapse`. Presenting and dismissing are driven by
24
- * `isOpen` instead, so a caller never has to keep two sources in step.
25
- */
26
- sheetRef: RefObject<BottomSheetHandle | null>;
27
- };
28
-
29
- const BottomSheetContext = createContext<BottomSheetContextValue | null>(null);
30
-
31
- export function BottomSheetProvider({
32
- value,
33
- children,
34
- }: {
35
- value: BottomSheetContextValue;
36
- children: ReactNode;
37
- }): ReactElement {
38
- return <BottomSheetContext value={value}>{children}</BottomSheetContext>;
39
- }
40
- BottomSheetProvider.displayName = "DelacourUI.BottomSheet.Provider";
41
-
42
- /** The sheet's state, or `null` outside one. For a component that works either way. */
43
- export function useBottomSheetContext(): BottomSheetContextValue | null {
44
- return use(BottomSheetContext);
45
- }
46
-
47
- /**
48
- * The enclosing sheet's open state and controls.
49
- *
50
- * Reach for this to drive a sheet from a control of your own — a row that closes
51
- * it, a button that snaps it somewhere — without threading props down.
52
- */
53
- export function useBottomSheet(): BottomSheetContextValue {
54
- const context = use(BottomSheetContext);
55
- if (context === null) throw new Error("useBottomSheet must be called inside a <BottomSheet>.");
56
- return context;
57
- }
58
-
59
- /** The same read, with a message naming the part that is misplaced. Internal. */
60
- export function useBottomSheetPart(component: string): BottomSheetContextValue {
61
- const context = use(BottomSheetContext);
62
- if (context === null) throw new Error(`${component} must be rendered inside a <BottomSheet>.`);
63
- return context;
64
- }
65
-
66
- export type BottomSheetPortalContextValue = {
67
- /**
68
- * The `BottomSheet.Overlay` element the portal lifted out of its own children,
69
- * or `null` when none was written.
70
- *
71
- * gorhom takes the scrim as a `backdropComponent` render prop rather than as a
72
- * child, so the element cannot be rendered where it was declared. The portal
73
- * publishes it here and `BottomSheet.Container` clones it into that slot.
74
- */
75
- overlay: ReactElement | null;
76
- };
77
-
78
- const BottomSheetPortalContext = createContext<BottomSheetPortalContextValue | null>(null);
79
-
80
- export function BottomSheetPortalProvider({
81
- value,
82
- children,
83
- }: {
84
- value: BottomSheetPortalContextValue;
85
- children: ReactNode;
86
- }): ReactElement {
87
- return <BottomSheetPortalContext value={value}>{children}</BottomSheetPortalContext>;
88
- }
89
- BottomSheetPortalProvider.displayName = "DelacourUI.BottomSheet.Portal.Provider";
90
-
91
- /** What the enclosing portal lifted out, or `null` for a container written without one. */
92
- export function useBottomSheetPortalContext(): BottomSheetPortalContextValue | null {
93
- return use(BottomSheetPortalContext);
94
- }
95
-
96
- export type BottomSheetContainerContextValue = {
97
- /**
98
- * Whether a pinned footer is drawing at the bottom of this sheet.
99
- *
100
- * `BottomSheet.Content` reads it to decide whether the safe-area band is its
101
- * to apply. Exactly one part takes it: both applying leaves a gap the height
102
- * of the home indicator between the content and the footer.
103
- */
104
- hasStickyFooter: boolean;
105
- /**
106
- * What a pinned footer covers, measured, in points.
107
- *
108
- * A shared value rather than state: it changes on every frame of the keyboard
109
- * animation as the footer gives up its safe-area band, and the body reserves it
110
- * with an animated spacer. Routing that through React would commit a dozen
111
- * renders per keystroke's worth of keyboard travel. `Screen` publishes its own
112
- * chrome the same way.
113
- *
114
- * Zero when there is no pinned footer.
115
- */
116
- footerHeight: SharedValue<number>;
117
- };
118
-
119
- const BottomSheetContainerContext = createContext<BottomSheetContainerContextValue | null>(null);
120
-
121
- export function BottomSheetContainerProvider({
122
- value,
123
- children,
124
- }: {
125
- value: BottomSheetContainerContextValue;
126
- children: ReactNode;
127
- }): ReactElement {
128
- return <BottomSheetContainerContext value={value}>{children}</BottomSheetContainerContext>;
129
- }
130
- BottomSheetContainerProvider.displayName = "DelacourUI.BottomSheet.Container.Provider";
131
-
132
- /** The enclosing container's layout facts, or `null` outside one. */
133
- export function useBottomSheetContainerContext(): BottomSheetContainerContextValue | null {
134
- return use(BottomSheetContainerContext);
135
- }