UNPKG

hcmobile-sdk

Version:

mobile-sdk

440 lines (395 loc) 11.9 kB
/** * github https://github.com/sohobloo/react-native-modal-dropdown */ 'use strict'; import React, { Component, } from 'react'; import { StyleSheet, Dimensions, View, Text, ListView, TouchableWithoutFeedback, TouchableNativeFeedback, TouchableOpacity, TouchableHighlight, Modal, ActivityIndicator, } from 'react-native'; import PropTypes from 'prop-types'; const TOUCHABLE_ELEMENTS = [ 'TouchableHighlight', 'TouchableOpacity', 'TouchableWithoutFeedback', 'TouchableNativeFeedback' ]; export default class ModalDropdown extends Component { static propTypes = { disabled: PropTypes.bool, scrollEnabled: PropTypes.bool, defaultIndex: PropTypes.number, defaultValue: PropTypes.string, options: PropTypes.array, accessible: PropTypes.bool, animated: PropTypes.bool, showsVerticalScrollIndicator: PropTypes.bool, keyboardShouldPersistTaps: PropTypes.string, style: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]), textStyle: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]), dropdownStyle: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]), dropdownTextStyle: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]), dropdownTextHighlightStyle: PropTypes.oneOfType([PropTypes.number, PropTypes.object, PropTypes.array]), adjustFrame: PropTypes.func, renderRow: PropTypes.func, renderSeparator: PropTypes.func, renderButtonText: PropTypes.func, onDropdownWillShow: PropTypes.func, onDropdownWillHide: PropTypes.func, onSelect: PropTypes.func }; static defaultProps = { disabled: false, scrollEnabled: true, defaultIndex: -1, defaultValue: 'Please select...', options: null, animated: true, showsVerticalScrollIndicator: true, keyboardShouldPersistTaps: 'never' }; constructor(props) { super(props); this._button = null; this._buttonFrame = null; this._nextValue = null; this._nextIndex = null; this.state = { accessible: !!props.accessible, loading: !props.options, showDropdown: false, buttonText: props.defaultValue, selectedIndex: props.defaultIndex }; } componentWillReceiveProps(nextProps) { let {buttonText, selectedIndex} = this.state; const {defaultIndex, defaultValue, options} = nextProps; buttonText = this._nextValue == null ? buttonText : this._nextValue.toString(); selectedIndex = this._nextIndex == null ? selectedIndex : this._nextIndex; if (selectedIndex < 0) { selectedIndex = defaultIndex; if (selectedIndex < 0) { buttonText = defaultValue; } } this._nextValue = null; this._nextIndex = null; this.setState({ loading: !options, buttonText, selectedIndex }); } render() { return ( <View {...this.props}> {this._renderButton()} {this._renderModal()} </View> ); } _updatePosition(callback) { if (this._button && this._button.measure) { this._button.measure((fx, fy, width, height, px, py) => { this._buttonFrame = {x: px, y: py, w: width, h: height}; callback && callback(); }); } } show() { this._updatePosition(() => { this.setState({ showDropdown: true }); }); } hide() { this.setState({ showDropdown: false }); } select(idx) { const {defaultValue, options, defaultIndex, renderButtonText} = this.props; let value = defaultValue; if (idx == null || !options || idx >= options.length) { idx = defaultIndex; } if (idx >= 0) { value = renderButtonText ? renderButtonText(options[idx]) : options[idx].toString(); } this._nextValue = value; this._nextIndex = idx; this.setState({ buttonText: value, selectedIndex: idx }); } _renderButton() { const {disabled, accessible, children, textStyle} = this.props; const {buttonText} = this.state; return ( <TouchableOpacity ref={button => this._button = button} disabled={disabled} accessible={accessible} onPress={this._onButtonPress} > { children || ( <View style={styles.button}> <Text style={[styles.buttonText, textStyle]} numberOfLines={1} > {buttonText} </Text> </View> ) } </TouchableOpacity> ); } _onButtonPress = () => { const {onDropdownWillShow} = this.props; if (!onDropdownWillShow || onDropdownWillShow() !== false) { this.show(); } }; _renderModal() { const {animated, accessible, dropdownStyle} = this.props; const {showDropdown, loading} = this.state; if (showDropdown && this._buttonFrame) { const frameStyle = this._calcPosition(); const animationType = animated ? 'fade' : 'none'; return ( <Modal animationType={animationType} visible={true} transparent={true} onRequestClose={this._onRequestClose} supportedOrientations={['portrait', 'portrait-upside-down', 'landscape', 'landscape-left', 'landscape-right']} > <TouchableWithoutFeedback accessible={accessible} disabled={!showDropdown} onPress={this._onModalPress} > <View style={styles.modal}> <View style={[styles.dropdown, dropdownStyle, frameStyle]}> {loading ? this._renderLoading() : this._renderDropdown()} </View> </View> </TouchableWithoutFeedback> </Modal> ); } } _calcPosition() { const {dropdownStyle, style, adjustFrame} = this.props; const dimensions = Dimensions.get('window'); const windowWidth = dimensions.width; const windowHeight = dimensions.height; const dropdownHeight = (dropdownStyle && StyleSheet.flatten(dropdownStyle).height) || StyleSheet.flatten(styles.dropdown).height; const bottomSpace = windowHeight - this._buttonFrame.y - this._buttonFrame.h; const rightSpace = windowWidth - this._buttonFrame.x; const showInBottom = bottomSpace >= dropdownHeight || bottomSpace >= this._buttonFrame.y; const showInLeft = rightSpace >= this._buttonFrame.x; const positionStyle = { height: dropdownHeight, top: showInBottom ? this._buttonFrame.y + this._buttonFrame.h : Math.max(0, this._buttonFrame.y - dropdownHeight), }; if (showInLeft) { positionStyle.left = this._buttonFrame.x; } else { const dropdownWidth = (dropdownStyle && StyleSheet.flatten(dropdownStyle).width) || (style && StyleSheet.flatten(style).width) || -1; if (dropdownWidth !== -1) { positionStyle.width = dropdownWidth; } positionStyle.right = rightSpace - this._buttonFrame.w; } return adjustFrame ? adjustFrame(positionStyle) : positionStyle; } _onRequestClose = () => { const {onDropdownWillHide} = this.props; if (!onDropdownWillHide || onDropdownWillHide() !== false) { this.hide(); } }; _onModalPress = () => { const {onDropdownWillHide} = this.props; if (!onDropdownWillHide || onDropdownWillHide() !== false) { this.hide(); } }; _renderLoading() { return ( <ActivityIndicator size='small'/> ); } _renderDropdown() { const {scrollEnabled, renderSeparator, showsVerticalScrollIndicator, keyboardShouldPersistTaps} = this.props; return ( <ListView scrollEnabled={scrollEnabled} style={styles.list} dataSource={this._dataSource} renderRow={this._renderRow} renderSeparator={renderSeparator || this._renderSeparator} automaticallyAdjustContentInsets={false} showsVerticalScrollIndicator={showsVerticalScrollIndicator} keyboardShouldPersistTaps={keyboardShouldPersistTaps} /> ); } get _dataSource() { const {options} = this.props; const ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 }); return ds.cloneWithRows(options); } _renderRow = (rowData, sectionID, rowID, highlightRow) => { const {renderRow, dropdownTextStyle, dropdownTextHighlightStyle, accessible} = this.props; const {selectedIndex} = this.state; const key = `row_${rowID}`; const highlighted = rowID == selectedIndex; const row = !renderRow ? (<Text style={[ styles.rowText, dropdownTextStyle, highlighted && styles.highlightedRowText, highlighted && dropdownTextHighlightStyle ]} > {rowData} </Text>) : renderRow(rowData, rowID, highlighted); const preservedProps = { key, accessible, onPress: () => this._onRowPress(rowData, sectionID, rowID, highlightRow), }; if (TOUCHABLE_ELEMENTS.find(name => name == row.type.displayName)) { const props = {...row.props}; props.key = preservedProps.key; props.onPress = preservedProps.onPress; const {children} = row.props; switch (row.type.displayName) { case 'TouchableHighlight': { return ( <TouchableHighlight {...props}> {children} </TouchableHighlight> ); } case 'TouchableOpacity': { return ( <TouchableOpacity {...props}> {children} </TouchableOpacity> ); } case 'TouchableWithoutFeedback': { return ( <TouchableWithoutFeedback {...props}> {children} </TouchableWithoutFeedback> ); } case 'TouchableNativeFeedback': { return ( <TouchableNativeFeedback {...props}> {children} </TouchableNativeFeedback> ); } default: break; } } return ( <TouchableHighlight {...preservedProps}> {row} </TouchableHighlight> ); }; _onRowPress(rowData, sectionID, rowID, highlightRow) { const {onSelect, renderButtonText, onDropdownWillHide} = this.props; if (!onSelect || onSelect(rowID, rowData) !== false) { highlightRow(sectionID, rowID); this._nextValue = rowData; this._nextIndex = rowID; this.setState({ buttonText: renderButtonText && renderButtonText(rowData) || rowData.toString(), selectedIndex: rowID }); } if (!onDropdownWillHide || onDropdownWillHide() !== false) { this.setState({ showDropdown: false }); } } _renderSeparator = (sectionID, rowID, adjacentRowHighlighted) => { const key = `spr_${rowID}`; return ( <View style={styles.separator} key={key} /> ); }; } const styles = StyleSheet.create({ button: { justifyContent: 'center' }, buttonText: { fontSize: 12 }, modal: { flexGrow: 1 }, dropdown: { position: 'absolute', height: (33 + StyleSheet.hairlineWidth) * 5, borderWidth: StyleSheet.hairlineWidth, borderColor: 'lightgray', borderRadius: 2, backgroundColor: 'white', justifyContent: 'center' }, loading: { alignSelf: 'center' }, list: { //flexGrow: 1, }, rowText: { paddingHorizontal: 6, paddingVertical: 10, fontSize: 11, color: 'gray', backgroundColor: 'white', textAlignVertical: 'center' }, highlightedRowText: { color: 'black' }, separator: { height: StyleSheet.hairlineWidth, backgroundColor: 'lightgray' } });