@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
@@ -7,12 +7,14 @@ import {
7
7
  ViewStyle,
8
8
  TextStyle,
9
9
  } from 'react-native'
10
+ import { EaseView } from 'react-native-ease'
10
11
  import { impactMedium } from '../../utils/haptics'
11
12
  import { useTheme } from '../../theme'
12
13
  import { s, vs, ms, mvs } from '../../utils/scaling'
13
- import { renderIcon } from '../../utils/icons'
14
+ import { Icon } from '../../utils/icons'
14
15
  import { RADIUS, TYPOGRAPHY } from '../../tokens'
15
16
  import { PressableButton } from '../../utils/pressable'
17
+ import { COLOR_TRANSITION } from '../../utils/animations'
16
18
 
17
19
  export type ButtonVariant = 'primary' | 'secondary' | 'text' | 'destructive'
18
20
  export type ButtonSize = 'sm' | 'md' | 'lg'
@@ -73,31 +75,35 @@ function ButtonBase({
73
75
  onPress?.()
74
76
  }
75
77
 
76
- const containerVariantStyle: ViewStyle = {
77
- primary: { backgroundColor: colors.primary },
78
- secondary: { backgroundColor: 'transparent', borderWidth: 1.5, borderColor: colors.primary },
79
- text: { backgroundColor: 'transparent' },
80
- destructive: { backgroundColor: colors.destructive },
81
- }[variant]
82
-
83
- const labelVariantStyle: TextStyle = {
84
- primary: { color: colors.primaryForeground },
85
- secondary: { color: colors.primary },
86
- // AUDIT FIX: was colors.foreground — visually indistinguishable from plain text,
87
- // no affordance that it's a CTA. Now uses accentResolved so text-only buttons
88
- // carry the brand voltage. Falls back to primary when no accent is defined.
89
- text: { color: colors.accentResolved },
90
- destructive: { color: colors.destructiveForeground },
91
- }[variant]
92
-
93
- const textColor = iconColor ?? (labelVariantStyle.color as string)
78
+ const isSecondary = variant === 'secondary'
79
+ const borderWidth = isSecondary ? 1.5 : 0
80
+
81
+ const animateBgColor: string = isDisabled
82
+ ? { primary: colors.surface, secondary: 'transparent', text: 'transparent', destructive: colors.surface }[variant]
83
+ : { primary: colors.primary, secondary: 'transparent', text: 'transparent', destructive: colors.destructive }[variant]
84
+
85
+ const animateBorderColor: string = isDisabled
86
+ ? (isSecondary ? colors.border : 'transparent')
87
+ : (isSecondary ? colors.primary : 'transparent')
88
+
89
+ const labelColor = isDisabled
90
+ ? colors.foregroundMuted
91
+ : {
92
+ primary: colors.primaryForeground,
93
+ secondary: colors.primary,
94
+ text: colors.accentResolved,
95
+ destructive: colors.destructiveForeground,
96
+ }[variant]
97
+
98
+ const textColor = iconColor ?? labelColor
94
99
 
95
100
  const effectiveIcon: React.ReactNode = iconName
96
- ? renderIcon(iconName, iconSizeMap[size], textColor)
101
+ ? <Icon name={iconName} size={iconSizeMap[size]} color={textColor} />
97
102
  : typeof icon === 'function' ? icon({ label, size, variant, color: textColor }) : icon
98
103
 
99
- const spinnerColor =
100
- variant === 'destructive' ? colors.destructiveForeground
104
+ const spinnerColor = isDisabled
105
+ ? colors.foregroundMuted
106
+ : variant === 'destructive' ? colors.destructiveForeground
101
107
  : variant === 'primary' ? colors.primaryForeground
102
108
  : colors.accentResolved
103
109
 
@@ -108,14 +114,6 @@ function ButtonBase({
108
114
  return (
109
115
  <View style={[fullWidth && styles.fullWidth, flex !== undefined && { flex }]}>
110
116
  <PressableButton
111
- style={[
112
- styles.base,
113
- containerVariantStyle,
114
- containerSizeStyles[size],
115
- fullWidth && styles.fullWidth,
116
- isDisabled && styles.disabled,
117
- restStyle,
118
- ]}
119
117
  enabled={!isDisabled}
120
118
  onPress={handlePress}
121
119
  rippleColor="transparent"
@@ -126,30 +124,42 @@ function ButtonBase({
126
124
  accessibilityHint={accessibilityHint}
127
125
  accessibilityState={{ disabled: isDisabled, busy: loading }}
128
126
  >
129
- {loading ? (
130
- <>
131
- <ActivityIndicator size="small" color={spinnerColor} style={{ marginRight: s(6) }} />
132
- <Text
133
- style={[styles.label, labelVariantStyle, labelSizeStyles[size], styles.labelLoading]}
134
- allowFontScaling={true}
135
- numberOfLines={1}
136
- >
137
- {label}
138
- </Text>
139
- </>
140
- ) : (
141
- <>
142
- {effectiveIcon && iconPosition === 'left' && <>{effectiveIcon}</>}
143
- <Text
144
- style={[styles.label, labelVariantStyle, labelSizeStyles[size], effectiveIcon ? styles.labelWithIcon : undefined]}
145
- allowFontScaling={true}
146
- numberOfLines={1}
147
- >
148
- {label}
149
- </Text>
150
- {effectiveIcon && iconPosition === 'right' && <>{effectiveIcon}</>}
151
- </>
152
- )}
127
+ <EaseView
128
+ style={[
129
+ styles.base,
130
+ containerSizeStyles[size],
131
+ { borderWidth },
132
+ fullWidth && styles.fullWidth,
133
+ restStyle,
134
+ ]}
135
+ animate={{ backgroundColor: animateBgColor, borderColor: animateBorderColor }}
136
+ transition={COLOR_TRANSITION}
137
+ >
138
+ {loading ? (
139
+ <>
140
+ <ActivityIndicator size="small" color={spinnerColor} style={{ marginRight: s(6) }} />
141
+ <Text
142
+ style={[styles.label, { color: labelColor }, labelSizeStyles[size], styles.labelLoading]}
143
+ allowFontScaling={true}
144
+ numberOfLines={1}
145
+ >
146
+ {label}
147
+ </Text>
148
+ </>
149
+ ) : (
150
+ <>
151
+ {effectiveIcon && iconPosition === 'left' && <>{effectiveIcon}</>}
152
+ <Text
153
+ style={[styles.label, { color: labelColor }, labelSizeStyles[size], effectiveIcon ? styles.labelWithIcon : undefined]}
154
+ allowFontScaling={true}
155
+ numberOfLines={1}
156
+ >
157
+ {label}
158
+ </Text>
159
+ {effectiveIcon && iconPosition === 'right' && <>{effectiveIcon}</>}
160
+ </>
161
+ )}
162
+ </EaseView>
153
163
  </PressableButton>
154
164
  </View>
155
165
  )
@@ -167,9 +177,6 @@ const styles = StyleSheet.create({
167
177
  fullWidth: {
168
178
  width: '100%',
169
179
  },
170
- disabled: {
171
- opacity: 0.45,
172
- },
173
180
  label: {
174
181
  fontFamily: 'Sohne-Medium',
175
182
  flexShrink: 1,
@@ -73,6 +73,7 @@ export function Card({ children, variant = 'elevated', onPress, style, accessibi
73
73
  activateOnHover
74
74
  accessibilityRole="button"
75
75
  accessibilityLabel={accessibilityLabel}
76
+ accessibilityState={{ disabled: false }}
76
77
  >
77
78
  {cardContent}
78
79
  </PressableCard>
@@ -1,40 +1,33 @@
1
1
  import React, { useCallback } from 'react'
2
2
  import {
3
3
  ScrollView,
4
- TouchableOpacity,
5
4
  Text,
6
5
  View,
7
6
  StyleSheet,
8
7
  ViewStyle,
9
8
  } from 'react-native'
10
- import Animated from 'react-native-reanimated'
11
9
  import { EaseView } from 'react-native-ease'
12
10
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
13
11
  import { useTheme } from '../../theme'
14
12
  import { s, vs, ms } from '../../utils/scaling'
15
- import { renderIcon } from '../../utils/icons'
16
- import { usePressScale } from '../../utils/usePressScale'
17
- import { COLOR_TRANSITION, PRESS_SCALE } from '../../utils/animations'
13
+ import { Icon } from '../../utils/icons'
14
+ import { PressableChip } from '../../utils/pressable'
15
+ import { COLOR_TRANSITION } from '../../utils/animations'
18
16
  import { RADIUS } from '../../tokens'
19
17
 
20
18
  export interface CategoryItem {
21
19
  label: string
22
20
  value: string
23
- /** Icon rendered to the left of the label. ReactNode or icon name string. */
24
21
  icon?: React.ReactNode | string
25
- /** Badge count over the icon/label. */
26
22
  badge?: number
27
23
  }
28
24
 
29
25
  export interface CategoryStripProps {
30
26
  categories: CategoryItem[]
31
27
  value?: string | string[]
32
- /** Called with new value(s) on selection change. */
33
28
  onValueChange?: (value: string | string[]) => void
34
- /** Allow multiple simultaneous selections. Defaults to false. */
35
29
  multiSelect?: boolean
36
30
  style?: ViewStyle
37
- /** Style applied to each pill item. */
38
31
  itemStyle?: ViewStyle
39
32
  accessibilityLabel?: string
40
33
  }
@@ -49,53 +42,47 @@ const CategoryChip = React.memo(function CategoryChip({
49
42
  onSelect: (value: string) => void
50
43
  }) {
51
44
  const { colors } = useTheme()
52
- const { animatedStyle: scaleStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
53
- pressScale: PRESS_SCALE.chip,
54
- })
55
- // Static color for icon — icon families take a static color prop, not animated.
56
45
  const iconColor = selected ? colors.primaryForeground : colors.foregroundSubtle
57
46
  const resolvedIcon =
58
47
  typeof item.icon === 'string'
59
- ? renderIcon(item.icon, 16, iconColor)
48
+ ? <Icon name={item.icon} size={16} color={iconColor} />
60
49
  : item.icon ?? null
61
50
 
62
51
  return (
63
- <Animated.View style={scaleStyle} {...hoverHandlers}>
64
- <TouchableOpacity
65
- onPress={() => onSelect(item.value)}
66
- onPressIn={onPressIn}
67
- onPressOut={onPressOut}
68
- activeOpacity={1}
69
- touchSoundDisabled={true}
70
- accessibilityRole="button"
71
- accessibilityLabel={item.label}
72
- accessibilityState={{ selected }}
52
+ <PressableChip
53
+ onPress={() => onSelect(item.value)}
54
+ enabled
55
+ rippleColor="transparent"
56
+ touchSoundDisabled
57
+ activateOnHover
58
+ accessibilityRole="button"
59
+ accessibilityLabel={item.label}
60
+ accessibilityState={{ selected }}
61
+ >
62
+ <EaseView
63
+ style={styles.chip}
64
+ animate={{
65
+ backgroundColor: selected ? colors.primary : colors.surface,
66
+ borderColor: selected ? colors.primary : colors.border,
67
+ }}
68
+ transition={COLOR_TRANSITION}
73
69
  >
74
- <EaseView
75
- style={styles.chip}
76
- animate={{
77
- backgroundColor: selected ? colors.primary : colors.surface,
78
- borderColor: selected ? colors.primary : colors.border,
79
- }}
80
- transition={COLOR_TRANSITION}
70
+ {resolvedIcon && <View style={styles.chipIcon}>{resolvedIcon}</View>}
71
+ <Text
72
+ style={[styles.chipLabel, { color: selected ? colors.primaryForeground : colors.foregroundSubtle }]}
73
+ allowFontScaling={true}
81
74
  >
82
- {resolvedIcon && <View style={styles.chipIcon}>{resolvedIcon}</View>}
83
- <Text
84
- style={[styles.chipLabel, { color: selected ? colors.primaryForeground : colors.foregroundSubtle }]}
85
- allowFontScaling={true}
86
- >
87
- {item.label}
88
- </Text>
89
- {item.badge !== undefined && item.badge > 0 && (
90
- <View style={[styles.chipBadge, { backgroundColor: colors.primary }]}>
91
- <Text style={[styles.chipBadgeText, { color: colors.primaryForeground }]}>
92
- {Math.min(item.badge, 99)}
93
- </Text>
94
- </View>
95
- )}
96
- </EaseView>
97
- </TouchableOpacity>
98
- </Animated.View>
75
+ {item.label}
76
+ </Text>
77
+ {item.badge !== undefined && item.badge > 0 && (
78
+ <View style={[styles.chipBadge, { backgroundColor: colors.primary }]}>
79
+ <Text style={[styles.chipBadgeText, { color: colors.primaryForeground }]}>
80
+ {Math.min(item.badge, 99)}
81
+ </Text>
82
+ </View>
83
+ )}
84
+ </EaseView>
85
+ </PressableChip>
99
86
  )
100
87
  })
101
88
 
@@ -4,8 +4,9 @@ import { EaseView } from 'react-native-ease'
4
4
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
5
5
  import { useTheme } from '../../theme'
6
6
  import { s, vs, ms, mvs } from '../../utils/scaling'
7
- import { renderIcon } from '../../utils/icons'
7
+ import { Icon } from '../../utils/icons'
8
8
  import { COLOR_TRANSITION } from '../../utils/animations'
9
+ import { RADIUS } from '../../tokens'
9
10
  import { PressableChip as PressableChipComponent } from '../../utils/pressable'
10
11
 
11
12
  export interface ChipProps {
@@ -43,7 +44,7 @@ function ChipBase({ label, selected = false, onPress, icon, iconName, style, acc
43
44
  }
44
45
 
45
46
  const resolvedIcon = iconName
46
- ? renderIcon(iconName, ms(13), selected ? colors.primaryForeground : colors.foreground)
47
+ ? <Icon name={iconName} size={ms(13)} color={selected ? colors.primaryForeground : colors.foreground} />
47
48
  : icon
48
49
 
49
50
  return (
@@ -111,7 +112,7 @@ export function ChipGroup({ options, value, onValueChange, multiSelect = false,
111
112
  // Now passes disabled state to the Chip's TouchableOpacity via onPress=undefined
112
113
  // and adds explicit accessibility state for screen readers.
113
114
  style={opt.disabled ? styles.chipDisabled : undefined}
114
- accessibilityLabel={opt.disabled ? `${opt.label}, unavailable` : opt.label}
115
+ accessibilityLabel={opt.disabled ? `${opt.label}, no disponible` : opt.label}
115
116
  />
116
117
  ))}
117
118
  </View>
@@ -121,7 +122,7 @@ export function ChipGroup({ options, value, onValueChange, multiSelect = false,
121
122
  const styles = StyleSheet.create({
122
123
  wrapper: {},
123
124
  chip: {
124
- borderRadius: 999,
125
+ borderRadius: RADIUS.full,
125
126
  paddingHorizontal: s(14),
126
127
  // AUDIT FIX: was vs(5) → ~28px total height — below WCAG 44px tap target.
127
128
  // vs(10) → ~44px total height meets WCAG 2.5.5 (AAA) minimum.
@@ -30,8 +30,8 @@ export function ConfirmDialog({
30
30
  visible,
31
31
  title,
32
32
  subtitle,
33
- confirmLabel = 'Confirm',
34
- cancelLabel = 'Cancel',
33
+ confirmLabel = 'Confirmar',
34
+ cancelLabel = 'Cancelar',
35
35
  confirmVariant = 'primary',
36
36
  loading = false,
37
37
  showCloseButton = false,
@@ -42,14 +42,21 @@ export function ConfirmDialog({
42
42
  const insets = useSafeAreaInsets()
43
43
  const ref = useRef<BottomSheetModal>(null)
44
44
  const wasOpened = useRef(false)
45
+ const isPresentedRef = useRef(false)
45
46
  const name = useId()
46
47
 
48
+ const handleDismiss = useCallback(() => {
49
+ isPresentedRef.current = false
50
+ onCancel()
51
+ }, [onCancel])
52
+
47
53
  useEffect(() => {
48
- if (visible) {
54
+ if (visible && !isPresentedRef.current) {
49
55
  impactMedium()
50
56
  ref.current?.present()
51
57
  wasOpened.current = true
52
- } else if (wasOpened.current) {
58
+ isPresentedRef.current = true
59
+ } else if (!visible && wasOpened.current && isPresentedRef.current) {
53
60
  ref.current?.dismiss()
54
61
  }
55
62
  }, [visible])
@@ -70,7 +77,7 @@ export function ConfirmDialog({
70
77
  <BottomSheetModal
71
78
  ref={ref}
72
79
  name={name}
73
- onDismiss={onCancel}
80
+ onDismiss={handleDismiss}
74
81
  enableDynamicSizing
75
82
  backdropComponent={renderBackdrop}
76
83
  backgroundStyle={{ ...styles.background, backgroundColor: colors.card }}
@@ -91,7 +98,7 @@ export function ConfirmDialog({
91
98
  activeOpacity={0.6}
92
99
  touchSoundDisabled={true}
93
100
  accessibilityRole="button"
94
- accessibilityLabel="Close"
101
+ accessibilityLabel="Cerrar"
95
102
  hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
96
103
  >
97
104
  <Feather name="x" size={ms(18)} color={colors.foregroundMuted} />
@@ -2,7 +2,7 @@ import React from 'react'
2
2
  import { View, Text, StyleSheet, ViewStyle, TextStyle } from 'react-native'
3
3
  import { useTheme } from '../../theme'
4
4
  import { s, ms, mvs } from '../../utils/scaling'
5
- import { renderIcon } from '../../utils/icons'
5
+ import { Icon } from '../../utils/icons'
6
6
 
7
7
  export type DetailRowSeparator = 'dotted' | 'solid' | 'dashed' | 'none'
8
8
  export type DetailRowLabelWeight = 'normal' | 'medium' | 'semibold' | 'bold'
@@ -56,11 +56,11 @@ function DetailRowBase({
56
56
  const { colors } = useTheme()
57
57
 
58
58
  const resolvedLeftIcon = leftIconName
59
- ? renderIcon(leftIconName, ms(14), leftIconColor ?? colors.foregroundMuted)
59
+ ? <Icon name={leftIconName} size={ms(14)} color={leftIconColor ?? colors.foregroundMuted} />
60
60
  : leftIcon
61
61
 
62
62
  const resolvedRightIcon = rightIconName
63
- ? renderIcon(rightIconName, ms(14), rightIconColor ?? colors.foregroundMuted)
63
+ ? <Icon name={rightIconName} size={ms(14)} color={rightIconColor ?? colors.foregroundMuted} />
64
64
  : null
65
65
 
66
66
  const separatorStyle: ViewStyle | null =
@@ -2,7 +2,7 @@ import React from 'react'
2
2
  import { View, Text, StyleSheet, ViewStyle } from 'react-native'
3
3
  import { useTheme } from '../../theme'
4
4
  import { s, vs, ms, mvs } from '../../utils/scaling'
5
- import { renderIcon } from '../../utils/icons'
5
+ import { Icon } from '../../utils/icons'
6
6
  import { Button } from '../Button'
7
7
 
8
8
  export interface EmptyStateProps {
@@ -32,7 +32,7 @@ export function EmptyState({ icon, iconName, iconColor, title, description, acti
32
32
  const isCompact = size === 'compact'
33
33
 
34
34
  const effectiveIcon: React.ReactNode = iconName
35
- ? renderIcon(iconName, isCompact ? 32 : 48, iconColor ?? colors.foregroundMuted)
35
+ ? <Icon name={iconName} size={isCompact ? 32 : 48} color={iconColor ?? colors.foregroundMuted} />
36
36
  : icon
37
37
 
38
38
  return (
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
3
3
  import { useTheme } from '../../theme'
4
- import { renderIcon } from '../../utils/icons'
4
+ import { Icon } from '../../utils/icons'
5
5
  import { s, vs, ms, mvs } from '../../utils/scaling'
6
6
  import { RADIUS } from '../../tokens'
7
7
  import { impactLight } from '../../utils/haptics'
@@ -35,20 +35,20 @@ interface ErrorBoundaryState {
35
35
  function DefaultErrorFallback({
36
36
  error,
37
37
  reset,
38
- title = 'Something went wrong',
38
+ title = 'Algo salió mal',
39
39
  message,
40
40
  }: ErrorFallbackProps & { title?: string; message?: string }) {
41
41
  const { colors } = useTheme()
42
42
  return (
43
43
  <View style={[styles.container, { backgroundColor: colors.background }]} accessibilityRole="alert">
44
44
  <View style={[styles.iconCircle, { backgroundColor: colors.destructiveTint }]}>
45
- {renderIcon('alert-triangle', ms(28), colors.destructive)}
45
+ <Icon name="alert-triangle" size={ms(28)} color={colors.destructive} />
46
46
  </View>
47
47
  <Text style={[styles.title, { color: colors.foreground }]} allowFontScaling={true}>
48
48
  {title}
49
49
  </Text>
50
50
  <Text style={[styles.message, { color: colors.foregroundMuted }]} allowFontScaling={true}>
51
- {message ?? error.message ?? 'An unexpected error occurred.'}
51
+ {message ?? error.message ?? 'Ocurrió un error inesperado.'}
52
52
  </Text>
53
53
  <TouchableOpacity
54
54
  style={[styles.button, { backgroundColor: colors.primary }]}
@@ -59,10 +59,10 @@ function DefaultErrorFallback({
59
59
  activeOpacity={0.85}
60
60
  touchSoundDisabled={true}
61
61
  accessibilityRole="button"
62
- accessibilityLabel="Try again"
62
+ accessibilityLabel="Intentar de nuevo"
63
63
  >
64
64
  <Text style={[styles.buttonLabel, { color: colors.primaryForeground }]} allowFontScaling={true}>
65
- Try again
65
+ Intentar de nuevo
66
66
  </Text>
67
67
  </TouchableOpacity>
68
68
  </View>