@chem-po/react-native 0.0.24 → 0.0.26

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 (48) hide show
  1. package/lib/commonjs/components/form/UploadProgress/index.js +8 -17
  2. package/lib/commonjs/components/form/UploadProgress/index.js.map +1 -1
  3. package/lib/commonjs/components/form/input/file/index.js +26 -8
  4. package/lib/commonjs/components/form/input/file/index.js.map +1 -1
  5. package/lib/commonjs/components/image/ImageViewModal.backup.js +285 -0
  6. package/lib/commonjs/components/image/ImageViewModal.backup.js.map +1 -0
  7. package/lib/commonjs/components/image/ImageViewModal.js +79 -99
  8. package/lib/commonjs/components/image/ImageViewModal.js.map +1 -1
  9. package/lib/commonjs/components/image/ImageViewModal.old.js +285 -0
  10. package/lib/commonjs/components/image/ImageViewModal.old.js.map +1 -0
  11. package/lib/commonjs/components/loading/ProgressBar.js +78 -0
  12. package/lib/commonjs/components/loading/ProgressBar.js.map +1 -0
  13. package/lib/commonjs/components/loading/index.js +11 -0
  14. package/lib/commonjs/components/loading/index.js.map +1 -1
  15. package/lib/module/components/form/UploadProgress/index.js +6 -15
  16. package/lib/module/components/form/UploadProgress/index.js.map +1 -1
  17. package/lib/module/components/form/input/file/index.js +27 -9
  18. package/lib/module/components/form/input/file/index.js.map +1 -1
  19. package/lib/module/components/image/ImageViewModal.backup.js +277 -0
  20. package/lib/module/components/image/ImageViewModal.backup.js.map +1 -0
  21. package/lib/module/components/image/ImageViewModal.js +80 -100
  22. package/lib/module/components/image/ImageViewModal.js.map +1 -1
  23. package/lib/module/components/image/ImageViewModal.old.js +277 -0
  24. package/lib/module/components/image/ImageViewModal.old.js.map +1 -0
  25. package/lib/module/components/loading/ProgressBar.js +70 -0
  26. package/lib/module/components/loading/ProgressBar.js.map +1 -0
  27. package/lib/module/components/loading/index.js +1 -0
  28. package/lib/module/components/loading/index.js.map +1 -1
  29. package/lib/typescript/components/form/UploadProgress/index.d.ts.map +1 -1
  30. package/lib/typescript/components/form/input/file/index.d.ts +5 -3
  31. package/lib/typescript/components/form/input/file/index.d.ts.map +1 -1
  32. package/lib/typescript/components/image/ImageViewModal.backup.d.ts +9 -0
  33. package/lib/typescript/components/image/ImageViewModal.backup.d.ts.map +1 -0
  34. package/lib/typescript/components/image/ImageViewModal.d.ts.map +1 -1
  35. package/lib/typescript/components/image/ImageViewModal.old.d.ts +9 -0
  36. package/lib/typescript/components/image/ImageViewModal.old.d.ts.map +1 -0
  37. package/lib/typescript/components/loading/ProgressBar.d.ts +7 -0
  38. package/lib/typescript/components/loading/ProgressBar.d.ts.map +1 -0
  39. package/lib/typescript/components/loading/index.d.ts +1 -0
  40. package/lib/typescript/components/loading/index.d.ts.map +1 -1
  41. package/package.json +11 -10
  42. package/src/components/form/UploadProgress/index.tsx +3 -17
  43. package/src/components/form/input/file/index.tsx +28 -8
  44. package/src/components/image/ImageViewModal.backup.tsx +261 -0
  45. package/src/components/image/ImageViewModal.old.tsx +261 -0
  46. package/src/components/image/ImageViewModal.tsx +72 -79
  47. package/src/components/loading/ProgressBar.tsx +75 -0
  48. package/src/components/loading/index.ts +1 -0
@@ -0,0 +1,261 @@
1
+ import { useScreen } from '@chem-po/react'
2
+ import { Ionicons } from '@expo/vector-icons'
3
+ import React, { useCallback, useMemo, useRef, useState } from 'react'
4
+ import { Animated, Image, Modal, StyleSheet, TouchableOpacity, View } from 'react-native'
5
+ import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler'
6
+ import { LoadingLogo } from '../loading/Loading'
7
+
8
+ interface ImageViewModalProps {
9
+ isOpen: boolean
10
+ onClose: () => void
11
+ src: string | null
12
+ }
13
+
14
+ export const ImageViewModal: React.FC<ImageViewModalProps> = ({ isOpen, onClose, src }) => {
15
+ const [loading, setLoading] = useState(true)
16
+ const screenWidth = useScreen(s => s.width)
17
+ const screenHeight = useScreen(s => s.height)
18
+ const [imageSize, setImageSize] = useState({ width: screenWidth / 2, height: screenHeight / 2 })
19
+
20
+ // Animated values for zoom and pan
21
+ const scale = useRef(new Animated.Value(1)).current
22
+ const translateX = useRef(new Animated.Value(0)).current
23
+ const translateY = useRef(new Animated.Value(0)).current
24
+
25
+ // Gesture state
26
+ const savedScale = useRef(1)
27
+ const savedTranslateX = useRef(0)
28
+ const savedTranslateY = useRef(0)
29
+ const currentScale = useRef(1)
30
+ const currentTranslateX = useRef(0)
31
+ const currentTranslateY = useRef(0)
32
+
33
+ const { height, width } = useMemo(() => {
34
+ if (loading) return imageSize
35
+ const ratio = imageSize.width / imageSize.height
36
+ let h = Math.min(imageSize.height, screenHeight * 0.9)
37
+ let w = h * ratio
38
+ if (w > screenWidth * 0.9) {
39
+ w = Math.min(imageSize.width, screenWidth * 0.9)
40
+ h = w / ratio
41
+ }
42
+ return { height: h, width: w }
43
+ }, [screenHeight, screenWidth, imageSize, loading])
44
+
45
+ const onLoadStart = useCallback(() => setLoading(true), [])
46
+ const onLoad = useCallback((e: any) => {
47
+ const { width: naturalWidth, height: naturalHeight } = e.nativeEvent.source
48
+ setImageSize({ width: naturalWidth, height: naturalHeight })
49
+ setLoading(false)
50
+ }, [])
51
+
52
+ // Reset zoom and pan when modal opens/closes
53
+ const resetTransform = useCallback(() => {
54
+ scale.setValue(1)
55
+ translateX.setValue(0)
56
+ translateY.setValue(0)
57
+ savedScale.current = 1
58
+ savedTranslateX.current = 0
59
+ savedTranslateY.current = 0
60
+ currentScale.current = 1
61
+ currentTranslateX.current = 0
62
+ currentTranslateY.current = 0
63
+ }, [scale, translateX, translateY])
64
+
65
+ // Reset when modal closes or src changes
66
+ React.useEffect(() => {
67
+ if (!isOpen || !src) {
68
+ resetTransform()
69
+ }
70
+ }, [isOpen, src, resetTransform])
71
+
72
+ // Pan gesture
73
+ const panGesture = Gesture.Pan()
74
+ .onUpdate(event => {
75
+ // Only allow panning if zoomed in
76
+ if (savedScale.current > 1) {
77
+ const newTranslateX = savedTranslateX.current + event.translationX
78
+ const newTranslateY = savedTranslateY.current + event.translationY
79
+
80
+ // Calculate bounds to prevent panning too far
81
+ const maxTranslateX = (width * savedScale.current - width) / 2
82
+ const maxTranslateY = (height * savedScale.current - height) / 2
83
+
84
+ const boundedTranslateX = Math.max(-maxTranslateX, Math.min(maxTranslateX, newTranslateX))
85
+ const boundedTranslateY = Math.max(-maxTranslateY, Math.min(maxTranslateY, newTranslateY))
86
+
87
+ translateX.setValue(boundedTranslateX)
88
+ translateY.setValue(boundedTranslateY)
89
+ currentTranslateX.current = boundedTranslateX
90
+ currentTranslateY.current = boundedTranslateY
91
+ }
92
+ })
93
+ .onEnd(() => {
94
+ savedTranslateX.current = currentTranslateX.current
95
+ savedTranslateY.current = currentTranslateY.current
96
+ })
97
+
98
+ // Pinch gesture
99
+ const pinchGesture = Gesture.Pinch()
100
+ .onUpdate(event => {
101
+ const newScale = savedScale.current * event.scale
102
+ // Limit zoom between 1x and 5x
103
+ const boundedScale = Math.max(1, Math.min(5, newScale))
104
+ scale.setValue(boundedScale)
105
+ currentScale.current = boundedScale
106
+
107
+ // If zooming out to 1x, reset position
108
+ if (boundedScale <= 1) {
109
+ translateX.setValue(0)
110
+ translateY.setValue(0)
111
+ currentTranslateX.current = 0
112
+ currentTranslateY.current = 0
113
+ }
114
+ })
115
+ .onEnd(() => {
116
+ savedScale.current = currentScale.current
117
+ // If scale is close to 1, snap back to 1
118
+ if (savedScale.current < 1.1) {
119
+ Animated.parallel([
120
+ Animated.spring(scale, { toValue: 1, useNativeDriver: true }),
121
+ Animated.spring(translateX, { toValue: 0, useNativeDriver: true }),
122
+ Animated.spring(translateY, { toValue: 0, useNativeDriver: true }),
123
+ ]).start()
124
+ savedScale.current = 1
125
+ savedTranslateX.current = 0
126
+ savedTranslateY.current = 0
127
+ currentScale.current = 1
128
+ currentTranslateX.current = 0
129
+ currentTranslateY.current = 0
130
+ } else {
131
+ savedTranslateX.current = currentTranslateX.current
132
+ savedTranslateY.current = currentTranslateY.current
133
+ }
134
+ })
135
+
136
+ // Double tap to zoom
137
+ const doubleTapGesture = Gesture.Tap()
138
+ .numberOfTaps(2)
139
+ .onEnd(() => {
140
+ if (savedScale.current > 1) {
141
+ // Zoom out
142
+ Animated.parallel([
143
+ Animated.spring(scale, { toValue: 1, useNativeDriver: true }),
144
+ Animated.spring(translateX, { toValue: 0, useNativeDriver: true }),
145
+ Animated.spring(translateY, { toValue: 0, useNativeDriver: true }),
146
+ ]).start()
147
+ savedScale.current = 1
148
+ savedTranslateX.current = 0
149
+ savedTranslateY.current = 0
150
+ currentScale.current = 1
151
+ currentTranslateX.current = 0
152
+ currentTranslateY.current = 0
153
+ } else {
154
+ // Zoom in to 2x
155
+ Animated.spring(scale, { toValue: 2, useNativeDriver: true }).start()
156
+ savedScale.current = 2
157
+ currentScale.current = 2
158
+ }
159
+ })
160
+
161
+ // Test simple single tap gesture
162
+
163
+ // Combine gestures - simplified approach
164
+ const combinedGestures = Gesture.Race(
165
+ doubleTapGesture,
166
+ Gesture.Simultaneous(panGesture, pinchGesture),
167
+ )
168
+
169
+ if (!isOpen) {
170
+ return null
171
+ }
172
+
173
+ return (
174
+ <Modal visible={isOpen} transparent animationType="fade" onRequestClose={onClose}>
175
+ <GestureHandlerRootView style={styles.gestureRoot}>
176
+ <View style={styles.modalOverlay}>
177
+ <TouchableOpacity
178
+ style={styles.backgroundTouchable}
179
+ activeOpacity={1}
180
+ onPress={onClose}
181
+ />
182
+ <View style={styles.contentContainer}>
183
+ <GestureDetector gesture={combinedGestures}>
184
+ <Animated.View
185
+ style={[
186
+ styles.imageContainer,
187
+ {
188
+ width,
189
+ height,
190
+ opacity: loading ? 0 : 1,
191
+ transform: [{ scale }, { translateX }, { translateY }],
192
+ },
193
+ ]}>
194
+ <Image
195
+ source={src ? { uri: src } : undefined}
196
+ style={styles.image}
197
+ onLoadStart={onLoadStart}
198
+ onLoad={onLoad}
199
+ resizeMode="contain"
200
+ />
201
+ </Animated.View>
202
+ </GestureDetector>
203
+
204
+ <TouchableOpacity style={styles.closeButton} onPress={onClose}>
205
+ <Ionicons name="close" size={24} color="white" />
206
+ </TouchableOpacity>
207
+
208
+ {loading || !src ? (
209
+ <View style={styles.loadingContainer}>
210
+ <LoadingLogo isLoading={loading} size={70} />
211
+ </View>
212
+ ) : null}
213
+ </View>
214
+ </View>
215
+ </GestureHandlerRootView>
216
+ </Modal>
217
+ )
218
+ }
219
+
220
+ const styles = StyleSheet.create({
221
+ gestureRoot: {
222
+ flex: 1,
223
+ },
224
+ modalOverlay: {
225
+ flex: 1,
226
+ backgroundColor: 'rgba(0, 0, 0, 0.7)',
227
+ },
228
+ backgroundTouchable: {
229
+ ...StyleSheet.absoluteFillObject,
230
+ },
231
+ contentContainer: {
232
+ flex: 1,
233
+ justifyContent: 'center',
234
+ alignItems: 'center',
235
+ padding: 16,
236
+ },
237
+ imageContainer: {
238
+ overflow: 'hidden',
239
+ borderRadius: 4,
240
+ },
241
+ image: {
242
+ width: '100%',
243
+ height: '100%',
244
+ },
245
+ closeButton: {
246
+ position: 'absolute',
247
+ top: 16,
248
+ right: 16,
249
+ width: 40,
250
+ height: 40,
251
+ borderRadius: 20,
252
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
253
+ justifyContent: 'center',
254
+ alignItems: 'center',
255
+ },
256
+ loadingContainer: {
257
+ ...StyleSheet.absoluteFillObject,
258
+ justifyContent: 'center',
259
+ alignItems: 'center',
260
+ },
261
+ })
@@ -1,8 +1,9 @@
1
1
  import { useScreen } from '@chem-po/react'
2
2
  import { Ionicons } from '@expo/vector-icons'
3
- import React, { useCallback, useMemo, useRef, useState } from 'react'
4
- import { Animated, Image, Modal, StyleSheet, TouchableOpacity, View } from 'react-native'
3
+ import React, { useCallback, useEffect, useMemo, useState } from 'react'
4
+ import { Image, Modal, StyleSheet, TouchableOpacity, View } from 'react-native'
5
5
  import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler'
6
+ import Animated, { useAnimatedStyle, useSharedValue, withSpring } from 'react-native-reanimated'
6
7
  import { LoadingLogo } from '../loading/Loading'
7
8
 
8
9
  interface ImageViewModalProps {
@@ -17,18 +18,15 @@ export const ImageViewModal: React.FC<ImageViewModalProps> = ({ isOpen, onClose,
17
18
  const screenHeight = useScreen(s => s.height)
18
19
  const [imageSize, setImageSize] = useState({ width: screenWidth / 2, height: screenHeight / 2 })
19
20
 
20
- // Animated values for zoom and pan
21
- const scale = useRef(new Animated.Value(1)).current
22
- const translateX = useRef(new Animated.Value(0)).current
23
- const translateY = useRef(new Animated.Value(0)).current
21
+ // Shared values for zoom and pan (reanimated)
22
+ const scale = useSharedValue(1)
23
+ const translateX = useSharedValue(0)
24
+ const translateY = useSharedValue(0)
24
25
 
25
- // Gesture state
26
- const savedScale = useRef(1)
27
- const savedTranslateX = useRef(0)
28
- const savedTranslateY = useRef(0)
29
- const currentScale = useRef(1)
30
- const currentTranslateX = useRef(0)
31
- const currentTranslateY = useRef(0)
26
+ // Saved state for gesture handling
27
+ const savedScale = useSharedValue(1)
28
+ const savedTranslateX = useSharedValue(0)
29
+ const savedTranslateY = useSharedValue(0)
32
30
 
33
31
  const { height, width } = useMemo(() => {
34
32
  if (loading) return imageSize
@@ -51,121 +49,116 @@ export const ImageViewModal: React.FC<ImageViewModalProps> = ({ isOpen, onClose,
51
49
 
52
50
  // Reset zoom and pan when modal opens/closes
53
51
  const resetTransform = useCallback(() => {
54
- scale.setValue(1)
55
- translateX.setValue(0)
56
- translateY.setValue(0)
57
- savedScale.current = 1
58
- savedTranslateX.current = 0
59
- savedTranslateY.current = 0
60
- currentScale.current = 1
61
- currentTranslateX.current = 0
62
- currentTranslateY.current = 0
63
- }, [scale, translateX, translateY])
52
+ scale.value = 1
53
+ translateX.value = 0
54
+ translateY.value = 0
55
+ savedScale.value = 1
56
+ savedTranslateX.value = 0
57
+ savedTranslateY.value = 0
58
+ }, [scale, translateX, translateY, savedScale, savedTranslateX, savedTranslateY])
64
59
 
65
60
  // Reset when modal closes or src changes
66
- React.useEffect(() => {
61
+ useEffect(() => {
67
62
  if (!isOpen || !src) {
68
63
  resetTransform()
69
64
  }
70
65
  }, [isOpen, src, resetTransform])
71
66
 
72
- // Pan gesture
67
+ // Pan gesture with worklet
73
68
  const panGesture = Gesture.Pan()
74
69
  .onUpdate(event => {
70
+ 'worklet'
75
71
  // Only allow panning if zoomed in
76
- if (savedScale.current > 1) {
77
- const newTranslateX = savedTranslateX.current + event.translationX
78
- const newTranslateY = savedTranslateY.current + event.translationY
72
+ if (savedScale.value > 1) {
73
+ const newTranslateX = savedTranslateX.value + event.translationX
74
+ const newTranslateY = savedTranslateY.value + event.translationY
79
75
 
80
76
  // Calculate bounds to prevent panning too far
81
- const maxTranslateX = (width * savedScale.current - width) / 2
82
- const maxTranslateY = (height * savedScale.current - height) / 2
77
+ const maxTranslateX = (width * savedScale.value - width) / 2
78
+ const maxTranslateY = (height * savedScale.value - height) / 2
83
79
 
84
80
  const boundedTranslateX = Math.max(-maxTranslateX, Math.min(maxTranslateX, newTranslateX))
85
81
  const boundedTranslateY = Math.max(-maxTranslateY, Math.min(maxTranslateY, newTranslateY))
86
82
 
87
- translateX.setValue(boundedTranslateX)
88
- translateY.setValue(boundedTranslateY)
89
- currentTranslateX.current = boundedTranslateX
90
- currentTranslateY.current = boundedTranslateY
83
+ translateX.value = boundedTranslateX
84
+ translateY.value = boundedTranslateY
91
85
  }
92
86
  })
93
87
  .onEnd(() => {
94
- savedTranslateX.current = currentTranslateX.current
95
- savedTranslateY.current = currentTranslateY.current
88
+ 'worklet'
89
+ savedTranslateX.value = translateX.value
90
+ savedTranslateY.value = translateY.value
96
91
  })
97
92
 
98
- // Pinch gesture
93
+ // Pinch gesture with worklet
99
94
  const pinchGesture = Gesture.Pinch()
100
95
  .onUpdate(event => {
101
- const newScale = savedScale.current * event.scale
96
+ 'worklet'
97
+ const newScale = savedScale.value * event.scale
102
98
  // Limit zoom between 1x and 5x
103
99
  const boundedScale = Math.max(1, Math.min(5, newScale))
104
- scale.setValue(boundedScale)
105
- currentScale.current = boundedScale
100
+ scale.value = boundedScale
106
101
 
107
102
  // If zooming out to 1x, reset position
108
103
  if (boundedScale <= 1) {
109
- translateX.setValue(0)
110
- translateY.setValue(0)
111
- currentTranslateX.current = 0
112
- currentTranslateY.current = 0
104
+ translateX.value = 0
105
+ translateY.value = 0
113
106
  }
114
107
  })
115
108
  .onEnd(() => {
116
- savedScale.current = currentScale.current
109
+ 'worklet'
110
+ savedScale.value = scale.value
117
111
  // If scale is close to 1, snap back to 1
118
- if (savedScale.current < 1.1) {
119
- Animated.parallel([
120
- Animated.spring(scale, { toValue: 1, useNativeDriver: true }),
121
- Animated.spring(translateX, { toValue: 0, useNativeDriver: true }),
122
- Animated.spring(translateY, { toValue: 0, useNativeDriver: true }),
123
- ]).start()
124
- savedScale.current = 1
125
- savedTranslateX.current = 0
126
- savedTranslateY.current = 0
127
- currentScale.current = 1
128
- currentTranslateX.current = 0
129
- currentTranslateY.current = 0
112
+ if (savedScale.value < 1.1) {
113
+ scale.value = withSpring(1)
114
+ translateX.value = withSpring(0)
115
+ translateY.value = withSpring(0)
116
+ savedScale.value = 1
117
+ savedTranslateX.value = 0
118
+ savedTranslateY.value = 0
130
119
  } else {
131
- savedTranslateX.current = currentTranslateX.current
132
- savedTranslateY.current = currentTranslateY.current
120
+ savedTranslateX.value = translateX.value
121
+ savedTranslateY.value = translateY.value
133
122
  }
134
123
  })
135
124
 
136
- // Double tap to zoom
125
+ // Double tap to zoom with worklet
137
126
  const doubleTapGesture = Gesture.Tap()
138
127
  .numberOfTaps(2)
139
128
  .onEnd(() => {
140
- if (savedScale.current > 1) {
129
+ 'worklet'
130
+ if (savedScale.value > 1) {
141
131
  // Zoom out
142
- Animated.parallel([
143
- Animated.spring(scale, { toValue: 1, useNativeDriver: true }),
144
- Animated.spring(translateX, { toValue: 0, useNativeDriver: true }),
145
- Animated.spring(translateY, { toValue: 0, useNativeDriver: true }),
146
- ]).start()
147
- savedScale.current = 1
148
- savedTranslateX.current = 0
149
- savedTranslateY.current = 0
150
- currentScale.current = 1
151
- currentTranslateX.current = 0
152
- currentTranslateY.current = 0
132
+ scale.value = withSpring(1)
133
+ translateX.value = withSpring(0)
134
+ translateY.value = withSpring(0)
135
+ savedScale.value = 1
136
+ savedTranslateX.value = 0
137
+ savedTranslateY.value = 0
153
138
  } else {
154
139
  // Zoom in to 2x
155
- Animated.spring(scale, { toValue: 2, useNativeDriver: true }).start()
156
- savedScale.current = 2
157
- currentScale.current = 2
140
+ scale.value = withSpring(2)
141
+ savedScale.value = 2
158
142
  }
159
143
  })
160
144
 
161
- // Test simple single tap gesture
162
-
163
- // Combine gestures - simplified approach
145
+ // Combine gestures
164
146
  const combinedGestures = Gesture.Race(
165
147
  doubleTapGesture,
166
148
  Gesture.Simultaneous(panGesture, pinchGesture),
167
149
  )
168
150
 
151
+ // Animated style using reanimated
152
+ const animatedStyle = useAnimatedStyle(() => {
153
+ return {
154
+ transform: [
155
+ { scale: scale.value },
156
+ { translateX: translateX.value },
157
+ { translateY: translateY.value },
158
+ ],
159
+ }
160
+ })
161
+
169
162
  if (!isOpen) {
170
163
  return null
171
164
  }
@@ -188,8 +181,8 @@ export const ImageViewModal: React.FC<ImageViewModalProps> = ({ isOpen, onClose,
188
181
  width,
189
182
  height,
190
183
  opacity: loading ? 0 : 1,
191
- transform: [{ scale }, { translateX }, { translateY }],
192
184
  },
185
+ animatedStyle,
193
186
  ]}>
194
187
  <Image
195
188
  source={src ? { uri: src } : undefined}
@@ -0,0 +1,75 @@
1
+ import { useColorModeValue, useThemeValue } from '@chem-po/react'
2
+ import React, { useEffect, useRef } from 'react'
3
+ import { Animated, StyleSheet, Text, View } from 'react-native'
4
+
5
+ export const ProgressBar = ({
6
+ progress,
7
+ label,
8
+ height = 26,
9
+ }: {
10
+ progress: number
11
+ label?: string
12
+ height?: number
13
+ }) => {
14
+ const accentColor = useThemeValue('colors.accent.300')
15
+ const animatedWidth = useRef(new Animated.Value(0)).current
16
+ const backgroundColor = useColorModeValue('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.2)')
17
+
18
+ useEffect(() => {
19
+ Animated.timing(animatedWidth, {
20
+ toValue: progress,
21
+ duration: 300,
22
+ useNativeDriver: false, // width animations require layout driver
23
+ }).start()
24
+ }, [progress, animatedWidth])
25
+
26
+ return (
27
+ <View style={[styles.progressContainer, { height, backgroundColor }]}>
28
+ <View style={styles.progressBar}>
29
+ <Animated.View
30
+ style={[
31
+ styles.progressFill,
32
+ {
33
+ width: animatedWidth.interpolate({
34
+ inputRange: [0, 1],
35
+ outputRange: ['0%', '100%'],
36
+ extrapolate: 'clamp',
37
+ }),
38
+ backgroundColor: accentColor,
39
+ },
40
+ ]}
41
+ />
42
+ </View>
43
+ <Text style={styles.label}>{label}</Text>
44
+ </View>
45
+ )
46
+ }
47
+
48
+ const styles = StyleSheet.create({
49
+ progressContainer: {
50
+ width: '100%',
51
+ justifyContent: 'center',
52
+ alignItems: 'center',
53
+ borderRadius: 20,
54
+ padding: 3,
55
+ },
56
+ progressBar: {
57
+ height: '100%',
58
+ width: '100%',
59
+ borderRadius: 20,
60
+ overflow: 'hidden',
61
+ },
62
+ progressFill: {
63
+ height: '100%',
64
+ },
65
+ label: {
66
+ position: 'absolute',
67
+ color: 'white',
68
+ fontSize: 12,
69
+ fontWeight: '500',
70
+ textTransform: 'uppercase',
71
+ textShadowColor: 'rgba(0, 0, 0, 0.67)',
72
+ textShadowOffset: { width: 1, height: 1 },
73
+ textShadowRadius: 3,
74
+ },
75
+ })
@@ -3,3 +3,4 @@ export * from './Loading'
3
3
  export * from './LoadingImage'
4
4
  export * from './LoadingOverlay'
5
5
  export * from './LoadingSwitch'
6
+ export * from './ProgressBar'