react-native-ui-lib
Version:
UI Toolset & Components Library for React Native
367 lines (331 loc) • 9.65 kB
JavaScript
import React from 'react';
import {StyleSheet} from 'react-native';
import PropTypes from 'prop-types';
import _ from 'lodash';
import * as Animatable from 'react-native-animatable';
import {BlurView} from 'react-native-blur';
import {BaseComponent} from '../../commons';
import View from '../view';
import Button from '../button';
import {ThemeManager, Colors, Typography, BorderRadiuses} from '../../style';
import Assets from '../../assets';
const DURATION = 300;
const DELAY = 100;
/**
* @description Toast component for showing a feedback about a user action.
* @extends: Animatable.View
* @extendslink: https://github.com/oblador/react-native-animatable
* @gif: https://media.giphy.com/media/3oFzm1pKqGXybiDUre/giphy.gif
* @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/ToastsScreen.js
*/
export default class Toast extends BaseComponent {
static displayName = 'Toast';
static propTypes = {
/**
* Whether to show or hide the toast
*/
visible: PropTypes.bool,
/**
* The position of the toast. top or bottom
*/
position: PropTypes.oneOf(['relative', 'top', 'bottom']),
/**
* The height of the toast
*/
height: PropTypes.number,
/**
* The background color of the toast
*/
backgroundColor: PropTypes.string,
/**
* the toast content color (message, actions labels)
*/
color: PropTypes.string,
/**
* the toast message
*/
message: PropTypes.string,
/**
* a custom style for the toast message
*/
messageStyle: PropTypes.oneOfType([PropTypes.object, PropTypes.number, PropTypes.array]),
/**
* one or two actions for the user to display in the toast
*/
actions: PropTypes.arrayOf(PropTypes.shape(Button.propTypes)),
/**
* callback for dismiss action
*/
onDismiss: PropTypes.func,
/**
* show dismiss action
*/
allowDismiss: PropTypes.bool,
/**
* should message be centered in the toast
*/
centerMessage: PropTypes.bool,
/**
* should the toast appear/disappear with animation
*/
animated: PropTypes.bool,
/**
* enable blur effect for Toast
*/
enableBlur: PropTypes.bool,
/**
* blur option for blur effect according to react-native-blur lib (make sure enableBlur is on)
*/
blurOptions: PropTypes.object,
/**
* custom zIndex for toast
*/
zIndex: PropTypes.number,
};
static defaultProps = {
position: 'top',
color: Colors.white,
animated: true,
zIndex: 100,
};
state = {
isVisible: false,
animationConfig: this.getAnimation(true),
contentAnimation: this.getContentAnimation(true),
duration: DURATION,
delay: DELAY,
};
constructor(props) {
super(props);
const {animated, position} = this.props;
if (animated && position === 'relative') {
setupRelativeAnimation(getHeight(this.props));
}
}
componentWillReceiveProps(nextProps) {
const {visible, animated, position} = nextProps;
const {isVisible} = this.state;
if (visible !== isVisible) {
if (animated && position === 'relative') {
setupRelativeAnimation(getHeight(nextProps));
}
const newState = animated ? {
animationConfig: this.getAnimation(visible),
contentAnimation: this.getContentAnimation(visible),
} : {
animationConfig: {},
contentAnimation: {},
};
this.setState(newState);
}
}
generateStyles() {
this.styles = createStyles(this.props);
}
getPositionStyle() {
const {position} = this.props;
return (position === 'relative') ? {position} : getAbsolutePositionStyle(position);
}
getAnimation(shouldShow) {
const {position} = this.props;
const animationDescriptor = getAnimationDescriptor(position, this.state);
const {animation, duration, delay} = shouldShow ? animationDescriptor.enter : animationDescriptor.exit;
return {animation, duration, delay, onAnimationEnd: () => this.onAnimationEnd()};
}
getContentAnimation(shouldShow) {
const {position} = this.props;
const {duration, delay} = this.state;
if (position === 'relative') {
return {
animation: shouldShow ? 'fadeIn' : 'fadeOut',
duration,
delay: shouldShow ? delay : 0,
onAnimationEnd: () => this.onAnimationEnd(),
};
}
}
getBlurOptions() {
const {blurOptions} = this.getThemeProps();
return {
blurType: 'light',
amount: 5,
...blurOptions,
};
}
renderMessage() {
const {message, messageStyle, centerMessage, color} = this.props;
const {contentAnimation} = this.state;
return (
<View flex centerH={centerMessage}>
<Animatable.Text style={[this.styles.message, color && {color}, messageStyle]} {...contentAnimation}>
{message}
</Animatable.Text>
</View>
);
}
renderOneAction() {
const action = _.first(this.props.actions);
const {contentAnimation} = this.state;
if (action) {
return (
<Animatable.View {...contentAnimation}>
<Button style={this.styles.oneActionStyle} size="medium" {...action} />
</Animatable.View>
);
}
}
renderTwoActions() {
const {actions} = this.props;
const {contentAnimation} = this.state;
return (
<Animatable.View style={this.styles.containerWithTwoActions} {...contentAnimation}>
<Button size="small" {...actions[0]} />
<Button marginL-12 size="small" {...actions[1]} />
</Animatable.View>
);
}
renderDismissButton() {
const {allowDismiss, onDismiss, color} = this.props;
const {contentAnimation} = this.state;
if (allowDismiss) {
return (
<Animatable.View style={{justifyContent: 'center'}} {...contentAnimation}>
<Button
link
iconStyle={[this.styles.dismissIconStyle, color && {tintColor: color}]}
iconSource={Assets.icons.x}
onPress={onDismiss}
/>
</Animatable.View>
);
}
}
render() {
const {backgroundColor, actions, allowDismiss, enableBlur, zIndex} = this.getThemeProps();
const {animationConfig} = this.state;
const hasOneAction = _.size(actions) === 1;
const hasTwoActions = _.size(actions) === 2;
const positionStyle = this.getPositionStyle();
const height = getHeight(this.props);
const blurOptions = this.getBlurOptions();
const shouldShowToast = this.shouldShowToast();
if (!shouldShowToast) {
return null;
}
return (
<Animatable.View
style={[
this.styles.container,
hasOneAction && this.styles.containerWithOneAction,
positionStyle,
backgroundColor && {backgroundColor},
{height},
{zIndex},
]}
{...animationConfig}
>
{enableBlur && <BlurView style={this.styles.blurView} {...blurOptions} />}
<View row flex centerV spread>
{this.renderMessage()}
{(hasOneAction || allowDismiss) && (
<View row height="100%">
{hasOneAction && this.renderOneAction()}
{this.renderDismissButton()}
</View>
)}
</View>
{hasTwoActions && <View>{this.renderTwoActions()}</View>}
</Animatable.View>
);
}
shouldShowToast() {
const {visible} = this.props;
const {isVisible} = this.state;
return isVisible || (visible && !isVisible);
}
onAnimationEnd() {
const {visible} = this.props;
this.setState({
isVisible: visible,
});
}
}
function createStyles() {
return StyleSheet.create({
container: {
backgroundColor: Colors.rgba(ThemeManager.primaryColor, 0.8),
paddingHorizontal: 15,
},
containerWithOneAction: {
paddingRight: 0,
},
containerWithTwoActions: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
paddingBottom: 14,
},
message: {
color: Colors.white,
...Typography.text80,
},
oneActionStyle: {
borderRadius: BorderRadiuses.br0,
minWidth: undefined,
height: '100%',
backgroundColor: Colors.rgba(ThemeManager.primaryColor, 0.7),
},
dismissIconStyle: {
width: 12,
height: 12,
tintColor: Colors.white,
},
blurView: {
...StyleSheet.absoluteFillObject,
},
});
}
function getAnimationDescriptor(name, {duration = DURATION, delay = DELAY}) {
const defaultProps = {duration, delay: 0};
const animationDescriptorMap = {
top: {
enter: {...defaultProps, animation: 'slideInDown'},
exit: {...defaultProps, animation: 'slideOutUp'},
},
bottom: {
enter: {...defaultProps, animation: 'slideInUp'},
exit: {...defaultProps, animation: 'slideOutDown'},
},
relative: {
enter: {...defaultProps, animation: 'growUp'},
exit: {...defaultProps, animation: 'growDown', delay},
},
};
return animationDescriptorMap[name] || {};
}
function getAbsolutePositionStyle(location) {
return {
position: 'absolute',
left: 0,
right: 0,
[location]: 0,
};
}
function setupRelativeAnimation(height) {
Animatable.initializeRegistryWithDefinitions({
growUp: {
from: {height: 0},
to: {height},
},
growDown: {
from: {height},
to: {height: 0},
},
});
}
function getHeight({height, actions}) {
if (_.isUndefined(height)) {
return (_.size(actions) === 2) ? 92 : 48;
}
return height;
}