react-conventions
Version:
An open source set of React components that implement Ambassador's Design and UX patterns.
196 lines (175 loc) • 5.71 kB
JavaScript
import React from 'react'
import classNames from 'classnames/bind'
import style from './style.scss'
import Icon from '../Icon'
class SelectField extends React.Component {
constructor(props) {
super(props)
}
static defaultProps = {
disabled: false,
options: [],
valueProp: '',
displayProp: ''
}
static propTypes = {
/**
* A string to display as the placeholder text.
*/
placeholder: React.PropTypes.string,
/**
* An array of objects which will be used as the options for the select field.
*/
options: React.PropTypes.array.isRequired,
/**
* The value of the option to be selected.
*/
value: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]),
/**
* Which field in the option object will be used as the value of the select field.
*/
valueProp: React.PropTypes.string.isRequired,
/**
* Which field in the option object will be used as the display of the select field.
*/
displayProp: React.PropTypes.string.isRequired,
/**
* Whether the select field is disabled.
*/
disabled: React.PropTypes.bool,
/**
* Optional styles to add to the select field.
*/
optClass: React.PropTypes.string,
/**
* A callback function to be called when an option is selected.
*/
changeCallback: React.PropTypes.func,
/**
* Icon to be displayed on the left
*/
icon: React.PropTypes.string
}
state = {
isOpen: false,
value: this.props.value
}
componentWillMount = () => {
// Select item
if (this.state.value !== '' && this.getIndex(this.state.value, this.props.options) > -1) {
this.selectItem(this.state.value, this.props.options)
}
// No value is passed in
else {
this.setState({selected: '', value: ''})
}
}
componentWillUnmount = () => {
document.removeEventListener('click', this.toggleSelectField)
}
componentWillReceiveProps = (nextProps) => {
if (nextProps.value === this.state.value) {
return
}
let validIndex = this.getIndex(nextProps.value, nextProps.options) > -1
if (nextProps.value === undefined || nextProps.value === null || !validIndex) {
this.setState({selected: '', value: ''})
}
else {
this.selectItem(nextProps.value, nextProps.options)
}
}
toggleSelectField = () => {
this.setState({isOpen: !this.state.isOpen}, () => {
if (this.state.isOpen) {
document.addEventListener('click', this.toggleSelectField)
}
else {
document.removeEventListener('click', this.toggleSelectField)
}
})
}
selectOption = (option, triggerCallback) => {
this.setState({selected: option, value: option[this.props.valueProp]}, () => {
if (triggerCallback && typeof this.props.changeCallback === 'function') {
this.props.changeCallback({
target: {
name: this.props.name,
value: option[this.props.valueProp],
option: option
}
})
}
})
}
selectItem = (value, options) => {
let index = this.getIndex(value, options)
if (index >= 0) {
this.selectOption(options[index], false)
}
}
getIndex = (value, options) => {
let optionIndex = -1
options.map((option, index) => {
if (option[this.props.valueProp] === value) {
optionIndex = index
}
})
return optionIndex
}
getDisplayText = () => {
if (this.state.selected !== '') {
return this.state.selected[this.props.displayProp]
}
else if (typeof this.props.placeholder !== 'undefined') {
return this.props.placeholder
}
else {
return 'Please select an option'
}
}
getDisplayIcon = () => {
if (this.state.selected && this.state.selected.icon) {
return <Icon name={this.state.selected.icon} fill={this.state.selected.iconColor || null} className={style.icon} height='16' width='16' />
}
else if (this.props.icon) {
return <Icon name={this.props.icon} className={style.icon} height='16' width='16' />
}
else {
return null
}
}
render() {
const cx = classNames.bind(style)
const disabledClass = this.props.disabled ? style['selectfield-disabled'] : ''
const activeClass = this.state.isOpen ? style['active'] : ''
const hasIconClass = !!this.getDisplayIcon() ? style['has-icon'] : ''
const selectFieldClass = cx(style['selectfield-component'], activeClass, disabledClass, hasIconClass, this.props.optClass)
const valueProp = this.props.valueProp
const selectedValues = this.state.value
let options = this.props.options.map((option, index) =>
<li key={index} onClick={this.selectOption.bind(null, option, true)}>{option.icon ? <Icon name={option.icon} fill={option.iconColor || null} className={style.icon} height='16' width='16' /> : null}{option[this.props.displayProp]}</li>
)
if (options.length === 0) {
options.push(<li key={0} className={style['not-clickable']}>Nothing to select</li>)
}
let value = ''
if (this.state.selected) {
value = this.state.selected[this.props.valueProp]
}
return (
<div className={selectFieldClass}>
<input type='hidden' name='selectfield-value' value={value} />
<div className={style['selectfield-value']} onClick={this.toggleSelectField}>
{this.getDisplayIcon()}
<span className={style['display-text']}>{this.getDisplayText()}</span>
<Icon name='icon-caret' width='10' height='10' />
</div>
<ul>
{options}
</ul>
</div>
)
}
}
export default SelectField