An enhanced React Native modal component with animations, customizable backdrop, and swipe-to-dismiss functionality
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
React Native Modal is an enhanced, animated, and highly customizable modal component for React Native applications. It extends the original React Native Modal component by adding smooth enter/exit animations, customizable backdrop appearance, swipe-to-dismiss functionality, and comprehensive event handling with device rotation support and keyboard avoidance.
npm install react-native-modal or yarn add react-native-modalimport Modal from "react-native-modal";For named imports:
import {
ReactNativeModal,
ModalProps,
OnSwipeCompleteParams,
AnimationEvent,
Animations,
SupportedAnimation,
Orientation,
Direction,
PresentationStyle,
OnOrientationChange,
GestureResponderEvent
} from "react-native-modal";CommonJS:
const Modal = require("react-native-modal");For TypeScript usage with React Native types:
import { StyleProp, ViewStyle, NativeSyntheticEvent, NativeTouchEvent } from "react-native";
import { PanResponderGestureState } from "react-native";import React, { useState } from "react";
import { Button, Text, View } from "react-native";
import Modal from "react-native-modal";
function ModalExample() {
const [isModalVisible, setModalVisible] = useState(false);
const toggleModal = () => {
setModalVisible(!isModalVisible);
};
return (
<View style={{ flex: 1 }}>
<Button title="Show modal" onPress={toggleModal} />
<Modal isVisible={isModalVisible}>
<View style={{ flex: 1, backgroundColor: "white" }}>
<Text>Hello! I am a modal.</Text>
<Button title="Hide modal" onPress={toggleModal} />
</View>
</Modal>
</View>
);
}React Native Modal is built around several key components:
The main modal component providing enhanced functionality over React Native's built-in Modal.
/**
* Enhanced React Native modal component with animations and gesture support
*/
class ReactNativeModal extends React.Component<ModalProps, State> {
static defaultProps: typeof defaultProps;
}
/**
* Default export - same as ReactNativeModal class
*/
export default ReactNativeModal;Complete props interface for configuring modal behavior and appearance.
type ModalProps = ViewProps & {
children: React.ReactNode;
// Swipe Gesture Props
onSwipeStart?: (gestureState: PanResponderGestureState) => void;
onSwipeMove?: (percentageShown: number, gestureState: PanResponderGestureState) => void;
onSwipeComplete?: (params: OnSwipeCompleteParams, gestureState: PanResponderGestureState) => void;
onSwipeCancel?: (gestureState: PanResponderGestureState) => void;
style?: StyleProp<ViewStyle>;
swipeDirection?: Direction | Array<Direction>;
// Inherited React Native Modal Props
onDismiss?: () => void;
onShow?: () => void;
hardwareAccelerated?: boolean;
onOrientationChange?: OnOrientationChange;
presentationStyle?: PresentationStyle;
// Additional Modal Props (defaults provided by defaultProps)
useNativeDriverForBackdrop?: boolean;
} & typeof defaultProps;
// All properties from defaultProps are automatically available as optional props:
// animationIn, animationInTiming, animationOut, animationOutTiming, avoidKeyboard,
// coverScreen, hasBackdrop, backdropColor, backdropOpacity, backdropTransitionInTiming,
// backdropTransitionOutTiming, customBackdrop, useNativeDriver, deviceHeight, deviceWidth,
// hideModalContentWhileAnimating, propagateSwipe, isVisible, panResponderThreshold,
// swipeThreshold, onModalShow, onModalWillShow, onModalHide, onModalWillHide,
// onBackdropPress, onBackButtonPress, scrollTo, scrollOffset, scrollOffsetMax,
// scrollHorizontal, statusBarTranslucent, supportedOrientationsParameters passed to the onSwipeComplete callback when a swipe gesture completes.
interface OnSwipeCompleteParams {
swipingDirection: Direction;
}/**
* Initializes custom slide animations, overriding react-native-animatable defaults
*/
function initializeAnimations(): void;
/**
* Builds animation configuration objects, handling custom animation definitions
*/
function buildAnimations(options: {
animationIn: Animation | CustomAnimation;
animationOut: Animation | CustomAnimation;
}): Animations;
/**
* Utility function for animation calculations: returns -(x - 1)
*/
function reversePercentage(x: number): number;
/**
* Creates slide translation animation objects for react-native-animatable
*/
function makeSlideTranslation(
translationType: string,
fromValue: number,
toValue: number
): CustomAnimation;type SupportedAnimation = Animation | CustomAnimation;
interface Animations {
animationIn: string;
animationOut: string;
}
type Orientation =
| "portrait"
| "portrait-upside-down"
| "landscape"
| "landscape-left"
| "landscape-right";
type Direction = "up" | "down" | "left" | "right";
type PresentationStyle =
| "fullScreen"
| "pageSheet"
| "formSheet"
| "overFullScreen";
type AnimationEvent = (...args: any[]) => void;
type OnOrientationChange = (orientation: NativeSyntheticEvent<any>) => void;
type OrNull<T> = null | T;
interface GestureResponderEvent extends NativeSyntheticEvent<NativeTouchEvent> {}
interface PanResponderGestureState {
stateID: number;
moveX: number;
moveY: number;
x0: number;
y0: number;
dx: number;
dy: number;
vx: number;
vy: number;
numberActiveTouches: number;
}
// React Native types used in the API
type StyleProp<T> = T | T[] | null | undefined;
type ViewStyle = Record<string, any>;
interface ViewProps {
style?: StyleProp<ViewStyle>;
testID?: string;
accessible?: boolean;
accessibilityLabel?: string;
accessibilityHint?: string;
accessibilityRole?: string;
accessibilityState?: Record<string, any>;
accessibilityValue?: Record<string, any>;
onLayout?: (event: any) => void;
pointerEvents?: 'none' | 'box-none' | 'box-only' | 'auto';
removeClippedSubviews?: boolean;
renderToHardwareTextureAndroid?: boolean;
shouldRasterizeIOS?: boolean;
collapsable?: boolean;
needsOffscreenAlphaCompositing?: boolean;
onStartShouldSetResponder?: (event: any) => boolean;
onMoveShouldSetResponder?: (event: any) => boolean;
onResponderGrant?: (event: any) => void;
onResponderMove?: (event: any) => void;
onResponderRelease?: (event: any) => void;
onResponderTerminate?: (event: any) => void;
onResponderTerminationRequest?: (event: any) => boolean;
onStartShouldSetResponderCapture?: (event: any) => boolean;
onMoveShouldSetResponderCapture?: (event: any) => boolean;
}
interface NativeSyntheticEvent<T> {
nativeEvent: T;
currentTarget: number;
target: number;
bubbles?: boolean;
cancelable?: boolean;
defaultPrevented?: boolean;
eventPhase?: number;
isTrusted?: boolean;
preventDefault(): void;
stopPropagation(): void;
persist(): void;
timeStamp: number;
type: string;
}
interface NativeTouchEvent {
changedTouches: Array<NativeTouchEvent>;
identifier: string;
locationX: number;
locationY: number;
pageX: number;
pageY: number;
target: string;
timestamp: number;
touches: Array<NativeTouchEvent>;
}const defaultProps = {
animationIn: "slideInUp" as Animation | CustomAnimation,
animationInTiming: 300,
animationOut: "slideOutDown" as Animation | CustomAnimation,
animationOutTiming: 300,
avoidKeyboard: false,
coverScreen: true,
hasBackdrop: true,
backdropColor: "black",
backdropOpacity: 0.7,
backdropTransitionInTiming: 300,
backdropTransitionOutTiming: 300,
customBackdrop: null,
useNativeDriver: false,
deviceHeight: null,
deviceWidth: null,
hideModalContentWhileAnimating: false,
propagateSwipe: false,
isVisible: false,
panResponderThreshold: 4,
swipeThreshold: 100,
onModalShow: () => null,
onModalWillShow: () => null,
onModalHide: () => null,
onModalWillHide: () => null,
onBackdropPress: () => null,
onBackButtonPress: () => null,
scrollTo: null,
scrollOffset: 0,
scrollOffsetMax: 0,
scrollHorizontal: false,
statusBarTranslucent: false,
supportedOrientations: ["portrait", "landscape"] as Orientation[]
};import React, { useState } from "react";
import { Button, Text, View } from "react-native";
import Modal from "react-native-modal";
function AnimatedModal() {
const [isVisible, setVisible] = useState(false);
return (
<View style={{ flex: 1 }}>
<Button title="Show Modal" onPress={() => setVisible(true)} />
<Modal
isVisible={isVisible}
animationIn="slideInUp"
animationOut="slideOutDown"
animationInTiming={600}
animationOutTiming={600}
onBackdropPress={() => setVisible(false)}>
<View style={{ backgroundColor: "white", padding: 20 }}>
<Text>This is a modal with custom animations!</Text>
<Button title="Close" onPress={() => setVisible(false)} />
</View>
</Modal>
</View>
);
}import React, { useState } from "react";
import { Text, View } from "react-native";
import Modal from "react-native-modal";
function SwipeableModal() {
const [isVisible, setVisible] = useState(true);
return (
<Modal
isVisible={isVisible}
swipeDirection={["up", "down"]}
swipeThreshold={100}
onSwipeComplete={() => setVisible(false)}
onSwipeStart={(gestureState) => console.log("Swipe started")}
onSwipeMove={(percentageShown) => console.log("Swipe progress:", percentageShown)}>
<View style={{ backgroundColor: "white", padding: 20 }}>
<Text>Swipe up or down to dismiss this modal</Text>
</View>
</Modal>
);
}import React, { useState } from "react";
import { Text, View, ImageBackground } from "react-native";
import Modal from "react-native-modal";
function CustomBackdropModal() {
const [isVisible, setVisible] = useState(true);
const CustomBackdrop = () => (
<ImageBackground
source={{ uri: "https://example.com/background.jpg" }}
style={{ flex: 1 }}
blurRadius={5}
/>
);
return (
<Modal
isVisible={isVisible}
customBackdrop={<CustomBackdrop />}
onBackdropPress={() => setVisible(false)}>
<View style={{ backgroundColor: "white", padding: 20 }}>
<Text>Modal with custom backdrop</Text>
</View>
</Modal>
);
}import React, { useState } from "react";
import { TextInput, Button, View } from "react-native";
import Modal from "react-native-modal";
function KeyboardModal() {
const [isVisible, setVisible] = useState(true);
const [text, setText] = useState("");
return (
<Modal
isVisible={isVisible}
avoidKeyboard={true}
onBackdropPress={() => setVisible(false)}>
<View style={{ backgroundColor: "white", padding: 20 }}>
<TextInput
value={text}
onChangeText={setText}
placeholder="Type something..."
style={{ borderWidth: 1, padding: 10, marginBottom: 10 }}
/>
<Button title="Close" onPress={() => setVisible(false)} />
</View>
</Modal>
);
}import React, { useState } from "react";
import { Text, Button, View } from "react-native";
import Modal from "react-native-modal";
function FullScreenModal() {
const [isVisible, setVisible] = useState(true);
return (
<Modal
isVisible={isVisible}
style={{ margin: 0 }} // Remove default margin for full screen
animationIn="slideInRight"
animationOut="slideOutRight">
<View style={{ flex: 1, backgroundColor: "white", padding: 20 }}>
<Text>Full screen modal</Text>
<Button title="Close" onPress={() => setVisible(false)} />
</View>
</Modal>
);
}