react-native-actionsheet
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.
211 lines (188 loc) • 5.55 kB
JavaScript
import React from 'react'
import { Text, View, Dimensions, Modal, TouchableHighlight, 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',
onPress: () => {},
styles: {}
}
constructor (props) {
super(props)
this.scrollEnabled = false
this.translateY = this._calculateHeight(props)
this.state = {
visible: false,
sheetAnim: new Animated.Value(this.translateY)
}
}
componentWillReceiveProps (nextProps) {
this.translateY = this._calculateHeight(nextProps)
}
get styles () {
const { styles } = this.props
const obj = {}
Object.keys(styles2).forEach((key) => {
const arr = [styles2[key]]
if (styles[key]) {
arr.push(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)
})
})
}
_cancel = () => {
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)
}).start()
}
_hideSheet (callback) {
Animated.timing(this.state.sheetAnim, {
toValue: this.translateY,
duration: 200
}).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 } = this.props
const fontColor = destructiveButtonIndex === index ? WARN_COLOR : tintColor
const buttonBoxStyle = cancelButtonIndex === index ? styles.cancelButtonBox : styles.buttonBox
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._cancel}
>
<View style={[styles.wrapper]}>
<Text
style={[styles.overlay]}
onPress={this._cancel}
/>
<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