diff --git a/components/ModalDropdown.js b/components/ModalDropdown.js index ace1472..c6ec1fe 100644 --- a/components/ModalDropdown.js +++ b/components/ModalDropdown.js @@ -4,11 +4,10 @@ 'use strict'; -import React, { - Component, -} from 'react'; +import React, { Component } from 'react'; import { + FlatList, StyleSheet, Dimensions, View, @@ -21,14 +20,13 @@ import { ActivityIndicator, } from 'react-native'; -import ListView from "deprecated-react-native-listview"; import PropTypes from 'prop-types'; const TOUCHABLE_ELEMENTS = [ 'TouchableHighlight', 'TouchableOpacity', 'TouchableWithoutFeedback', - 'TouchableNativeFeedback' + 'TouchableNativeFeedback', ]; export default class ModalDropdown extends Component { @@ -41,14 +39,40 @@ export default class ModalDropdown extends Component { accessible: PropTypes.bool, animated: PropTypes.bool, + noButtonTextUpdate: 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]), + style: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.object, + PropTypes.array, + ]), + textStyle: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.object, + PropTypes.array, + ]), + buttonStyle: 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, @@ -57,7 +81,7 @@ export default class ModalDropdown extends Component { onDropdownWillShow: PropTypes.func, onDropdownWillHide: PropTypes.func, - onSelect: PropTypes.func + onSelect: PropTypes.func, }; static defaultProps = { @@ -68,7 +92,8 @@ export default class ModalDropdown extends Component { options: null, animated: true, showsVerticalScrollIndicator: true, - keyboardShouldPersistTaps: 'never' + keyboardShouldPersistTaps: 'never', + noButtonTextUpdate: false, }; constructor(props) { @@ -84,13 +109,19 @@ export default class ModalDropdown extends Component { loading: !props.options, showDropdown: false, buttonText: props.defaultValue, - selectedIndex: props.defaultIndex + selectedIndex: props.defaultIndex, }; } - componentWillReceiveProps(nextProps) { - let {buttonText, selectedIndex} = this.state; - const {defaultIndex, defaultValue, options} = nextProps; + UNSAFE_componentWillReceiveProps(nextProps) { + let { buttonText, selectedIndex } = this.state; + const { + defaultIndex, + defaultValue, + options, + noButtonTextUpdate, + disabled, + } = nextProps; buttonText = this._nextValue == null ? buttonText : this._nextValue; selectedIndex = this._nextIndex == null ? selectedIndex : this._nextIndex; if (selectedIndex < 0) { @@ -102,11 +133,20 @@ export default class ModalDropdown extends Component { this._nextValue = null; this._nextIndex = null; - this.setState({ - loading: !options, - buttonText, - selectedIndex - }); + if (noButtonTextUpdate) { + this.setState({ + disabled, + loading: !options, + selectedIndex, + }); + } else { + this.setState({ + disabled, + loading: !options, + buttonText, + selectedIndex, + }); + } } render() { @@ -121,7 +161,7 @@ export default class ModalDropdown extends Component { _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}; + this._buttonFrame = { x: px, y: py, w: width, h: height }; callback && callback(); }); } @@ -130,19 +170,24 @@ export default class ModalDropdown extends Component { show() { this._updatePosition(() => { this.setState({ - showDropdown: true + showDropdown: true, }); }); } hide() { this.setState({ - showDropdown: false + showDropdown: false, }); } select(idx) { - const {defaultValue, options, defaultIndex, renderButtonText} = this.props; + const { + defaultValue, + options, + defaultIndex, + renderButtonText, + } = this.props; let value = defaultValue; if (idx == null || !options || idx >= options.length) { @@ -150,7 +195,9 @@ export default class ModalDropdown extends Component { } if (idx >= 0) { - value = renderButtonText ? renderButtonText(options[idx]) : options[idx].toString(); + value = renderButtonText + ? renderButtonText(options[idx]) + : options[idx].toString(); } this._nextValue = value; @@ -158,60 +205,69 @@ export default class ModalDropdown extends Component { this.setState({ buttonText: value, - selectedIndex: idx + selectedIndex: idx, }); } _renderButton() { - const {disabled, accessible, children, textStyle} = this.props; - const {buttonText} = this.state; + const { + disabled, + accessible, + children, + textStyle, + buttonStyle, + } = this.props; + const { buttonText } = this.state; return ( - this._button = button} - disabled={disabled} - accessible={accessible} - onPress={this._onButtonPress} + (this._button = button)} + disabled={disabled} + accessible={accessible} + onPress={this._onButtonPress} > - { - children || - ( - - - {buttonText} - - - ) - } + {children || ( + + + {buttonText} + + + )} ); } _onButtonPress = () => { - const {onDropdownWillShow} = this.props; - if (!onDropdownWillShow || - onDropdownWillShow() !== false) { + const { onDropdownWillShow } = this.props; + if (!onDropdownWillShow || onDropdownWillShow() !== false) { this.show(); } }; _renderModal() { - const {animated, accessible, dropdownStyle} = this.props; - const {showDropdown, loading} = this.state; + 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 ( - - @@ -225,30 +281,37 @@ export default class ModalDropdown extends Component { } _calcPosition() { - const {dropdownStyle, style, adjustFrame} = this.props; + 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) || + const dropdownHeight = + (dropdownStyle && StyleSheet.flatten(dropdownStyle).height) || StyleSheet.flatten(styles.dropdown).height; - const bottomSpace = windowHeight - this._buttonFrame.y - this._buttonFrame.h; + const bottomSpace = + windowHeight - this._buttonFrame.y - this._buttonFrame.h; const rightSpace = windowWidth - this._buttonFrame.x; - const showInBottom = bottomSpace >= dropdownHeight || bottomSpace >= this._buttonFrame.y; + 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), + 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; + const dropdownWidth = + (dropdownStyle && StyleSheet.flatten(dropdownStyle).width) || + (style && StyleSheet.flatten(style).width) || + -1; if (dropdownWidth !== -1) { positionStyle.width = dropdownWidth; } @@ -259,90 +322,90 @@ export default class ModalDropdown extends Component { } _onRequestClose = () => { - const {onDropdownWillHide} = this.props; - if (!onDropdownWillHide || - onDropdownWillHide() !== false) { + const { onDropdownWillHide } = this.props; + if (!onDropdownWillHide || onDropdownWillHide() !== false) { this.hide(); } }; _onModalPress = () => { - const {onDropdownWillHide} = this.props; - if (!onDropdownWillHide || - onDropdownWillHide() !== false) { + const { onDropdownWillHide } = this.props; + if (!onDropdownWillHide || onDropdownWillHide() !== false) { this.hide(); } }; _renderLoading() { - return ( - - ); + return ; } _renderDropdown() { - const {scrollEnabled, renderSeparator, showsVerticalScrollIndicator, keyboardShouldPersistTaps} = this.props; + const { + scrollEnabled, + renderSeparator, + showsVerticalScrollIndicator, + keyboardShouldPersistTaps, + options, + } = this.props; return ( - ); } - get _dataSource() { - const {options} = this.props; - const ds = new ListView.DataSource({ - rowHasChanged: (r1, r2) => r1 !== r2 - }); - return ds.cloneWithRows(options); - } + _keyExtractor = (item, index) => `key-${index}`; - _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 ? - ( { + const { + renderRow, + dropdownTextStyle, + dropdownTextProps, + dropdownTextHighlightStyle, + accessible, + } = this.props; + const { selectedIndex } = this.state; + const key = `row_${index}`; + const highlighted = index == selectedIndex; + const row = !renderRow ? ( + - {rowData} - ) : - renderRow(rowData, rowID, highlighted); + {item} + + ) : ( + renderRow(item, index, highlighted) + ); const preservedProps = { key, accessible, - onPress: () => this._onRowPress(rowData, sectionID, rowID, highlightRow), + onPress: () => this._onRowPress(item, index, separators), }; - if (TOUCHABLE_ELEMENTS.find(name => name == row.type.displayName)) { - const props = {...row.props}; + 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; + const { children } = row.props; switch (row.type.displayName) { case 'TouchableHighlight': { - return ( - - {children} - - ); + return {children}; } case 'TouchableOpacity': { - return ( - - {children} - - ); + return {children}; } case 'TouchableWithoutFeedback': { return ( @@ -362,51 +425,51 @@ export default class ModalDropdown extends Component { break; } } - return ( - - {row} - - ); + return {row}; }; - _onRowPress(rowData, sectionID, rowID, highlightRow) { - const {onSelect, renderButtonText, onDropdownWillHide} = this.props; + _onRowPress(rowData, rowID, highlightRow) { + const { onSelect, renderButtonText, onDropdownWillHide } = this.props; if (!onSelect || onSelect(rowID, rowData) !== false) { - highlightRow(sectionID, rowID); - const value = renderButtonText && renderButtonText(rowData) || rowData.toString(); + highlightRow.highlight(rowID); + const value = + (renderButtonText && renderButtonText(rowData)) || rowData.toString(); this._nextValue = value; this._nextIndex = rowID; - this.setState({ - buttonText: value, - selectedIndex: rowID - }); + + if (this.props.noButtonTextUpdate) { + this.setState({ + selectedIndex: rowID, + }); + } else { + this.setState({ + buttonText: rowData.toString(), + selectedIndex: rowID, + }); + } } if (!onDropdownWillHide || onDropdownWillHide() !== false) { this.setState({ - showDropdown: false + showDropdown: false, }); } } - _renderSeparator = (sectionID, rowID, adjacentRowHighlighted) => { - const key = `spr_${rowID}`; - return ( - - ); + _renderSeparator = ({ leadingItem = '' }) => { + const key = `spr_${leadingItem}`; + return ; }; } const styles = StyleSheet.create({ button: { - justifyContent: 'center' + justifyContent: 'center', }, buttonText: { - fontSize: 12 + fontSize: 12, }, modal: { - flexGrow: 1 + flexGrow: 1, }, dropdown: { position: 'absolute', @@ -415,10 +478,10 @@ const styles = StyleSheet.create({ borderColor: 'lightgray', borderRadius: 2, backgroundColor: 'white', - justifyContent: 'center' + justifyContent: 'center', }, loading: { - alignSelf: 'center' + alignSelf: 'center', }, list: { //flexGrow: 1, @@ -429,13 +492,13 @@ const styles = StyleSheet.create({ fontSize: 11, color: 'gray', backgroundColor: 'white', - textAlignVertical: 'center' + textAlignVertical: 'center', }, highlightedRowText: { - color: 'black' + color: 'black', }, separator: { height: StyleSheet.hairlineWidth, - backgroundColor: 'lightgray' - } + backgroundColor: 'lightgray', + }, }); diff --git a/example/ModalDropdown.js b/example/ModalDropdownListView.js similarity index 99% rename from example/ModalDropdown.js rename to example/ModalDropdownListView.js index 4dc5342..15a6a65 100644 --- a/example/ModalDropdown.js +++ b/example/ModalDropdownListView.js @@ -88,7 +88,7 @@ export default class ModalDropdown extends Component { }; } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { let {buttonText, selectedIndex} = this.state; const {defaultIndex, defaultValue, options} = nextProps; buttonText = this._nextValue == null ? buttonText : this._nextValue; diff --git a/example/index.js b/example/index.js index db616bb..42d70c1 100644 --- a/example/index.js +++ b/example/index.js @@ -4,7 +4,7 @@ * @flow */ -import React, {Component} from 'react'; +import React, { Component } from 'react'; import { AppRegistry, StyleSheet, @@ -17,19 +17,29 @@ import { } from 'react-native'; // import ModalDropdown from 'react-native-modal-dropdown'; -import ModalDropdown from './ModalDropdown'; +import ModalDropdown from '../components/ModalDropdown'; -const DEMO_OPTIONS_1 = ['option 1', 'option 2', 'option 3', 'option 4', 'option 5', 'option 6', 'option 7', 'option 8', 'option 9']; +const DEMO_OPTIONS_1 = [ + 'option 1', + 'option 2', + 'option 3', + 'option 4', + 'option 5', + 'option 6', + 'option 7', + 'option 8', + 'option 9', +]; const DEMO_OPTIONS_2 = [ - {"name": "Rex", "age": 30}, - {"name": "Mary", "age": 25}, - {"name": "John", "age": 41}, - {"name": "Jim", "age": 22}, - {"name": "Susan", "age": 52}, - {"name": "Brent", "age": 33}, - {"name": "Alex", "age": 16}, - {"name": "Ian", "age": 20}, - {"name": "Phil", "age": 24}, + { name: 'Rex', age: 30 }, + { name: 'Mary', age: 25 }, + { name: 'John', age: 41 }, + { name: 'Jim', age: 22 }, + { name: 'Susan', age: 52 }, + { name: 'Brent', age: 33 }, + { name: 'Alex', age: 16 }, + { name: 'Ian', age: 20 }, + { name: 'Phil', age: 24 }, ]; class Demo extends Component { @@ -44,94 +54,106 @@ class Demo extends Component { } render() { - const dropdown_6_icon = this.state.dropdown_6_icon_heart ? require('./images/heart.png') : require('./images/flower.png'); + const dropdown_6_icon = this.state.dropdown_6_icon_heart + ? require('./images/heart.png') + : require('./images/flower.png'); return ( - - this._dropdown_6_onSelect(idx, value)}> - + + this._dropdown_6_onSelect(idx, value)} + > + - this._dropdown_2_renderButtonText(rowData)} - renderRow={this._dropdown_2_renderRow.bind(this)} - renderSeparator={(sectionID, rowID, adjacentRowHighlighted) => this._dropdown_2_renderSeparator(sectionID, rowID, adjacentRowHighlighted)} + + this._dropdown_2_renderButtonText(rowData) + } + renderRow={this._dropdown_2_renderRow.bind(this)} + renderSeparator={(sectionID, rowID, adjacentRowHighlighted) => + this._dropdown_2_renderSeparator( + sectionID, + rowID, + adjacentRowHighlighted + ) + } /> - { - this.refs.dropdown_2.select(0); - }}> - - select Rex - + { + this.refs.dropdown_2.select(0); + }} + > + select Rex - this._scrollView = el} - style={styles.scrollView} - contentContainerStyle={styles.contentContainer} - showsVerticalScrollIndicator={true} - scrollEventThrottle={1}> - - {'Scroll view example.'} - - this._dropdown_3 = el} - style={styles.dropdown_3} - options={DEMO_OPTIONS_1} - adjustFrame={style => this._dropdown_3_adjustFrame(style)} - dropdownTextStyle={styles.dropdown_3_dropdownTextStyle} - dropdownTextHighlightStyle={styles.dropdown_3_dropdownTextHighlightStyle} + (this._scrollView = el)} + style={styles.scrollView} + contentContainerStyle={styles.contentContainer} + showsVerticalScrollIndicator={true} + scrollEventThrottle={1} + > + {'Scroll view example.'} + (this._dropdown_3 = el)} + style={styles.dropdown_3} + options={DEMO_OPTIONS_1} + adjustFrame={(style) => this._dropdown_3_adjustFrame(style)} + dropdownTextStyle={styles.dropdown_3_dropdownTextStyle} + dropdownTextHighlightStyle={ + styles.dropdown_3_dropdownTextHighlightStyle + } /> - - this._dropdown_4_onSelect(idx, value)} + + this._dropdown_4_onSelect(idx, value)} /> - + - - {'Show dropdown'} - + {'Show dropdown'} this._dropdown_5_select(2)}> - - {'Select the 3rd option'} - + {'Select the 3rd option'} this._dropdown_5_select(-1)}> - - {'Clear selection'} - + {'Clear selection'} - this._dropdown_5 = el} - style={styles.dropdown_5} - options={['Select me to hide', `I can't be selected`, 'I can only be selected outside']} - defaultValue='Try the Show button above' - onDropdownWillShow={this._dropdown_5_willShow.bind(this)} - onDropdownWillHide={this._dropdown_5_willHide.bind(this)} - onSelect={this._dropdown_5_onSelect.bind(this)} + (this._dropdown_5 = el)} + style={styles.dropdown_5} + options={[ + 'Select me to hide', + `I can't be selected`, + 'I can only be selected outside', + ]} + defaultValue='Try the Show button above' + onDropdownWillShow={this._dropdown_5_willShow.bind(this)} + onDropdownWillHide={this._dropdown_5_willHide.bind(this)} + onSelect={this._dropdown_5_onSelect.bind(this)} /> @@ -140,21 +162,30 @@ class Demo extends Component { } _dropdown_2_renderButtonText(rowData) { - const {name, age} = rowData; + const { name, age } = rowData; return `${name} - ${age}`; } _dropdown_2_renderRow(rowData, rowID, highlighted) { - let icon = highlighted ? require('./images/heart.png') : require('./images/flower.png'); + let icon = highlighted + ? require('./images/heart.png') + : require('./images/flower.png'); let evenRow = rowID % 2; return ( - - - + + + {`${rowData.name} (${rowData.age})`} @@ -165,23 +196,27 @@ class Demo extends Component { _dropdown_2_renderSeparator(sectionID, rowID, adjacentRowHighlighted) { if (rowID == DEMO_OPTIONS_1.length - 1) return; let key = `spr_${rowID}`; - return (); + return ; } _dropdown_3_adjustFrame(style) { - console.log(`frameStyle={width:${style.width}, height:${style.height}, top:${style.top}, left:${style.left}, right:${style.right}}`); + console.log( + `frameStyle={width:${style.width}, height:${style.height}, top:${style.top}, left:${style.left}, right:${style.right}}` + ); style.top -= 15; style.left += 150; return style; } _dropdown_4_willShow() { - setTimeout(() => this.setState({ - dropdown_4_options: DEMO_OPTIONS_1, - dropdown_4_defaultValue: 'loaded', - }), 2000); + setTimeout( + () => + this.setState({ + dropdown_4_options: DEMO_OPTIONS_1, + dropdown_4_defaultValue: 'loaded', + }), + 2000 + ); } _dropdown_4_willHide() { @@ -225,7 +260,7 @@ class Demo extends Component { _dropdown_6_onSelect(idx, value) { this.setState({ dropdown_6_icon_heart: !this.state.dropdown_6_icon_heart, - }) + }); } } @@ -313,11 +348,11 @@ const styles = StyleSheet.create({ }, dropdown_3_dropdownTextStyle: { backgroundColor: '#000', - color: '#fff' + color: '#fff', }, dropdown_3_dropdownTextHighlightStyle: { backgroundColor: '#fff', - color: '#000' + color: '#000', }, dropdown_4: { margin: 8, diff --git a/package.json b/package.json index db7820b..52f7852 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,7 @@ "url": "https://github.com/sohobloo/react-native-modal-dropdown.git" }, "dependencies": { - "prop-types": "^15.6.0", - "deprecated-react-native-listview": "0.0.5" + "prop-types": "^15.6.0" }, "scripts": { "test": "echo \"no test specified\" && exit 0"