@haroldtran/react-native-modals 0.0.6 → 0.0.10

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.
@@ -1,9 +1,7 @@
1
- // @flow
2
-
3
1
  import React from 'react';
4
2
  import { StyleSheet } from 'react-native';
5
- import type { ModalProps } from '../type';
6
3
  import SlideAnimation from '../animations/SlideAnimation';
4
+ import type { ModalProps } from '../type';
7
5
  import BaseModal from './BaseModal';
8
6
 
9
7
  const styles = StyleSheet.create({
@@ -16,21 +14,27 @@ const styles = StyleSheet.create({
16
14
  },
17
15
  });
18
16
 
19
- const BottomModal = ({
20
- style,
21
- modalStyle,
22
- ...restProps
23
- }: ModalProps) => (
24
- <BaseModal
25
- modalAnimation={new SlideAnimation({
26
- slideFrom: 'bottom',
27
- })}
28
- {...restProps}
29
- style={StyleSheet.flatten([styles.container, style])}
30
- modalStyle={StyleSheet.flatten([styles.modal, modalStyle])}
31
- width={1}
32
- swipeDirection="down"
33
- />
34
- );
17
+ const BottomModal = (props: ModalProps) => {
18
+ const { style, modalStyle, ...restProps } = props;
19
+
20
+ const modalAnimation = React.useMemo(
21
+ () =>
22
+ new SlideAnimation({
23
+ slideFrom: 'bottom',
24
+ }),
25
+ [],
26
+ );
27
+
28
+ return (
29
+ <BaseModal
30
+ modalAnimation={modalAnimation}
31
+ {...restProps}
32
+ style={StyleSheet.flatten([styles.container, style])}
33
+ modalStyle={StyleSheet.flatten([styles.modal, modalStyle])}
34
+ width={1}
35
+ swipeDirection="down"
36
+ />
37
+ );
38
+ };
35
39
 
36
40
  export default BottomModal;
@@ -1,6 +1,6 @@
1
- import React, { Component } from "react";
2
- import { Animated, Dimensions, PanResponder } from "react-native";
3
- import type { DragEvent, SwipeDirection } from "../type";
1
+ import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react';
2
+ import { Animated, PanResponder, PanResponderGestureState, useWindowDimensions } from 'react-native';
3
+ import type { DragEvent, SwipeDirection } from '../type';
4
4
 
5
5
  type Props = {
6
6
  style?: any;
@@ -11,238 +11,203 @@ type Props = {
11
11
  onSwipeOut?: (event: DragEvent) => void;
12
12
  swipeThreshold?: number;
13
13
  swipeDirection?: SwipeDirection | Array<SwipeDirection>;
14
- children: ({
15
- onLayout,
16
- pan,
17
- }: {
18
- onLayout: (event: any) => void;
19
- pan: Animated.ValueXY;
20
- }) => React.ReactNode;
14
+ pointerEvents?: 'auto' | 'none' | 'box-none' | 'box-only';
15
+ children: (args: { onLayout: (event: any) => void; pan: Animated.ValueXY }) => React.ReactNode;
21
16
  };
22
17
 
23
- export default class DraggableView extends Component<Props> {
24
- static defaultProps = {
25
- style: null,
26
- onMove: () => {},
27
- onSwiping: () => {},
28
- onSwipingOut: () => {},
29
- onSwipeOut: null,
30
- onRelease: () => {},
31
- swipeThreshold: 100,
32
- swipeDirection: [],
33
- };
34
-
35
- // instance properties
36
- pan: Animated.ValueXY;
37
- allowedDirections: SwipeDirection[];
38
- layout: { x: number; y: number; width: number; height: number } | null;
39
- panEventListenerId: string | number | null = null;
40
- currentSwipeDirection: SwipeDirection | null = null;
41
-
42
- constructor(props: Props) {
43
- super(props);
44
-
45
- this.pan = new Animated.ValueXY();
46
- this.allowedDirections = ([] as SwipeDirection[]).concat(
47
- props.swipeDirection || [],
48
- );
49
- this.layout = null;
50
- }
51
-
52
- componentDidMount() {
53
- this.panEventListenerId = this.pan.addListener(
54
- (axis: { x: number; y: number }) => {
55
- this.props.onMove?.(this.createDragEvent(axis));
56
- },
57
- );
58
- }
59
-
60
- componentWillUnmount() {
61
- if (this.panEventListenerId != null) {
62
- this.pan.removeListener(this.panEventListenerId as string);
63
- }
64
- }
65
-
66
- onLayout = (event: any) => {
67
- this.layout = event.nativeEvent.layout;
68
- };
69
-
70
- getSwipeDirection(gestureState: any): SwipeDirection | null {
71
- if (this.isValidHorizontalSwipe(gestureState)) {
72
- return gestureState.dx > 0 ? "right" : "left";
73
- } else if (this.isValidVerticalSwipe(gestureState)) {
74
- return gestureState.dy > 0 ? "down" : "up";
75
- }
76
- return null;
77
- }
78
-
79
- getDisappearDirection() {
80
- const { width, height } = Dimensions.get("window");
81
- const vertical = height / 2 + (this.layout ? this.layout.height / 2 : 0);
82
- const horizontal = width / 2 + (this.layout ? this.layout.width / 2 : 0);
83
- let toValue;
84
- if (this.currentSwipeDirection === "up") {
85
- toValue = {
86
- x: 0,
87
- y: -vertical,
88
- };
89
- } else if (this.currentSwipeDirection === "down") {
90
- toValue = {
91
- x: 0,
92
- y: vertical,
93
- };
94
- } else if (this.currentSwipeDirection === "left") {
95
- toValue = {
96
- x: -horizontal,
97
- y: 0,
98
- };
99
- } else if (this.currentSwipeDirection === "right") {
100
- toValue = {
101
- x: horizontal,
102
- y: 0,
103
- };
104
- }
105
- return toValue;
106
- }
107
-
108
- isValidHorizontalSwipe({ vx, dy }: any) {
109
- return this.isValidSwipe(vx, dy);
110
- }
18
+ const DraggableView = memo((props: Props) => {
19
+ const {
20
+ style = null,
21
+ onMove = () => {},
22
+ onSwiping = () => {},
23
+ onSwipingOut = () => {},
24
+ onSwipeOut = null,
25
+ onRelease = () => {},
26
+ swipeThreshold = 100,
27
+ swipeDirection = [],
28
+ pointerEvents = 'auto',
29
+ children: renderContent,
30
+ } = props;
31
+
32
+ const { width: screenWidth, height: screenHeight } = useWindowDimensions();
33
+
34
+ const pan = useRef(new Animated.ValueXY()).current;
35
+ const layoutRef = useRef<{ x: number; y: number; width: number; height: number } | null>(null);
36
+ const currentSwipeDirectionRef = useRef<SwipeDirection | null>(null);
37
+
38
+ // Track pan values without using ._value
39
+ const propsRef = useRef(props);
40
+ propsRef.current = props;
41
+
42
+ const panValueRef = useRef({ x: 0, y: 0 });
43
+
44
+ const allowedDirections = useMemo(
45
+ () => ([] as SwipeDirection[]).concat(props.swipeDirection || []),
46
+ [props.swipeDirection],
47
+ );
48
+
49
+ useEffect(() => {
50
+ const panListenerId = pan.addListener((value) => {
51
+ panValueRef.current = value;
52
+ propsRef.current.onMove?.({
53
+ axis: value,
54
+ layout: layoutRef.current,
55
+ swipeDirection: currentSwipeDirectionRef.current,
56
+ });
57
+ });
58
+ return () => pan.removeListener(panListenerId);
59
+ }, [pan]);
111
60
 
112
- isValidVerticalSwipe({ vy, dx }: any) {
113
- return this.isValidSwipe(vy, dx);
114
- }
61
+ const onLayout = useCallback((event: any) => {
62
+ layoutRef.current = event.nativeEvent.layout;
63
+ }, []);
115
64
 
116
- // eslint-disable-next-line class-methods-use-this
117
- isValidSwipe(velocity: number, directionalOffset: number) {
65
+ const getSwipeDirection = useCallback((gestureState: PanResponderGestureState): SwipeDirection | null => {
66
+ const { dx, dy, vx, vy } = gestureState;
118
67
  const velocityThreshold = 0.3;
119
68
  const directionalOffsetThreshold = 80;
120
- // eslint-disable-next-line max-len
121
- return (
122
- Math.abs(velocity) > velocityThreshold &&
123
- Math.abs(directionalOffset) < directionalOffsetThreshold
124
- );
125
- }
126
-
127
- isAllowedDirection({ dy, dx }: any) {
128
- const draggedDown = dy > 0;
129
- const draggedUp = dy < 0;
130
- const draggedLeft = dx < 0;
131
- const draggedRight = dx > 0;
132
- const isAllowedDirection = (d: SwipeDirection) =>
133
- this.currentSwipeDirection === d && this.allowedDirections.includes(d);
134
- if (draggedDown && isAllowedDirection("down")) {
135
- return true;
136
- } else if (draggedUp && isAllowedDirection("up")) {
137
- return true;
138
- } else if (draggedLeft && isAllowedDirection("left")) {
139
- return true;
140
- } else if (draggedRight && isAllowedDirection("right")) {
141
- return true;
69
+
70
+ if (Math.abs(vx) > velocityThreshold && Math.abs(dy) < directionalOffsetThreshold) {
71
+ return dx > 0 ? 'right' : 'left';
72
+ } else if (Math.abs(vy) > velocityThreshold && Math.abs(dx) < directionalOffsetThreshold) {
73
+ return dy > 0 ? 'down' : 'up';
142
74
  }
143
- return false;
144
- }
145
-
146
- createDragEvent(axis: { x: number; y: number }): DragEvent {
147
- return {
148
- axis,
149
- layout: this.layout,
150
- swipeDirection: this.currentSwipeDirection,
151
- };
152
- }
153
-
154
- panResponder = PanResponder.create({
155
- onMoveShouldSetPanResponder: (evt, gestureState) =>
156
- gestureState.dx !== 0 && gestureState.dy !== 0,
157
- onStartShouldSetPanResponder: () => true,
158
- onPanResponderMove: (event: any, gestureState: any) => {
159
- const isVerticalSwipe = (d: SwipeDirection | null) =>
160
- ["up", "down"].includes(d as string);
161
- const isHorizontalSwipe = (d: SwipeDirection | null) =>
162
- ["left", "right"].includes(d as string);
163
-
164
- const newSwipeDirection = this.getSwipeDirection(gestureState);
165
- const isSameDirection =
166
- isVerticalSwipe(this.currentSwipeDirection) ===
167
- isVerticalSwipe(newSwipeDirection) ||
168
- isHorizontalSwipe(this.currentSwipeDirection) ===
169
- isHorizontalSwipe(newSwipeDirection);
170
- // newDirection & currentSwipeDirection must be same direction
171
- if (newSwipeDirection && isSameDirection) {
172
- this.currentSwipeDirection = newSwipeDirection;
173
- }
174
- if (this.isAllowedDirection(gestureState)) {
175
- let animEvent: any;
176
- if (isVerticalSwipe(this.currentSwipeDirection)) {
177
- animEvent = { dy: this.pan.y };
178
- } else if (isHorizontalSwipe(this.currentSwipeDirection)) {
179
- animEvent = { dx: this.pan.x };
180
- }
181
- if (animEvent) {
182
- Animated.event([null, animEvent], { useNativeDriver: false })(
183
- event,
184
- gestureState,
185
- );
186
- }
187
- this.props.onSwiping?.(
188
- this.createDragEvent({
189
- x: (this.pan.x as any)._value,
190
- y: (this.pan.y as any)._value,
191
- }),
192
- );
193
- }
194
- },
195
- onPanResponderRelease: () => {
196
- this.pan.flattenOffset();
197
- const event = this.createDragEvent({
198
- x: (this.pan.x as any)._value,
199
- y: (this.pan.y as any)._value,
200
- });
201
- // on swipe out
202
- const threshold = this.props.swipeThreshold ?? 0;
203
- if (
204
- (this.props.onSwipeOut &&
205
- Math.abs((this.pan.y as any)._value) > threshold) ||
206
- Math.abs((this.pan.x as any)._value) > threshold
207
- ) {
208
- const toValue = this.getDisappearDirection();
209
- this.props.onSwipingOut?.(event);
210
- if (!toValue) return;
211
- Animated.spring(this.pan, {
212
- toValue,
213
- velocity: 0,
214
- tension: 65,
215
- friction: 11,
216
- useNativeDriver: false,
217
- }).start(() => {
218
- this.props.onSwipeOut?.(event);
219
- });
220
- return;
221
- }
222
- // on release
223
- this.currentSwipeDirection = null;
224
- this.props.onRelease?.(event);
225
- Animated.spring(this.pan, {
226
- toValue: { x: 0, y: 0 },
227
- velocity: 0,
228
- tension: 65,
229
- friction: 11,
230
- useNativeDriver: false,
231
- }).start();
75
+ return null;
76
+ }, []);
77
+
78
+ const getDisappearDirection = useCallback(() => {
79
+ const vertical = screenHeight / 2 + (layoutRef.current ? layoutRef.current.height / 2 : 0);
80
+ const horizontal = screenWidth / 2 + (layoutRef.current ? layoutRef.current.width / 2 : 0);
81
+
82
+ switch (currentSwipeDirectionRef.current) {
83
+ case 'up':
84
+ return { x: 0, y: -vertical };
85
+ case 'down':
86
+ return { x: 0, y: vertical };
87
+ case 'left':
88
+ return { x: -horizontal, y: 0 };
89
+ case 'right':
90
+ return { x: horizontal, y: 0 };
91
+ default:
92
+ return undefined;
93
+ }
94
+ }, [screenWidth, screenHeight]);
95
+
96
+ const isAllowedDirection = useCallback(
97
+ (gestureState: PanResponderGestureState) => {
98
+ const { dx, dy } = gestureState;
99
+ const draggedDown = dy > 0;
100
+ const draggedUp = dy < 0;
101
+ const draggedLeft = dx < 0;
102
+ const draggedRight = dx > 0;
103
+
104
+ const isCurrent = (d: SwipeDirection) => currentSwipeDirectionRef.current === d && allowedDirections.includes(d);
105
+
106
+ return (
107
+ (draggedDown && isCurrent('down')) ||
108
+ (draggedUp && isCurrent('up')) ||
109
+ (draggedLeft && isCurrent('left')) ||
110
+ (draggedRight && isCurrent('right'))
111
+ );
232
112
  },
113
+ [allowedDirections],
114
+ );
115
+
116
+ const createDragEvent = useCallback(
117
+ (): DragEvent => ({
118
+ axis: panValueRef.current,
119
+ layout: layoutRef.current,
120
+ swipeDirection: currentSwipeDirectionRef.current,
121
+ }),
122
+ [],
123
+ );
124
+
125
+ const panResponder = useMemo(
126
+ () =>
127
+ PanResponder.create({
128
+ onStartShouldSetPanResponder: () =>
129
+ allowedDirections.length > 0,
130
+ onMoveShouldSetPanResponder: (_, gestureState) =>
131
+ allowedDirections.length > 0 &&
132
+ (gestureState.dx !== 0 || gestureState.dy !== 0),
133
+ onPanResponderMove: (event, gestureState) => {
134
+ const isVertical = (d: SwipeDirection | null) => d === 'up' || d === 'down';
135
+ const isHorizontal = (d: SwipeDirection | null) => d === 'left' || d === 'right';
136
+
137
+ const newDir = getSwipeDirection(gestureState);
138
+ const isSame =
139
+ isVertical(currentSwipeDirectionRef.current) === isVertical(newDir) ||
140
+ isHorizontal(currentSwipeDirectionRef.current) === isHorizontal(newDir);
141
+
142
+ if (newDir && isSame) {
143
+ currentSwipeDirectionRef.current = newDir;
144
+ }
145
+
146
+ if (isAllowedDirection(gestureState)) {
147
+ let animEvent: any;
148
+ if (isVertical(currentSwipeDirectionRef.current)) {
149
+ animEvent = { dy: pan.y };
150
+ } else if (isHorizontal(currentSwipeDirectionRef.current)) {
151
+ animEvent = { dx: pan.x };
152
+ }
153
+
154
+ if (animEvent) {
155
+ Animated.event([null, animEvent], { useNativeDriver: false })(event, gestureState);
156
+ }
157
+ propsRef.current.onSwiping?.(createDragEvent());
158
+ }
159
+ },
160
+ onPanResponderRelease: () => {
161
+ pan.flattenOffset();
162
+ const event = createDragEvent();
163
+ const threshold = propsRef.current.swipeThreshold ?? 100;
164
+
165
+ if (
166
+ (propsRef.current.onSwipeOut && Math.abs(panValueRef.current.y) > threshold) ||
167
+ Math.abs(panValueRef.current.x) > threshold
168
+ ) {
169
+ const toValue = getDisappearDirection();
170
+ propsRef.current.onSwipingOut?.(event);
171
+ if (!toValue) return;
172
+
173
+ Animated.spring(pan, {
174
+ toValue,
175
+ velocity: 0,
176
+ tension: 65,
177
+ friction: 11,
178
+ useNativeDriver: false,
179
+ }).start(() => {
180
+ propsRef.current.onSwipeOut?.(event);
181
+ });
182
+ return;
183
+ }
184
+
185
+ currentSwipeDirectionRef.current = null;
186
+ propsRef.current.onRelease?.(event);
187
+ Animated.spring(pan, {
188
+ toValue: { x: 0, y: 0 },
189
+ velocity: 0,
190
+ tension: 65,
191
+ friction: 11,
192
+ useNativeDriver: false,
193
+ }).start();
194
+ },
195
+ }),
196
+ [getSwipeDirection, isAllowedDirection, pan, createDragEvent, getDisappearDirection],
197
+ );
198
+
199
+ const content = renderContent({
200
+ pan,
201
+ onLayout,
233
202
  });
234
203
 
235
- render() {
236
- const { style, children: renderContent } = this.props;
237
- const content = renderContent({
238
- pan: this.pan,
239
- onLayout: this.onLayout,
240
- });
204
+ return (
205
+ <Animated.View {...panResponder.panHandlers} style={style} pointerEvents={pointerEvents}>
206
+ {content}
207
+ </Animated.View>
208
+ );
209
+ });
210
+
211
+ DraggableView.displayName = 'DraggableView';
241
212
 
242
- return (
243
- <Animated.View {...this.panResponder.panHandlers} style={style}>
244
- {content}
245
- </Animated.View>
246
- );
247
- }
248
- }
213
+ export default DraggableView;
@@ -1,39 +1,31 @@
1
- // @flow
1
+ import React from 'react';
2
+ import { PixelRatio, Platform, StyleSheet, Text, TouchableHighlight } from 'react-native';
3
+ import { Positions } from '../constants/Constants';
4
+ import type { ModalButtonProps } from '../type';
2
5
 
3
- import React from "react";
4
- import {
5
- PixelRatio,
6
- Platform,
7
- StyleSheet,
8
- Text,
9
- TouchableHighlight,
10
- } from "react-native";
11
- import { Positions } from "../constants/Constants";
12
- import type { ModalButtonProps } from "../type";
13
-
14
- const isAndroid = Platform.OS === "android";
6
+ const isAndroid = Platform.OS === 'android';
15
7
 
16
8
  const styles = StyleSheet.create({
17
9
  button: {
18
10
  flex: 1,
19
- width: "100%",
20
- justifyContent: "center",
21
- alignItems: "center",
11
+ width: '100%',
12
+ justifyContent: 'center',
13
+ alignItems: 'center',
22
14
  paddingTop: 16,
23
15
  paddingBottom: 16,
24
16
  },
25
17
  border: {
26
- borderLeftColor: "#CCD0D5",
18
+ borderLeftColor: '#CCD0D5',
27
19
  borderLeftWidth: 1 / PixelRatio.get(),
28
20
  },
29
21
  text: {
30
- fontWeight: isAndroid ? "400" : "500",
31
- fontFamily: isAndroid ? "sans-serif-medium" : "System",
22
+ fontWeight: isAndroid ? '400' : '500',
23
+ fontFamily: isAndroid ? 'sans-serif-medium' : 'System',
32
24
  fontSize: isAndroid ? 19 : 16,
33
- color: "#044DE0",
25
+ color: '#044DE0',
34
26
  },
35
27
  disable: {
36
- color: "#C5C6C5",
28
+ color: '#C5C6C5',
37
29
  },
38
30
  });
39
31
 
@@ -43,7 +35,7 @@ const ModalButton = ({
43
35
  style,
44
36
  textStyle,
45
37
  activeOpacity = 0.6,
46
- align = "center",
38
+ align = 'center',
47
39
  disabled = false,
48
40
  bordered = false,
49
41
  }: ModalButtonProps) => {
@@ -57,8 +49,7 @@ const ModalButton = ({
57
49
  onPress={onPress}
58
50
  disabled={disabled}
59
51
  activeOpacity={activeOpacity}
60
- style={[styles.button, buttonAlign, border, style]}
61
- >
52
+ style={[styles.button, buttonAlign, border, style]}>
62
53
  <Text style={[styles.text, disable, textStyle]}>{text}</Text>
63
54
  </TouchableHighlight>
64
55
  );
@@ -1,9 +1,7 @@
1
- // @flow
2
-
3
1
  import React from 'react';
4
- import { View, StyleSheet } from 'react-native';
5
- import ModalContext from './ModalContext';
2
+ import { StyleSheet, View } from 'react-native';
6
3
  import type { ModalContentProps } from '../type';
4
+ import ModalContext from './ModalContext';
7
5
 
8
6
  const styles = StyleSheet.create({
9
7
  content: {
@@ -15,16 +13,9 @@ const styles = StyleSheet.create({
15
13
  },
16
14
  });
17
15
 
18
- const ModalContent = ({
19
- style,
20
- children,
21
- }: ModalContentProps) => (
16
+ const ModalContent = ({ style, children }: ModalContentProps) => (
22
17
  <ModalContext.Consumer>
23
- {({ hasTitle }) => (
24
- <View style={[styles.content, hasTitle && styles.noPaddingTop, style]}>
25
- {children}
26
- </View>
27
- )}
18
+ {({ hasTitle }) => <View style={[styles.content, hasTitle && styles.noPaddingTop, style]}>{children}</View>}
28
19
  </ModalContext.Consumer>
29
20
  );
30
21
 
@@ -1,7 +1,5 @@
1
- // @flow
2
-
3
1
  import React, { Children, cloneElement } from 'react';
4
- import { View, StyleSheet, PixelRatio } from 'react-native';
2
+ import { PixelRatio, StyleSheet, View } from 'react-native';
5
3
  import type { ModalFooterProps } from '../type';
6
4
 
7
5
  const styles = StyleSheet.create({
@@ -18,31 +16,22 @@ const styles = StyleSheet.create({
18
16
  },
19
17
  });
20
18
 
21
- const ModalActionList = ({
22
- style,
23
- children,
24
- bordered = true,
25
- }: ModalFooterProps) => {
26
- const containerStyle = children.length > 2
27
- ? styles.actionsVertical
28
- : styles.actionsHorizontal;
19
+ const ModalActionList = ({ style, children, bordered = true }: ModalFooterProps) => {
20
+ const containerStyle = children.length > 2 ? styles.actionsVertical : styles.actionsHorizontal;
29
21
 
30
- const border = bordered
31
- ? styles.border
32
- : null;
22
+ const border = bordered ? styles.border : null;
33
23
 
34
24
  // apply horizontal border if actions legnth is 2 & bordered is true
35
- const content = children.length === 2
36
- ? Children.map(children, ((child, index) => cloneElement(child, {
37
- bordered: (1 % index === 0 && bordered),
38
- })))
39
- : children;
25
+ const content =
26
+ children.length === 2
27
+ ? Children.map(children, (child, index) =>
28
+ cloneElement(child, {
29
+ bordered: 1 % index === 0 && bordered,
30
+ }),
31
+ )
32
+ : children;
40
33
 
41
- return (
42
- <View style={[containerStyle, border, style]}>
43
- {content}
44
- </View>
45
- );
34
+ return <View style={[containerStyle, border, style]}>{content}</View>;
46
35
  };
47
36
 
48
37
  export default ModalActionList;