@holper/react-native-holper-storybook 0.6.102 → 0.7.1
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/LICENSE +21 -0
- package/{readme.md → README.md} +19 -20
- package/index.js +3 -2
- package/lib/components/Button/index.tsx +66 -0
- package/lib/components/Button/{style.js → style.ts} +8 -7
- package/lib/components/Card/index.tsx +33 -0
- package/lib/components/Card/{style.js → style.ts} +5 -4
- package/lib/components/ConfirmationModal/{index.js → index.tsx} +25 -79
- package/lib/components/ConfirmationModal/{style.js → style.tsx} +14 -13
- package/lib/components/Container/{index.js → index.tsx} +7 -28
- package/lib/components/Container/{style.js → style.ts} +6 -5
- package/lib/components/CustomChatView/{index.js → index.tsx} +22 -30
- package/lib/components/CustomChatView/{style.js → style.ts} +1 -1
- package/lib/components/DeckSwiper/index.tsx +90 -0
- package/lib/components/DeckSwiper/{style.js → style.ts} +13 -12
- package/lib/components/DonutCountdown/index.tsx +86 -0
- package/lib/components/DonutCountdown/style.ts +8 -0
- package/lib/components/FloatingContainer/index.tsx +35 -0
- package/lib/components/FloatingContainer/{style.js → style.ts} +7 -6
- package/lib/components/Footer/index.tsx +35 -0
- package/lib/components/Footer/{style.js → style.ts} +4 -3
- package/lib/components/Header/index.tsx +21 -0
- package/lib/components/Header/{style.js → style.ts} +4 -3
- package/lib/components/ImagePicker/{index.js → index.tsx} +3 -12
- package/lib/components/ImageResponsive/index.tsx +24 -0
- package/lib/components/ImageResponsive/style.ts +9 -0
- package/lib/components/ImageViewer/index.tsx +36 -0
- package/lib/components/ImageViewer/{style.js → style.ts} +4 -3
- package/lib/components/Input/{index.js → index.tsx} +6 -33
- package/lib/components/Input/{style.js → style.ts} +7 -18
- package/lib/components/InputPin/{index.js → index.tsx} +6 -13
- package/lib/components/InputPin/{style.js → style.ts} +7 -6
- package/lib/components/MenuItem/index.tsx +25 -0
- package/lib/components/MenuItem/{style.js → style.ts} +9 -7
- package/lib/components/NavigationTitle/{index.js → index.tsx} +9 -30
- package/lib/components/NavigationTitle/{style.js → style.ts} +12 -11
- package/lib/components/Notification/index.tsx +44 -0
- package/lib/components/Notification/{style.js → style.ts} +13 -11
- package/lib/components/PreventDoubleClick/index.tsx +28 -0
- package/lib/components/Select/index.tsx +51 -0
- package/lib/components/Select/style.ts +64 -0
- package/lib/components/SwipeablePanel/{index.js → index.tsx} +58 -85
- package/lib/components/SwipeablePanel/{style.js → style.ts} +15 -14
- package/lib/components/Switch/index.tsx +30 -0
- package/lib/components/TakePicture/{confirmPictureModal.js → confirmPictureModal.tsx} +9 -33
- package/lib/components/TakePicture/index.tsx +148 -0
- package/lib/components/TakePicture/{style.js → style.ts} +4 -4
- package/lib/components/Text/index.tsx +33 -0
- package/lib/components/Text/{style.js → style.ts} +4 -2
- package/lib/components/Textarea/{index.js → index.tsx} +5 -24
- package/lib/components/Textarea/{style.js → style.ts} +5 -4
- package/lib/components/TimeOutButton/index.tsx +67 -0
- package/lib/components/TimeOutButton/{style.js → style.ts} +4 -3
- package/lib/components/Toast/index.tsx +34 -0
- package/lib/components/Toast/style.ts +12 -0
- package/lib/components/UploadDocument/{index.js → index.tsx} +49 -105
- package/lib/components/UploadDocument/{style.js → style.ts} +16 -15
- package/lib/components/VirtualKeyboard/index.tsx +75 -0
- package/lib/components/VirtualKeyboard/{style.js → style.ts} +9 -8
- package/lib/components/index.ts +29 -0
- package/lib/configs/{constants.js → constants.ts} +50 -48
- package/lib/configs/types.ts +326 -0
- package/lib/hooks/index.ts +2 -0
- package/lib/hooks/{useDebounce.js → useDebounce.tsx} +6 -4
- package/lib/hooks/useLoadFonts.tsx +13 -0
- package/lib/index.js +3 -2
- package/package.json +72 -59
- package/lib/components/Button/index.js +0 -104
- package/lib/components/Card/index.js +0 -49
- package/lib/components/DeckSwiper/index.js +0 -118
- package/lib/components/FlashMessage/index.js +0 -81
- package/lib/components/FloatingContainer/index.js +0 -69
- package/lib/components/Footer/index.js +0 -61
- package/lib/components/Header/index.js +0 -45
- package/lib/components/ImageResponsive/index.js +0 -39
- package/lib/components/ImageResponsive/style.js +0 -7
- package/lib/components/ImageViewer/index.js +0 -62
- package/lib/components/MenuItem/index.js +0 -44
- package/lib/components/Notification/index.js +0 -80
- package/lib/components/PreventDoubleClick/index.js +0 -21
- package/lib/components/Select/index.js +0 -89
- package/lib/components/Select/style.js +0 -81
- package/lib/components/Switch/index.js +0 -57
- package/lib/components/TakePicture/index.js +0 -198
- package/lib/components/Text/index.js +0 -75
- package/lib/components/TimeOutButton/index.js +0 -104
- package/lib/components/VirtualKeyboard/index.js +0 -86
- package/lib/components/index.js +0 -28
- package/lib/configs/loadFonts.js +0 -11
- package/lib/hooks/index.js +0 -1
|
@@ -1,21 +1,23 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { Ionicons } from '@expo/vector-icons';
|
|
2
|
+
import Constants from 'expo-constants';
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
3
4
|
import {
|
|
4
|
-
View,
|
|
5
|
-
ScrollView,
|
|
6
5
|
Animated,
|
|
7
6
|
Dimensions,
|
|
8
7
|
PanResponder,
|
|
9
|
-
TouchableWithoutFeedback,
|
|
10
8
|
Platform,
|
|
9
|
+
ScrollView,
|
|
11
10
|
TouchableOpacity,
|
|
11
|
+
TouchableWithoutFeedback,
|
|
12
|
+
View,
|
|
12
13
|
} from 'react-native';
|
|
13
|
-
|
|
14
|
-
import Ionicons from 'react-native-vector-icons/Ionicons';
|
|
15
|
-
import { CountdownCircleTimer } from 'react-native-countdown-circle-timer';
|
|
14
|
+
|
|
16
15
|
import { Colors } from '../../configs/constants';
|
|
16
|
+
import DonutCountdown from '../DonutCountdown';
|
|
17
17
|
import style from './style';
|
|
18
18
|
|
|
19
|
+
import type { SwipeablePanelProps } from '../../configs/types';
|
|
20
|
+
|
|
19
21
|
const { height: screenHeight } = Dimensions.get('window');
|
|
20
22
|
|
|
21
23
|
const STATUS = {
|
|
@@ -25,7 +27,7 @@ const STATUS = {
|
|
|
25
27
|
|
|
26
28
|
const calcMarginTop = () => {
|
|
27
29
|
if (Platform.OS === 'ios') {
|
|
28
|
-
return
|
|
30
|
+
return Constants.statusBarHeight === 44 ? 10 : -50;
|
|
29
31
|
} else {
|
|
30
32
|
return -50;
|
|
31
33
|
}
|
|
@@ -41,16 +43,43 @@ const SwipeablePanel = ({
|
|
|
41
43
|
onClose,
|
|
42
44
|
avoidScroll,
|
|
43
45
|
children,
|
|
44
|
-
}) => {
|
|
46
|
+
}: SwipeablePanelProps) => {
|
|
45
47
|
const LARGE_PANEL_HEIGHT = screenHeight - offset - calcMarginTop(); // This is the margin top
|
|
46
48
|
const MEDIUM_PANEL_HEIGHT = 400; // Fixed height
|
|
47
49
|
|
|
48
50
|
const [closeButtonClicked, setCloseButtonClicked] = useState(false);
|
|
49
51
|
const [status, setStatus] = useState(STATUS.LARGE);
|
|
50
52
|
const [animatedValueY, setAnimatedValueY] = useState(LARGE_PANEL_HEIGHT);
|
|
51
|
-
const pan = useRef(
|
|
52
|
-
|
|
53
|
-
)
|
|
53
|
+
const pan = useRef(new Animated.ValueXY({ x: 0, y: LARGE_PANEL_HEIGHT })).current;
|
|
54
|
+
|
|
55
|
+
const _animateTo = (newStatus = 0, firstAnimate?: boolean) => {
|
|
56
|
+
if (!firstAnimate && lockPanel) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (status === newStatus) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let newY = 0;
|
|
65
|
+
|
|
66
|
+
if (newStatus === STATUS.MEDIUM) {
|
|
67
|
+
newY = MEDIUM_PANEL_HEIGHT;
|
|
68
|
+
} else if (newStatus === STATUS.LARGE) {
|
|
69
|
+
newY = LARGE_PANEL_HEIGHT;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
setStatus(newStatus);
|
|
73
|
+
|
|
74
|
+
Animated.spring(pan, {
|
|
75
|
+
toValue: { x: 0, y: newY },
|
|
76
|
+
tension: 80,
|
|
77
|
+
friction: 25,
|
|
78
|
+
useNativeDriver: true,
|
|
79
|
+
restDisplacementThreshold: 10,
|
|
80
|
+
restSpeedThreshold: 10,
|
|
81
|
+
}).start();
|
|
82
|
+
};
|
|
54
83
|
|
|
55
84
|
useEffect(() => {
|
|
56
85
|
pan.y.addListener(({ value }) => setAnimatedValueY(value));
|
|
@@ -58,6 +87,7 @@ const SwipeablePanel = ({
|
|
|
58
87
|
if (autoMaximize) {
|
|
59
88
|
setTimeout(() => _animateTo(STATUS.MEDIUM), 2000);
|
|
60
89
|
}
|
|
90
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
61
91
|
}, [offset, autoMaximize]);
|
|
62
92
|
|
|
63
93
|
const panResponder = useRef(
|
|
@@ -76,10 +106,8 @@ const SwipeablePanel = ({
|
|
|
76
106
|
pan.setValue({ x: 0, y: 0 });
|
|
77
107
|
},
|
|
78
108
|
onPanResponderMove: (evt, gestureState) => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Math.abs(pan.y._value) <= pan.y._offset
|
|
82
|
-
) {
|
|
109
|
+
// @ts-expect-error omit ._value and ._offset
|
|
110
|
+
if (status === STATUS.MEDIUM && Math.abs(pan.y._value) <= pan.y._offset) {
|
|
83
111
|
pan.setValue({
|
|
84
112
|
x: 0,
|
|
85
113
|
y: gestureState.dy,
|
|
@@ -100,35 +128,6 @@ const SwipeablePanel = ({
|
|
|
100
128
|
})
|
|
101
129
|
).current;
|
|
102
130
|
|
|
103
|
-
const _animateTo = (newStatus = 0, firstAnimate) => {
|
|
104
|
-
if (!firstAnimate && lockPanel) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (status === newStatus) {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
let newY = 0;
|
|
113
|
-
|
|
114
|
-
if (newStatus === STATUS.MEDIUM) {
|
|
115
|
-
newY = MEDIUM_PANEL_HEIGHT;
|
|
116
|
-
} else if (newStatus === STATUS.LARGE) {
|
|
117
|
-
newY = LARGE_PANEL_HEIGHT;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
setStatus(newStatus);
|
|
121
|
-
|
|
122
|
-
Animated.spring(pan, {
|
|
123
|
-
toValue: { x: 0, y: newY },
|
|
124
|
-
tension: 80,
|
|
125
|
-
friction: 25,
|
|
126
|
-
useNativeDriver: true,
|
|
127
|
-
restDisplacementThreshold: 10,
|
|
128
|
-
restSpeedThreshold: 10,
|
|
129
|
-
}).start();
|
|
130
|
-
};
|
|
131
|
-
|
|
132
131
|
const renderContent = () => (
|
|
133
132
|
<TouchableWithoutFeedback onPress={() => _animateTo(STATUS.MEDIUM)}>
|
|
134
133
|
<View style={style.contentContainer}>{children}</View>
|
|
@@ -137,9 +136,7 @@ const SwipeablePanel = ({
|
|
|
137
136
|
|
|
138
137
|
return (
|
|
139
138
|
<Animated.View style={style.background}>
|
|
140
|
-
<TouchableWithoutFeedback
|
|
141
|
-
onPress={() => (maximized ? null : _animateTo(STATUS.LARGE))}
|
|
142
|
-
>
|
|
139
|
+
<TouchableWithoutFeedback onPress={() => (maximized ? null : _animateTo(STATUS.LARGE))}>
|
|
143
140
|
<View style={style.backgroundLayer} />
|
|
144
141
|
</TouchableWithoutFeedback>
|
|
145
142
|
<Animated.View
|
|
@@ -161,34 +158,32 @@ const SwipeablePanel = ({
|
|
|
161
158
|
<TouchableOpacity
|
|
162
159
|
onPress={() => {
|
|
163
160
|
setCloseButtonClicked(true);
|
|
164
|
-
onClose
|
|
161
|
+
if (onClose) {
|
|
162
|
+
onClose();
|
|
163
|
+
}
|
|
165
164
|
}}
|
|
166
165
|
style={style.closeButton}
|
|
167
166
|
>
|
|
168
|
-
<Ionicons name=
|
|
167
|
+
<Ionicons name="close-outline" size={20} color={Colors.darkblue} />
|
|
169
168
|
</TouchableOpacity>
|
|
170
169
|
)}
|
|
171
170
|
|
|
172
171
|
{closeAfterSeconds > 0 && (
|
|
173
172
|
<View style={style.autoClose}>
|
|
174
|
-
<
|
|
175
|
-
isPlaying={true}
|
|
173
|
+
<DonutCountdown
|
|
176
174
|
duration={closeAfterSeconds}
|
|
177
|
-
|
|
175
|
+
color={Colors.green}
|
|
178
176
|
onComplete={() => {
|
|
179
177
|
if (!closeButtonClicked) {
|
|
180
|
-
onClose
|
|
178
|
+
if (onClose) {
|
|
179
|
+
onClose();
|
|
180
|
+
}
|
|
181
181
|
}
|
|
182
182
|
}}
|
|
183
|
-
|
|
183
|
+
radius={16}
|
|
184
184
|
strokeWidth={4}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
<Animated.Text style={{ color: animatedColor, fontSize: 14 }}>
|
|
188
|
-
{remainingTime}
|
|
189
|
-
</Animated.Text>
|
|
190
|
-
)}
|
|
191
|
-
</CountdownCircleTimer>
|
|
185
|
+
textSize="small"
|
|
186
|
+
/>
|
|
192
187
|
</View>
|
|
193
188
|
)}
|
|
194
189
|
|
|
@@ -210,26 +205,4 @@ const SwipeablePanel = ({
|
|
|
210
205
|
);
|
|
211
206
|
};
|
|
212
207
|
|
|
213
|
-
SwipeablePanel.defaultProps = {
|
|
214
|
-
offset: 0,
|
|
215
|
-
maximized: false,
|
|
216
|
-
lockPanel: false,
|
|
217
|
-
autoMaximize: false,
|
|
218
|
-
closeButton: false,
|
|
219
|
-
avoidScroll: false,
|
|
220
|
-
closeAfterSeconds: 0,
|
|
221
|
-
onClose: () => {},
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
SwipeablePanel.propTypes = {
|
|
225
|
-
offset: PropTypes.number,
|
|
226
|
-
maximized: PropTypes.bool,
|
|
227
|
-
lockPanel: PropTypes.bool,
|
|
228
|
-
autoMaximize: PropTypes.bool,
|
|
229
|
-
closeAfterSeconds: PropTypes.number,
|
|
230
|
-
closeButton: PropTypes.bool,
|
|
231
|
-
avoidScroll: PropTypes.bool,
|
|
232
|
-
onClose: PropTypes.func,
|
|
233
|
-
};
|
|
234
|
-
|
|
235
208
|
export default SwipeablePanel;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {Dimensions} from 'react-native';
|
|
2
|
-
import {Colors} from '../../configs/constants';
|
|
1
|
+
import { Dimensions, StyleSheet } from 'react-native';
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
import { Colors } from '../../configs/constants';
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
const { width } = Dimensions.get('window');
|
|
6
|
+
|
|
7
|
+
export default StyleSheet.create({
|
|
7
8
|
background: {
|
|
8
9
|
position: 'relative',
|
|
9
10
|
width,
|
|
@@ -11,11 +12,11 @@ export default {
|
|
|
11
12
|
flex: 1,
|
|
12
13
|
justifyContent: 'center',
|
|
13
14
|
alignItems: 'center',
|
|
14
|
-
overflow: 'hidden'
|
|
15
|
+
overflow: 'hidden',
|
|
15
16
|
},
|
|
16
17
|
backgroundLayer: {
|
|
17
18
|
width,
|
|
18
|
-
flex: 1
|
|
19
|
+
flex: 1,
|
|
19
20
|
},
|
|
20
21
|
panel: {
|
|
21
22
|
position: 'absolute',
|
|
@@ -41,25 +42,25 @@ export default {
|
|
|
41
42
|
elevation: 1,
|
|
42
43
|
zIndex: 2,
|
|
43
44
|
paddingHorizontal: 20,
|
|
44
|
-
paddingBottom: 20
|
|
45
|
+
paddingBottom: 20,
|
|
45
46
|
},
|
|
46
47
|
barContainer: {
|
|
47
48
|
height: 50,
|
|
48
49
|
justifyContent: 'center',
|
|
49
|
-
alignItems: 'center'
|
|
50
|
+
alignItems: 'center',
|
|
50
51
|
},
|
|
51
52
|
bar: {
|
|
52
53
|
backgroundColor: Colors.lightblue,
|
|
53
54
|
height: 5,
|
|
54
55
|
width: width * 0.12,
|
|
55
|
-
borderRadius: 5
|
|
56
|
+
borderRadius: 5,
|
|
56
57
|
},
|
|
57
58
|
content: {
|
|
58
|
-
paddingHorizontal: 20
|
|
59
|
+
paddingHorizontal: 20,
|
|
59
60
|
},
|
|
60
61
|
contentContainer: {
|
|
61
62
|
width: '100%',
|
|
62
|
-
flex: 1
|
|
63
|
+
flex: 1,
|
|
63
64
|
},
|
|
64
65
|
closeButton: {
|
|
65
66
|
position: 'absolute',
|
|
@@ -70,11 +71,11 @@ export default {
|
|
|
70
71
|
borderRadius: 15,
|
|
71
72
|
backgroundColor: Colors.lightblue,
|
|
72
73
|
alignItems: 'center',
|
|
73
|
-
justifyContent: 'center'
|
|
74
|
+
justifyContent: 'center',
|
|
74
75
|
},
|
|
75
76
|
autoClose: {
|
|
76
77
|
position: 'absolute',
|
|
77
78
|
left: 10,
|
|
78
|
-
top: 10
|
|
79
|
+
top: 10,
|
|
79
80
|
},
|
|
80
|
-
};
|
|
81
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Platform, Switch as RNSwitch } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { Colors } from '../../configs/constants';
|
|
4
|
+
|
|
5
|
+
import type { SwitchProps } from '../../configs/types';
|
|
6
|
+
|
|
7
|
+
const Switch = ({ value, size = 'medium', ...props }: SwitchProps) => (
|
|
8
|
+
<RNSwitch
|
|
9
|
+
trackColor={{
|
|
10
|
+
false: Colors.lightblue,
|
|
11
|
+
true: Colors.lightblue,
|
|
12
|
+
}}
|
|
13
|
+
thumbColor={value ? Colors.green : Colors.midblue}
|
|
14
|
+
ios_backgroundColor={Colors.white}
|
|
15
|
+
value={value}
|
|
16
|
+
style={{
|
|
17
|
+
transform: [
|
|
18
|
+
{
|
|
19
|
+
scaleX: size === 'small' ? (Platform.OS === 'ios' ? 0.5 : 0.7) : Platform.OS === 'ios' ? 0.7 : 0.9,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
scaleY: size === 'small' ? (Platform.OS === 'ios' ? 0.5 : 0.7) : Platform.OS === 'ios' ? 0.7 : 0.9,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
}}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
export default Switch;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
1
|
import { Modal, TouchableOpacity, View } from 'react-native';
|
|
4
|
-
|
|
2
|
+
|
|
5
3
|
import ImageResponsive from '../ImageResponsive';
|
|
4
|
+
import Text from '../Text';
|
|
6
5
|
import style from './style';
|
|
7
6
|
|
|
7
|
+
import type { ConfirmPictureModalProps } from '../../configs/types';
|
|
8
|
+
|
|
8
9
|
export const ConfirmPictureModal = ({
|
|
9
10
|
avatar,
|
|
10
11
|
visible,
|
|
@@ -13,18 +14,13 @@ export const ConfirmPictureModal = ({
|
|
|
13
14
|
image,
|
|
14
15
|
repeatPictureText,
|
|
15
16
|
usePictureText,
|
|
16
|
-
}) => (
|
|
17
|
-
<Modal
|
|
18
|
-
animationType='slide'
|
|
19
|
-
transparent={false}
|
|
20
|
-
visible={visible}
|
|
21
|
-
onRequestClose={() => {}}
|
|
22
|
-
>
|
|
17
|
+
}: ConfirmPictureModalProps) => (
|
|
18
|
+
<Modal animationType="slide" transparent={false} visible={visible} onRequestClose={() => {}}>
|
|
23
19
|
<View style={style.cameraTakenImageContainer}>
|
|
24
20
|
<ImageResponsive
|
|
25
|
-
source={
|
|
26
|
-
uri
|
|
27
|
-
}
|
|
21
|
+
source={
|
|
22
|
+
image && typeof image === 'object' && 'uri' in image ? { uri: image.uri } : image === null ? undefined : image
|
|
23
|
+
}
|
|
28
24
|
style={avatar ? style.cameraTakenImage : style.cameraContainer}
|
|
29
25
|
/>
|
|
30
26
|
|
|
@@ -39,23 +35,3 @@ export const ConfirmPictureModal = ({
|
|
|
39
35
|
</View>
|
|
40
36
|
</Modal>
|
|
41
37
|
);
|
|
42
|
-
|
|
43
|
-
ConfirmPictureModal.defaultProps = {
|
|
44
|
-
visible: false,
|
|
45
|
-
avatar: false,
|
|
46
|
-
repeatPictureText: ' ',
|
|
47
|
-
usePictureText: ' ',
|
|
48
|
-
image: null,
|
|
49
|
-
onRepeatPhoto: () => {},
|
|
50
|
-
onUsePhoto: () => {},
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
ConfirmPictureModal.propTypes = {
|
|
54
|
-
visible: PropTypes.bool.isRequired,
|
|
55
|
-
avatar: PropTypes.bool,
|
|
56
|
-
repeatPictureText: PropTypes.string.isRequired,
|
|
57
|
-
usePictureText: PropTypes.string.isRequired,
|
|
58
|
-
image: PropTypes.object,
|
|
59
|
-
onRepeatPhoto: PropTypes.func.isRequired,
|
|
60
|
-
onUsePhoto: PropTypes.func.isRequired,
|
|
61
|
-
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { CameraType } from 'expo-camera';
|
|
2
|
+
|
|
3
|
+
import { Ionicons } from '@expo/vector-icons';
|
|
4
|
+
import { CameraView, useCameraPermissions } from 'expo-camera';
|
|
5
|
+
import * as ImageManipulator from 'expo-image-manipulator';
|
|
6
|
+
import { useRef, useState } from 'react';
|
|
7
|
+
import { ActivityIndicator, Modal, TouchableOpacity, View } from 'react-native';
|
|
8
|
+
import { Circle, Defs, Mask, Rect, Svg } from 'react-native-svg';
|
|
9
|
+
|
|
10
|
+
import { Colors } from '../../configs/constants';
|
|
11
|
+
import Button from '../Button';
|
|
12
|
+
import Container from '../Container';
|
|
13
|
+
import Text from '../Text';
|
|
14
|
+
import { ConfirmPictureModal } from './confirmPictureModal';
|
|
15
|
+
import style from './style';
|
|
16
|
+
|
|
17
|
+
import type { TakePictureProps } from '../../configs/types';
|
|
18
|
+
|
|
19
|
+
const DESIRED_RATIO = '16:9';
|
|
20
|
+
|
|
21
|
+
const SvgCircle = () => (
|
|
22
|
+
<Svg height="100%" width="100%">
|
|
23
|
+
<Defs>
|
|
24
|
+
<Mask id="mask" x="0" y="0" height="100%" width="100%">
|
|
25
|
+
<Rect height="100%" width="100%" fill={Colors.white} />
|
|
26
|
+
<Circle r="29%" cx="50%" cy="50%" fill={Colors.darkgray} />
|
|
27
|
+
</Mask>
|
|
28
|
+
</Defs>
|
|
29
|
+
<Rect height="100%" width="100%" fill="rgba(0, 0, 0, 0.8)" mask="url(#mask)" fill-opacity="0" />
|
|
30
|
+
</Svg>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const TakePicture = ({
|
|
34
|
+
visible,
|
|
35
|
+
onClose,
|
|
36
|
+
avatar,
|
|
37
|
+
cameraErrorMessage,
|
|
38
|
+
requestPermissionsMessage,
|
|
39
|
+
processingPictureMessage,
|
|
40
|
+
repeatPictureText,
|
|
41
|
+
usePictureText,
|
|
42
|
+
}: TakePictureProps) => {
|
|
43
|
+
const [permission, requestPermission] = useCameraPermissions();
|
|
44
|
+
const [type, setType] = useState<CameraType>('back');
|
|
45
|
+
const [image, setImage] = useState<ImageManipulator.ImageResult | null>(null);
|
|
46
|
+
const [takingPicture, setTakingPicture] = useState<boolean>(false);
|
|
47
|
+
const camera = useRef<CameraView>(null);
|
|
48
|
+
|
|
49
|
+
const flipCamera = () => {
|
|
50
|
+
setType((current) => (current === 'back' ? 'front' : 'back'));
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const takePicture = async () => {
|
|
54
|
+
if (camera.current) {
|
|
55
|
+
setTakingPicture(true);
|
|
56
|
+
const tempImage = await camera.current.takePictureAsync({ quality: 1.0 });
|
|
57
|
+
|
|
58
|
+
if (avatar) {
|
|
59
|
+
const avatarImage = await ImageManipulator.manipulateAsync(
|
|
60
|
+
tempImage.uri,
|
|
61
|
+
[
|
|
62
|
+
{
|
|
63
|
+
crop: {
|
|
64
|
+
originX: 0,
|
|
65
|
+
originY: (tempImage.height - tempImage.width) / 2,
|
|
66
|
+
width: tempImage.width,
|
|
67
|
+
height: tempImage.width,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
{ compress: 1.0, format: ImageManipulator.SaveFormat.PNG }
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
setImage(avatarImage);
|
|
75
|
+
setTakingPicture(false);
|
|
76
|
+
} else {
|
|
77
|
+
setImage(tempImage);
|
|
78
|
+
setTakingPicture(false);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const repeatPhoto = () => {
|
|
84
|
+
setImage(null);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const usePhoto = () => {
|
|
88
|
+
if (image) {
|
|
89
|
+
onClose(typeof image === 'object' ? image.uri : image);
|
|
90
|
+
setImage(null);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const closeModal = () => {
|
|
95
|
+
setImage(null);
|
|
96
|
+
onClose();
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
if (!permission?.granted) {
|
|
100
|
+
return (
|
|
101
|
+
<View style={style.cameraPermissionsContainer}>
|
|
102
|
+
<Text color="red">{cameraErrorMessage}</Text>
|
|
103
|
+
<Button onPress={requestPermission} variant="inverted">
|
|
104
|
+
<Text color="white">{requestPermissionsMessage}</Text>
|
|
105
|
+
</Button>
|
|
106
|
+
</View>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<Modal animationType="slide" transparent={false} visible={visible} onRequestClose={() => onClose()}>
|
|
112
|
+
<Container style={style.cameraContainer}>
|
|
113
|
+
<CameraView ref={camera} style={style.cameraContainer} facing={type} ratio={DESIRED_RATIO}>
|
|
114
|
+
{avatar && <SvgCircle />}
|
|
115
|
+
|
|
116
|
+
<View style={style.cameraFlipContainer}>
|
|
117
|
+
<TouchableOpacity onPress={closeModal} style={style.closeIcon}>
|
|
118
|
+
<Ionicons name="close-outline" color={Colors.darkblue} size={24} />
|
|
119
|
+
</TouchableOpacity>
|
|
120
|
+
<TouchableOpacity style={style.cameraFlipBtn} onPress={flipCamera}>
|
|
121
|
+
<Ionicons name="camera-reverse-outline" style={style.cameraFlipIcon} color={Colors.white} size={40} />
|
|
122
|
+
</TouchableOpacity>
|
|
123
|
+
<TouchableOpacity style={style.cameraRecordBtn} onPress={takePicture}>
|
|
124
|
+
<Ionicons name="radio-button-on-outline" color={Colors.red} size={60} />
|
|
125
|
+
</TouchableOpacity>
|
|
126
|
+
</View>
|
|
127
|
+
</CameraView>
|
|
128
|
+
|
|
129
|
+
{takingPicture && (
|
|
130
|
+
<View style={style.cameraTakingPictureOverlay}>
|
|
131
|
+
<ActivityIndicator color={Colors.darkblue} />
|
|
132
|
+
<Text color="white">{processingPictureMessage} ...</Text>
|
|
133
|
+
</View>
|
|
134
|
+
)}
|
|
135
|
+
<ConfirmPictureModal
|
|
136
|
+
visible={image !== null}
|
|
137
|
+
image={image}
|
|
138
|
+
repeatPictureText={repeatPictureText}
|
|
139
|
+
usePictureText={usePictureText}
|
|
140
|
+
onRepeatPhoto={repeatPhoto}
|
|
141
|
+
onUsePhoto={usePhoto}
|
|
142
|
+
/>
|
|
143
|
+
</Container>
|
|
144
|
+
</Modal>
|
|
145
|
+
);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export default TakePicture;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Dimensions, StyleSheet } from 'react-native';
|
|
2
|
+
|
|
2
3
|
import { Colors } from '../../configs/constants';
|
|
3
4
|
|
|
4
5
|
const { width } = Dimensions.get('window');
|
|
5
6
|
|
|
6
|
-
export default {
|
|
7
|
+
export default StyleSheet.create({
|
|
7
8
|
cameraContainer: {
|
|
8
9
|
flex: 1,
|
|
9
10
|
...StyleSheet.absoluteFillObject,
|
|
@@ -25,7 +26,6 @@ export default {
|
|
|
25
26
|
flex: 1,
|
|
26
27
|
backgroundColor: Colors.transparent,
|
|
27
28
|
flexDirection: 'row',
|
|
28
|
-
position: 'relative',
|
|
29
29
|
marginBottom: 20,
|
|
30
30
|
...StyleSheet.absoluteFillObject,
|
|
31
31
|
},
|
|
@@ -92,4 +92,4 @@ export default {
|
|
|
92
92
|
justifyContent: 'center',
|
|
93
93
|
alignItems: 'center',
|
|
94
94
|
},
|
|
95
|
-
};
|
|
95
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Text as RNText } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import style from './style';
|
|
4
|
+
|
|
5
|
+
import type { TextProps } from '../../configs/types';
|
|
6
|
+
|
|
7
|
+
const Text = ({
|
|
8
|
+
variant = 'default',
|
|
9
|
+
size = 'medium',
|
|
10
|
+
color = 'dark',
|
|
11
|
+
weight = 'regular',
|
|
12
|
+
align = 'left',
|
|
13
|
+
style: customStyle = {},
|
|
14
|
+
children,
|
|
15
|
+
...props
|
|
16
|
+
}: TextProps) => (
|
|
17
|
+
<RNText
|
|
18
|
+
style={[
|
|
19
|
+
style[variant as keyof typeof style],
|
|
20
|
+
style[size as keyof typeof style],
|
|
21
|
+
style[color as keyof typeof style],
|
|
22
|
+
style[weight as keyof typeof style],
|
|
23
|
+
style[align as keyof typeof style],
|
|
24
|
+
{ ...customStyle },
|
|
25
|
+
]}
|
|
26
|
+
allowFontScaling={false}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
</RNText>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export default Text;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { StyleSheet } from 'react-native';
|
|
2
|
+
|
|
1
3
|
import { Colors } from '../../configs/constants';
|
|
2
4
|
|
|
3
|
-
export default {
|
|
5
|
+
export default StyleSheet.create({
|
|
4
6
|
// colors
|
|
5
7
|
dark: {
|
|
6
8
|
color: Colors.darkblue,
|
|
@@ -96,4 +98,4 @@ export default {
|
|
|
96
98
|
justify: {
|
|
97
99
|
textAlign: 'justify',
|
|
98
100
|
},
|
|
99
|
-
};
|
|
101
|
+
});
|
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
1
|
import { TextInput } from 'react-native';
|
|
2
|
+
|
|
4
3
|
import { Colors } from '../../configs/constants';
|
|
5
4
|
import style from './style';
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
numberOfLines = 4,
|
|
11
|
-
fitToContainer,
|
|
12
|
-
...props
|
|
13
|
-
}) => (
|
|
6
|
+
import type { TextareaProps } from '../../configs/types';
|
|
7
|
+
|
|
8
|
+
const Textarea = ({ variant = 'default', disabled, numberOfLines = 4, fitToContainer, ...props }: TextareaProps) => (
|
|
14
9
|
<TextInput
|
|
15
10
|
style={[
|
|
16
11
|
style.textarea,
|
|
@@ -22,24 +17,10 @@ const Textarea = ({
|
|
|
22
17
|
multiline
|
|
23
18
|
editable={!disabled}
|
|
24
19
|
numberOfLines={numberOfLines}
|
|
25
|
-
textAlignVertical=
|
|
20
|
+
textAlignVertical="top"
|
|
26
21
|
placeholderTextColor={Colors.midblue}
|
|
27
22
|
{...props}
|
|
28
23
|
/>
|
|
29
24
|
);
|
|
30
25
|
|
|
31
|
-
Textarea.defaultProps = {
|
|
32
|
-
variant: 'default',
|
|
33
|
-
disabled: false,
|
|
34
|
-
fitToContainer: false,
|
|
35
|
-
numberOfLines: 4,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
Textarea.propTypes = {
|
|
39
|
-
variant: PropTypes.oneOf(['default', 'completed', 'error']),
|
|
40
|
-
disabled: PropTypes.bool,
|
|
41
|
-
fitToContainer: PropTypes.bool,
|
|
42
|
-
numberOfLines: PropTypes.number,
|
|
43
|
-
};
|
|
44
|
-
|
|
45
26
|
export default Textarea;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Dimensions } from 'react-native';
|
|
2
|
-
|
|
1
|
+
import { Dimensions, StyleSheet } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import { borderRadius, Colors } from '../../configs/constants';
|
|
3
4
|
|
|
4
5
|
const { width } = Dimensions.get('window');
|
|
5
6
|
|
|
6
|
-
export default {
|
|
7
|
+
export default StyleSheet.create({
|
|
7
8
|
textarea: {
|
|
8
9
|
borderRadius,
|
|
9
10
|
borderWidth: 1,
|
|
@@ -34,4 +35,4 @@ export default {
|
|
|
34
35
|
disabled: {
|
|
35
36
|
opacity: 0.5,
|
|
36
37
|
},
|
|
37
|
-
};
|
|
38
|
+
});
|