@retray-dev/ui-kit 1.6.0 → 1.7.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.
@@ -20,6 +20,7 @@ export interface ToastItem {
20
20
  title?: string
21
21
  description?: string
22
22
  variant?: ToastVariant
23
+ icon?: React.ReactNode
23
24
  /** Auto-dismiss delay in milliseconds. Defaults to `3000`. */
24
25
  duration?: number
25
26
  }
@@ -97,15 +98,22 @@ function ToastNotification({ item, onDismiss }: { item: ToastItem; onDismiss: ()
97
98
  success: colors.successForeground,
98
99
  }[item.variant ?? 'default']
99
100
 
101
+ const leftIcon = item.icon ?? (
102
+ <Text style={[styles.defaultIcon, { color: textColor }]}>
103
+ {item.variant === 'success' ? '✓' : item.variant === 'destructive' ? '✖' : 'ℹ'}
104
+ </Text>
105
+ )
106
+
100
107
  return (
101
108
  <GestureDetector gesture={panGesture}>
102
109
  <Animated.View style={[styles.toast, { backgroundColor: bgColor }, animatedStyle]}>
110
+ <View style={styles.leftIconContainer}>{leftIcon}</View>
103
111
  <View style={styles.toastContent}>
104
112
  {item.title ? (
105
113
  <Text style={[styles.toastTitle, { color: textColor }]}>{item.title}</Text>
106
114
  ) : null}
107
115
  {item.description ? (
108
- <Text style={[styles.toastDescription, { color: textColor, opacity: 0.85 }]}>
116
+ <Text style={[styles.toastDescription, { color: textColor, opacity: 0.85 }]}>
109
117
  {item.description}
110
118
  </Text>
111
119
  ) : null}
@@ -170,9 +178,9 @@ const styles = StyleSheet.create({
170
178
  toast: {
171
179
  flexDirection: 'row',
172
180
  alignItems: 'center',
173
- borderRadius: 12,
174
- paddingHorizontal: 16,
175
- paddingVertical: 12,
181
+ borderRadius: 16,
182
+ paddingHorizontal: 20,
183
+ paddingVertical: 14,
176
184
  shadowColor: '#000',
177
185
  shadowOffset: { width: 0, height: 4 },
178
186
  shadowOpacity: 0.15,
@@ -183,18 +191,28 @@ const styles = StyleSheet.create({
183
191
  flex: 1,
184
192
  gap: 4,
185
193
  },
194
+ leftIconContainer: {
195
+ width: 36,
196
+ alignItems: 'center',
197
+ justifyContent: 'center',
198
+ marginRight: 8,
199
+ },
200
+ defaultIcon: {
201
+ fontSize: 22,
202
+ fontWeight: '700',
203
+ },
186
204
  toastTitle: {
187
- fontSize: 14,
205
+ fontSize: 15,
188
206
  fontWeight: '600',
189
207
  },
190
208
  toastDescription: {
191
- fontSize: 13,
209
+ fontSize: 14,
192
210
  },
193
211
  dismissButton: {
194
212
  padding: 12,
195
213
  marginLeft: 4,
196
214
  },
197
215
  dismissIcon: {
198
- fontSize: 12,
216
+ fontSize: 14,
199
217
  },
200
218
  })
@@ -1,5 +1,5 @@
1
- import React, { useRef } from 'react'
2
- import { TouchableOpacity, Animated, Text, StyleSheet, TouchableOpacityProps, ViewStyle } from 'react-native'
1
+ import React, { useRef, useEffect } from 'react'
2
+ import { TouchableOpacity, Animated, Text, StyleSheet, TouchableOpacityProps, ViewStyle, View, Easing } from 'react-native'
3
3
  import * as Haptics from 'expo-haptics'
4
4
  import { useTheme } from '../../theme'
5
5
 
@@ -12,7 +12,10 @@ export interface ToggleProps extends TouchableOpacityProps {
12
12
  variant?: ToggleVariant
13
13
  size?: ToggleSize
14
14
  label?: string
15
- icon?: React.ReactNode
15
+ /** Icon to show when not pressed */
16
+ icon?: React.ReactNode | ((pressed: boolean) => React.ReactNode)
17
+ /** Icon to show when pressed/active. If omitted, a default check mark is used. */
18
+ activeIcon?: React.ReactNode | ((pressed: boolean) => React.ReactNode)
16
19
  }
17
20
 
18
21
  const sizeStyles: Record<ToggleSize, ViewStyle> = {
@@ -28,12 +31,24 @@ export function Toggle({
28
31
  size = 'md',
29
32
  label,
30
33
  icon,
34
+ activeIcon,
31
35
  disabled,
32
36
  style,
33
37
  ...props
34
38
  }: ToggleProps) {
35
39
  const { colors } = useTheme()
36
40
  const scale = useRef(new Animated.Value(1)).current
41
+ // 0 = unpressed, 1 = pressed — used to interpolate colors (JS thread)
42
+ const pressAnim = useRef(new Animated.Value(pressed ? 1 : 0)).current
43
+
44
+ useEffect(() => {
45
+ Animated.timing(pressAnim, {
46
+ toValue: pressed ? 1 : 0,
47
+ duration: 150,
48
+ easing: Easing.out(Easing.ease),
49
+ useNativeDriver: false,
50
+ }).start()
51
+ }, [pressed, pressAnim])
37
52
 
38
53
  const handlePressIn = () => {
39
54
  if (disabled) return
@@ -44,32 +59,70 @@ export function Toggle({
44
59
  Animated.spring(scale, { toValue: 1, useNativeDriver: true, speed: 40, bounciness: 4 }).start()
45
60
  }
46
61
 
47
- const containerStyle: ViewStyle = pressed
48
- ? { backgroundColor: colors.accent }
49
- : variant === 'outline'
50
- ? { backgroundColor: 'transparent', borderWidth: 1, borderColor: colors.border }
51
- : { backgroundColor: 'transparent' }
62
+ // Keep borderWidth constant at 2 to prevent layout jumps when pressing.
63
+ // Animate borderColor and backgroundColor instead.
64
+ const borderColor = pressAnim.interpolate({
65
+ inputRange: [0, 1],
66
+ outputRange: [variant === 'outline' ? colors.border : 'transparent', colors.primary],
67
+ })
68
+
69
+ const backgroundColor = pressAnim.interpolate({
70
+ inputRange: [0, 1],
71
+ outputRange: ['transparent', colors.accent],
72
+ })
73
+
74
+ const textColor = pressAnim.interpolate({
75
+ inputRange: [0, 1],
76
+ outputRange: [colors.foreground, colors.primary],
77
+ })
78
+
79
+ const LeftIcon = () => {
80
+ const renderProp = (prop?: any) => {
81
+ if (!prop) return null
82
+ if (typeof prop === 'function') return prop(pressed)
83
+ return prop
84
+ }
52
85
 
53
- const textColor = pressed ? colors.accentForeground : colors.foreground
86
+ if (!pressed) return renderProp(icon)
87
+
88
+ const active = renderProp(activeIcon)
89
+ if (active) return <>{active}</>
90
+
91
+ return (
92
+ <View style={[styles.checkContainer, { borderColor: colors.primary }]}>
93
+ <Text style={[styles.checkMark, { color: colors.primary }]}>✓</Text>
94
+ </View>
95
+ )
96
+ }
54
97
 
55
98
  return (
56
99
  <Animated.View style={{ transform: [{ scale }] }}>
57
- <TouchableOpacity
58
- style={[styles.base, containerStyle, sizeStyles[size], disabled && styles.disabled, style]}
59
- onPress={() => {
60
- Haptics.selectionAsync()
61
- onPressedChange?.(!pressed)
62
- }}
63
- onPressIn={handlePressIn}
64
- onPressOut={handlePressOut}
65
- disabled={disabled}
66
- activeOpacity={1}
67
- touchSoundDisabled={true}
68
- {...props}
100
+ <Animated.View
101
+ style={[
102
+ styles.base,
103
+ sizeStyles[size],
104
+ { borderColor, backgroundColor, borderWidth: 2 },
105
+ disabled && styles.disabled,
106
+ style,
107
+ ]}
69
108
  >
70
- {icon}
71
- {label ? <Text style={[styles.label, { color: textColor }]}>{label}</Text> : null}
72
- </TouchableOpacity>
109
+ <TouchableOpacity
110
+ style={styles.touchable}
111
+ onPress={() => {
112
+ Haptics.selectionAsync()
113
+ onPressedChange?.(!pressed)
114
+ }}
115
+ onPressIn={handlePressIn}
116
+ onPressOut={handlePressOut}
117
+ disabled={disabled}
118
+ activeOpacity={1}
119
+ touchSoundDisabled={true}
120
+ {...props}
121
+ >
122
+ <LeftIcon />
123
+ {label ? <Animated.Text style={[styles.label, { color: textColor }]}>{label}</Animated.Text> : null}
124
+ </TouchableOpacity>
125
+ </Animated.View>
73
126
  </Animated.View>
74
127
  )
75
128
  }
@@ -77,10 +130,14 @@ export function Toggle({
77
130
  const styles = StyleSheet.create({
78
131
  base: {
79
132
  borderRadius: 8,
133
+ overflow: 'hidden',
134
+ },
135
+ touchable: {
80
136
  flexDirection: 'row',
81
137
  alignItems: 'center',
82
138
  justifyContent: 'center',
83
139
  gap: 8,
140
+ flex: 1,
84
141
  },
85
142
  disabled: {
86
143
  opacity: 0.45,
@@ -89,4 +146,16 @@ const styles = StyleSheet.create({
89
146
  fontSize: 14,
90
147
  fontWeight: '500',
91
148
  },
149
+ checkContainer: {
150
+ width: 24,
151
+ height: 24,
152
+ borderRadius: 12,
153
+ borderWidth: 2,
154
+ alignItems: 'center',
155
+ justifyContent: 'center',
156
+ },
157
+ checkMark: {
158
+ fontSize: 14,
159
+ fontWeight: '700',
160
+ },
92
161
  })
package/src/index.ts CHANGED
@@ -28,3 +28,5 @@ export * from './components/Sheet'
28
28
  export * from './components/Select'
29
29
  export * from './components/Toast'
30
30
  export * from './components/CurrencyInput'
31
+ export * from './components/CurrencyDisplay'
32
+ export * from './components/CurrencyInputLarge'