diff --git a/.eslintrc b/.eslintrc index c5360064..ff27af56 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,10 @@ { 'parser': 'babel-eslint', + 'globals': { + 'setTimeout': true, + }, + 'plugins': [ 'react', ], diff --git a/index.js b/index.js new file mode 100644 index 00000000..d3a0e372 --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +import Dropdown from './src/components/dropdown'; + +export { Dropdown }; diff --git a/src/components/dropdown/index.js b/src/components/dropdown/index.js new file mode 100644 index 00000000..535aa7f7 --- /dev/null +++ b/src/components/dropdown/index.js @@ -0,0 +1,206 @@ +import PropTypes from 'prop-types'; +import React, { PureComponent } from 'react'; +import { + Text, + View, + ScrollView, + Modal, + TouchableWithoutFeedback, + Dimensions, + Platform, +} from 'react-native'; +import { Button } from 'react-native-material-buttons'; +import { TextField } from 'react-native-material-textfield'; + +import styles from './styles'; + +export default class Dropdown extends PureComponent { + static defaultProps = { + overlayColor: 'transparent', + }; + + static propTypes = { + overlayColor: PropTypes.string, + + value: PropTypes.string, + data: PropTypes.arrayOf(PropTypes.shape({ + value: PropTypes.string, + })), + + onChangeText: PropTypes.func, + onFocus: PropTypes.func, + onBlur: PropTypes.func, + }; + + constructor(props) { + super(props); + + this.onPress = this.onPress.bind(this); + this.onClose = this.onClose.bind(this); + this.updateContainerRef = this.updateRef.bind(this, 'container'); + this.updateScrollRef = this.updateRef.bind(this, 'scroll'); + this.renderAccessory = this.renderAccessory.bind(this); + + this.state = { + value: this.props.value, + offset: 0, + modal: false, + }; + } + + isFocused() { + return this.state.modal; + } + + onPress() { + let { value } = this.state; + let { data, onFocus } = this.props; + + let offset = 0; + + if (value) { + let index = data + .indexOf( + data + .filter((item) => value === item.value) + .shift() + ); + + if (~index) { + offset = (index - 1) * 36; + } + } + + this.container.measureInWindow((x, y, width, height) => { + this.setState({ + modal: true, + width: width + 16, + top: Platform.select({ ios: y + 1, android: y }) + 26, + left: x - 8, + offset, + }); + + if ('function' === typeof onFocus) { + onFocus(); + } + }); + } + + onClose() { + let { onBlur } = this.props; + + if ('function' === typeof onBlur) { + onBlur(); + } + + this.setState({ modal: false }); + } + + onSelect(index) { + let { data, onChangeText } = this.props; + let { value } = data[index]; + + if ('function' === typeof onChangeText) { + onChangeText(value); + } + + this.setState({ value }); + + setTimeout(this.onClose, 400); + } + + updateRef(name, ref) { + this[name] = ref; + + /* XXX: Initial position for ScrollView */ + /* FIXME: Android */ + if ('scroll' === name && ref) { + let { offset } = this.state; + + ref.scrollTo({ x: 0, y: offset, animated: false }); + } + } + + renderAccessory() { + let triangleStyle = { + backgroundColor: TextField.defaultProps.baseColor, + }; + + return ( + + + + + + ); + } + + renderItems() { + let { data } = this.props; + + return data + .map(({ value }, index) => ( + + )); + } + + render() { + let { value, left, top, width, modal } = this.state; + let { data, onChangeText, overlayColor, ...props } = this.props; + + let dimensions = Dimensions.get('window'); + + let modalStyle = { + width: dimensions.width, + height: dimensions.height, + backgroundColor: overlayColor, + }; + + let pickerStyle = { + width, + top, + left, + }; + + return ( + undefined} ref={this.updateContainerRef}> + + + + + + + + + + + + {this.renderItems()} + + + + + + + ); + } +} diff --git a/src/components/dropdown/styles.js b/src/components/dropdown/styles.js new file mode 100644 index 00000000..3f7cab11 --- /dev/null +++ b/src/components/dropdown/styles.js @@ -0,0 +1,68 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + accessory: { + width: 24, + height: 24, + justifyContent: 'center', + alignItems: 'center', + }, + + triangle: { + width: 8, + height: 8, + transform: [{ + translateY: -4, + }, { + rotate: '45deg', + }], + }, + + triangleContainer: { + width: 12, + height: 6, + overflow: 'hidden', + alignItems: 'center', + + backgroundColor: 'transparent', /* XXX: Required */ + }, + + picker: { + backgroundColor: 'white', + borderRadius: 2, + minHeight: 36 + 16, + maxHeight: (36 * 5) + 16 - 24, + + position: 'absolute', + + shadowColor: 'rgb(0, 0, 0)', + shadowOpacity: 0.54, + shadowRadius: 2, + shadowOffset: { width: 0, height: 2 }, + elevation: 4, + + transform: [{ + translateY: -36 - 8, + }], + }, + + scroll: { + flex: 1, + borderRadius: 2, + }, + + scrollContainer: { + paddingVertical: 8, + }, + + item: { + height: 36, + paddingHorizontal: 8, + borderRadius: 0, + justifyContent: 'center', + }, + + text: { + fontSize: 16, + }, +});