UNPKG

@helpscout/hsds-react

Version:

React component library for Help Scout's Design System

496 lines (415 loc) 15.4 kB
"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;