@retray-dev/ui-kit 7.0.1 → 9.1.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 (234) hide show
  1. package/COMPONENTS.md +567 -14
  2. package/EXAMPLES.md +21 -14
  3. package/README.md +14 -8
  4. package/dist/Accordion.js +57 -5
  5. package/dist/Accordion.mjs +4 -3
  6. package/dist/AlertBanner.js +4 -1
  7. package/dist/AlertBanner.mjs +3 -2
  8. package/dist/AppHeader.d.mts +40 -0
  9. package/dist/AppHeader.d.ts +40 -0
  10. package/dist/AppHeader.js +515 -0
  11. package/dist/AppHeader.mjs +10 -0
  12. package/dist/Avatar.js +39 -29
  13. package/dist/Avatar.mjs +2 -1
  14. package/dist/Badge.js +11 -1
  15. package/dist/Badge.mjs +2 -1
  16. package/dist/Button.d.mts +8 -3
  17. package/dist/Button.d.ts +8 -3
  18. package/dist/Button.js +126 -108
  19. package/dist/Button.mjs +6 -5
  20. package/dist/ButtonGroup.mjs +1 -0
  21. package/dist/Card.js +90 -70
  22. package/dist/Card.mjs +5 -4
  23. package/dist/CategoryStrip.js +79 -22
  24. package/dist/CategoryStrip.mjs +6 -6
  25. package/dist/Checkbox.js +118 -86
  26. package/dist/Checkbox.mjs +5 -5
  27. package/dist/Chip.js +113 -80
  28. package/dist/Chip.mjs +5 -5
  29. package/dist/ConfirmDialog.js +140 -110
  30. package/dist/ConfirmDialog.mjs +7 -6
  31. package/dist/CurrencyDisplay.mjs +1 -0
  32. package/dist/CurrencyInput.d.mts +1 -1
  33. package/dist/CurrencyInput.d.ts +1 -1
  34. package/dist/CurrencyInput.js +9 -5
  35. package/dist/CurrencyInput.mjs +5 -4
  36. package/dist/DetailRow.mjs +1 -0
  37. package/dist/EmptyState.js +131 -111
  38. package/dist/EmptyState.mjs +7 -6
  39. package/dist/ErrorBoundary.d.mts +42 -0
  40. package/dist/ErrorBoundary.d.ts +42 -0
  41. package/dist/ErrorBoundary.js +351 -0
  42. package/dist/ErrorBoundary.mjs +7 -0
  43. package/dist/Form.mjs +1 -0
  44. package/dist/HolographicCard.d.mts +55 -0
  45. package/dist/HolographicCard.d.ts +55 -0
  46. package/dist/HolographicCard.js +316 -0
  47. package/dist/HolographicCard.mjs +191 -0
  48. package/dist/IconButton.d.mts +8 -3
  49. package/dist/IconButton.d.ts +8 -3
  50. package/dist/IconButton.js +115 -98
  51. package/dist/IconButton.mjs +5 -4
  52. package/dist/ImageViewer.d.mts +23 -0
  53. package/dist/ImageViewer.d.ts +23 -0
  54. package/dist/ImageViewer.js +582 -0
  55. package/dist/ImageViewer.mjs +8 -0
  56. package/dist/Input.mjs +4 -3
  57. package/dist/LabelValue.mjs +1 -0
  58. package/dist/ListGroup.mjs +1 -0
  59. package/dist/ListItem.js +131 -117
  60. package/dist/ListItem.mjs +6 -5
  61. package/dist/MediaCard.js +54 -6
  62. package/dist/MediaCard.mjs +6 -5
  63. package/dist/MenuGroup.mjs +1 -0
  64. package/dist/MenuItem.js +91 -79
  65. package/dist/MenuItem.mjs +6 -5
  66. package/dist/MonthPicker.d.mts +10 -2
  67. package/dist/MonthPicker.d.ts +10 -2
  68. package/dist/MonthPicker.js +80 -17
  69. package/dist/MonthPicker.mjs +3 -2
  70. package/dist/PagerDots.d.mts +35 -0
  71. package/dist/PagerDots.d.ts +35 -0
  72. package/dist/PagerDots.js +392 -0
  73. package/dist/PagerDots.mjs +7 -0
  74. package/dist/Pressable.d.mts +5 -5
  75. package/dist/Pressable.d.ts +5 -5
  76. package/dist/Pressable.js +97 -86
  77. package/dist/Pressable.mjs +5 -4
  78. package/dist/PricingCard.d.mts +50 -0
  79. package/dist/PricingCard.d.ts +50 -0
  80. package/dist/PricingCard.js +636 -0
  81. package/dist/PricingCard.mjs +11 -0
  82. package/dist/Progress.mjs +3 -2
  83. package/dist/RadioGroup.js +81 -30
  84. package/dist/RadioGroup.mjs +5 -5
  85. package/dist/RetrayProvider.d.mts +2 -0
  86. package/dist/RetrayProvider.d.ts +2 -0
  87. package/dist/RetrayProvider.js +214 -0
  88. package/dist/RetrayProvider.mjs +5 -0
  89. package/dist/Select.js +51 -4
  90. package/dist/Select.mjs +5 -4
  91. package/dist/SelectableGrid.d.mts +44 -0
  92. package/dist/SelectableGrid.d.ts +44 -0
  93. package/dist/SelectableGrid.js +448 -0
  94. package/dist/SelectableGrid.mjs +9 -0
  95. package/dist/Separator.mjs +1 -0
  96. package/dist/Sheet.d.mts +13 -1
  97. package/dist/Sheet.d.ts +13 -1
  98. package/dist/Sheet.js +115 -5
  99. package/dist/Sheet.mjs +4 -2
  100. package/dist/Skeleton.d.mts +50 -0
  101. package/dist/Skeleton.d.ts +50 -0
  102. package/dist/Skeleton.js +61 -0
  103. package/dist/Skeleton.mjs +4 -2
  104. package/dist/Slider.js +51 -4
  105. package/dist/Slider.mjs +3 -2
  106. package/dist/Spinner.js +28 -7
  107. package/dist/Spinner.mjs +2 -1
  108. package/dist/Switch.js +98 -48
  109. package/dist/Switch.mjs +4 -3
  110. package/dist/TabBar.d.mts +42 -0
  111. package/dist/TabBar.d.ts +42 -0
  112. package/dist/TabBar.js +361 -0
  113. package/dist/TabBar.mjs +6 -0
  114. package/dist/Tabs.js +92 -62
  115. package/dist/Tabs.mjs +5 -4
  116. package/dist/Text.js +16 -0
  117. package/dist/Text.mjs +2 -1
  118. package/dist/Textarea.mjs +4 -3
  119. package/dist/Toast.d.mts +7 -7
  120. package/dist/Toast.d.ts +7 -7
  121. package/dist/Toast.mjs +1 -0
  122. package/dist/Toggle.d.mts +6 -3
  123. package/dist/Toggle.d.ts +6 -3
  124. package/dist/Toggle.js +135 -120
  125. package/dist/Toggle.mjs +5 -5
  126. package/dist/VirtualList.mjs +1 -0
  127. package/dist/{chunk-7H2OR44A.mjs → chunk-26BCI223.mjs} +1 -1
  128. package/dist/{chunk-CRYBX2CM.mjs → chunk-2TFTAWVJ.mjs} +44 -59
  129. package/dist/chunk-3DKJ2GIC.mjs +30 -0
  130. package/dist/{chunk-KWCPOM6W.mjs → chunk-3U4SSNWP.mjs} +32 -48
  131. package/dist/chunk-4I7D47FH.mjs +139 -0
  132. package/dist/chunk-4K625MVM.mjs +142 -0
  133. package/dist/{chunk-MN7OG7IY.mjs → chunk-6OAZJ577.mjs} +6 -4
  134. package/dist/{chunk-L7E7TVEZ.mjs → chunk-756RAKE4.mjs} +2 -2
  135. package/dist/{chunk-HSPSMN6U.mjs → chunk-7QHVVCB3.mjs} +2 -2
  136. package/dist/{chunk-URLL5JBR.mjs → chunk-A3A6KNQN.mjs} +3 -3
  137. package/dist/chunk-AJ7ZDNBT.mjs +120 -0
  138. package/dist/{chunk-FTLJOUOQ.mjs → chunk-AV4EMIRH.mjs} +25 -28
  139. package/dist/chunk-AZJF2BLK.mjs +115 -0
  140. package/dist/chunk-BNP626TY.mjs +159 -0
  141. package/dist/{chunk-5IKW3VNC.mjs → chunk-DVK4G2GT.mjs} +17 -1
  142. package/dist/{chunk-6LQYY7HC.mjs → chunk-EH745HE5.mjs} +2 -2
  143. package/dist/chunk-EJ7ZPXOH.mjs +163 -0
  144. package/dist/{chunk-RKLHUDZS.mjs → chunk-GD6KXMG5.mjs} +29 -15
  145. package/dist/{chunk-RR2VQLKE.mjs → chunk-GQYFLP3D.mjs} +14 -17
  146. package/dist/{chunk-Y6MXOREN.mjs → chunk-ID72TK46.mjs} +8 -17
  147. package/dist/{chunk-NQGVLMWG.mjs → chunk-JMOZEC77.mjs} +1 -1
  148. package/dist/{chunk-GCWOGZYL.mjs → chunk-JT7HKXRB.mjs} +39 -29
  149. package/dist/{chunk-LWG526VX.mjs → chunk-KIHCWCWL.mjs} +47 -62
  150. package/dist/chunk-LXJIIOYQ.mjs +104 -0
  151. package/dist/{chunk-SBZYEV4S.mjs → chunk-M6ZXVBTK.mjs} +5 -2
  152. package/dist/{chunk-XDMN67KV.mjs → chunk-MAC465BB.mjs} +10 -8
  153. package/dist/chunk-MBMXYJJV.mjs +36 -0
  154. package/dist/chunk-MLF3EZFW.mjs +119 -0
  155. package/dist/chunk-NA7PARID.mjs +147 -0
  156. package/dist/{chunk-QXGYKWI7.mjs → chunk-O3HA6TYM.mjs} +9 -4
  157. package/dist/{chunk-63357L2X.mjs → chunk-OB4JUQ3O.mjs} +1 -1
  158. package/dist/{chunk-AU2VDY4P.mjs → chunk-PFZTM6D5.mjs} +52 -4
  159. package/dist/chunk-QKH5ZOD5.mjs +97 -0
  160. package/dist/{chunk-KZJRQOIU.mjs → chunk-TERDKCLE.mjs} +11 -1
  161. package/dist/{chunk-U4N7WF4Z.mjs → chunk-UREA2GYY.mjs} +28 -23
  162. package/dist/{chunk-TAJ2PQ2O.mjs → chunk-VGTDN7SW.mjs} +7 -6
  163. package/dist/{chunk-URDE3EUU.mjs → chunk-VQ57HWPL.mjs} +27 -15
  164. package/dist/chunk-WBOOUHSS.mjs +62 -0
  165. package/dist/{chunk-GNGLDL6Z.mjs → chunk-WJLKJMKR.mjs} +18 -0
  166. package/dist/{chunk-YZJAFS4P.mjs → chunk-X4G6APW6.mjs} +22 -19
  167. package/dist/chunk-Y6FXYEAI.mjs +8 -0
  168. package/dist/chunk-YFZ3ELX5.mjs +16 -0
  169. package/dist/{chunk-QCNARS3X.mjs → chunk-YNROWHQJ.mjs} +1 -1
  170. package/dist/chunk-Z4BVUWW6.mjs +196 -0
  171. package/dist/{chunk-GPOUINK5.mjs → chunk-ZJKGQMYH.mjs} +10 -27
  172. package/dist/index-wt-orHUi.d.mts +85 -0
  173. package/dist/index-wt-orHUi.d.ts +85 -0
  174. package/dist/index.d.mts +59 -51
  175. package/dist/index.d.ts +59 -51
  176. package/dist/index.js +1940 -744
  177. package/dist/index.mjs +49 -39
  178. package/package.json +35 -5
  179. package/src/components/Accordion/Accordion.tsx +12 -1
  180. package/src/components/AlertBanner/AlertBanner.tsx +5 -0
  181. package/src/components/AppHeader/AppHeader.tsx +172 -0
  182. package/src/components/AppHeader/index.ts +1 -0
  183. package/src/components/Avatar/Avatar.tsx +10 -2
  184. package/src/components/Badge/Badge.tsx +8 -1
  185. package/src/components/Button/Button.tsx +20 -27
  186. package/src/components/Card/Card.tsx +12 -23
  187. package/src/components/CategoryStrip/CategoryStrip.tsx +17 -21
  188. package/src/components/Checkbox/Checkbox.tsx +26 -40
  189. package/src/components/Chip/Chip.tsx +24 -33
  190. package/src/components/CurrencyInput/CurrencyInput.tsx +10 -8
  191. package/src/components/EmptyState/EmptyState.tsx +2 -1
  192. package/src/components/ErrorBoundary/ErrorBoundary.tsx +153 -0
  193. package/src/components/ErrorBoundary/index.ts +1 -0
  194. package/src/components/HolographicCard/HolographicCard.tsx +315 -0
  195. package/src/components/HolographicCard/index.ts +1 -0
  196. package/src/components/IconButton/IconButton.tsx +19 -27
  197. package/src/components/ImageViewer/ImageViewer.tsx +290 -0
  198. package/src/components/ImageViewer/index.ts +1 -0
  199. package/src/components/ListItem/ListItem.tsx +70 -67
  200. package/src/components/MediaCard/MediaCard.tsx +8 -2
  201. package/src/components/MenuItem/MenuItem.tsx +10 -25
  202. package/src/components/MonthPicker/MonthPicker.tsx +39 -13
  203. package/src/components/MonthPicker/index.ts +1 -1
  204. package/src/components/PagerDots/PagerDots.tsx +200 -0
  205. package/src/components/PagerDots/index.ts +1 -0
  206. package/src/components/Pressable/Pressable.tsx +19 -35
  207. package/src/components/PricingCard/PricingCard.tsx +220 -0
  208. package/src/components/PricingCard/index.ts +1 -0
  209. package/src/components/RadioGroup/RadioGroup.tsx +14 -27
  210. package/src/components/RetrayProvider/RetrayProvider.tsx +59 -0
  211. package/src/components/RetrayProvider/index.ts +1 -0
  212. package/src/components/SelectableGrid/SelectableGrid.tsx +205 -0
  213. package/src/components/SelectableGrid/index.ts +1 -0
  214. package/src/components/Sheet/Sheet.tsx +65 -1
  215. package/src/components/Skeleton/Skeleton.tsx +142 -1
  216. package/src/components/Spinner/Spinner.tsx +17 -2
  217. package/src/components/Switch/Switch.tsx +30 -58
  218. package/src/components/TabBar/TabBar.tsx +169 -0
  219. package/src/components/TabBar/index.ts +1 -0
  220. package/src/components/Tabs/Tabs.tsx +23 -26
  221. package/src/components/Text/Text.tsx +2 -0
  222. package/src/components/Toggle/Toggle.tsx +35 -51
  223. package/src/fonts.ts +4 -1
  224. package/src/index.ts +23 -2
  225. package/src/utils/animations.ts +29 -1
  226. package/src/utils/fontGuard.ts +34 -0
  227. package/src/utils/haptics.ts +211 -9
  228. package/src/utils/pressable.ts +66 -0
  229. package/dist/chunk-76PFOSM2.mjs +0 -41
  230. package/dist/chunk-DITNP6PL.mjs +0 -106
  231. package/dist/chunk-JBLL7U3U.mjs +0 -64
  232. package/dist/chunk-LG4DO3DK.mjs +0 -174
  233. package/dist/chunk-RMMK64W5.mjs +0 -54
  234. package/dist/chunk-RTC3CFXF.mjs +0 -29
@@ -7,17 +7,14 @@ import {
7
7
  StyleSheet,
8
8
  ViewStyle,
9
9
  } from 'react-native'
10
- import Animated, {
11
- useAnimatedStyle,
12
- interpolateColor,
13
- } from 'react-native-reanimated'
10
+ import Animated from 'react-native-reanimated'
11
+ import { EaseView } from 'react-native-ease'
14
12
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
15
13
  import { useTheme } from '../../theme'
16
14
  import { s, vs, ms } from '../../utils/scaling'
17
15
  import { renderIcon } from '../../utils/icons'
18
16
  import { usePressScale } from '../../utils/usePressScale'
19
- import { useColorTransition } from '../../utils/useColorTransition'
20
- import { PRESS_SCALE } from '../../utils/animations'
17
+ import { COLOR_TRANSITION, PRESS_SCALE } from '../../utils/animations'
21
18
  import { RADIUS } from '../../tokens'
22
19
 
23
20
  export interface CategoryItem {
@@ -55,17 +52,6 @@ const CategoryChip = React.memo(function CategoryChip({
55
52
  const { animatedStyle: scaleStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
56
53
  pressScale: PRESS_SCALE.chip,
57
54
  })
58
- const progress = useColorTransition(selected)
59
-
60
- const surfaceStyle = useAnimatedStyle(() => ({
61
- backgroundColor: interpolateColor(progress.value, [0, 1], [colors.surface, colors.primary]),
62
- borderColor: interpolateColor(progress.value, [0, 1], [colors.border, colors.primary]),
63
- }))
64
-
65
- const textColorStyle = useAnimatedStyle(() => ({
66
- color: interpolateColor(progress.value, [0, 1], [colors.foregroundSubtle, colors.primaryForeground]),
67
- }))
68
-
69
55
  // Static color for icon — icon families take a static color prop, not animated.
70
56
  const iconColor = selected ? colors.primaryForeground : colors.foregroundSubtle
71
57
  const resolvedIcon =
@@ -85,11 +71,21 @@ const CategoryChip = React.memo(function CategoryChip({
85
71
  accessibilityLabel={item.label}
86
72
  accessibilityState={{ selected }}
87
73
  >
88
- <Animated.View style={[styles.chip, surfaceStyle]}>
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}
81
+ >
89
82
  {resolvedIcon && <View style={styles.chipIcon}>{resolvedIcon}</View>}
90
- <Animated.Text style={[styles.chipLabel, textColorStyle]} allowFontScaling={true}>
83
+ <Text
84
+ style={[styles.chipLabel, { color: selected ? colors.primaryForeground : colors.foregroundSubtle }]}
85
+ allowFontScaling={true}
86
+ >
91
87
  {item.label}
92
- </Animated.Text>
88
+ </Text>
93
89
  {item.badge !== undefined && item.badge > 0 && (
94
90
  <View style={[styles.chipBadge, { backgroundColor: colors.primary }]}>
95
91
  <Text style={[styles.chipBadgeText, { color: colors.primaryForeground }]}>
@@ -97,7 +93,7 @@ const CategoryChip = React.memo(function CategoryChip({
97
93
  </Text>
98
94
  </View>
99
95
  )}
100
- </Animated.View>
96
+ </EaseView>
101
97
  </TouchableOpacity>
102
98
  </Animated.View>
103
99
  )
@@ -1,16 +1,11 @@
1
1
  import React from 'react'
2
- import { TouchableOpacity, View, Text, StyleSheet, ViewStyle } from 'react-native'
3
- import Animated, {
4
- useAnimatedStyle,
5
- interpolateColor,
6
- withTiming,
7
- } from 'react-native-reanimated'
2
+ import { View, Text, StyleSheet, ViewStyle } from 'react-native'
3
+ import { EaseView } from 'react-native-ease'
8
4
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
9
5
  import { useTheme } from '../../theme'
10
6
  import { s, vs, ms, mvs } from '../../utils/scaling'
11
- import { usePressScale } from '../../utils/usePressScale'
12
- import { useColorTransition } from '../../utils/useColorTransition'
13
- import { PRESS_SCALE, TIMINGS, EASINGS } from '../../utils/animations'
7
+ import { COLOR_TRANSITION, OPACITY_TRANSITION } from '../../utils/animations'
8
+ import { PressableButton } from '../../utils/pressable'
14
9
 
15
10
  export interface CheckboxProps {
16
11
  checked?: boolean
@@ -30,47 +25,38 @@ export function Checkbox({
30
25
  accessibilityLabel,
31
26
  }: CheckboxProps) {
32
27
  const { colors } = useTheme()
33
- const { animatedStyle: scaleStyle, onPressIn, onPressOut } = usePressScale({
34
- pressScale: PRESS_SCALE.button,
35
- disabled,
36
- })
37
- const progress = useColorTransition(checked)
38
28
 
39
- const boxStyle = useAnimatedStyle(() => ({
40
- borderColor: interpolateColor(progress.value, [0, 1], [colors.border, colors.primary]),
41
- backgroundColor: interpolateColor(progress.value, [0, 1], ['transparent', colors.primary]),
42
- }))
43
-
44
- const checkStyle = useAnimatedStyle(() => ({
45
- opacity: withTiming(checked ? 1 : 0, { duration: TIMINGS.state.duration, easing: EASINGS.standard }),
46
- }))
29
+ const handlePress = () => {
30
+ hapticSelection()
31
+ onCheckedChange?.(!checked)
32
+ }
47
33
 
48
34
  return (
49
35
  // AUDIT FIX: opacity was applied only to the box, leaving the label at full
50
36
  // opacity when disabled — a contradictory visual signal. Now the entire row
51
37
  // dims uniformly so label and control communicate the same disabled state.
52
- <TouchableOpacity
38
+ <PressableButton
53
39
  style={[styles.row, disabled && styles.rowDisabled, style]}
54
- onPress={() => {
55
- hapticSelection()
56
- onCheckedChange?.(!checked)
57
- }}
58
- onPressIn={onPressIn}
59
- onPressOut={onPressOut}
60
- disabled={disabled}
61
- activeOpacity={1}
62
- touchSoundDisabled={true}
40
+ onPress={handlePress}
41
+ enabled={!disabled}
42
+ rippleColor="transparent"
43
+ touchSoundDisabled
63
44
  accessibilityRole="checkbox"
64
45
  accessibilityLabel={accessibilityLabel ?? label}
65
46
  accessibilityState={{ checked, disabled: !!disabled }}
66
47
  >
67
- <Animated.View style={scaleStyle}>
68
- <Animated.View style={[styles.box, boxStyle]}>
69
- <Animated.View style={checkStyle}>
70
- <View style={[styles.checkmark, { borderColor: colors.primaryForeground }]} />
71
- </Animated.View>
72
- </Animated.View>
73
- </Animated.View>
48
+ <EaseView
49
+ style={styles.box}
50
+ animate={{
51
+ borderColor: checked ? colors.primary : colors.border,
52
+ backgroundColor: checked ? colors.primary : 'transparent',
53
+ }}
54
+ transition={COLOR_TRANSITION}
55
+ >
56
+ <EaseView animate={{ opacity: checked ? 1 : 0 }} transition={OPACITY_TRANSITION}>
57
+ <View style={[styles.checkmark, { borderColor: colors.primaryForeground }]} />
58
+ </EaseView>
59
+ </EaseView>
74
60
  {label ? (
75
61
  <Text
76
62
  style={[styles.label, { color: colors.foreground }]}
@@ -79,7 +65,7 @@ export function Checkbox({
79
65
  {label}
80
66
  </Text>
81
67
  ) : null}
82
- </TouchableOpacity>
68
+ </PressableButton>
83
69
  )
84
70
  }
85
71
 
@@ -1,16 +1,12 @@
1
1
  import React from 'react'
2
- import { TouchableOpacity, View, StyleSheet, ViewStyle } from 'react-native'
3
- import Animated, {
4
- useAnimatedStyle,
5
- interpolateColor,
6
- } from 'react-native-reanimated'
2
+ import { View, Text, StyleSheet, ViewStyle } from 'react-native'
3
+ import { EaseView } from 'react-native-ease'
7
4
  import { selectionAsync as hapticSelection } from '../../utils/haptics'
8
5
  import { useTheme } from '../../theme'
9
6
  import { s, vs, ms, mvs } from '../../utils/scaling'
10
7
  import { renderIcon } from '../../utils/icons'
11
- import { usePressScale } from '../../utils/usePressScale'
12
- import { useColorTransition } from '../../utils/useColorTransition'
13
- import { PRESS_SCALE } from '../../utils/animations'
8
+ import { COLOR_TRANSITION } from '../../utils/animations'
9
+ import { PressableChip as PressableChipComponent } from '../../utils/pressable'
14
10
 
15
11
  export interface ChipProps {
16
12
  label: string
@@ -40,19 +36,6 @@ export interface ChipGroupProps {
40
36
 
41
37
  function ChipBase({ label, selected = false, onPress, icon, iconName, style, accessibilityLabel }: ChipProps) {
42
38
  const { colors } = useTheme()
43
- const { animatedStyle: scaleStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
44
- pressScale: PRESS_SCALE.chip,
45
- })
46
- const colorProgress = useColorTransition(selected)
47
-
48
- const surfaceStyle = useAnimatedStyle(() => ({
49
- backgroundColor: interpolateColor(colorProgress.value, [0, 1], [colors.surface, colors.primary]),
50
- borderColor: interpolateColor(colorProgress.value, [0, 1], [colors.border, colors.primary]),
51
- }))
52
-
53
- const textStyle = useAnimatedStyle(() => ({
54
- color: interpolateColor(colorProgress.value, [0, 1], [colors.foreground, colors.primaryForeground]),
55
- }))
56
39
 
57
40
  const handlePress = () => {
58
41
  hapticSelection()
@@ -64,25 +47,33 @@ function ChipBase({ label, selected = false, onPress, icon, iconName, style, acc
64
47
  : icon
65
48
 
66
49
  return (
67
- <Animated.View style={[styles.wrapper, scaleStyle, style]} {...hoverHandlers}>
68
- <TouchableOpacity
50
+ <View style={[styles.wrapper, style]}>
51
+ <PressableChipComponent
69
52
  onPress={handlePress}
70
- onPressIn={onPressIn}
71
- onPressOut={onPressOut}
72
- activeOpacity={1}
73
- touchSoundDisabled={true}
53
+ rippleColor="transparent"
54
+ touchSoundDisabled
74
55
  accessibilityRole="button"
75
56
  accessibilityLabel={accessibilityLabel ?? label}
76
57
  accessibilityState={{ selected }}
77
58
  >
78
- <Animated.View style={[styles.chip, surfaceStyle]}>
59
+ <EaseView
60
+ style={styles.chip}
61
+ animate={{
62
+ backgroundColor: selected ? colors.primary : colors.surface,
63
+ borderColor: selected ? colors.primary : colors.border,
64
+ }}
65
+ transition={COLOR_TRANSITION}
66
+ >
79
67
  {resolvedIcon ? <View style={styles.chipIcon}>{resolvedIcon}</View> : null}
80
- <Animated.Text style={[styles.label, textStyle]} allowFontScaling={true}>
68
+ <Text
69
+ style={[styles.label, { color: selected ? colors.primaryForeground : colors.foreground }]}
70
+ allowFontScaling={true}
71
+ >
81
72
  {label}
82
- </Animated.Text>
83
- </Animated.View>
84
- </TouchableOpacity>
85
- </Animated.View>
73
+ </Text>
74
+ </EaseView>
75
+ </PressableChipComponent>
76
+ </View>
86
77
  )
87
78
  }
88
79
 
@@ -2,15 +2,13 @@ import React from 'react'
2
2
  import { ViewStyle, TextStyle } from 'react-native'
3
3
  import { Input } from '../Input'
4
4
  import { ms, vs } from '../../utils/scaling'
5
- import { renderIcon } from '../../utils/icons'
6
- import { useTheme } from '../../theme'
7
5
 
8
6
  export interface CurrencyInputProps {
9
7
  value?: string
10
8
  onChangeText?: (formatted: string) => void
11
9
  /** Called with the parsed numeric value (no separators, no prefix). */
12
10
  onChangeValue?: (raw: number) => void
13
- /** Symbol prepended to the formatted value. Defaults to `'$'`. */
11
+ /** Currency symbol shown left of the value. Any string (`'$'`, `'€'`, `'£'`, `'COP '`). Defaults to `'$'`. */
14
12
  prefix?: string
15
13
  /** Character used to separate groups of three digits. Defaults to `'.'`. */
16
14
  thousandsSeparator?: '.' | ','
@@ -47,7 +45,6 @@ export function CurrencyInput({
47
45
  containerStyle,
48
46
  style,
49
47
  }: CurrencyInputProps) {
50
- const { colors } = useTheme()
51
48
  const handleChange = (text: string) => {
52
49
  const withoutPrefix = prefix && text.startsWith(prefix) ? text.slice(prefix.length) : text
53
50
  const formatted = formatCurrency(withoutPrefix, thousandsSeparator)
@@ -58,11 +55,15 @@ export function CurrencyInput({
58
55
  onChangeValue?.(isNaN(raw) ? 0 : raw)
59
56
  }
60
57
 
61
- const inputStyle: TextStyle = size === 'large'
58
+ const isLarge = size === 'large'
59
+ const inputStyle: TextStyle = isLarge
62
60
  ? { fontFamily: 'Sohne-Regular', fontSize: ms(36) }
63
61
  : { fontFamily: 'Sohne-Regular' }
64
62
 
65
- const dollarIcon = renderIcon('dollar-sign', size === 'large' ? 24 : 16, colors.foregroundMuted)
63
+ const prefixStyle: TextStyle = {
64
+ fontFamily: 'Sohne-Regular',
65
+ fontSize: isLarge ? ms(32) : ms(17),
66
+ }
66
67
 
67
68
  // Remove prefix from display value if present
68
69
  const displayValue = value && prefix && value.startsWith(prefix) ? value.slice(prefix.length) : value
@@ -77,9 +78,10 @@ export function CurrencyInput({
77
78
  hint={hint}
78
79
  placeholder={placeholder ?? '0'}
79
80
  editable={editable}
80
- prefix={dollarIcon}
81
+ prefix={prefix}
82
+ prefixStyle={prefixStyle}
81
83
  containerStyle={containerStyle}
82
- inputWrapperStyle={size === 'large' ? { paddingVertical: vs(16), minHeight: 72 } : undefined}
84
+ inputWrapperStyle={isLarge ? { paddingVertical: vs(16), minHeight: 72 } : undefined}
83
85
  style={[inputStyle, style]}
84
86
  />
85
87
  )
@@ -84,8 +84,10 @@ const styles = StyleSheet.create({
84
84
  borderWidth: 1,
85
85
  borderStyle: 'dashed',
86
86
  borderRadius: ms(12),
87
+ paddingBottom: vs(32),
87
88
  },
88
89
  containerCompact: {
90
+ paddingBottom: vs(20),
89
91
  },
90
92
  iconWrapper: {
91
93
  width: s(80),
@@ -125,7 +127,6 @@ const styles = StyleSheet.create({
125
127
  },
126
128
  action: {
127
129
  marginTop: vs(8),
128
- marginBottom: s(32),
129
130
  paddingHorizontal: s(32),
130
131
  },
131
132
  })
@@ -0,0 +1,153 @@
1
+ import React from 'react'
2
+ import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
3
+ import { useTheme } from '../../theme'
4
+ import { renderIcon } from '../../utils/icons'
5
+ import { s, vs, ms, mvs } from '../../utils/scaling'
6
+ import { RADIUS } from '../../tokens'
7
+ import { impactLight } from '../../utils/haptics'
8
+
9
+ export interface ErrorFallbackProps {
10
+ error: Error
11
+ /** Reset the boundary and attempt to re-render children. */
12
+ reset: () => void
13
+ }
14
+
15
+ export interface ErrorBoundaryProps {
16
+ children: React.ReactNode
17
+ /**
18
+ * Custom fallback. Either a React node, or a render function receiving the
19
+ * caught error and a `reset` callback. Defaults to a themed error card.
20
+ */
21
+ fallback?: React.ReactNode | ((props: ErrorFallbackProps) => React.ReactNode)
22
+ /** Title shown by the default fallback. */
23
+ title?: string
24
+ /** Body message shown by the default fallback. */
25
+ message?: string
26
+ /** Called whenever an error is caught — wire your crash reporter here. */
27
+ onError?: (error: Error, info: React.ErrorInfo) => void
28
+ }
29
+
30
+ interface ErrorBoundaryState {
31
+ error: Error | null
32
+ }
33
+
34
+ /** Default themed fallback — uses `useTheme`, so it must be a component (not inline). */
35
+ function DefaultErrorFallback({
36
+ error,
37
+ reset,
38
+ title = 'Something went wrong',
39
+ message,
40
+ }: ErrorFallbackProps & { title?: string; message?: string }) {
41
+ const { colors } = useTheme()
42
+ return (
43
+ <View style={[styles.container, { backgroundColor: colors.background }]} accessibilityRole="alert">
44
+ <View style={[styles.iconCircle, { backgroundColor: colors.destructiveTint }]}>
45
+ {renderIcon('alert-triangle', ms(28), colors.destructive)}
46
+ </View>
47
+ <Text style={[styles.title, { color: colors.foreground }]} allowFontScaling={true}>
48
+ {title}
49
+ </Text>
50
+ <Text style={[styles.message, { color: colors.foregroundMuted }]} allowFontScaling={true}>
51
+ {message ?? error.message ?? 'An unexpected error occurred.'}
52
+ </Text>
53
+ <TouchableOpacity
54
+ style={[styles.button, { backgroundColor: colors.primary }]}
55
+ onPress={() => {
56
+ impactLight()
57
+ reset()
58
+ }}
59
+ activeOpacity={0.85}
60
+ touchSoundDisabled={true}
61
+ accessibilityRole="button"
62
+ accessibilityLabel="Try again"
63
+ >
64
+ <Text style={[styles.buttonLabel, { color: colors.primaryForeground }]} allowFontScaling={true}>
65
+ Try again
66
+ </Text>
67
+ </TouchableOpacity>
68
+ </View>
69
+ )
70
+ }
71
+
72
+ /**
73
+ * Catches render-time errors in its subtree and shows a themed fallback instead
74
+ * of unmounting the whole app. Wrap screens or risky subtrees.
75
+ *
76
+ * @example
77
+ * <ErrorBoundary onError={reportCrash}>
78
+ * <DocumentViewer />
79
+ * </ErrorBoundary>
80
+ */
81
+ export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
82
+ state: ErrorBoundaryState = { error: null }
83
+
84
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
85
+ return { error }
86
+ }
87
+
88
+ componentDidCatch(error: Error, info: React.ErrorInfo) {
89
+ this.props.onError?.(error, info)
90
+ }
91
+
92
+ reset = () => {
93
+ this.setState({ error: null })
94
+ }
95
+
96
+ render() {
97
+ const { error } = this.state
98
+ if (error) {
99
+ const { fallback, title, message } = this.props
100
+ if (typeof fallback === 'function') {
101
+ return fallback({ error, reset: this.reset })
102
+ }
103
+ if (fallback !== undefined) {
104
+ return fallback
105
+ }
106
+ return <DefaultErrorFallback error={error} reset={this.reset} title={title} message={message} />
107
+ }
108
+ return this.props.children
109
+ }
110
+ }
111
+
112
+ const styles = StyleSheet.create({
113
+ container: {
114
+ flex: 1,
115
+ alignItems: 'center',
116
+ justifyContent: 'center',
117
+ paddingHorizontal: s(32),
118
+ gap: vs(12),
119
+ },
120
+ iconCircle: {
121
+ width: s(64),
122
+ height: s(64),
123
+ borderRadius: RADIUS.full,
124
+ alignItems: 'center',
125
+ justifyContent: 'center',
126
+ marginBottom: vs(4),
127
+ },
128
+ title: {
129
+ fontFamily: 'Sohne-SemiBold',
130
+ fontSize: ms(18),
131
+ lineHeight: mvs(24),
132
+ textAlign: 'center',
133
+ },
134
+ message: {
135
+ fontFamily: 'Sohne-Regular',
136
+ fontSize: ms(14),
137
+ lineHeight: mvs(20),
138
+ textAlign: 'center',
139
+ },
140
+ button: {
141
+ marginTop: vs(8),
142
+ paddingHorizontal: s(20),
143
+ paddingVertical: vs(10),
144
+ borderRadius: RADIUS.md,
145
+ minHeight: vs(44),
146
+ alignItems: 'center',
147
+ justifyContent: 'center',
148
+ },
149
+ buttonLabel: {
150
+ fontFamily: 'Sohne-Medium',
151
+ fontSize: ms(15),
152
+ },
153
+ })
@@ -0,0 +1 @@
1
+ export * from './ErrorBoundary'