react-conventions
Version:
An open source set of React components that implement Ambassador's Design and UX patterns.
312 lines (259 loc) • 10.7 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.Typeahead = undefined;
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _bind = require('classnames/bind');
var _bind2 = _interopRequireDefault(_bind);
var _reactClickOutside = require('react-click-outside');
var _reactClickOutside2 = _interopRequireDefault(_reactClickOutside);
var _fuzzy = require('fuzzy');
var _fuzzy2 = _interopRequireDefault(_fuzzy);
var _debounce = require('lodash/debounce');
var _debounce2 = _interopRequireDefault(_debounce);
var _reactLoader = require('react-loader');
var _reactLoader2 = _interopRequireDefault(_reactLoader);
var _Input = require('../Input');
var _Input2 = _interopRequireDefault(_Input);
var _Icon = require('../Icon');
var _Icon2 = _interopRequireDefault(_Icon);
var _style = require('./style.scss');
var _style2 = _interopRequireDefault(_style);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var Typeahead = exports.Typeahead = function (_React$Component) {
_inherits(Typeahead, _React$Component);
function Typeahead(props) {
_classCallCheck(this, Typeahead);
var _this = _possibleConstructorReturn(this, (Typeahead.__proto__ || Object.getPrototypeOf(Typeahead)).call(this, props));
_this.state = {
isActive: false,
value: _this.props.value || '',
results: [],
selected: '',
searchStr: ''
};
_this.componentWillMount = function () {
if (typeof _this.state.value !== 'undefined' && _this.state.value !== '' && _this.getIndex(_this.state.value, _this.props.options) > -1) {
_this.selectItem(_this.state.value, _this.props.options);
} else {
_this.setState({ selected: '' });
}
};
_this.componentWillReceiveProps = function (nextProps) {
if (nextProps.value && nextProps.value !== _this.state.value && _this.getIndex(nextProps.value, nextProps.options) > -1) {
_this.setState({ value: nextProps.value }, function () {
_this.selectItem(nextProps.value, nextProps.options);
});
} else if (nextProps.value === '' && nextProps.value !== _this.state.value) {
_this.clearSearch();
}
};
_this.selectOption = function (option) {
var normalizedOption = option.original ? option.original : option;
var newState = {
selected: normalizedOption,
searchStr: normalizedOption[_this.props.displayProp],
value: normalizedOption[_this.props.valueProp],
isActive: false
};
if (_this.props.resetAfterSelection) {
newState.searchStr = '';
newState.value = '';
// Focus the input field
_this._inputField.focus();
}
_this.setState(newState, function () {
if (typeof _this.props.changeCallback === 'function') {
_this.props.changeCallback({
target: {
name: _this.props.name,
value: normalizedOption[_this.props.valueProp],
option: normalizedOption
}
});
}
});
};
_this.selectItem = function (value, options) {
var index = _this.getIndex(value, options);
if (index >= 0) {
_this.selectOption(options[index], false);
}
};
_this.getIndex = function (value, options) {
var optionIndex = -1;
options.map(function (option, index) {
if (option[_this.props.valueProp] === value) {
optionIndex = index;
}
});
return optionIndex;
};
_this.handleChange = function (event) {
if (!event.target.value.length) {
_this.clearSearch();
return;
}
_this.setState({ searchStr: event.target.value });
if (typeof _this.props.searchCallback === 'function') {
_this.props.searchCallback(event.target.value).then(function (options) {
_this.updateResults(event, options);
});
} else {
_this.updateResults(event, _this.props.options);
}
};
_this.handleClickOutside = function () {
_this.setState({ isActive: false });
};
_this.updateResults = function (event, options) {
var str = {
pre: '<b>',
post: '</b>',
extract: function extract(el) {
return el[_this.props.displayProp];
}
};
if (_this.props.optionsFilterPredicate) {
options = options.filter(_this.props.optionsFilterPredicate);
}
var results = _fuzzy2.default.filter(event.target.value, options, str);
_this.setState({ results: results, isActive: true });
};
_this.getDynamicList = function (str) {
return {
__html: str
};
};
_this.clearSearch = function () {
_this.setState({ isActive: false, searchStr: '', selected: '', value: '' }, function () {
if (typeof _this.props.changeCallback === 'function') {
_this.props.changeCallback({
target: {
name: _this.props.name,
value: '',
option: ''
}
});
}
});
};
_this.onChange = typeof _this.props.searchCallback === 'function' && props.searchDebounceTime > 0 ? (0, _debounce2.default)(_this.handleChange, props.searchDebounceTime) : _this.handleChange;
return _this;
}
_createClass(Typeahead, [{
key: 'render',
value: function render() {
var _this2 = this;
var cx = _bind2.default.bind(_style2.default);
var loaderClass = this.props.loading ? 'loading' : null;
var typeaheadClass = cx(_style2.default['typeahead-component'], loaderClass, this.props.optClass);
var spinnerOptions = {
color: '#9198A0',
length: 4,
lines: 10,
radius: 5,
left: 'calc(100% - 21px)',
width: 3
};
var options = this.state.results.map(function (option, index) {
return _react2.default.createElement('li', {
key: index,
onClick: _this2.selectOption.bind(null, option, true),
dangerouslySetInnerHTML: _this2.getDynamicList(option.string) });
});
return _react2.default.createElement(
'div',
{ className: typeaheadClass },
_react2.default.createElement(_Input2.default, { ref: function ref(c) {
return _this2._inputField = c;
}, changeCallback: this.onChange, value: this.state.searchStr, placeholder: this.props.placeholder, disabled: this.props.disabled }),
this.state.searchStr !== '' && !this.props.loading && !this.props.disabled ? _react2.default.createElement(
_Icon2.default,
{ name: 'icon-delete-1-1', onClick: this.clearSearch, className: _style2.default['reset-button'] },
'Reset'
) : null,
this.props.loading ? _react2.default.createElement(_reactLoader2.default, { loaded: false, options: spinnerOptions }) : null,
this.state.isActive ? _react2.default.createElement(
'ul',
{ className: _style2.default['typeahead-list'] },
options
) : null
);
}
}]);
return Typeahead;
}(_react2.default.Component);
Typeahead.defaultProps = {
disabled: false,
options: [],
valueProp: '',
displayProp: '',
resetAfterSelection: false,
searchDebounceTime: 0
};
Typeahead.propTypes = {
/**
* Name of the typeahead.
*/
name: _react2.default.PropTypes.string,
/**
* A string to display as the placeholder text.
*/
placeholder: _react2.default.PropTypes.string,
/**
* An array of objects which will be used as the options for the select field.
*/
options: _react2.default.PropTypes.array.isRequired,
/**
* Value of the typeahead.
*/
value: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.number, _react2.default.PropTypes.string]),
/**
* Which field in the option object will be used as the value of the select field.
*/
valueProp: _react2.default.PropTypes.string.isRequired,
/**
* Which field in the option object will be used as the display of the select field.
*/
displayProp: _react2.default.PropTypes.string.isRequired,
/**
* Whether the select field is disabled.
*/
disabled: _react2.default.PropTypes.bool,
/**
* Optional styles to add to the select field.
*/
optClass: _react2.default.PropTypes.string,
/**
* A callback function to be called when an option is selected.
*/
changeCallback: _react2.default.PropTypes.func,
/**
* A callback for updating options when typeahead search value is changed.
*/
searchCallback: _react2.default.PropTypes.func,
/**
* A loading state to be set to true when asynchronous searching is in progress.
*/
loading: _react2.default.PropTypes.bool,
/**
* A function to filter options.
*/
optionsFilterPredicate: _react2.default.PropTypes.func,
/**
* Clear search string after selection.
*/
resetAfterSelection: _react2.default.PropTypes.bool,
/**
* Search debounce time.
*/
searchDebounceTime: _react2.default.PropTypes.number
};
exports.default = (0, _reactClickOutside2.default)(Typeahead);