Socket
Socket
Sign inDemoInstall

react-native-modal

Package Overview
Dependencies
19
Maintainers
2
Versions
104
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 4.1.1 to 5.0.0-0

22

package.json
{
"name": "react-native-modal",
"version": "4.1.1",
"version": "5.0.0-0",
"description": "An enhanced React-Native modal",

@@ -11,3 +11,3 @@ "main": "src/index.js",

"test": "npm run lint",
"prettier": "prettier --write --print-width 100 --single-quote --trailing-comma all src/**/*.js",
"prettier": "prettier --write src/**/*.js",
"reset": "watchman watch-del-all && rm -rf node_modules/ && npm cache clean && npm prune && yarn cache clean"

@@ -27,3 +27,3 @@ },

"author": "Mazzarolo Matteo",
"license": "ISC",
"license": "MIT",
"homepage": "https://github.com/react-native-community/react-native-modal",

@@ -35,12 +35,12 @@ "repository": {

"dependencies": {
"prop-types": "15.5.10",
"react-native-animatable": "^1.2.3"
"prop-types": "15.6.0",
"react-native-animatable": "^1.2.4"
},
"devDependencies": {
"babel-eslint": "^7.2.3",
"eslint": "4.5.0",
"babel-eslint": "^8.1.2",
"eslint": "4.14.0",
"eslint-plugin-react-app": "^1.0.2",
"husky": "0.14.3",
"lint-staged": "4.0.4",
"prettier": "1.6.1",
"lint-staged": "6.0.0",
"prettier": "1.9.2",
"react": ">=15.0.0",

@@ -51,4 +51,4 @@ "react-native": ">=0.24.0"

"*.js": [
"npm run test",
"npm run prettier",
"lint",
"prettier --write",
"git add"

@@ -55,0 +55,0 @@ ]

@@ -6,11 +6,12 @@ # react-native-modal

An enhanced, animated and customizable react-native modal.
An enhanced, animated and customizable react-native modal.
## Features
- Smooth enter/exit animations
- Plain simple and flexible APIs
- Customizable backdrop opacity, color and timing
- Listeners for the modal animations ending
- Resize itself correctly on device rotation
* Smooth enter/exit animations
* Plain simple and flexible APIs
* Customizable backdrop opacity, color and timing
* Listeners for the modal animations ending
* Resize itself correctly on device rotation
* Swipeable

@@ -31,20 +32,68 @@ ## Demo

Since react-native-modal is an extension of the original react native modal, it works in a similar fashion [react-native original modal](https://facebook.github.io/react-native/docs/modal.html).
1. Import react-native-modal:
```javascript
import React, { Component } from 'react'
import { Text, TouchableOpacity, View } from 'react-native'
import Modal from 'react-native-modal'
import Modal from "react-native-modal";
```
2. Create a modal and nest its content inside of it:
```javascript
render () {
return (
<View>
<Modal>
<View style={{ flex: 1 }}>
<Text>I am the modal content!</Text>
</View>
</Modal>
</View>
)
}
```
3. Then simply show it by setting the `isVisible` prop to true:
```javascript
render () {
return (
<View>
<Modal isVisible={true}>
<View style={{ flex: 1 }}>
<Text>I am the modal content!</Text>
</View>
</Modal>
</View>
)
}
```
The `isVisible` prop is the only prop you'll really need to make the modal work: you should control this prop value by saving it in your state and setting it to `true` or `false` when needed.
## A complete example
The following example consists in a component (`ModalTester`) with a button and a modal.
The modal is controlled by the `isModalVisible` state variable and it is initially hidden, since its value is `false`.
Pressing the button sets `isModalVisible` to true, making the modal visible.
Inside the modal there is another button that, when pressed, sets `isModalVisible` to false, hiding the modal.
```javascript
import React, { Component } from "react";
import { Text, TouchableOpacity, View } from "react-native";
import Modal from "react-native-modal";
export default class ModalTester extends Component {
state = {
isModalVisible: false
}
};
_showModal = () => this.setState({ isModalVisible: true })
_toggleModal = () =>
this.setState({ isModalVisible: !this.state.isModalVisible });
_hideModal = () => this.setState({ isModalVisible: false })
render () {
render() {
return (
<View style={{ flex: 1 }}>
<TouchableOpacity onPress={this._showModal}>
<TouchableOpacity onPress={this._toggleModal}>
<Text>Show Modal</Text>

@@ -55,10 +104,13 @@ </TouchableOpacity>

<Text>Hello!</Text>
<TouchableOpacity onPress={this._toggleModal}>
<Text>Hide me!</Text>
</TouchableOpacity>
</View>
</Modal>
</View>
)
);
}
}
```
For a more complex example take a look at the `/example` directory.

@@ -68,21 +120,24 @@

| Name | Type| Default | Description |
| --- | --- | --- | --- |
| animationIn | string or object | 'slideInUp' | Modal show animation |
| animationInTiming | number | 300 | Timing for the modal show animation (in ms) |
| animationOut | string or object | 'slideOutDown' | Modal hide animation |
| animationOutTiming | number | 300 | Timing for the modal hide animation (in ms) |
| avoidKeyboard | bool | false | Move the modal up if the keyboard is open |
| backdropColor | string | 'black' | The backdrop background color |
| backdropOpacity | number | 0.70 | The backdrop opacity when the modal is visible |
| backdropTransitionInTiming | number | 300 | The backdrop show timing (in ms) |
| backdropTransitionOutTiming | number | 300 | The backdrop hide timing (in ms) |
| onBackButtonPress | func | () => null | Called when the Android back button is pressed |
| onBackdropPress | func | () => null | Called when the backdrop is pressed |
| useNativeDriver | bool | false | Define if animations should use [native driver](https://facebook.github.io/react-native/docs/animated.html#using-the-native-driver) |
| isVisible | bool | **REQUIRED** | Show the modal? |
| children | node | **REQUIRED** | The modal content |
| onModalShow | func | () => null | Called when the modal is completely visible |
| onModalHide | func | () => null | Called when the modal is completely hidden |
| style | any | null | Style applied to the modal |
| Name | Type | Default | Description |
| --------------------------- | ---------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| animationIn | string or object | 'slideInUp' | Modal show animation |
| animationInTiming | number | 300 | Timing for the modal show animation (in ms) |
| animationOut | string or object | 'slideOutDown' | Modal hide animation |
| animationOutTiming | number | 300 | Timing for the modal hide animation (in ms) |
| avoidKeyboard | bool | false | Move the modal up if the keyboard is open |
| backdropColor | string | 'black' | The backdrop background color |
| backdropOpacity | number | 0.70 | The backdrop opacity when the modal is visible |
| backdropTransitionInTiming | number | 300 | The backdrop show timing (in ms) |
| backdropTransitionOutTiming | number | 300 | The backdrop hide timing (in ms) |
| children | node | **REQUIRED** | The modal content |
| isVisible | bool | **REQUIRED** | Show the modal? |
| onBackButtonPress | func | () => null | Called when the Android back button is pressed |
| onBackdropPress | func | () => null | Called when the backdrop is pressed |
| onModalHide | func | () => null | Called when the modal is completely hidden |
| onModalShow | func | () => null | Called when the modal is completely visible |
| onSwipe | func | null | Called when the `swipeThreshold` has been reached | e |
| swipeThreshold | number | 100 | Swiping threshold that when reached calls `onSwipe` |
| swipeDirection | string | null | Defines the direction where the modal can be swiped (can be 'up', 'down', 'left, or 'right') |
| useNativeDriver | bool | false | Define if animations should use [native driver](https://facebook.github.io/react-native/docs/animated.html#using-the-native-driver) |
| style | any | null | Style applied to the modal |

@@ -92,11 +147,47 @@ ## Frequently Asked Questions

### The component is not working as expected
Under the hood `react-native-modal` uses react-native original [Modal component](https://facebook.github.io/react-native/docs/modal.html).
Before reporting a bug, try swapping `react-native-modal` with react-native original Modal component and, if the issue persists, check if it has already been reported as a [react-native issue](https://github.com/facebook/react-native/issues).
Before reporting a bug, try swapping `react-native-modal` with react-native original Modal component and, if the issue persists, check if it has already been reported as a [react-native issue](https://github.com/facebook/react-native/issues).
## Avaliable animations
### How can I hide the modal by pressing outside of its content?
The prop `onBackdropPress` allows you to handle this situation:
```javascript
<Modal
isVisible={this.state.isVisible}
onBackdropPress={() => this.setState({ isVisible: false })}
>
<View style={{ flex: 1 }}>
<Text>I am the modal content!</Text>
</View>
</Modal>
```
### How can I hide the modal by swiping it?
The prop `onSwipe` allows you to handle this situation (remember to set `swipeDirection` too!):
```javascript
<Modal
isVisible={this.state.isVisible}
onSwipe={() => this.setState({ isVisible: false })}
swipeDirection="left"
>
<View style={{ flex: 1 }}>
<Text>I am the modal content!</Text>
</View>
</Modal>
```
### The modal doesn't change orientation
Add a `supportedOrientations={['portrait', 'landscape']}` prop to the component, as described [in the React Native documentation](https://facebook.github.io/react-native/docs/modal.html#supportedorientations)
## Available animations
Take a look at [react-native-animatable](https://github.com/oblador/react-native-animatable) to see the dozens of animations available out-of-the-box. You can also pass in custom animation definitions and have them automatically register with react-native-animatable. For more information on creating custom animations, see the react-native-animatable [animation definition schema](https://github.com/oblador/react-native-animatable#animation-definition-schema).
Pull requests, feedbacks and suggestions are welcome!
Pull requests, feedbacks and suggestions are welcome!
P.S.: Thanks [@oblador](https://github.com/oblador) for react-native-animatable, [@brentvatne](https://github.com/brentvatne) for the npm namespace and to anyone who contributed to this library!
P.S.: Thanks [@oblador](https://github.com/oblador) for react-native-animatable, [@brentvatne](https://github.com/brentvatne) for the npm namespace and to anyone who contributed to this library!
/**
* Since react-native-animatable applies by default a margin of 100 to its sliding animation,
* Since react-native-animatable applies by default a margin of 100 to its sliding animation,
* we reset them here overriding the margin to 0.
*/
import { Dimensions } from 'react-native';
const { height, width } = Dimensions.get('window');
import { Dimensions } from "react-native";
const { height, width } = Dimensions.get("window");
function makeSlideTranslation(translationType, fromValue, toValue) {
const makeSlideTranslation = (translationType, fromValue, toValue) => {
return {
from: {
[translationType]: fromValue,
[translationType]: fromValue
},
to: {
[translationType]: toValue,
},
[translationType]: toValue
}
};
}
};
export const slideInDown = makeSlideTranslation('translateY', -height, 0);
export const slideInDown = makeSlideTranslation("translateY", -height, 0);
export const slideInUp = makeSlideTranslation('translateY', height, 0);
export const slideInUp = makeSlideTranslation("translateY", height, 0);
export const slideInLeft = makeSlideTranslation('translateX', -width, 0);
export const slideInLeft = makeSlideTranslation("translateX", -width, 0);
export const slideInRight = makeSlideTranslation('translateX', width, 0);
export const slideInRight = makeSlideTranslation("translateX", width, 0);
export const slideOutDown = makeSlideTranslation('translateY', 0, height);
export const slideOutDown = makeSlideTranslation("translateY", 0, height);
export const slideOutUp = makeSlideTranslation('translateY', 0, -height);
export const slideOutUp = makeSlideTranslation("translateY", 0, -height);
export const slideOutLeft = makeSlideTranslation('translateX', 0, -width);
export const slideOutLeft = makeSlideTranslation("translateX", 0, -width);
export const slideOutRight = makeSlideTranslation('translateX', 0, width);
export const slideOutRight = makeSlideTranslation("translateX", 0, width);

@@ -1,25 +0,28 @@

declare module 'react-native-modal' {
import { Component, ReactNode } from 'react'
import { StyleProp, ViewStyle } from 'react-native'
declare module "react-native-modal" {
import { Component, ReactNode } from "react";
import { StyleProp, ViewStyle } from "react-native";
type AnimationConfig = string | { from: Object, to: Object }
type AnimationConfig = string | { from: Object; to: Object };
export interface ModalProps {
animationIn?: AnimationConfig
animationInTiming?: number
animationOut?: AnimationConfig
animationOutTiming?: number
avoidKeyboard?: boolean
backdropColor?: string
backdropOpacity?: number
backdropTransitionInTiming?: number
backdropTransitionOutTiming?: number
useNativeDriver?: boolean
children: ReactNode
isVisible: boolean
onModalShow?: () => void
onModalHide?: () => void
onBackButtonPress?: () => void
onBackdropPress?: () => void
style?: StyleProp<ViewStyle>
animationIn?: AnimationConfig;
animationInTiming?: number;
animationOut?: AnimationConfig;
animationOutTiming?: number;
avoidKeyboard?: boolean;
backdropColor?: string;
backdropOpacity?: number;
backdropTransitionInTiming?: number;
backdropTransitionOutTiming?: number;
useNativeDriver?: boolean;
children: ReactNode;
isVisible: boolean;
onModalShow?: () => void;
onModalHide?: () => void;
onBackButtonPress?: () => void;
onBackdropPress?: () => void;
onSwipe?: () => void;
onSwipeThreshold?: number;
style?: StyleProp<ViewStyle>;
swipeDirection?: string;
}

@@ -29,3 +32,3 @@

export default Modal
export default Modal;
}

@@ -1,2 +0,2 @@

import React, { Component } from 'react';
import React, { Component } from "react";
import {

@@ -8,4 +8,7 @@ Dimensions,

KeyboardAvoidingView,
} from 'react-native';
import PropTypes from 'prop-types';
Platform,
PanResponder,
Animated
} from "react-native";
import PropTypes from "prop-types";
import {

@@ -15,7 +18,7 @@ View,

registerAnimation,
createAnimation,
} from 'react-native-animatable';
import * as ANIMATION_DEFINITIONS from './animations';
createAnimation
} from "react-native-animatable";
import * as ANIMATION_DEFINITIONS from "./animations";
import styles from './index.style.js';
import styles from "./index.style.js";

@@ -31,3 +34,3 @@ // Override default animations

const isObject = obj => {
return obj !== null && typeof obj === 'object';
return obj !== null && typeof obj === "object";
};

@@ -52,13 +55,16 @@

onBackdropPress: PropTypes.func,
onSwipe: PropTypes.func,
swipeThreshold: PropTypes.number,
swipeDirection: PropTypes.oneOf(["up", "down", "left", "right"]),
useNativeDriver: PropTypes.bool,
style: PropTypes.any,
style: PropTypes.any
};
static defaultProps = {
animationIn: 'slideInUp',
animationIn: "slideInUp",
animationInTiming: 300,
animationOut: 'slideOutDown',
animationOut: "slideOutDown",
animationOutTiming: 300,
avoidKeyboard: false,
backdropColor: 'black',
backdropColor: "black",
backdropOpacity: 0.7,

@@ -72,3 +78,4 @@ backdropTransitionInTiming: 300,

onBackButtonPress: () => null,
useNativeDriver: false,
swipeThreshold: 100,
useNativeDriver: false
};

@@ -79,15 +86,22 @@

// isVisible prop to false.
// We also store in the state the device width and height so that we can update the modal on
// We store in the state the device width and height so that we can update the modal on
// device rotation.
state = {
isVisible: false,
deviceWidth: Dimensions.get('window').width,
deviceHeight: Dimensions.get('window').height,
deviceWidth: Dimensions.get("window").width,
deviceHeight: Dimensions.get("window").height,
isSwipeable: this.props.swipeDirection ? true : false,
pan: null
};
transitionLock = null;
inSwipeClosingState = false;
constructor(props) {
super(props);
this._buildAnimations(props);
this.buildAnimations(props);
if (this.state.isSwipeable) {
this.state = { ...this.state, pan: new Animated.ValueXY() };
this.buildPanResponder();
}
}

@@ -103,4 +117,13 @@

) {
this._buildAnimations(nextProps);
this.buildAnimations(nextProps);
}
if (
this.props.backdropOpacity !== nextProps.backdropOpacity &&
this.backdropRef
) {
this.backdropRef.transitionTo(
{ opacity: nextProps.backdropOpacity },
this.props.backdropTransitionInTiming
);
}
}

@@ -116,9 +139,15 @@

if (this.state.isVisible) {
this._open();
this.open();
}
DeviceEventEmitter.addListener('didUpdateDimensions', this._handleDimensionsUpdate);
DeviceEventEmitter.addListener(
"didUpdateDimensions",
this.handleDimensionsUpdate
);
}
componentWillUnmount() {
DeviceEventEmitter.removeListener('didUpdateDimensions', this._handleDimensionsUpdate);
DeviceEventEmitter.removeListener(
"didUpdateDimensions",
this.handleDimensionsUpdate
);
}

@@ -129,6 +158,5 @@

if (this.props.isVisible && !prevProps.isVisible) {
this._open();
}
// On modal close request, we slide the view down and fade out the backdrop
else if (!this.props.isVisible && prevProps.isVisible) {
this.open();
} else if (!this.props.isVisible && prevProps.isVisible) {
// On modal close request, we slide the view down and fade out the backdrop
this._close();

@@ -138,4 +166,85 @@ }

buildPanResponder = () => {
let animEvt = null;
if (
this.props.swipeDirection === "right" ||
this.props.swipeDirection === "left"
) {
animEvt = Animated.event([null, { dx: this.state.pan.x }]);
} else {
animEvt = Animated.event([null, { dy: this.state.pan.y }]);
}
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: (evt, gestureState) => {
// Dim the background while swiping the modal
const accDistance = this.getAccDistancePerDirection(gestureState);
const newOpacityFactor = 1 - accDistance / this.state.deviceWidth;
if (this.isSwipeDirectionAllowed(gestureState)) {
this.backdropRef.transitionTo({
opacity: this.props.backdropOpacity * newOpacityFactor
});
animEvt(evt, gestureState);
}
},
onPanResponderRelease: (evt, gestureState) => {
// Call the onSwipe prop if the threshold has been exceeded
const accDistance = this.getAccDistancePerDirection(gestureState);
if (accDistance > this.props.swipeThreshold) {
if (this.props.onSwipe) {
this.inSwipeClosingState = true;
this.props.onSwipe();
return;
}
}
//Reset backdrop opacity and modal position
this.backdropRef.transitionTo(
{ opacity: this.props.backdropOpacity },
this.props.backdropTransitionInTiming
);
Animated.spring(this.state.pan, {
toValue: { x: 0, y: 0 },
bounciness: 0
}).start();
}
});
};
getAccDistancePerDirection = gestureState => {
switch (this.props.swipeDirection) {
case "up":
return -gestureState.dy;
case "down":
return gestureState.dy;
case "right":
return gestureState.dx;
case "left":
return -gestureState.dx;
default:
return 0;
}
};
isSwipeDirectionAllowed = ({ dy, dx }) => {
const draggedDown = dy > 0;
const draggedUp = dy < 0;
const draggedLeft = dx < 0;
const draggedRight = dx > 0;
if (this.props.swipeDirection === "up" && draggedUp) {
return true;
} else if (this.props.swipeDirection === "down" && draggedDown) {
return true;
} else if (this.props.swipeDirection === "right" && draggedRight) {
return true;
} else if (this.props.swipeDirection === "left" && draggedLeft) {
return true;
}
return false;
};
// User can define custom react-native-animatable animations, see PR #72
_buildAnimations = props => {
buildAnimations = props => {
let animationIn = props.animationIn;

@@ -145,9 +254,9 @@ let animationOut = props.animationOut;

if (isObject(animationIn)) {
makeAnimation('animationIn', animationIn);
animationIn = 'animationIn';
makeAnimation("animationIn", animationIn);
animationIn = "animationIn";
}
if (isObject(animationOut)) {
makeAnimation('animationOut', animationOut);
animationOut = 'animationOut';
makeAnimation("animationOut", animationOut);
animationOut = "animationOut";
}

@@ -159,7 +268,10 @@

_handleDimensionsUpdate = dimensionsUpdate => {
handleDimensionsUpdate = dimensionsUpdate => {
// Here we update the device dimensions in the state if the layout changed (triggering a render)
const deviceWidth = Dimensions.get('window').width;
const deviceHeight = Dimensions.get('window').height;
if (deviceWidth !== this.state.deviceWidth || deviceHeight !== this.state.deviceHeight) {
const deviceWidth = Dimensions.get("window").width;
const deviceHeight = Dimensions.get("window").height;
if (
deviceWidth !== this.state.deviceWidth ||
deviceHeight !== this.state.deviceHeight
) {
this.setState({ deviceWidth, deviceHeight });

@@ -169,3 +281,3 @@ }

_open = () => {
open = () => {
if (this.transitionLock) return;

@@ -175,4 +287,12 @@ this.transitionLock = true;

{ opacity: this.props.backdropOpacity },
this.props.backdropTransitionInTiming,
this.props.backdropTransitionInTiming
);
// This is for reset the pan position, if not modal get stuck
// at the last release position when you try to open it.
// Could certainly be improve - no idea for the moment.
if (this.state.isSwipeable) {
this.state.pan.setValue({ x: 0, y: 0 });
}
this.contentRef[this.animationIn](this.props.animationInTiming).then(() => {

@@ -182,4 +302,3 @@ this.transitionLock = false;

this._close();
}
else {
} else {
this.props.onModalShow();

@@ -193,9 +312,27 @@ }

this.transitionLock = true;
this.backdropRef.transitionTo({ opacity: 0 }, this.props.backdropTransitionOutTiming);
this.contentRef[this.animationOut](this.props.animationOutTiming).then(() => {
this.backdropRef.transitionTo(
{ opacity: 0 },
this.props.backdropTransitionOutTiming
);
let animationOut = this.animationOut;
if (this.inSwipeClosingState) {
this.inSwipeClosingState = false;
if (this.props.swipeDirection === "up") {
animationOut = "slideOutUp";
} else if (this.props.swipeDirection === "down") {
animationOut = "slideOutDown";
} else if (this.props.swipeDirection === "right") {
animationOut = "slideOutRight";
} else if (this.props.swipeDirection === "left") {
animationOut = "slideOutLeft";
}
}
this.contentRef[animationOut](this.props.animationOutTiming).then(() => {
this.transitionLock = false;
if (this.props.isVisible) {
this._open();
}
else {
this.open();
} else {
this.setState({ isVisible: false });

@@ -232,10 +369,18 @@ this.props.onModalHide();

styles.content,
style,
style
];
let panHandlers = {};
let panPosition = {};
if (this.state.isSwipeable) {
panHandlers = { ...this.panResponder.panHandlers };
panPosition = this.state.pan.getLayout();
}
const containerView = (
<View
{...panHandlers}
ref={ref => (this.contentRef = ref)}
style={computedStyle}
pointerEvents={'box-none'}
style={[panPosition, computedStyle]}
pointerEvents="box-none"
useNativeDriver={useNativeDriver}

@@ -251,3 +396,3 @@ {...otherProps}

transparent={true}
animationType={'none'}
animationType={"none"}
visible={this.state.isVisible}

@@ -266,4 +411,4 @@ onRequestClose={onBackButtonPress}

width: deviceWidth,
height: deviceHeight,
},
height: deviceHeight
}
]}

@@ -275,4 +420,4 @@ />

<KeyboardAvoidingView
behavior={'padding'}
pointerEvents={'box-none'}
behavior={Platform.OS === "ios" ? "padding" : null}
pointerEvents="box-none"
style={computedStyle.concat([{ margin: 0 }])}

@@ -279,0 +424,0 @@ >

@@ -1,6 +0,6 @@

import { StyleSheet } from 'react-native';
import { StyleSheet } from "react-native";
export default StyleSheet.create({
backdrop: {
position: 'absolute',
position: "absolute",
top: 0,

@@ -11,8 +11,8 @@ bottom: 0,

opacity: 0,
backgroundColor: 'black',
backgroundColor: "black"
},
content: {
flex: 1,
justifyContent: 'center',
},
justifyContent: "center"
}
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc