react-native-actionsheet-ex
Version:
Cross platform ActionSheet. This component implements a custom ActionSheet and provides the same way to drawing it on the defferent platforms(iOS and Android). Actually, In order to keep the best effect, it still uses the ActionSheetIOS on iOS. Based on
234 lines (210 loc) • 6.24 kB
JavaScript
import React from 'react'
import { Text, View, Dimensions, Modal, TouchableHighlight, TouchableNativeFeedback, Animated, ScrollView, Easing } from 'react-native'
import * as utils from './utils'
import styles2 from './styles'
const WARN_COLOR = '#FF3B30'
const MAX_HEIGHT = Dimensions.get('window').height * 0.7
class ActionSheet extends React.Component {
static defaultProps = {
tintColor: '#007AFF',
buttonUnderlayColor: '#F4F4F4',
useRipple: false,
onPress: () => {},
styles: {}
}
constructor (props) {
super(props)
this.scrollEnabled = false
this.translateY = this._calculateHeight(props)
this.state = {
visible: false,
sheetAnim: new Animated.Value(this.translateY)
}
}
componentDidUpdate () {
this.translateY = this._calculateHeight(this.props)
}
get styles () {
const { styles } = this.props
const obj = {}
Object.keys(styles2).forEach((key) => {
let arr = Object.assign({}, styles2[key])
if (styles[key]) {
arr = Object.assign({}, arr, styles[key])
}
obj[key] = [arr]
})
return obj
}
show = () => {
this.setState({ visible: true }, () => {
this._showSheet()
})
}
hide = (index) => {
this._hideSheet(() => {
this.setState({ visible: false }, () => {
this.props.onPress(index)
})
})
}
handleCancel = () => {
const { cancelButtonIndex } = this.props
// 保持和 ActionSheetIOS 一致,
// 未设置 cancelButtonIndex 时,点击背景不隐藏 ActionSheet
if (utils.isset(cancelButtonIndex)) {
this.hide(cancelButtonIndex)
}
}
_showSheet = () => {
Animated.timing(this.state.sheetAnim, {
toValue: 0,
duration: 250,
easing: Easing.out(Easing.ease),
useNativeDriver: false
}).start()
}
_hideSheet (callback) {
Animated.timing(this.state.sheetAnim, {
toValue: this.translateY,
duration: 200,
useNativeDriver: false
}).start(callback)
}
/**
* elements: titleBox, messageBox, buttonBox, cancelButtonBox
* box size: height, marginTop, marginBottom
*/
_calculateHeight (props) {
const styles = this.styles
const getHeight = (name) => {
const style = styles[name][styles[name].length - 1]
let h = 0
;['height', 'marginTop', 'marginBottom'].forEach((attrName) => {
if (typeof style[attrName] !== 'undefined') {
h += style[attrName]
}
})
return h
}
let height = 0
if (props.title) height += getHeight('titleBox')
if (props.message) height += getHeight('messageBox')
if (utils.isset(props.cancelButtonIndex)) {
height += getHeight('cancelButtonBox')
height += (props.options.length - 1) * getHeight('buttonBox')
} else {
height += props.options.length * getHeight('buttonBox')
}
if (height > MAX_HEIGHT) {
this.scrollEnabled = true
height = MAX_HEIGHT
} else {
this.scrollEnabled = false
}
return height
}
_renderTitle () {
const { title } = this.props
const styles = this.styles
if (!title) return null
return (
<View style={styles.titleBox}>
{
React.isValidElement(title) ? title : (<Text style={styles.titleText}>{title}</Text>)
}
</View>
)
}
_renderMessage () {
const { message } = this.props
const styles = this.styles
if (!message) return null
return (
<View style={styles.messageBox}>
{
React.isValidElement(message) ? message : (<Text style={styles.messageText}>{message}</Text>)
}
</View>
)
}
_renderCancelButton () {
const { options, cancelButtonIndex } = this.props
if (!utils.isset(cancelButtonIndex)) return null
return this._createButton(options[cancelButtonIndex], cancelButtonIndex)
}
_createButton (title, index) {
const styles = this.styles
const { buttonUnderlayColor, cancelButtonIndex, destructiveButtonIndex, tintColor, useRipple } = this.props
const fontColor = destructiveButtonIndex === index ? WARN_COLOR : tintColor
const buttonBoxStyle = cancelButtonIndex === index ? styles.cancelButtonBox : styles.buttonBox
if (useRipple) {
return (
<TouchableNativeFeedback
key={index}
onPress={() => this.hide(index)}
background={TouchableNativeFeedback.SelectableBackground()}
>
<View
style={buttonBoxStyle}
>
{
React.isValidElement(title) ? title : (<Text style={[styles.buttonText, { color: fontColor }]}>{title}</Text>)
}
</View>
</TouchableNativeFeedback>
)
} else {
return (
<TouchableHighlight
key={index}
activeOpacity={1}
underlayColor={buttonUnderlayColor}
style={buttonBoxStyle}
onPress={() => this.hide(index)}
>
{
React.isValidElement(title) ? title : (<Text style={[styles.buttonText, { color: fontColor }]}>{title}</Text>)
}
</TouchableHighlight>
)
}
}
_renderOptions () {
const { cancelButtonIndex } = this.props
return this.props.options.map((title, index) => {
return cancelButtonIndex === index ? null : this._createButton(title, index)
})
}
render () {
const styles = this.styles
const { visible, sheetAnim } = this.state
return (
<Modal
visible={visible}
animationType='none'
transparent
onRequestClose={this.handleCancel}
>
<View style={[styles.wrapper]}>
<Text
style={[styles.overlay]}
onPress={this.handleCancel}
/>
<Animated.View
style={[
styles.body,
{ height: this.translateY, transform: [{ translateY: sheetAnim }] }
]}
>
{this._renderTitle()}
{this._renderMessage()}
<ScrollView scrollEnabled={this.scrollEnabled}>{this._renderOptions()}</ScrollView>
{this._renderCancelButton()}
</Animated.View>
</View>
</Modal>
)
}
}
export default ActionSheet