@retray-dev/ui-kit 12.2.0 → 13.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/CONSUMER.md +26 -11
  2. package/DESIGN.md +2 -2
  3. package/README.md +15 -11
  4. package/{COMPONENTS.md → SKILL.md} +374 -996
  5. package/dist/Accordion.d.mts +2 -0
  6. package/dist/Accordion.d.ts +2 -0
  7. package/dist/Accordion.js +49 -210
  8. package/dist/Accordion.mjs +6 -6
  9. package/dist/AlertBanner.js +29 -153
  10. package/dist/AlertBanner.mjs +3 -4
  11. package/dist/AppHeader.d.mts +5 -2
  12. package/dist/AppHeader.d.ts +5 -2
  13. package/dist/AppHeader.js +45 -239
  14. package/dist/AppHeader.mjs +6 -8
  15. package/dist/Avatar.d.mts +17 -1
  16. package/dist/Avatar.d.ts +17 -1
  17. package/dist/Avatar.js +80 -115
  18. package/dist/Avatar.mjs +2 -3
  19. package/dist/Badge.js +24 -149
  20. package/dist/Badge.mjs +3 -4
  21. package/dist/Button.js +79 -267
  22. package/dist/Button.mjs +6 -7
  23. package/dist/ButtonGroup.mjs +0 -1
  24. package/dist/Card.js +15 -200
  25. package/dist/Card.mjs +4 -6
  26. package/dist/CategoryStrip.d.mts +0 -5
  27. package/dist/CategoryStrip.d.ts +0 -5
  28. package/dist/CategoryStrip.js +47 -265
  29. package/dist/CategoryStrip.mjs +6 -7
  30. package/dist/Checkbox.d.mts +2 -1
  31. package/dist/Checkbox.d.ts +2 -1
  32. package/dist/Checkbox.js +18 -201
  33. package/dist/Checkbox.mjs +5 -6
  34. package/dist/Chip.js +44 -236
  35. package/dist/Chip.mjs +7 -7
  36. package/dist/ConfirmDialog.d.mts +2 -1
  37. package/dist/ConfirmDialog.d.ts +2 -1
  38. package/dist/ConfirmDialog.js +110 -300
  39. package/dist/ConfirmDialog.mjs +7 -8
  40. package/dist/CurrencyDisplay.js +1 -114
  41. package/dist/CurrencyDisplay.mjs +2 -3
  42. package/dist/CurrencyInput.js +35 -162
  43. package/dist/CurrencyInput.mjs +5 -6
  44. package/dist/DetailRow.js +25 -150
  45. package/dist/DetailRow.mjs +3 -4
  46. package/dist/EmptyState.js +80 -268
  47. package/dist/EmptyState.mjs +7 -8
  48. package/dist/ErrorBoundary.js +32 -199
  49. package/dist/ErrorBoundary.mjs +4 -5
  50. package/dist/Form.js +1 -114
  51. package/dist/Form.mjs +2 -3
  52. package/dist/HolographicCard.d.mts +0 -28
  53. package/dist/HolographicCard.d.ts +0 -28
  54. package/dist/HolographicCard.js +20 -130
  55. package/dist/HolographicCard.mjs +9 -33
  56. package/dist/IconButton.js +36 -234
  57. package/dist/IconButton.mjs +5 -7
  58. package/dist/IconPicker.js +222 -929
  59. package/dist/IconPicker.mjs +5 -6
  60. package/dist/ImageUpload.d.mts +3 -3
  61. package/dist/ImageUpload.d.ts +3 -3
  62. package/dist/ImageUpload.js +49 -238
  63. package/dist/ImageUpload.mjs +5 -7
  64. package/dist/ImageViewer.js +75 -266
  65. package/dist/ImageViewer.mjs +8 -9
  66. package/dist/Input.d.mts +1 -1
  67. package/dist/Input.d.ts +1 -1
  68. package/dist/Input.js +35 -162
  69. package/dist/Input.mjs +4 -5
  70. package/dist/LabelValue.js +24 -149
  71. package/dist/LabelValue.mjs +3 -4
  72. package/dist/ListGroup.js +1 -114
  73. package/dist/ListGroup.mjs +2 -3
  74. package/dist/ListItem.d.mts +2 -1
  75. package/dist/ListItem.d.ts +2 -1
  76. package/dist/ListItem.js +41 -236
  77. package/dist/ListItem.mjs +5 -7
  78. package/dist/MediaCard.d.mts +0 -14
  79. package/dist/MediaCard.d.ts +0 -14
  80. package/dist/MediaCard.js +69 -315
  81. package/dist/MediaCard.mjs +5 -7
  82. package/dist/MenuGroup.js +1 -114
  83. package/dist/MenuGroup.mjs +2 -3
  84. package/dist/MenuItem.d.mts +2 -1
  85. package/dist/MenuItem.d.ts +2 -1
  86. package/dist/MenuItem.js +39 -235
  87. package/dist/MenuItem.mjs +5 -7
  88. package/dist/MonthPicker.js +8 -163
  89. package/dist/MonthPicker.mjs +3 -4
  90. package/dist/NumberStepper.d.mts +2 -1
  91. package/dist/NumberStepper.d.ts +2 -1
  92. package/dist/NumberStepper.js +44 -239
  93. package/dist/NumberStepper.mjs +5 -7
  94. package/dist/PagerDots.d.mts +1 -1
  95. package/dist/PagerDots.d.ts +1 -1
  96. package/dist/PagerDots.js +69 -224
  97. package/dist/PagerDots.mjs +6 -6
  98. package/dist/Pressable.js +14 -85
  99. package/dist/Pressable.mjs +4 -5
  100. package/dist/PricingCard.js +87 -272
  101. package/dist/PricingCard.mjs +8 -9
  102. package/dist/Progress.js +3 -123
  103. package/dist/Progress.mjs +3 -4
  104. package/dist/RadioGroup.js +52 -265
  105. package/dist/RadioGroup.mjs +5 -6
  106. package/dist/RetrayProvider.js +3 -6
  107. package/dist/RetrayProvider.mjs +3 -4
  108. package/dist/Select.d.mts +3 -1
  109. package/dist/Select.d.ts +3 -1
  110. package/dist/Select.js +27 -233
  111. package/dist/Select.mjs +4 -6
  112. package/dist/SelectableCard.js +33 -209
  113. package/dist/SelectableCard.mjs +5 -6
  114. package/dist/SelectableGrid.d.mts +0 -21
  115. package/dist/SelectableGrid.d.ts +0 -21
  116. package/dist/SelectableGrid.js +49 -272
  117. package/dist/SelectableGrid.mjs +5 -7
  118. package/dist/Separator.js +1 -114
  119. package/dist/Separator.mjs +2 -3
  120. package/dist/Sheet.d.mts +1 -1
  121. package/dist/Sheet.d.ts +1 -1
  122. package/dist/Sheet.js +33 -175
  123. package/dist/Sheet.mjs +3 -4
  124. package/dist/SheetSelect.js +39 -236
  125. package/dist/SheetSelect.mjs +6 -7
  126. package/dist/Skeleton.js +4 -124
  127. package/dist/Skeleton.mjs +3 -4
  128. package/dist/Slider.d.mts +2 -1
  129. package/dist/Slider.d.ts +2 -1
  130. package/dist/Slider.js +8 -161
  131. package/dist/Slider.mjs +3 -4
  132. package/dist/Spinner.js +3 -116
  133. package/dist/Spinner.mjs +2 -3
  134. package/dist/Stats.js +36 -234
  135. package/dist/Stats.mjs +5 -7
  136. package/dist/Switch.d.mts +2 -1
  137. package/dist/Switch.d.ts +2 -1
  138. package/dist/Switch.js +26 -176
  139. package/dist/Switch.mjs +5 -5
  140. package/dist/TabBar.js +43 -200
  141. package/dist/TabBar.mjs +5 -5
  142. package/dist/Tabs.js +15 -199
  143. package/dist/Tabs.mjs +5 -6
  144. package/dist/Text.js +9 -130
  145. package/dist/Text.mjs +2 -3
  146. package/dist/Textarea.d.mts +2 -1
  147. package/dist/Textarea.d.ts +2 -1
  148. package/dist/Textarea.js +71 -219
  149. package/dist/Textarea.mjs +4 -5
  150. package/dist/Toast.d.mts +12 -10
  151. package/dist/Toast.d.ts +12 -10
  152. package/dist/Toast.js +1 -114
  153. package/dist/Toast.mjs +2 -3
  154. package/dist/Toggle.js +39 -236
  155. package/dist/Toggle.mjs +6 -7
  156. package/dist/{chunk-ELGEOM7I.mjs → chunk-2QXJDRVU.mjs} +13 -10
  157. package/dist/{chunk-LIS6I5UP.mjs → chunk-2VIDP72N.mjs} +3 -3
  158. package/dist/{chunk-NHDI3VQB.mjs → chunk-422IVD3H.mjs} +16 -12
  159. package/dist/{chunk-DF7JA72E.mjs → chunk-4NQFTHN3.mjs} +13 -7
  160. package/dist/{chunk-3XCFYSX4.mjs → chunk-5MYNAAFE.mjs} +13 -17
  161. package/dist/{chunk-E7NEHHXV.mjs → chunk-62BBSSUF.mjs} +3 -3
  162. package/dist/{chunk-UBUXUMER.mjs → chunk-77UOVFIS.mjs} +7 -5
  163. package/dist/{chunk-M3C7XM2M.mjs → chunk-7BZJRB77.mjs} +28 -18
  164. package/dist/chunk-ARONDO7M.mjs +40 -0
  165. package/dist/{chunk-GH67YXG6.mjs → chunk-AZV7KNJI.mjs} +3 -3
  166. package/dist/{chunk-2P2CB235.mjs → chunk-BULKGOIZ.mjs} +7 -8
  167. package/dist/{chunk-RJNLAH76.mjs → chunk-C5ZRMR2E.mjs} +4 -2
  168. package/dist/chunk-CM2DG4MR.mjs +142 -0
  169. package/dist/{chunk-UQ4742ET.mjs → chunk-COA2YZOX.mjs} +8 -6
  170. package/dist/{chunk-EDLCGYIO.mjs → chunk-CZN6L2QU.mjs} +11 -8
  171. package/dist/{chunk-TS7DGUIR.mjs → chunk-DBHSUUKU.mjs} +2 -2
  172. package/dist/{chunk-57V2LXCK.mjs → chunk-DE25XTVQ.mjs} +3 -3
  173. package/dist/{chunk-RMRS44MQ.mjs → chunk-E2PONRJG.mjs} +13 -9
  174. package/dist/{chunk-GUTDFUNF.mjs → chunk-EHGBHFMH.mjs} +9 -17
  175. package/dist/{chunk-ZIMY2QUM.mjs → chunk-ERWJPVX7.mjs} +2 -2
  176. package/dist/{chunk-NLZY4TXU.mjs → chunk-ESQDPO5E.mjs} +7 -7
  177. package/dist/{chunk-VJBUCITV.mjs → chunk-EW2FIDSM.mjs} +1 -1
  178. package/dist/{chunk-HC4VVCWY.mjs → chunk-FTTI6T5Q.mjs} +4 -4
  179. package/dist/{chunk-MVMGPZN6.mjs → chunk-H6MQL7PS.mjs} +12 -7
  180. package/dist/{chunk-CF27NBXO.mjs → chunk-HHOOFDBA.mjs} +38 -41
  181. package/dist/{chunk-2HFD4IHU.mjs → chunk-HUSSF6TF.mjs} +1 -1
  182. package/dist/{chunk-HEDQPK4I.mjs → chunk-IDVUZIVY.mjs} +16 -22
  183. package/dist/chunk-IFYMBOEN.mjs +14 -0
  184. package/dist/{chunk-QOLWA2PW.mjs → chunk-IGU223UM.mjs} +80 -4
  185. package/dist/chunk-IJCMPVW5.mjs +121 -0
  186. package/dist/{chunk-AENAVIKT.mjs → chunk-ITG4JQM3.mjs} +4 -4
  187. package/dist/{chunk-E5UKLSJZ.mjs → chunk-K3QX2M26.mjs} +11 -8
  188. package/dist/{chunk-4OORJ2DY.mjs → chunk-K7TKID3V.mjs} +8 -7
  189. package/dist/{chunk-2LG326TT.mjs → chunk-KAGADD2O.mjs} +4 -4
  190. package/dist/{chunk-IVSRW4HS.mjs → chunk-KC5QDYGZ.mjs} +4 -4
  191. package/dist/{chunk-7AFZWSCI.mjs → chunk-KPTY7UYQ.mjs} +1 -1
  192. package/dist/{chunk-YTXRIXNZ.mjs → chunk-KSSVIFYR.mjs} +9 -12
  193. package/dist/chunk-L3YKPTJQ.mjs +119 -0
  194. package/dist/chunk-M53LC4Q7.mjs +35 -0
  195. package/dist/chunk-MZ6WRTD2.mjs +40 -0
  196. package/dist/chunk-NGEN2EES.mjs +581 -0
  197. package/dist/{chunk-ZR6HSEAB.mjs → chunk-NPCBNGNE.mjs} +17 -26
  198. package/dist/{chunk-C43HRKXH.mjs → chunk-OBV72JD4.mjs} +1 -1
  199. package/dist/{chunk-LPV4NJJK.mjs → chunk-PGQ6FMXS.mjs} +6 -5
  200. package/dist/{chunk-MEPSKGBO.mjs → chunk-PI6RULJX.mjs} +1 -1
  201. package/dist/{chunk-F3YTWO3T.mjs → chunk-RA6SAAFE.mjs} +9 -8
  202. package/dist/{chunk-UNNRUJTM.mjs → chunk-RRKM4MKB.mjs} +7 -7
  203. package/dist/{chunk-ULGNQPNE.mjs → chunk-S2VGME7X.mjs} +1 -1
  204. package/dist/{chunk-OLVJFKXS.mjs → chunk-S44XWTTC.mjs} +35 -25
  205. package/dist/{chunk-YMYIEVZP.mjs → chunk-SZEKQAOY.mjs} +1 -1
  206. package/dist/{chunk-BXF4AMHY.mjs → chunk-TMH263OK.mjs} +5 -4
  207. package/dist/{chunk-NJG7DHVF.mjs → chunk-U6DEBYU5.mjs} +10 -9
  208. package/dist/{chunk-QXDGGOLC.mjs → chunk-UMZTPUB3.mjs} +33 -21
  209. package/dist/{chunk-KSUWPU2F.mjs → chunk-WIPEDNSD.mjs} +7 -7
  210. package/dist/{chunk-QDAZGZUF.mjs → chunk-XCIG6HT2.mjs} +3 -3
  211. package/dist/{chunk-4J2PXL36.mjs → chunk-Y6YS33GM.mjs} +40 -38
  212. package/dist/{chunk-4XOB5TTD.mjs → chunk-ZKDKKQCE.mjs} +5 -5
  213. package/dist/{chunk-LOBLCFMN.mjs → chunk-ZTPYUU5C.mjs} +5 -5
  214. package/dist/fonts.mjs +0 -2
  215. package/dist/index.d.mts +13 -73
  216. package/dist/index.d.ts +13 -73
  217. package/dist/index.js +1149 -1892
  218. package/dist/index.mjs +81 -86
  219. package/package.json +20 -20
  220. package/src/components/Accordion/Accordion.tsx +15 -9
  221. package/src/components/AlertBanner/AlertBanner.tsx +7 -6
  222. package/src/components/AppHeader/AppHeader.tsx +25 -10
  223. package/src/components/Avatar/Avatar.tsx +92 -1
  224. package/src/components/Avatar/index.ts +2 -2
  225. package/src/components/Badge/Badge.tsx +2 -2
  226. package/src/components/Button/Button.tsx +50 -46
  227. package/src/components/Card/Card.tsx +1 -0
  228. package/src/components/CategoryStrip/CategoryStrip.tsx +36 -49
  229. package/src/components/Checkbox/Checkbox.tsx +3 -0
  230. package/src/components/Chip/Chip.tsx +5 -4
  231. package/src/components/ConfirmDialog/ConfirmDialog.tsx +33 -17
  232. package/src/components/DetailRow/DetailRow.tsx +3 -3
  233. package/src/components/EmptyState/EmptyState.tsx +2 -2
  234. package/src/components/ErrorBoundary/ErrorBoundary.tsx +6 -6
  235. package/src/components/HolographicCard/HolographicCard.tsx +14 -95
  236. package/src/components/IconButton/IconButton.tsx +2 -2
  237. package/src/components/IconPicker/IconPicker.tsx +13 -12
  238. package/src/components/ImageUpload/ImageUpload.tsx +43 -46
  239. package/src/components/ImageViewer/ImageViewer.tsx +3 -3
  240. package/src/components/Input/Input.tsx +11 -5
  241. package/src/components/LabelValue/LabelValue.tsx +2 -2
  242. package/src/components/ListItem/ListItem.tsx +7 -4
  243. package/src/components/MediaCard/MediaCard.tsx +21 -59
  244. package/src/components/MenuItem/MenuItem.tsx +5 -2
  245. package/src/components/MonthPicker/MonthPicker.tsx +2 -2
  246. package/src/components/NumberStepper/NumberStepper.tsx +10 -6
  247. package/src/components/PagerDots/PagerDots.tsx +38 -28
  248. package/src/components/PricingCard/PricingCard.tsx +6 -6
  249. package/src/components/RadioGroup/RadioGroup.tsx +18 -31
  250. package/src/components/Select/Select.tsx +35 -39
  251. package/src/components/SelectableCard/SelectableCard.tsx +4 -6
  252. package/src/components/SelectableGrid/SelectableGrid.tsx +37 -72
  253. package/src/components/Sheet/Sheet.tsx +28 -15
  254. package/src/components/Sheet/index.ts +2 -2
  255. package/src/components/SheetSelect/SheetSelect.tsx +3 -3
  256. package/src/components/Skeleton/Skeleton.tsx +1 -1
  257. package/src/components/Slider/Slider.tsx +3 -0
  258. package/src/components/Spinner/Spinner.tsx +2 -2
  259. package/src/components/Stats/Stats.tsx +2 -2
  260. package/src/components/Switch/Switch.tsx +12 -7
  261. package/src/components/TabBar/TabBar.tsx +9 -8
  262. package/src/components/Text/Text.tsx +13 -1
  263. package/src/components/Textarea/Textarea.tsx +18 -32
  264. package/src/components/Toggle/Toggle.tsx +3 -3
  265. package/src/hooks/useConfirmDialog.ts +31 -42
  266. package/src/index.ts +3 -4
  267. package/src/theme/ThemeProvider.tsx +1 -4
  268. package/src/theme/colorUtils.ts +1 -72
  269. package/src/theme/colors.ts +40 -1
  270. package/src/theme/types.ts +2 -2
  271. package/src/utils/animations.ts +0 -47
  272. package/src/utils/curatedIcons.ts +93 -801
  273. package/src/utils/haptics.ts +13 -208
  274. package/src/utils/icons.ts +27 -91
  275. package/src/utils/pressable.ts +10 -61
  276. package/dist/VirtualList.d.mts +0 -19
  277. package/dist/VirtualList.d.ts +0 -19
  278. package/dist/VirtualList.js +0 -38
  279. package/dist/VirtualList.mjs +0 -2
  280. package/dist/chunk-2BA3JMKK.mjs +0 -136
  281. package/dist/chunk-3DKJ2GIC.mjs +0 -30
  282. package/dist/chunk-7ELGZ66G.mjs +0 -164
  283. package/dist/chunk-DVK4G2GT.mjs +0 -59
  284. package/dist/chunk-EJ7ZPXOH.mjs +0 -163
  285. package/dist/chunk-KA7LTET3.mjs +0 -71
  286. package/dist/chunk-LNPKGWBG.mjs +0 -134
  287. package/dist/chunk-NC5ZTR2Y.mjs +0 -32
  288. package/dist/chunk-SAWUXP3A.mjs +0 -1114
  289. package/dist/chunk-Y6FXYEAI.mjs +0 -8
  290. package/dist/chunk-YNROWHQJ.mjs +0 -46
  291. package/src/components/VirtualList/VirtualList.tsx +0 -60
  292. package/src/components/VirtualList/index.ts +0 -1
  293. package/src/utils/fontGuard.ts +0 -35
  294. package/src/utils/hover.ts +0 -25
  295. package/src/utils/useColorTransition.ts +0 -40
  296. package/src/utils/usePressScale.ts +0 -75
@@ -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,19 +75,16 @@ function ButtonBase({
73
75
  onPress?.()
74
76
  }
75
77
 
76
- const containerVariantStyle: ViewStyle = isDisabled
77
- ? {
78
- primary: { backgroundColor: colors.surface },
79
- secondary: { backgroundColor: 'transparent', borderWidth: 1.5, borderColor: colors.border },
80
- text: { backgroundColor: 'transparent' },
81
- destructive: { backgroundColor: colors.surface },
82
- }[variant]
83
- : {
84
- primary: { backgroundColor: colors.primary },
85
- secondary: { backgroundColor: 'transparent', borderWidth: 1.5, borderColor: colors.primary },
86
- text: { backgroundColor: 'transparent' },
87
- destructive: { backgroundColor: colors.destructive },
88
- }[variant]
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')
89
88
 
90
89
  const labelColor = isDisabled
91
90
  ? colors.foregroundMuted
@@ -99,7 +98,7 @@ function ButtonBase({
99
98
  const textColor = iconColor ?? labelColor
100
99
 
101
100
  const effectiveIcon: React.ReactNode = iconName
102
- ? renderIcon(iconName, iconSizeMap[size], textColor)
101
+ ? <Icon name={iconName} size={iconSizeMap[size]} color={textColor} />
103
102
  : typeof icon === 'function' ? icon({ label, size, variant, color: textColor }) : icon
104
103
 
105
104
  const spinnerColor = isDisabled
@@ -115,13 +114,6 @@ function ButtonBase({
115
114
  return (
116
115
  <View style={[fullWidth && styles.fullWidth, flex !== undefined && { flex }]}>
117
116
  <PressableButton
118
- style={[
119
- styles.base,
120
- containerVariantStyle,
121
- containerSizeStyles[size],
122
- fullWidth && styles.fullWidth,
123
- restStyle,
124
- ]}
125
117
  enabled={!isDisabled}
126
118
  onPress={handlePress}
127
119
  rippleColor="transparent"
@@ -132,30 +124,42 @@ function ButtonBase({
132
124
  accessibilityHint={accessibilityHint}
133
125
  accessibilityState={{ disabled: isDisabled, busy: loading }}
134
126
  >
135
- {loading ? (
136
- <>
137
- <ActivityIndicator size="small" color={spinnerColor} style={{ marginRight: s(6) }} />
138
- <Text
139
- style={[styles.label, { color: labelColor }, labelSizeStyles[size], styles.labelLoading]}
140
- allowFontScaling={true}
141
- numberOfLines={1}
142
- >
143
- {label}
144
- </Text>
145
- </>
146
- ) : (
147
- <>
148
- {effectiveIcon && iconPosition === 'left' && <>{effectiveIcon}</>}
149
- <Text
150
- style={[styles.label, { color: labelColor }, labelSizeStyles[size], effectiveIcon ? styles.labelWithIcon : undefined]}
151
- allowFontScaling={true}
152
- numberOfLines={1}
153
- >
154
- {label}
155
- </Text>
156
- {effectiveIcon && iconPosition === 'right' && <>{effectiveIcon}</>}
157
- </>
158
- )}
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>
159
163
  </PressableButton>
160
164
  </View>
161
165
  )
@@ -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
 
@@ -14,6 +14,7 @@ export interface CheckboxProps {
14
14
  disabled?: boolean
15
15
  style?: ViewStyle
16
16
  accessibilityLabel?: string
17
+ accessibilityHint?: string
17
18
  }
18
19
 
19
20
  export function Checkbox({
@@ -23,6 +24,7 @@ export function Checkbox({
23
24
  disabled,
24
25
  style,
25
26
  accessibilityLabel,
27
+ accessibilityHint,
26
28
  }: CheckboxProps) {
27
29
  const { colors } = useTheme()
28
30
 
@@ -43,6 +45,7 @@ export function Checkbox({
43
45
  touchSoundDisabled
44
46
  accessibilityRole="checkbox"
45
47
  accessibilityLabel={accessibilityLabel ?? label}
48
+ accessibilityHint={accessibilityHint}
46
49
  accessibilityState={{ checked, disabled: !!disabled }}
47
50
  >
48
51
  <EaseView
@@ -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.
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useRef, useId } from 'react'
1
+ import React, { useCallback, useEffect, useRef, useState, useId } from 'react'
2
2
  import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
3
3
  import {
4
4
  BottomSheetModal,
@@ -24,42 +24,57 @@ export interface ConfirmDialogProps {
24
24
  showCloseButton?: boolean
25
25
  onConfirm: () => void
26
26
  onCancel: () => void
27
+ accessibilityHint?: string
27
28
  }
28
29
 
29
30
  export function ConfirmDialog({
30
31
  visible,
31
32
  title,
32
33
  subtitle,
33
- confirmLabel = 'Confirm',
34
- cancelLabel = 'Cancel',
34
+ confirmLabel = 'Confirmar',
35
+ cancelLabel = 'Cancelar',
35
36
  confirmVariant = 'primary',
36
37
  loading = false,
37
38
  showCloseButton = false,
38
39
  onConfirm,
39
40
  onCancel,
41
+ accessibilityHint,
40
42
  }: ConfirmDialogProps) {
43
+ type DialogState = 'idle' | 'presenting' | 'presented' | 'dismissing'
44
+
41
45
  const { colors } = useTheme()
42
46
  const insets = useSafeAreaInsets()
43
47
  const ref = useRef<BottomSheetModal>(null)
44
- const wasOpened = useRef(false)
45
- const isPresentedRef = useRef(false)
48
+ const dialogState = useRef<DialogState>('idle')
49
+ const onCancelRef = useRef(onCancel)
46
50
  const name = useId()
51
+ const [tick, setTick] = useState(0)
47
52
 
48
- const handleDismiss = useCallback(() => {
49
- isPresentedRef.current = false
50
- onCancel()
53
+ useEffect(() => {
54
+ onCancelRef.current = onCancel
51
55
  }, [onCancel])
52
56
 
57
+ const handleDismiss = useCallback(() => {
58
+ dialogState.current = 'idle'
59
+ onCancelRef.current?.()
60
+ setTick((t) => t + 1)
61
+ }, [])
62
+
53
63
  useEffect(() => {
54
- if (visible && !isPresentedRef.current) {
55
- impactMedium()
56
- ref.current?.present()
57
- wasOpened.current = true
58
- isPresentedRef.current = true
59
- } else if (!visible && wasOpened.current && isPresentedRef.current) {
60
- ref.current?.dismiss()
64
+ if (visible) {
65
+ if (dialogState.current === 'idle') {
66
+ dialogState.current = 'presenting'
67
+ impactMedium()
68
+ ref.current?.present()
69
+ dialogState.current = 'presented'
70
+ }
71
+ } else {
72
+ if (dialogState.current === 'presented' || dialogState.current === 'presenting') {
73
+ dialogState.current = 'dismissing'
74
+ ref.current?.dismiss()
75
+ }
61
76
  }
62
- }, [visible])
77
+ }, [visible, tick])
63
78
 
64
79
  const renderBackdrop = useCallback(
65
80
  (props: BottomSheetBackdropProps) => (
@@ -98,7 +113,7 @@ export function ConfirmDialog({
98
113
  activeOpacity={0.6}
99
114
  touchSoundDisabled={true}
100
115
  accessibilityRole="button"
101
- accessibilityLabel="Close"
116
+ accessibilityLabel="Cerrar"
102
117
  hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
103
118
  >
104
119
  <Feather name="x" size={ms(18)} color={colors.foregroundMuted} />
@@ -122,6 +137,7 @@ export function ConfirmDialog({
122
137
  notificationSuccess()
123
138
  onConfirm()
124
139
  }}
140
+ accessibilityHint={accessibilityHint}
125
141
  icon={
126
142
  <Feather
127
143
  name={confirmVariant === 'destructive' ? 'trash-2' : 'check'}
@@ -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>