@retray-dev/ui-kit 1.0.0 → 1.5.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 +120 -64
- package/README.md +18 -19
- package/dist/index.d.mts +39 -4
- package/dist/index.d.ts +39 -4
- package/dist/index.js +431 -265
- package/dist/index.mjs +430 -265
- package/package.json +20 -3
- package/src/components/Accordion/Accordion.tsx +50 -38
- package/src/components/Alert/Alert.tsx +3 -1
- package/src/components/Avatar/Avatar.tsx +5 -1
- package/src/components/Badge/Badge.tsx +1 -1
- package/src/components/Button/Button.tsx +24 -8
- package/src/components/Card/Card.tsx +2 -8
- package/src/components/Checkbox/Checkbox.tsx +35 -7
- package/src/components/EmptyState/EmptyState.tsx +1 -3
- package/src/components/Input/Input.tsx +18 -10
- package/src/components/Progress/Progress.tsx +3 -4
- package/src/components/RadioGroup/RadioGroup.tsx +72 -45
- package/src/components/Select/Select.tsx +117 -70
- package/src/components/Sheet/Sheet.tsx +9 -2
- package/src/components/Skeleton/Skeleton.tsx +36 -13
- package/src/components/Slider/Slider.tsx +5 -4
- package/src/components/Spinner/Spinner.tsx +1 -7
- package/src/components/Switch/Switch.tsx +5 -1
- package/src/components/Tabs/Tabs.tsx +82 -31
- package/src/components/Textarea/Textarea.tsx +29 -10
- package/src/components/Toast/Toast.tsx +69 -33
- package/src/components/Toggle/Toggle.tsx +32 -20
- package/src/theme/colors.ts +4 -0
- package/src/theme/types.ts +2 -0
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import React, { createContext, useContext, useState, useCallback, useEffect
|
|
2
|
-
import { View, Text, TouchableOpacity,
|
|
1
|
+
import React, { createContext, useContext, useState, useCallback, useEffect } from 'react'
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
|
|
3
|
+
import Animated, {
|
|
4
|
+
useSharedValue,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
withSpring,
|
|
7
|
+
withTiming,
|
|
8
|
+
runOnJS,
|
|
9
|
+
Easing,
|
|
10
|
+
} from 'react-native-reanimated'
|
|
11
|
+
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
|
|
3
12
|
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
4
13
|
import * as Haptics from 'expo-haptics'
|
|
5
14
|
import { useTheme } from '../../theme'
|
|
@@ -11,6 +20,7 @@ export interface ToastItem {
|
|
|
11
20
|
title?: string
|
|
12
21
|
description?: string
|
|
13
22
|
variant?: ToastVariant
|
|
23
|
+
/** Auto-dismiss delay in milliseconds. Defaults to `3000`. */
|
|
14
24
|
duration?: number
|
|
15
25
|
}
|
|
16
26
|
|
|
@@ -28,57 +38,83 @@ export function useToast() {
|
|
|
28
38
|
return useContext(ToastContext)
|
|
29
39
|
}
|
|
30
40
|
|
|
41
|
+
const SWIPE_THRESHOLD = 80
|
|
42
|
+
const VELOCITY_THRESHOLD = 800
|
|
43
|
+
|
|
31
44
|
function ToastNotification({ item, onDismiss }: { item: ToastItem; onDismiss: () => void }) {
|
|
32
45
|
const { colors } = useTheme()
|
|
33
|
-
const translateY =
|
|
34
|
-
const
|
|
46
|
+
const translateY = useSharedValue(-80)
|
|
47
|
+
const translateX = useSharedValue(0)
|
|
48
|
+
const opacity = useSharedValue(0)
|
|
35
49
|
|
|
36
50
|
useEffect(() => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Animated.timing(opacity, { toValue: 1, duration: 200, useNativeDriver: true }),
|
|
40
|
-
]).start()
|
|
51
|
+
translateY.value = withTiming(0, { duration: 120, easing: Easing.out(Easing.exp) })
|
|
52
|
+
opacity.value = withTiming(1, { duration: 100 })
|
|
41
53
|
|
|
42
54
|
const timer = setTimeout(() => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
55
|
+
translateY.value = withTiming(-80, { duration: 200 })
|
|
56
|
+
opacity.value = withTiming(0, { duration: 200 }, (done) => {
|
|
57
|
+
if (done) runOnJS(onDismiss)()
|
|
58
|
+
})
|
|
47
59
|
}, item.duration ?? 3000)
|
|
48
60
|
|
|
49
61
|
return () => clearTimeout(timer)
|
|
50
62
|
}, [])
|
|
51
63
|
|
|
64
|
+
const panGesture = Gesture.Pan()
|
|
65
|
+
.onUpdate((e) => {
|
|
66
|
+
translateX.value = e.translationX
|
|
67
|
+
})
|
|
68
|
+
.onEnd((e) => {
|
|
69
|
+
const shouldDismiss =
|
|
70
|
+
Math.abs(translateX.value) > SWIPE_THRESHOLD ||
|
|
71
|
+
Math.abs(e.velocityX) > VELOCITY_THRESHOLD
|
|
72
|
+
if (shouldDismiss) {
|
|
73
|
+
const direction = translateX.value > 0 ? 1 : -1
|
|
74
|
+
translateX.value = withTiming(direction * 500, { duration: 200 }, (done) => {
|
|
75
|
+
if (done) runOnJS(onDismiss)()
|
|
76
|
+
})
|
|
77
|
+
opacity.value = withTiming(0, { duration: 150 })
|
|
78
|
+
} else {
|
|
79
|
+
translateX.value = withSpring(0, { damping: 20, stiffness: 300 })
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
84
|
+
opacity: opacity.value,
|
|
85
|
+
transform: [{ translateY: translateY.value }, { translateX: translateX.value }],
|
|
86
|
+
}))
|
|
87
|
+
|
|
52
88
|
const bgColor = {
|
|
53
89
|
default: colors.foreground,
|
|
54
90
|
destructive: colors.destructive,
|
|
55
|
-
success:
|
|
91
|
+
success: colors.success,
|
|
56
92
|
}[item.variant ?? 'default']
|
|
57
93
|
|
|
58
94
|
const textColor = {
|
|
59
95
|
default: colors.background,
|
|
60
96
|
destructive: colors.destructiveForeground,
|
|
61
|
-
success:
|
|
97
|
+
success: colors.successForeground,
|
|
62
98
|
}[item.variant ?? 'default']
|
|
63
99
|
|
|
64
100
|
return (
|
|
65
|
-
<
|
|
66
|
-
style={[styles.toast, { backgroundColor: bgColor
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
</
|
|
81
|
-
</
|
|
101
|
+
<GestureDetector gesture={panGesture}>
|
|
102
|
+
<Animated.View style={[styles.toast, { backgroundColor: bgColor }, animatedStyle]}>
|
|
103
|
+
<View style={styles.toastContent}>
|
|
104
|
+
{item.title ? (
|
|
105
|
+
<Text style={[styles.toastTitle, { color: textColor }]}>{item.title}</Text>
|
|
106
|
+
) : null}
|
|
107
|
+
{item.description ? (
|
|
108
|
+
<Text style={[styles.toastDescription, { color: textColor, opacity: 0.85 }]}>
|
|
109
|
+
{item.description}
|
|
110
|
+
</Text>
|
|
111
|
+
) : null}
|
|
112
|
+
</View>
|
|
113
|
+
<TouchableOpacity onPress={onDismiss} style={styles.dismissButton} touchSoundDisabled={true}>
|
|
114
|
+
<Text style={[styles.dismissIcon, { color: textColor }]}>✕</Text>
|
|
115
|
+
</TouchableOpacity>
|
|
116
|
+
</Animated.View>
|
|
117
|
+
</GestureDetector>
|
|
82
118
|
)
|
|
83
119
|
}
|
|
84
120
|
|
|
@@ -155,8 +191,8 @@ const styles = StyleSheet.create({
|
|
|
155
191
|
fontSize: 13,
|
|
156
192
|
},
|
|
157
193
|
dismissButton: {
|
|
158
|
-
padding:
|
|
159
|
-
marginLeft:
|
|
194
|
+
padding: 12,
|
|
195
|
+
marginLeft: 4,
|
|
160
196
|
},
|
|
161
197
|
dismissIcon: {
|
|
162
198
|
fontSize: 12,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { TouchableOpacity, Text, StyleSheet, TouchableOpacityProps, ViewStyle } from 'react-native'
|
|
1
|
+
import React, { useRef } from 'react'
|
|
2
|
+
import { TouchableOpacity, Animated, Text, StyleSheet, TouchableOpacityProps, ViewStyle } from 'react-native'
|
|
3
3
|
import * as Haptics from 'expo-haptics'
|
|
4
4
|
import { useTheme } from '../../theme'
|
|
5
5
|
|
|
@@ -33,32 +33,44 @@ export function Toggle({
|
|
|
33
33
|
...props
|
|
34
34
|
}: ToggleProps) {
|
|
35
35
|
const { colors } = useTheme()
|
|
36
|
+
const scale = useRef(new Animated.Value(1)).current
|
|
37
|
+
|
|
38
|
+
const handlePressIn = () => {
|
|
39
|
+
if (disabled) return
|
|
40
|
+
Animated.spring(scale, { toValue: 0.95, useNativeDriver: true, speed: 40, bounciness: 0 }).start()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const handlePressOut = () => {
|
|
44
|
+
Animated.spring(scale, { toValue: 1, useNativeDriver: true, speed: 40, bounciness: 4 }).start()
|
|
45
|
+
}
|
|
36
46
|
|
|
37
47
|
const containerStyle: ViewStyle = pressed
|
|
38
48
|
? { backgroundColor: colors.accent }
|
|
39
49
|
: variant === 'outline'
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
? { backgroundColor: 'transparent', borderWidth: 1, borderColor: colors.border }
|
|
51
|
+
: { backgroundColor: 'transparent' }
|
|
42
52
|
|
|
43
53
|
const textColor = pressed ? colors.accentForeground : colors.foreground
|
|
44
54
|
|
|
45
55
|
return (
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
styles.base,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
<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}
|
|
69
|
+
>
|
|
70
|
+
{icon}
|
|
71
|
+
{label ? <Text style={[styles.label, { color: textColor }]}>{label}</Text> : null}
|
|
72
|
+
</TouchableOpacity>
|
|
73
|
+
</Animated.View>
|
|
62
74
|
)
|
|
63
75
|
}
|
|
64
76
|
|
package/src/theme/colors.ts
CHANGED
|
@@ -18,6 +18,8 @@ export const defaultLight: ThemeColors = {
|
|
|
18
18
|
border: '#e5e5e5',
|
|
19
19
|
input: '#e5e5e5',
|
|
20
20
|
ring: '#a3a3a3',
|
|
21
|
+
success: '#16a34a',
|
|
22
|
+
successForeground: '#ffffff',
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export const defaultDark: ThemeColors = {
|
|
@@ -38,4 +40,6 @@ export const defaultDark: ThemeColors = {
|
|
|
38
40
|
border: '#2a2a2a',
|
|
39
41
|
input: '#2a2a2a',
|
|
40
42
|
ring: '#d4d4d4',
|
|
43
|
+
success: '#22c55e',
|
|
44
|
+
successForeground: '#ffffff',
|
|
41
45
|
}
|