@retray-dev/ui-kit 5.4.0 → 6.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.
- package/COMPONENTS.md +261 -126
- package/dist/index.d.mts +147 -44
- package/dist/index.d.ts +147 -44
- package/dist/index.js +429 -305
- package/dist/index.mjs +419 -303
- package/package.json +7 -2
- package/src/components/Accordion/Accordion.tsx +6 -2
- package/src/components/AlertBanner/AlertBanner.tsx +16 -33
- package/src/components/Button/Button.tsx +18 -6
- package/src/components/Card/Card.tsx +12 -9
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +4 -4
- package/src/components/CurrencyDisplay/CurrencyDisplay.tsx +14 -3
- package/src/components/EmptyState/EmptyState.tsx +21 -6
- package/src/components/Input/Input.tsx +21 -10
- package/src/components/ListItem/ListItem.tsx +14 -8
- package/src/components/MediaCard/MediaCard.tsx +1 -0
- package/src/components/MenuItem/MenuItem.tsx +206 -0
- package/src/components/MenuItem/index.ts +2 -0
- package/src/components/Select/Select.tsx +1 -1
- package/src/components/Separator/Separator.tsx +2 -0
- package/src/components/Sheet/Sheet.tsx +164 -51
- package/src/components/Sheet/index.ts +1 -1
- package/src/components/Tabs/Tabs.tsx +4 -4
- package/src/components/Toast/Toast.tsx +41 -267
- package/src/components/Toast/index.ts +1 -2
- package/src/components/Toggle/Toggle.tsx +2 -2
- package/src/index.ts +1 -0
- package/src/theme/colors.ts +3 -0
- package/src/theme/types.ts +11 -0
- package/src/tokens.ts +3 -3
|
@@ -1,281 +1,55 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import {
|
|
3
|
-
import { FontAwesome5, MaterialIcons, Entypo, AntDesign } from '@expo/vector-icons'
|
|
4
|
-
import Animated, {
|
|
5
|
-
useSharedValue,
|
|
6
|
-
useAnimatedStyle,
|
|
7
|
-
withSpring,
|
|
8
|
-
withTiming,
|
|
9
|
-
Easing,
|
|
10
|
-
} from 'react-native-reanimated'
|
|
11
|
-
import { scheduleOnRN } from 'react-native-worklets'
|
|
12
|
-
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Toaster, toast as sonnerToast } from 'sonner-native'
|
|
13
3
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
14
|
-
import { notificationSuccess, notificationError, impactLight } from '../../utils/haptics'
|
|
15
4
|
import { useTheme } from '../../theme'
|
|
16
5
|
import { s, vs, ms } from '../../utils/scaling'
|
|
17
|
-
import { renderIcon } from '../../utils/icons'
|
|
18
6
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export interface ToastAction {
|
|
22
|
-
label: string
|
|
23
|
-
onPress: () => void
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface ToastItem {
|
|
27
|
-
id: string
|
|
28
|
-
title?: string
|
|
29
|
-
description?: string
|
|
30
|
-
variant?: ToastVariant
|
|
31
|
-
icon?: React.ReactNode
|
|
32
|
-
iconName?: string
|
|
33
|
-
iconColor?: string
|
|
34
|
-
/** Auto-dismiss delay in milliseconds. Defaults to `3000`. */
|
|
35
|
-
duration?: number
|
|
36
|
-
/** Optional inline action button rendered at the end of the toast. */
|
|
37
|
-
action?: ToastAction
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface ToastContextValue {
|
|
41
|
-
toast: (item: Omit<ToastItem, 'id'>) => void
|
|
42
|
-
dismiss: (id: string) => void
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const ToastContext = createContext<ToastContextValue>({
|
|
46
|
-
toast: () => {},
|
|
47
|
-
dismiss: () => {},
|
|
48
|
-
})
|
|
7
|
+
// Direct function API — no hook required
|
|
8
|
+
export { sonnerToast as toast }
|
|
49
9
|
|
|
10
|
+
// useToast — backward-compat wrapper
|
|
50
11
|
export function useToast() {
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const VELOCITY_THRESHOLD = 800
|
|
56
|
-
|
|
57
|
-
function ToastNotification({ item, onDismiss }: { item: ToastItem; onDismiss: () => void }) {
|
|
58
|
-
const { colors } = useTheme()
|
|
59
|
-
const translateY = useSharedValue(-80)
|
|
60
|
-
const translateX = useSharedValue(0)
|
|
61
|
-
const opacity = useSharedValue(0)
|
|
62
|
-
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
translateY.value = withTiming(0, { duration: 120, easing: Easing.out(Easing.exp) })
|
|
65
|
-
opacity.value = withTiming(1, { duration: 100 })
|
|
66
|
-
|
|
67
|
-
const timer = setTimeout(() => {
|
|
68
|
-
translateY.value = withTiming(-80, { duration: 200 })
|
|
69
|
-
opacity.value = withTiming(0, { duration: 200 }, (done) => {
|
|
70
|
-
if (done) scheduleOnRN(onDismiss)
|
|
71
|
-
})
|
|
72
|
-
}, item.duration ?? 3000)
|
|
73
|
-
|
|
74
|
-
return () => clearTimeout(timer)
|
|
75
|
-
}, [])
|
|
76
|
-
|
|
77
|
-
const panGesture = Gesture.Pan()
|
|
78
|
-
.onUpdate((e) => {
|
|
79
|
-
translateX.value = e.translationX
|
|
80
|
-
})
|
|
81
|
-
.onEnd((e) => {
|
|
82
|
-
const shouldDismiss =
|
|
83
|
-
Math.abs(translateX.value) > SWIPE_THRESHOLD ||
|
|
84
|
-
Math.abs(e.velocityX) > VELOCITY_THRESHOLD
|
|
85
|
-
if (shouldDismiss) {
|
|
86
|
-
const direction = translateX.value > 0 ? 1 : -1
|
|
87
|
-
translateX.value = withTiming(direction * 500, { duration: 200 }, (done) => {
|
|
88
|
-
if (done) scheduleOnRN(onDismiss)
|
|
89
|
-
})
|
|
90
|
-
opacity.value = withTiming(0, { duration: 150 })
|
|
91
|
-
} else {
|
|
92
|
-
translateX.value = withSpring(0, { damping: 20, stiffness: 300 })
|
|
93
|
-
}
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
const animatedStyle = useAnimatedStyle(() => ({
|
|
97
|
-
opacity: opacity.value,
|
|
98
|
-
transform: [{ translateY: translateY.value }, { translateX: translateX.value }],
|
|
99
|
-
}))
|
|
100
|
-
|
|
101
|
-
const variant = item.variant ?? 'default'
|
|
102
|
-
|
|
103
|
-
const bgColor = {
|
|
104
|
-
default: colors.card,
|
|
105
|
-
destructive: colors.destructiveTint,
|
|
106
|
-
success: colors.successTint,
|
|
107
|
-
warning: colors.warningTint,
|
|
108
|
-
}[variant]
|
|
109
|
-
|
|
110
|
-
const borderColor = {
|
|
111
|
-
default: colors.border,
|
|
112
|
-
destructive: colors.destructiveBorder,
|
|
113
|
-
success: colors.successBorder,
|
|
114
|
-
warning: colors.warningBorder,
|
|
115
|
-
}[variant]
|
|
116
|
-
|
|
117
|
-
const accentColor = {
|
|
118
|
-
default: colors.primary,
|
|
119
|
-
destructive: colors.destructive,
|
|
120
|
-
success: colors.success,
|
|
121
|
-
warning: colors.warning,
|
|
122
|
-
}[variant]
|
|
123
|
-
|
|
124
|
-
const titleColor = variant === 'default' ? colors.foreground : accentColor
|
|
125
|
-
const descColor = variant === 'default' ? colors.foregroundMuted : accentColor
|
|
126
|
-
|
|
127
|
-
const defaultIcon =
|
|
128
|
-
variant === 'success' ? (
|
|
129
|
-
<FontAwesome5 name="check-circle" size={16} color={accentColor} />
|
|
130
|
-
) : variant === 'destructive' ? (
|
|
131
|
-
<AntDesign name="exclamation-circle" size={16} color={accentColor} />
|
|
132
|
-
) : variant === 'warning' ? (
|
|
133
|
-
<MaterialIcons name="warning-amber" size={17} color={accentColor} />
|
|
134
|
-
) : (
|
|
135
|
-
<Entypo name="info-with-circle" size={16} color={accentColor} />
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
const leftIcon: React.ReactNode = item.iconName
|
|
139
|
-
? renderIcon(item.iconName, 16, item.iconColor ?? accentColor)
|
|
140
|
-
: item.icon ?? defaultIcon
|
|
141
|
-
|
|
142
|
-
return (
|
|
143
|
-
<GestureDetector gesture={panGesture}>
|
|
144
|
-
<Animated.View style={[styles.toast, { backgroundColor: bgColor, borderColor }, animatedStyle]}>
|
|
145
|
-
<View style={styles.leftIconContainer}>{leftIcon}</View>
|
|
146
|
-
<View style={styles.toastContent}>
|
|
147
|
-
{item.title ? (
|
|
148
|
-
<Text style={[styles.toastTitle, { color: titleColor }]} allowFontScaling={true}>{item.title}</Text>
|
|
149
|
-
) : null}
|
|
150
|
-
{item.description ? (
|
|
151
|
-
<Text style={[styles.toastDescription, { color: descColor }]} allowFontScaling={true}>
|
|
152
|
-
{item.description}
|
|
153
|
-
</Text>
|
|
154
|
-
) : null}
|
|
155
|
-
</View>
|
|
156
|
-
{item.action && (
|
|
157
|
-
<TouchableOpacity
|
|
158
|
-
onPress={() => { item.action!.onPress(); onDismiss() }}
|
|
159
|
-
style={styles.actionButton}
|
|
160
|
-
touchSoundDisabled={true}
|
|
161
|
-
>
|
|
162
|
-
<Text style={[styles.actionLabel, { color: accentColor }]} allowFontScaling={true}>
|
|
163
|
-
{item.action.label}
|
|
164
|
-
</Text>
|
|
165
|
-
</TouchableOpacity>
|
|
166
|
-
)}
|
|
167
|
-
<TouchableOpacity onPress={onDismiss} style={styles.dismissButton} touchSoundDisabled={true}>
|
|
168
|
-
<AntDesign name="close-circle" size={16} color={descColor} />
|
|
169
|
-
</TouchableOpacity>
|
|
170
|
-
</Animated.View>
|
|
171
|
-
</GestureDetector>
|
|
172
|
-
)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Must wrap the app root alongside ThemeProvider.
|
|
177
|
-
* Renders toasts in an absolute overlay at the top of the screen.
|
|
178
|
-
* Use `useToast()` anywhere inside to trigger toasts.
|
|
179
|
-
*/
|
|
180
|
-
export interface ToastProviderProps {
|
|
181
|
-
children: React.ReactNode
|
|
12
|
+
return {
|
|
13
|
+
toast: sonnerToast,
|
|
14
|
+
dismiss: sonnerToast.dismiss,
|
|
15
|
+
}
|
|
182
16
|
}
|
|
183
17
|
|
|
184
|
-
|
|
185
|
-
|
|
18
|
+
// ToastProvider — wraps children + renders the Toaster
|
|
19
|
+
export function ToastProvider({ children }: { children: React.ReactNode }) {
|
|
20
|
+
const { colorScheme } = useTheme()
|
|
186
21
|
const insets = useSafeAreaInsets()
|
|
187
22
|
|
|
188
|
-
const toast = useCallback((item: Omit<ToastItem, 'id'>) => {
|
|
189
|
-
const id = Math.random().toString(36).slice(2)
|
|
190
|
-
if (item.variant === 'success') {
|
|
191
|
-
notificationSuccess()
|
|
192
|
-
} else if (item.variant === 'destructive') {
|
|
193
|
-
notificationError()
|
|
194
|
-
} else if (item.variant === 'warning') {
|
|
195
|
-
notificationError()
|
|
196
|
-
} else {
|
|
197
|
-
impactLight()
|
|
198
|
-
}
|
|
199
|
-
setToasts((prev) => [{ ...item, id }, ...prev].slice(0, 3))
|
|
200
|
-
}, [])
|
|
201
|
-
|
|
202
|
-
const dismiss = useCallback((id: string) => {
|
|
203
|
-
setToasts((prev) => prev.filter((t) => t.id !== id))
|
|
204
|
-
}, [])
|
|
205
|
-
|
|
206
23
|
return (
|
|
207
|
-
|
|
24
|
+
<>
|
|
208
25
|
{children}
|
|
209
|
-
<
|
|
210
|
-
{
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
26
|
+
<Toaster
|
|
27
|
+
theme={colorScheme}
|
|
28
|
+
position="top-center"
|
|
29
|
+
richColors={false}
|
|
30
|
+
gap={vs(8)}
|
|
31
|
+
offset={insets.top + vs(8)}
|
|
32
|
+
visibleToasts={3}
|
|
33
|
+
closeButton={false}
|
|
34
|
+
swipeToDismissDirection="up"
|
|
35
|
+
duration={4000}
|
|
36
|
+
toastOptions={{
|
|
37
|
+
style: {
|
|
38
|
+
borderRadius: ms(12),
|
|
39
|
+
paddingHorizontal: s(12),
|
|
40
|
+
paddingVertical: vs(10),
|
|
41
|
+
},
|
|
42
|
+
titleStyle: {
|
|
43
|
+
fontFamily: 'Poppins-Medium',
|
|
44
|
+
fontSize: ms(13),
|
|
45
|
+
},
|
|
46
|
+
descriptionStyle: {
|
|
47
|
+
fontFamily: 'Poppins-Regular',
|
|
48
|
+
fontSize: ms(12),
|
|
49
|
+
opacity: 0.85,
|
|
50
|
+
},
|
|
51
|
+
}}
|
|
52
|
+
/>
|
|
53
|
+
</>
|
|
215
54
|
)
|
|
216
55
|
}
|
|
217
|
-
|
|
218
|
-
const styles = StyleSheet.create({
|
|
219
|
-
container: {
|
|
220
|
-
position: 'absolute',
|
|
221
|
-
left: s(16),
|
|
222
|
-
right: s(16),
|
|
223
|
-
gap: vs(8),
|
|
224
|
-
zIndex: 9999,
|
|
225
|
-
},
|
|
226
|
-
containerWeb: {
|
|
227
|
-
left: undefined,
|
|
228
|
-
right: undefined,
|
|
229
|
-
alignSelf: 'center',
|
|
230
|
-
width: s(400),
|
|
231
|
-
},
|
|
232
|
-
toast: {
|
|
233
|
-
flexDirection: 'row',
|
|
234
|
-
alignItems: 'flex-start',
|
|
235
|
-
borderRadius: ms(10),
|
|
236
|
-
borderWidth: 0.5,
|
|
237
|
-
paddingHorizontal: s(12),
|
|
238
|
-
paddingVertical: vs(10),
|
|
239
|
-
shadowColor: '#000',
|
|
240
|
-
shadowOffset: { width: 0, height: 2 },
|
|
241
|
-
shadowOpacity: 0.06,
|
|
242
|
-
shadowRadius: 4,
|
|
243
|
-
elevation: 3,
|
|
244
|
-
},
|
|
245
|
-
toastContent: {
|
|
246
|
-
flex: 1,
|
|
247
|
-
gap: vs(2),
|
|
248
|
-
},
|
|
249
|
-
leftIconContainer: {
|
|
250
|
-
marginTop: vs(1),
|
|
251
|
-
alignItems: 'center',
|
|
252
|
-
justifyContent: 'center',
|
|
253
|
-
marginRight: s(10),
|
|
254
|
-
},
|
|
255
|
-
toastTitle: {
|
|
256
|
-
fontFamily: 'Poppins-Medium',
|
|
257
|
-
fontSize: ms(13),
|
|
258
|
-
lineHeight: ms(18),
|
|
259
|
-
},
|
|
260
|
-
toastDescription: {
|
|
261
|
-
fontFamily: 'Poppins-Regular',
|
|
262
|
-
fontSize: ms(12),
|
|
263
|
-
lineHeight: ms(17),
|
|
264
|
-
opacity: 0.85,
|
|
265
|
-
},
|
|
266
|
-
actionButton: {
|
|
267
|
-
paddingHorizontal: s(8),
|
|
268
|
-
paddingVertical: vs(4),
|
|
269
|
-
marginLeft: s(4),
|
|
270
|
-
},
|
|
271
|
-
actionLabel: {
|
|
272
|
-
fontFamily: 'Poppins-Medium',
|
|
273
|
-
fontSize: ms(12),
|
|
274
|
-
textDecorationLine: 'underline',
|
|
275
|
-
},
|
|
276
|
-
dismissButton: {
|
|
277
|
-
padding: s(6),
|
|
278
|
-
marginLeft: s(2),
|
|
279
|
-
marginTop: vs(0),
|
|
280
|
-
},
|
|
281
|
-
})
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
export { ToastProvider, useToast } from './Toast'
|
|
2
|
-
export type { ToastProviderProps, ToastItem, ToastVariant } from './Toast'
|
|
1
|
+
export { ToastProvider, useToast, toast } from './Toast'
|
|
@@ -77,11 +77,11 @@ export function Toggle({
|
|
|
77
77
|
|
|
78
78
|
const handlePressIn = () => {
|
|
79
79
|
if (disabled) return
|
|
80
|
-
Animated.spring(scale, { toValue: 0.95, useNativeDriver: nativeDriver,
|
|
80
|
+
Animated.spring(scale, { toValue: 0.95, useNativeDriver: nativeDriver, stiffness: 600, damping: 35, mass: 0.8 }).start()
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
const handlePressOut = () => {
|
|
84
|
-
Animated.spring(scale, { toValue: 1, useNativeDriver: nativeDriver,
|
|
84
|
+
Animated.spring(scale, { toValue: 1, useNativeDriver: nativeDriver, stiffness: 280, damping: 22, mass: 0.8 }).start()
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
// Keep borderWidth constant at 2 to prevent layout jumps when pressing.
|
package/src/index.ts
CHANGED
|
@@ -34,6 +34,7 @@ export * from './components/CurrencyDisplay'
|
|
|
34
34
|
// CurrencyInputLarge is deprecated — use <CurrencyInput size="large" /> instead
|
|
35
35
|
export { CurrencyInput as CurrencyInputLarge } from './components/CurrencyInput'
|
|
36
36
|
export * from './components/ListItem'
|
|
37
|
+
export * from './components/MenuItem'
|
|
37
38
|
export * from './components/Chip'
|
|
38
39
|
export * from './components/ConfirmDialog'
|
|
39
40
|
export * from './components/LabelValue'
|
package/src/theme/colors.ts
CHANGED
|
@@ -89,6 +89,9 @@ export function deriveColors(t: ThemeColors, scheme: 'light' | 'dark'): Resolved
|
|
|
89
89
|
successBorder,
|
|
90
90
|
warningTint,
|
|
91
91
|
warningBorder,
|
|
92
|
+
overlay: t.overlay ?? 'rgba(0,0,0,0.45)',
|
|
93
|
+
accentResolved: t.accent ?? t.primary,
|
|
94
|
+
accentForegroundResolved: t.accentForeground ?? t.primaryForeground,
|
|
92
95
|
ring: t.primary, // focus ring always = primary
|
|
93
96
|
input: t.border, // input border always = border
|
|
94
97
|
}
|
package/src/theme/types.ts
CHANGED
|
@@ -14,6 +14,12 @@ export type ThemeColors = {
|
|
|
14
14
|
successForeground: string
|
|
15
15
|
warning: string
|
|
16
16
|
warningForeground: string
|
|
17
|
+
/** Backdrop/overlay color. Default: 'rgba(0,0,0,0.45)' */
|
|
18
|
+
overlay?: string
|
|
19
|
+
/** Color accent (e.g. Airbnb coral). Default: same as primary */
|
|
20
|
+
accent?: string
|
|
21
|
+
/** Text color on accent background. Default: same as primaryForeground */
|
|
22
|
+
accentForeground?: string
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
// Full resolved palette — what components actually consume via useTheme().
|
|
@@ -35,6 +41,11 @@ export type ResolvedColors = ThemeColors & {
|
|
|
35
41
|
warningTint: string
|
|
36
42
|
warningBorder: string
|
|
37
43
|
|
|
44
|
+
// Derived optional tokens (always present in ResolvedColors)
|
|
45
|
+
overlay: string // backdrop — default 'rgba(0,0,0,0.45)'
|
|
46
|
+
accentResolved: string // accent — default = primary
|
|
47
|
+
accentForegroundResolved: string // text on accent — default = primaryForeground
|
|
48
|
+
|
|
38
49
|
// Aliases (ring + input always equal primary + border for coherence)
|
|
39
50
|
ring: string // = primary
|
|
40
51
|
input: string // = border
|
package/src/tokens.ts
CHANGED
|
@@ -170,10 +170,10 @@ export const TYPOGRAPHY = {
|
|
|
170
170
|
},
|
|
171
171
|
'uppercase-tag': {
|
|
172
172
|
fontFamily: 'Poppins-Bold',
|
|
173
|
-
fontSize:
|
|
173
|
+
fontSize: 10,
|
|
174
174
|
fontWeight: '700' as const,
|
|
175
|
-
lineHeight:
|
|
176
|
-
letterSpacing: 0.
|
|
175
|
+
lineHeight: 13,
|
|
176
|
+
letterSpacing: 0.8,
|
|
177
177
|
textTransform: 'uppercase' as const,
|
|
178
178
|
},
|
|
179
179
|
'button-lg': {
|