@helpscout/hsds-react
Version:
React component library for Help Scout's Design System
496 lines (415 loc) • 15.4 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
exports.__esModule = true;
exports.default = exports.EditableTextarea = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
var _inheritsLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/inheritsLoose"));
var _react = _interopRequireDefault(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _reactTextareaAutosize = _interopRequireDefault(require("react-textarea-autosize"));
var _getValidProps = _interopRequireDefault(require("@helpscout/react-utils/dist/getValidProps"));
var _classnames = _interopRequireDefault(require("classnames"));
var _lodash = _interopRequireDefault(require("lodash.debounce"));
var _EditableTextarea = require("./EditableTextarea.css");
var _EditableField = require("../EditableField/EditableField.css");
var _Icon = _interopRequireDefault(require("../Icon"));
var _Tooltip = _interopRequireDefault(require("../Tooltip"));
var _EditableTextarea2 = require("./EditableTextarea.utils");
var _Keys = require("../../constants/Keys");
var _EditableField2 = require("../EditableField/EditableField.constants");
var _EditableField3 = require("../EditableField/EditableField.utils");
var _jsxRuntime = require("react/jsx-runtime");
function noop() {}
var EditableTextarea = /*#__PURE__*/function (_React$PureComponent) {
(0, _inheritsLoose2.default)(EditableTextarea, _React$PureComponent);
function EditableTextarea(props) {
var _this;
_this = _React$PureComponent.call(this, props) || this;
_this.textArea = void 0;
_this.debouncedScroll = void 0;
_this.editableTextareaRef = void 0;
_this.setEditableTextareaNode = function (node) {
_this.editableTextareaRef = node;
_this.props.innerRef(node);
};
_this.detectScroll = function (e) {
// If the user scrolls to the bottom, remove the visual clamp cue
var hasReachedBottom = _this.textArea.current.clientHeight + _this.textArea.current.scrollTop >= _this.textArea.current.scrollHeight;
_this.setState({
clamped: !hasReachedBottom
});
};
_this.handleOnChange = function (e) {
_this.setState({
value: e.target.value,
validationInfo: null,
validated: false
}, function () {
var id = _this.props.id;
var value = _this.state.value;
var item = {
value: value,
id: id
};
_this.props.onChange({
name: id,
value: [item],
event: e
});
});
};
_this.handleOnClick = function (e) {
var _this$props = _this.props,
id = _this$props.id,
onInputFocus = _this$props.onInputFocus;
var value = _this.state.value;
var item = {
value: value,
id: id
};
if (_this.state.readOnly) {
_this.setState({
prevValue: _this.textArea.current.value,
readOnly: false
}, function () {
_this.textArea.current.focus();
onInputFocus({
name: id,
value: [item],
event: e
});
});
}
};
_this.handleOnKeyDown = function (e) {
var code = e.key;
var isShiftPressed = e.shiftKey;
var stop = function stop() {
return e.preventDefault() && e.stopPropagation();
};
if (!isShiftPressed && code === _Keys.key.ENTER) {
stop();
_this.setState({
value: _this.state.value.trim()
}, function () {
var id = _this.props.id;
var value = _this.state.value;
var item = {
value: value,
id: id
};
_this.props.onEnter({
name: id,
value: [item],
event: e
}); // trigger blur
_this.handleOnBlur();
});
} else if (code === _Keys.key.ESCAPE) {
stop();
_this.setState({
value: _this.state.prevValue
}, function () {
var id = _this.props.id;
var value = _this.state.value;
var item = {
value: value,
id: id
};
_this.props.onEscape({
name: id,
value: [item],
event: e
});
});
}
_this.props.onInputKeyDown({
name: _this.props.id,
value: [{
value: _this.state.value,
id: _this.props.id
}],
event: e
});
};
_this.handleOnKeyUp = function (e) {
var code = e.key;
if (code === _Keys.key.ESCAPE) {
e.preventDefault() && e.stopPropagation();
_this.textArea.current.blur();
}
_this.props.onInputKeyUp({
name: _this.props.id,
value: [{
value: _this.state.value,
id: _this.props.id
}],
event: e
});
};
_this.handleOnBlur = function (e) {
var _this$props2 = _this.props,
id = _this$props2.id,
onCommit = _this$props2.onCommit,
onInputBlur = _this$props2.onInputBlur,
validate = _this$props2.validate;
var _this$state = _this.state,
prevValue = _this$state.prevValue,
value = _this$state.value,
validated = _this$state.validated;
var item = {
value: value,
id: id
}; // Unchanged value, or ESC case
if (value === prevValue) {
_this.setState({
readOnly: true,
validationInfo: null
}, function () {
(0, _EditableTextarea2.scrollToTop)(_this.textArea.current);
});
} else {
if (!validated) {
validate({
data: {
cause: _EditableField2.CAUSE.BLUR,
operation: _EditableField2.OPERATION.UPDATE,
item: item
},
name: id,
value: value,
values: [item]
}).then(function (validation) {
if (validation.isValid) {
_this.setState({
readOnly: true,
validationInfo: null,
validated: true,
value: value
}, function () {
onCommit({
data: {
cause: _EditableField2.CAUSE.BLUR,
operation: _EditableField2.OPERATION.UPDATE,
item: item
},
name: id,
value: [item]
});
onInputBlur({
name: id,
value: [item],
event: e
});
(0, _EditableTextarea2.scrollToTop)(_this.textArea.current);
});
} else {
_this.setState({
validationInfo: validation,
validated: true
}, function () {
(0, _EditableTextarea2.scrollToTop)(_this.textArea.current);
});
}
});
}
}
};
_this.handleTextareaHeightChange = function () {
_this.setClampVisualCue();
};
_this.setClampVisualCue = function () {
_this.setState({
clamped: Boolean(_this.state.value) && _this.textArea.current.clientHeight > 0 && _this.textArea.current.clientHeight < _this.textArea.current.scrollHeight
});
};
_this.renderValidationInfo = function () {
var id = _this.props.id;
var validationInfo = _this.state.validationInfo;
if (!validationInfo) return null;
if (id !== validationInfo.name) return null;
var DEFAULT_ICON = 'alert-small';
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_EditableField.ValidationIconUI, {
className: EditableTextarea.className + "__validation",
color: (0, _EditableField3.getValidationColor)(validationInfo),
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Tooltip.default, {
animationDelay: 0,
animationDuration: 0,
display: "block",
placement: "top-end",
title: validationInfo.message,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon.default, {
name: validationInfo.icon || DEFAULT_ICON,
size: 24
})
})
});
};
var valueFromProps = props.value && props.value.trim();
_this.state = {
clamped: false,
readOnly: true,
prevValue: valueFromProps,
value: valueFromProps,
validated: false,
validationInfo: null
};
_this.textArea = /*#__PURE__*/_react.default.createRef();
_this.debouncedScroll = (0, _lodash.default)(_this.detectScroll, 30);
return _this;
}
var _proto = EditableTextarea.prototype;
_proto.getClassName = function getClassName() {
var _this$props3 = this.props,
className = _this$props3.className,
floatingLabels = _this$props3.floatingLabels;
return (0, _classnames.default)(EditableTextarea.className, floatingLabels && 'with-floatingLabels', className);
};
_proto.componentDidMount = function componentDidMount() {
this.textArea.current.addEventListener('scroll', this.debouncedScroll);
};
_proto.componentWillUnmount = function componentWillUnmount() {
this.textArea.current.removeEventListener('scroll', this.debouncedScroll);
};
_proto.UNSAFE_componentWillReceiveProps = function UNSAFE_componentWillReceiveProps(nextProps) {
// Tested
if (nextProps.value === this.state.value) return;
this.setState({
value: nextProps.value
});
};
_proto.componentDidUpdate = function componentDidUpdate(prevProps, prevState) {
if (this.props.value !== prevProps.value) {
this.setClampVisualCue();
}
if (this.state.value !== prevState.value) {
this.setClampVisualCue();
}
};
_proto.render = function render() {
var _this$props4 = this.props,
floatingLabels = _this$props4.floatingLabels,
id = _this$props4.id,
label = _this$props4.label,
maxRows = _this$props4.maxRows,
placeholder = _this$props4.placeholder,
overflowCueColor = _this$props4.overflowCueColor,
rest = (0, _objectWithoutPropertiesLoose2.default)(_this$props4, ["floatingLabels", "id", "label", "maxRows", "placeholder", "overflowCueColor"]);
var _this$state2 = this.state,
clamped = _this$state2.clamped,
readOnly = _this$state2.readOnly,
value = _this$state2.value,
validationInfo = _this$state2.validationInfo;
var textAreaClasses = (0, _classnames.default)('EditableTextarea__Textarea', 'field', readOnly && !Boolean(value) && 'is-placeholder');
var maskClasses = (0, _classnames.default)('EditableTextarea__Mask', 'field', (!readOnly || Boolean(value)) && !floatingLabels && 'is-hidden', readOnly && !Boolean(value) && 'is-inline');
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_EditableTextarea.ComponentUI, {
ref: this.setEditableTextareaNode,
className: this.getClassName(),
children: [!floatingLabels ? /*#__PURE__*/(0, _jsxRuntime.jsx)("label", {
className: "EditableTextarea__label",
htmlFor: id,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_EditableField.LabelTextUI, {
children: label
})
}) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_EditableTextarea.EditableTextareaUI, {
className: (0, _classnames.default)('EditableTextarea__ResizableTextarea', readOnly && 'is-readonly', readOnly && clamped && 'is-clamped', !Boolean(value) && 'with-placeholder'),
overflowCueColor: overflowCueColor,
focusIndicatorColor: (0, _EditableField3.getValidationColor)(validationInfo),
inputValue: value,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactTextareaAutosize.default, (0, _extends2.default)({}, (0, _getValidProps.default)(rest), {
className: textAreaClasses,
id: id,
inputRef: this.textArea,
maxRows: maxRows,
placeholder: placeholder,
readOnly: readOnly,
value: value,
onBlur: this.handleOnBlur,
onFocus: this.handleOnClick,
onChange: this.handleOnChange,
onClick: this.handleOnClick,
onHeightChange: this.handleTextareaHeightChange,
onKeyDown: this.handleOnKeyDown,
onKeyUp: this.handleOnKeyUp
})), /*#__PURE__*/(0, _jsxRuntime.jsx)(_EditableTextarea.MaskUI, {
className: maskClasses,
onClick: this.handleOnClick,
inputValue: value,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)("span", {
children: placeholder
})
}), this.renderValidationInfo()]
})]
});
};
return EditableTextarea;
}(_react.default.PureComponent);
exports.EditableTextarea = EditableTextarea;
EditableTextarea.className = 'c-EditableTextarea';
EditableTextarea.defaultProps = {
'data-cy': 'EditableTextarea',
floatingLabels: false,
id: 'editabletextarea',
innerRef: noop,
label: 'Notes',
maxRows: 5,
overflowCueColor: 'white',
placeholder: 'Add notes',
value: '',
onCommit: noop,
onChange: noop,
onInputBlur: noop,
onInputFocus: noop,
onInputKeyDown: noop,
onInputKeyUp: noop,
onEnter: noop,
onEscape: noop,
validate: function validate() {
return Promise.resolve({
isValid: true
});
}
};
EditableTextarea.propTypes = {
/** The className of the component. */
className: _propTypes.default.string,
/** The id to assign to the component. */
id: _propTypes.default.string,
/** Uses the "floating label" pattern with animation */
floatingLabels: _propTypes.default.bool,
/** The label for the field */
label: _propTypes.default.string,
/** The maximum number of lines the textarea will grow to (max height). */
maxRows: _propTypes.default.number,
/** The color of the visual cue when content is overflowing. */
overflowCueColor: _propTypes.default.string,
/** The placeholder of the textarea. */
placeholder: _propTypes.default.string,
/** The value of the textarea. */
value: _propTypes.default.string,
/** Function that validates the value, should always return a Promise that resolves to a Validation type */
validate: _propTypes.default.func,
/** Retrieve the inner DOM node. */
innerRef: _propTypes.default.func,
/** Fires when either the input or an option is changed */
onChange: _propTypes.default.func,
/** Fires when Enter is pressed on the input */
onEnter: _propTypes.default.func,
/** Fires when Escape is pressed on the input */
onEscape: _propTypes.default.func,
/** Fires when a change is “saved” (see below) */
onCommit: _propTypes.default.func,
/** Fired when the input is focused */
onInputFocus: _propTypes.default.func,
/** Fired when the input is blurred */
onInputBlur: _propTypes.default.func,
/** Fires on textarea keyup */
onInputKeyDown: _propTypes.default.func,
/** Fires on textarea keydown */
onInputKeyUp: _propTypes.default.func,
/** Data attr for Cypress tests. */
'data-cy': _propTypes.default.string
};
var _default = EditableTextarea;
exports.default = _default;