Skip to content

Commit 21c2bcb

Browse files
Inbal-Tishethanshar
authored andcommitted
Feat/dialoge drag (wix#351)
* Add pan gesture to dialog to respond to drag and swipe * lint fix * adding deprecated comments * beautify * adjusting velocity for swipe and dismiss animation speed * changing entrance animation style * adjusting animations * updating demo screen * adding check for animationConfig to entrance animation (removing support for animation type config) * restoring support for custom animations * fix for ios - shows content again before dismissing modal * moving panResponder create to ctor * moving pan gesture to PanGesture component * adjusting demo screen * fix for top check * fix for description * variable renaming * initPosition before dismissing * changing direction names * pr comments fix
1 parent bc30ae0 commit 21c2bcb

File tree

5 files changed

+328
-73
lines changed

5 files changed

+328
-73
lines changed

demo/src/screens/componentScreens/DialogScreen.js

+33-9
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,12 @@ class DialogScreen extends Component {
77
showDialog3: false,
88
showDialog4: false,
99
showDialog5: false,
10+
showDialog6: false
1011
};
1112

1213
renderDialogContent(dialogIndex, extraProps) {
1314
return (
14-
<View bg-white flex br20 padding-18 spread {...extraProps}>
15+
<View flex br20 padding-18 spread {...extraProps}>
1516
<View height={100}>
1617
<Text text50>This is Dialog</Text>
1718
</View>
@@ -23,11 +24,11 @@ class DialogScreen extends Component {
2324
}
2425

2526
render() {
26-
const {showDialog1, showDialog2, showDialog3, showDialog4, showDialog5} = this.state;
27+
const {showDialog1, showDialog2, showDialog3, showDialog4, showDialog5, showDialog6} = this.state;
2728
return (
2829
<View flex bg-dark80 padding-12 center>
29-
<Button size={'small'} label='show default dialog in center' onPress={() => this.setState({showDialog1: true})} />
30-
<Button marginT-20 size={'small'} label='show bottom dialog' onPress={() => this.setState({showDialog2: true})} />
30+
<Button size={'small'} label='show default dialog in center' onPress={() => this.setState({showDialog1: true})}/>
31+
<Button marginT-20 size={'small'} label='show bottom dialog' onPress={() => this.setState({showDialog2: true})}/>
3132
<Button
3233
marginT-20
3334
size={'small'}
@@ -46,7 +47,20 @@ class DialogScreen extends Component {
4647
label='show dialog with height based on content '
4748
onPress={() => this.setState({showDialog5: true})}
4849
/>
49-
<Dialog visible={showDialog1} width='90%' height='60%' onDismiss={() => this.setState({showDialog1: false})}>
50+
<Button
51+
marginT-20
52+
size={'small'}
53+
label='show dialog with animation configuration'
54+
onPress={() => this.setState({showDialog6: true})}
55+
/>
56+
57+
<Dialog
58+
visible={showDialog1}
59+
width='90%'
60+
height='60%'
61+
onDismiss={() => this.setState({showDialog1: false})}
62+
style={{backgroundColor: Colors.white}}
63+
>
5064
{this.renderDialogContent(1)}
5165
</Dialog>
5266
<Dialog
@@ -68,7 +82,7 @@ class DialogScreen extends Component {
6882
centerH
6983
onDismiss={() => this.setState({showDialog3: false})}
7084
>
71-
{this.renderDialogContent(3, {'marginV-20': true})}
85+
{this.renderDialogContent(3, {'marginV-20': true, 'bg-white': true})}
7286
</Dialog>
7387
<Dialog
7488
visible={showDialog4}
@@ -77,23 +91,33 @@ class DialogScreen extends Component {
7791
top
7892
centerH
7993
onDismiss={() => this.setState({showDialog4: false})}
80-
dismissSwipeDirection={Dialog.swipeDirections.UP}
94+
// dismissSwipeDirection={Dialog.swipeDirections.UP}
8195
style={{backgroundColor: Colors.white}}
8296
>
8397
{this.renderDialogContent(4, {br0: true})}
8498
</Dialog>
85-
8699
<Dialog
87100
visible={showDialog5}
88101
width='100%'
89102
height={null}
90103
bottom
91104
centerH
92105
onDismiss={() => this.setState({showDialog5: false})}
93-
animationConfig={{duration: 1000}}
106+
style={{backgroundColor: Colors.white}}
94107
>
95108
{this.renderDialogContent(5, {flex: false})}
96109
</Dialog>
110+
<Dialog
111+
visible={showDialog6}
112+
width='80%'
113+
height='40%'
114+
bottom
115+
centerH
116+
onDismiss={() => this.setState({showDialog6: false})}
117+
animationConfig={{animation: 'slideInLeft', duration: 1000}}
118+
>
119+
{this.renderDialogContent(6, {'marginV-20': true, 'bg-yellow60': true})}
120+
</Dialog>
97121
</View>
98122
);
99123
}

src/components/dialog/index.js

+118-64
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import _ from 'lodash';
22
import PropTypes from 'prop-types';
33
import React from 'react';
4-
import {StyleSheet, TouchableWithoutFeedback, SafeAreaView} from 'react-native';
4+
import {StyleSheet, TouchableWithoutFeedback, SafeAreaView, Animated, Easing} from 'react-native';
55
import * as Animatable from 'react-native-animatable';
6-
import GestureRecognizer, {swipeDirections} from 'react-native-swipe-gestures';
76
import {Constants} from '../../helpers';
8-
import {Colors, AnimatableManager} from '../../style';
7+
import {AnimatableManager, Colors} from '../../style';
98
import {BaseComponent} from '../../commons';
109
import Modal from '../../screensComponents/modal';
1110
import View from '../view';
11+
import PanGestureView from '../panGestureView';
12+
1213

1314
/*eslint-disable*/
1415
/**
@@ -22,8 +23,8 @@ import View from '../view';
2223

2324
const SWIPE_DIRECTIONS = {
2425
UP: 'up',
25-
DOWN: 'down',
26-
};
26+
DOWN: 'down'
27+
}; // DEFRECATED
2728

2829
class Dialog extends BaseComponent {
2930
static displayName = 'Dialog'
@@ -33,13 +34,13 @@ class Dialog extends BaseComponent {
3334
*/
3435
visible: PropTypes.bool,
3536
/**
36-
* dismiss callback for when clicking on the background
37+
* Dismiss callback for when clicking on the background
3738
*/
3839
onDismiss: PropTypes.func,
3940
/**
40-
* the direction of the swipe to dismiss the dialog (default is 'down')
41+
* The direction of the swipe to dismiss the dialog (default is 'down')
4142
*/
42-
dismissSwipeDirection: PropTypes.oneOf(Object.values(SWIPE_DIRECTIONS)),
43+
dismissSwipeDirection: PropTypes.oneOf(Object.values(SWIPE_DIRECTIONS)), // DEFRECATED
4344
/**
4445
* The color of the overlay background
4546
*/
@@ -53,85 +54,137 @@ class Dialog extends BaseComponent {
5354
*/
5455
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
5556
/**
56-
* the animation configuration to pass to the dialog (based on react-native-animatable,
57-
* ex. {animation, duration, easing,..})
57+
* The animation configuration to pass to the dialog (ex. {animation, delay, duration, easing})
5858
*/
5959
animationConfig: PropTypes.object,
6060
/**
6161
* The dialog container style
6262
*/
63-
containerStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
63+
containerStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array])
6464
};
6565

6666
static defaultProps = {
6767
overlayBackgroundColor: Colors.rgba(Colors.dark10, 0.6),
6868
width: '90%',
69-
height: '70%',
70-
dismissSwipeDirection: SWIPE_DIRECTIONS.DOWN,
69+
height: '70%'
7170
};
7271

73-
static swipeDirections = SWIPE_DIRECTIONS;
72+
static swipeDirections = SWIPE_DIRECTIONS; // DEFRECATED
73+
74+
constructor(props) {
75+
super(props);
76+
77+
this.initialPosition = props.top ? -Constants.screenHeight : Constants.screenHeight;
78+
79+
this.state = {
80+
alignments: this.state.alignments,
81+
deltaY: new Animated.Value(this.initialPosition)
82+
};
83+
84+
if (props.dismissSwipeDirection) {
85+
console.warn('Dialog component\'s prop \'dismissSwipeDirection\' is deprecated, please remove it');
86+
}
87+
}
7488

7589
generateStyles() {
7690
this.styles = createStyles(this.props);
7791
}
7892

79-
onSwipe(gestureName) {
80-
const {SWIPE_UP, SWIPE_DOWN} = swipeDirections;
81-
const {dismissSwipeDirection} = this.props;
82-
83-
switch (gestureName) {
84-
case SWIPE_UP:
85-
if (dismissSwipeDirection === SWIPE_DIRECTIONS.UP) {
86-
_.invoke(this.props, 'onDismiss');
87-
}
88-
break;
89-
case SWIPE_DOWN:
90-
if (dismissSwipeDirection === SWIPE_DIRECTIONS.DOWN) {
91-
_.invoke(this.props, 'onDismiss');
92-
}
93-
break;
94-
default:
95-
break;
96-
}
93+
onDismiss = () => {
94+
this.initPositions();
95+
_.invoke(this.props, 'onDismiss');
9796
}
9897

99-
render() {
100-
const {visible, overlayBackgroundColor, style, onDismiss, bottom, animationConfig, top} = this.getThemeProps();
101-
const {alignments} = this.state;
102-
const centerByDefault = _.isEmpty(alignments);
103-
const config = {
104-
velocityThreshold: 0.3,
105-
directionalOffsetThreshold: 80,
106-
};
98+
initPositions() {
99+
this.setState({
100+
deltaY: new Animated.Value(this.initialPosition)
101+
});
102+
}
103+
104+
onModalShow = () => {
105+
const {animationConfig} = this.getThemeProps();
106+
const {deltaY} = this.state;
107+
108+
Animated.timing(deltaY, {
109+
toValue: 0,
110+
duration: _.get(animationConfig, 'duration', 280),
111+
delay: _.get(animationConfig, 'delay', 200),
112+
easing: _.get(animationConfig, 'easing', Easing.bezier(0.165, 0.84, 0.44, 1)),
113+
useNativeDriver: _.get(animationConfig, 'useNativeDriver', true)
114+
}).start();
115+
}
116+
117+
renderContent() {
118+
const {bottom} = this.getThemeProps();
107119
const bottomInsets = Constants.getSafeAreaInsets().paddingBottom;
108-
const animation = top ? AnimatableManager.presets.slideInDown : AnimatableManager.presets.slideInUp;
120+
121+
return (
122+
<TouchableWithoutFeedback>
123+
<SafeAreaView style={{flexGrow: 1}}>
124+
{this.props.children}
125+
{Constants.isIphoneX && bottom && <View style={{height: bottomInsets}}/>}
126+
</SafeAreaView>
127+
</TouchableWithoutFeedback>
128+
);
129+
}
130+
131+
renderDraggableContainer() {
132+
const {style, top} = this.getThemeProps();
133+
134+
return (
135+
<PanGestureView
136+
style={[this.styles.dialogContainer, style]}
137+
direction={top && PanGestureView.directions.UP}
138+
onDismiss={this.onDismiss}
139+
>
140+
{this.renderContent()}
141+
</PanGestureView>
142+
);
143+
}
144+
145+
renderAnimationContainer() {
146+
const {animationConfig, top} = this.getThemeProps();
147+
const {alignments, deltaY} = this.state;
148+
const centerByDefault = _.isEmpty(alignments);
149+
const hasCustomAnimation = (animationConfig && animationConfig.animation);
150+
const Container = hasCustomAnimation ? Animatable.View : Animated.View;
151+
const defaultAnimation = top ? AnimatableManager.presets.slideInDown : AnimatableManager.presets.slideInUp;
152+
const animation = hasCustomAnimation ? Object.assign(defaultAnimation, animationConfig) : {};
153+
154+
return (
155+
<Container
156+
style={[
157+
this.styles.overlay,
158+
{...alignments},
159+
centerByDefault && this.styles.centerContent,
160+
!hasCustomAnimation && {
161+
transform: [{
162+
translateY: deltaY
163+
}]
164+
}
165+
]}
166+
pointerEvents='box-none'
167+
{...animation}
168+
>
169+
{this.renderDraggableContainer()}
170+
</Container>
171+
);
172+
}
173+
174+
render() {
175+
const {visible, overlayBackgroundColor} = this.getThemeProps();
109176

110177
return (
111178
<Modal
112179
transparent
113180
visible={visible}
114181
animationType={'fade'}
115-
onBackgroundPress={onDismiss}
116-
onRequestClose={onDismiss}
182+
onBackgroundPress={this.onDismiss}
183+
onRequestClose={this.onDismiss}
117184
overlayBackgroundColor={overlayBackgroundColor}
185+
onShow={this.onModalShow}
118186
>
119-
<View center={centerByDefault} style={[this.styles.overlay, alignments]} pointerEvents="box-none">
120-
<Animatable.View style={[this.styles.dialogContainer, style]} {...animation} {...animationConfig}>
121-
<GestureRecognizer
122-
onSwipe={(direction, state) => this.onSwipe(direction, state)}
123-
config={config}
124-
style={this.styles.gestureContainer}
125-
>
126-
<TouchableWithoutFeedback>
127-
<SafeAreaView style={{flexGrow: 1}}>
128-
{this.props.children}
129-
{Constants.isIphoneX && bottom && <View style={{height: bottomInsets}}/>}
130-
</SafeAreaView>
131-
</TouchableWithoutFeedback>
132-
</GestureRecognizer>
133-
</Animatable.View>
134-
</View>
187+
{this.renderAnimationContainer()}
135188
</Modal>
136189
);
137190
}
@@ -140,15 +193,16 @@ class Dialog extends BaseComponent {
140193
function createStyles({width, height}) {
141194
return StyleSheet.create({
142195
overlay: {
143-
flex: 1,
196+
flex: 1
144197
},
145198
dialogContainer: {
146199
width,
147-
height,
148-
},
149-
gestureContainer: {
150-
flexGrow: 1,
200+
height
151201
},
202+
centerContent: {
203+
justifyContent: 'center',
204+
alignItems: 'center'
205+
}
152206
});
153207
}
154208

src/components/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module.exports = {
2020
get ListItem() { return require('./listItem').default; },
2121
get Notification() { return require('./notification').default; },
2222
get PageControl() { return require('./pageControl').default; },
23+
get PanGestureView() { return require('./panGestureView').default; },
2324
get Picker() { return require('./picker').default; },
2425
get Stepper() { return require('./stepper').default; },
2526
get TabBar() { return require('./tabBar').default; },

0 commit comments

Comments
 (0)