@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
@@ -5,7 +5,7 @@ import { impactLight } from '../../utils/haptics'
5
5
  import { useTheme } from '../../theme'
6
6
  import { s, vs, ms, mvs } from '../../utils/scaling'
7
7
  import { RADIUS } from '../../tokens'
8
- import { renderIcon } from '../../utils/icons'
8
+ import { Icon } from '../../utils/icons'
9
9
  import { COLOR_TRANSITION, OPACITY_TRANSITION, SPRING_ELASTIC } from '../../utils/animations'
10
10
 
11
11
  type SelectType = 'radio' | 'checkbox'
@@ -146,20 +146,18 @@ export function SelectableCard({
146
146
  })()
147
147
 
148
148
  const resolvedIcon = iconName
149
- ? renderIcon(iconName, ms(22), disabled ? colors.foregroundMuted : colors.foregroundMuted)
149
+ ? <Icon name={iconName} size={ms(22)} color={disabled ? colors.foregroundMuted : colors.foregroundMuted} />
150
150
  : icon
151
151
 
152
152
  const resolvedIconElement = resolvedIcon ? (
153
153
  <View style={[styles.iconWrapper, disabled && { opacity: 0.45 }]}>{resolvedIcon}</View>
154
154
  ) : null
155
155
 
156
- const selectorAccessibilityRole = type === 'radio' ? 'radio' : 'checkbox'
157
-
158
156
  return (
159
157
  <Pressable
160
158
  onPress={handlePress}
161
159
  disabled={disabled}
162
- accessibilityRole="button"
160
+ accessibilityRole={type === 'radio' ? 'radio' : 'checkbox'}
163
161
  accessibilityLabel={`${title}${description ? `, ${description}` : ''}`}
164
162
  accessibilityState={{ selected: isSelected, disabled }}
165
163
  style={[
@@ -171,7 +169,7 @@ export function SelectableCard({
171
169
  >
172
170
  <View style={styles.row}>
173
171
  {/* Selection indicator */}
174
- <View style={styles.selectorContainer} accessibilityRole={selectorAccessibilityRole} accessibilityState={{ selected: isSelected, disabled }}>
172
+ <View style={styles.selectorContainer}>
175
173
  {type === 'radio' ? (
176
174
  <EaseView
177
175
  style={styles.radioCircle}
@@ -1,38 +1,27 @@
1
1
  import React, { useState } from 'react'
2
- import { View, Text, TouchableOpacity, StyleSheet, ViewStyle, ScrollView } from 'react-native'
3
- import Animated from 'react-native-reanimated'
2
+ import { View, Text, StyleSheet, ViewStyle, ScrollView } from 'react-native'
4
3
  import { useTheme } from '../../theme'
5
- import { renderIcon } from '../../utils/icons'
6
- import { usePressScale } from '../../utils/usePressScale'
4
+ import { Icon } from '../../utils/icons'
5
+ import { PressableChip } from '../../utils/pressable'
7
6
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
8
- import { PRESS_SCALE } from '../../utils/animations'
9
7
  import { s, vs, ms, mvs } from '../../utils/scaling'
10
8
  import { RADIUS } from '../../tokens'
11
9
 
12
10
  export interface SelectableGridItem<T extends string | number = string> {
13
- /** Unique value emitted on selection. */
14
11
  value: T
15
- /** Label rendered under the icon. */
16
12
  label?: string
17
- /** Icon name resolved via the icon registry. */
18
13
  iconName?: string
19
- /** Custom icon node — overrides `iconName`. */
20
14
  icon?: React.ReactNode
21
15
  disabled?: boolean
22
16
  }
23
17
 
24
18
  export interface SelectableGridProps<T extends string | number = string> {
25
19
  items: SelectableGridItem<T>[]
26
- /** Selected value(s). Array when `multiple`. */
27
20
  value: T | T[] | null
28
21
  onChange: (value: T) => void
29
- /** Allow multiple selections. `value` should be an array. Defaults to false. */
30
22
  multiple?: boolean
31
- /** Columns per row. Defaults to 4. Ignored when `orientation='horizontal'`. */
32
23
  numColumns?: number
33
- /** Gap between cells (dp). Defaults to 12. */
34
24
  gap?: number
35
- /** Layout orientation. 'grid' (default) wraps into rows. 'horizontal' creates a single scrollable row. */
36
25
  orientation?: 'grid' | 'horizontal'
37
26
  style?: ViewStyle
38
27
  }
@@ -51,63 +40,44 @@ interface CellProps<T extends string | number> {
51
40
 
52
41
  function Cell<T extends string | number>({ item, selected, width, onPress }: CellProps<T>) {
53
42
  const { colors } = useTheme()
54
- const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
55
- pressScale: PRESS_SCALE.chip,
56
- disabled: item.disabled,
57
- })
58
43
 
59
44
  const iconColor = selected ? colors.primary : colors.foregroundSubtle
60
- const iconNode = item.icon ?? (item.iconName ? renderIcon(item.iconName, ms(24), iconColor) : null)
45
+ const iconNode = item.icon ?? (item.iconName ? <Icon name={item.iconName} size={ms(24)} color={iconColor} /> : null)
61
46
 
62
47
  return (
63
- <Animated.View style={[{ width }, animatedStyle]}>
64
- <TouchableOpacity
65
- onPress={onPress}
66
- onPressIn={onPressIn}
67
- onPressOut={onPressOut}
68
- disabled={item.disabled}
69
- activeOpacity={1}
70
- touchSoundDisabled={true}
71
- accessibilityRole="button"
72
- accessibilityState={{ selected, disabled: item.disabled }}
73
- accessibilityLabel={item.label ?? String(item.value)}
74
- {...hoverHandlers}
75
- style={[
76
- styles.cell,
77
- {
78
- backgroundColor: selected ? colors.primary + '14' : colors.surface,
79
- borderColor: selected ? colors.primary : 'transparent',
80
- },
81
- item.disabled && styles.cellDisabled,
82
- ]}
83
- >
84
- {iconNode}
85
- {item.label ? (
86
- <Text
87
- style={[styles.label, { color: selected ? colors.primary : colors.foreground }]}
88
- numberOfLines={1}
89
- allowFontScaling={true}
90
- >
91
- {item.label}
92
- </Text>
93
- ) : null}
94
- </TouchableOpacity>
95
- </Animated.View>
48
+ <PressableChip
49
+ onPress={onPress}
50
+ enabled={!item.disabled}
51
+ rippleColor="transparent"
52
+ touchSoundDisabled
53
+ activateOnHover
54
+ accessibilityRole="button"
55
+ accessibilityState={{ selected, disabled: item.disabled }}
56
+ accessibilityLabel={item.label ?? String(item.value)}
57
+ style={[
58
+ { width },
59
+ styles.cell,
60
+ {
61
+ backgroundColor: selected ? colors.primary + '14' : colors.surface,
62
+ borderColor: selected ? colors.primary : 'transparent',
63
+ },
64
+ item.disabled && styles.cellDisabled,
65
+ ]}
66
+ >
67
+ {iconNode}
68
+ {item.label ? (
69
+ <Text
70
+ style={[styles.label, { color: selected ? colors.primary : colors.foreground }]}
71
+ numberOfLines={1}
72
+ allowFontScaling={true}
73
+ >
74
+ {item.label}
75
+ </Text>
76
+ ) : null}
77
+ </PressableChip>
96
78
  )
97
79
  }
98
80
 
99
- /**
100
- * Grid of selectable cells (icon + label) — for store / category / emoji pickers
101
- * where a list would be the wrong shape. Single or multi select.
102
- *
103
- * @example
104
- * <SelectableGrid
105
- * items={categories}
106
- * value={selected}
107
- * onChange={setSelected}
108
- * numColumns={4}
109
- * />
110
- */
111
81
  export function SelectableGrid<T extends string | number = string>({
112
82
  items,
113
83
  value,
@@ -120,10 +90,7 @@ export function SelectableGrid<T extends string | number = string>({
120
90
  }: SelectableGridProps<T>) {
121
91
  const [containerWidth, setContainerWidth] = useState(0)
122
92
  const gapPx = s(gap)
123
- // Compute exact cell width so `numColumns` always fits — percentage widths + gap
124
- // overflow and wrap one short. -0.5 guards against sub-pixel rounding overflow.
125
93
  const cellWidth = containerWidth > 0 ? (containerWidth - gapPx * (numColumns - 1)) / numColumns - 0.5 : 0
126
- // Horizontal mode: fixed 72dp cell width (same scale as grid cells)
127
94
  const horizCellWidth = s(72)
128
95
 
129
96
  const handlePress = (item: SelectableGridItem<T>) => {
@@ -184,22 +151,21 @@ const styles = StyleSheet.create({
184
151
  paddingHorizontal: s(4),
185
152
  },
186
153
  cell: {
187
- flex: 1,
188
154
  borderRadius: RADIUS.md,
189
155
  borderWidth: 2,
190
156
  alignItems: 'center',
191
157
  justifyContent: 'center',
192
158
  gap: vs(4),
193
- paddingHorizontal: s(12),
194
- paddingVertical: vs(12),
159
+ paddingHorizontal: s(8),
160
+ paddingVertical: vs(10),
195
161
  },
196
162
  cellDisabled: {
197
163
  opacity: 0.4,
198
164
  },
199
165
  label: {
200
166
  fontFamily: 'Sohne-Medium',
201
- fontSize: ms(12),
202
- lineHeight: mvs(15),
167
+ fontSize: ms(11),
168
+ lineHeight: mvs(14),
203
169
  textAlign: 'center',
204
170
  },
205
171
  })
@@ -163,7 +163,7 @@ export function Sheet({
163
163
  activeOpacity={0.6}
164
164
  touchSoundDisabled={true}
165
165
  accessibilityRole="button"
166
- accessibilityLabel="Close"
166
+ accessibilityLabel="Cerrar"
167
167
  hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
168
168
  >
169
169
  <AntDesign name="close" size={ms(18)} color={colors.foregroundMuted} />
@@ -4,7 +4,7 @@ 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
9
  import { PressableChip } from '../../utils/pressable'
10
10
  import { RADIUS } from '../../tokens'
@@ -47,7 +47,7 @@ function SheetSelectChip({
47
47
  }
48
48
 
49
49
  const iconColor = selected ? colors.primaryForeground : colors.foreground
50
- const resolvedIcon = option.iconName ? renderIcon(option.iconName, ms(13), iconColor) : null
50
+ const resolvedIcon = option.iconName ? <Icon name={option.iconName} size={ms(13)} color={iconColor} /> : null
51
51
 
52
52
  return (
53
53
  <PressableChip
@@ -55,7 +55,7 @@ function SheetSelectChip({
55
55
  rippleColor="transparent"
56
56
  touchSoundDisabled
57
57
  accessibilityRole="button"
58
- accessibilityLabel={option.disabled ? `${option.label}, unavailable` : option.label}
58
+ accessibilityLabel={option.disabled ? `${option.label}, no disponible` : option.label}
59
59
  accessibilityState={{ selected, disabled: option.disabled }}
60
60
  >
61
61
  <EaseView
@@ -83,7 +83,7 @@ export function Skeleton({
83
83
  ]}
84
84
  onLayout={(e) => setContainerWidth(e.nativeEvent.layout.width)}
85
85
  accessibilityRole="progressbar"
86
- accessibilityLabel="Loading"
86
+ accessibilityLabel="Cargando"
87
87
  accessibilityState={{ busy: true }}
88
88
  >
89
89
  <Animated.View style={[StyleSheet.absoluteFill, shimmerStyle]}>
@@ -13,7 +13,7 @@ export interface SpinnerProps extends Omit<ActivityIndicatorProps, 'size'> {
13
13
 
14
14
  const sizeMap: Record<SpinnerSize, 'small' | 'large'> = {
15
15
  sm: 'small',
16
- md: 'small',
16
+ md: 'large',
17
17
  lg: 'large',
18
18
  }
19
19
 
@@ -25,7 +25,7 @@ const labelFontSize: Record<SpinnerSize, number> = {
25
25
 
26
26
  export function Spinner({ size = 'md', color, label, ...props }: SpinnerProps) {
27
27
  const { colors } = useTheme()
28
- const a11yLabel = label || 'Loading'
28
+ const a11yLabel = label || 'Cargando'
29
29
 
30
30
  if (label) {
31
31
  return (
@@ -4,7 +4,7 @@ import { impactLight } from '../../utils/haptics'
4
4
  import { useTheme } from '../../theme'
5
5
  import { s, vs, ms, mvs } from '../../utils/scaling'
6
6
  import { RADIUS } from '../../tokens'
7
- import { renderIcon } from '../../utils/icons'
7
+ import { Icon } from '../../utils/icons'
8
8
  import { PressableCard } from '../../utils/pressable'
9
9
 
10
10
  export type StatsVariant = 'elevated' | 'outlined' | 'filled'
@@ -114,7 +114,7 @@ function StatsComponent({
114
114
 
115
115
  const iconColorResolved = iconColor ?? colors.primary
116
116
 
117
- const resolvedIcon = iconName ? renderIcon(iconName, sizeStyles.iconSize, iconColorResolved) : icon
117
+ const resolvedIcon = iconName ? <Icon name={iconName} size={sizeStyles.iconSize} color={iconColorResolved} /> : icon
118
118
 
119
119
  const iconElement = resolvedIcon ? (
120
120
  <View style={styles.iconWrapper}>{resolvedIcon}</View>
@@ -1,11 +1,12 @@
1
1
  import React from 'react'
2
- import { TouchableOpacity, StyleSheet, ViewStyle, View } from 'react-native'
2
+ import { StyleSheet, ViewStyle, View } from 'react-native'
3
3
  import { EaseView } from 'react-native-ease'
4
4
  import { Feather } from '@expo/vector-icons'
5
5
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
6
6
  import { useTheme } from '../../theme'
7
7
  import { s } from '../../utils/scaling'
8
8
  import { COLOR_TRANSITION, OPACITY_TRANSITION, SPRING_ELASTIC } from '../../utils/animations'
9
+ import { PressableButton } from '../../utils/pressable'
9
10
 
10
11
  const TRACK_WIDTH = s(52)
11
12
  const TRACK_HEIGHT = s(30)
@@ -30,14 +31,14 @@ export function Switch({ checked = false, onCheckedChange, disabled, style, acce
30
31
 
31
32
  return (
32
33
  <View style={[{ alignSelf: 'flex-start' }, style]}>
33
- <TouchableOpacity
34
+ <PressableButton
34
35
  onPress={() => {
35
36
  hapticSelection()
36
37
  onCheckedChange?.(!checked)
37
38
  }}
38
- disabled={disabled}
39
- activeOpacity={0.8}
40
- touchSoundDisabled={true}
39
+ enabled={!disabled}
40
+ rippleColor="transparent"
41
+ touchSoundDisabled
41
42
  accessibilityRole="switch"
42
43
  accessibilityLabel={accessibilityLabel}
43
44
  accessibilityState={{ checked, disabled: isDisabled }}
@@ -76,7 +77,7 @@ export function Switch({ checked = false, onCheckedChange, disabled, style, acce
76
77
  </EaseView>
77
78
  </EaseView>
78
79
  </View>
79
- </TouchableOpacity>
80
+ </PressableButton>
80
81
  </View>
81
82
  )
82
83
  }
@@ -84,6 +85,8 @@ export function Switch({ checked = false, onCheckedChange, disabled, style, acce
84
85
  const styles = StyleSheet.create({
85
86
  touchable: {
86
87
  alignSelf: 'flex-start',
88
+ minHeight: 44,
89
+ justifyContent: 'center',
87
90
  },
88
91
  trackContainer: {
89
92
  position: 'relative',
@@ -1,10 +1,11 @@
1
1
  import React from 'react'
2
- import { View, Text, TouchableOpacity, StyleSheet, ViewStyle } from 'react-native'
2
+ import { View, Text, StyleSheet, ViewStyle } from 'react-native'
3
3
  import { useSafeAreaInsets } from 'react-native-safe-area-context'
4
4
  import { useTheme } from '../../theme'
5
- import { renderIcon } from '../../utils/icons'
5
+ import { Icon } from '../../utils/icons'
6
6
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
7
7
  import { s, vs, ms, mvs } from '../../utils/scaling'
8
+ import { PressableTab } from '../../utils/pressable'
8
9
 
9
10
  export interface TabBarItem {
10
11
  /** Unique key for the tab. */
@@ -74,23 +75,23 @@ export function TabBar({
74
75
  {items.map((item) => {
75
76
  const active = item.key === activeKey
76
77
  const tint = active ? resolvedActive : resolvedInactive
77
- const iconNode = item.icon ?? (item.iconName ? renderIcon(item.iconName, ms(24), tint) : null)
78
+ const iconNode = item.icon ?? (item.iconName ? <Icon name={item.iconName} size={ms(24)} color={tint} /> : null)
78
79
  const showBadge = item.badge !== undefined && item.badge !== false
79
80
  const badgeCount = typeof item.badge === 'number' ? item.badge : undefined
80
81
 
81
82
  return (
82
- <TouchableOpacity
83
+ <PressableTab
83
84
  key={item.key}
84
- style={styles.tab}
85
85
  onPress={() => {
86
86
  if (!active) hapticSelection()
87
87
  onTabPress(item.key)
88
88
  }}
89
- activeOpacity={0.7}
90
- touchSoundDisabled={true}
89
+ rippleColor="transparent"
90
+ touchSoundDisabled
91
91
  accessibilityRole="tab"
92
92
  accessibilityState={{ selected: active }}
93
93
  accessibilityLabel={item.label ?? item.key}
94
+ style={styles.tab}
94
95
  >
95
96
  <View>
96
97
  {iconNode}
@@ -115,7 +116,7 @@ export function TabBar({
115
116
  {item.label}
116
117
  </Text>
117
118
  ) : null}
118
- </TouchableOpacity>
119
+ </PressableTab>
119
120
  )
120
121
  })}
121
122
  </View>
@@ -1,9 +1,11 @@
1
1
  import React from 'react'
2
2
  import { Text as RNText, TextProps as RNTextProps, TextStyle } from 'react-native'
3
+
4
+ declare const __DEV__: boolean | undefined
3
5
  import { useTheme } from '../../theme'
4
6
  import { TYPOGRAPHY } from '../../tokens'
5
7
  import { ms, mvs } from '../../utils/scaling'
6
- import { warnIfFontsMissing } from '../../utils/fontGuard'
8
+ import { isLoaded as expoFontIsLoaded } from 'expo-font'
7
9
 
8
10
  export type TextVariant =
9
11
  | 'display-hero'
@@ -70,6 +72,15 @@ const defaultColorVariant: Partial<Record<TextVariant, 'foreground' | 'foregroun
70
72
  'button-sm': 'foreground',
71
73
  }
72
74
 
75
+ let fontWarned = false
76
+ function warnIfFontsMissing(): void {
77
+ if (fontWarned || typeof __DEV__ === 'undefined' || !__DEV__) return
78
+ fontWarned = true
79
+ if (!expoFontIsLoaded('Sohne-Regular')) {
80
+ console.warn('[retray-ui-kit] Sohne fonts not loaded — text falls back to system font.')
81
+ }
82
+ }
83
+
73
84
  function TextBase({ variant = 'body-md', color, style, uppercase, children, ...props }: TextProps) {
74
85
  warnIfFontsMissing()
75
86
  const { colors } = useTheme()
@@ -1,15 +1,10 @@
1
1
  import React, { useState } from 'react'
2
2
  import { TextInput, View, Text, StyleSheet, TextInputProps, ViewStyle, Platform } from 'react-native'
3
- import Animated, {
4
- useAnimatedStyle,
5
- interpolateColor,
6
- interpolate,
7
- } from 'react-native-reanimated'
3
+ import { EaseView } from 'react-native-ease'
8
4
  import { useTheme } from '../../theme'
9
5
  import { s, vs, ms } from '../../utils/scaling'
10
- import { renderIcon } from '../../utils/icons'
11
- import { useColorTransition } from '../../utils/useColorTransition'
12
- import { TIMINGS } from '../../utils/animations'
6
+ import { Icon } from '../../utils/icons'
7
+ import { COLOR_TRANSITION } from '../../utils/animations'
13
8
 
14
9
  const webInputResetStyle: Record<string, unknown> =
15
10
  Platform.OS === 'web'
@@ -20,6 +15,7 @@ export interface TextareaProps extends TextInputProps {
20
15
  label?: string
21
16
  error?: string
22
17
  hint?: string
18
+ disabled?: boolean
23
19
  rows?: number
24
20
  prefixIcon?: string
25
21
  prefixIconNode?: React.ReactNode
@@ -31,6 +27,7 @@ export function Textarea({
31
27
  label,
32
28
  error,
33
29
  hint,
30
+ disabled,
34
31
  rows = 4,
35
32
  prefixIcon,
36
33
  prefixIconNode,
@@ -44,46 +41,36 @@ export function Textarea({
44
41
  }: TextareaProps) {
45
42
  const { colors } = useTheme()
46
43
  const [focused, setFocused] = useState(false)
47
- const focusProgress = useColorTransition(focused, {
48
- duration: focused ? TIMINGS.focusIn.duration : TIMINGS.focusOut.duration,
49
- })
50
44
 
51
45
  const resolvedPrefixIcon = prefixIcon
52
- ? renderIcon(prefixIcon, ms(16), prefixIconColor ?? colors.foregroundMuted)
46
+ ? <Icon name={prefixIcon} size={ms(16)} color={prefixIconColor ?? colors.foregroundMuted} />
53
47
  : prefixIconNode
54
48
 
55
- // Border drawn on an absolute overlay (mirrors Input.tsx) so the 1px→2px
56
- // focus weight change never resizes the box / reflows content.
57
- const borderAnimStyle = useAnimatedStyle(() => ({
58
- borderColor: error
59
- ? colors.destructive
60
- : interpolateColor(focusProgress.value, [0, 1], [colors.border, colors.primary]),
61
- borderWidth: error
62
- ? 2
63
- : interpolate(focusProgress.value, [0, 1], [1, 2]),
64
- }))
49
+ const borderColor = error ? colors.destructive : (focused ? colors.primary : colors.border)
65
50
 
66
51
  return (
67
52
  <View style={[styles.container, containerStyle]}>
68
53
  {label ? <Text style={[styles.label, { color: colors.foreground }]} allowFontScaling={true}>{label}</Text> : null}
69
- <Animated.View
70
- style={[
71
- styles.inputWrapper,
72
- { backgroundColor: colors.background },
73
- ]}
74
- >
75
- <Animated.View style={[styles.borderOverlay, borderAnimStyle]} pointerEvents="none" />
54
+ <View style={[styles.inputWrapper, { backgroundColor: colors.background }]}>
55
+ <EaseView
56
+ style={[styles.borderOverlay, { borderWidth: error ? 2 : 1 }]}
57
+ animate={{ borderColor }}
58
+ transition={COLOR_TRANSITION}
59
+ pointerEvents="none"
60
+ />
76
61
  {resolvedPrefixIcon ? <View style={styles.prefixIcon}>{resolvedPrefixIcon}</View> : null}
77
62
  <TextInput
78
63
  multiline
79
64
  numberOfLines={rows}
80
65
  textAlignVertical="top"
66
+ editable={!disabled}
81
67
  style={[
82
68
  styles.input,
83
69
  {
84
70
  color: colors.foreground,
85
71
  minHeight: rows * vs(30),
86
72
  },
73
+ disabled && { opacity: 0.45 },
87
74
  webInputResetStyle,
88
75
  style,
89
76
  ]}
@@ -98,9 +85,10 @@ export function Textarea({
98
85
  placeholderTextColor={colors.foregroundMuted}
99
86
  allowFontScaling={true}
100
87
  accessibilityLabel={accessibilityLabel ?? label}
88
+ accessibilityState={{ disabled: !!disabled }}
101
89
  {...props}
102
90
  />
103
- </Animated.View>
91
+ </View>
104
92
  {error ? (
105
93
  <Text
106
94
  style={[styles.helperText, { color: colors.destructive }]}
@@ -128,8 +116,6 @@ const styles = StyleSheet.create({
128
116
  marginBottom: vs(2),
129
117
  },
130
118
  inputWrapper: {
131
- // Border lives on borderOverlay (absolute); wrapper carries none so the
132
- // focus weight change never reflows content.
133
119
  borderRadius: 8,
134
120
  paddingHorizontal: s(14),
135
121
  paddingVertical: vs(11),
@@ -5,7 +5,7 @@ import { FontAwesome5 } from '@expo/vector-icons'
5
5
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
6
6
  import { useTheme } from '../../theme'
7
7
  import { s, vs, ms } from '../../utils/scaling'
8
- import { renderIcon } from '../../utils/icons'
8
+ import { Icon } from '../../utils/icons'
9
9
  import { COLOR_TRANSITION } from '../../utils/animations'
10
10
  import { PressableButton } from '../../utils/pressable'
11
11
 
@@ -30,13 +30,13 @@ function ToggleIcon({ pressed, iconName, activeIconName, icon, activeIcon, iconC
30
30
  }
31
31
 
32
32
  if (pressed) {
33
- if (activeIconName) return <>{renderIcon(activeIconName, iconSize, activeIconColor ?? primaryColor)}</>
33
+ if (activeIconName) return <Icon name={activeIconName} size={iconSize} color={activeIconColor ?? primaryColor} />
34
34
  const active = renderProp(activeIcon)
35
35
  if (active) return <>{active}</>
36
36
  return <FontAwesome5 name="check-circle" size={iconSize} color={primaryColor} />
37
37
  }
38
38
 
39
- if (iconName) return <>{renderIcon(iconName, iconSize, iconColor ?? mutedColor)}</>
39
+ if (iconName) return <Icon name={iconName} size={iconSize} color={iconColor ?? mutedColor} />
40
40
  const custom = renderProp(icon)
41
41
  if (custom) return <>{custom}</>
42
42