sc-react-ions
Version:
An open source set of React components that implement Ambassador's Design and UX patterns.
479 lines (403 loc) • 16 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
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') {
var inputText = _this._textValue.textContent;
var shouldTriggerCallback = inputText !== _this.state.value;
var previousValue = _this.state.value;
var isEditing = _this.state.error !== '';
_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 {
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();
}
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: 'md-copy', 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: 'md-check', onClick: _this.handleSave, height: '20', width: '20', className: _style2.default['save-button'] },
'Save'
),
_react2.default.createElement(
_Icon2.default,
{ name: 'md-close', 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: _propTypes2.default.string,
/**
* A callback function to be called when save is clicked.
*/
changeCallback: _propTypes2.default.func,
/**
* Value of the input.
*/
value: _propTypes2.default.oneOfType([_propTypes2.default.number, _propTypes2.default.string, _propTypes2.default.array]),
/**
* Boolean used to show/hide the input vs formatted display.
*/
isEditing: _propTypes2.default.bool,
/**
* Optional styles to add to the inline-edit.
*/
optClass: _propTypes2.default.string,
/**
* Optional placeholder string for empty submission.
*/
placeholder: _propTypes2.default.string,
/**
* Whether the inline-edit is readonly.
*/
readonly: _propTypes2.default.bool,
/**
* Boolean used to show/hide the loader.
*/
loading: _propTypes2.default.bool,
/**
* Error to display under the field.
*/
error: _propTypes2.default.string,
/**
* Boolean used to display the copy to clipboard icon.
*/
copyToClipboard: _propTypes2.default.bool,
/**
* A label to display next to the component.
*/
label: _propTypes2.default.string,
/**
* An icon to display next to the component.
*/
icon: _propTypes2.default.string,
/**
* Text to display inside the tooltip.
*/
tooltipText: _propTypes2.default.string,
/**
* The placement of the tooltip.
*/
tooltipPlacement: _propTypes2.default.oneOf(['left', 'right', 'top', 'bottom']),
/**
* An optional class to add to the tooltip.
*/
tooltipClass: _propTypes2.default.string,
/**
* Type of the field.
*/
type: _propTypes2.default.oneOf(['text', 'select']),
/**
* Options for the dropdown menu (required if type is 'select').
*/
selectOptions: _propTypes2.default.array
};
exports.default = InlineEdit;