Skip to content

Commit

Permalink
Added Dropdown component
Browse files Browse the repository at this point in the history
  • Loading branch information
n4kz committed May 26, 2017
1 parent 11cce29 commit e431ac3
Show file tree
Hide file tree
Showing 4 changed files with 281 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
'parser': 'babel-eslint',

'globals': {
'setTimeout': true,
},

'plugins': [
'react',
],
Expand Down
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Dropdown from './src/components/dropdown';

export { Dropdown };
206 changes: 206 additions & 0 deletions src/components/dropdown/index.js
Original file line number Diff line number Diff line change
@@ -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 (
<View style={styles.accessory}>
<View style={styles.triangleContainer}>
<View style={[styles.triangle, triangleStyle]} />
</View>
</View>
);
}

renderItems() {
let { data } = this.props;

return data
.map(({ value }, index) => (
<Button
color='white'
style={styles.item}
rippleContainerBorderRadius={0}
shadeBorderRadius={0}
onPress={() => this.onSelect(index)}
key={index}
>
<Text style={styles.text}>{value}</Text>
</Button>
));
}

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 (
<View onLayout={() => undefined} ref={this.updateContainerRef}>
<TouchableWithoutFeedback onPress={this.onPress}>
<View pointerEvents='box-only'>
<TextField
{...props}

value={value}
editable={false}
renderAccessory={this.renderAccessory}
/>
</View>
</TouchableWithoutFeedback>

<Modal visible={modal} transparent={true} onRequestClose={this.onClose}>
<TouchableWithoutFeedback onPress={this.onClose}>
<View style={modalStyle}>
<View style={[styles.picker, pickerStyle]}>
<ScrollView
ref={this.updateScrollRef}
style={styles.scroll}
contentContainerStyle={styles.scrollContainer}
>
{this.renderItems()}
</ScrollView>
</View>
</View>
</TouchableWithoutFeedback>
</Modal>
</View>
);
}
}
68 changes: 68 additions & 0 deletions src/components/dropdown/styles.js
Original file line number Diff line number Diff line change
@@ -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,
},
});

0 comments on commit e431ac3

Please sign in to comment.