@retray-dev/ui-kit 13.0.0 → 13.4.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 (344) hide show
  1. package/CHANGELOG.md +680 -0
  2. package/CONSUMER.md +26 -9
  3. package/README.md +11 -12
  4. package/{COMPONENTS.md → SKILL.md} +515 -815
  5. package/dist/Accordion.d.mts +8 -6
  6. package/dist/Accordion.d.ts +8 -6
  7. package/dist/Accordion.js +40 -40
  8. package/dist/Accordion.mjs +4 -5
  9. package/dist/AlertBanner.d.mts +3 -3
  10. package/dist/AlertBanner.d.ts +3 -3
  11. package/dist/AlertBanner.js +7 -13
  12. package/dist/AlertBanner.mjs +4 -5
  13. package/dist/AppHeader.d.mts +8 -5
  14. package/dist/AppHeader.d.ts +8 -5
  15. package/dist/AppHeader.js +44 -31
  16. package/dist/AppHeader.mjs +6 -7
  17. package/dist/Avatar.d.mts +4 -4
  18. package/dist/Avatar.d.ts +4 -4
  19. package/dist/Avatar.mjs +2 -3
  20. package/dist/Badge.d.mts +5 -5
  21. package/dist/Badge.d.ts +5 -5
  22. package/dist/Badge.js +7 -13
  23. package/dist/Badge.mjs +3 -4
  24. package/dist/Button.d.mts +5 -5
  25. package/dist/Button.d.ts +5 -5
  26. package/dist/Button.js +31 -29
  27. package/dist/Button.mjs +5 -6
  28. package/dist/ButtonGroup.d.mts +3 -3
  29. package/dist/ButtonGroup.d.ts +3 -3
  30. package/dist/ButtonGroup.mjs +0 -1
  31. package/dist/Card.d.mts +13 -13
  32. package/dist/Card.d.ts +13 -13
  33. package/dist/Card.js +23 -14
  34. package/dist/Card.mjs +4 -5
  35. package/dist/CategoryStrip.d.mts +3 -3
  36. package/dist/CategoryStrip.d.ts +3 -3
  37. package/dist/CategoryStrip.js +30 -27
  38. package/dist/CategoryStrip.mjs +5 -6
  39. package/dist/Checkbox.d.mts +3 -2
  40. package/dist/Checkbox.d.ts +3 -2
  41. package/dist/Checkbox.js +26 -15
  42. package/dist/Checkbox.mjs +3 -4
  43. package/dist/Chip.d.mts +5 -5
  44. package/dist/Chip.d.ts +5 -5
  45. package/dist/Chip.js +30 -27
  46. package/dist/Chip.mjs +5 -6
  47. package/dist/ConfirmDialog.d.mts +3 -2
  48. package/dist/ConfirmDialog.d.ts +3 -2
  49. package/dist/ConfirmDialog.js +67 -49
  50. package/dist/ConfirmDialog.mjs +7 -7
  51. package/dist/CurrencyDisplay.d.mts +3 -3
  52. package/dist/CurrencyDisplay.d.ts +3 -3
  53. package/dist/CurrencyDisplay.mjs +2 -3
  54. package/dist/CurrencyInput.d.mts +2 -2
  55. package/dist/CurrencyInput.d.ts +2 -2
  56. package/dist/CurrencyInput.js +7 -13
  57. package/dist/CurrencyInput.mjs +4 -5
  58. package/dist/DetailRow.d.mts +6 -6
  59. package/dist/DetailRow.d.ts +6 -6
  60. package/dist/DetailRow.js +7 -13
  61. package/dist/DetailRow.mjs +3 -4
  62. package/dist/EmptyState.d.mts +4 -4
  63. package/dist/EmptyState.d.ts +4 -4
  64. package/dist/EmptyState.js +31 -29
  65. package/dist/EmptyState.mjs +6 -7
  66. package/dist/ErrorBoundary.d.mts +9 -7
  67. package/dist/ErrorBoundary.d.ts +9 -7
  68. package/dist/ErrorBoundary.js +33 -29
  69. package/dist/ErrorBoundary.mjs +5 -6
  70. package/dist/Form.d.mts +9 -9
  71. package/dist/Form.d.ts +9 -9
  72. package/dist/Form.mjs +2 -3
  73. package/dist/HolographicCard.d.mts +2 -2
  74. package/dist/HolographicCard.d.ts +2 -2
  75. package/dist/HolographicCard.js +23 -14
  76. package/dist/HolographicCard.mjs +2 -3
  77. package/dist/IconButton.d.mts +4 -4
  78. package/dist/IconButton.d.ts +4 -4
  79. package/dist/IconButton.js +30 -27
  80. package/dist/IconButton.mjs +4 -5
  81. package/dist/IconPicker.d.mts +2 -2
  82. package/dist/IconPicker.d.ts +2 -2
  83. package/dist/IconPicker.js +40 -45
  84. package/dist/IconPicker.mjs +6 -7
  85. package/dist/Image.d.mts +18 -0
  86. package/dist/Image.d.ts +18 -0
  87. package/dist/Image.js +53 -0
  88. package/dist/Image.mjs +2 -0
  89. package/dist/ImageUpload.d.mts +2 -4
  90. package/dist/ImageUpload.d.ts +2 -4
  91. package/dist/ImageUpload.js +50 -40
  92. package/dist/ImageUpload.mjs +5 -6
  93. package/dist/ImageViewer.d.mts +2 -2
  94. package/dist/ImageViewer.d.ts +2 -2
  95. package/dist/ImageViewer.js +31 -28
  96. package/dist/ImageViewer.mjs +6 -7
  97. package/dist/Input.d.mts +4 -4
  98. package/dist/Input.d.ts +4 -4
  99. package/dist/Input.js +7 -13
  100. package/dist/Input.mjs +3 -4
  101. package/dist/ItemGroup.d.mts +23 -0
  102. package/dist/ItemGroup.d.ts +23 -0
  103. package/dist/{ListGroup.js → ItemGroup.js} +11 -13
  104. package/dist/ItemGroup.mjs +4 -0
  105. package/dist/LabelValue.d.mts +4 -4
  106. package/dist/LabelValue.d.ts +4 -4
  107. package/dist/LabelValue.js +7 -13
  108. package/dist/LabelValue.mjs +3 -4
  109. package/dist/ListItem.d.mts +7 -6
  110. package/dist/ListItem.d.ts +7 -6
  111. package/dist/ListItem.js +33 -28
  112. package/dist/ListItem.mjs +5 -6
  113. package/dist/MediaCard.d.mts +6 -6
  114. package/dist/MediaCard.d.ts +6 -6
  115. package/dist/MediaCard.js +30 -27
  116. package/dist/MediaCard.mjs +5 -6
  117. package/dist/MenuItem.d.mts +6 -5
  118. package/dist/MenuItem.d.ts +6 -5
  119. package/dist/MenuItem.js +33 -28
  120. package/dist/MenuItem.mjs +5 -6
  121. package/dist/MonthPicker.d.mts +2 -2
  122. package/dist/MonthPicker.d.ts +2 -2
  123. package/dist/MonthPicker.js +23 -14
  124. package/dist/MonthPicker.mjs +3 -4
  125. package/dist/NumberStepper.d.mts +4 -3
  126. package/dist/NumberStepper.d.ts +4 -3
  127. package/dist/NumberStepper.js +34 -28
  128. package/dist/NumberStepper.mjs +5 -6
  129. package/dist/PagerDots.d.mts +2 -2
  130. package/dist/PagerDots.d.ts +2 -2
  131. package/dist/PagerDots.js +30 -27
  132. package/dist/PagerDots.mjs +4 -5
  133. package/dist/Pressable.d.mts +3 -27
  134. package/dist/Pressable.d.ts +3 -27
  135. package/dist/Pressable.js +23 -14
  136. package/dist/Pressable.mjs +2 -3
  137. package/dist/PricingCard.d.mts +2 -2
  138. package/dist/PricingCard.d.ts +2 -2
  139. package/dist/PricingCard.js +31 -29
  140. package/dist/PricingCard.mjs +7 -8
  141. package/dist/Progress.d.mts +2 -2
  142. package/dist/Progress.d.ts +2 -2
  143. package/dist/Progress.mjs +2 -3
  144. package/dist/RadioGroup.d.mts +2 -2
  145. package/dist/RadioGroup.d.ts +2 -2
  146. package/dist/RadioGroup.js +23 -14
  147. package/dist/RadioGroup.mjs +3 -4
  148. package/dist/RetrayProvider.d.mts +1 -1
  149. package/dist/RetrayProvider.d.ts +1 -1
  150. package/dist/RetrayProvider.js +14 -34
  151. package/dist/RetrayProvider.mjs +3 -4
  152. package/dist/ScreenContainer.d.mts +24 -0
  153. package/dist/ScreenContainer.d.ts +24 -0
  154. package/dist/ScreenContainer.js +85 -0
  155. package/dist/ScreenContainer.mjs +3 -0
  156. package/dist/Select.d.mts +3 -2
  157. package/dist/Select.d.ts +3 -2
  158. package/dist/Select.js +41 -46
  159. package/dist/Select.mjs +3 -4
  160. package/dist/SelectableCard.d.mts +5 -5
  161. package/dist/SelectableCard.d.ts +5 -5
  162. package/dist/SelectableCard.js +30 -27
  163. package/dist/SelectableCard.mjs +5 -6
  164. package/dist/SelectableGrid.d.mts +5 -4
  165. package/dist/SelectableGrid.d.ts +5 -4
  166. package/dist/SelectableGrid.js +80 -45
  167. package/dist/SelectableGrid.mjs +5 -6
  168. package/dist/Separator.d.mts +4 -2
  169. package/dist/Separator.d.ts +4 -2
  170. package/dist/Separator.js +29 -1
  171. package/dist/Separator.mjs +3 -3
  172. package/dist/Sheet.d.mts +11 -11
  173. package/dist/Sheet.d.ts +11 -11
  174. package/dist/Sheet.js +62 -34
  175. package/dist/Sheet.mjs +4 -4
  176. package/dist/SheetSelect.d.mts +2 -2
  177. package/dist/SheetSelect.d.ts +2 -2
  178. package/dist/SheetSelect.js +30 -27
  179. package/dist/SheetSelect.mjs +5 -6
  180. package/dist/Skeleton.d.mts +5 -5
  181. package/dist/Skeleton.d.ts +5 -5
  182. package/dist/Skeleton.mjs +3 -4
  183. package/dist/Slider.d.mts +3 -2
  184. package/dist/Slider.d.ts +3 -2
  185. package/dist/Slider.js +25 -14
  186. package/dist/Slider.mjs +3 -4
  187. package/dist/Spinner.d.mts +2 -2
  188. package/dist/Spinner.d.ts +2 -2
  189. package/dist/Spinner.mjs +2 -3
  190. package/dist/Stats.d.mts +6 -6
  191. package/dist/Stats.d.ts +6 -6
  192. package/dist/Stats.js +30 -27
  193. package/dist/Stats.mjs +5 -6
  194. package/dist/Switch.d.mts +3 -2
  195. package/dist/Switch.d.ts +3 -2
  196. package/dist/Switch.js +25 -15
  197. package/dist/Switch.mjs +3 -4
  198. package/dist/TabBar.d.mts +3 -3
  199. package/dist/TabBar.d.ts +3 -3
  200. package/dist/TabBar.js +30 -27
  201. package/dist/TabBar.mjs +4 -5
  202. package/dist/Tabs.d.mts +13 -13
  203. package/dist/Tabs.d.ts +13 -13
  204. package/dist/Tabs.js +23 -14
  205. package/dist/Tabs.mjs +3 -4
  206. package/dist/Text.d.mts +4 -4
  207. package/dist/Text.d.ts +4 -4
  208. package/dist/Text.js +20 -2
  209. package/dist/Text.mjs +3 -4
  210. package/dist/Textarea.d.mts +3 -3
  211. package/dist/Textarea.d.ts +3 -3
  212. package/dist/Textarea.js +7 -13
  213. package/dist/Textarea.mjs +3 -4
  214. package/dist/Toast.d.mts +15 -13
  215. package/dist/Toast.d.ts +15 -13
  216. package/dist/Toast.mjs +2 -3
  217. package/dist/Toggle.d.mts +4 -4
  218. package/dist/Toggle.d.ts +4 -4
  219. package/dist/Toggle.js +30 -27
  220. package/dist/Toggle.mjs +4 -5
  221. package/dist/VirtualizedList.d.mts +28 -0
  222. package/dist/VirtualizedList.d.ts +28 -0
  223. package/dist/VirtualizedList.js +130 -0
  224. package/dist/VirtualizedList.mjs +3 -0
  225. package/dist/{chunk-MZ6WRTD2.mjs → chunk-24JTXQ2M.mjs} +7 -13
  226. package/dist/{chunk-OBV72JD4.mjs → chunk-2DDJ53DK.mjs} +9 -11
  227. package/dist/{chunk-6CR4S6W2.mjs → chunk-2J5OZOMX.mjs} +19 -8
  228. package/dist/{chunk-4NQFTHN3.mjs → chunk-3GE4UFV5.mjs} +2 -2
  229. package/dist/{chunk-KAGADD2O.mjs → chunk-3RIZCKRM.mjs} +2 -2
  230. package/dist/{chunk-DE25XTVQ.mjs → chunk-3VHFOSZR.mjs} +2 -2
  231. package/dist/{chunk-UOKFSFNJ.mjs → chunk-4PF4LKNT.mjs} +4 -2
  232. package/dist/{chunk-5MYNAAFE.mjs → chunk-5J7VKFSZ.mjs} +4 -4
  233. package/dist/{chunk-BTUW5LSG.mjs → chunk-5TNQ573V.mjs} +4 -3
  234. package/dist/{chunk-6QLBHUEG.mjs → chunk-6T2DVIQT.mjs} +7 -5
  235. package/dist/{chunk-L3YKPTJQ.mjs → chunk-7CE6PDCQ.mjs} +2 -2
  236. package/dist/{chunk-Y6YS33GM.mjs → chunk-AHFEAY6M.mjs} +4 -4
  237. package/dist/{chunk-4ZO5PTKF.mjs → chunk-AZRATPNP.mjs} +5 -3
  238. package/dist/{chunk-V2ZB2XNS.mjs → chunk-BGXOEFDM.mjs} +9 -22
  239. package/dist/{chunk-KC5QDYGZ.mjs → chunk-BMAAAJWN.mjs} +2 -2
  240. package/dist/{chunk-IJCMPVW5.mjs → chunk-BQMJQMWY.mjs} +2 -2
  241. package/dist/{chunk-E4EQSCKR.mjs → chunk-BTPCY4C7.mjs} +7 -5
  242. package/dist/chunk-BVJAYPAD.mjs +55 -0
  243. package/dist/{chunk-RA6SAAFE.mjs → chunk-BWLVX2SQ.mjs} +4 -4
  244. package/dist/{chunk-EROPDCB5.mjs → chunk-CCEM3HIJ.mjs} +30 -25
  245. package/dist/chunk-CTUFFKGS.mjs +30 -0
  246. package/dist/{chunk-EHGBHFMH.mjs → chunk-CYGYC7XT.mjs} +8 -4
  247. package/dist/{chunk-ESQDPO5E.mjs → chunk-DLAOTHHS.mjs} +7 -6
  248. package/dist/{chunk-QY3X2UYR.mjs → chunk-DYYPDQA2.mjs} +21 -7
  249. package/dist/{chunk-S44XWTTC.mjs → chunk-E4BJ5WXG.mjs} +3 -3
  250. package/dist/{chunk-HUSSF6TF.mjs → chunk-EQNCMDZC.mjs} +1 -1
  251. package/dist/{chunk-PI6RULJX.mjs → chunk-EQYTDFDD.mjs} +1 -1
  252. package/dist/{chunk-BULKGOIZ.mjs → chunk-FE26TPCI.mjs} +4 -4
  253. package/dist/{chunk-DBHSUUKU.mjs → chunk-FOUSI6JD.mjs} +1 -1
  254. package/dist/{chunk-KPTY7UYQ.mjs → chunk-GR7PKEKD.mjs} +1 -1
  255. package/dist/{chunk-RRKM4MKB.mjs → chunk-HLWGFBIF.mjs} +3 -3
  256. package/dist/chunk-HMKJGVXA.mjs +35 -0
  257. package/dist/{chunk-U6DEBYU5.mjs → chunk-IFGZUJFH.mjs} +3 -3
  258. package/dist/{chunk-2VIDP72N.mjs → chunk-K3V6OTVB.mjs} +1 -1
  259. package/dist/{chunk-K7TKID3V.mjs → chunk-K4YFTUMC.mjs} +3 -3
  260. package/dist/{chunk-NGEN2EES.mjs → chunk-MQAK2W6L.mjs} +14 -22
  261. package/dist/{chunk-CM2DG4MR.mjs → chunk-MSS3CD6F.mjs} +4 -4
  262. package/dist/{chunk-TETMEKZE.mjs → chunk-NQYS6RPX.mjs} +8 -5
  263. package/dist/{chunk-62BBSSUF.mjs → chunk-P5KC3RTG.mjs} +1 -1
  264. package/dist/{chunk-K3QX2M26.mjs → chunk-PPKCGCZ3.mjs} +5 -5
  265. package/dist/{chunk-ITG4JQM3.mjs → chunk-QEE3EQ3N.mjs} +2 -2
  266. package/dist/{chunk-URIH43IJ.mjs → chunk-RLPPRIJ7.mjs} +20 -34
  267. package/dist/{chunk-XCIG6HT2.mjs → chunk-S433IOQE.mjs} +2 -2
  268. package/dist/{chunk-IGU223UM.mjs → chunk-SWUZKVYO.mjs} +1 -1
  269. package/dist/{chunk-MP7GLMIR.mjs → chunk-T4KMKHTI.mjs} +55 -23
  270. package/dist/{chunk-2QOHHBJC.mjs → chunk-UBTP4NPP.mjs} +5 -21
  271. package/dist/{chunk-TMH263OK.mjs → chunk-UEA2VYGW.mjs} +3 -3
  272. package/dist/chunk-VISIOH33.mjs +37 -0
  273. package/dist/{chunk-SZEKQAOY.mjs → chunk-VSKBODEY.mjs} +1 -1
  274. package/dist/{chunk-FTTI6T5Q.mjs → chunk-W422TEH2.mjs} +3 -3
  275. package/dist/{chunk-WIPEDNSD.mjs → chunk-WD5LBXPR.mjs} +4 -4
  276. package/dist/chunk-WFNGSYS4.mjs +111 -0
  277. package/dist/chunk-WR6DCNAE.mjs +65 -0
  278. package/dist/{chunk-ERWJPVX7.mjs → chunk-XKBB2UZU.mjs} +2 -2
  279. package/dist/{chunk-CBIZLRYH.mjs → chunk-Y5TPAKOS.mjs} +14 -17
  280. package/dist/{chunk-AZV7KNJI.mjs → chunk-YKWIMVGU.mjs} +2 -2
  281. package/dist/{chunk-ZKDKKQCE.mjs → chunk-YOXSXHDE.mjs} +4 -4
  282. package/dist/{chunk-PGQ6FMXS.mjs → chunk-ZO5BRTCW.mjs} +2 -2
  283. package/dist/{chunk-KSSVIFYR.mjs → chunk-ZQGCQ7SA.mjs} +14 -34
  284. package/dist/{chunk-ZTPYUU5C.mjs → chunk-ZRUUUVOO.mjs} +3 -3
  285. package/dist/fonts.mjs +0 -2
  286. package/dist/{index-CY34hxPN.d.ts → index-CinAt5Uo.d.mts} +3 -3
  287. package/dist/{index-CY34hxPN.d.mts → index-CinAt5Uo.d.ts} +3 -3
  288. package/dist/index.d.mts +69 -19
  289. package/dist/index.d.ts +69 -19
  290. package/dist/index.js +1023 -839
  291. package/dist/index.mjs +76 -70
  292. package/package.json +15 -12
  293. package/src/components/Accordion/Accordion.tsx +12 -18
  294. package/src/components/AppHeader/AppHeader.tsx +33 -10
  295. package/src/components/Checkbox/Checkbox.tsx +3 -0
  296. package/src/components/ConfirmDialog/ConfirmDialog.tsx +7 -21
  297. package/src/components/ErrorBoundary/ErrorBoundary.tsx +5 -2
  298. package/src/components/Image/Image.tsx +50 -0
  299. package/src/components/Image/index.ts +2 -0
  300. package/src/components/ImageUpload/ImageUpload.tsx +34 -26
  301. package/src/components/{ListGroup/ListGroup.tsx → ItemGroup/ItemGroup.tsx} +15 -29
  302. package/src/components/ItemGroup/index.ts +2 -0
  303. package/src/components/ListGroup/index.tsx +20 -0
  304. package/src/components/ListItem/ListItem.tsx +3 -0
  305. package/src/components/MenuGroup/index.tsx +20 -0
  306. package/src/components/MenuItem/MenuItem.tsx +3 -0
  307. package/src/components/NumberStepper/NumberStepper.tsx +4 -0
  308. package/src/components/Pressable/Pressable.tsx +0 -24
  309. package/src/components/ScreenContainer/ScreenContainer.tsx +94 -0
  310. package/src/components/ScreenContainer/index.ts +2 -0
  311. package/src/components/Select/Select.tsx +25 -30
  312. package/src/components/SelectableGrid/SelectableGrid.tsx +51 -20
  313. package/src/components/Separator/Separator.tsx +35 -2
  314. package/src/components/Sheet/Sheet.tsx +3 -21
  315. package/src/components/Sheet/index.ts +2 -2
  316. package/src/components/Slider/Slider.tsx +3 -0
  317. package/src/components/Switch/Switch.tsx +3 -1
  318. package/src/components/Tabs/Tabs.tsx +9 -9
  319. package/src/components/Tabs/index.ts +1 -1
  320. package/src/components/Text/Text.tsx +7 -0
  321. package/src/components/VirtualizedList/VirtualizedList.tsx +154 -0
  322. package/src/components/VirtualizedList/index.ts +2 -0
  323. package/src/hooks/useConfirmDialog.ts +2 -11
  324. package/src/hooks/useSheetModal.ts +40 -0
  325. package/src/index.ts +5 -1
  326. package/src/theme/colors.ts +19 -57
  327. package/src/tokens.ts +21 -7
  328. package/src/utils/curatedIcons.ts +9 -18
  329. package/src/utils/haptics.ts +10 -21
  330. package/src/utils/icons.ts +7 -14
  331. package/dist/ListGroup.d.mts +0 -34
  332. package/dist/ListGroup.d.ts +0 -34
  333. package/dist/ListGroup.mjs +0 -5
  334. package/dist/MenuGroup.d.mts +0 -34
  335. package/dist/MenuGroup.d.ts +0 -34
  336. package/dist/MenuGroup.js +0 -106
  337. package/dist/MenuGroup.mjs +0 -5
  338. package/dist/chunk-ARONDO7M.mjs +0 -40
  339. package/dist/chunk-EW2FIDSM.mjs +0 -29
  340. package/dist/chunk-S2VGME7X.mjs +0 -82
  341. package/dist/chunk-Y6FXYEAI.mjs +0 -8
  342. package/src/components/ListGroup/index.ts +0 -1
  343. package/src/components/MenuGroup/MenuGroup.tsx +0 -145
  344. package/src/components/MenuGroup/index.ts +0 -1
@@ -1,836 +1,559 @@
1
- # @retray-dev/ui-kit — Component Reference (v13.0.0)
1
+ ---
2
+ name: retray-ui-kit
3
+ description: >
4
+ Complete guide for @retray-dev/ui-kit — a React Native / Expo component
5
+ library (~57 components). Airbnb-inspired design, Sohne typography,
6
+ haptic-rich interactions, animated with pressto + react-native-ease.
7
+ Theme system, BottomSheet patterns, icon resolution, and conventions.
8
+ ---
2
9
 
3
- This file is the AI reference for this package. Add all three lines below to your project's `CLAUDE.md` to give Claude full context components, setup guide, and usage examples:
10
+ # @retray-dev/ui-kitAgent Skill
4
11
 
5
- ```markdown
6
- ## UI Components
7
- @./node_modules/@retray-dev/ui-kit/COMPONENTS.md
8
- @./node_modules/@retray-dev/ui-kit/CONSUMER.md
9
- @./node_modules/@retray-dev/ui-kit/EXAMPLES.md
10
- ```
12
+ > **Recommended — copy to repo root (no dependency on node_modules existing):**
13
+ > ```bash
14
+ > cp node_modules/@retray-dev/ui-kit/SKILL.md SKILL.md
15
+ > cp node_modules/@retray-dev/ui-kit/CONSUMER.md CONSUMER.md
16
+ > cp node_modules/@retray-dev/ui-kit/EXAMPLES.md EXAMPLES.md
17
+ > ```
18
+ >
19
+ > Then add to `CLAUDE.md`:
20
+ > ```markdown
21
+ > ## UI Kit
22
+ > @./SKILL.md
23
+ > @./CONSUMER.md
24
+ > @./EXAMPLES.md
25
+ > ```
11
26
 
12
27
  ---
13
28
 
14
- ## Setup (Required)
15
-
16
- ### Recommended: `RetrayProvider` (one wrapper)
29
+ ## Required Setup
17
30
 
18
- Since v8, a single `RetrayProvider` wires all five required providers in the correct order — use this and skip the manual nesting:
31
+ ### Providers
32
+ Use `RetrayProvider` (single wrapper) or manual tree. Order is mandatory:
19
33
 
20
- ```tsx
21
- import { RetrayProvider } from '@retray-dev/ui-kit'
22
-
23
- export default function App() {
24
- const [fontsLoaded] = useFonts(SohneFonts)
25
- if (!fontsLoaded) return null
26
- return (
27
- <RetrayProvider colorScheme="system">
28
- {/* your app */}
29
- </RetrayProvider>
30
- )
31
- }
32
34
  ```
33
-
34
- ### Manual (equivalent) — if you need a custom tree
35
-
36
- `RetrayProvider` is exactly this; the individual providers stay exported. Order is mandatory:
37
-
38
- ```tsx
39
- import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context'
40
- import { GestureHandlerRootView } from 'react-native-gesture-handler'
41
- import { ThemeProvider, BottomSheetModalProvider, ToastProvider } from '@retray-dev/ui-kit'
42
-
43
- export default function App() {
44
- return (
45
- <SafeAreaProvider initialMetrics={initialWindowMetrics}>
46
- <GestureHandlerRootView style={{ flex: 1 }}>
47
- <ThemeProvider colorScheme="system">
48
- <BottomSheetModalProvider>
49
- <ToastProvider>
50
- {/* your app */}
51
- </ToastProvider>
52
- </BottomSheetModalProvider>
53
- </ThemeProvider>
54
- </GestureHandlerRootView>
55
- </SafeAreaProvider>
56
- )
57
- }
35
+ SafeAreaProvider > GestureHandlerRootView > ThemeProvider > BottomSheetModalProvider > ToastProvider
58
36
  ```
59
37
 
60
- **Provider order is mandatory:**
61
- - `SafeAreaProvider` must be outermost — required by `@gorhom/bottom-sheet`
62
- - `initialMetrics={initialWindowMetrics}` is required on Android to avoid a "No safe area value available" crash on first render
63
- - `GestureHandlerRootView` must wrap everything — required by `@gorhom/bottom-sheet`
64
- - `BottomSheetModalProvider` must be inside `GestureHandlerRootView`
65
- - `ToastProvider` wraps children and renders `Toaster` (from `sonner-native`) internally
66
-
67
- ### Peer dependencies
38
+ `initialMetrics={initialWindowMetrics}` required on Android.
68
39
 
69
- Install all required peers (Expo projects: swap `pnpm add` for `npx expo install` to get SDK-pinned versions):
70
-
71
- ```bash
72
- pnpm add expo-font expo-haptics expo-linear-gradient react-native-safe-area-context @gorhom/bottom-sheet react-native-reanimated react-native-gesture-handler react-native-worklets @react-native-picker/picker @react-native-community/slider @expo/vector-icons react-native-size-matters react-native-svg react-native-screens sonner-native pressto react-native-ease
73
- ```
74
-
75
- Then add the Worklets Babel plugin to `babel.config.js` (required by `@gorhom/bottom-sheet` and the pressable animations):
40
+ ### Peer Dependencies (all required unless noted)
41
+ - **Hard:** `expo-haptics`, `expo-linear-gradient`, `expo-font`, `expo-image`, `react-native-reanimated`, `react-native-gesture-handler`, `react-native-worklets`, `react-native-safe-area-context`, `react-native-screens`, `react-native-svg`, `@gorhom/bottom-sheet`, `@react-native-picker/picker`, `@react-native-community/slider`, `@expo/vector-icons`, `react-native-size-matters`, `sonner-native`, `react-native-ease`, `pressto`
42
+ - **Optional:** `react-native-image-picker` (ImageUpload), `@shopify/react-native-skia` + `expo-sensors` (HolographicCard deep-import only)
76
43
 
44
+ ### Babel Config
77
45
  ```js
78
- module.exports = function (api) {
79
- api.cache(true)
80
- return {
81
- presets: ['babel-preset-expo'],
82
- plugins: ['react-native-worklets/plugin'], // NOT react-native-reanimated/plugin
83
- }
84
- }
85
- ```
86
-
87
- | Peer | Required? | Used by | Notes |
88
- |------|-----------|---------|-------|
89
- | `pressto` | **Required** | Every interactive component | Press-scale animations (`src/utils/pressable.ts`). Static import via the barrel — omitting it crashes the module load, not just one component. |
90
- | `sonner-native` | **Required** | `Toast` | Also pulls `react-native-svg` + `react-native-screens`. |
91
- | `react-native-reanimated` (≥4.0) | **Required** | Animations, `Sheet`, pressables | Needs the `react-native-worklets/plugin` Babel plugin. |
92
- | `react-native-gesture-handler` | **Required** | `Sheet`, pressables | Wrap app in `GestureHandlerRootView`. |
93
- | `@gorhom/bottom-sheet` (≥5.2.0) | **Required** | `Sheet`, `ConfirmDialog` | 5.1.x crashes on Reanimated v4. |
94
- | `expo-haptics` | **Required** | Haptic feedback (all interactions) | Web-safe wrapper, no-op on web. |
95
- | `expo-font` | **Required** | All `Text` | Load `SohneFonts` at app root before rendering. |
96
- | `expo-linear-gradient` | **Required** | `Skeleton` shimmer | |
97
- | `@react-native-picker/picker` | **Required** | `Select` | |
98
- | `@react-native-community/slider` | **Required** | `Slider` | |
99
- | `@expo/vector-icons` | **Required** | Icons everywhere | |
100
- | `react-native-size-matters` | **Required** | Responsive scaling | |
101
- | `react-native-ease` | **Required** | `Checkbox`, `Chip`, `CategoryStrip`, `Switch`, `RadioGroup`, `Toggle` | Declarative `EaseView` animation layer. Static import in source — omitting it crashes the module load. `pnpm add react-native-ease` |
102
-
103
- | `@shopify/react-native-skia` + `expo-sensors` | **Optional** | Deep-import `HolographicCard` only | `pnpm add @shopify/react-native-skia expo-sensors` |
104
-
105
-
106
-
107
- ### Troubleshooting: `react-native-screens` codegen on RN 0.83
108
-
109
- Toast pulls `sonner-native`, which imports `react-native-screens` via its `src/*` path. On **React Native 0.83** that source trips a `@react-native/codegen` parse error (`CT.WithDefault` not parseable). It is a `react-native-screens` issue, not the kit — but since Toast surfaces it, add this resolver to your `metro.config.js` to force the compiled `lib/commonjs` build:
110
-
111
- ```js
112
- // metro.config.js
113
- const { getDefaultConfig } = require('expo/metro-config')
114
- const config = getDefaultConfig(__dirname)
115
-
116
- const defaultResolveRequest = config.resolver.resolveRequest
117
- config.resolver.resolveRequest = (context, moduleName, platform) => {
118
- // Force react-native-screens to its compiled output, bypassing the src codegen bug.
119
- if (moduleName.startsWith('react-native-screens/src')) {
120
- moduleName = moduleName.replace('react-native-screens/src', 'react-native-screens/lib/commonjs')
121
- }
122
- return (defaultResolveRequest ?? context.resolveRequest)(context, moduleName, platform)
123
- }
124
-
125
- module.exports = config
126
- ```
127
-
128
- ## Typography — Sohne (Required)
129
-
130
- All components use **Sohne** as the font family. You **must** load it before rendering any UI kit component.
131
-
132
- ### How it works
133
-
134
- 1. When you install `@retray-dev/ui-kit`, the **postinstall script** automatically copies 28 `.otf` font files to `assets/fonts/sohne/` in your project root
135
- 2. You must define `SohneFonts` with static `require()` calls in your `App.tsx` (see boilerplate below)
136
- 3. Pass it to `expo-font`'s `useFonts()` hook at your app root
137
- 4. All library components reference fonts by family name (e.g., `fontFamily: 'Sohne-SemiBold'`)
138
-
139
- ### SohneFonts boilerplate — copy this into your App.tsx
140
-
141
- ```tsx
142
- import { useFonts } from 'expo-font'
143
-
144
- // Fonts copied to assets/fonts/sohne/ by @retray-dev/ui-kit postinstall
145
- const SohneFonts = {
146
- 'Sohne-ExtraLight': require('./assets/fonts/sohne/Sohne-ExtraLight.otf'),
147
- 'Sohne-ExtraLightItalic': require('./assets/fonts/sohne/Sohne-ExtraLightItalic.otf'),
148
- 'Sohne-Light': require('./assets/fonts/sohne/Sohne-Light.otf'),
149
- 'Sohne-LightItalic': require('./assets/fonts/sohne/Sohne-LightItalic.otf'),
150
- 'Sohne-Regular': require('./assets/fonts/sohne/Sohne-Regular.otf'),
151
- 'Sohne-Italic': require('./assets/fonts/sohne/Sohne-Italic.otf'),
152
- 'Sohne-Medium': require('./assets/fonts/sohne/Sohne-Medium.otf'),
153
- 'Sohne-MediumItalic': require('./assets/fonts/sohne/Sohne-MediumItalic.otf'),
154
- 'Sohne-SemiBold': require('./assets/fonts/sohne/Sohne-SemiBold.otf'),
155
- 'Sohne-SemiBoldItalic': require('./assets/fonts/sohne/Sohne-SemiBoldItalic.otf'),
156
- 'Sohne-Bold': require('./assets/fonts/sohne/Sohne-Bold.otf'),
157
- 'Sohne-BoldItalic': require('./assets/fonts/sohne/Sohne-BoldItalic.otf'),
158
- 'Sohne-ExtraBold': require('./assets/fonts/sohne/Sohne-ExtraBold.otf'),
159
- 'Sohne-ExtraBoldItalic': require('./assets/fonts/sohne/Sohne-ExtraBoldItalic.otf'),
160
- 'SohneMono-ExtraLight': require('./assets/fonts/sohne/SohneMono-ExtraLight.otf'),
161
- 'SohneMono-ExtraLightItalic': require('./assets/fonts/sohne/SohneMono-ExtraLightItalic.otf'),
162
- 'SohneMono-Light': require('./assets/fonts/sohne/SohneMono-Light.otf'),
163
- 'SohneMono-LightItalic': require('./assets/fonts/sohne/SohneMono-LightItalic.otf'),
164
- 'SohneMono-Regular': require('./assets/fonts/sohne/SohneMono-Regular.otf'),
165
- 'SohneMono-Italic': require('./assets/fonts/sohne/SohneMono-Italic.otf'),
166
- 'SohneMono-Medium': require('./assets/fonts/sohne/SohneMono-Medium.otf'),
167
- 'SohneMono-MediumItalic': require('./assets/fonts/sohne/SohneMono-MediumItalic.otf'),
168
- 'SohneMono-SemiBold': require('./assets/fonts/sohne/SohneMono-SemiBold.otf'),
169
- 'SohneMono-SemiBoldItalic': require('./assets/fonts/sohne/SohneMono-SemiBoldItalic.otf'),
170
- 'SohneMono-Bold': require('./assets/fonts/sohne/SohneMono-Bold.otf'),
171
- 'SohneMono-BoldItalic': require('./assets/fonts/sohne/SohneMono-BoldItalic.otf'),
172
- 'SohneMono-ExtraBold': require('./assets/fonts/sohne/SohneMono-ExtraBold.otf'),
173
- 'SohneMono-ExtraBoldItalic': require('./assets/fonts/sohne/SohneMono-ExtraBoldItalic.otf'),
174
- }
175
-
176
- export default function App() {
177
- const [fontsLoaded] = useFonts(SohneFonts)
178
- if (!fontsLoaded) return null
179
- return (
180
- // ... your providers and app
181
- )
182
- }
183
- ```
184
-
185
- ### .gitignore recommendation
186
-
187
- Fonts are copied to `assets/fonts/sohne/` on install. You can either:
188
- - **Commit them** (no network needed during CI builds)
189
- - **Ignore them** (re-copied on every `pnpm install`)
190
-
191
- ```gitignore
192
- # Sohne fonts — copied by @retray-dev/ui-kit postinstall
193
- # Either commit these or ignore them (re-copied on install)
194
- assets/fonts/sohne/
195
- ```
196
-
197
- **Included weights (28 files):**
198
- - Sohne: `Sohne-ExtraLight`, `Sohne-Light`, `Sohne-Regular`, `Sohne-Medium`, `Sohne-SemiBold`, `Sohne-Bold`, `Sohne-ExtraBold` + italic variants
199
- - SohneMono: `SohneMono-ExtraLight`, `SohneMono-Light`, `SohneMono-Regular`, `SohneMono-Medium`, `SohneMono-SemiBold`, `SohneMono-Bold`, `SohneMono-ExtraBold` + italic variants
200
-
201
- Pair with `expo-splash-screen` in production:
202
- ```tsx
203
- import * as SplashScreen from 'expo-splash-screen'
204
- SplashScreen.preventAutoHideAsync()
205
-
206
- useEffect(() => {
207
- if (fontsLoaded) SplashScreen.hideAsync()
208
- }, [fontsLoaded])
46
+ plugins: ['react-native-worklets/plugin']
47
+ // NOT react-native-reanimated/plugin
209
48
  ```
210
49
 
211
- ---
212
-
213
- ## Theme System
214
-
215
- ### ThemeProvider Props
216
-
217
- | Prop | Type | Default | Notes |
218
- |------|------|---------|-------|
219
- | colorScheme | `'light' \| 'dark' \| 'system'` | `'system'` | `'system'` auto-detects device setting and updates when it changes |
220
- | theme | `{ light?: Partial<ThemeColors>, dark?: Partial<ThemeColors> }` | — | Override any subset of the 12 public tokens per scheme |
221
-
222
- **Custom theme example:**
223
- ```tsx
224
- const myTheme = {
225
- light: { primary: '#ff385c', primaryForeground: '#ffffff' },
226
- dark: { primary: '#ff385c', primaryForeground: '#ffffff' },
227
- }
228
- <ThemeProvider theme={myTheme} colorScheme="system">
229
- ```
230
-
231
- ### useTheme Hook
232
-
233
- ```tsx
234
- import { useTheme } from '@retray-dev/ui-kit'
235
-
236
- function MyComponent() {
237
- const { colors, colorScheme } = useTheme()
238
- return <View style={{ backgroundColor: colors.background }} />
239
- }
240
- ```
241
-
242
- Returns `colors` (full `ResolvedColors` palette) and `colorScheme` (`'light' | 'dark'`).
50
+ ### Fonts (Sohne)
51
+ Postinstall copies 28 `.otf` to `assets/fonts/sohne/`. Consumer defines static `require()` calls in App.tsx with `expo-font`'s `useFonts()`.
243
52
 
244
53
  ---
245
54
 
246
- ## Theme Tokens
55
+ ## Design System (Airbnb-inspired)
247
56
 
248
- ### Public Tokens (ThemeColors) — 12 tokens consumer can override
57
+ | Token | Rule |
58
+ |---|---|
59
+ | **Touch targets** | ≥44pt height on all interactive elements |
60
+ | **Spacing** | 8pt grid, 4pt micro-steps |
61
+ | **Radius** | sm=6, md=8, lg=12, xl=16 |
62
+ | **Colors** | 12 public tokens → `deriveColors()` computes 26 resolved tokens |
63
+ | **Typography** | Sohne, modest weights (500-600 display), `allowFontScaling={true}` everywhere |
64
+ | **Shadows** | Max 2 tiers. Flat baseline + single float tier |
65
+ | **Elevation** | Depth from rounded corners + surface separation, not shadows |
249
66
 
250
- These are the only values you need to supply when customizing the theme. The library derives all other colors internally.
67
+ ### Theme Tokens 12 Public (consumer can override)
251
68
 
252
- | Token | Light Default | Dark Default | Semantic Role |
253
- |-------|--------------|--------------|---------------|
69
+ | Token | Light Default | Dark Default | Role |
70
+ |-------|--------------|--------------|------|
254
71
  | `background` | `#ffffff` | `#0f0f0f` | Screen / page background |
255
- | `foreground` | `#1a1a1a` | `#fafafa` | Primary text — deep near-black, not pure black |
256
- | `card` | `#ffffff` | `#1c1c1c` | Card / elevated surface background |
257
- | `primary` | `#1a1a1a` | `#fafafa` | Primary action (buttons, selected states, active indicators) |
258
- | `primaryForeground` | `#ffffff` | `#0f0f0f` | Text/icon placed on primary-colored backgrounds |
72
+ | `foreground` | `#1a1a1a` | `#fafafa` | Primary text |
73
+ | `card` | `#ffffff` | `#1c1c1c` | Card / elevated surface |
74
+ | `primary` | `#1a1a1a` | `#fafafa` | Primary action (buttons, selected states) |
75
+ | `primaryForeground` | `#ffffff` | `#0f0f0f` | Text/icon on primary |
259
76
  | `border` | `#dddddd` | `#303030` | Borders, dividers, input outlines |
260
- | `destructive` | `#c72828` | `#ef5350` | Error / danger / delete actions |
261
- | `destructiveForeground` | `#ffffff` | `#ffffff` | Text/icon on destructive backgrounds |
262
- | `success` | `#1a7a45` | `#2e7d52` | Success / confirmation states |
263
- | `successForeground` | `#ffffff` | `#ffffff` | Text/icon on success backgrounds |
264
- | `warning` | `#9a5200` | `#f5a623` | Warning / caution states |
265
- | `warningForeground` | `#ffffff` | `#0f0f0f` | Text/icon on warning backgrounds |
266
- | `overlay` *(optional)* | `rgba(0,0,0,0.45)` | `rgba(0,0,0,0.45)` | Backdrop/overlay color behind sheets and dialogs |
267
- | `accent` *(optional)* | `#d4561d` | `#e87645` | Secondary brand accent (e.g. Airbnb coral). Falls back to `primary` |
268
- | `accentForeground` *(optional)* | same as `primaryForeground` | same as `primaryForeground` | Text/icon on accent backgrounds. Falls back to `primaryForeground` |
269
-
270
- ### Derived Tokens (ResolvedColors) read-only via useTheme().colors
271
-
272
- The full palette components consume. Never supply these directly — they are computed by `deriveColors()` from the 12 public tokens above.
273
-
274
- | Token | Derived From | Purpose |
275
- |-------|-------------|---------|
276
- | `foregroundSubtle` | `foreground` @ ~70% | Body text, subtitles, secondary content |
277
- | `foregroundMuted` | `foreground` @ ~62% | Captions, timestamps, placeholders |
278
- | `surface` | `background` slightly off-canvas | Chip backgrounds, input fills, tag backgrounds |
279
- | `surfaceStrong` | `background` stronger offset | Pressed/hover fill states |
280
- | `skeleton` | `background` @ ±10% | Skeleton placeholder higher contrast than surface for visibility |
281
- | `destructiveTint` | `destructive` blended to bg | Alert banner background, toast background |
282
- | `destructiveBorder` | `destructive` @ 30% | Alert banner border, badge outline |
283
- | `successTint` | `success` blended to bg | Success banner background |
77
+ | `destructive` | `#c72828` | `#ef5350` | Error / danger / delete |
78
+ | `destructiveForeground` | `#ffffff` | `#ffffff` | Text on destructive |
79
+ | `success` | `#1a7a45` | `#2e7d52` | Success / confirmation |
80
+ | `successForeground` | `#ffffff` | `#ffffff` | Text on success |
81
+ | `warning` | `#9a5200` | `#f5a623` | Warning / caution |
82
+ | `warningForeground` | `#ffffff` | `#0f0f0f` | Text on warning |
83
+ | `overlay` *(optional)* | `rgba(0,0,0,0.45)` | `rgba(0,0,0,0.45)` | Backdrop behind sheets |
84
+ | `accent` *(optional)* | `#d4561d` | `#e87645` | Brand accent (falls back to `primary`) |
85
+ | `accentForeground` *(optional)* | `primaryForeground` | `primaryForeground` | Text on accent |
86
+
87
+ ### Derived Tokens — 26 ResolvedColors (read-only via `useTheme().colors`)
88
+
89
+ | Token | Source | Purpose |
90
+ |-------|--------|---------|
91
+ | `foregroundSubtle` | `foreground` @ ~70% | Body text, subtitles |
92
+ | `foregroundMuted` | `foreground` @ ~62% | Captions, placeholders (WCAG AA 4.5:1) |
93
+ | `surface` | `background` off-canvas | Chip backgrounds, input fills |
94
+ | `surfaceStrong` | `background` stronger offset | Pressed/hover states |
95
+ | `skeleton` | `background` @ ±10% | Skeleton placeholder |
96
+ | `destructiveTint` | `destructive` to bg | Alert banner bg |
97
+ | `destructiveBorder` | `destructive` @ 30% | Alert banner border |
98
+ | `successTint` | `success` to bg | Success banner bg |
284
99
  | `successBorder` | `success` @ 30% | Success banner border |
285
- | `warningTint` | `warning` blended to bg | Warning banner background |
100
+ | `warningTint` | `warning` to bg | Warning banner bg |
286
101
  | `warningBorder` | `warning` @ 30% | Warning banner border |
287
- | `ring` | `= primary` | Focus ring color (always matches primary) |
288
- | `input` | `= border` | Input field border (always matches border) |
289
- | `separator` | `border` @ ±16-22% | Divider/separator line — deliberately darker than border |
290
- | `overlay` | `overlay` token or `rgba(0,0,0,0.45)` | Backdrop behind sheets and dialogs |
291
- | `accentResolved` | `accent` token or `= primary` | Resolved accent color — always present |
292
- | `accentForegroundResolved` | `accentForeground` token or `= primaryForeground` | Resolved text on accent — always present |
293
-
294
- **Usage example — building a custom component using derived tokens:**
102
+ | `ring` | `= primary` | Focus ring |
103
+ | `input` | `= border` | Input border |
104
+ | `separator` | `border` @ ±16-22% | Divider line |
105
+ | `overlay` | `overlay` token or `rgba(0,0,0,0.45)` | Sheet backdrop |
106
+ | `accentResolved` | `accent` token or `= primary` | Resolved accent |
107
+ | `accentForegroundResolved` | `accentForeground` or `= primaryForeground` | Resolved text on accent |
108
+
109
+ ### Color Utility
295
110
  ```tsx
296
- const { colors } = useTheme()
297
-
298
- // Text hierarchy
299
- <Text style={{ color: colors.foreground }}>Primary text</Text>
300
- <Text style={{ color: colors.foregroundSubtle }}>Secondary text</Text>
301
- <Text style={{ color: colors.foregroundMuted }}>Caption / timestamp</Text>
302
-
303
- // Surface fills (unselected chips, inactive backgrounds)
304
- <View style={{ backgroundColor: colors.surface }}>
305
-
306
- // Warning alert banner
307
- <View style={{ backgroundColor: colors.warningTint, borderColor: colors.warningBorder }}>
308
- <Text style={{ color: colors.warning }}>Warning message</Text>
111
+ import { withAlpha } from '@retray-dev/ui-kit'
112
+ // hex → rgba: withAlpha('#1a1a1a', 0.15) → 'rgba(26,26,26,0.15)'
309
113
  ```
310
114
 
311
- ### deriveColors export
312
-
313
- ```tsx
314
- import { deriveColors } from '@retray-dev/ui-kit'
315
-
316
- const resolved = deriveColors(myThemeColors, 'light')
317
- // resolved contains all 26 ResolvedColors tokens
318
- ```
115
+ ### Shape Language
116
+ Soft rounded rects everywhere. Buttons: `RADIUS.md=14px` (NOT pill). IconButton: `RADIUS.full` (circle). Inputs: 8px. Cards: 14px. Modals: 16px top corners only.
319
117
 
320
118
  ---
321
119
 
322
- ### Color Utilities
323
-
324
- **Import:** `import { withAlpha } from '@retray-dev/ui-kit'`
325
-
326
- Convert a hex color to rgba with the given alpha. Useful for semi-transparent backgrounds, borders, and overlays derived from theme colors.
327
-
328
- ```tsx
329
- import { useTheme, withAlpha } from '@retray-dev/ui-kit'
330
-
331
- const { colors } = useTheme()
332
-
333
- <View style={{ backgroundColor: withAlpha(colors.primary, 0.15) }}>
334
- <Text style={{ color: colors.primary }}>Tinted background</Text>
335
- </View>
336
- ```
337
-
338
- ---
339
-
340
- ## Design Tokens
341
-
342
- Static structural constants — no context or provider needed.
120
+ ### Design Tokens — Static Constants, No Provider Needed
343
121
 
344
122
  ```ts
345
123
  import { SPACING, ICON_SIZES, RADIUS, SHADOWS, BREAKPOINTS, TYPOGRAPHY } from '@retray-dev/ui-kit'
346
124
  ```
347
125
 
348
- ### SPACING — 8pt grid with 2pt micro-step
126
+ **SPACING — 8pt grid**
349
127
 
350
128
  | Key | Value | Use |
351
129
  |-----|-------|-----|
352
- | `xxs` | 2 | Micro gaps (icon to text in badges) |
130
+ | `xxs` | 2 | Micro gaps |
353
131
  | `xs` | 4 | Tight internal gaps |
354
132
  | `sm` | 8 | Component internal padding |
355
133
  | `md` | 12 | Medium gaps |
356
134
  | `base` | 16 | Default content padding |
357
135
  | `lg` | 24 | Section internal padding |
358
- | `xl` | 32 | Between major content blocks |
136
+ | `xl` | 32 | Between major blocks |
359
137
  | `xxl` | 48 | Section padding |
360
- | `section` | 64 | Major band separators (Airbnb hero → grid rhythm) |
361
-
362
- **Types:** `Spacing`, `SpacingKey`
363
-
364
- ```tsx
365
- <View style={{ gap: SPACING.md, padding: SPACING.base }} />
366
- ```
138
+ | `section` | 64 | Major band separators |
367
139
 
368
- ### ICON_SIZES
140
+ **ICON_SIZES**
369
141
 
370
142
  | Key | Value | Use |
371
143
  |-----|-------|-----|
372
- | `sm` | 14 | Badge icons, inline micro icons |
144
+ | `sm` | 14 | Badge icons |
373
145
  | `md` | 18 | Standard component icons |
374
146
  | `lg` | 22 | Larger inline icons |
375
147
  | `xl` | 28 | Feature icons |
376
148
  | `2xl` | 32 | Display icons |
377
149
 
378
- **Types:** `IconSize`, `IconSizeKey`
379
-
380
- ### RADIUS — Airbnb shape language
150
+ **RADIUS Airbnb shape language**
381
151
 
382
152
  | Key | Value | Used in |
383
153
  |-----|-------|---------|
384
- | `none` | 0 | No rounding |
154
+ | `none` | 0 | |
385
155
  | `xs` | 4 | Micro chips, tags |
386
156
  | `sm` | 8 | Inputs, Textarea, Select, Checkbox |
387
- | `md` | 14 | Cards, Buttons (all variants), MediaCard, AlertBanner, Toast, EmptyState |
157
+ | `md` | 14 | Cards, Buttons, MediaCard, AlertBanner, Toast |
388
158
  | `lg` | 20 | Sheet top corners |
389
159
  | `xl` | 32 | Large decorative elements |
390
160
  | `full` | 9999 | IconButton (circle), CategoryStrip chips |
391
161
 
392
- **Types:** `Radius`, `RadiusKey`
393
-
394
- ```tsx
395
- <View style={{ borderRadius: RADIUS.md }} />
396
- ```
397
-
398
- ### SHADOWS — Cross-platform shadow presets
399
-
400
- | Key | shadowOffset.height | shadowOpacity | shadowRadius | elevation | Use |
401
- |-----|---------------------|---------------|--------------|-----------|-----|
402
- | `sm` | 1 | 0.06 | 4 | 2 | Default card shadow |
403
- | `md` | 2 | 0.10 | 8 | 5 | Hover float / elevated elements |
404
- | `lg` | 6 | 0.16 | 16 | 10 | Modals, overlays |
405
- | `xl` | 12 | 0.24 | 24 | 18 | High-elevation dialogs |
162
+ **SHADOWS Cross-platform**
406
163
 
407
- ```tsx
408
- <View style={[styles.card, SHADOWS.sm]} />
409
- // Hover state
410
- <View style={[styles.card, hovered ? SHADOWS.md : SHADOWS.sm]} />
411
- ```
164
+ | Key | opacity | radius | elevation | Use |
165
+ |-----|---------|--------|-----------|-----|
166
+ | `sm` | 0.06 | 4 | 2 | Default card |
167
+ | `md` | 0.10 | 8 | 5 | Hover float |
168
+ | `lg` | 0.12 | 12 | 8 | Modals |
169
+ | `xl` | 0.18 | 24 | 16 | High-elevation |
412
170
 
413
- ### BREAKPOINTS
171
+ **BREAKPOINTS**
414
172
 
415
173
  | Key | Value | Use |
416
174
  |-----|-------|-----|
417
175
  | `wide` | 700 | Tablet / wide layout threshold |
418
176
 
419
- ```tsx
420
- const isWide = useWindowDimensions().width >= BREAKPOINTS.wide
421
- ```
422
-
423
- ### TYPOGRAPHY — 16 Airbnb-aligned variants
424
-
425
- All components use these tokens for text styling. Import and use in custom components for consistency.
177
+ **TYPOGRAPHY — 17 variants**
426
178
 
427
- | Key | Size | Weight | lineHeight | letterSpacing | Use |
428
- |-----|------|--------|-----------|---------------|-----|
429
- | `display-hero` | 64 | 700 | 70 | -1 | Large number display, balance totals |
179
+ | Key | Size | Weight | lh | ls | Use |
180
+ |-----|------|--------|----|----|-----|
181
+ | `display-hero` | 64 | 700 | 70 | -1 | Large number display |
430
182
  | `display-xl` | 28 | 700 | 40 | 0 | Screen titles, hero headings |
431
- | `display-lg` | 22 | 500 | 26 | -0.44 | Section headings |
432
- | `display-md` | 21 | 700 | 30 | 0 | Card titles, dialog titles |
433
- | `display-sm` | 20 | 600 | 24 | -0.18 | Sub-section headings |
434
- | `title-md` | 16 | 600 | 20 | 0 | Row titles, list item titles |
435
- | `title-sm` | 16 | 500 | 20 | 0 | Secondary titles |
183
+ | `display-lg` | 24 | 600 | 32 | -0.3 | Section headings |
184
+ | `display-md` | 20 | 600 | 28 | 0 | Card titles |
185
+ | `display-sm` | 18 | 600 | 24 | -0.18 | Sub-section headings |
186
+ | `title-md` | 17 | 600 | 22 | 0 | Row titles |
187
+ | `title-sm` | 15 | 500 | 20 | 0 | Secondary titles |
436
188
  | `body-md` | 16 | 400 | 24 | 0 | Primary body copy |
437
- | `body-sm` | 14 | 400 | 20 | 0 | Secondary body, descriptions |
438
- | `caption` | 14 | 500 | 18 | 0 | Labels above inputs, item captions |
439
- | `caption-sm` | 13 | 400 | 16 | 0 | Timestamps, metadata |
440
- | `badge-text` | 11 | 600 | 13 | 0 | Badge labels, small tags |
441
- | `micro-label` | 12 | 700 | 16 | 0 | Micro labels, overlines |
442
- | `uppercase-tag` | 10 | 700 | 13 | 0.8 | Uppercase decorative tags (auto-uppercase) |
443
- | `button-lg` | 16 | 500 | 20 | 0 | Button labels (md/lg size) |
444
- | `button-sm` | 14 | 500 | 18 | 0 | Button labels (sm size) |
445
-
446
- **Types:** `Typography`, `TypographyKey`
447
-
448
- ```tsx
449
- import { TYPOGRAPHY } from '@retray-dev/ui-kit'
450
-
451
- // Use in StyleSheet
452
- const styles = StyleSheet.create({
453
- heading: {
454
- ...TYPOGRAPHY['display-xl'],
455
- color: colors.foreground,
456
- },
457
- })
458
- ```
189
+ | `body-sm` | 14 | 400 | 20 | 0 | Secondary body |
190
+ | `caption` | 14 | 500 | 18 | 0 | Input labels |
191
+ | `caption-sm` | 13 | 400 | 16 | 0 | Timestamps |
192
+ | `badge-text` | 11 | 600 | 14 | 0 | Badge labels |
193
+ | `badge-text-md` | 13 | 600 | 16 | 0 | Badge labels (md size) |
194
+ | `micro-label` | 12 | 700 | 16 | 0 | Overlines |
195
+ | `uppercase-tag` | 11 | 700 | 14 | 0.6 | Decorative tags (auto-uppercase) |
196
+ | `button-lg` | 16 | 500 | 22 | 0 | Button (md/lg) |
197
+ | `button-sm` | 14 | 500 | 18 | 0 | Button (sm) |
459
198
 
460
199
  ---
461
200
 
462
- ## Migration Guide: v4 → v5
463
-
464
- ### Breaking Changes
465
-
466
- **Button variants renamed:**
467
- | v4 | v5 |
468
- |----|-----|
469
- | `outline` | `secondary` |
470
- | `ghost` | `text` |
471
- | `secondary` (filled gray) | removed — use Card/surface instead |
472
-
473
- **Text variants replaced:**
474
- | v4 | v5 equivalent |
475
- |----|---------------|
476
- | `h1` | `display-xl` |
477
- | `h2` | `display-lg` or `display-md` |
478
- | `h3` | `display-sm` |
479
- | `body` | `body-md` |
480
- | `label` | `title-sm` or `caption` |
481
- | `caption` | `caption-sm` |
482
-
483
- **Theme tokens changed:**
484
- | v4 | v5 |
485
- |----|-----|
486
- | `secondary`, `secondaryForeground` | removed (use `surface` derived token) |
487
- | `accent`, `accentForeground` | removed (use `surfaceStrong`) |
488
- | `muted` | removed → `surface` (derived) |
489
- | `mutedForeground` | removed → `foregroundSubtle` / `foregroundMuted` (derived) |
490
- | Added | `warning`, `warningForeground` |
491
- | Added (derived) | `warningTint`, `warningBorder` |
492
-
493
- **IconButton variant:**
494
- | v4 | v5 |
495
- |----|-----|
496
- | `ghost` | `text` |
201
+ ## Icons
497
202
 
498
- ---
203
+ ### Icon Component
204
+ ```tsx
205
+ import { Icon } from '@retray-dev/ui-kit'
206
+ <Icon name="home" size={24} color="#000" />
207
+ // Returns null if name not found — no crash
208
+ ```
499
209
 
500
- ## Migration Guide: v5 → v6
210
+ ### Resolution Order (first-match wins)
211
+ Feather > AntDesign > Entypo > FontAwesome5 > MaterialIcons > Ionicons
501
212
 
502
- ### New Components
213
+ **Feather is first** — most names resolve there (outlined style). Names with `-outline` suffix resolve to Ionicons (no Feather collision).
503
214
 
504
- **`MenuItem`** — Navigation row with icon, label, and optional right slot (`rightRender`). Replaces ad-hoc `ListItem` usage for settings/nav menus. Zero horizontal padding by design — consumer controls spacing.
215
+ ### `iconName` props on components
216
+ | Component | Prop(s) | Color |
217
+ |-----------|---------|-------|
218
+ | `Button` | `iconName`, `iconColor` | Variant label |
219
+ | `IconButton` | `iconName`, `iconColor` | Variant foreground |
220
+ | `Input` | `prefixIcon`, `prefixIconColor`, `suffixIcon`, `suffixIconColor` | `foregroundMuted` |
221
+ | `ListItem` | `leftIcon`, `leftIconColor`, `rightIcon`, `rightIconColor` | `foreground` / `foregroundMuted` |
222
+ | `Badge` | `iconName`, `iconColor` | Variant foreground |
223
+ | `Toggle` | `iconName`, `iconColor`, `activeIconName`, `activeIconColor` | `foregroundMuted` / `primary` |
224
+ | `AlertBanner` | `iconName`, `iconColor` | Title color |
225
+ | `EmptyState` | `iconName`, `iconColor` | `foregroundMuted` |
226
+ | `MediaCard` | `actionIconName` | White |
227
+ | `AvatarGroup` | — | — (avatars use own colors) |
228
+ | `Chip` | `iconName` | Variant foreground |
229
+ | `DetailRow` | `leftIconName`, `leftIconColor`, `rightIconName`, `rightIconColor` | `foregroundMuted` |
230
+ | `MenuItem` | `iconName`, `iconColor` | `foreground` |
231
+
232
+ ### curatedIcons.ts
233
+ 8 categories, ~20 icons each, all Feather (outlined). Actions, comunicación, navegación, comida, negocios, perfil, multimedia, texto.
234
+
235
+ ### Rules
236
+ - Prefer Feather (outlined) over FA5 solid (filled). FA5 `defaultStyle='regular'` but some names only exist in `solid` — avoid those.
237
+ - Icon lookup introspects `glyphMap` per call — no caching between renders.
505
238
 
239
+ ### `getResponsiveFontSize` utility
506
240
  ```tsx
507
- import { MenuItem } from '@retray-dev/ui-kit'
508
- // variants: 'plain' (default) | 'card'
509
- <MenuItem label="Profile" subtitle="Edit your details" iconName="user" onPress={() => {}} />
510
- <MenuItem label="Notifications" iconName="bell" rightRender={<Switch value={on} onValueChange={setOn} />} showChevron={false} onPress={() => {}} />
241
+ import { getResponsiveFontSize } from '@retray-dev/ui-kit'
242
+ const fontSize = getResponsiveFontSize(text, 48)
243
+ // Steps: ≤10→max, ≤12→max-4, ≤14→max-6, >14→max-8
511
244
  ```
512
245
 
513
- ### Breaking Changes
246
+ ---
514
247
 
515
- **Sheet keyboard defaults changed:**
516
- | Prop | v5 default | v6 default |
517
- |------|-----------|-----------|
518
- | `keyboardBehavior` | `'fillParent'` (Android) / `'interactive'` (iOS) | `'interactive'` (both) |
519
- | `android_keyboardInputMode` | `'adjustResize'` | `'adjustPan'` |
248
+ ## Animations & Interactions
520
249
 
521
- If you relied on `adjustResize`, set it explicitly: `android_keyboardInputMode="adjustResize"`.
250
+ ### Pressables (pressto)
251
+ | Pressable | Scale | Used by |
252
+ |---|---|---|
253
+ | `PressableButton` | 0.95 | Button, IconButton, Toggle, Checkbox |
254
+ | `PressableCard` | 0.98 | Card, MediaCard, Stats, Pressable |
255
+ | `PressableRow` | 0.97 | ListItem, MenuItem |
256
+ | `PressableChip` | 0.94 | Chip |
257
+ | `PressableTab` | 0.95 | Tabs triggers |
522
258
 
523
- **Toast API simplified:**
259
+ All use `enabled={!disabled}`, `rippleColor="transparent"`, `touchSoundDisabled`.
524
260
 
525
- ```tsx
526
- // v5hook-only API
527
- const { toast } = useToast()
528
- toast.success('Done')
261
+ ### Spring Presets
262
+ - `glide`: `{ stiffness: 380, damping: 38, mass: 1.0 }` sliding indicators
263
+ - `elastic`: `{ stiffness: 320, damping: 22, mass: 0.7 }` Switch thumb, RadioGroup dot
529
264
 
530
- // v6 — direct import preferred (hook still works for compat)
531
- import { toast } from '@retray-dev/ui-kit'
532
- toast.success('Done')
533
- ```
265
+ ### Timing Presets
266
+ - `state`: 160ms (checkbox/toggle color)
267
+ - `expand` / `collapse`: 240ms / 200ms (Accordion)
268
+ - `shimmer`: 1400ms (Skeleton)
534
269
 
535
- ### New Theme Tokens
270
+ ### EaseView (react-native-ease)
271
+ Declarative color/opacity transitions: Switch track, Checkbox fill, RadioGroup dot. UI thread.
536
272
 
537
- Three optional `ThemeColors` tokens — fall back gracefully if omitted:
273
+ ### Reanimated v4
274
+ Complex animations only (gestures, layout interpolations). Simple opacity: use RN `Animated` + `useNativeDriver`.
538
275
 
539
- | Token | Default | Purpose |
540
- |-------|---------|---------|
541
- | `overlay` | `rgba(0,0,0,0.45)` | Backdrop color behind sheets and dialogs |
542
- | `accent` | `= primary` | Secondary brand accent color |
543
- | `accentForeground` | `= primaryForeground` | Text on accent backgrounds |
276
+ ---
544
277
 
545
- Access resolved values via `useTheme().colors.overlay`, `.accentResolved`, `.accentForegroundResolved`.
278
+ ## Sheets (BottomSheetModal)
546
279
 
547
- ### Token Corrections
280
+ ### Strict rules (from @gorhom/bottom-sheet)
281
+ 1. **Always `BottomSheetModal`** — never `BottomSheet` base. `present()` in handler directly (not `useEffect`).
282
+ 2. **`enableDynamicSizing`** — no `snapPoints` (mutually exclusive)
283
+ 3. **`topInset={insets.top}`** — prevents notch overlap
284
+ 4. **`keyboardBehavior="interactive"`** + **`android_keyboardInputMode="adjustPan"`** — keyboard
285
+ 5. **`SheetTextInput`** inside sheets — never `<TextInput />`
286
+ 6. **`onDismiss`** for cleanup, not `onClose`
287
+ 7. **`renderBackdrop`** wrapped in `useCallback`
288
+ 8. **`enableBlurKeyboardOnGesture={true}`** — dismiss keyboard on drag
289
+ 9. **`BottomSheetModalProvider`** re-exported, included in `RetrayProvider`
548
290
 
549
- - `TYPOGRAPHY['uppercase-tag']` size corrected to `10` (was documented as `8`, actual value was always `10`)
291
+ ### Input inside Sheet
292
+ Use `<Input sheetMode />` — transparently swaps to `BottomSheetTextInput`. For low-level: `SheetTextInput`.
550
293
 
551
294
  ---
552
295
 
553
- ## Migration Guide: v6 → v7
554
-
555
- ### Breaking Changes
296
+ ## Haptics (expo-haptics)
556
297
 
557
- **No prop API changes** all component imports and props are identical. This is a breaking version bump due to visual behavior changes that affect rendered output.
298
+ Web-safe via static `import * as Haptics` with `Platform.OS === 'web'` guard at call sites.
558
299
 
559
- **Typography scale corrected:**
300
+ | Function | Usage |
301
+ |---|---|
302
+ | `selectionAsync()` | Checkbox, Switch, Toggle, RadioGroup, Select, Slider (step), Accordion, ListItem, MenuItem, ConfirmDialog cancel |
303
+ | `impactLight()` / `impactMedium()` / `impactHeavy()` | Button, Sheet open, ConfirmDialog open, Stats |
304
+ | `notificationSuccess()` / `notificationError()` / `notificationWarning()` | ConfirmDialog confirm, form validation |
560
305
 
561
- | Token | v6 | v7 | Reason |
562
- |-------|----|----|--------|
563
- | `display-lg` | 22px / 500 | 24px / 600 | Removed weight inversion vs display-md |
564
- | `display-md` | 21px / 700 | 20px / 600 | 4px gap preserved; weight normalised |
565
- | `title-md` | 16px / 600 | 17px / 600 | Now visibly distinct from title-sm |
566
- | `title-sm` | 16px / 500 | 15px / 500 | Was same px as title-md |
567
- | `uppercase-tag` | 10px / 0.8 letterSpacing | 11px / 0.6 | Below Apple HIG 11pt minimum |
568
- | `badge-text-md` | (missing) | 13px / 600 | New canonical token for Badge md |
569
-
570
- **Default color values changed (WCAG AA fixes):**
571
-
572
- | Token | v6 | v7 | WCAG impact |
573
- |-------|----|----|-------------|
574
- | `foregroundMuted` opacity | 0.38 (~#ababab) | 0.62 (~#767676) | 2.2:1 ❌ → 4.5:1 ✓ |
575
- | `foregroundSubtle` opacity | 0.55 (~#858585) | 0.70 (~#646464) | 3.5:1 ❌ → 5.9:1 ✓ |
576
- | `warning` (light) | `#e67e00` | `#9a5200` | 2.86:1 ❌ → 5.86:1 ✓ |
577
- | `warningForeground` (dark) | `#ffffff` | `#0f0f0f` | Dark text on amber, 8.6:1 ✓ |
578
- | `destructive` (light) | `#e53935` | `#c72828` | 4.22:1 ❌ → 5.59:1 ✓ |
579
- | `accent` (light) | `= primary` | `#d4561d` | Explicit brand accent |
580
- | `accent` (dark) | `= primary` | `#e87645` | Warm accent for dark surfaces |
306
+ ---
581
307
 
582
- **Component visual behavior changes:**
308
+ ## Theming
583
309
 
584
- | Component | Change |
585
- |-----------|--------|
586
- | `AlertBanner` | Background now uses semantic tint per variant (was white card); 1px semantic border added |
587
- | `Button` (text variant) | Label color: `foreground` → `accentResolved` — clearer CTA signal |
588
- | `Card` (elevated variant) | `borderWidth: 0` — shadow is sole depth signal; border was redundant |
589
- | `Chip` | `paddingVertical` doubled to hit 44pt WCAG 2.5.5 tap target |
590
- | `Input` / `Textarea` | Border: 1px at rest → animates to 2px on focus (was always 2px) |
591
- | `Switch` | Off-state now shows animated 1.5px border (invisible on white surfaces before) |
592
- | `Tabs` | Labels always `Sohne-SemiBold`; active = color only — eliminates layout reflow on selection |
593
- | `Checkbox` / `RadioGroup` | Disabled `opacity: 0.45` now on full row (was box only) |
594
- | `Toast` | `richColors={true}` — semantic variants now visually distinct by color |
310
+ ### Consumer supplies 12 ThemeColors
311
+ ```tsx
312
+ import { ThemeProvider, ThemeColors } from '@retray-dev/ui-kit'
595
313
 
596
- **If you customised any of these values** via `ThemeProvider` overrides, your overrides continue to take precedence — default palette changes only affect apps using the out-of-the-box defaults.
314
+ const customLight: ThemeColors = {
315
+ background: '#ffffff',
316
+ foreground: '#1a1a1a',
317
+ primary: '#d4561d',
318
+ }
319
+ ```
597
320
 
598
- ### New Compound Component Section
321
+ ### deriveColors()
322
+ Computes 26 `ResolvedColors` from 12 `ThemeColors`. Components consume `useTheme().colors` — never raw `ThemeColors`.
599
323
 
600
- The example app now has a dedicated **Compound Components** section showcasing `Card`, `ButtonGroup`, `Form`, `ListGroup`, and `MenuGroup` with all sub-components and variants.
324
+ ### Structural tokens (not in theme)
325
+ Spacing, radii, shadows, typography = static `as const` exports from `tokens.ts`. No provider needed.
601
326
 
602
327
  ---
603
328
 
604
- ## Migration Guide: v7 → v8
329
+ ## Toast (sonner-native)
605
330
 
606
- ### Breaking Changes
331
+ ```tsx
332
+ import { toast } from 'sonner-native'
333
+ // or
334
+ import { useToast } from '@retray-dev/ui-kit'
335
+ const { toast } = useToast()
607
336
 
608
- **1. New required peer dependency: `sonner-native`**
609
- Toast (`ToastProvider` / `toast` / `useToast`) imports `sonner-native`, but it was never declared as a peer — installs silently lacked it and Metro failed with `Unable to resolve "sonner-native"`. It is now a declared peer. Install it:
610
- ```bash
611
- pnpm add sonner-native
337
+ toast('Hello', { description: 'World' })
338
+ toast.success('Saved')
339
+ toast.error('Failed', { description: 'Check network' })
340
+ toast.promise(save(), { loading: 'Saving...', success: 'Done', error: 'Failed' })
612
341
  ```
613
- (You already need its companions `react-native-svg` and `react-native-screens`.)
614
342
 
615
- **2. `@gorhom/bottom-sheet` peer range tightened to `>=5.2.0`**
616
- 5.1.x crashes with Reanimated v4 (`useWorkletCallback is not a function`), which broke `Sheet`, `ConfirmDialog`, and `Select` on open. The range no longer admits broken versions. Pin `5.2.8` if you also use Worklets `0.5.x`.
343
+ `Toaster` rendered inside `ToastProvider`. Config: `richColors: false`, `swipeToDismissDirection="up"`, `duration: 4000`.
617
344
 
618
- **3. `./fonts` stays at `src/fonts.ts` (unchanged)**
619
- The `./fonts` export deliberately points at `src/fonts.ts`, not `dist`. The `.otf` files ship as raw assets in `src/assets/fonts/` and are resolved by Metro at your build time via `require()`. Compiling this entry to `dist` and repointing the `require()` paths breaks Metro asset resolution under pnpm symlinked workspaces (`requiring unknown module ../src/assets/fonts/...`), so it is intentionally left as source. No API change — import as before:
620
- ```ts
621
- import { SohneFonts } from '@retray-dev/ui-kit/fonts'
622
- ```
345
+ ---
623
346
 
624
- ### New packaging & DX
347
+ ## Data Display Patterns
625
348
 
626
- - **`RetrayProvider`** — one wrapper replacing the five-provider boilerplate (see Setup below).
627
- - **Dev font guard** — if you render a UI kit component without loading `SohneFonts`, `Text` now logs a one-time `console.warn` in dev (silent in production) instead of failing invisibly.
349
+ ### ListItem
350
+ - `rightActions` prop for swipe-to-reveal (iOS Mail style)
351
+ - Uses `PressableRow` (scale 0.97)
352
+ - Haptic: `selectionAsync()` on press
353
+ - Zero horizontal padding by design
628
354
 
629
- ### New components
355
+ ### ListGroup
356
+ - Optional `Header` / `Footer` sub-components
357
+ - Auto-separators between items except last
630
358
 
631
- `RetrayProvider`, `AppHeader`, `TabBar`, `PagerDots`, `SelectableGrid`, `PricingCard`, `ErrorBoundary`, `ImageViewer`, plus `Skeleton.MediaCard` / `Skeleton.ListItem` sub-skeletons and the optional deep-import-only `HolographicCard` (Skia foil card). `MonthPicker` gained `minValue`/`maxValue` and `Date` bridge helpers.
359
+ ### MenuItem / MenuGroup
360
+ - Settings/nav rows. Auto-separators.
361
+ - `rightRender` for Switch, Badge, etc.
362
+ - `variant="card"` for standalone surface
632
363
 
633
- ### Optional peers (only if you use the matching component)
364
+ ### Chip / ChipGroup
365
+ - Multi-select toggle chips. `PressableChip` (scale 0.94)
366
+ - `ChipGroup` wraps to rows; `CategoryStrip` scrolls horizontally
634
367
 
635
- - `@shopify/react-native-skia` + `expo-sensors` → `HolographicCard` (deep-import only, not in the main barrel).
368
+ ### LabelValue
369
+ - Key-value rows (label left, value right)
370
+ - Icon support
636
371
 
637
- ---
372
+ ### DetailRow
373
+ - Ticket/receipt rows with `···` dotted separator
374
+ - Icon + label + value
638
375
 
639
- ## Migration Guide: v9 → v10
640
-
641
- ### Breaking Changes
642
-
643
- **1. `SohneFonts` export removed from `@retray-dev/ui-kit/fonts`**
644
-
645
- The `SohneFonts` object with `require()` calls is no longer exported. Metro cannot reliably resolve `require()` from inside `node_modules`, especially in monorepos. The export now returns `undefined` and logs a deprecation warning.
646
-
647
- **New approach — postinstall script:**
648
- 1. When you install `@retray-dev/ui-kit`, a **postinstall script** automatically copies 28 `.otf` font files to `assets/fonts/sohne/` in your project root
649
- 2. Define `SohneFonts` locally in your `App.tsx` with static `require()` calls (see below)
650
- 3. Pass it to `expo-font`'s `useFonts()` hook
651
-
652
- **Migration:**
653
-
654
- ```diff
655
- // App.tsx
656
- import { useFonts } from 'expo-font'
657
- -import { SohneFonts } from '@retray-dev/ui-kit/fonts'
658
-
659
- +// Fonts copied to assets/fonts/sohne/ by @retray-dev/ui-kit postinstall
660
- +const SohneFonts = {
661
- + 'Sohne-ExtraLight': require('./assets/fonts/sohne/Sohne-ExtraLight.otf'),
662
- + 'Sohne-ExtraLightItalic': require('./assets/fonts/sohne/Sohne-ExtraLightItalic.otf'),
663
- + 'Sohne-Light': require('./assets/fonts/sohne/Sohne-Light.otf'),
664
- + 'Sohne-LightItalic': require('./assets/fonts/sohne/Sohne-LightItalic.otf'),
665
- + 'Sohne-Regular': require('./assets/fonts/sohne/Sohne-Regular.otf'),
666
- + 'Sohne-Italic': require('./assets/fonts/sohne/Sohne-Italic.otf'),
667
- + 'Sohne-Medium': require('./assets/fonts/sohne/Sohne-Medium.otf'),
668
- + 'Sohne-MediumItalic': require('./assets/fonts/sohne/Sohne-MediumItalic.otf'),
669
- + 'Sohne-SemiBold': require('./assets/fonts/sohne/Sohne-SemiBold.otf'),
670
- + 'Sohne-SemiBoldItalic': require('./assets/fonts/sohne/Sohne-SemiBoldItalic.otf'),
671
- + 'Sohne-Bold': require('./assets/fonts/sohne/Sohne-Bold.otf'),
672
- + 'Sohne-BoldItalic': require('./assets/fonts/sohne/Sohne-BoldItalic.otf'),
673
- + 'Sohne-ExtraBold': require('./assets/fonts/sohne/Sohne-ExtraBold.otf'),
674
- + 'Sohne-ExtraBoldItalic': require('./assets/fonts/sohne/Sohne-ExtraBoldItalic.otf'),
675
- + 'SohneMono-ExtraLight': require('./assets/fonts/sohne/SohneMono-ExtraLight.otf'),
676
- + 'SohneMono-ExtraLightItalic': require('./assets/fonts/sohne/SohneMono-ExtraLightItalic.otf'),
677
- + 'SohneMono-Light': require('./assets/fonts/sohne/SohneMono-Light.otf'),
678
- + 'SohneMono-LightItalic': require('./assets/fonts/sohne/SohneMono-LightItalic.otf'),
679
- + 'SohneMono-Regular': require('./assets/fonts/sohne/SohneMono-Regular.otf'),
680
- + 'SohneMono-Italic': require('./assets/fonts/sohne/SohneMono-Italic.otf'),
681
- + 'SohneMono-Medium': require('./assets/fonts/sohne/SohneMono-Medium.otf'),
682
- + 'SohneMono-MediumItalic': require('./assets/fonts/sohne/SohneMono-MediumItalic.otf'),
683
- + 'SohneMono-SemiBold': require('./assets/fonts/sohne/SohneMono-SemiBold.otf'),
684
- + 'SohneMono-SemiBoldItalic': require('./assets/fonts/sohne/SohneMono-SemiBoldItalic.otf'),
685
- + 'SohneMono-Bold': require('./assets/fonts/sohne/SohneMono-Bold.otf'),
686
- + 'SohneMono-BoldItalic': require('./assets/fonts/sohne/SohneMono-BoldItalic.otf'),
687
- + 'SohneMono-ExtraBold': require('./assets/fonts/sohne/SohneMono-ExtraBold.otf'),
688
- + 'SohneMono-ExtraBoldItalic': require('./assets/fonts/sohne/SohneMono-ExtraBoldItalic.otf'),
689
- +}
376
+ ### Skeleton
377
+ - `Skeleton.MediaCard`, `Skeleton.ListItem`, `Skeleton.List` (6 items, 2 columns)
378
+ - Shimmer animation loops every 1200ms
690
379
 
691
- export default function App() {
692
- const [fontsLoaded] = useFonts(SohneFonts)
693
- // ...
694
- }
695
- ```
380
+ ---
696
381
 
697
- **.gitignore recommendation:**
698
- ```gitignore
699
- # Sohne fonts — copied by @retray-dev/ui-kit postinstall
700
- assets/fonts/sohne/
701
- ```
382
+ ## Accessibility (WCAG AA)
702
383
 
703
- **Why this change:** Metro bundler requires `require()` calls to originate from the consumer's source tree, not from `node_modules`. The previous approach worked in simple setups but failed in monorepos and symlinked workspaces.
384
+ - `allowFontScaling={true}` on all `<Text>` and `<TextInput>`
385
+ - Touch targets ≥44pt
386
+ - `foregroundMuted` = `#9a9a9a` (dark) / `#a2a2a2` (light) — ≥4.5:1 contrast
387
+ - Accessibility props passed through via `...props`
704
388
 
705
389
  ---
706
390
 
707
- ## Migration Guide: v10 → v11
708
-
709
- ### New — public utility
710
-
711
- **`withAlpha(hex, alpha)`** — hex-to-rgba color helper, now exported from the package root. Useful for semi-transparent overlays derived from theme colors without adding a new token.
391
+ ## Deep Import Pattern
712
392
 
393
+ Prefer deep imports in production for smaller bundle:
713
394
  ```tsx
714
- import { useTheme, withAlpha } from '@retray-dev/ui-kit'
715
-
716
- const { colors } = useTheme()
717
- <View style={{ backgroundColor: withAlpha(colors.primary, 0.15) }} />
395
+ import { Button } from '@retray-dev/ui-kit/Button' // deep
396
+ // vs
397
+ import { Button } from '@retray-dev/ui-kit' // barrel
718
398
  ```
719
399
 
720
- ### Updated
721
-
722
- - `Stats` component promoted from internal/experimental to public (`Stats` + `Stats.Group`).
723
- - Documentation refreshed for `IconPicker` and `NumberStepper`.
724
-
725
- No breaking changes in v11. Safe minor upgrade from v10.
400
+ Deep-import only (NOT in barrel):
401
+ ```tsx
402
+ import { HolographicCard } from '@retray-dev/ui-kit/HolographicCard'
403
+ ```
726
404
 
727
405
  ---
728
406
 
729
- ## Migration Guide: v11 → v12
730
-
731
- ### Breaking Changes
407
+ ## Anti-Patterns
732
408
 
733
- `Sheet` and `ConfirmDialog` were rewritten on top of `@gorhom/bottom-sheet`'s **`BottomSheetModal`** (lazy-mounted, `present()` / `dismiss()` driven) — replacing the old `BottomSheet` + `index={-1}` + `snapToIndex(0)` pattern. This fixes timing issues with `enableDynamicSizing` and unifies the API with the rest of the gorhom ecosystem.
409
+ | Anti-pattern | Why | Fix |
410
+ |---|---|---|
411
+ | `<TextInput />` inside Sheet | Keyboard broken | Use `<Input sheetMode />` or `<SheetTextInput />` |
412
+ | `BottomSheet` base with `snapToIndex` | Fragile timing | Use `BottomSheetModal` with `present()` |
413
+ | `snapPoints` + `enableDynamicSizing` | Runtime error | Use one or the other |
414
+ | Static import of optional peer | Crash at module init | Dynamic `import()` in handler |
415
+ | Bundling native modules | Duplicate Context | Externalize in tsup |
416
+ | `--legacy-peer-deps` | Hides conflicts | Use `overrides` in package.json |
734
417
 
735
- **1. Removed `responsive` / `dialogMaxWidth` props from `Sheet` and `ConfirmDialog`**
418
+ ---
736
419
 
737
- The previous wide-screen fallback that bypassed gorhom with a native `Modal` + `ScrollView` was a partial workaround (see REGLA 1). It has been deleted. `@gorhom/bottom-sheet` handles responsive behavior natively — the modal simply renders inside its modal layer at the device width.
420
+ ### Image
738
421
 
739
- ```diff
740
- <Sheet
741
- open={open}
742
- onClose={() => setOpen(false)}
743
- title="Options"
744
- - responsive
745
- - dialogMaxWidth={600}
746
- />
747
- ```
422
+ **Import:** `import { Image } from '@retray-dev/ui-kit'`
748
423
 
749
- **2. `onClose` is the only close handler**
424
+ **When to use:** Image display with automatic fallback when source is null or fails to load. Wraps `expo-image` for performant caching and cross-fade transitions. Supports aspect ratio containers.
750
425
 
751
- `onClose` is called from `BottomSheetModal.onDismiss` the native gorhom callback. The previous `onClose` prop (passed directly to `BottomSheet`) was redundant. No public-API change for consumers; this is an internal alignment.
426
+ | Prop | Type | Default | Notes |
427
+ |------|------|---------|-------|
428
+ | src | `string \| null` | — | Image URI. `null` or load failure shows fallback/skeleton |
429
+ | fallback | `ReactNode` | — | Custom fallback when src is null or image fails |
430
+ | aspectRatio | `number` | — | Width/height ratio — sets container proportionally (e.g. `16/9`, `4/3`) |
431
+ | borderRadius | `number` | `0` | Corner radius on the image |
432
+ | style | `ImageStyle` | — | — |
752
433
 
753
- **3. `Sheet` requires `BottomSheetModalProvider` at app root**
434
+ All remaining `expo-image` props (`contentFit`, `transition`, `placeholder`, etc.) pipe through.
754
435
 
755
- `RetrayProvider` already wires it. If you assemble providers manually, ensure `BottomSheetModalProvider` sits inside `GestureHandlerRootView`:
436
+ **Fallback behavior:** When `src` is null/falsy or the image errors on load:
437
+ - If `fallback` is provided: renders fallback node inside a container
438
+ - Otherwise: renders a `skeleton`-colored placeholder matching the image dimensions
756
439
 
440
+ **Examples:**
757
441
  ```tsx
758
- <SafeAreaProvider initialMetrics={initialWindowMetrics}>
759
- <GestureHandlerRootView style={{ flex: 1 }}>
760
- <ThemeProvider>
761
- <BottomSheetModalProvider>
762
- <ToastProvider>{/* app */}</ToastProvider>
763
- </BottomSheetModalProvider>
764
- </ThemeProvider>
765
- </GestureHandlerRootView>
766
- </SafeAreaProvider>
442
+ <Image src="https://example.com/photo.jpg" aspectRatio={4/3} borderRadius={12} />
443
+ <Image src={null} fallback={<Icon name="image" size={32} color={colors.foregroundMuted} />} />
444
+ <Image src={avatar} borderRadius={40} style={{ width: 80, height: 80 }} />
445
+ <Image src={banner} aspectRatio={16/9} contentFit="cover" transition={500} />
767
446
  ```
768
447
 
769
- **4. Keyboard prop renames (no consumer action — defaults match v11)**
770
-
771
- | Prop | v11 | v12 default |
772
- |------|-----|-------------|
773
- | `keyboardBehavior` | `'interactive'` | `'interactive'` (unchanged) |
774
- | `android_keyboardInputMode` | `'adjustPan'` | `'adjustPan'` (unchanged) |
775
-
776
- **5. Removed top-level imports of `Modal`, `ScrollView`, `useWindowDimensions`, `BREAKPOINTS` from `Sheet.tsx`**
777
-
778
- Internal only — no public API.
448
+ ---
779
449
 
780
- ### Behavioral changes (no code change required)
450
+ ### ScreenContainer
781
451
 
782
- - **Backdrop / swipe close is now driven by gorhom's modal lifecycle** — `onClose` fires once on full dismiss instead of on every interaction. State-setter closures (e.g. `setOpen(false)`) are safe to call from `onClose` without causing "dismiss on unmounted" loops.
783
- - **`topInset={insets.top}`** is now applied automatically from safe-area context — sheet never crosses the notch / status bar on iOS or Android.
784
- - **`snapPoints` + `enableDynamicSizing` are mutually exclusive.** If you pass `snapPoints`, dynamic sizing is disabled and the sheet snaps to those points. Omit `snapPoints` to use dynamic sizing (default).
452
+ **Import:** `import { ScreenContainer } from '@retray-dev/ui-kit'`
785
453
 
786
- ### New recommended pattern
454
+ **When to use:** Consistent screen-level layout wrapper. Sets background color, safe area padding, optional scroll, keyboard avoidance, and centers content with a max-width constraint for wide screens.
787
455
 
788
- **Use `Input` with `sheetMode` inside a Sheet** instead of `SheetTextInput` directly. The new `sheetMode` prop on `Input` swaps in `BottomSheetTextInput` for keyboard-aware focus/blur handling while preserving the full `Input` API (label, error, hint, prefix/suffix, icons, type="password").
456
+ | Prop | Type | Default | Notes |
457
+ |------|------|---------|-------|
458
+ | children | `ReactNode` | required | Screen content |
459
+ | maxWidth | `number` | `700` (BREAKPOINTS.wide) | Max content width. Set to `0` to disable centering |
460
+ | scroll | `boolean` | `false` | Wrap content in ScrollView |
461
+ | backgroundColor | `string` | Theme `background` | Override background color |
462
+ | paddingTop | `number` | Safe area top | Override top padding |
463
+ | paddingBottom | `number` | Safe area bottom | Override bottom padding |
464
+ | paddingHorizontal | `number` | `0` | Horizontal padding |
465
+ | keyboard | `boolean` | `false` | Enable KeyboardAvoidingView (iOS: padding behavior) |
466
+ | style | `ViewStyle` | — | Additional container styles |
467
+
468
+ **Behavior:** Wraps children in a centered View (maxWidth with `alignSelf: 'center'`). When `keyboard={true}`, wraps in `KeyboardAvoidingView`. When `scroll={true}`, wraps in `ScrollView` with `keyboardShouldPersistTaps="handled"`.
789
469
 
470
+ **Examples:**
790
471
  ```tsx
791
- <Sheet open={open} onClose={() => setOpen(false)} title="Add note">
792
- <Input
793
- label="Note"
794
- placeholder="Type your note..."
795
- value={note}
796
- onChangeText={setNote}
797
- sheetMode
798
- />
799
- <Button label="Save" fullWidth onPress={handleSave} />
800
- </Sheet>
472
+ // Basic screen
473
+ <ScreenContainer>
474
+ <Text variant="display-xl">Home</Text>
475
+ {/* screen content */}
476
+ </ScreenContainer>
477
+
478
+ // Scrollable with keyboard avoidance
479
+ <ScreenContainer scroll keyboard paddingHorizontal={16}>
480
+ <Input label="Name" />
481
+ <Input label="Email" />
482
+ <Button label="Submit" fullWidth />
483
+ </ScreenContainer>
484
+
485
+ // Wide desktop constraint
486
+ <ScreenContainer maxWidth={700} paddingHorizontal={16}>
487
+ <Text variant="display-lg">Dashboard</Text>
488
+ </ScreenContainer>
801
489
  ```
802
490
 
803
- `SheetTextInput` is still re-exported for low-level use.
804
-
805
491
  ---
806
492
 
807
- ## Migration Guide: v12.0 → v12.1
493
+ ### VirtualizedList
808
494
 
809
- ### New IconPicker feedback pattern (REGLA 4)
495
+ **Import:** `import { VirtualizedList } from '@retray-dev/ui-kit'`
810
496
 
811
- `IconPicker` now follows the "no frozen screen" rule. When the user taps the trigger, the sheet presents **immediately** (no `useEffect` delay). While the inner grid measures its container, a centered `<Spinner />` is shown inside the sheet so the user sees visible feedback. The grid fades in as soon as `onLayout` fires.
497
+ **When to use:** Performant sectioned lists with sticky headers, pull-to-refresh, and empty state. Built on React Native `SectionList` for memory-efficient rendering of large datasets.
812
498
 
813
- This is now the canonical pattern for any sheet whose content needs to measure or load before rendering — apply it to new overlay components.
499
+ | Prop | Type | Default | Notes |
500
+ |------|------|---------|-------|
501
+ | sections | `VirtualizedListSection<T>[]` | required | `{ title?: string, data: T[] }` — sections with optional headers |
502
+ | renderItem | `(info: SectionListRenderItemInfo<T>) => ReactElement` | required | Row renderer for each item |
503
+ | emptyTitle | `string` | `'Sin contenido'` | Title shown when no data |
504
+ | emptyDescription | `string` | — | Description below empty title |
505
+ | emptyComponent | `ReactNode` | — | Custom empty state (overrides emptyTitle/emptyDescription) |
506
+ | refreshing | `boolean` | `false` | Pull-to-refresh loading state |
507
+ | onRefresh | `() => void` | — | Pull-to-refresh handler |
508
+ | stickyHeaders | `boolean` | `true` | Section headers stick to top while scrolling |
509
+ | headerColor | `string` | `foregroundMuted` | Section header text color override |
510
+ | style | `ViewStyle` | — | List container style |
814
511
 
815
- ### New race-condition fix in `Sheet` and `ConfirmDialog`
512
+ Also accepts all `SectionListProps` (e.g. `ListHeaderComponent`, `ListFooterComponent`, `ItemSeparatorComponent`).
816
513
 
817
- Both components now track a `wasOpened` ref so `dismiss()` is only called after the sheet has been presented at least once. This eliminates a class of crashes that occurred when the parent component unmounted (or the `open`/`visible` prop flipped) before the gorham modal had a chance to mount.
514
+ **Section headers:** Uppercase 13pt SemiBold text. Sections with empty `data` arrays are filtered out automatically.
818
515
 
819
- No consumer action required the fix is internal.
516
+ **Empty state:** When all sections have empty data, shows centered emptyTitle/emptyDescription or custom emptyComponent.
820
517
 
821
- ### New — expanded curated icon library
518
+ **Examples:**
519
+ ```tsx
520
+ // Simple sectioned list
521
+ <VirtualizedList
522
+ sections={[
523
+ { title: 'Fruits', data: ['Apple', 'Banana', 'Cherry'] },
524
+ { title: 'Vegetables', data: ['Carrot', 'Broccoli'] },
525
+ ]}
526
+ renderItem={({ item }) => <ListItem title={item} showSeparator />}
527
+ />
528
+
529
+ // With pull-to-refresh
530
+ <VirtualizedList
531
+ sections={data}
532
+ renderItem={renderRow}
533
+ refreshing={loading}
534
+ onRefresh={handleRefresh}
535
+ />
822
536
 
823
- `src/utils/curatedIcons.ts` has been expanded: every category now carries 42+ icons (some up to 66), totaling ~600 themed icons across the 12 categories. Selection rules are now codified in REGLA 5 — only Feather, Ionicons `-outline`, and themed FA5/Entypo/AntDesign icons are eligible (no filled variants).
537
+ // Custom empty state
538
+ <VirtualizedList
539
+ sections={[]}
540
+ renderItem={() => null}
541
+ emptyTitle="No items found"
542
+ emptyDescription="Try adjusting your filters"
543
+ />
544
+ ```
545
+
546
+ ---
824
547
 
825
- ### Updated
548
+ ## Version
826
549
 
827
- - `Sheet` and `ConfirmDialog` pass a stable `name` prop to `BottomSheetModal` (from `useId()`) for correct gorhom modal registry behavior when multiple modals are mounted.
828
- - `Input` `sheetMode` prop documented in the Setup section (replaces the `SheetTextInput` boilerplate inside sheets).
829
- - `CompositionScreen` example now uses `<Input sheetMode />` inside a `Sheet` to demonstrate the keyboard-friendly pattern.
550
+ **Current: 13.4.0.** Requires Expo SDK 54+, React Native 0.81+, React 19.
551
+
552
+ For full setup guide, see `CONSUMER.md`. For usage examples, see `EXAMPLES.md`.
830
553
 
831
554
  ---
832
555
 
833
- ## Components
556
+ ## Component Reference
834
557
 
835
558
  ---
836
559
 
@@ -857,18 +580,19 @@ No consumer action required — the fix is internal.
857
580
  |---------|------|--------|--------------|-------------|
858
581
  | `display-hero` | 64 | 700 | `foreground` | Large numeric displays — balance totals, stats, hero numbers |
859
582
  | `display-xl` | 28 | 700 | `foreground` | Screen-level titles, onboarding hero headings |
860
- | `display-lg` | 22 | 500 | `foreground` | Section headings, card group labels |
861
- | `display-md` | 21 | 700 | `foreground` | Card titles, dialog headings |
862
- | `display-sm` | 20 | 600 | `foreground` | Sub-section titles |
863
- | `title-md` | 16 | 600 | `foreground` | List row titles, navigation labels |
864
- | `title-sm` | 16 | 500 | `foreground` | Secondary row titles |
583
+ | `display-lg` | 24 | 600 | `foreground` | Section headings, card group labels |
584
+ | `display-md` | 20 | 600 | `foreground` | Card titles, dialog headings |
585
+ | `display-sm` | 18 | 600 | `foreground` | Sub-section titles |
586
+ | `title-md` | 17 | 600 | `foreground` | List row titles, navigation labels |
587
+ | `title-sm` | 15 | 500 | `foreground` | Secondary row titles |
865
588
  | `body-md` | 16 | 400 | `foregroundSubtle` | Main body copy, descriptions |
866
589
  | `body-sm` | 14 | 400 | `foregroundSubtle` | Secondary descriptions, detail text |
867
590
  | `caption` | 14 | 500 | `foreground` | Labels above inputs, item captions |
868
591
  | `caption-sm` | 13 | 400 | `foregroundMuted` | Timestamps, metadata, helper text |
869
592
  | `badge-text` | 11 | 600 | `foreground` | Badge labels, small status tags |
593
+ | `badge-text-md` | 13 | 600 | `foreground` | Badge labels (md size) |
870
594
  | `micro-label` | 12 | 700 | `foreground` | Micro labels, overlines |
871
- | `uppercase-tag` | 10 | 700 | `foreground` | Decorative uppercase category labels (auto-transforms text) |
595
+ | `uppercase-tag` | 11 | 700 | `foreground` | Decorative uppercase category labels (auto-transforms text) |
872
596
  | `button-lg` | 16 | 500 | `foreground` | Internal use in Button component (md/lg) |
873
597
  | `button-sm` | 14 | 500 | `foreground` | Internal use in Button component (sm) |
874
598
 
@@ -1835,9 +1559,10 @@ const [guests, setGuests] = useState(2)
1835
1559
  | Prop | Type | Default | Notes |
1836
1560
  |------|------|---------|-------|
1837
1561
  | orientation | `'horizontal' \| 'vertical'` | `'horizontal'` | Direction of the line |
1562
+ | label | `string` | — | Label shown in center of horizontal separator — renders as `── label ──` |
1838
1563
  | style | `ViewStyle` | — | — |
1839
1564
 
1840
- **Styling:** 1px thickness, `border` token color.
1565
+ **Styling:** 1px thickness, `border` token color. When `label` is provided, uses uppercase medium text with letter-spacing.
1841
1566
 
1842
1567
  **Examples:**
1843
1568
  ```tsx
@@ -1851,6 +1576,9 @@ const [guests, setGuests] = useState(2)
1851
1576
  <Text>Right</Text>
1852
1577
  </View>
1853
1578
 
1579
+ // With label
1580
+ <Separator label="o" />
1581
+
1854
1582
  // With custom spacing
1855
1583
  <Separator style={{ marginVertical: SPACING.lg }} />
1856
1584
  ```
@@ -2501,15 +2229,21 @@ const [accepted, setAccepted] = useState(false)
2501
2229
  | tabs | `TabItem[]` | required | Tab definitions |
2502
2230
  | variant | `'pill' \| 'underline'` | `'pill'` | Visual style |
2503
2231
  | value | `string` | — | Controlled active tab |
2504
- | onValueChange | `(value: string) => void` | — | — |
2232
+ | onValueChange | `(value: T) => void` | — | Generic infers from `value` prop type |
2505
2233
  | children | `ReactNode` | — | `TabsContent` components |
2506
2234
  | style | `ViewStyle` | — | — |
2507
2235
 
2508
2236
  **TabItem type:**
2509
2237
  ```ts
2510
- { label: string; value: string; icon?: ReactNode | ((isActive: boolean) => ReactNode) }
2238
+ interface TabItem<T extends string = string> {
2239
+ label: string
2240
+ value: T
2241
+ icon?: ReactNode | ((isActive: boolean) => ReactNode)
2242
+ }
2511
2243
  ```
2512
2244
 
2245
+ The generic `T` propagates from `TabItem<T>` through `onValueChange(value: T)`, so callbacks receive the correct union type without manual casting.
2246
+
2513
2247
  **Variants:**
2514
2248
  - `pill` — Animated background pill slides to active tab. Full-width distributed tabs. Active: filled `primary` + `primaryForeground` text. Default for top-of-screen navigation.
2515
2249
  - `underline` — 2px bottom border on active tab, no background. Tabs don't stretch — natural width. Airbnb-style filter tabs.
@@ -3847,7 +3581,8 @@ export default function Screen() {
3847
3581
  | subtitle | `string` | — | Secondary line under the title |
3848
3582
  | onBack | `() => void` | — | Shows a back button on the left when provided |
3849
3583
  | backIconName | `string` | `'chevron-left'` | Icon for the back button |
3850
- | left | `ReactNode` | — | Custom left content overrides the back button |
3584
+ | iconName | `string` | — | Decorative icon left of title, after back button. Ignored when `left` is provided |
3585
+ | left | `ReactNode` | — | Custom left content — overrides the back button and `iconName` |
3851
3586
  | right | `ReactNode` | — | Custom right content (actions) |
3852
3587
  | titleAlign | `'auto' \| 'left' \| 'center'` | `'auto'` | `auto` = left on narrow, centered on wide |
3853
3588
  | bordered | `boolean` | `true` | Hairline border underneath |
@@ -3862,6 +3597,13 @@ export default function Screen() {
3862
3597
  onBack={navigation.goBack}
3863
3598
  right={<IconButton iconName="more-horizontal" variant="text" onPress={openMenu} />}
3864
3599
  />
3600
+
3601
+ {/* With decorative icon and back button */}
3602
+ <AppHeader
3603
+ title="Perfil"
3604
+ iconName="user"
3605
+ onBack={() => router.back()}
3606
+ />
3865
3607
  ```
3866
3608
 
3867
3609
  ---
@@ -4135,17 +3877,23 @@ export default function TabLayout() {
4135
3877
  | title | `string` | `'Something went wrong'` | Default fallback title |
4136
3878
  | message | `string` | `error.message` | Default fallback body |
4137
3879
  | onError | `(error, info) => void` | — | Wire your crash reporter here |
3880
+ | onCatch | `(error, componentStack) => void` | — | Simplified callback — just error + stack string |
4138
3881
 
4139
3882
  **Default fallback:** centered themed card with a destructive icon, title, message, and a "Try again" button that calls `reset()`.
4140
3883
 
4141
- **Example:**
3884
+ **Examples:**
4142
3885
  ```tsx
4143
3886
  <ErrorBoundary onError={reportCrash}>
4144
3887
  <DocumentViewer />
4145
3888
  </ErrorBoundary>
4146
3889
 
4147
- // Custom fallback
4148
- <ErrorBoundary fallback={({ error, reset }) => <MyFallback error={error} onRetry={reset} />}>
3890
+ // Use componentStack in custom fallback
3891
+ <ErrorBoundary fallback={({ error, componentStack, reset }) => <MyFallback error={error} stack={componentStack} onRetry={reset} />}>
3892
+ <RiskyScreen />
3893
+ </ErrorBoundary>
3894
+
3895
+ // Simplified crash reporter
3896
+ <ErrorBoundary onCatch={(error, stack) => logCrash(error, stack)}>
4149
3897
  <RiskyScreen />
4150
3898
  </ErrorBoundary>
4151
3899
  ```
@@ -4309,7 +4057,7 @@ import { HolographicCard, FOIL_PRESETS } from '@retray-dev/ui-kit/HolographicCar
4309
4057
 
4310
4058
  **When to use:** Image selection from the device library — product photos, profile avatars, tenant logos. Shows a dashed placeholder when empty, the selected image when filled, and a loading overlay while uploading.
4311
4059
 
4312
- **Requires:** `expo-image-picker` installed in the consuming app (`pnpm add expo-image-picker`). It is an optional peer dep — consumers who don't use `ImageUpload` never pull it.
4060
+ **Requires:** `react-native-image-picker` installed in the consuming app (`pnpm add react-native-image-picker`). It is an optional peer dep — consumers who don't use `ImageUpload` never pull it. It is dynamically imported at tap time.
4313
4061
 
4314
4062
  | Prop | Type | Default | Notes |
4315
4063
  |------|------|---------|-------|
@@ -4323,9 +4071,9 @@ import { HolographicCard, FOIL_PRESETS } from '@retray-dev/ui-kit/HolographicCar
4323
4071
  | borderRadius | `number` | `RADIUS.lg` | — |
4324
4072
  | resizeMode | `'cover' \| 'contain' \| 'stretch'` | `'cover'` | Image resize mode |
4325
4073
  | disabled | `boolean` | `false` | Prevents pressing |
4326
- | allowsEditing | `boolean` | `true` | When `true`, iOS opens the crop/editing screen after selecting an image. Set `false` to accept the image directly without cropping |
4327
4074
  | style | `ViewStyle` | — | — |
4328
4075
  | accessibilityLabel | `string` | — | — |
4076
+ | onPickerStarting | `() => void` | — | Called synchronously when user taps the upload area, before dynamic import and permission request |
4329
4077
 
4330
4078
  **Examples:**
4331
4079
  ```tsx
@@ -4465,171 +4213,123 @@ const { visible, loading, open, onConfirm, onCancel } = useConfirmDialog({
4465
4213
 
4466
4214
  ---
4467
4215
 
4468
- ## Icon System
4216
+ ### Image
4469
4217
 
4470
- The library ships a built-in icon resolver — pass any icon name string to components without manual imports.
4218
+ **Import:** `import { Image } from '@retray-dev/ui-kit'`
4471
4219
 
4472
- ### Supported families (priority order first match wins)
4220
+ **When to use:** Display images with automatic fallback on load failure. Wraps `expo-image` for performant loading and caching.
4473
4221
 
4474
- | Priority | Family | Best for |
4475
- |---|---|---|
4476
- | 1 | `Feather` | Clean line icons, UI essentials (first match wins) |
4477
- | 2 | `AntDesign` | Semantic UI icons |
4478
- | 3 | `Entypo` | Social, media, navigation icons |
4479
- | 4 | `FontAwesome5` | Wide coverage |
4480
- | 5 | `MaterialIcons` | Material-style icons |
4481
- | 6 | `Ionicons` | Fallback |
4222
+ | Prop | Type | Default | Notes |
4223
+ |------|------|---------|-------|
4224
+ | src | `string \| null` | | Image URI. `null` or failed load shows fallback |
4225
+ | fallback | `ReactNode` | | Custom fallback content when image fails to load |
4226
+ | aspectRatio | `number` | | Width/height ratio — e.g. `16/9`, `4/3`. Sets container height proportionally |
4227
+ | borderRadius | `number` | `0` | Border radius override |
4482
4228
 
4483
- Browse all available icons: **https://icons.expo.fyi**
4229
+ **All other `expo-image` props pass through** (except `source` — use `src` instead).
4484
4230
 
4485
- ### Standalone `Icon` component
4231
+ **Behavior:** On load error or `null` src: if `fallback` node provided, renders it inside container; otherwise renders a skeleton-colored placeholder with `accessibilityLabel="Imagen no disponible"`.
4486
4232
 
4233
+ **Examples:**
4487
4234
  ```tsx
4488
- import { Icon } from '@retray-dev/ui-kit'
4235
+ // Basic
4236
+ <Image src="https://example.com/photo.jpg" aspectRatio={16/9} />
4489
4237
 
4490
- <Icon name="home" size={24} color={colors.foreground} />
4491
- <Icon name="star" size={20} color={colors.primary} />
4238
+ // With fallback
4239
+ <Image src={user.avatar} fallback={<Avatar fallbackText={user.name} />} />
4492
4240
 
4493
- // Force a specific family when same name exists in multiple families:
4494
- <Icon name="heart" size={24} color="red" family="FontAwesome5" />
4241
+ // Rounded
4242
+ <Image src={photo.uri} borderRadius={12} aspectRatio={4/3} />
4495
4243
  ```
4496
4244
 
4497
- **Props:**
4498
-
4499
- | Prop | Type | Required | Notes |
4500
- |------|------|----------|-------|
4501
- | name | `string` | yes | Icon name (e.g. `"home"`, `"arrow-right"`) |
4502
- | size | `number` | yes | Icon size in points |
4503
- | color | `string` | yes | Icon color |
4504
- | family | `IconFamily` | no | Force a specific family |
4505
-
4506
- Returns `null` (no crash) if name not found in any family.
4507
-
4508
- > **v13:** `renderIcon`, `getValidIconNames`, and `configureIconFamilies` were removed. Use `Icon` component directly for all icon rendering.
4509
-
4510
- ### `iconName` props on components
4511
-
4512
- All components with icon slots accept `iconName` — auto-resolved size and color:
4513
-
4514
- | Component | Prop(s) | Slot | Default color |
4515
- |-----------|---------|------|---------------|
4516
- | `Button` | `iconName`, `iconColor` | Left or right of label | Variant label color |
4517
- | `IconButton` | `iconName`, `iconColor` | Center | Variant foreground |
4518
- | `Input` | `prefixIcon`, `prefixIconColor` | Before input | `foregroundMuted` |
4519
- | `Input` | `suffixIcon`, `suffixIconColor` | After input | `foregroundMuted` |
4520
- | `ListItem` | `leftIcon`, `leftIconColor` | Left 44×44 slot | `foreground` |
4521
- | `ListItem` | `rightIcon`, `rightIconColor` | Right slot | `foregroundMuted` |
4522
- | `Badge` | `iconName`, `iconColor` | Before label | Variant foreground |
4523
- | `Toggle` | `iconName`, `iconColor` | When not pressed | `foregroundMuted` |
4524
- | `Toggle` | `activeIconName`, `activeIconColor` | When pressed | `primary` |
4525
- | `AlertBanner` | `iconName`, `iconColor` | Left of content | Variant title color |
4526
- | `EmptyState` | `iconName`, `iconColor` | Center icon slot | `foregroundMuted` |
4527
- | `Toast` | `iconName`, `iconColor` | Left of message | Variant text color |
4528
- | `MediaCard` | `actionIconName` | Top-right of image | `#ffffff` |
4529
- | `Chip` | `iconName` | Before label | Variant foreground |
4530
-
4531
- **Precedence:** `iconName` always takes precedence over the corresponding `ReactNode` prop when both are supplied.
4532
-
4533
4245
  ---
4534
4246
 
4535
- ### `getResponsiveFontSize` utility
4536
-
4537
- Calculates a stepped-down font size for long numeric strings (currency amounts, large numbers) that would overflow at full size.
4538
-
4539
- ```tsx
4540
- import { getResponsiveFontSize } from '@retray-dev/ui-kit'
4247
+ ### ScreenContainer
4541
4248
 
4542
- // Default steps: ≤10 chars max, ≤12 → max-4, ≤14 → max-6, >14 → max-8
4543
- const fontSize = getResponsiveFontSize(formatCOP(amount), 48)
4249
+ **Import:** `import { ScreenContainer } from '@retray-dev/ui-kit'`
4544
4250
 
4545
- <Text style={{ fontSize }} numberOfLines={1} adjustsFontSizeToFit>
4546
- {formatCOP(amount)}
4547
- </Text>
4548
- ```
4251
+ **When to use:** Consistent screen layout with safe area insets, optional scroll, keyboard avoidance, and responsive max-width centering.
4549
4252
 
4550
- **Signature:**
4551
- ```ts
4552
- getResponsiveFontSize(
4553
- text: string,
4554
- maxSize: number,
4555
- steps?: { maxLen: number; subtract: number }[]
4556
- ): number
4557
- ```
4253
+ | Prop | Type | Default | Notes |
4254
+ |------|------|---------|-------|
4255
+ | children | `ReactNode` | required | Screen content |
4256
+ | maxWidth | `number` | `700` | Max content width (points). `0` disables |
4257
+ | scroll | `boolean` | `false` | Enable scroll view |
4258
+ | backgroundColor | `string` | theme `background` | — |
4259
+ | paddingTop | `number` | safe area top | Override top padding |
4260
+ | paddingBottom | `number` | safe area bottom | Override bottom padding |
4261
+ | paddingHorizontal | `number` | `0` | Horizontal padding |
4262
+ | keyboard | `boolean` | `false` | Enable `KeyboardAvoidingView` (iOS `padding` behavior) |
4263
+ | style | `ViewStyle` | — | Additional wrapper style |
4264
+
4265
+ **Behavior:** Applies top/bottom safe area insets, centers content horizontally when screen exceeds `maxWidth`. When `keyboard` is enabled, wraps content in `KeyboardAvoidingView` with iOS `padding` behavior.
4558
4266
 
4559
- Custom steps example:
4267
+ **Examples:**
4560
4268
  ```tsx
4561
- getResponsiveFontSize(text, 48, [
4562
- { maxLen: 8, subtract: 0 },
4563
- { maxLen: 11, subtract: 6 },
4564
- { maxLen: 14, subtract: 10 },
4565
- ])
4269
+ // Basic scroll screen
4270
+ <ScreenContainer scroll paddingHorizontal={16}>
4271
+ <Text variant="title-md">Welcome</Text>
4272
+ </ScreenContainer>
4273
+
4274
+ // With keyboard avoidance for forms
4275
+ <ScreenContainer keyboard paddingHorizontal={16}>
4276
+ <Input label="Name" />
4277
+ <Input label="Email" />
4278
+ <Button label="Submit" />
4279
+ </ScreenContainer>
4280
+
4281
+ // Non-scroll, max width disabled
4282
+ <ScreenContainer maxWidth={0}>
4283
+ <FullWidthContent />
4284
+ </ScreenContainer>
4566
4285
  ```
4567
4286
 
4568
- **Alternative:** Use `<CurrencyDisplay autoScale maxFontSize={48} />` for currency values — no manual sizing needed.
4569
-
4570
4287
  ---
4571
4288
 
4572
- ## Design System Conventions
4573
-
4574
- ### Touch targets
4575
- All interactive elements maintain ≥44pt touch height per Apple HIG:
4576
- - Button md: 48px, sm: 40px, lg: 56px
4577
- - Input / Textarea: 56px (14px vertical padding)
4578
- - IconButton md: 44px, lg: 52px
4579
- - Checkbox: 24×24px box
4580
- - Switch: 30px track height
4581
- - MonthPicker arrows: 44×44px
4582
-
4583
- ### Haptic patterns
4584
- - `impactLight` — Button, Card (press), Sheet open, Toast, MediaCard action
4585
- - `selectionAsync` — Checkbox, Switch, Toggle, RadioGroup, Select, Slider steps, Accordion, ListItem, Chip, CategoryStrip, Tabs, MonthPicker
4586
- - `notificationSuccess` — Toast `success`
4587
- - `notificationError` — Toast `destructive`, Toast `warning`
4588
-
4589
- ### Animation press scales
4590
- - `Button` → 0.95 (strong spring feedback)
4591
- - `Card` → 0.98 (subtle, appropriate for large surfaces)
4592
- - `ListItem` → 0.97 (between — medium row targets)
4593
- - `MediaCard` → 0.98 (large surface)
4594
- - `IconButton` → 0.95
4595
- - `Chip`, `CategoryStrip chip` → 0.95
4596
-
4597
- ### Platform differences
4598
- - `useNativeDriver` — `Platform.OS !== 'web'` everywhere (native driver disabled on web)
4599
- - `Select` — wheel modal on iOS, dialog on Android, `<select>` on web
4600
- - `Toast` — full width on mobile, 400px centered on web
4601
- - Hover states — web only
4602
- - `Skeleton` shimmer highlight — adapts opacity for light/dark mode
4603
-
4604
- ### Dynamic Type
4605
- All `Text` and `TextInput` components have `allowFontScaling={true}` — respects user font size accessibility settings.
4606
-
4607
- ### Scaling utilities (internal)
4608
- Used internally — not exported. `s()` horizontal scale, `vs()` vertical scale, `ms()` moderate scale relative to 350×680 base.
4289
+ ### VirtualizedList
4609
4290
 
4610
- ---
4291
+ **Import:** `import { VirtualizedList } from '@retray-dev/ui-kit'`
4611
4292
 
4612
- ## Full Composition Examples
4293
+ **When to use:** Sectioned lists with sticky headers, empty state, and pull-to-refresh. Wraps React Native `SectionList` with Spanish defaults.
4613
4294
 
4614
- The package includes **EXAMPLES.md** with complete, working code for 4 real-world app screens:
4295
+ | Prop | Type | Default | Notes |
4296
+ |------|------|---------|-------|
4297
+ | sections | `VirtualizedListSection<T>[]` | required | `{ title?: string; data: T[] }` — only non-empty sections render |
4298
+ | renderItem | `(info) => ReactElement` | required | Standard `SectionList` render function |
4299
+ | emptyTitle | `string` | `'Sin contenido'` | Title shown when no data |
4300
+ | emptyDescription | `string` | — | Description below title |
4301
+ | emptyComponent | `ReactNode` | — | Custom empty state (overrides titles) |
4302
+ | refreshing | `boolean` | `false` | Pull-to-refresh state |
4303
+ | onRefresh | `() => void` | — | Refresh handler |
4304
+ | stickyHeaders | `boolean` | `true` | Enable sticky section headers |
4305
+ | headerColor | `string` | `foregroundMuted` | Section header text color |
4615
4306
 
4616
- 1. **Finance Dashboard** MonthPicker, CurrencyDisplay, Progress, CategoryStrip, ListItem, DetailRow
4617
- 2. **Edit Profile** — Avatar, Badge, Input, Select, Switch, Card, AlertBanner
4618
- 3. **Onboarding Flow** — Badge, Progress, Input, RadioGroup, EmptyState (multi-step wizard)
4619
- 4. **Settings Screen** — MenuGroup, ListGroup, Form, MenuItem, ListItem, compound component patterns
4307
+ **All other `SectionList` props pass through** (except `sections`, `renderItem`, `ListEmptyComponent`).
4620
4308
 
4621
- **For AI agents:** Import EXAMPLES.md from the package for full source code and patterns:
4309
+ **Behavior:** Sections with empty `data` arrays are filtered out. When no sections have data, renders the empty state (custom component or default title). Sticky headers render section titles in uppercase with `Sohne-SemiBold`.
4310
+
4311
+ **Examples:**
4312
+ ```tsx
4313
+ const sections = [
4314
+ { title: 'Hoy', data: todayItems },
4315
+ { title: 'Ayer', data: yesterdayItems },
4316
+ ]
4317
+
4318
+ <VirtualizedList
4319
+ sections={sections}
4320
+ renderItem={({ item }) => <ListItem title={item.name} />}
4321
+ refreshing={refreshing}
4322
+ onRefresh={handleRefresh}
4323
+ />
4622
4324
 
4623
- ```markdown
4624
- ## UI Kit Composition Examples
4625
- @./node_modules/@retray-dev/ui-kit/EXAMPLES.md
4325
+ // With custom empty state
4326
+ <VirtualizedList
4327
+ sections={[]}
4328
+ renderItem={() => null}
4329
+ emptyTitle="No hay transacciones"
4330
+ emptyDescription="Tus transacciones aparecerán aquí"
4331
+ />
4626
4332
  ```
4627
4333
 
4628
- Each example includes:
4629
- - Complete component code (copy-paste ready)
4630
- - State management setup
4631
- - Proper imports and theme usage
4632
- - Common patterns (spacing, colors, navigation, toast feedback)
4633
- - StyleSheet definitions
4334
+ ---
4634
4335
 
4635
- **For human developers:** Examples are also live in the `example/` app. Clone the repo, run `pnpm install && pnpm build`, then `cd example && pnpm start` to see them in action.