@retray-dev/ui-kit 12.2.0 → 13.2.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 (296) hide show
  1. package/CONSUMER.md +26 -11
  2. package/DESIGN.md +2 -2
  3. package/README.md +15 -11
  4. package/{COMPONENTS.md → SKILL.md} +374 -996
  5. package/dist/Accordion.d.mts +2 -0
  6. package/dist/Accordion.d.ts +2 -0
  7. package/dist/Accordion.js +49 -210
  8. package/dist/Accordion.mjs +6 -6
  9. package/dist/AlertBanner.js +29 -153
  10. package/dist/AlertBanner.mjs +3 -4
  11. package/dist/AppHeader.d.mts +5 -2
  12. package/dist/AppHeader.d.ts +5 -2
  13. package/dist/AppHeader.js +45 -239
  14. package/dist/AppHeader.mjs +6 -8
  15. package/dist/Avatar.d.mts +17 -1
  16. package/dist/Avatar.d.ts +17 -1
  17. package/dist/Avatar.js +80 -115
  18. package/dist/Avatar.mjs +2 -3
  19. package/dist/Badge.js +24 -149
  20. package/dist/Badge.mjs +3 -4
  21. package/dist/Button.js +79 -267
  22. package/dist/Button.mjs +6 -7
  23. package/dist/ButtonGroup.mjs +0 -1
  24. package/dist/Card.js +15 -200
  25. package/dist/Card.mjs +4 -6
  26. package/dist/CategoryStrip.d.mts +0 -5
  27. package/dist/CategoryStrip.d.ts +0 -5
  28. package/dist/CategoryStrip.js +47 -265
  29. package/dist/CategoryStrip.mjs +6 -7
  30. package/dist/Checkbox.d.mts +2 -1
  31. package/dist/Checkbox.d.ts +2 -1
  32. package/dist/Checkbox.js +18 -201
  33. package/dist/Checkbox.mjs +5 -6
  34. package/dist/Chip.js +44 -236
  35. package/dist/Chip.mjs +7 -7
  36. package/dist/ConfirmDialog.d.mts +2 -1
  37. package/dist/ConfirmDialog.d.ts +2 -1
  38. package/dist/ConfirmDialog.js +110 -300
  39. package/dist/ConfirmDialog.mjs +7 -8
  40. package/dist/CurrencyDisplay.js +1 -114
  41. package/dist/CurrencyDisplay.mjs +2 -3
  42. package/dist/CurrencyInput.js +35 -162
  43. package/dist/CurrencyInput.mjs +5 -6
  44. package/dist/DetailRow.js +25 -150
  45. package/dist/DetailRow.mjs +3 -4
  46. package/dist/EmptyState.js +80 -268
  47. package/dist/EmptyState.mjs +7 -8
  48. package/dist/ErrorBoundary.js +32 -199
  49. package/dist/ErrorBoundary.mjs +4 -5
  50. package/dist/Form.js +1 -114
  51. package/dist/Form.mjs +2 -3
  52. package/dist/HolographicCard.d.mts +0 -28
  53. package/dist/HolographicCard.d.ts +0 -28
  54. package/dist/HolographicCard.js +20 -130
  55. package/dist/HolographicCard.mjs +9 -33
  56. package/dist/IconButton.js +36 -234
  57. package/dist/IconButton.mjs +5 -7
  58. package/dist/IconPicker.js +222 -929
  59. package/dist/IconPicker.mjs +5 -6
  60. package/dist/ImageUpload.d.mts +3 -3
  61. package/dist/ImageUpload.d.ts +3 -3
  62. package/dist/ImageUpload.js +49 -238
  63. package/dist/ImageUpload.mjs +5 -7
  64. package/dist/ImageViewer.js +75 -266
  65. package/dist/ImageViewer.mjs +8 -9
  66. package/dist/Input.d.mts +1 -1
  67. package/dist/Input.d.ts +1 -1
  68. package/dist/Input.js +35 -162
  69. package/dist/Input.mjs +4 -5
  70. package/dist/LabelValue.js +24 -149
  71. package/dist/LabelValue.mjs +3 -4
  72. package/dist/ListGroup.js +1 -114
  73. package/dist/ListGroup.mjs +2 -3
  74. package/dist/ListItem.d.mts +2 -1
  75. package/dist/ListItem.d.ts +2 -1
  76. package/dist/ListItem.js +41 -236
  77. package/dist/ListItem.mjs +5 -7
  78. package/dist/MediaCard.d.mts +0 -14
  79. package/dist/MediaCard.d.ts +0 -14
  80. package/dist/MediaCard.js +69 -315
  81. package/dist/MediaCard.mjs +5 -7
  82. package/dist/MenuGroup.js +1 -114
  83. package/dist/MenuGroup.mjs +2 -3
  84. package/dist/MenuItem.d.mts +2 -1
  85. package/dist/MenuItem.d.ts +2 -1
  86. package/dist/MenuItem.js +39 -235
  87. package/dist/MenuItem.mjs +5 -7
  88. package/dist/MonthPicker.js +8 -163
  89. package/dist/MonthPicker.mjs +3 -4
  90. package/dist/NumberStepper.d.mts +2 -1
  91. package/dist/NumberStepper.d.ts +2 -1
  92. package/dist/NumberStepper.js +44 -239
  93. package/dist/NumberStepper.mjs +5 -7
  94. package/dist/PagerDots.d.mts +1 -1
  95. package/dist/PagerDots.d.ts +1 -1
  96. package/dist/PagerDots.js +69 -224
  97. package/dist/PagerDots.mjs +6 -6
  98. package/dist/Pressable.js +14 -85
  99. package/dist/Pressable.mjs +4 -5
  100. package/dist/PricingCard.js +87 -272
  101. package/dist/PricingCard.mjs +8 -9
  102. package/dist/Progress.js +3 -123
  103. package/dist/Progress.mjs +3 -4
  104. package/dist/RadioGroup.js +52 -265
  105. package/dist/RadioGroup.mjs +5 -6
  106. package/dist/RetrayProvider.js +3 -6
  107. package/dist/RetrayProvider.mjs +3 -4
  108. package/dist/Select.d.mts +3 -1
  109. package/dist/Select.d.ts +3 -1
  110. package/dist/Select.js +27 -233
  111. package/dist/Select.mjs +4 -6
  112. package/dist/SelectableCard.js +33 -209
  113. package/dist/SelectableCard.mjs +5 -6
  114. package/dist/SelectableGrid.d.mts +0 -21
  115. package/dist/SelectableGrid.d.ts +0 -21
  116. package/dist/SelectableGrid.js +49 -272
  117. package/dist/SelectableGrid.mjs +5 -7
  118. package/dist/Separator.js +1 -114
  119. package/dist/Separator.mjs +2 -3
  120. package/dist/Sheet.d.mts +1 -1
  121. package/dist/Sheet.d.ts +1 -1
  122. package/dist/Sheet.js +33 -175
  123. package/dist/Sheet.mjs +3 -4
  124. package/dist/SheetSelect.js +39 -236
  125. package/dist/SheetSelect.mjs +6 -7
  126. package/dist/Skeleton.js +4 -124
  127. package/dist/Skeleton.mjs +3 -4
  128. package/dist/Slider.d.mts +2 -1
  129. package/dist/Slider.d.ts +2 -1
  130. package/dist/Slider.js +8 -161
  131. package/dist/Slider.mjs +3 -4
  132. package/dist/Spinner.js +3 -116
  133. package/dist/Spinner.mjs +2 -3
  134. package/dist/Stats.js +36 -234
  135. package/dist/Stats.mjs +5 -7
  136. package/dist/Switch.d.mts +2 -1
  137. package/dist/Switch.d.ts +2 -1
  138. package/dist/Switch.js +26 -176
  139. package/dist/Switch.mjs +5 -5
  140. package/dist/TabBar.js +43 -200
  141. package/dist/TabBar.mjs +5 -5
  142. package/dist/Tabs.js +15 -199
  143. package/dist/Tabs.mjs +5 -6
  144. package/dist/Text.js +9 -130
  145. package/dist/Text.mjs +2 -3
  146. package/dist/Textarea.d.mts +2 -1
  147. package/dist/Textarea.d.ts +2 -1
  148. package/dist/Textarea.js +71 -219
  149. package/dist/Textarea.mjs +4 -5
  150. package/dist/Toast.d.mts +12 -10
  151. package/dist/Toast.d.ts +12 -10
  152. package/dist/Toast.js +1 -114
  153. package/dist/Toast.mjs +2 -3
  154. package/dist/Toggle.js +39 -236
  155. package/dist/Toggle.mjs +6 -7
  156. package/dist/{chunk-ELGEOM7I.mjs → chunk-2QXJDRVU.mjs} +13 -10
  157. package/dist/{chunk-LIS6I5UP.mjs → chunk-2VIDP72N.mjs} +3 -3
  158. package/dist/{chunk-NHDI3VQB.mjs → chunk-422IVD3H.mjs} +16 -12
  159. package/dist/{chunk-DF7JA72E.mjs → chunk-4NQFTHN3.mjs} +13 -7
  160. package/dist/{chunk-3XCFYSX4.mjs → chunk-5MYNAAFE.mjs} +13 -17
  161. package/dist/{chunk-E7NEHHXV.mjs → chunk-62BBSSUF.mjs} +3 -3
  162. package/dist/{chunk-UBUXUMER.mjs → chunk-77UOVFIS.mjs} +7 -5
  163. package/dist/{chunk-M3C7XM2M.mjs → chunk-7BZJRB77.mjs} +28 -18
  164. package/dist/chunk-ARONDO7M.mjs +40 -0
  165. package/dist/{chunk-GH67YXG6.mjs → chunk-AZV7KNJI.mjs} +3 -3
  166. package/dist/{chunk-2P2CB235.mjs → chunk-BULKGOIZ.mjs} +7 -8
  167. package/dist/{chunk-RJNLAH76.mjs → chunk-C5ZRMR2E.mjs} +4 -2
  168. package/dist/chunk-CM2DG4MR.mjs +142 -0
  169. package/dist/{chunk-UQ4742ET.mjs → chunk-COA2YZOX.mjs} +8 -6
  170. package/dist/{chunk-EDLCGYIO.mjs → chunk-CZN6L2QU.mjs} +11 -8
  171. package/dist/{chunk-TS7DGUIR.mjs → chunk-DBHSUUKU.mjs} +2 -2
  172. package/dist/{chunk-57V2LXCK.mjs → chunk-DE25XTVQ.mjs} +3 -3
  173. package/dist/{chunk-RMRS44MQ.mjs → chunk-E2PONRJG.mjs} +13 -9
  174. package/dist/{chunk-GUTDFUNF.mjs → chunk-EHGBHFMH.mjs} +9 -17
  175. package/dist/{chunk-ZIMY2QUM.mjs → chunk-ERWJPVX7.mjs} +2 -2
  176. package/dist/{chunk-NLZY4TXU.mjs → chunk-ESQDPO5E.mjs} +7 -7
  177. package/dist/{chunk-VJBUCITV.mjs → chunk-EW2FIDSM.mjs} +1 -1
  178. package/dist/{chunk-HC4VVCWY.mjs → chunk-FTTI6T5Q.mjs} +4 -4
  179. package/dist/{chunk-MVMGPZN6.mjs → chunk-H6MQL7PS.mjs} +12 -7
  180. package/dist/{chunk-CF27NBXO.mjs → chunk-HHOOFDBA.mjs} +38 -41
  181. package/dist/{chunk-2HFD4IHU.mjs → chunk-HUSSF6TF.mjs} +1 -1
  182. package/dist/{chunk-HEDQPK4I.mjs → chunk-IDVUZIVY.mjs} +16 -22
  183. package/dist/chunk-IFYMBOEN.mjs +14 -0
  184. package/dist/{chunk-QOLWA2PW.mjs → chunk-IGU223UM.mjs} +80 -4
  185. package/dist/chunk-IJCMPVW5.mjs +121 -0
  186. package/dist/{chunk-AENAVIKT.mjs → chunk-ITG4JQM3.mjs} +4 -4
  187. package/dist/{chunk-E5UKLSJZ.mjs → chunk-K3QX2M26.mjs} +11 -8
  188. package/dist/{chunk-4OORJ2DY.mjs → chunk-K7TKID3V.mjs} +8 -7
  189. package/dist/{chunk-2LG326TT.mjs → chunk-KAGADD2O.mjs} +4 -4
  190. package/dist/{chunk-IVSRW4HS.mjs → chunk-KC5QDYGZ.mjs} +4 -4
  191. package/dist/{chunk-7AFZWSCI.mjs → chunk-KPTY7UYQ.mjs} +1 -1
  192. package/dist/{chunk-YTXRIXNZ.mjs → chunk-KSSVIFYR.mjs} +9 -12
  193. package/dist/chunk-L3YKPTJQ.mjs +119 -0
  194. package/dist/chunk-M53LC4Q7.mjs +35 -0
  195. package/dist/chunk-MZ6WRTD2.mjs +40 -0
  196. package/dist/chunk-NGEN2EES.mjs +581 -0
  197. package/dist/{chunk-ZR6HSEAB.mjs → chunk-NPCBNGNE.mjs} +17 -26
  198. package/dist/{chunk-C43HRKXH.mjs → chunk-OBV72JD4.mjs} +1 -1
  199. package/dist/{chunk-LPV4NJJK.mjs → chunk-PGQ6FMXS.mjs} +6 -5
  200. package/dist/{chunk-MEPSKGBO.mjs → chunk-PI6RULJX.mjs} +1 -1
  201. package/dist/{chunk-F3YTWO3T.mjs → chunk-RA6SAAFE.mjs} +9 -8
  202. package/dist/{chunk-UNNRUJTM.mjs → chunk-RRKM4MKB.mjs} +7 -7
  203. package/dist/{chunk-ULGNQPNE.mjs → chunk-S2VGME7X.mjs} +1 -1
  204. package/dist/{chunk-OLVJFKXS.mjs → chunk-S44XWTTC.mjs} +35 -25
  205. package/dist/{chunk-YMYIEVZP.mjs → chunk-SZEKQAOY.mjs} +1 -1
  206. package/dist/{chunk-BXF4AMHY.mjs → chunk-TMH263OK.mjs} +5 -4
  207. package/dist/{chunk-NJG7DHVF.mjs → chunk-U6DEBYU5.mjs} +10 -9
  208. package/dist/{chunk-QXDGGOLC.mjs → chunk-UMZTPUB3.mjs} +33 -21
  209. package/dist/{chunk-KSUWPU2F.mjs → chunk-WIPEDNSD.mjs} +7 -7
  210. package/dist/{chunk-QDAZGZUF.mjs → chunk-XCIG6HT2.mjs} +3 -3
  211. package/dist/{chunk-4J2PXL36.mjs → chunk-Y6YS33GM.mjs} +40 -38
  212. package/dist/{chunk-4XOB5TTD.mjs → chunk-ZKDKKQCE.mjs} +5 -5
  213. package/dist/{chunk-LOBLCFMN.mjs → chunk-ZTPYUU5C.mjs} +5 -5
  214. package/dist/fonts.mjs +0 -2
  215. package/dist/index.d.mts +13 -73
  216. package/dist/index.d.ts +13 -73
  217. package/dist/index.js +1149 -1892
  218. package/dist/index.mjs +81 -86
  219. package/package.json +20 -20
  220. package/src/components/Accordion/Accordion.tsx +15 -9
  221. package/src/components/AlertBanner/AlertBanner.tsx +7 -6
  222. package/src/components/AppHeader/AppHeader.tsx +25 -10
  223. package/src/components/Avatar/Avatar.tsx +92 -1
  224. package/src/components/Avatar/index.ts +2 -2
  225. package/src/components/Badge/Badge.tsx +2 -2
  226. package/src/components/Button/Button.tsx +50 -46
  227. package/src/components/Card/Card.tsx +1 -0
  228. package/src/components/CategoryStrip/CategoryStrip.tsx +36 -49
  229. package/src/components/Checkbox/Checkbox.tsx +3 -0
  230. package/src/components/Chip/Chip.tsx +5 -4
  231. package/src/components/ConfirmDialog/ConfirmDialog.tsx +33 -17
  232. package/src/components/DetailRow/DetailRow.tsx +3 -3
  233. package/src/components/EmptyState/EmptyState.tsx +2 -2
  234. package/src/components/ErrorBoundary/ErrorBoundary.tsx +6 -6
  235. package/src/components/HolographicCard/HolographicCard.tsx +14 -95
  236. package/src/components/IconButton/IconButton.tsx +2 -2
  237. package/src/components/IconPicker/IconPicker.tsx +13 -12
  238. package/src/components/ImageUpload/ImageUpload.tsx +43 -46
  239. package/src/components/ImageViewer/ImageViewer.tsx +3 -3
  240. package/src/components/Input/Input.tsx +11 -5
  241. package/src/components/LabelValue/LabelValue.tsx +2 -2
  242. package/src/components/ListItem/ListItem.tsx +7 -4
  243. package/src/components/MediaCard/MediaCard.tsx +21 -59
  244. package/src/components/MenuItem/MenuItem.tsx +5 -2
  245. package/src/components/MonthPicker/MonthPicker.tsx +2 -2
  246. package/src/components/NumberStepper/NumberStepper.tsx +10 -6
  247. package/src/components/PagerDots/PagerDots.tsx +38 -28
  248. package/src/components/PricingCard/PricingCard.tsx +6 -6
  249. package/src/components/RadioGroup/RadioGroup.tsx +18 -31
  250. package/src/components/Select/Select.tsx +35 -39
  251. package/src/components/SelectableCard/SelectableCard.tsx +4 -6
  252. package/src/components/SelectableGrid/SelectableGrid.tsx +37 -72
  253. package/src/components/Sheet/Sheet.tsx +28 -15
  254. package/src/components/Sheet/index.ts +2 -2
  255. package/src/components/SheetSelect/SheetSelect.tsx +3 -3
  256. package/src/components/Skeleton/Skeleton.tsx +1 -1
  257. package/src/components/Slider/Slider.tsx +3 -0
  258. package/src/components/Spinner/Spinner.tsx +2 -2
  259. package/src/components/Stats/Stats.tsx +2 -2
  260. package/src/components/Switch/Switch.tsx +12 -7
  261. package/src/components/TabBar/TabBar.tsx +9 -8
  262. package/src/components/Text/Text.tsx +13 -1
  263. package/src/components/Textarea/Textarea.tsx +18 -32
  264. package/src/components/Toggle/Toggle.tsx +3 -3
  265. package/src/hooks/useConfirmDialog.ts +31 -42
  266. package/src/index.ts +3 -4
  267. package/src/theme/ThemeProvider.tsx +1 -4
  268. package/src/theme/colorUtils.ts +1 -72
  269. package/src/theme/colors.ts +40 -1
  270. package/src/theme/types.ts +2 -2
  271. package/src/utils/animations.ts +0 -47
  272. package/src/utils/curatedIcons.ts +93 -801
  273. package/src/utils/haptics.ts +13 -208
  274. package/src/utils/icons.ts +27 -91
  275. package/src/utils/pressable.ts +10 -61
  276. package/dist/VirtualList.d.mts +0 -19
  277. package/dist/VirtualList.d.ts +0 -19
  278. package/dist/VirtualList.js +0 -38
  279. package/dist/VirtualList.mjs +0 -2
  280. package/dist/chunk-2BA3JMKK.mjs +0 -136
  281. package/dist/chunk-3DKJ2GIC.mjs +0 -30
  282. package/dist/chunk-7ELGZ66G.mjs +0 -164
  283. package/dist/chunk-DVK4G2GT.mjs +0 -59
  284. package/dist/chunk-EJ7ZPXOH.mjs +0 -163
  285. package/dist/chunk-KA7LTET3.mjs +0 -71
  286. package/dist/chunk-LNPKGWBG.mjs +0 -134
  287. package/dist/chunk-NC5ZTR2Y.mjs +0 -32
  288. package/dist/chunk-SAWUXP3A.mjs +0 -1114
  289. package/dist/chunk-Y6FXYEAI.mjs +0 -8
  290. package/dist/chunk-YNROWHQJ.mjs +0 -46
  291. package/src/components/VirtualList/VirtualList.tsx +0 -60
  292. package/src/components/VirtualList/index.ts +0 -1
  293. package/src/utils/fontGuard.ts +0 -35
  294. package/src/utils/hover.ts +0 -25
  295. package/src/utils/useColorTransition.ts +0 -40
  296. package/src/utils/usePressScale.ts +0 -75
@@ -1,18 +1,18 @@
1
1
  import React from 'react'
2
+ import { Image } from 'expo-image'
2
3
  import {
3
4
  View,
4
5
  Text,
5
6
  StyleSheet,
6
7
  ViewStyle,
7
8
  TextStyle,
8
- Image,
9
9
  ImageSourcePropType,
10
10
  } from 'react-native'
11
11
  import { Entypo } from '@expo/vector-icons'
12
12
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
13
13
  import { useTheme } from '../../theme'
14
14
  import { s, vs, ms, mvs } from '../../utils/scaling'
15
- import { renderIcon } from '../../utils/icons'
15
+ import { Icon } from '../../utils/icons'
16
16
  import { RADIUS } from '../../tokens'
17
17
  import { PressableRow } from '../../utils/pressable'
18
18
 
@@ -85,6 +85,7 @@ export interface ListItemProps {
85
85
  captionStyle?: TextStyle
86
86
  /** Accessibility label override. Defaults to the title. */
87
87
  accessibilityLabel?: string
88
+ accessibilityHint?: string
88
89
  }
89
90
 
90
91
  function ListItemBase({
@@ -110,6 +111,7 @@ function ListItemBase({
110
111
  subtitleNumberOfLines = 2,
111
112
  captionStyle,
112
113
  accessibilityLabel,
114
+ accessibilityHint,
113
115
  }: ListItemProps) {
114
116
  const { colors } = useTheme()
115
117
 
@@ -122,7 +124,7 @@ function ListItemBase({
122
124
  const effectiveLeft: React.ReactNode = imageSource
123
125
  ? <Image source={imageSource} style={styles.image} />
124
126
  : leftIcon
125
- ? renderIcon(leftIcon, 24, leftIconColor ?? colors.foreground)
127
+ ? <Icon name={leftIcon} size={24} color={leftIconColor ?? colors.foreground} />
126
128
  : leftRender
127
129
 
128
130
  const hasRightContent = !!(rightIcon || (rightActions && rightActions.length > 0) || rightRender !== undefined || showChevron)
@@ -181,7 +183,7 @@ function ListItemBase({
181
183
  {hasRightContent ? (
182
184
  rightIcon ? (
183
185
  <View style={styles.rightContainer}>
184
- {renderIcon(rightIcon, 24, rightIconColor ?? colors.foregroundMuted)}
186
+ <Icon name={rightIcon} size={24} color={rightIconColor ?? colors.foregroundMuted} />
185
187
  </View>
186
188
  ) : rightActions && rightActions.length > 0 ? (
187
189
  <View style={styles.rightActionsContainer}>
@@ -221,6 +223,7 @@ function ListItemBase({
221
223
  activateOnHover
222
224
  accessibilityRole="button"
223
225
  accessibilityLabel={a11yLabel}
226
+ accessibilityHint={accessibilityHint}
224
227
  accessibilityState={{ disabled: !!disabled }}
225
228
  >
226
229
  {content}
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
+ import { Image } from 'expo-image'
2
3
  import {
3
4
  View,
4
- Image,
5
5
  Text,
6
6
  TouchableOpacity,
7
7
  StyleSheet,
@@ -9,15 +9,12 @@ import {
9
9
  ImageSourcePropType,
10
10
  Platform,
11
11
  } from 'react-native'
12
- import Animated from 'react-native-reanimated'
13
12
  import { impactLight } from '../../utils/haptics'
14
13
  import { useTheme } from '../../theme'
15
14
  import { s, vs, ms, mvs } from '../../utils/scaling'
16
- import { renderIcon } from '../../utils/icons'
17
- import { useHover } from '../../utils/hover'
18
- import { usePressScale } from '../../utils/usePressScale'
19
- import { SPRINGS, PRESS_SCALE } from '../../utils/animations'
20
- import { RADIUS, SHADOWS } from '../../tokens'
15
+ import { Icon } from '../../utils/icons'
16
+ import { PressableCard } from '../../utils/pressable'
17
+ import { RADIUS } from '../../tokens'
21
18
 
22
19
  export type MediaCardAspectRatio = '1:1' | '4:3' | '16:9' | '4:5' | '3:2'
23
20
 
@@ -30,34 +27,20 @@ const aspectRatioMap: Record<MediaCardAspectRatio, number> = {
30
27
  }
31
28
 
32
29
  export interface MediaCardProps {
33
- /** Image source — URI string or require(). */
34
30
  imageSource?: ImageSourcePropType
35
- /** Image aspect ratio. Defaults to `'4:3'`. */
36
31
  aspectRatio?: MediaCardAspectRatio
37
- /** Badge content rendered top-left over the image (e.g. a Badge component or Text). */
38
32
  badge?: React.ReactNode
39
- /** Icon rendered in a circle button top-right over the image. Defaults to `'heart'`. */
40
33
  actionIcon?: React.ReactNode
41
- /** Icon name for the action button. Overrides `actionIcon`. */
42
34
  actionIconName?: string
43
- /** Whether the action icon is in active/filled state. */
44
35
  actionActive?: boolean
45
- /** Called when the top-right action icon is pressed. */
46
36
  onActionPress?: () => void
47
- /** Primary text below the image. */
48
37
  title?: string
49
- /** Secondary text below the title. */
50
38
  subtitle?: string
51
- /** Tertiary / caption text below subtitle. */
52
39
  caption?: string
53
- /** Called when the card body is pressed. */
54
40
  onPress?: () => void
55
41
  style?: ViewStyle
56
- /** Style for the image container. */
57
42
  imageStyle?: ViewStyle
58
- /** Additional content rendered below caption. */
59
43
  footer?: React.ReactNode
60
- /** Accessibility label override. Defaults to title (and subtitle if present). */
61
44
  accessibilityLabel?: string
62
45
  }
63
46
 
@@ -79,13 +62,6 @@ function MediaCardBase({
79
62
  accessibilityLabel,
80
63
  }: MediaCardProps) {
81
64
  const { colors } = useTheme()
82
- const { hovered, hoverHandlers } = useHover()
83
- const { animatedStyle, onPressIn, onPressOut } = usePressScale({
84
- pressScale: PRESS_SCALE.card,
85
- pressInSpring: SPRINGS.surfacePressIn,
86
- pressOutSpring: SPRINGS.surfacePressOut,
87
- disabled: !onPress,
88
- })
89
65
 
90
66
  const handlePress = () => {
91
67
  if (!onPress) return
@@ -95,22 +71,14 @@ function MediaCardBase({
95
71
 
96
72
  const ratio = aspectRatioMap[aspectRatio]
97
73
 
98
- // Action icon: active = primary fill, inactive = foreground outline
99
74
  const resolvedActionIcon = actionIconName
100
- ? renderIcon(actionIconName, 18, actionActive ? colors.primary : colors.background)
101
- : actionIcon ?? renderIcon('heart', 18, actionActive ? colors.primary : colors.background)
75
+ ? <Icon name={actionIconName} size={18} color={actionActive ? colors.primary : colors.background} />
76
+ : actionIcon ?? <Icon name="heart" size={18} color={actionActive ? colors.primary : colors.background} />
102
77
 
103
78
  const a11yLabel = accessibilityLabel ?? [title, subtitle].filter(Boolean).join('. ')
104
79
 
105
80
  const cardContent = (
106
- <View
107
- style={[
108
- styles.card,
109
- hovered && styles.cardHovered,
110
- style,
111
- ]}
112
- {...(Platform.OS === 'web' ? hoverHandlers : {})}
113
- >
81
+ <View style={[styles.card, style]}>
114
82
  <View style={[styles.imageContainer, imageStyle]}>
115
83
  <View style={{ paddingTop: `${ratio * 100}%` as `${number}%` }}>
116
84
  <View style={StyleSheet.absoluteFill}>
@@ -118,7 +86,7 @@ function MediaCardBase({
118
86
  <Image
119
87
  source={imageSource}
120
88
  style={styles.image}
121
- resizeMode="cover"
89
+ contentFit="cover"
122
90
  />
123
91
  ) : (
124
92
  <View style={[styles.imagePlaceholder, { backgroundColor: colors.surface }]} />
@@ -136,16 +104,14 @@ function MediaCardBase({
136
104
  <TouchableOpacity
137
105
  style={[styles.actionButton, { backgroundColor: 'rgba(0,0,0,0.24)' }]}
138
106
  onPress={(e) => {
139
- // Stop propagation to prevent triggering parent onPress
140
107
  e?.stopPropagation?.()
141
108
  impactLight()
142
109
  onActionPress?.()
143
110
  }}
144
111
  activeOpacity={0.8}
145
112
  touchSoundDisabled={true}
146
- // On web, avoid nested <button> by using a non-button role when parent is pressable
147
113
  accessibilityRole={Platform.OS === 'web' && onPress ? undefined : 'button'}
148
- accessibilityLabel={actionIconName ?? 'action'}
114
+ accessibilityLabel={actionIconName ?? 'acción'}
149
115
  accessibilityState={{ selected: actionActive }}
150
116
  >
151
117
  {resolvedActionIcon}
@@ -178,19 +144,18 @@ function MediaCardBase({
178
144
 
179
145
  if (onPress) {
180
146
  return (
181
- <Animated.View style={animatedStyle}>
182
- <TouchableOpacity
183
- onPress={handlePress}
184
- onPressIn={onPressIn}
185
- onPressOut={onPressOut}
186
- activeOpacity={1}
187
- touchSoundDisabled={true}
188
- accessibilityRole="button"
189
- accessibilityLabel={a11yLabel}
190
- >
191
- {cardContent}
192
- </TouchableOpacity>
193
- </Animated.View>
147
+ <PressableCard
148
+ onPress={handlePress}
149
+ enabled
150
+ rippleColor="transparent"
151
+ touchSoundDisabled
152
+ activateOnHover
153
+ accessibilityRole="button"
154
+ accessibilityLabel={a11yLabel}
155
+ accessibilityState={{ disabled: false }}
156
+ >
157
+ {cardContent}
158
+ </PressableCard>
194
159
  )
195
160
  }
196
161
 
@@ -205,9 +170,6 @@ const styles = StyleSheet.create({
205
170
  overflow: 'hidden',
206
171
  backgroundColor: 'transparent',
207
172
  },
208
- cardHovered: {
209
- ...SHADOWS.md,
210
- },
211
173
  imageContainer: {
212
174
  borderRadius: RADIUS.md,
213
175
  overflow: 'hidden',
@@ -10,7 +10,7 @@ import { Entypo } from '@expo/vector-icons'
10
10
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
11
11
  import { useTheme } from '../../theme'
12
12
  import { s, vs, ms } from '../../utils/scaling'
13
- import { renderIcon } from '../../utils/icons'
13
+ import { Icon } from '../../utils/icons'
14
14
  import { RADIUS } from '../../tokens'
15
15
  import { PressableRow } from '../../utils/pressable'
16
16
 
@@ -55,6 +55,7 @@ export interface MenuItemProps {
55
55
  labelStyle?: TextStyle
56
56
  /** Accessibility label override. Defaults to label. */
57
57
  accessibilityLabel?: string
58
+ accessibilityHint?: string
58
59
  }
59
60
 
60
61
  function MenuItemBase({
@@ -72,6 +73,7 @@ function MenuItemBase({
72
73
  style,
73
74
  labelStyle,
74
75
  accessibilityLabel,
76
+ accessibilityHint,
75
77
  }: MenuItemProps) {
76
78
  const { colors } = useTheme()
77
79
 
@@ -81,7 +83,7 @@ function MenuItemBase({
81
83
  }
82
84
 
83
85
  const resolvedIcon: React.ReactNode = iconName
84
- ? renderIcon(iconName, 22, iconColor ?? colors.foreground)
86
+ ? <Icon name={iconName} size={22} color={iconColor ?? colors.foreground} />
85
87
  : icon
86
88
 
87
89
  const cardStyle: ViewStyle =
@@ -112,6 +114,7 @@ function MenuItemBase({
112
114
  activateOnHover
113
115
  accessibilityRole="button"
114
116
  accessibilityLabel={a11yLabel}
117
+ accessibilityHint={accessibilityHint}
115
118
  accessibilityState={{ disabled }}
116
119
  >
117
120
  {resolvedIcon ? (
@@ -83,7 +83,7 @@ export function MonthPicker({ value, onChange, minValue, maxValue, locale = 'en'
83
83
  activeOpacity={0.6}
84
84
  touchSoundDisabled={true}
85
85
  accessibilityRole="button"
86
- accessibilityLabel="Previous month"
86
+ accessibilityLabel="Mes anterior"
87
87
  accessibilityState={{ disabled: prevDisabled }}
88
88
  hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
89
89
  >
@@ -103,7 +103,7 @@ export function MonthPicker({ value, onChange, minValue, maxValue, locale = 'en'
103
103
  activeOpacity={0.6}
104
104
  touchSoundDisabled={true}
105
105
  accessibilityRole="button"
106
- accessibilityLabel="Next month"
106
+ accessibilityLabel="Mes siguiente"
107
107
  accessibilityState={{ disabled: nextDisabled }}
108
108
  hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
109
109
  >
@@ -3,7 +3,7 @@ import { View, Text, StyleSheet, ViewStyle } from 'react-native'
3
3
  import { impactLight } from '../../utils/haptics'
4
4
  import { useTheme } from '../../theme'
5
5
  import { s, ms, mvs } from '../../utils/scaling'
6
- import { renderIcon } from '../../utils/icons'
6
+ import { Icon } from '../../utils/icons'
7
7
  import { RADIUS } from '../../tokens'
8
8
  import { PressableButton } from '../../utils/pressable'
9
9
 
@@ -19,6 +19,7 @@ export interface NumberStepperProps {
19
19
  disabled?: boolean
20
20
  style?: ViewStyle
21
21
  accessibilityLabel?: string
22
+ accessibilityHint?: string
22
23
  }
23
24
 
24
25
  const sizeConfig: Record<NumberStepperSize, { button: number; icon: number; valueFontSize: number; valueLineHeight: number; valueMinWidth: number }> = {
@@ -37,6 +38,7 @@ function NumberStepperBase({
37
38
  disabled = false,
38
39
  style,
39
40
  accessibilityLabel,
41
+ accessibilityHint,
40
42
  }: NumberStepperProps) {
41
43
  const { colors } = useTheme()
42
44
 
@@ -77,10 +79,11 @@ function NumberStepperBase({
77
79
  rippleColor="transparent"
78
80
  touchSoundDisabled
79
81
  accessibilityRole="button"
80
- accessibilityLabel={`Decrease, current value ${displayValue}`}
82
+ accessibilityLabel={`Disminuir, valor actual ${displayValue}`}
83
+ accessibilityHint={accessibilityHint}
81
84
  accessibilityState={{ disabled: !canDecrement }}
82
85
  >
83
- {renderIcon('minus', iconSize, canDecrement ? colors.foreground : colors.foregroundMuted)}
86
+ <Icon name="minus" size={iconSize} color={canDecrement ? colors.foreground : colors.foregroundMuted} />
84
87
  </PressableButton>
85
88
  <Text
86
89
  style={[
@@ -93,7 +96,7 @@ function NumberStepperBase({
93
96
  },
94
97
  ]}
95
98
  allowFontScaling={true}
96
- accessibilityLabel={accessibilityLabel ?? `Quantity: ${displayValue}`}
99
+ accessibilityLabel={accessibilityLabel ?? `Cantidad: ${displayValue}`}
97
100
  accessibilityRole="text"
98
101
  >
99
102
  {displayValue}
@@ -114,10 +117,11 @@ function NumberStepperBase({
114
117
  rippleColor="transparent"
115
118
  touchSoundDisabled
116
119
  accessibilityRole="button"
117
- accessibilityLabel={`Increase, current value ${displayValue}`}
120
+ accessibilityLabel={`Aumentar, valor actual ${displayValue}`}
121
+ accessibilityHint={accessibilityHint}
118
122
  accessibilityState={{ disabled: !canIncrement }}
119
123
  >
120
- {renderIcon('plus', iconSize, canIncrement ? colors.foreground : colors.foregroundMuted)}
124
+ <Icon name="plus" size={iconSize} color={canIncrement ? colors.foreground : colors.foregroundMuted} />
121
125
  </PressableButton>
122
126
  </View>
123
127
  )
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect } from 'react'
2
- import { View, TouchableOpacity, StyleSheet, ViewStyle } from 'react-native'
2
+ import { View, StyleSheet, ViewStyle } from 'react-native'
3
3
  import Animated, {
4
4
  useSharedValue,
5
5
  useAnimatedStyle,
@@ -10,7 +10,8 @@ import { useTheme } from '../../theme'
10
10
  import { s } from '../../utils/scaling'
11
11
  import { SPRINGS } from '../../utils/animations'
12
12
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
13
- import { renderIcon } from '../../utils/icons'
13
+ import { Icon } from '../../utils/icons'
14
+ import { PressableButton } from '../../utils/pressable'
14
15
 
15
16
  export interface PagerDotsProps {
16
17
  /** Total number of pages. */
@@ -23,7 +24,7 @@ export interface PagerDotsProps {
23
24
  showControls?: boolean | { onPrevious?: () => void; onNext?: () => void }
24
25
  /** Diameter of an inactive dot (dp). Defaults to 8. */
25
26
  dotSize?: number
26
- /** Gap between dots (dp). Defaults to 8. */
27
+ /** Gap between dots (dp). Defaults to 4. */
27
28
  spacing?: number
28
29
  /** Active dot color. Defaults to theme `primary`. */
29
30
  activeColor?: string
@@ -53,7 +54,7 @@ function Dot({ active, size, activeColor, inactiveColor, onPress, index, total }
53
54
 
54
55
  // Active dot stretches into a pill (width = 2.5×). Color crossfades on the UI thread.
55
56
  const animatedStyle = useAnimatedStyle(() => ({
56
- width: size + progress.value * size * 1.5,
57
+ width: size + progress.value * size,
57
58
  backgroundColor: interpolateColor(progress.value, [0, 1], [inactiveColor, activeColor]),
58
59
  }))
59
60
 
@@ -69,16 +70,17 @@ function Dot({ active, size, activeColor, inactiveColor, onPress, index, total }
69
70
  }
70
71
 
71
72
  return (
72
- <TouchableOpacity
73
+ <PressableButton
73
74
  onPress={handlePress}
74
- activeOpacity={0.7}
75
- touchSoundDisabled={true}
75
+ rippleColor="transparent"
76
+ touchSoundDisabled
76
77
  accessibilityRole="button"
77
- accessibilityLabel={`Page ${index + 1} of ${total}${active ? ', current page' : ''}`}
78
- hitSlop={{ top: 8, bottom: 8, left: 4, right: 4 }}
78
+ accessibilityLabel={`Página ${index + 1} de ${total}${active ? ', página actual' : ''}`}
79
+ hitSlop={{ top: 10, bottom: 10, left: 18, right: 18 }}
80
+ style={styles.dotTouchable}
79
81
  >
80
82
  {dot}
81
- </TouchableOpacity>
83
+ </PressableButton>
82
84
  )
83
85
  }
84
86
 
@@ -95,7 +97,7 @@ export function PagerDots({
95
97
  onDotPress,
96
98
  showControls = false,
97
99
  dotSize = 8,
98
- spacing = 8,
100
+ spacing = 4,
99
101
  activeColor,
100
102
  inactiveColor,
101
103
  style,
@@ -133,21 +135,21 @@ export function PagerDots({
133
135
  <View
134
136
  style={[styles.container, { gap: s(spacing) }, style]}
135
137
  accessibilityRole="adjustable"
136
- accessibilityLabel={`Page ${activeIndex + 1} of ${count}`}
138
+ accessibilityLabel={`Página ${activeIndex + 1} de ${count}`}
137
139
  >
138
140
  {hasControls && (
139
- <TouchableOpacity
141
+ <PressableButton
140
142
  onPress={handlePrevious}
141
- disabled={!canGoPrev}
142
- activeOpacity={0.7}
143
- touchSoundDisabled={true}
143
+ enabled={canGoPrev}
144
+ rippleColor="transparent"
145
+ touchSoundDisabled
144
146
  accessibilityRole="button"
145
- accessibilityLabel="Previous page"
146
- hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
147
+ accessibilityLabel="Página anterior"
148
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
147
149
  style={[styles.controlBtn, !canGoPrev && styles.controlBtnDisabled]}
148
150
  >
149
- {renderIcon('chevron-left', s(18), canGoPrev ? colors.foreground : colors.foregroundMuted)}
150
- </TouchableOpacity>
151
+ <Icon name="chevron-left" size={s(18)} color={canGoPrev ? colors.foreground : colors.foregroundMuted} />
152
+ </PressableButton>
151
153
  )}
152
154
  <View style={[styles.dotsRow, { gap: s(spacing) }]}>
153
155
  {Array.from({ length: count }).map((_, i) => (
@@ -164,18 +166,18 @@ export function PagerDots({
164
166
  ))}
165
167
  </View>
166
168
  {hasControls && (
167
- <TouchableOpacity
169
+ <PressableButton
168
170
  onPress={handleNext}
169
- disabled={!canGoNext}
170
- activeOpacity={0.7}
171
- touchSoundDisabled={true}
171
+ enabled={canGoNext}
172
+ rippleColor="transparent"
173
+ touchSoundDisabled
172
174
  accessibilityRole="button"
173
- accessibilityLabel="Next page"
174
- hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
175
+ accessibilityLabel="Página siguiente"
176
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
175
177
  style={[styles.controlBtn, !canGoNext && styles.controlBtnDisabled]}
176
178
  >
177
- {renderIcon('chevron-right', s(18), canGoNext ? colors.foreground : colors.foregroundMuted)}
178
- </TouchableOpacity>
179
+ <Icon name="chevron-right" size={s(18)} color={canGoNext ? colors.foreground : colors.foregroundMuted} />
180
+ </PressableButton>
179
181
  )}
180
182
  </View>
181
183
  )
@@ -193,6 +195,14 @@ const styles = StyleSheet.create({
193
195
  },
194
196
  controlBtn: {
195
197
  padding: s(4),
198
+ minHeight: 44,
199
+ justifyContent: 'center',
200
+ },
201
+ dotTouchable: {
202
+ minHeight: 44,
203
+ paddingHorizontal: s(14),
204
+ justifyContent: 'center',
205
+ alignItems: 'center',
196
206
  },
197
207
  controlBtnDisabled: {
198
208
  opacity: 0.3,
@@ -3,7 +3,7 @@ import { View, Text, StyleSheet, ViewStyle } from 'react-native'
3
3
  import { useTheme } from '../../theme'
4
4
  import { Button } from '../Button'
5
5
  import { Badge } from '../Badge'
6
- import { renderIcon } from '../../utils/icons'
6
+ import { Icon } from '../../utils/icons'
7
7
  import { s, vs, ms, mvs } from '../../utils/scaling'
8
8
  import { RADIUS, SHADOWS } from '../../tokens'
9
9
 
@@ -113,11 +113,11 @@ export function PricingCard({
113
113
  <View style={styles.features}>
114
114
  {features.map(normalize).map((f, i) => (
115
115
  <View key={i} style={styles.featureRow}>
116
- {renderIcon(
117
- f.included ? 'check' : 'minus',
118
- ms(16),
119
- f.included ? colors.success : colors.foregroundMuted,
120
- )}
116
+ <Icon
117
+ name={f.included ? 'check' : 'minus'}
118
+ size={ms(16)}
119
+ color={f.included ? colors.success : colors.foregroundMuted}
120
+ />
121
121
  <Text
122
122
  style={[
123
123
  styles.featureLabel,
@@ -1,12 +1,11 @@
1
1
  import React from 'react'
2
- import { TouchableOpacity, View, Text, StyleSheet, ViewStyle } from 'react-native'
3
- import Animated from 'react-native-reanimated'
2
+ import { View, Text, StyleSheet, ViewStyle } from 'react-native'
4
3
  import { EaseView } from 'react-native-ease'
5
4
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
6
5
  import { useTheme } from '../../theme'
7
6
  import { s, vs, ms, mvs } from '../../utils/scaling'
8
- import { usePressScale } from '../../utils/usePressScale'
9
- import { COLOR_TRANSITION, SPRING_ELASTIC, PRESS_SCALE } from '../../utils/animations'
7
+ import { PressableButton } from '../../utils/pressable'
8
+ import { COLOR_TRANSITION, SPRING_ELASTIC } from '../../utils/animations'
10
9
 
11
10
  export interface RadioOption {
12
11
  label: string
@@ -33,16 +32,9 @@ function RadioItem({
33
32
  onSelect: () => void
34
33
  }) {
35
34
  const { colors } = useTheme()
36
- const { animatedStyle: scaleStyle, onPressIn, onPressOut } = usePressScale({
37
- pressScale: PRESS_SCALE.button,
38
- disabled: option.disabled,
39
- })
40
35
 
41
36
  return (
42
- // AUDIT FIX: opacity was applied only to the radio circle, leaving the label
43
- // at full opacity when disabled. The whole row now dims uniformly so users
44
- // get a single, consistent disabled signal across the entire item.
45
- <TouchableOpacity
37
+ <PressableButton
46
38
  style={[styles.row, option.disabled && styles.rowDisabled]}
47
39
  onPress={() => {
48
40
  if (!option.disabled) {
@@ -50,35 +42,31 @@ function RadioItem({
50
42
  onSelect()
51
43
  }
52
44
  }}
53
- onPressIn={onPressIn}
54
- onPressOut={onPressOut}
55
- activeOpacity={1}
56
- touchSoundDisabled={true}
57
- disabled={option.disabled}
45
+ enabled={!option.disabled}
46
+ rippleColor="transparent"
47
+ touchSoundDisabled
58
48
  accessibilityRole="radio"
59
49
  accessibilityLabel={option.label}
60
50
  accessibilityState={{ checked: selected, disabled: !!option.disabled }}
61
51
  >
62
- <Animated.View style={scaleStyle}>
52
+ <EaseView
53
+ style={styles.radio}
54
+ animate={{ borderColor: selected ? colors.primary : colors.border }}
55
+ transition={COLOR_TRANSITION}
56
+ >
63
57
  <EaseView
64
- style={styles.radio}
65
- animate={{ borderColor: selected ? colors.primary : colors.border }}
66
- transition={COLOR_TRANSITION}
67
- >
68
- <EaseView
69
- style={[styles.dot, { backgroundColor: colors.primary }]}
70
- animate={{ scale: selected ? 1 : 0, opacity: selected ? 1 : 0 }}
71
- transition={SPRING_ELASTIC}
72
- />
73
- </EaseView>
74
- </Animated.View>
58
+ style={[styles.dot, { backgroundColor: colors.primary }]}
59
+ animate={{ scale: selected ? 1 : 0, opacity: selected ? 1 : 0 }}
60
+ transition={SPRING_ELASTIC}
61
+ />
62
+ </EaseView>
75
63
  <Text
76
64
  style={[styles.label, { color: colors.foreground }]}
77
65
  allowFontScaling={true}
78
66
  >
79
67
  {option.label}
80
68
  </Text>
81
- </TouchableOpacity>
69
+ </PressableButton>
82
70
  )
83
71
  }
84
72
 
@@ -121,7 +109,6 @@ const styles = StyleSheet.create({
121
109
  alignItems: 'center',
122
110
  gap: s(12),
123
111
  },
124
- // AUDIT FIX: was opacity on the inner circle only
125
112
  rowDisabled: {
126
113
  opacity: 0.45,
127
114
  },