@zwdvidal/react-native-dropdownalert
Version:
A simple alert to notify users about new chat messages, something went wrong or everything is ok.
650 lines (646 loc) • 18.8 kB
JavaScript
import React, { Component } from "react";
import {
StyleSheet,
SafeAreaView,
View,
TouchableOpacity,
Animated,
StatusBar,
PanResponder
} from "react-native";
import PropTypes from "prop-types";
import {
StatusBarDefaultBarStyle,
StatusBarDefaultBackgroundColor,
DEFAULT_IMAGE_DIMENSIONS,
WINDOW,
IS_IOS,
IS_ANDROID,
IS_IOS_BELOW_11
} from "./constants";
import { validateType } from "./functions";
import Label from "./label";
import ImageView from "./imageview";
export default class DropdownAlert extends Component {
static propTypes = {
imageSrc: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
infoImageSrc: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
warnImageSrc: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
errorImageSrc: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
successImageSrc: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
cancelBtnImageSrc: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
infoColor: PropTypes.string,
warnColor: PropTypes.string,
errorColor: PropTypes.string,
successColor: PropTypes.string,
closeInterval: PropTypes.number,
startDelta: PropTypes.number,
endDelta: PropTypes.number,
wrapperStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
containerStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
safeAreaStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
titleStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
messageStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
imageStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
cancelBtnImageStyle: PropTypes.oneOfType([
PropTypes.object,
PropTypes.number
]),
titleNumOfLines: PropTypes.number,
messageNumOfLines: PropTypes.number,
onClose: PropTypes.func,
onCancel: PropTypes.func,
showCancel: PropTypes.bool,
tapToCloseEnabled: PropTypes.bool,
panResponderEnabled: PropTypes.bool,
replaceEnabled: PropTypes.bool,
translucent: PropTypes.bool,
useNativeDriver: PropTypes.bool,
isInteraction: PropTypes.bool,
activeStatusBarStyle: PropTypes.string,
activeStatusBarBackgroundColor: PropTypes.string,
inactiveStatusBarStyle: PropTypes.string,
inactiveStatusBarBackgroundColor: PropTypes.string,
updateStatusBar: PropTypes.bool,
elevation: PropTypes.number,
zIndex: PropTypes.number,
sensitivity: PropTypes.number,
defaultContainer: PropTypes.oneOfType([PropTypes.object, PropTypes.number]),
defaultTextContainer: PropTypes.oneOfType([
PropTypes.object,
PropTypes.number
]),
renderImage: PropTypes.func,
renderCancel: PropTypes.func,
renderTitle: PropTypes.func,
renderMessage: PropTypes.func,
testID: PropTypes.string,
accessibilityLabel: PropTypes.string,
accessible: PropTypes.bool,
titleTextProps: PropTypes.object,
messageTextProps: PropTypes.object
};
static defaultProps = {
onClose: null,
onCancel: null,
closeInterval: 5000,
startDelta: -100,
endDelta: 0,
titleNumOfLines: 1,
messageNumOfLines: 3,
imageSrc: null,
infoImageSrc: require("./assets/info.png"),
warnImageSrc: require("./assets/warn.png"),
errorImageSrc: require("./assets/error.png"),
successImageSrc: require("./assets/success.png"),
cancelBtnImageSrc: require("./assets/cancel.png"),
infoColor: "#2B73B6",
warnColor: "#cd853f",
errorColor: "#f2564b",
successColor: "#7ec048",
showCancel: false,
tapToCloseEnabled: true,
panResponderEnabled: true,
replaceEnabled: true,
wrapperStyle: null,
containerStyle: {
padding: 16,
flexDirection: "row"
},
safeAreaStyle: {
flexDirection: "row",
flex: 1
},
titleStyle: {
fontSize: 16,
textAlign: "left",
fontWeight: "bold",
color: "white",
backgroundColor: "transparent"
},
messageStyle: {
fontSize: 15,
textAlign: "left",
fontWeight: "normal",
color: "white",
backgroundColor: "transparent"
},
imageStyle: {
padding: 8,
width: DEFAULT_IMAGE_DIMENSIONS,
height: DEFAULT_IMAGE_DIMENSIONS,
alignSelf: "center"
},
buttonTextStyle: {
fontSize: 15,
fontWeight: "normal",
color: "white"
},
cancelBtnImageStyle: {
padding: 8,
width: DEFAULT_IMAGE_DIMENSIONS,
height: DEFAULT_IMAGE_DIMENSIONS,
alignSelf: "center"
},
defaultContainer: {
padding: 8,
paddingTop: IS_ANDROID ? 0 : 20,
flexDirection: "row"
},
defaultTextContainer: {
flex: 1,
padding: 8
},
translucent: false,
activeStatusBarStyle: "light-content",
activeStatusBarBackgroundColor: StatusBarDefaultBackgroundColor,
inactiveStatusBarStyle: StatusBarDefaultBarStyle,
inactiveStatusBarBackgroundColor: StatusBarDefaultBackgroundColor,
updateStatusBar: true,
isInteraction: undefined,
useNativeDriver: true,
elevation: 1,
zIndex: null,
sensitivity: 20,
renderImage: undefined,
renderCancel: undefined,
renderTitle: undefined,
renderMessage: undefined,
testID: undefined,
accessibilityLabel: undefined,
accessible: false,
titleTextProps: undefined,
messageTextProps: undefined
};
constructor(props) {
super(props);
this.state = {
animationValue: new Animated.Value(0),
duration: 450,
type: "",
message: "",
buttonText: "",
onButtonClick: () => {},
title: "",
isOpen: false,
startDelta: props.startDelta,
endDelta: props.endDelta,
topValue: 0,
payload: {}
};
this.types = {
INFO: "info",
WARN: "warn",
ERROR: "error",
SUCCESS: "success",
CUSTOM: "custom"
};
}
componentDidMount() {
this.createPanResponder();
}
componentWillUnmount() {
if (this._closeTimeoutId != null) {
clearTimeout(this._closeTimeoutId);
}
if (this.state.isOpen) {
this.closeDirectly();
}
}
createPanResponder = () => {
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => {
return this.props.panResponderEnabled;
},
onMoveShouldSetPanResponder: (evt, gestureState) => {
return (
Math.abs(gestureState.dx) < this.props.sensitivity &&
Math.abs(gestureState.dy) >= this.props.sensitivity &&
this.props.panResponderEnabled
);
},
onPanResponderMove: (evt, gestureState) => {
if (gestureState.dy < 0) {
this.setState({
topValue: gestureState.dy
});
}
},
onPanResponderRelease: (evt, gestureState) => {
const delta = this.state.startDelta / 5;
if (gestureState.dy < delta) {
this.close("pan");
}
},
onPanResponderTerminate: (evt, gestureState) => {
const delta = this.state.startDelta / 5;
if (gestureState.dy < delta) {
this.close("pan");
}
}
});
};
alertWithType = (
type,
title,
message,
buttonText,
onButtonClick,
payload,
interval
) => {
if (validateType(type) == false) {
return;
}
if (typeof title !== "string") {
title = `${title}`;
}
if (typeof message !== "string") {
message = `${message}`;
}
if (typeof buttonText !== "string") {
buttonText = `${buttonText}`;
}
const closeInterval =
typeof interval === "number" && interval > 1
? interval
: this.props.closeInterval;
if (this.props.replaceEnabled == false) {
this.setState({
type: type,
message: message,
buttonText: buttonText,
onButtonClick: onButtonClick,
title: title,
topValue: 0,
payload: payload
});
if (this.state.isOpen == false) {
this.setState({
isOpen: true
});
this.animate(1);
}
if (closeInterval > 1) {
if (this._closeTimeoutId != null) {
clearTimeout(this._closeTimeoutId);
}
this._closeTimeoutId = setTimeout(
function() {
this.close("automatic");
}.bind(this),
closeInterval
);
}
} else {
var delayInMilliSeconds = 0;
if (this.state.isOpen == true) {
delayInMilliSeconds = 475;
this.close();
}
var self = this;
setTimeout(
function() {
if (self.state.isOpen == false) {
self.setState({
type: type,
message: message,
buttonText: buttonText,
onButtonClick: onButtonClick,
title: title,
isOpen: true,
topValue: 0,
payload: payload
});
}
self.animate(1);
if (closeInterval > 1) {
this._closeTimeoutId = setTimeout(
function() {
self.close("automatic");
}.bind(self),
closeInterval
);
}
}.bind(this),
delayInMilliSeconds
);
}
};
resetStatusBarColor = () => {
if (this.props.updateStatusBar) {
if (IS_ANDROID) {
StatusBar.setBackgroundColor(
this.props.inactiveStatusBarBackgroundColor,
true
);
}
StatusBar.setBarStyle(this.props.inactiveStatusBarStyle, true);
}
};
close = action => {
if (action == undefined) {
action = "programmatic";
}
var onClose = this.props.onClose;
if (action == "cancel") {
onClose = this.props.onCancel;
}
if (this.state.isOpen) {
if (this._closeTimeoutId != null) {
clearTimeout(this._closeTimeoutId);
}
this.animate(0);
this.resetStatusBarColor();
setTimeout(
function() {
if (this.state.isOpen) {
this.resetStatusBarColor();
this.setState({
isOpen: false
});
if (onClose) {
const data = {
type: this.state.type,
title: this.state.title,
message: this.state.message,
action: action, // !!! How the alert was closed: automatic, programmatic, tap, pan or cancel
payload: this.state.payload
};
onClose(data);
}
}
}.bind(this),
this.state.duration
);
}
};
closeDirectly() {
if (this.state.isOpen) {
if (this._closeTimeoutId != null) {
clearTimeout(this._closeTimeoutId);
}
this.setState({
isOpen: false
});
this.resetStatusBarColor();
}
}
animate = toValue => {
Animated.spring(this.state.animationValue, {
toValue: toValue,
duration: this.state.duration,
friction: 9,
useNativeDriver: this.props.useNativeDriver,
isInteraction: this.props.isInteraction
}).start();
};
onLayoutEvent(event) {
const { x, y, width, height } = event.nativeEvent.layout;
var actualStartDelta = this.state.startDelta;
var actualEndDelta = this.state.endDelta;
const { startDelta, endDelta } = this.props;
if (startDelta < 0) {
const delta = 0 - height;
if (delta != startDelta) {
actualStartDelta = delta;
}
} else if (startDelta > WINDOW.height) {
actualStartDelta = WINDOW.height + height;
}
if (endDelta < 0) {
actualEndDelta = 0;
} else if (endDelta > WINDOW.height) {
actualEndDelta = WINDOW.height - height;
}
const heightDelta = WINDOW.height - endDelta - height;
if (heightDelta < 0) {
actualEndDelta = endDelta + heightDelta;
}
if (
actualStartDelta != this.state.startDelta ||
actualEndDelta != this.state.endDelta
) {
this.setState({
startDelta: actualStartDelta,
endDelta: actualEndDelta
});
}
}
getStyleForType(type) {
const { defaultContainer } = this.props;
switch (type) {
case this.types.INFO:
return [
StyleSheet.flatten(defaultContainer),
{ backgroundColor: this.props.infoColor }
];
case this.types.WARN:
return [
StyleSheet.flatten(defaultContainer),
{ backgroundColor: this.props.warnColor }
];
case this.types.ERROR:
return [
StyleSheet.flatten(defaultContainer),
{ backgroundColor: this.props.errorColor }
];
case this.types.SUCCESS:
return [
StyleSheet.flatten(defaultContainer),
{ backgroundColor: this.props.successColor }
];
default:
return [
StyleSheet.flatten(defaultContainer),
StyleSheet.flatten(this.props.containerStyle)
];
}
}
getSourceForType(type) {
switch (type) {
case this.types.INFO:
return this.props.infoImageSrc;
case this.types.WARN:
return this.props.warnImageSrc;
case this.types.ERROR:
return this.props.errorImageSrc;
case this.types.SUCCESS:
return this.props.successImageSrc;
default:
return this.props.imageSrc;
}
}
getBackgroundColorForType(type) {
switch (type) {
case this.types.INFO:
return this.props.infoColor;
case this.types.WARN:
return this.props.warnColor;
case this.types.ERROR:
return this.props.errorColor;
case this.types.SUCCESS:
return this.props.successColor;
default:
return this.props.containerStyle.backgroundColor;
}
}
renderImage(source) {
if (this.props.renderImage) {
return this.props.renderImage(this.props, this.state);
}
return (
<ImageView
style={StyleSheet.flatten(this.props.imageStyle)}
source={source}
/>
);
}
renderCancel(show) {
if (show) {
if (this.props.renderCancel) {
return this.props.renderCancel(this.props);
} else {
return (
<TouchableOpacity
style={{
alignSelf: this.props.cancelBtnImageStyle.alignSelf,
width: this.props.cancelBtnImageStyle.width,
height: this.props.cancelBtnImageStyle.height
}}
onPress={() => this.close("cancel")}
>
<ImageView
style={this.props.cancelBtnImageStyle}
source={this.props.cancelBtnImageSrc}
/>
</TouchableOpacity>
);
}
}
return null;
}
renderTitle() {
// if (this.props.renderTitle) {
// return this.props.renderTitle(this.props, this.state);
// }
// const { titleTextProps, titleStyle, titleNumOfLines } = this.props;
// return <Label {...titleTextProps} style={StyleSheet.flatten(titleStyle)} numberOfLines={titleNumOfLines} text={this.state.title} />;
return null;
}
renderMessage() {
if (this.props.renderMessage) {
return this.props.renderMessage(this.props, this.state);
}
const { messageTextProps, messageStyle, messageNumOfLines } = this.props;
return (
<Label
{...messageTextProps}
style={StyleSheet.flatten(messageStyle)}
numberOfLines={messageNumOfLines}
text={this.state.message}
/>
);
}
renderButton = () => {
const { buttonTextStyle } = this.props;
return (
<TouchableOpacity
onPress={this.state.onButtonClick}
style={{ justifyContent: "center", alignItems: "center" }}
>
<Label
text={this.state.buttonText}
style={StyleSheet.flatten(buttonTextStyle)}
/>
</TouchableOpacity>
);
};
render() {
const { isOpen, type } = this.state;
const ContentView = IS_IOS_BELOW_11 ? View : SafeAreaView;
if (isOpen) {
let style = this.getStyleForType(type);
const source = this.getSourceForType(type);
const backgroundColor = this.getBackgroundColorForType(type);
let {
activeStatusBarBackgroundColor,
translucent,
updateStatusBar,
activeStatusBarStyle,
cancelBtnImageSrc,
showCancel
} = this.props;
if (IS_ANDROID) {
if (translucent) {
style = [style, { paddingTop: StatusBar.currentHeight }];
}
if (type !== this.types.CUSTOM) {
activeStatusBarBackgroundColor = backgroundColor;
}
}
if (updateStatusBar) {
if (IS_ANDROID) {
StatusBar.setBackgroundColor(activeStatusBarBackgroundColor, true);
StatusBar.setTranslucent(translucent);
}
StatusBar.setBarStyle(activeStatusBarStyle, true);
}
let wrapperStyle = {
transform: [
{
translateY: this.state.animationValue.interpolate({
inputRange: [0, 1],
outputRange: [this.state.startDelta, this.state.endDelta]
})
}
],
position: "absolute",
top: this.state.topValue,
left: 0,
right: 0,
elevation: this.props.elevation
};
if (this.props.zIndex != null) wrapperStyle["zIndex"] = this.props.zIndex;
return (
<Animated.View
ref={ref => (this.mainView = ref)}
{...this._panResponder.panHandlers}
style={[wrapperStyle, this.props.wrapperStyle]}
>
<TouchableOpacity
activeOpacity={
!this.props.tapToCloseEnabled || showCancel ? 1 : 0.95
}
onPress={
!this.props.tapToCloseEnabled ? null : () => this.close("tap")
}
disabled={!this.props.tapToCloseEnabled}
onLayout={event => this.onLayoutEvent(event)}
testID={this.props.testID}
accessibilityLabel={this.props.accessibilityLabel}
accessible={this.props.accessible}
>
<View style={style}>
<ContentView style={StyleSheet.flatten(this.props.safeAreaStyle)}>
{this.renderImage(source)}
<View
style={StyleSheet.flatten(this.props.defaultTextContainer)}
>
{this.renderTitle()}
{this.renderMessage()}
</View>
{this.renderButton()}
</ContentView>
{this.renderCancel(showCancel)}
</View>
</TouchableOpacity>
</Animated.View>
);
}
return null;
}
}