@retray-dev/ui-kit 12.1.0 → 13.0.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 (282) hide show
  1. package/COMPONENTS.md +183 -147
  2. package/CONSUMER.md +2 -2
  3. package/DESIGN.md +2 -2
  4. package/README.md +13 -8
  5. package/dist/Accordion.d.mts +6 -0
  6. package/dist/Accordion.d.ts +6 -0
  7. package/dist/Accordion.js +62 -208
  8. package/dist/Accordion.mjs +6 -5
  9. package/dist/AlertBanner.js +29 -151
  10. package/dist/AlertBanner.mjs +3 -3
  11. package/dist/AppHeader.js +37 -233
  12. package/dist/AppHeader.mjs +6 -7
  13. package/dist/Avatar.d.mts +17 -1
  14. package/dist/Avatar.d.ts +17 -1
  15. package/dist/Avatar.js +80 -113
  16. package/dist/Avatar.mjs +2 -2
  17. package/dist/Badge.js +24 -147
  18. package/dist/Badge.mjs +3 -3
  19. package/dist/Button.js +86 -274
  20. package/dist/Button.mjs +6 -6
  21. package/dist/Card.js +15 -198
  22. package/dist/Card.mjs +4 -5
  23. package/dist/CategoryStrip.d.mts +0 -5
  24. package/dist/CategoryStrip.d.ts +0 -5
  25. package/dist/CategoryStrip.js +47 -263
  26. package/dist/CategoryStrip.mjs +6 -6
  27. package/dist/Checkbox.js +15 -198
  28. package/dist/Checkbox.mjs +5 -5
  29. package/dist/Chip.js +44 -234
  30. package/dist/Chip.mjs +7 -6
  31. package/dist/ConfirmDialog.js +100 -296
  32. package/dist/ConfirmDialog.mjs +7 -7
  33. package/dist/CurrencyDisplay.js +1 -112
  34. package/dist/CurrencyDisplay.mjs +2 -2
  35. package/dist/CurrencyInput.js +35 -160
  36. package/dist/CurrencyInput.mjs +5 -5
  37. package/dist/DetailRow.js +25 -148
  38. package/dist/DetailRow.mjs +3 -3
  39. package/dist/EmptyState.js +87 -275
  40. package/dist/EmptyState.mjs +7 -7
  41. package/dist/ErrorBoundary.js +32 -197
  42. package/dist/ErrorBoundary.mjs +4 -4
  43. package/dist/Form.js +1 -112
  44. package/dist/Form.mjs +2 -2
  45. package/dist/HolographicCard.d.mts +0 -28
  46. package/dist/HolographicCard.d.ts +0 -28
  47. package/dist/HolographicCard.js +20 -130
  48. package/dist/HolographicCard.mjs +9 -32
  49. package/dist/IconButton.js +36 -232
  50. package/dist/IconButton.mjs +5 -6
  51. package/dist/IconPicker.js +222 -927
  52. package/dist/IconPicker.mjs +5 -5
  53. package/dist/ImageUpload.d.mts +5 -1
  54. package/dist/ImageUpload.d.ts +5 -1
  55. package/dist/ImageUpload.js +32 -215
  56. package/dist/ImageUpload.mjs +5 -6
  57. package/dist/ImageViewer.js +75 -264
  58. package/dist/ImageViewer.mjs +8 -8
  59. package/dist/Input.d.mts +1 -1
  60. package/dist/Input.d.ts +1 -1
  61. package/dist/Input.js +35 -160
  62. package/dist/Input.mjs +4 -4
  63. package/dist/LabelValue.js +24 -147
  64. package/dist/LabelValue.mjs +3 -3
  65. package/dist/ListGroup.js +1 -112
  66. package/dist/ListGroup.mjs +2 -2
  67. package/dist/ListItem.js +38 -233
  68. package/dist/ListItem.mjs +5 -6
  69. package/dist/MediaCard.d.mts +0 -14
  70. package/dist/MediaCard.d.ts +0 -14
  71. package/dist/MediaCard.js +69 -313
  72. package/dist/MediaCard.mjs +5 -6
  73. package/dist/MenuGroup.js +1 -112
  74. package/dist/MenuGroup.mjs +2 -2
  75. package/dist/MenuItem.js +36 -232
  76. package/dist/MenuItem.mjs +5 -6
  77. package/dist/MonthPicker.js +8 -161
  78. package/dist/MonthPicker.mjs +3 -3
  79. package/dist/NumberStepper.js +40 -236
  80. package/dist/NumberStepper.mjs +5 -6
  81. package/dist/PagerDots.d.mts +1 -1
  82. package/dist/PagerDots.d.ts +1 -1
  83. package/dist/PagerDots.js +69 -222
  84. package/dist/PagerDots.mjs +6 -5
  85. package/dist/Pressable.js +14 -85
  86. package/dist/Pressable.mjs +4 -4
  87. package/dist/PricingCard.js +94 -279
  88. package/dist/PricingCard.mjs +8 -8
  89. package/dist/Progress.js +3 -121
  90. package/dist/Progress.mjs +3 -3
  91. package/dist/RadioGroup.js +52 -263
  92. package/dist/RadioGroup.mjs +5 -5
  93. package/dist/RetrayProvider.d.mts +1 -1
  94. package/dist/RetrayProvider.d.ts +1 -1
  95. package/dist/RetrayProvider.js +5 -6
  96. package/dist/RetrayProvider.mjs +3 -3
  97. package/dist/Select.d.mts +2 -1
  98. package/dist/Select.d.ts +2 -1
  99. package/dist/Select.js +24 -230
  100. package/dist/Select.mjs +4 -5
  101. package/dist/SelectableCard.d.mts +27 -0
  102. package/dist/SelectableCard.d.ts +27 -0
  103. package/dist/SelectableCard.js +335 -0
  104. package/dist/SelectableCard.mjs +8 -0
  105. package/dist/SelectableGrid.d.mts +0 -21
  106. package/dist/SelectableGrid.d.ts +0 -21
  107. package/dist/SelectableGrid.js +49 -269
  108. package/dist/SelectableGrid.mjs +5 -6
  109. package/dist/Separator.js +1 -112
  110. package/dist/Separator.mjs +2 -2
  111. package/dist/Sheet.js +16 -163
  112. package/dist/Sheet.mjs +3 -3
  113. package/dist/SheetSelect.js +39 -234
  114. package/dist/SheetSelect.mjs +6 -6
  115. package/dist/Skeleton.d.mts +3 -1
  116. package/dist/Skeleton.d.ts +3 -1
  117. package/dist/Skeleton.js +7 -124
  118. package/dist/Skeleton.mjs +3 -3
  119. package/dist/Slider.js +6 -159
  120. package/dist/Slider.mjs +3 -3
  121. package/dist/Spinner.js +3 -114
  122. package/dist/Spinner.mjs +2 -2
  123. package/dist/Stats.d.mts +4 -1
  124. package/dist/Stats.d.ts +4 -1
  125. package/dist/Stats.js +60 -234
  126. package/dist/Stats.mjs +5 -6
  127. package/dist/Switch.js +24 -173
  128. package/dist/Switch.mjs +5 -4
  129. package/dist/TabBar.js +43 -198
  130. package/dist/TabBar.mjs +5 -4
  131. package/dist/Tabs.js +15 -197
  132. package/dist/Tabs.mjs +5 -5
  133. package/dist/Text.js +9 -128
  134. package/dist/Text.mjs +2 -2
  135. package/dist/Textarea.d.mts +2 -1
  136. package/dist/Textarea.d.ts +2 -1
  137. package/dist/Textarea.js +71 -217
  138. package/dist/Textarea.mjs +4 -4
  139. package/dist/Toast.js +1 -112
  140. package/dist/Toast.mjs +2 -2
  141. package/dist/Toggle.js +39 -234
  142. package/dist/Toggle.mjs +6 -6
  143. package/dist/{chunk-FFTYLPSB.mjs → chunk-2QOHHBJC.mjs} +13 -7
  144. package/dist/{chunk-BCWEHE34.mjs → chunk-2VIDP72N.mjs} +3 -3
  145. package/dist/{chunk-PGERH3P7.mjs → chunk-4NQFTHN3.mjs} +13 -7
  146. package/dist/{chunk-3N2M3WZL.mjs → chunk-4ZO5PTKF.mjs} +4 -4
  147. package/dist/{chunk-MYZ2EDYU.mjs → chunk-5MYNAAFE.mjs} +13 -17
  148. package/dist/{chunk-E7NEHHXV.mjs → chunk-62BBSSUF.mjs} +3 -3
  149. package/dist/{chunk-ISY26JQJ.mjs → chunk-6CR4S6W2.mjs} +3 -3
  150. package/dist/{chunk-FUVYSVGR.mjs → chunk-6QLBHUEG.mjs} +8 -7
  151. package/dist/chunk-ARONDO7M.mjs +40 -0
  152. package/dist/{chunk-3UYAZ7I4.mjs → chunk-AZV7KNJI.mjs} +3 -3
  153. package/dist/{chunk-HLMPMUK2.mjs → chunk-BTUW5LSG.mjs} +11 -8
  154. package/dist/chunk-BULKGOIZ.mjs +235 -0
  155. package/dist/{chunk-265G6A46.mjs → chunk-CBIZLRYH.mjs} +29 -12
  156. package/dist/chunk-CM2DG4MR.mjs +142 -0
  157. package/dist/{chunk-2I2AYECM.mjs → chunk-DBHSUUKU.mjs} +2 -2
  158. package/dist/{chunk-P64WHW4A.mjs → chunk-DE25XTVQ.mjs} +3 -3
  159. package/dist/{chunk-DI7CBDL6.mjs → chunk-E4EQSCKR.mjs} +5 -5
  160. package/dist/{chunk-357YO24D.mjs → chunk-EHGBHFMH.mjs} +9 -17
  161. package/dist/{chunk-GK4VRMNE.mjs → chunk-EROPDCB5.mjs} +24 -27
  162. package/dist/{chunk-XBAGGKLW.mjs → chunk-ERWJPVX7.mjs} +2 -2
  163. package/dist/{chunk-LRM4AVYY.mjs → chunk-ESQDPO5E.mjs} +7 -7
  164. package/dist/{chunk-EFLFRAHD.mjs → chunk-EW2FIDSM.mjs} +1 -1
  165. package/dist/{chunk-7HSILTC4.mjs → chunk-FTTI6T5Q.mjs} +4 -4
  166. package/dist/{chunk-X26S5EVZ.mjs → chunk-HUSSF6TF.mjs} +1 -1
  167. package/dist/chunk-IFYMBOEN.mjs +14 -0
  168. package/dist/{chunk-S3KJCPEJ.mjs → chunk-IGU223UM.mjs} +80 -4
  169. package/dist/chunk-IJCMPVW5.mjs +121 -0
  170. package/dist/{chunk-I4V5XZPS.mjs → chunk-ITG4JQM3.mjs} +4 -4
  171. package/dist/{chunk-F4V6XLP4.mjs → chunk-K3QX2M26.mjs} +11 -8
  172. package/dist/{chunk-V6NFJXKO.mjs → chunk-K7TKID3V.mjs} +8 -7
  173. package/dist/{chunk-ZHMSAYLT.mjs → chunk-KAGADD2O.mjs} +4 -4
  174. package/dist/{chunk-3GEYJ7I5.mjs → chunk-KC5QDYGZ.mjs} +4 -4
  175. package/dist/{chunk-HJ46DTJE.mjs → chunk-KPTY7UYQ.mjs} +1 -1
  176. package/dist/{chunk-EMUWGDWC.mjs → chunk-KSSVIFYR.mjs} +11 -12
  177. package/dist/chunk-L3YKPTJQ.mjs +119 -0
  178. package/dist/chunk-M53LC4Q7.mjs +35 -0
  179. package/dist/{chunk-NXI4YDZ2.mjs → chunk-MP7GLMIR.mjs} +17 -25
  180. package/dist/chunk-MZ6WRTD2.mjs +40 -0
  181. package/dist/chunk-NGEN2EES.mjs +581 -0
  182. package/dist/{chunk-JULSIZDM.mjs → chunk-OBV72JD4.mjs} +1 -1
  183. package/dist/{chunk-2A2LEFZG.mjs → chunk-PGQ6FMXS.mjs} +6 -5
  184. package/dist/{chunk-BQZE3HAW.mjs → chunk-PI6RULJX.mjs} +1 -1
  185. package/dist/{chunk-FA2KMTH5.mjs → chunk-RA6SAAFE.mjs} +9 -8
  186. package/dist/{chunk-FVTVCJAH.mjs → chunk-RRKM4MKB.mjs} +7 -7
  187. package/dist/{chunk-AKM4EPOT.mjs → chunk-S2VGME7X.mjs} +1 -1
  188. package/dist/{chunk-OULVKTWL.mjs → chunk-S44XWTTC.mjs} +35 -25
  189. package/dist/{chunk-QSFV2P7O.mjs → chunk-SZEKQAOY.mjs} +1 -1
  190. package/dist/{chunk-N4ZPVCJH.mjs → chunk-TETMEKZE.mjs} +9 -9
  191. package/dist/{chunk-2CBQKU7H.mjs → chunk-TMH263OK.mjs} +5 -4
  192. package/dist/{chunk-D3Y2T42P.mjs → chunk-U6DEBYU5.mjs} +10 -9
  193. package/dist/{chunk-4WFMPFZB.mjs → chunk-UOKFSFNJ.mjs} +2 -2
  194. package/dist/{chunk-WOEWGSTU.mjs → chunk-URIH43IJ.mjs} +13 -21
  195. package/dist/{chunk-JCZQOY4O.mjs → chunk-V2ZB2XNS.mjs} +16 -10
  196. package/dist/{chunk-P73V2EKS.mjs → chunk-WIPEDNSD.mjs} +7 -7
  197. package/dist/{chunk-BOVUP27T.mjs → chunk-XCIG6HT2.mjs} +6 -5
  198. package/dist/chunk-Y6YS33GM.mjs +131 -0
  199. package/dist/{chunk-5OLNXP3S.mjs → chunk-ZKDKKQCE.mjs} +29 -7
  200. package/dist/{chunk-DF6DU42P.mjs → chunk-ZTPYUU5C.mjs} +5 -5
  201. package/dist/{index-wt-orHUi.d.ts → index-CY34hxPN.d.mts} +1 -0
  202. package/dist/{index-wt-orHUi.d.mts → index-CY34hxPN.d.ts} +1 -0
  203. package/dist/index.d.mts +15 -74
  204. package/dist/index.d.ts +15 -74
  205. package/dist/index.js +1055 -1562
  206. package/dist/index.mjs +81 -84
  207. package/package.json +8 -10
  208. package/src/components/Accordion/Accordion.tsx +32 -9
  209. package/src/components/AlertBanner/AlertBanner.tsx +7 -6
  210. package/src/components/AppHeader/AppHeader.tsx +1 -1
  211. package/src/components/Avatar/Avatar.tsx +92 -1
  212. package/src/components/Avatar/index.ts +2 -2
  213. package/src/components/Badge/Badge.tsx +2 -2
  214. package/src/components/Button/Button.tsx +64 -57
  215. package/src/components/Card/Card.tsx +1 -0
  216. package/src/components/CategoryStrip/CategoryStrip.tsx +36 -49
  217. package/src/components/Chip/Chip.tsx +5 -4
  218. package/src/components/ConfirmDialog/ConfirmDialog.tsx +13 -6
  219. package/src/components/DetailRow/DetailRow.tsx +3 -3
  220. package/src/components/EmptyState/EmptyState.tsx +2 -2
  221. package/src/components/ErrorBoundary/ErrorBoundary.tsx +6 -6
  222. package/src/components/HolographicCard/HolographicCard.tsx +14 -95
  223. package/src/components/IconButton/IconButton.tsx +2 -2
  224. package/src/components/IconPicker/IconPicker.tsx +13 -12
  225. package/src/components/ImageUpload/ImageUpload.tsx +24 -28
  226. package/src/components/ImageViewer/ImageViewer.tsx +3 -3
  227. package/src/components/Input/Input.tsx +11 -5
  228. package/src/components/LabelValue/LabelValue.tsx +2 -2
  229. package/src/components/ListItem/ListItem.tsx +4 -4
  230. package/src/components/MediaCard/MediaCard.tsx +21 -59
  231. package/src/components/MenuItem/MenuItem.tsx +2 -2
  232. package/src/components/MonthPicker/MonthPicker.tsx +2 -2
  233. package/src/components/NumberStepper/NumberStepper.tsx +6 -6
  234. package/src/components/PagerDots/PagerDots.tsx +38 -28
  235. package/src/components/PricingCard/PricingCard.tsx +6 -6
  236. package/src/components/RadioGroup/RadioGroup.tsx +18 -31
  237. package/src/components/Select/Select.tsx +32 -39
  238. package/src/components/SelectableCard/SelectableCard.tsx +302 -0
  239. package/src/components/SelectableCard/index.ts +1 -0
  240. package/src/components/SelectableGrid/SelectableGrid.tsx +38 -72
  241. package/src/components/Sheet/Sheet.tsx +11 -4
  242. package/src/components/SheetSelect/SheetSelect.tsx +3 -3
  243. package/src/components/Skeleton/Skeleton.tsx +6 -3
  244. package/src/components/Spinner/Spinner.tsx +2 -2
  245. package/src/components/Stats/Stats.tsx +36 -8
  246. package/src/components/Switch/Switch.tsx +9 -6
  247. package/src/components/TabBar/TabBar.tsx +9 -8
  248. package/src/components/Text/Text.tsx +12 -1
  249. package/src/components/Textarea/Textarea.tsx +18 -32
  250. package/src/components/Toggle/Toggle.tsx +3 -3
  251. package/src/hooks/useConfirmDialog.ts +31 -42
  252. package/src/index.ts +4 -4
  253. package/src/theme/ThemeProvider.tsx +1 -4
  254. package/src/theme/colorUtils.ts +1 -72
  255. package/src/theme/colors.ts +47 -1
  256. package/src/theme/types.ts +6 -3
  257. package/src/utils/animations.ts +0 -47
  258. package/src/utils/curatedIcons.ts +93 -801
  259. package/src/utils/haptics.ts +13 -208
  260. package/src/utils/icons.ts +27 -91
  261. package/src/utils/pressable.ts +10 -61
  262. package/dist/VirtualList.d.mts +0 -19
  263. package/dist/VirtualList.d.ts +0 -19
  264. package/dist/VirtualList.js +0 -38
  265. package/dist/VirtualList.mjs +0 -2
  266. package/dist/chunk-3DKJ2GIC.mjs +0 -30
  267. package/dist/chunk-AQEVCEXV.mjs +0 -164
  268. package/dist/chunk-DOGIPOF5.mjs +0 -131
  269. package/dist/chunk-DVK4G2GT.mjs +0 -59
  270. package/dist/chunk-EJ7ZPXOH.mjs +0 -163
  271. package/dist/chunk-J6Q2YJEV.mjs +0 -134
  272. package/dist/chunk-JNVAIDLK.mjs +0 -136
  273. package/dist/chunk-KA7LTET3.mjs +0 -71
  274. package/dist/chunk-KHYX4IOM.mjs +0 -1114
  275. package/dist/chunk-NC5ZTR2Y.mjs +0 -32
  276. package/dist/chunk-YNROWHQJ.mjs +0 -46
  277. package/src/components/VirtualList/VirtualList.tsx +0 -60
  278. package/src/components/VirtualList/index.ts +0 -1
  279. package/src/utils/fontGuard.ts +0 -35
  280. package/src/utils/hover.ts +0 -25
  281. package/src/utils/useColorTransition.ts +0 -40
  282. package/src/utils/usePressScale.ts +0 -75
@@ -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
 
@@ -81,7 +81,7 @@ function MenuItemBase({
81
81
  }
82
82
 
83
83
  const resolvedIcon: React.ReactNode = iconName
84
- ? renderIcon(iconName, 22, iconColor ?? colors.foreground)
84
+ ? <Icon name={iconName} size={22} color={iconColor ?? colors.foreground} />
85
85
  : icon
86
86
 
87
87
  const cardStyle: ViewStyle =
@@ -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
 
@@ -77,10 +77,10 @@ function NumberStepperBase({
77
77
  rippleColor="transparent"
78
78
  touchSoundDisabled
79
79
  accessibilityRole="button"
80
- accessibilityLabel={`Decrease, current value ${displayValue}`}
80
+ accessibilityLabel={`Disminuir, valor actual ${displayValue}`}
81
81
  accessibilityState={{ disabled: !canDecrement }}
82
82
  >
83
- {renderIcon('minus', iconSize, canDecrement ? colors.foreground : colors.foregroundMuted)}
83
+ <Icon name="minus" size={iconSize} color={canDecrement ? colors.foreground : colors.foregroundMuted} />
84
84
  </PressableButton>
85
85
  <Text
86
86
  style={[
@@ -93,7 +93,7 @@ function NumberStepperBase({
93
93
  },
94
94
  ]}
95
95
  allowFontScaling={true}
96
- accessibilityLabel={accessibilityLabel ?? `Quantity: ${displayValue}`}
96
+ accessibilityLabel={accessibilityLabel ?? `Cantidad: ${displayValue}`}
97
97
  accessibilityRole="text"
98
98
  >
99
99
  {displayValue}
@@ -114,10 +114,10 @@ function NumberStepperBase({
114
114
  rippleColor="transparent"
115
115
  touchSoundDisabled
116
116
  accessibilityRole="button"
117
- accessibilityLabel={`Increase, current value ${displayValue}`}
117
+ accessibilityLabel={`Aumentar, valor actual ${displayValue}`}
118
118
  accessibilityState={{ disabled: !canIncrement }}
119
119
  >
120
- {renderIcon('plus', iconSize, canIncrement ? colors.foreground : colors.foregroundMuted)}
120
+ <Icon name="plus" size={iconSize} color={canIncrement ? colors.foreground : colors.foregroundMuted} />
121
121
  </PressableButton>
122
122
  </View>
123
123
  )
@@ -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
  },
@@ -1,13 +1,11 @@
1
1
  import React, { useRef, useState } from 'react'
2
2
  import { View, Text, TouchableOpacity, Modal, StyleSheet, ViewStyle, Platform } from 'react-native'
3
- import Animated from 'react-native-reanimated'
4
3
  import { Picker } from '@react-native-picker/picker'
5
4
  import { Entypo } from '@expo/vector-icons'
6
5
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
7
6
  import { useTheme } from '../../theme'
8
7
  import { s, vs, ms } from '../../utils/scaling'
9
- import { usePressScale } from '../../utils/usePressScale'
10
- import { PRESS_SCALE } from '../../utils/animations'
8
+ import { PressableButton } from '../../utils/pressable'
11
9
 
12
10
  const isIOS = Platform.OS === 'ios'
13
11
  const isAndroid = Platform.OS === 'android'
@@ -25,6 +23,7 @@ export interface SelectProps {
25
23
  onValueChange?: (value: string) => void
26
24
  placeholder?: string
27
25
  label?: string
26
+ hint?: string
28
27
  error?: string
29
28
  disabled?: boolean
30
29
  style?: ViewStyle
@@ -37,16 +36,13 @@ export function Select({
37
36
  onValueChange,
38
37
  placeholder = 'Select an option',
39
38
  label,
39
+ hint,
40
40
  error,
41
41
  disabled,
42
42
  style,
43
43
  accessibilityLabel,
44
44
  }: SelectProps) {
45
45
  const { colors } = useTheme()
46
- const { animatedStyle, onPressIn, onPressOut } = usePressScale({
47
- pressScale: PRESS_SCALE.button,
48
- disabled,
49
- })
50
46
  const [pickerVisible, setPickerVisible] = useState(false)
51
47
  const [pendingValue, setPendingValue] = useState<string | undefined>(value)
52
48
  const pickerRef = useRef<React.ElementRef<typeof Picker>>(null)
@@ -82,38 +78,36 @@ export function Select({
82
78
 
83
79
  {/* Trigger button — shown on iOS and Android only */}
84
80
  {!isWeb ? (
85
- <Animated.View style={[animatedStyle, { opacity: disabled ? 0.45 : 1 }]}>
86
- <TouchableOpacity
81
+ <PressableButton
82
+ style={[
83
+ styles.trigger,
84
+ {
85
+ borderColor: error ? colors.destructive : colors.border,
86
+ backgroundColor: colors.background,
87
+ },
88
+ disabled && { opacity: 0.45 },
89
+ ]}
90
+ onPress={handleOpen}
91
+ enabled={!disabled}
92
+ rippleColor="transparent"
93
+ touchSoundDisabled
94
+ accessibilityRole="combobox"
95
+ accessibilityLabel={accessibilityLabel ?? label}
96
+ accessibilityValue={{ text: selected?.label ?? placeholder }}
97
+ accessibilityState={{ disabled: !!disabled, expanded: pickerVisible }}
98
+ >
99
+ <Text
87
100
  style={[
88
- styles.trigger,
89
- {
90
- borderColor: error ? colors.destructive : colors.border,
91
- backgroundColor: colors.background,
92
- },
101
+ styles.triggerText,
102
+ { color: selected ? colors.foreground : colors.foregroundMuted },
93
103
  ]}
94
- onPress={handleOpen}
95
- onPressIn={onPressIn}
96
- onPressOut={onPressOut}
97
- activeOpacity={1}
98
- touchSoundDisabled={true}
99
- accessibilityRole="combobox"
100
- accessibilityLabel={accessibilityLabel ?? label}
101
- accessibilityValue={{ text: selected?.label ?? placeholder }}
102
- accessibilityState={{ disabled: !!disabled, expanded: pickerVisible }}
104
+ numberOfLines={1}
105
+ allowFontScaling={true}
103
106
  >
104
- <Text
105
- style={[
106
- styles.triggerText,
107
- { color: selected ? colors.foreground : colors.foregroundMuted },
108
- ]}
109
- numberOfLines={1}
110
- allowFontScaling={true}
111
- >
112
- {selected?.label ?? placeholder}
113
- </Text>
114
- <Entypo name="chevron-down" size={20} color={colors.foregroundMuted} />
115
- </TouchableOpacity>
116
- </Animated.View>
107
+ {selected?.label ?? placeholder}
108
+ </Text>
109
+ <Entypo name="chevron-down" size={20} color={colors.foregroundMuted} />
110
+ </PressableButton>
117
111
  ) : null}
118
112
 
119
113
  {/* iOS: Modal with wheel Picker */}
@@ -224,6 +218,8 @@ export function Select({
224
218
 
225
219
  {error ? (
226
220
  <Text style={[styles.helperText, { color: colors.destructive }]} allowFontScaling={true}>{error}</Text>
221
+ ) : !error && hint ? (
222
+ <Text style={[styles.helperText, { color: colors.foregroundMuted }]} allowFontScaling={true}>{hint}</Text>
227
223
  ) : null}
228
224
  </View>
229
225
  )
@@ -251,9 +247,6 @@ const styles = StyleSheet.create({
251
247
  fontSize: ms(15),
252
248
  flex: 1,
253
249
  },
254
- chevron: {
255
- marginLeft: s(8),
256
- },
257
250
  helperText: {
258
251
  fontFamily: 'Sohne-Regular',
259
252
  fontSize: ms(13),