@retray-dev/ui-kit 12.2.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 (268) hide show
  1. package/COMPONENTS.md +85 -143
  2. package/CONSUMER.md +2 -2
  3. package/DESIGN.md +2 -2
  4. package/README.md +11 -6
  5. package/dist/Accordion.js +48 -210
  6. package/dist/Accordion.mjs +6 -5
  7. package/dist/AlertBanner.js +29 -153
  8. package/dist/AlertBanner.mjs +3 -3
  9. package/dist/AppHeader.js +37 -235
  10. package/dist/AppHeader.mjs +6 -7
  11. package/dist/Avatar.d.mts +17 -1
  12. package/dist/Avatar.d.ts +17 -1
  13. package/dist/Avatar.js +80 -115
  14. package/dist/Avatar.mjs +2 -2
  15. package/dist/Badge.js +24 -149
  16. package/dist/Badge.mjs +3 -3
  17. package/dist/Button.js +79 -267
  18. package/dist/Button.mjs +6 -6
  19. package/dist/Card.js +15 -200
  20. package/dist/Card.mjs +4 -5
  21. package/dist/CategoryStrip.d.mts +0 -5
  22. package/dist/CategoryStrip.d.ts +0 -5
  23. package/dist/CategoryStrip.js +47 -265
  24. package/dist/CategoryStrip.mjs +6 -6
  25. package/dist/Checkbox.js +15 -200
  26. package/dist/Checkbox.mjs +5 -5
  27. package/dist/Chip.js +44 -236
  28. package/dist/Chip.mjs +7 -6
  29. package/dist/ConfirmDialog.js +84 -286
  30. package/dist/ConfirmDialog.mjs +7 -7
  31. package/dist/CurrencyDisplay.js +1 -114
  32. package/dist/CurrencyDisplay.mjs +2 -2
  33. package/dist/CurrencyInput.js +35 -162
  34. package/dist/CurrencyInput.mjs +5 -5
  35. package/dist/DetailRow.js +25 -150
  36. package/dist/DetailRow.mjs +3 -3
  37. package/dist/EmptyState.js +80 -268
  38. package/dist/EmptyState.mjs +7 -7
  39. package/dist/ErrorBoundary.js +32 -199
  40. package/dist/ErrorBoundary.mjs +4 -4
  41. package/dist/Form.js +1 -114
  42. package/dist/Form.mjs +2 -2
  43. package/dist/HolographicCard.d.mts +0 -28
  44. package/dist/HolographicCard.d.ts +0 -28
  45. package/dist/HolographicCard.js +20 -130
  46. package/dist/HolographicCard.mjs +9 -32
  47. package/dist/IconButton.js +36 -234
  48. package/dist/IconButton.mjs +5 -6
  49. package/dist/IconPicker.js +222 -929
  50. package/dist/IconPicker.mjs +5 -5
  51. package/dist/ImageUpload.d.mts +3 -1
  52. package/dist/ImageUpload.d.ts +3 -1
  53. package/dist/ImageUpload.js +25 -215
  54. package/dist/ImageUpload.mjs +5 -6
  55. package/dist/ImageViewer.js +75 -266
  56. package/dist/ImageViewer.mjs +8 -8
  57. package/dist/Input.d.mts +1 -1
  58. package/dist/Input.d.ts +1 -1
  59. package/dist/Input.js +35 -162
  60. package/dist/Input.mjs +4 -4
  61. package/dist/LabelValue.js +24 -149
  62. package/dist/LabelValue.mjs +3 -3
  63. package/dist/ListGroup.js +1 -114
  64. package/dist/ListGroup.mjs +2 -2
  65. package/dist/ListItem.js +38 -235
  66. package/dist/ListItem.mjs +5 -6
  67. package/dist/MediaCard.d.mts +0 -14
  68. package/dist/MediaCard.d.ts +0 -14
  69. package/dist/MediaCard.js +69 -315
  70. package/dist/MediaCard.mjs +5 -6
  71. package/dist/MenuGroup.js +1 -114
  72. package/dist/MenuGroup.mjs +2 -2
  73. package/dist/MenuItem.js +36 -234
  74. package/dist/MenuItem.mjs +5 -6
  75. package/dist/MonthPicker.js +8 -163
  76. package/dist/MonthPicker.mjs +3 -3
  77. package/dist/NumberStepper.js +40 -238
  78. package/dist/NumberStepper.mjs +5 -6
  79. package/dist/PagerDots.d.mts +1 -1
  80. package/dist/PagerDots.d.ts +1 -1
  81. package/dist/PagerDots.js +69 -224
  82. package/dist/PagerDots.mjs +6 -5
  83. package/dist/Pressable.js +14 -85
  84. package/dist/Pressable.mjs +4 -4
  85. package/dist/PricingCard.js +87 -272
  86. package/dist/PricingCard.mjs +8 -8
  87. package/dist/Progress.js +3 -123
  88. package/dist/Progress.mjs +3 -3
  89. package/dist/RadioGroup.js +52 -265
  90. package/dist/RadioGroup.mjs +5 -5
  91. package/dist/RetrayProvider.js +3 -6
  92. package/dist/RetrayProvider.mjs +3 -3
  93. package/dist/Select.d.mts +2 -1
  94. package/dist/Select.d.ts +2 -1
  95. package/dist/Select.js +24 -232
  96. package/dist/Select.mjs +4 -5
  97. package/dist/SelectableCard.js +33 -209
  98. package/dist/SelectableCard.mjs +5 -5
  99. package/dist/SelectableGrid.d.mts +0 -21
  100. package/dist/SelectableGrid.d.ts +0 -21
  101. package/dist/SelectableGrid.js +49 -271
  102. package/dist/SelectableGrid.mjs +5 -6
  103. package/dist/Separator.js +1 -114
  104. package/dist/Separator.mjs +2 -2
  105. package/dist/Sheet.js +7 -162
  106. package/dist/Sheet.mjs +3 -3
  107. package/dist/SheetSelect.js +39 -236
  108. package/dist/SheetSelect.mjs +6 -6
  109. package/dist/Skeleton.js +4 -124
  110. package/dist/Skeleton.mjs +3 -3
  111. package/dist/Slider.js +6 -161
  112. package/dist/Slider.mjs +3 -3
  113. package/dist/Spinner.js +3 -116
  114. package/dist/Spinner.mjs +2 -2
  115. package/dist/Stats.js +36 -234
  116. package/dist/Stats.mjs +5 -6
  117. package/dist/Switch.js +24 -175
  118. package/dist/Switch.mjs +5 -4
  119. package/dist/TabBar.js +43 -200
  120. package/dist/TabBar.mjs +5 -4
  121. package/dist/Tabs.js +15 -199
  122. package/dist/Tabs.mjs +5 -5
  123. package/dist/Text.js +9 -130
  124. package/dist/Text.mjs +2 -2
  125. package/dist/Textarea.d.mts +2 -1
  126. package/dist/Textarea.d.ts +2 -1
  127. package/dist/Textarea.js +71 -219
  128. package/dist/Textarea.mjs +4 -4
  129. package/dist/Toast.js +1 -114
  130. package/dist/Toast.mjs +2 -2
  131. package/dist/Toggle.js +39 -236
  132. package/dist/Toggle.mjs +6 -6
  133. package/dist/{chunk-M3C7XM2M.mjs → chunk-2QOHHBJC.mjs} +3 -3
  134. package/dist/{chunk-LIS6I5UP.mjs → chunk-2VIDP72N.mjs} +3 -3
  135. package/dist/{chunk-DF7JA72E.mjs → chunk-4NQFTHN3.mjs} +13 -7
  136. package/dist/{chunk-UBUXUMER.mjs → chunk-4ZO5PTKF.mjs} +4 -4
  137. package/dist/{chunk-3XCFYSX4.mjs → chunk-5MYNAAFE.mjs} +13 -17
  138. package/dist/{chunk-E7NEHHXV.mjs → chunk-62BBSSUF.mjs} +3 -3
  139. package/dist/{chunk-MVMGPZN6.mjs → chunk-6CR4S6W2.mjs} +3 -3
  140. package/dist/{chunk-EDLCGYIO.mjs → chunk-6QLBHUEG.mjs} +8 -7
  141. package/dist/chunk-ARONDO7M.mjs +40 -0
  142. package/dist/{chunk-GH67YXG6.mjs → chunk-AZV7KNJI.mjs} +3 -3
  143. package/dist/{chunk-RMRS44MQ.mjs → chunk-BTUW5LSG.mjs} +11 -8
  144. package/dist/{chunk-2P2CB235.mjs → chunk-BULKGOIZ.mjs} +7 -8
  145. package/dist/{chunk-NHDI3VQB.mjs → chunk-CBIZLRYH.mjs} +15 -12
  146. package/dist/chunk-CM2DG4MR.mjs +142 -0
  147. package/dist/{chunk-TS7DGUIR.mjs → chunk-DBHSUUKU.mjs} +2 -2
  148. package/dist/{chunk-57V2LXCK.mjs → chunk-DE25XTVQ.mjs} +3 -3
  149. package/dist/{chunk-UQ4742ET.mjs → chunk-E4EQSCKR.mjs} +5 -5
  150. package/dist/{chunk-GUTDFUNF.mjs → chunk-EHGBHFMH.mjs} +9 -17
  151. package/dist/{chunk-CF27NBXO.mjs → chunk-EROPDCB5.mjs} +16 -24
  152. package/dist/{chunk-ZIMY2QUM.mjs → chunk-ERWJPVX7.mjs} +2 -2
  153. package/dist/{chunk-NLZY4TXU.mjs → chunk-ESQDPO5E.mjs} +7 -7
  154. package/dist/{chunk-VJBUCITV.mjs → chunk-EW2FIDSM.mjs} +1 -1
  155. package/dist/{chunk-HC4VVCWY.mjs → chunk-FTTI6T5Q.mjs} +4 -4
  156. package/dist/{chunk-2HFD4IHU.mjs → chunk-HUSSF6TF.mjs} +1 -1
  157. package/dist/chunk-IFYMBOEN.mjs +14 -0
  158. package/dist/{chunk-QOLWA2PW.mjs → chunk-IGU223UM.mjs} +80 -4
  159. package/dist/chunk-IJCMPVW5.mjs +121 -0
  160. package/dist/{chunk-AENAVIKT.mjs → chunk-ITG4JQM3.mjs} +4 -4
  161. package/dist/{chunk-E5UKLSJZ.mjs → chunk-K3QX2M26.mjs} +11 -8
  162. package/dist/{chunk-4OORJ2DY.mjs → chunk-K7TKID3V.mjs} +8 -7
  163. package/dist/{chunk-2LG326TT.mjs → chunk-KAGADD2O.mjs} +4 -4
  164. package/dist/{chunk-IVSRW4HS.mjs → chunk-KC5QDYGZ.mjs} +4 -4
  165. package/dist/{chunk-7AFZWSCI.mjs → chunk-KPTY7UYQ.mjs} +1 -1
  166. package/dist/{chunk-YTXRIXNZ.mjs → chunk-KSSVIFYR.mjs} +9 -12
  167. package/dist/chunk-L3YKPTJQ.mjs +119 -0
  168. package/dist/chunk-M53LC4Q7.mjs +35 -0
  169. package/dist/{chunk-ZR6HSEAB.mjs → chunk-MP7GLMIR.mjs} +17 -25
  170. package/dist/chunk-MZ6WRTD2.mjs +40 -0
  171. package/dist/chunk-NGEN2EES.mjs +581 -0
  172. package/dist/{chunk-C43HRKXH.mjs → chunk-OBV72JD4.mjs} +1 -1
  173. package/dist/{chunk-LPV4NJJK.mjs → chunk-PGQ6FMXS.mjs} +6 -5
  174. package/dist/{chunk-MEPSKGBO.mjs → chunk-PI6RULJX.mjs} +1 -1
  175. package/dist/{chunk-F3YTWO3T.mjs → chunk-RA6SAAFE.mjs} +9 -8
  176. package/dist/{chunk-UNNRUJTM.mjs → chunk-RRKM4MKB.mjs} +7 -7
  177. package/dist/{chunk-ULGNQPNE.mjs → chunk-S2VGME7X.mjs} +1 -1
  178. package/dist/{chunk-OLVJFKXS.mjs → chunk-S44XWTTC.mjs} +35 -25
  179. package/dist/{chunk-YMYIEVZP.mjs → chunk-SZEKQAOY.mjs} +1 -1
  180. package/dist/{chunk-ELGEOM7I.mjs → chunk-TETMEKZE.mjs} +9 -9
  181. package/dist/{chunk-BXF4AMHY.mjs → chunk-TMH263OK.mjs} +5 -4
  182. package/dist/{chunk-NJG7DHVF.mjs → chunk-U6DEBYU5.mjs} +10 -9
  183. package/dist/{chunk-RJNLAH76.mjs → chunk-UOKFSFNJ.mjs} +2 -2
  184. package/dist/{chunk-HEDQPK4I.mjs → chunk-URIH43IJ.mjs} +13 -21
  185. package/dist/{chunk-QXDGGOLC.mjs → chunk-V2ZB2XNS.mjs} +6 -6
  186. package/dist/{chunk-KSUWPU2F.mjs → chunk-WIPEDNSD.mjs} +7 -7
  187. package/dist/{chunk-QDAZGZUF.mjs → chunk-XCIG6HT2.mjs} +3 -3
  188. package/dist/{chunk-4J2PXL36.mjs → chunk-Y6YS33GM.mjs} +40 -38
  189. package/dist/{chunk-4XOB5TTD.mjs → chunk-ZKDKKQCE.mjs} +5 -5
  190. package/dist/{chunk-LOBLCFMN.mjs → chunk-ZTPYUU5C.mjs} +5 -5
  191. package/dist/index.d.mts +12 -72
  192. package/dist/index.d.ts +12 -72
  193. package/dist/index.js +1051 -1838
  194. package/dist/index.mjs +81 -85
  195. package/package.json +8 -10
  196. package/src/components/Accordion/Accordion.tsx +12 -9
  197. package/src/components/AlertBanner/AlertBanner.tsx +7 -6
  198. package/src/components/AppHeader/AppHeader.tsx +1 -1
  199. package/src/components/Avatar/Avatar.tsx +92 -1
  200. package/src/components/Avatar/index.ts +2 -2
  201. package/src/components/Badge/Badge.tsx +2 -2
  202. package/src/components/Button/Button.tsx +50 -46
  203. package/src/components/Card/Card.tsx +1 -0
  204. package/src/components/CategoryStrip/CategoryStrip.tsx +36 -49
  205. package/src/components/Chip/Chip.tsx +5 -4
  206. package/src/components/ConfirmDialog/ConfirmDialog.tsx +3 -3
  207. package/src/components/DetailRow/DetailRow.tsx +3 -3
  208. package/src/components/EmptyState/EmptyState.tsx +2 -2
  209. package/src/components/ErrorBoundary/ErrorBoundary.tsx +6 -6
  210. package/src/components/HolographicCard/HolographicCard.tsx +14 -95
  211. package/src/components/IconButton/IconButton.tsx +2 -2
  212. package/src/components/IconPicker/IconPicker.tsx +13 -12
  213. package/src/components/ImageUpload/ImageUpload.tsx +14 -25
  214. package/src/components/ImageViewer/ImageViewer.tsx +3 -3
  215. package/src/components/Input/Input.tsx +11 -5
  216. package/src/components/LabelValue/LabelValue.tsx +2 -2
  217. package/src/components/ListItem/ListItem.tsx +4 -4
  218. package/src/components/MediaCard/MediaCard.tsx +21 -59
  219. package/src/components/MenuItem/MenuItem.tsx +2 -2
  220. package/src/components/MonthPicker/MonthPicker.tsx +2 -2
  221. package/src/components/NumberStepper/NumberStepper.tsx +6 -6
  222. package/src/components/PagerDots/PagerDots.tsx +38 -28
  223. package/src/components/PricingCard/PricingCard.tsx +6 -6
  224. package/src/components/RadioGroup/RadioGroup.tsx +18 -31
  225. package/src/components/Select/Select.tsx +32 -39
  226. package/src/components/SelectableCard/SelectableCard.tsx +4 -6
  227. package/src/components/SelectableGrid/SelectableGrid.tsx +38 -72
  228. package/src/components/Sheet/Sheet.tsx +1 -1
  229. package/src/components/SheetSelect/SheetSelect.tsx +3 -3
  230. package/src/components/Skeleton/Skeleton.tsx +1 -1
  231. package/src/components/Spinner/Spinner.tsx +2 -2
  232. package/src/components/Stats/Stats.tsx +2 -2
  233. package/src/components/Switch/Switch.tsx +9 -6
  234. package/src/components/TabBar/TabBar.tsx +9 -8
  235. package/src/components/Text/Text.tsx +12 -1
  236. package/src/components/Textarea/Textarea.tsx +18 -32
  237. package/src/components/Toggle/Toggle.tsx +3 -3
  238. package/src/hooks/useConfirmDialog.ts +31 -42
  239. package/src/index.ts +3 -4
  240. package/src/theme/ThemeProvider.tsx +1 -4
  241. package/src/theme/colorUtils.ts +1 -72
  242. package/src/theme/colors.ts +40 -1
  243. package/src/theme/types.ts +2 -2
  244. package/src/utils/animations.ts +0 -47
  245. package/src/utils/curatedIcons.ts +93 -801
  246. package/src/utils/haptics.ts +13 -208
  247. package/src/utils/icons.ts +27 -91
  248. package/src/utils/pressable.ts +10 -61
  249. package/dist/VirtualList.d.mts +0 -19
  250. package/dist/VirtualList.d.ts +0 -19
  251. package/dist/VirtualList.js +0 -38
  252. package/dist/VirtualList.mjs +0 -2
  253. package/dist/chunk-2BA3JMKK.mjs +0 -136
  254. package/dist/chunk-3DKJ2GIC.mjs +0 -30
  255. package/dist/chunk-7ELGZ66G.mjs +0 -164
  256. package/dist/chunk-DVK4G2GT.mjs +0 -59
  257. package/dist/chunk-EJ7ZPXOH.mjs +0 -163
  258. package/dist/chunk-KA7LTET3.mjs +0 -71
  259. package/dist/chunk-LNPKGWBG.mjs +0 -134
  260. package/dist/chunk-NC5ZTR2Y.mjs +0 -32
  261. package/dist/chunk-SAWUXP3A.mjs +0 -1114
  262. package/dist/chunk-YNROWHQJ.mjs +0 -46
  263. package/src/components/VirtualList/VirtualList.tsx +0 -60
  264. package/src/components/VirtualList/index.ts +0 -1
  265. package/src/utils/fontGuard.ts +0 -35
  266. package/src/utils/hover.ts +0 -25
  267. package/src/utils/useColorTransition.ts +0 -40
  268. 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),