UNPKG

@eccenca/gui-elements

Version:

Collection of low-level GUI elements like Buttons, Icons or Alerts. Also includes core styles for those elements.

252 lines (228 loc) 8.22 kB
import React from 'react'; import classNames from 'classnames'; import Select from 'react-select/lib/Select'; import Creatable from 'react-select/lib/Creatable'; import Async from 'react-select/lib/Async'; import AsyncCreatable from 'react-select/lib/AsyncCreatable'; import _ from 'lodash'; import PerformanceMixin from './../../mixins/PerformanceMixin'; import UniqueIdWrapper from '../../utils/uniqueId'; import Button from '../Button/Button'; // format value to lowercase string const stringCompare = function(value) { return _.toLower(_.toString(value)); }; const clearRenderer = () => ( <Button iconName="clear" className="mdl-button--clearance" /> ); const SelectBox = React.createClass({ mixins: [PerformanceMixin], displayName: 'SelectBox', propTypes: { /** * contains values which are available in dropdown list * options is an array of objects or strings and/or numbers */ options: React.PropTypes.arrayOf( (propValue, key, componentName, location, propFullName) => { const containObjects = _.isPlainObject(_.head(propValue)); const isObject = _.isPlainObject(propValue[key]); const isNumberOrString = _.isString(propValue[key]) || _.isNumber(propValue[key]); if ( (!containObjects && !isNumberOrString) || (containObjects && !isObject) ) { return new Error( `Invalid prop \`${propFullName}\` supplied to` + ` \`${componentName}\`. No mixed content (object vs string/number) allowed.` ); } return false; } ), /** * contains selected value * value is an object or a strings a numbers */ value: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.number, React.PropTypes.object, // only needed for multiple inputs React.PropTypes.array, ]), // onChange handler onChange: React.PropTypes.func.isRequired, // allow creation of new values creatable: React.PropTypes.bool, }, onChange(newValue) { // If the options consist of plainvalues, we just want to return the plain value if (_.get(newValue, '$plainValue', false)) { return this.props.onChange(newValue.value, this.props.name); } return this.props.onChange(newValue, this.props.name); }, // default check for value creation // prevent double values (check case insensitive, and handle numbers as string) uniqueOptions({option: newObject, options}) { return !_.some( options, ({value, label}) => stringCompare(value) === stringCompare(newObject.value) && stringCompare(label) === stringCompare(newObject.label) ); }, onFocus() { this.setState({ focused: true, }); }, onBlur() { this.setState({ focused: false, }); }, render() { const { autofocus, className, creatable, placeholder = '', optionsOnTop, value, async = false, uniqueId, ...passProps } = this.props; // we do not want to pass onChange, as we wrap onChange ourselves delete passProps.onChange; // we do not want to pass name, as we use it ourselves delete passProps.name; passProps.onFocus = this.onFocus; passProps.onBlur = this.onBlur; passProps.clearable = _.isUndefined(passProps.clearable) ? true : passProps.clearable; if (passProps.clearable) { passProps.clearRenderer = clearRenderer; } const focused = this.state && typeof this.state.focused !== 'undefined' ? this.state.focused : autofocus; const classes = classNames( { 'mdl-textfield mdl-js-textfield mdl-textfield--full-width': !!placeholder, 'mdl-textfield--floating-label': !!placeholder, 'is-dirty': !_.isNil(value) && (_.isNumber(value) || !_.isEmpty(value)), 'is-focused': focused === true, 'Select--optionsontop': optionsOnTop === true, }, className ); let parsedValue = null; // if value is not empty or a number check for formatting if (!_.isEmpty(value) || _.isNumber(value)) { // in case of multi select is used if (_.isArray(value)) { parsedValue = _.map( value, it => (_.isPlainObject(it) ? it : {value: it, label: it}) ); } else { parsedValue = _.isPlainObject(value) ? value : {value, label: value}; } } let component = null; if (async) { const { ignoreCase = false, ignoreAccents = false, ...passAsyncProps } = passProps; if (creatable) { component = ( <AsyncCreatable {...passAsyncProps} value={parsedValue} id={uniqueId} onChange={this.onChange} isOptionUnique={ this.props.isOptionUnique || this.uniqueOptions } ignoreAccents={ignoreAccents} ignoreCase={ignoreCase} placeholder="" /> ); } else { component = ( <Async {...passAsyncProps} id={uniqueId} value={parsedValue} onChange={this.onChange} ignoreAccents={ignoreAccents} ignoreCase={ignoreCase} placeholder="" /> ); } } else { const {options, ...passSyncProps} = passProps; // parse values to object format if needed const parsedOptions = _.isPlainObject(options[0]) ? options : _.map(options, it => ({ value: it, label: it, $plainValue: true, })); if (creatable) { component = ( <Creatable {...passSyncProps} id={uniqueId} value={parsedValue} options={parsedOptions} onChange={this.onChange} isOptionUnique={ this.props.isOptionUnique || this.uniqueOptions } placeholder="" /> ); } else { component = ( <Select {...passSyncProps} id={uniqueId} value={parsedValue} options={parsedOptions} onChange={this.onChange} placeholder="" /> ); } } return ( <div className={classes}> {component} {placeholder && ( <label className="mdl-textfield__label" htmlFor={uniqueId}> {placeholder} </label> )} </div> ); }, }); export default UniqueIdWrapper(SelectBox, { prefix: 'selectBox', targetProp: 'uniqueId', });