1 | import * as React from 'react';
|
2 | import { Animated, BackHandler, Easing, StyleSheet, TouchableWithoutFeedback, View } from 'react-native';
|
3 | import { getStatusBarHeight, getBottomSpace } from 'react-native-iphone-x-helper';
|
4 | import Surface from './Surface';
|
5 | import { withTheme } from '../core/theming';
|
6 | import useAnimatedValue from '../utils/useAnimatedValue';
|
7 | import { addEventListener } from '../utils/addEventListener';
|
8 | const DEFAULT_DURATION = 220;
|
9 | const TOP_INSET = getStatusBarHeight(true);
|
10 | const BOTTOM_INSET = getBottomSpace();
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | function Modal(_ref) {
|
52 | let {
|
53 | dismissable = true,
|
54 | visible = false,
|
55 | overlayAccessibilityLabel = 'Close modal',
|
56 | onDismiss,
|
57 | children,
|
58 | contentContainerStyle,
|
59 | style,
|
60 | theme,
|
61 | testID
|
62 | } = _ref;
|
63 | const visibleRef = React.useRef(visible);
|
64 | React.useEffect(() => {
|
65 | visibleRef.current = visible;
|
66 | });
|
67 | const {
|
68 | colors,
|
69 | animation
|
70 | } = theme;
|
71 | const opacity = useAnimatedValue(visible ? 1 : 0);
|
72 | const [rendered, setRendered] = React.useState(visible);
|
73 |
|
74 | if (visible && !rendered) {
|
75 | setRendered(true);
|
76 | }
|
77 |
|
78 | const handleBack = () => {
|
79 | if (dismissable) {
|
80 | hideModal();
|
81 | }
|
82 |
|
83 | return true;
|
84 | };
|
85 |
|
86 | const subscription = React.useRef(undefined);
|
87 |
|
88 | const showModal = () => {
|
89 | var _subscription$current;
|
90 |
|
91 | (_subscription$current = subscription.current) === null || _subscription$current === void 0 ? void 0 : _subscription$current.remove();
|
92 | subscription.current = addEventListener(BackHandler, 'hardwareBackPress', handleBack);
|
93 | const {
|
94 | scale
|
95 | } = animation;
|
96 | Animated.timing(opacity, {
|
97 | toValue: 1,
|
98 | duration: scale * DEFAULT_DURATION,
|
99 | easing: Easing.out(Easing.cubic),
|
100 | useNativeDriver: true
|
101 | }).start();
|
102 | };
|
103 |
|
104 | const removeListeners = () => {
|
105 | var _subscription$current2;
|
106 |
|
107 | if ((_subscription$current2 = subscription.current) !== null && _subscription$current2 !== void 0 && _subscription$current2.remove) {
|
108 | var _subscription$current3;
|
109 |
|
110 | (_subscription$current3 = subscription.current) === null || _subscription$current3 === void 0 ? void 0 : _subscription$current3.remove();
|
111 | } else {
|
112 | BackHandler.removeEventListener('hardwareBackPress', handleBack);
|
113 | }
|
114 | };
|
115 |
|
116 | const hideModal = () => {
|
117 | removeListeners();
|
118 | const {
|
119 | scale
|
120 | } = animation;
|
121 | Animated.timing(opacity, {
|
122 | toValue: 0,
|
123 | duration: scale * DEFAULT_DURATION,
|
124 | easing: Easing.out(Easing.cubic),
|
125 | useNativeDriver: true
|
126 | }).start(_ref2 => {
|
127 | let {
|
128 | finished
|
129 | } = _ref2;
|
130 |
|
131 | if (!finished) {
|
132 | return;
|
133 | }
|
134 |
|
135 | if (visible && onDismiss) {
|
136 | onDismiss();
|
137 | }
|
138 |
|
139 | if (visibleRef.current) {
|
140 | showModal();
|
141 | } else {
|
142 | setRendered(false);
|
143 | }
|
144 | });
|
145 | };
|
146 |
|
147 | const prevVisible = React.useRef(null);
|
148 | React.useEffect(() => {
|
149 | if (prevVisible.current !== visible) {
|
150 | if (visible) {
|
151 | showModal();
|
152 | } else {
|
153 | hideModal();
|
154 | }
|
155 | }
|
156 |
|
157 | prevVisible.current = visible;
|
158 | });
|
159 | React.useEffect(() => {
|
160 | return removeListeners;
|
161 | }, []);
|
162 | if (!rendered) return null;
|
163 | return React.createElement(Animated.View, {
|
164 | pointerEvents: visible ? 'auto' : 'none',
|
165 | accessibilityViewIsModal: true,
|
166 | accessibilityLiveRegion: "polite",
|
167 | style: StyleSheet.absoluteFill,
|
168 | onAccessibilityEscape: hideModal,
|
169 | testID: testID
|
170 | }, React.createElement(TouchableWithoutFeedback, {
|
171 | accessibilityLabel: overlayAccessibilityLabel,
|
172 | accessibilityRole: "button",
|
173 | disabled: !dismissable,
|
174 | onPress: dismissable ? hideModal : undefined,
|
175 | importantForAccessibility: "no"
|
176 | }, React.createElement(Animated.View, {
|
177 | testID: `${testID}-backdrop`,
|
178 | style: [styles.backdrop, {
|
179 | backgroundColor: colors.backdrop,
|
180 | opacity
|
181 | }]
|
182 | })), React.createElement(View, {
|
183 | style: [styles.wrapper, {
|
184 | marginTop: TOP_INSET,
|
185 | marginBottom: BOTTOM_INSET
|
186 | }, style],
|
187 | pointerEvents: "box-none"
|
188 | }, React.createElement(Surface, {
|
189 | style: [{
|
190 | opacity
|
191 | }, styles.content, contentContainerStyle]
|
192 | }, children)));
|
193 | }
|
194 |
|
195 | export default withTheme(Modal);
|
196 | const styles = StyleSheet.create({
|
197 | backdrop: {
|
198 | flex: 1
|
199 | },
|
200 | wrapper: { ...StyleSheet.absoluteFillObject,
|
201 | justifyContent: 'center'
|
202 | },
|
203 | content: {
|
204 | backgroundColor: 'transparent',
|
205 | justifyContent: 'center'
|
206 | }
|
207 | });
|
208 |
|
\ | No newline at end of file |