Skip to content

Commit f2b645a

Browse files
committed
Add ImperativeDialog - fix bug while keeping simple
1 parent 135c815 commit f2b645a

File tree

5 files changed

+261
-179
lines changed

5 files changed

+261
-179
lines changed

demo/src/screens/incubatorScreens/IncubatorDialogScreen.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export default class IncubatorDialogScreen extends Component {
3636

3737
renderVerticalItem = ({item}: {item: Item}) => {
3838
return (
39-
<Text text50 margin-20 color={item.value}>
39+
<Text text50 margin-20 color={item.value} onPress={this.closeDialog}>
4040
{item.label}
4141
</Text>
4242
);
@@ -54,6 +54,10 @@ export default class IncubatorDialogScreen extends Component {
5454
this.setState({visible: false});
5555
};
5656

57+
onDismiss = () => {
58+
this.setState({visible: false});
59+
};
60+
5761
render() {
5862
const {visible} = this.state;
5963

@@ -65,7 +69,7 @@ export default class IncubatorDialogScreen extends Component {
6569
<View flex center>
6670
<Button marginV-20 label="Open Dialog" onPress={this.openDialog}/>
6771
</View>
68-
<Incubator.Dialog visible={visible} onDismiss={this.closeDialog} bottom containerStyle={styles.dialogContainer}>
72+
<Incubator.Dialog visible={visible} onDismiss={this.onDismiss} bottom containerStyle={styles.dialogContainer}>
6973
<View style={styles.dialog}>
7074
<Text text60 margin-s2>
7175
Title (swipe here)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React, { PropsWithChildren } from 'react';
2+
import { StyleProp, ViewStyle } from 'react-native';
3+
import { PanningDirections, PanningDirectionsEnum } from '../panView';
4+
import { ModalProps } from '../../components/modal';
5+
import { AlignmentModifiers } from '../../commons/modifiers';
6+
declare type DialogDirections = PanningDirections;
7+
declare const DialogDirectionsEnum: typeof PanningDirectionsEnum;
8+
export { DialogDirections, DialogDirectionsEnum };
9+
interface _DialogProps extends AlignmentModifiers {
10+
/**
11+
* The initial visibility of the dialog.
12+
*/
13+
initialVisibility?: boolean;
14+
/**
15+
* Callback that is called after the dialog's dismiss (after the animation has ended).
16+
*/
17+
onDismiss?: (props?: ImperativeDialogProps) => void;
18+
/**
19+
* The direction from which and to which the dialog is animating \ panning (default bottom).
20+
*/
21+
direction?: DialogDirections;
22+
/**
23+
* The Dialog`s container style (it is set to {position: 'absolute'})
24+
*/
25+
containerStyle?: StyleProp<ViewStyle>;
26+
/**
27+
* Whether or not to ignore background press.
28+
*/
29+
ignoreBackgroundPress?: boolean;
30+
/**
31+
* Additional props for the modal.
32+
*/
33+
modalProps?: ModalProps;
34+
/**
35+
* Used to locate this view in end-to-end tests
36+
* The container has the unchanged id.
37+
* Currently supported inner IDs:
38+
* TODO: add missing <TestID>(s?)
39+
* <TestID>.modal - the Modal's id.
40+
* <TestID>.overlayFadingBackground - the fading background id.
41+
*/
42+
testID?: string;
43+
}
44+
export declare type ImperativeDialogProps = PropsWithChildren<_DialogProps>;
45+
export interface ImperativeDialogMethods {
46+
open: () => void;
47+
close: () => void;
48+
}
49+
declare const _default: React.ForwardRefExoticComponent<_DialogProps & {
50+
children?: React.ReactNode;
51+
} & React.RefAttributes<ImperativeDialogMethods>>;
52+
export default _default;
Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,15 @@
1-
import { PropsWithChildren } from 'react';
2-
import { StyleProp, ViewStyle } from 'react-native';
3-
import { PanningDirections, PanningDirectionsEnum } from '../panView';
4-
import { ModalProps } from '../../components/modal';
5-
import { AlignmentModifiers } from '../../commons/modifiers';
6-
declare type DialogDirections = PanningDirections;
7-
declare const DialogDirectionsEnum: typeof PanningDirectionsEnum;
1+
/// <reference types="react" />
2+
import { ImperativeDialogProps, DialogDirections, DialogDirectionsEnum } from './ImperativeDialog';
83
export { DialogDirections, DialogDirectionsEnum };
9-
interface _DialogProps extends AlignmentModifiers {
4+
export interface DialogProps extends Omit<ImperativeDialogProps, 'initialVisibility'> {
105
/**
11-
* Control visibility of the dialog.
6+
* The visibility of the dialog.
127
*/
138
visible?: boolean;
14-
/**
15-
* Callback that is called after the dialog's dismiss (after the animation has ended).
16-
*/
17-
onDismiss?: (props?: DialogProps) => void;
18-
/**
19-
* The direction from which and to which the dialog is animating \ panning (default bottom).
20-
*/
21-
direction?: DialogDirections;
22-
/**
23-
* The Dialog`s container style (it is set to {position: 'absolute'})
24-
*/
25-
containerStyle?: StyleProp<ViewStyle>;
26-
/**
27-
* Whether or not to ignore background press.
28-
*/
29-
ignoreBackgroundPress?: boolean;
30-
/**
31-
* Additional props for the modal.
32-
*/
33-
modalProps?: ModalProps;
34-
/**
35-
* Used to locate this view in end-to-end tests
36-
* The container has the unchanged id.
37-
* Currently supported inner IDs:
38-
* TODO: add missing <TestID>(s?)
39-
* <TestID>.modal - the Modal's id.
40-
* <TestID>.overlayFadingBackground - the fading background id.
41-
*/
42-
testID?: string;
439
}
44-
export declare type DialogProps = PropsWithChildren<_DialogProps>;
4510
declare const Dialog: {
4611
(props: DialogProps): JSX.Element;
4712
displayName: string;
48-
directions: typeof PanningDirectionsEnum;
13+
directions: typeof import("../panView").PanningDirectionsEnum;
4914
};
5015
export default Dialog;
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import React, {PropsWithChildren, useMemo, useCallback, useState, useImperativeHandle, forwardRef} from 'react';
2+
import {StyleSheet, StyleProp, ViewStyle} from 'react-native';
3+
import {useSharedValue, withTiming, useAnimatedStyle} from 'react-native-reanimated';
4+
import TransitionView, {TransitionViewAnimationType} from '../TransitionView';
5+
import PanView, {PanningDirections, PanningDirectionsEnum} from '../panView';
6+
import View from '../../components/view';
7+
import Modal, {ModalProps} from '../../components/modal';
8+
import {AlignmentModifiers} from '../../commons/modifiers';
9+
type DialogDirections = PanningDirections;
10+
const DialogDirectionsEnum = PanningDirectionsEnum;
11+
export {DialogDirections, DialogDirectionsEnum};
12+
13+
interface _DialogProps extends AlignmentModifiers {
14+
/**
15+
* The initial visibility of the dialog.
16+
*/
17+
initialVisibility?: boolean;
18+
/**
19+
* Callback that is called after the dialog's dismiss (after the animation has ended).
20+
*/
21+
onDismiss?: (props?: ImperativeDialogProps) => void;
22+
/**
23+
* The direction from which and to which the dialog is animating \ panning (default bottom).
24+
*/
25+
direction?: DialogDirections;
26+
/**
27+
* The Dialog`s container style (it is set to {position: 'absolute'})
28+
*/
29+
containerStyle?: StyleProp<ViewStyle>;
30+
/**
31+
* Whether or not to ignore background press.
32+
*/
33+
ignoreBackgroundPress?: boolean;
34+
/**
35+
* Additional props for the modal.
36+
*/
37+
modalProps?: ModalProps;
38+
/**
39+
* Used to locate this view in end-to-end tests
40+
* The container has the unchanged id.
41+
* Currently supported inner IDs:
42+
* TODO: add missing <TestID>(s?)
43+
* <TestID>.modal - the Modal's id.
44+
* <TestID>.overlayFadingBackground - the fading background id.
45+
*/
46+
testID?: string;
47+
}
48+
49+
export type ImperativeDialogProps = PropsWithChildren<_DialogProps>;
50+
51+
export interface ImperativeDialogMethods {
52+
open: () => void;
53+
close: () => void;
54+
}
55+
56+
import {Colors} from 'style';
57+
const DEFAULT_OVERLAY_BACKGROUND_COLORS = Colors.rgba(Colors.black, 0.2);
58+
59+
const ImperativeDialog = (props: ImperativeDialogProps, ref: any) => {
60+
const {
61+
initialVisibility = false,
62+
onDismiss,
63+
direction = DialogDirectionsEnum.DOWN,
64+
children,
65+
containerStyle,
66+
ignoreBackgroundPress,
67+
modalProps = {},
68+
testID
69+
} = props;
70+
const transitionAnimatorRef = React.createRef<typeof TransitionView>();
71+
const {overlayBackgroundColor = DEFAULT_OVERLAY_BACKGROUND_COLORS, ...otherModalProps} = modalProps;
72+
const fadeOpacity = useSharedValue<number>(Number(initialVisibility));
73+
const [visible, setVisible] = useState(initialVisibility);
74+
75+
const open = useCallback(() => {
76+
if (!visible) {
77+
setVisible(true);
78+
}
79+
}, [visible, setVisible]);
80+
81+
const close = useCallback(() => {
82+
if (visible) {
83+
transitionAnimatorRef.current?.animateOut();
84+
}
85+
}, [visible, transitionAnimatorRef]);
86+
87+
useImperativeHandle(ref, () => ({
88+
open,
89+
close
90+
}));
91+
92+
const directions = useMemo((): DialogDirections[] => {
93+
return [direction];
94+
}, [direction]);
95+
96+
const onBackgroundPress = useCallback(() => {
97+
close();
98+
}, [close]);
99+
100+
const onPanViewDismiss = useCallback(() => {
101+
fadeOpacity.value = 0;
102+
setVisible(false);
103+
onDismiss?.();
104+
// eslint-disable-next-line react-hooks/exhaustive-deps
105+
}, [onDismiss, setVisible]);
106+
107+
const onTransitionAnimationStart = useCallback((type: TransitionViewAnimationType) => {
108+
fadeOpacity.value = withTiming(Number(type === 'enter'), {duration: 300});
109+
// eslint-disable-next-line react-hooks/exhaustive-deps
110+
}, []);
111+
112+
const onTransitionAnimationEnd = useCallback((type: TransitionViewAnimationType) => {
113+
if (type === 'exit') {
114+
setVisible(false);
115+
onDismiss?.();
116+
}
117+
},
118+
[onDismiss, setVisible]);
119+
120+
const panStyle = useMemo(() => {
121+
return [containerStyle, styles.panView];
122+
}, [containerStyle]);
123+
124+
const fadeStyle = useAnimatedStyle(() => {
125+
return {
126+
opacity: fadeOpacity.value,
127+
backgroundColor: overlayBackgroundColor
128+
};
129+
}, [overlayBackgroundColor]);
130+
131+
return (
132+
<Modal
133+
transparent
134+
animationType={'none'}
135+
{...otherModalProps}
136+
testID={`${testID}.modal`}
137+
useGestureHandlerRootView
138+
visible={visible}
139+
onBackgroundPress={ignoreBackgroundPress ? undefined : onBackgroundPress}
140+
onRequestClose={onBackgroundPress}
141+
onDismiss={undefined}
142+
>
143+
<View testID={`${testID}.overlayFadingBackground`} absF reanimated style={fadeStyle} pointerEvents="none"/>
144+
{/* TODO: remove?
145+
{this.renderDialogView()}
146+
{addBottomSafeArea && <View style={{marginTop: bottomInsets}}/>} */}
147+
148+
<PanView
149+
directions={directions}
150+
dismissible
151+
animateToOrigin
152+
containerStyle={panStyle}
153+
onDismiss={onPanViewDismiss}
154+
>
155+
<TransitionView
156+
ref={transitionAnimatorRef}
157+
enterFrom={direction}
158+
exitTo={direction}
159+
onAnimationStart={onTransitionAnimationStart}
160+
onAnimationEnd={onTransitionAnimationEnd}
161+
>
162+
{children}
163+
</TransitionView>
164+
</PanView>
165+
</Modal>
166+
);
167+
};
168+
169+
ImperativeDialog.displayName = 'IGNORE';
170+
171+
export default forwardRef<ImperativeDialogMethods, ImperativeDialogProps>(ImperativeDialog);
172+
173+
const styles = StyleSheet.create({
174+
panView: {
175+
position: 'absolute'
176+
}
177+
});

0 commit comments

Comments
 (0)