UNPKG

@crave/farmblocks-hoc-input

Version:

A High Order Component that adds behaviour of label support, different styles for focus and errors, and validation error messages.

282 lines (249 loc) • 9.96 kB
import _styled from "styled-components"; function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } /* eslint-disable react/static-property-placement */ import * as React from "react"; import PropTypes from "prop-types"; import wrapDisplayName from "recompose/wrapDisplayName"; import Link from "@crave/farmblocks-link"; import Label from "@crave/farmblocks-label"; import { SmMagnifier, SmCross, SmChevronDown } from "@crave/farmblocks-icon"; import Text from "@crave/farmblocks-text"; import { fontSizes, fontTypes } from "@crave/farmblocks-theme"; import Wrapper from "./styledComponents/Wrapper"; const ICON_SIZE = 18; export const formInputProps = { label: PropTypes.node, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), type: PropTypes.string, small: PropTypes.bool, active: PropTypes.bool, focused: PropTypes.bool, disabled: PropTypes.bool, invalid: PropTypes.bool, onChange: PropTypes.func, onFocus: PropTypes.func, onBlur: PropTypes.func, innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), input: PropTypes.shape({ value: PropTypes.any, onChange: PropTypes.func }), readOnly: PropTypes.bool, refName: PropTypes.string, leftIcon: PropTypes.elementType, prefix: PropTypes.node, suffix: PropTypes.node, autoControlFocusedStyle: PropTypes.bool, className: PropTypes.string, borderRadius: PropTypes.string, mb: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), validationMessages: PropTypes.arrayOf(PropTypes.string) }; const getValueFromProps = ({ input, value }) => input ? input.value : value; const formInput = WrappedComponent => { var _class, _temp; return _temp = _class = class Input extends React.Component { constructor(props) { super(props); _defineProperty(this, "componentDidUpdate", prevProps => { if (this.props.focused && !prevProps.focused) { this.setInputFocus(); } if (!this.props.focused && prevProps.focused) { var _this$inputRef; (_this$inputRef = this.inputRef) === null || _this$inputRef === void 0 ? void 0 : _this$inputRef.blur(); } if (!this.props.autoControlFocusedStyle && this.props.focused !== prevProps.focused) { this.setState({ focused: this.props.focused }); } const nextValue = getValueFromProps(this.props); const prevValue = getValueFromProps(prevProps); if (nextValue !== prevValue) { this.setState({ value: nextValue }); } }); _defineProperty(this, "handleClearClick", () => { var _this$props$onChange, _this$props; this.setState({ value: "" }); (_this$props$onChange = (_this$props = this.props).onChange) === null || _this$props$onChange === void 0 ? void 0 : _this$props$onChange.call(_this$props, { type: "change", value: "", target: { value: "" } }); }); _defineProperty(this, "setInputFocus", () => { var _this$inputRef2; (_this$inputRef2 = this.inputRef) === null || _this$inputRef2 === void 0 ? void 0 : _this$inputRef2.focus(); }); _defineProperty(this, "onChange", event => { var _this$props$input, _this$props$input$onC, _this$props$onChange2, _this$props2; this.setState({ value: event.value || event.target.value }); (_this$props$input = this.props.input) === null || _this$props$input === void 0 ? void 0 : (_this$props$input$onC = _this$props$input.onChange) === null || _this$props$input$onC === void 0 ? void 0 : _this$props$input$onC.call(_this$props$input, event); (_this$props$onChange2 = (_this$props2 = this.props).onChange) === null || _this$props$onChange2 === void 0 ? void 0 : _this$props$onChange2.call(_this$props2, event); }); _defineProperty(this, "onFocus", event => { var _this$props$onFocus, _this$props3; (_this$props$onFocus = (_this$props3 = this.props).onFocus) === null || _this$props$onFocus === void 0 ? void 0 : _this$props$onFocus.call(_this$props3, event); if (!this.props.autoControlFocusedStyle) { return; } this.setState({ focused: true }); }); _defineProperty(this, "onBlur", event => { var _this$props$onBlur, _this$props4; (_this$props$onBlur = (_this$props4 = this.props).onBlur) === null || _this$props$onBlur === void 0 ? void 0 : _this$props$onBlur.call(_this$props4, event); if (!this.props.autoControlFocusedStyle) { return; } this.setState({ focused: false }); }); _defineProperty(this, "renderInput", ({ innerRef, refName, leftIcon, prefix, suffix, ...inputProps }) => { const handlers = { onChange: this.onChange, onFocus: this.onFocus, onBlur: this.onBlur }; const isSearch = inputProps.type && inputProps.type.toLowerCase() === "search"; const LeftIcon = leftIcon || isSearch && SmMagnifier; const clearButton = isSearch && this.state.value && /*#__PURE__*/React.createElement(Link, { className: "clear icon right", "data-testid": "input-clear", onClick: this.handleClearClick }, /*#__PURE__*/React.createElement(SmCross, { size: 24 })); const isDropdown = inputProps.role === "combobox"; const RightIcon = isDropdown && SmChevronDown; return ( /*#__PURE__*/ // Although we use onMouseDown, this element don't need role=button // as it just prevents the input blur on click in the clear icon. // there's no functionality in keyboard navigation. // eslint-disable-next-line jsx-a11y/no-static-element-interactions React.createElement("div", { className: `input ${isDropdown ? "dropdown" : ""}`, ref: element => { this.inputRef = element && element.querySelector("input, textarea"); } }, prefix && /*#__PURE__*/React.createElement("div", { className: "prefix" }, prefix), LeftIcon && /*#__PURE__*/React.createElement("div", { className: "icon left" }, /*#__PURE__*/React.createElement(LeftIcon, { size: ICON_SIZE })), /*#__PURE__*/React.createElement(WrappedComponent, _extends({ className: `wrapped ${this.state.focused ? "focused" : ""}` }, inputProps, handlers, { [refName]: innerRef, value: this.state.value })), clearButton, RightIcon && /*#__PURE__*/React.createElement("div", { className: "icon right" }, /*#__PURE__*/React.createElement(SmChevronDown, { size: ICON_SIZE })), suffix && /*#__PURE__*/React.createElement("div", { className: "suffix" }, suffix)) ); }); this.state = { value: getValueFromProps(props), focused: props.focused }; } componentDidMount() { if (this.props.focused) { this.setInputFocus(); } } render() { const { value } = this.state; const { label, active, focused, onChange, onFocus, onBlur, invalid, input, meta, protected: covered, autoControlFocusedStyle, className, borderRadius, validationMessages, mb, small, ...wrappedComponentProps } = this.props; const wrapperProps = { protected: covered, active, focused: this.state.focused, invalid: invalid || (validationMessages === null || validationMessages === void 0 ? void 0 : validationMessages.length) > 0, filled: !!value || value === 0, disabled: wrappedComponentProps.disabled, type: wrappedComponentProps.type, small, className, borderRadius, mb }; return /*#__PURE__*/React.createElement(Wrapper, _extends({}, wrapperProps, { onClick: this.setInputFocus }), this.renderInput(wrappedComponentProps), label && /*#__PURE__*/React.createElement(Label, { className: "label", focused: this.state.focused || active, invalid: wrapperProps.invalid, protected: covered, disabled: wrapperProps.disabled }, label), validationMessages === null || validationMessages === void 0 ? void 0 : validationMessages.map(text => /*#__PURE__*/React.createElement(_StyledText, { className: "error-message-text", size: fontSizes.SMALL, type: fontTypes.NEGATIVE, key: `err-${text}` }, text))); } }, _defineProperty(_class, "displayName", wrapDisplayName(WrappedComponent, "formInput")), _defineProperty(_class, "propTypes", { ...WrappedComponent.propTypes, ...formInputProps }), _defineProperty(_class, "defaultProps", { type: "text", refName: "ref", autoControlFocusedStyle: true, focused: false, borderRadius: "4px", mb: "24px" }), _temp; }; export default formInput; var _StyledText = _styled(Text).withConfig({ displayName: "formInput___StyledText", componentId: "sc-181syp4-0" })(["margin-top:8px;order:3;"]);