封裝一個(gè)HOC渔彰,方便任意頁(yè)面增加Banner功能
BannerConstants.js
export const BannerStatus = {
success: 'success',
warning: 'warning',
error: 'error',
info: 'info',
};
export const BannerTimeout = 3000;
ToastBannerComponent
import React from 'react';
import {
View,
StyleSheet,
Text,
TouchableHighlight,
} from 'react-native';
import PropTypes from 'prop-types';
import svgPath from '../../../../../asserts/SVG/svgPath';
import { BannerStatus } from './BannerConstant';
import SVGImage from '../../../../../common/components/image/SVGImage';
const ToastBannerComponent = (props) => {
const {
status, message, actionText, action
} = props;
if (!message) {
return null;
}
let bannerColor = 'gray';
let backgroundColor = 'transparent';
let path;
if (status === BannerStatus.success) {
bannerColor = 'green;
backgroundColor = '#FEFEFE';
path = svgPath.SUCCESS_TICK_FILLED;
} else if (status === BannerStatus.warning) {
bannerColor = 'yellow';
backgroundColor = '#FEFEFE';
path = svgPath.WARNING_FILLED;
} else if (status === BannerStatus.error) {
bannerColor = 'red';
backgroundColor = 'yellow;
path = svgPath.ERROR_FILLED;
}
return (
<View style={{
backgroundColor,
borderBottomColor: bannerColor,
borderTopColor: bannerColor,
borderTopWidth: StyleSheet.hairlineWidth,
borderBottomWidth: StyleSheet.hairlineWidth,
justifyContent: 'center',
}}>
<View style={{
flexDirection: 'row',
justifyContent: 'space-between',
margin: MarginSize.mini,
}}>
{path &&
(<View style={styles.iconView}>
<SVGImage
{...applyTestIdInProps(props, 'banner svg image')}
path={path}
color={bannerColor}
/>
</View>)
}
<Text
style={[styles.message,
{ textAlign: (status === BannerStatus.info) ? 'center' : 'auto' }]}
>
{message}
</Text>
{actionText && (
<TouchableHighlight
accessible
{...testId('BannerAction-Button')}
underlayColor='transparent'
style={{ alignSelf: 'center' }}
onPress={() => { if (action) { action(status); } }}>
<Text
style={styles.actionText}
>
{actionText}
</Text>
</TouchableHighlight>)
}
</View>
</View>
);
};
ToastBannerComponent.propTypes = {
status: PropTypes.string,
message: PropTypes.string.isRequired,
actionText: PropTypes.string,
action: PropTypes.func,
};
ToastBannerComponent.defaultProps = {
status: BannerStatus.success,
message: '',
actionText: undefined,
action: () => { },
};
const styles = StyleSheet.create({
iconView: {
alignItems: 'center',
justifyContent: 'center',
width: 30
height: 30,
alignContent: 'center',
alignSelf: 'center'
},
message: {
flex: 1,
marginHorizontal: 20,
alignContent: 'center',
alignSelf: 'center'
},
actionText: {
marginRight: 20,
color: 'blue',
alignContent: 'center',
alignSelf: 'center'
},
});
export default ToastBannerComponent;
ToastBannerHOC.js
import React from 'react';
import {
View,
SafeAreaView,
StyleSheet,
Animated,
} from 'react-native';
import { BannerStatus, BannerTimeout } from './BannerConstant';
import ToastBannerComponent from './ToastBanner';
/**
* Usage: wrap component you already designed.
* Don't use SafeAreaView in your component, it will added here.
* And you can pass `safeAreaViewProps` with options
* `export default withToastBanner(YourComponent);`
*
* showBanner:
this.props.showSuccessBanner('Well Done!');
this.props.showSuccessBanner('Well Done!', 5 * 1000);
this.props.showInfoBanner('Information For You!');
this.props.showInfoBanner('Information For You!', 5 * 1000);
this.props.showWarningBanner('There is a warning!There is a warning!There is a warning!There is v!There is a warning!', 'Fix Warnings', () => {
console.log('Fixed warnings');
this.props.hideBanner();
});
this.props.showWarningBanner('There is a warning!There is a warning!There is a warning!There is v!There is a warning!', 'Fix Warnings', () => {
console.log('Fixed warnings');
this.props.hideBanner();
}, 10 * 1000);
this.props.showErrorBanner('There is an error!There is an error!There is an error!There is an error!There is an error!', 'Resolve', () => {
console.log('Resolved'); this.props.hideBanner();
});
this.props.showErrorBanner('There is an error!There is an error!There is an error!There is an error!There is an error!', 'Resolve', () => {
console.log('Resolved'); this.props.hideBanner();
}, 10 * 1000);
hideBanner:
`this.props.hideBanner();`
Or:
Use a `timeout` to hide banner automatically.
*/
export default function withToastBanner(WrappedComponent, options) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
makeBannerBackground: true,
marginAnimation: new Animated.Value(0),
message: 'Banner',
status: BannerStatus.info,
actionText: '',
action: undefined,
};
this.displayBanner = false;
this.options = options;
this.bannerHeight = 0;
}
showSuccessBanner = (message, timeout) => {
// If not set timeout, use a default one to auto hide banner
this.showBanner({
status: BannerStatus.success,
message,
actionText: undefined,
action: undefined,
timeout: timeout || BannerTimeout
});
}
showInfoBanner = (message, timeout) => {
// If not set timeout, use a default one to auto hide banner
this.showBanner({
status: BannerStatus.info,
message,
actionText: undefined,
action: undefined,
timeout: timeout || BannerTimeout
});
}
showWarningBanner = (
message,
actionText,
action,
timeout
) => {
// Only if user set timeout, it will auto hide.
// Otherwise pending until user respond to it.
this.showBanner({
status: BannerStatus.warning,
message,
actionText,
action,
timeout
});
}
showErrorBanner = (
message,
actionText,
action,
timeout
) => {
// Only if user set timeout, it will auto hide.
// Otherwise pending until user respond to it.
this.showBanner({
status: BannerStatus.error,
message,
actionText,
action,
timeout
});
}
showBanner = ({
status,
message,
actionText,
action,
timeout
}) => {
console.log(`showBanner ${message}`);
this.displayBanner = true;
this.setState({
status,
message,
actionText,
action,
})
if (timeout && timeout !== 0) {
this.autoHideBanner(timeout);
}
}
autoHideBanner = (timeout) => {
console.log(`autoHideBanner timeout at ${timeout}`);
setTimeout(() => {
this.hideBanner();
}, timeout);
}
hideBanner = () => {
console.log('hideBanner');
if (!this.displayBanner) {
console.log('Already hidden');
return;
}
this.displayBanner = false;
this.moveBanner(500, 0 - this.bannerHeight);
setTimeout(() => {
this.moveBanner(0, 0);
this.setState({
message: '',
makeBannerBackground: true
});
}, 600);
}
// private functions
moveBanner = (duration, marginTop) => {
console.log(`moveBanner ${duration} ${marginTop}`);
Animated.timing(
this.state.marginAnimation, {
useNativeDriver: false,
toValue: marginTop,
duration
}).start();
}
initBanner = () => {
console.log('initBanner');
this.moveBanner(0, 0 - this.bannerHeight);
}
onBannerLayout = (e) => {
const bannerHeight = e.nativeEvent.layout.height;
console.log(`onBannerLayout ${bannerHeight}`);
if (bannerHeight > 0 && this.displayBanner) {
console.log(`onBannerLayout 2 ${bannerHeight}`);
this.bannerHeight = bannerHeight;
this.initBanner();
this.setState({
makeBannerBackground: false
});
setTimeout(() => {
this.moveBanner(500, 0)
}, 1);
}
}
render() {
let safeAreaViewProps = null;
if (this.options) {
safeAreaViewProps = this.props.safeAreaViewProps;
}
return (
<SafeAreaView
style={styles.container}
{...safeAreaViewProps}
>
<Animated.View
style={[{ marginTop: this.state.marginAnimation }]}
>
<View style={{ height: '100%' }}>
{
this.displayBanner && (
<View style={{
position: this.state.makeBannerBackground ? 'absolute' : 'relative',
zIndex: 10,
backgroundColor: 'transparent',
width: '100%',
height: 'auto',
}}>
<View onLayout={this.onBannerLayout}>
<ToastBannerComponent
status={this.state.status}
message={this.state.message}
action={this.state.action}
actionText={this.state.actionText}
/>
</View>
</View>
)
}
<View style={{
zIndex: 11,
backgroundColor: 'white',
height: '100%'
}}>
<WrappedComponent
showBanner={this.showBanner}
showSuccessBanner={this.showSuccessBanner}
showInfoBanner={this.showInfoBanner}
showWarningBanner={this.showWarningBanner}
showErrorBanner={this.showErrorBanner}
hideBanner={this.hideBanner}
{...this.props}
/>
</View>
</View>
</Animated.View>
</SafeAreaView>
);
}
};
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'transparent',
},
});