src/views/form/dropdown.field.js
import React from 'react';
import { FontAwesomeIcon as FA } from '@fortawesome/react-fontawesome';
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
import Field from './field.view';
import PropTypes from 'prop-types';
/**
* @external {HTMLOptionElement} https://developer.mozilla.org/en-US/docs/Web/API/HTMLOptionElement
*/
/**
* Controls a dropdown type of input
*
* #### SCSS import:
* ```
* @import "~@orloxx/ui-core/scss/form/dropdown-field";
* ```
*
* @extends {Field}
*
* @example
* <DropdownField id='sex' name='sex' label='Sex'>
* <option value='M'>Male</option>
* <option value='F'>Female</option>
* </DropdownField>
*/
class DropdownField extends Field {
/**
* @type {Object}
* @property {Array<HTMLOptionElement>} children - Elements to show in the dropdown list
*/
static propTypes = Object.assign({}, Field.propTypes, {
children: PropTypes.array.isRequired,
});
/**
* @ignore
*/
static defaultProps = Object.assign({}, Field.defaultProps, {
children: [],
});
/**
* @ignore
*/
constructor(props) {
super(props);
/**
* @ignore
*/
this.state = Object.assign({}, this.state, {
isFocused: false,
options: [],
selectedOption: {
id: '', label: '',
},
});
}
/**
* @ignore
*/
componentWillMount() {
const options = this.props.children.map(el => ({
id: el.props.value,
label: el.props.children,
selected: el.props.selected,
}));
const { selectedOption } = this.state;
this.setState({
options,
selectedOption: options.find(el => el.selected) || selectedOption,
});
}
/**
* @ignore
*/
componentDidUpdate(prevProps, prevState) {
if (prevState.isFocused && !this.state.isFocused) {
this.validate();
}
}
/**
* @ignore
*/
onSelect(e, option) {
e.preventDefault();
this.setState({ selectedOption: option, isFocused: false });
}
/**
* @ignore
*/
handleFocus({ relatedTarget }) {
const isFocused = relatedTarget &&
(relatedTarget.classList.contains('dropdown__link') ||
relatedTarget.classList.contains('dropdown__input') ||
relatedTarget.classList.contains('field__icon'));
this.setState({ isFocused: !!isFocused });
}
/**
* @ignore
*/
renderIcon() {
return this.state.isFocused
? (<FA icon={faChevronUp} />)
: (<FA icon={faChevronDown} />);
}
/**
* @ignore
*/
renderOptions() {
if (this.state.isFocused) {
return this.state.options.map(option => (
<li className='dropdown__item' key={option.id}>
<a
className='dropdown__link' href='' title={option.label}
onTouchEnd={e => this.onSelect(e, option)}
onClick={e => this.onSelect(e, option)}>{option.label}</a>
</li>
));
}
return null;
}
/**
* @ignore
*/
render() {
const { id, label, name, placeholder, required } = this.props;
const { selectedOption, isFocused } = this.state;
return (
<div className={`field dropdown ${this.fieldClasses}`} onBlur={e => this.handleFocus(e)}>
<label className='field__label' htmlFor={id}>
{label} {this.requiredLabel}
</label>
<div className='field__inputWrapper'>
<input
type='hidden' id={`${id}-value`}
name={`${name}-value`} value={selectedOption.id} />
<input
className='field__input dropdown__input' type='text'
id={id} name={name} ref={this.input}
placeholder={placeholder} title={placeholder}
required={required}
value={selectedOption.label} readOnly
onFocus={() => this.setState({ isFocused: true })} />
<button
type='button' className='field__icon'
title={`Toggle ${label} dropdown`}
onClick={() => this.setState({ isFocused: !isFocused })}>
{this.renderIcon()}
</button>
{this.renderValidationIcon()}
</div>
<ul className='dropdown__list'>
{this.renderOptions()}
</ul>
{this.renderValidationMessages()}
</div>
);
}
}
export default DropdownField;