Skip to content

Feat/incubator dialog fixes and improvements #1704

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Dec 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions demo/src/screens/incubatorScreens/IncubatorDialogScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, {Component} from 'react';
import {StyleSheet} from 'react-native';
import {StyleSheet, ModalProps} from 'react-native';
import {FlatList} from 'react-native-gesture-handler';
import {View, Text, Card, Button, Incubator, Colors, BorderRadiuses} from 'react-native-ui-lib'; //eslint-disable-line
import {View, Text, Card, Button, Incubator, Colors, BorderRadiuses, Constants} from 'react-native-ui-lib'; //eslint-disable-line

interface Item {
value: string;
Expand Down Expand Up @@ -33,10 +33,11 @@ const colors: Item[] = [

export default class IncubatorDialogScreen extends Component {
state = {visible: false};
modalProps: ModalProps = {supportedOrientations: ['portrait', 'landscape']};

renderVerticalItem = ({item}: {item: Item}) => {
return (
<Text text50 margin-20 color={item.value}>
<Text text50 margin-20 color={item.value} onPress={this.closeDialog}>
{item.label}
</Text>
);
Expand All @@ -54,6 +55,10 @@ export default class IncubatorDialogScreen extends Component {
this.setState({visible: false});
};

onDismiss = () => {
this.setState({visible: false});
};

render() {
const {visible} = this.state;

Expand All @@ -65,7 +70,14 @@ export default class IncubatorDialogScreen extends Component {
<View flex center>
<Button marginV-20 label="Open Dialog" onPress={this.openDialog}/>
</View>
<Incubator.Dialog visible={visible} onDismiss={this.closeDialog} bottom containerStyle={styles.dialogContainer}>
<Incubator.Dialog
useSafeArea
visible={visible}
onDismiss={this.onDismiss}
bottom
centerH
modalProps={this.modalProps}
>
<View style={styles.dialog}>
<Text text60 margin-s2>
Title (swipe here)
Expand All @@ -86,14 +98,11 @@ export default class IncubatorDialogScreen extends Component {
}

const styles = StyleSheet.create({
dialogContainer: {
bottom: 20,
alignSelf: 'center'
},
dialog: {
marginBottom: 20,
backgroundColor: Colors.white,
width: 200,
height: 300,
maxHeight: Constants.screenHeight * 0.8,
width: 300,
borderRadius: BorderRadiuses.br20
},
verticalScroll: {
Expand Down
7 changes: 7 additions & 0 deletions generatedTypes/src/incubator/Dialog/ImperativeDialog.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';
import { ImperativeDialogMethods, DialogDirections, DialogDirectionsEnum } from './types';
export { DialogDirections, DialogDirectionsEnum };
declare const _default: React.ForwardRefExoticComponent<import("./types")._DialogProps & {
children?: React.ReactNode;
} & React.RefAttributes<ImperativeDialogMethods>>;
export default _default;
11 changes: 11 additions & 0 deletions generatedTypes/src/incubator/Dialog/helpers/useAlignmentStyle.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { AlignmentModifiers } from '../../../commons/modifiers';
export declare enum AlignmentType {
DEFAULT = "default",
BOTTOM = "bottom",
TOP = "top"
}
declare const useAlignmentStyle: (props: AlignmentModifiers) => {
alignmentType: AlignmentType;
alignmentStyle: any[];
};
export default useAlignmentStyle;
15 changes: 15 additions & 0 deletions generatedTypes/src/incubator/Dialog/helpers/useFadeView.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// <reference types="react" />
import { ModalProps } from '../../../components/modal';
import { TransitionViewAnimationType } from '../../TransitionView';
import { ImperativeDialogProps } from '../types';
export declare type AnimationType = TransitionViewAnimationType;
export declare type FadeViewProps = Pick<ImperativeDialogProps, 'initialVisibility' | 'testID'> & Pick<ModalProps, 'overlayBackgroundColor'>;
export interface FadeViewMethods {
hideNow: () => void;
}
declare const useFadeView: (props: FadeViewProps) => {
FadeView: JSX.Element;
hideNow: () => void;
fade: (type: AnimationType) => void;
};
export default useFadeView;
17 changes: 17 additions & 0 deletions generatedTypes/src/incubator/Dialog/helpers/useSafeAreaView.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/// <reference types="react" />
import { ImperativeDialogProps } from '../types';
import { AlignmentType } from './useAlignmentStyle';
export interface SafeAreaProps extends Pick<ImperativeDialogProps, 'useSafeArea'> {
alignmentType: AlignmentType;
}
/**
* TODO: technically useSafeArea can be sent to either PanView or TransitionView.
* however that causes some performance \ UI bugs (when there is a safe area).
* TransitionView is less pronouns than PanView but still not good.
* We think this is because of reanimation 2, we should re-visit this problem later.
*/
declare const useSafeAreaView: (props: SafeAreaProps) => {
topSafeArea: JSX.Element | undefined;
bottomSafeArea: JSX.Element | undefined;
};
export default useSafeAreaView;
45 changes: 5 additions & 40 deletions generatedTypes/src/incubator/Dialog/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,15 @@
import { PropsWithChildren } from 'react';
import { StyleProp, ViewStyle } from 'react-native';
import { PanningDirections, PanningDirectionsEnum } from '../panView';
import { ModalProps } from '../../components/modal';
import { AlignmentModifiers } from '../../commons/modifiers';
declare type DialogDirections = PanningDirections;
declare const DialogDirectionsEnum: typeof PanningDirectionsEnum;
/// <reference types="react" />
import { ImperativeDialogProps, DialogDirections, DialogDirectionsEnum } from './types';
export { DialogDirections, DialogDirectionsEnum };
interface _DialogProps extends AlignmentModifiers {
export interface DialogProps extends Omit<ImperativeDialogProps, 'initialVisibility'> {
/**
* Control visibility of the dialog.
* The visibility of the dialog.
*/
visible?: boolean;
/**
* Callback that is called after the dialog's dismiss (after the animation has ended).
*/
onDismiss?: (props?: DialogProps) => void;
/**
* The direction from which and to which the dialog is animating \ panning (default bottom).
*/
direction?: DialogDirections;
/**
* The Dialog`s container style (it is set to {position: 'absolute'})
*/
containerStyle?: StyleProp<ViewStyle>;
/**
* Whether or not to ignore background press.
*/
ignoreBackgroundPress?: boolean;
/**
* Additional props for the modal.
*/
modalProps?: ModalProps;
/**
* Used to locate this view in end-to-end tests
* The container has the unchanged id.
* Currently supported inner IDs:
* TODO: add missing <TestID>(s?)
* <TestID>.modal - the Modal's id.
* <TestID>.overlayFadingBackground - the fading background id.
*/
testID?: string;
}
export declare type DialogProps = PropsWithChildren<_DialogProps>;
declare const Dialog: {
(props: DialogProps): JSX.Element;
displayName: string;
directions: typeof PanningDirectionsEnum;
directions: typeof import("../panView").PanningDirectionsEnum;
};
export default Dialog;
44 changes: 44 additions & 0 deletions generatedTypes/src/incubator/Dialog/types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { PropsWithChildren } from 'react';
import { AlignmentModifiers } from '../../commons/modifiers';
import { ModalProps } from '../../components/modal';
import { ViewProps } from '../../components/view';
import { PanningDirections, PanningDirectionsEnum } from '../panView';
declare type DialogDirections = PanningDirections;
declare const DialogDirectionsEnum: typeof PanningDirectionsEnum;
export { DialogDirections, DialogDirectionsEnum };
export interface _DialogProps extends AlignmentModifiers, Pick<ViewProps, 'useSafeArea'> {
/**
* The initial visibility of the dialog.
*/
initialVisibility?: boolean;
/**
* Callback that is called after the dialog's dismiss (after the animation has ended).
*/
onDismiss?: (props?: ImperativeDialogProps) => void;
/**
* The direction from which and to which the dialog is animating \ panning (default down).
*/
direction?: DialogDirections;
/**
* Whether or not to ignore background press.
*/
ignoreBackgroundPress?: boolean;
/**
* Additional props for the modal.
*/
modalProps?: ModalProps;
/**
* Used to locate this view in end-to-end tests
* The container has the unchanged id.
* Currently supported inner IDs:
* TODO: add missing <TestID>(s?)
* <TestID>.modal - the Modal's id.
* <TestID>.overlayFadingBackground - the fading background id.
*/
testID?: string;
}
export declare type ImperativeDialogProps = PropsWithChildren<_DialogProps>;
export interface ImperativeDialogMethods {
open: () => void;
close: () => void;
}
126 changes: 126 additions & 0 deletions src/incubator/Dialog/ImperativeDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React, {useMemo, useCallback, useState, useImperativeHandle, forwardRef} from 'react';
import {StyleSheet} from 'react-native';
import View from '../../components/view';
import Modal from '../../components/modal';
import TransitionView, {TransitionViewAnimationType} from '../TransitionView';
import PanView from '../panView';
import useAlignmentStyle from './helpers/useAlignmentStyle';
import useSafeAreaView from './helpers/useSafeAreaView';
import useFadeView from './helpers/useFadeView';
import {ImperativeDialogProps, ImperativeDialogMethods, DialogDirections, DialogDirectionsEnum} from './types';
export {DialogDirections, DialogDirectionsEnum};

const ImperativeDialog = (props: ImperativeDialogProps, ref: any) => {
const {
initialVisibility = false,
onDismiss,
direction = DialogDirectionsEnum.DOWN,
children,
ignoreBackgroundPress,
modalProps = {},
useSafeArea,
testID
} = props;
const transitionAnimatorRef = React.createRef<typeof TransitionView>();
const {overlayBackgroundColor, ...otherModalProps} = modalProps;
const [visible, setVisible] = useState(initialVisibility);
const {alignmentType, alignmentStyle} = useAlignmentStyle(props);
const {topSafeArea, bottomSafeArea} = useSafeAreaView({useSafeArea, alignmentType});
const {FadeView, hideNow, fade} = useFadeView({
initialVisibility,
testID: `${testID}.overlayFadingBackground`,
overlayBackgroundColor
});

const open = useCallback(() => {
if (!visible) {
setVisible(true);
}
}, [visible, setVisible]);

const close = useCallback(() => {
if (visible) {
transitionAnimatorRef.current?.animateOut();
}
}, [visible, transitionAnimatorRef]);

useImperativeHandle(ref, () => ({
open,
close
}));

const directions = useMemo((): DialogDirections[] => {
return [direction];
}, [direction]);

const onBackgroundPress = useCallback(() => {
close();
}, [close]);

const onPanViewDismiss = useCallback(() => {
hideNow();
setVisible(false);
onDismiss?.();
}, [hideNow, onDismiss, setVisible]);

const onTransitionAnimationEnd = useCallback((type: TransitionViewAnimationType) => {
if (type === 'exit') {
setVisible(false);
onDismiss?.();
}
},
[onDismiss, setVisible]);

const renderDialog = () => {
return (
<PanView
directions={directions}
dismissible
animateToOrigin
containerStyle={styles.panView}
onDismiss={onPanViewDismiss}
>
<TransitionView
ref={transitionAnimatorRef}
enterFrom={direction}
exitTo={direction}
onAnimationStart={fade}
onAnimationEnd={onTransitionAnimationEnd}
>
{topSafeArea}
{children}
{bottomSafeArea}
</TransitionView>
</PanView>
);
};

return (
<Modal
transparent
animationType={'none'}
{...otherModalProps}
testID={`${testID}.modal`}
useGestureHandlerRootView
visible={visible}
onBackgroundPress={ignoreBackgroundPress ? undefined : onBackgroundPress}
onRequestClose={ignoreBackgroundPress ? undefined : onBackgroundPress}
onDismiss={undefined}
>
{FadeView}
<View pointerEvents={'box-none'} style={alignmentStyle}>
{renderDialog()}
</View>
</Modal>
);
};

ImperativeDialog.displayName = 'IGNORE';

export default forwardRef<ImperativeDialogMethods, ImperativeDialogProps>(ImperativeDialog);

const styles = StyleSheet.create({
panView: {
position: 'absolute'
}
});
21 changes: 21 additions & 0 deletions src/incubator/Dialog/dialog.api.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "Dialog",
"category": "incubator",
"description": "Component for displaying custom content inside a popup dialog",
"note": "Use alignment modifiers to control the dialog position (top, bottom, centerV, centerH, etc... by default the dialog is aligned to center)",
"modifiers": ["alignment"],
"example": "https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/incubatorScreens/IncubatorDialogScreen.js",
"props": [
{"name": "visible", "type": "boolean", "description": "The visibility of the dialog"},
{"name": "onDismiss", "type": "(props?: ImperativeDialogProps) => void", "description": "Callback that is called after the dialog's dismiss (after the animation has ended)."},
{
"name": "direction",
"type": "up | down | left | right",
"description": "The direction from which and to which the dialog is animating \\ panning (default bottom).",
"default": "down"
},
{"name": "ignoreBackgroundPress", "type": "boolean", "description": "Whether or not to ignore background press."},
{"name": "modalProps", "type": "ModalProps", "description": "Pass props to the dialog modal"},
{"name": "testID", "type": "string", "description": "Used as a testing identifier"}
]
}
Loading