UNPKG

react-conventions

Version:

An open source set of React components that implement Ambassador's Design and UX patterns.

473 lines (406 loc) 16.2 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _clipboard = require('clipboard'); var _clipboard2 = _interopRequireDefault(_clipboard); var _style = require('./style.scss'); var _style2 = _interopRequireDefault(_style); var _bind = require('classnames/bind'); var _bind2 = _interopRequireDefault(_bind); var _Icon = require('../Icon'); var _Icon2 = _interopRequireDefault(_Icon); var _Spinner = require('../Spinner'); var _Spinner2 = _interopRequireDefault(_Spinner); var _Tooltip = require('../Tooltip'); var _Tooltip2 = _interopRequireDefault(_Tooltip); var _SelectField = require('../SelectField'); var _SelectField2 = _interopRequireDefault(_SelectField); 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 InlineEdit = function (_React$Component) { _inherits(InlineEdit, _React$Component); function InlineEdit(props) { _classCallCheck(this, InlineEdit); var _this = _possibleConstructorReturn(this, (InlineEdit.__proto__ || Object.getPrototypeOf(InlineEdit)).call(this, props)); _this.state = { isEditing: _this.props.isEditing, value: _this.props.value || '', loading: _this.props.loading, error: _this.props.error, copied: false }; _this.componentWillReceiveProps = function (nextProps) { if (nextProps.isEditing) { _this.showButtons(); } var newState = {}; if (nextProps.loading !== _this.state.loading) { newState.loading = nextProps.loading; } if (nextProps.error !== _this.state.error) { newState.error = nextProps.error; } if (nextProps.error !== '' && _this.props.type === 'text') { _this.showButtons(); } if (Object.keys(newState).length > 0) { _this.setState(newState); } }; _this.componentDidMount = function () { if (_this.props.type === 'text') { _this.attachKeyListeners(); _this.activateCopyToClipboard(); } _this.getStyles(); }; _this.shouldComponentUpdate = function (nextProps, nextState) { return _this.state.isEditing !== nextState.isEditing || _this.state.value !== nextState.value || _this.state.loading !== nextState.loading || _this.state.error !== nextState.error || _this.state.copied !== nextState.copied || _this.state.inlineEditMaxWidth !== nextState.inlineEditMaxWidth || _this.props.tooltipText !== nextProps.tooltipText || _this.props.tooltipPlacement !== nextProps.tooltipPlacement || _this.props.readonly !== nextProps.readonly; }; _this.handleSave = function (event) { if (_this.props.type === 'text') { (function () { var inputText = _this._textValue.textContent; var shouldTriggerCallback = inputText !== _this.state.value; var previousValue = _this.state.value; var isEditing = _this.state.error !== '' ? true : false; _this.setState({ isEditing: isEditing, value: inputText }, function () { if (!isEditing) { _this.activateCopyToClipboard(); _this._textValue.blur(); _this._textValue.scrollLeft = 0; } if (typeof _this.props.changeCallback === 'function' && shouldTriggerCallback) { var _event = { target: { name: _this.props.name, value: _this.state.value } }; _this.props.changeCallback(_event); } }); })(); } else { (function () { var shouldTriggerCallback = event.target.value !== _this.state.value; _this.setState({ value: event.target.value }, function () { if (typeof _this.props.changeCallback === 'function' && shouldTriggerCallback) { var _event2 = { target: { name: _this.props.name, value: _this.state.value } }; _this.props.changeCallback(_event2); } }); })(); } }; _this.handleCancel = function () { var newState = { isEditing: false }; var shouldTriggerCallback = false; if (_this.state.error !== '' && _this.props.value !== _this.state.value) { newState.error = ''; newState.value = _this.props.value; shouldTriggerCallback = true; } _this.setState(newState, function () { _this.activateCopyToClipboard(); _this._textValue.blur(); _this._textValue.scrollLeft = 0; if (typeof _this.props.changeCallback === 'function' && shouldTriggerCallback) { var event = { target: { name: _this.props.name, value: _this.state.value, canceled: true } }; _this.props.changeCallback(event); } }); }; _this.showButtons = function () { if (!_this.props.readonly) { _this.setState({ isEditing: true }, function () { _this.selectElementContents(_this._textValue); }); } }; _this.getField = function () { if (_this.props.type === 'select') { return _this.getSelect(); } else { return _this.getSpan(); } }; _this.getSelect = function () { var selectClass = _bind2.default.bind(_style2.default)(_style2.default['inline-edit-select'], _this.state.loading ? 'loading' : ''); return _react2.default.createElement(_SelectField2.default, { options: _this.props.options, valueProp: 'value', displayProp: 'label', changeCallback: _this.handleSave, value: _this.state.value, optClass: selectClass, disabled: _this.props.readonly }); }; _this.getSpan = function () { if (_this.state.isEditing) { return _react2.default.createElement('span', { id: 'span_id', contentEditable: true, className: _style2.default['inline-text-wrapper'], dangerouslySetInnerHTML: { __html: _this.state.value }, ref: function ref(c) { return _this._textValue = c; } }); } return _react2.default.createElement( 'span', { id: 'span_id', onClick: _this.showButtons, className: _style2.default['inline-text-wrapper-hover'], ref: function ref(c) { return _this._textValue = c; } }, _this.props.tooltipText ? _react2.default.createElement( _Tooltip2.default, { content: _this.props.tooltipText, tooltipPlacement: _this.props.tooltipPlacement, appendToBody: true, className: _style2.default['value-tooltip'], optClass: _this.props.tooltipClass || '' }, _this.state.value || _this.props.placeholder ) : _react2.default.createElement( 'span', null, _this.state.value || _this.props.placeholder ) ); }; _this.getCopyIcon = function () { if (_this.state.copied) { return 'copied!'; } var copyIconFill = _this.state.value === '' ? '#9198A0' : '#3C97D3'; return _react2.default.createElement(_Icon2.default, { name: 'icon-clipboard-1', height: '14', width: '14', fill: copyIconFill }); }; _this.getIcon = function () { if (_this.props.icon) { return _react2.default.createElement( 'span', { className: _style2.default['inline-icon'], ref: function ref(c) { return _this._inlineIcon = c; } }, _react2.default.createElement(_Icon2.default, { name: _this.props.icon, height: '14', width: '14', fill: '#9198A0' }) ); } }; _this.getLabel = function () { if (_this.props.label) { return _react2.default.createElement( 'label', { className: _style2.default['inline-label'], ref: function ref(c) { return _this._inlineLabel = c; } }, _this.props.label ); } }; _this.selectElementContents = function (element) { var range = document.createRange(); range.selectNodeContents(element); var selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); element.focus(); }; _this.attachKeyListeners = function () { _this._textValue.addEventListener('keypress', _this.handleKeyPress); _this._textValue.addEventListener('keyup', _this.handleKeyUp); }; _this.handleKeyPress = function (event) { // Grabs the character code, even in FireFox var charCode = event.keyCode ? event.keyCode : event.which; if (charCode === 13) { event.preventDefault(); _this.handleSave(); } }; _this.handleKeyUp = function (event) { // Grabs the character code, even in FireFox var charCode = event.keyCode ? event.keyCode : event.which; if (charCode === 27) { event.preventDefault(); _this.handleCancel(); } }; _this.activateCopyToClipboard = function () { if (!_this.props.copyToClipboard) { return; } var clipboard = new _clipboard2.default(_this._copyTrigger); clipboard.on('success', function () { _this.handleCopy(); }); }; _this.handleCopy = function () { _this.setState({ copied: true }, function () { setTimeout(function () { _this.setState({ copied: false }); }, 1800); }); }; _this.getStyles = function () { var offset = 0; if (_this._inlineIcon) { // Add width and margin to the offset offset += _this._inlineIcon.getBoundingClientRect().width + 5; } if (_this._inlineLabel) { // Add width and margin to the offset offset += _this._inlineLabel.getBoundingClientRect().width + 10; } _this.setState({ inlineEditMaxWidth: 'calc(100% - ' + offset + 'px)' }); }; _this.render = function () { var cx = _bind2.default.bind(_style2.default); var readonlyClass = _this.props.readonly ? 'readonly' : ''; var errorClass = _this.state.error !== '' ? 'error' : ''; var placeholderClass = _this.state.value === '' ? 'placeholder' : ''; var copyDisabledClass = _this.state.value === '' ? 'disabled' : ''; var copyIconClass = cx(_style2.default['copy-icon'], copyDisabledClass, _this.state.copied ? 'copied' : ''); var inlineEditClass = cx(_style2.default['inline-edit-wrapper'], _this.props.optClass, readonlyClass, errorClass, placeholderClass); var overflowWrapperClass = cx(_style2.default['inline-text-overflow-wrapper'], _this.props.type === 'select' ? _style2.default['visible'] : ''); return _react2.default.createElement( 'div', { className: inlineEditClass }, _react2.default.createElement( 'div', { className: _style2.default['inline-edit-wrapper-inner'] }, _this.getIcon(), _this.getLabel(), _react2.default.createElement( 'div', { className: overflowWrapperClass, style: { maxWidth: _this.state.inlineEditMaxWidth } }, _this.getField(), _this.state.isEditing && !_this.state.loading ? _react2.default.createElement( 'div', { className: _style2.default['inline-button-wrapper'] }, _react2.default.createElement( _Icon2.default, { name: 'icon-check-2-1', onClick: _this.handleSave, height: '20', width: '20', className: _style2.default['save-button'] }, 'Save' ), _react2.default.createElement( _Icon2.default, { name: 'icon-delete-1-1', onClick: _this.handleCancel, height: '20', width: '20', className: _style2.default['cancel-button'] }, 'Cancel' ) ) : null, _this.props.copyToClipboard && !_this.state.isEditing && !_this.state.loading ? _react2.default.createElement( 'span', { ref: function ref(c) { return _this._copyTrigger = c; }, 'data-clipboard-text': _this.state.value }, _react2.default.createElement( 'span', { className: copyIconClass }, _this.getCopyIcon() ) ) : null, _react2.default.createElement( 'div', { className: _style2.default['loader-wrapper'] }, _react2.default.createElement(_Spinner2.default, { loading: _this.state.loading, optClass: _style2.default['spinner'], type: 'spinner-bounce', color: '#9198A0' }) ) ) ), _this.state.error && _this.state.error !== '' ? _react2.default.createElement( 'div', { className: _style2.default['error-text'] }, _this.state.error ) : null ); }; return _this; } return InlineEdit; }(_react2.default.Component); InlineEdit.defaultProps = { isEditing: false, placeholder: 'Click to edit', loading: false, readonly: false, error: '', value: '', tooltipPlacement: 'right', type: 'text' }; InlineEdit.propTypes = { /** * Name of the input. */ name: _react2.default.PropTypes.string, /** * A callback function to be called when save is clicked. */ changeCallback: _react2.default.PropTypes.func, /** * Value of the input. */ value: _react2.default.PropTypes.oneOfType([_react2.default.PropTypes.number, _react2.default.PropTypes.string, _react2.default.PropTypes.array]), /** * Boolean used to show/hide the input vs formatted display. */ isEditing: _react2.default.PropTypes.bool, /** * Optional styles to add to the inline-edit. */ optClass: _react2.default.PropTypes.string, /** * Optional placeholder string for empty submission. */ placeholder: _react2.default.PropTypes.string, /** * Whether the inline-edit is readonly. */ readonly: _react2.default.PropTypes.bool, /** * Boolean used to show/hide the loader. */ loading: _react2.default.PropTypes.bool, /** * Error to display under the field. */ error: _react2.default.PropTypes.string, /** * Boolean used to display the copy to clipboard icon. */ copyToClipboard: _react2.default.PropTypes.bool, /** * A label to display next to the component. */ label: _react2.default.PropTypes.string, /** * An icon to display next to the component. */ icon: _react2.default.PropTypes.string, /** * Text to display inside the tooltip. */ tooltipText: _react2.default.PropTypes.string, /** * The placement of the tooltip. */ tooltipPlacement: _react2.default.PropTypes.oneOf(['left', 'right', 'top', 'bottom']), /** * An optional class to add to the tooltip. */ tooltipClass: _react2.default.PropTypes.string, /** * Type of the field. */ type: _react2.default.PropTypes.oneOf(['text', 'select']), /** * Options for the dropdown menu (required if type is 'select'). */ selectOptions: _react2.default.PropTypes.array }; exports.default = InlineEdit;