diff --git a/docs/3.x.x/en/plugin-development/ui-components.md b/docs/3.x.x/en/plugin-development/ui-components.md index 6dd8a9b14a..9c4d0746f6 100644 --- a/docs/3.x.x/en/plugin-development/ui-components.md +++ b/docs/3.x.x/en/plugin-development/ui-components.md @@ -403,6 +403,12 @@ export default Foo; *** +## InputEmail + +Please refer to the [InputText documentation](#InputText); + +*** + ## InputNumber InputNumber component. @@ -487,6 +493,12 @@ Input type: 'toggle' component *** +## InputEmailWithErrors + +Please refer to the [InputTextWithErrors](#InputTextWithErrors) documentation. + +*** + ## InputNumberWithErrors Please refer to the [InputTextWithErrors](#InputTextWithErrors) documentation. @@ -518,6 +530,7 @@ Component integrates Label, InputText, InputDescription and InputErrors. | onChange | func | yes | Handler to modify the input's value | | onFocus | func | no | Function executed when the user enters the input | | name | string | yes | The name of the input | +| noErrorsDescription | bool | no | Remove the input's errors description | | placeholder | string | no | Input's placeholder, works with i18n | | style | object | no | Overrides the container style | | tabIndex | string | no | Input's tabIndex | @@ -618,8 +631,9 @@ Component integrates Label, InputToggle, InputDescription and InputErrors. | label | string or func or object | no sets the input's label | | labelClassName | string | no | Overrides the Label's className | | labelStyle | object | no | Overrides the Label's style | -| onChange | func | yes | Handler to modify the input's value | | name | string | yes | The name of the input | +| noErrorsDescription | bool | no | Remove the input's errors description | +| onChange | func | yes | Handler to modify the input's value | | style | object | no | Overrides the container style | | tabIndex | string | no | Input's tabIndex | | validations | object | no | Input's validations | @@ -656,6 +670,7 @@ Component integrates Label, InputTextArea, InputDescription and InputErrors. | onChange | func | yes | Handler to modify the input's value | | onFocus | func | no | Function executed when the user enters the input | | name | string | yes | The name of the input | +| noErrorsDescription | bool | no | Remove the input's errors description | | placeholder | string | no | Input's placeholder, works with i18n | | style | object | no | Overrides the container style | | tabIndex | string | no | Input's tabIndex | diff --git a/packages/strapi-helper-plugin/lib/src/components/InputEmail/index.js b/packages/strapi-helper-plugin/lib/src/components/InputEmail/index.js new file mode 100644 index 0000000000..ce157a0aab --- /dev/null +++ b/packages/strapi-helper-plugin/lib/src/components/InputEmail/index.js @@ -0,0 +1,108 @@ +/** + * + * InputEmail + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { isEmpty } from 'lodash'; +import { FormattedMessage } from 'react-intl'; +import cn from 'classnames'; + +import styles from './styles.scss'; + +class InputEmail extends React.Component { + state = { isFocused: false }; + + handleBlur = (e) => { + this.setState({ isFocused: !this.state.isFocused }); + this.props.onBlur(e); + } + + handleFocus = (e) => { + console.log('ok'); + this.setState({ isFocused: !this.state.isFocused }); + this.props.onFocus(e); + } + + render() { + const { + autoFocus, + className, + deactivateErrorHighlight, + disabled, + error, + name, + onChange, + placeholder, + style, + tabIndex, + value, + } = this.props; + + return ( +
+ + + {(message) => ( + + )} + +
+ ); + } +} + +InputEmail.defaultProps = { + autoFocus: false, + className: '', + deactivateErrorHighlight: false, + disabled: false, + error: false, + onBlur: () => {}, + onFocus: () => {}, + placeholder: 'app.utils.placeholder.defaultMessage', + style: {}, + tabIndex: '0', +}; + +InputEmail.propTypes = { + autoFocus: PropTypes.bool, + className: PropTypes.string, + deactivateErrorHighlight: PropTypes.bool, + disabled: PropTypes.bool, + error: PropTypes.bool, + onBlur: PropTypes.func, + onChange: PropTypes.func.isRequired, + onFocus: PropTypes.func, + name: PropTypes.string.isRequired, + placeholder: PropTypes.string, + style: PropTypes.object, + tabIndex: PropTypes.string, + value: PropTypes.string.isRequired, +}; + +export default InputEmail; diff --git a/packages/strapi-helper-plugin/lib/src/components/InputEmail/styles.scss b/packages/strapi-helper-plugin/lib/src/components/InputEmail/styles.scss new file mode 100644 index 0000000000..2e2c98315e --- /dev/null +++ b/packages/strapi-helper-plugin/lib/src/components/InputEmail/styles.scss @@ -0,0 +1,71 @@ +.addonEmail { + width: 3.4rem; + height: 3.4rem; + margin-top: .9rem; + padding-left: 0.9rem; + background-color: rgba(16, 22, 34, 0.02); + border: 1px solid #E3E9F3; + border-radius: 0.25rem; + color: rgba(16, 22, 34, 0.5); + line-height: 3.2rem; + font-size: 1.3rem; + font-family: 'Lato'; + font-weight: 600!important; + text-transform: capitalize; + -moz-appearance: none; + -webkit-appearance: none; + -webkit-font-smoothing: antialiased; + &:after { + content: '@'; + display: inline-table; + color: #B3B5B9; + font-size: 16px; + font-weight: 900; + font-family: Lato; + } + + & + input { + border-left: 0px !important; + } +} + +.inputEmail { + min-width: 200px; + margin-bottom: 1rem; + font-size: 1.3rem; + + > input { + height: 3.4rem; + margin-top: .9rem; + padding-left: 1rem; + background-size: 0 !important; + border: 1px solid #E3E9F3; + border-radius: 0.25rem; + line-height: 3.4rem; + font-size: 1.3rem; + font-family: 'Lato' !important; + box-shadow: 0px 1px 1px rgba(104, 118, 142, 0.05); + &:focus { + border-color: #78caff; + } + } + + & + span { + border-color: #E3E9F3; + } +} + +.errorAddon { + border: 1px solid #ff203c!important; + border-right: none!important; +} + +.addonFocus { + border-color: #78caff; + border-right: 0; +} + +.invalidEmail { + border-color: #ff203c !important; + border-left: 0; +} diff --git a/packages/strapi-helper-plugin/lib/src/components/InputEmailWithErrors/index.js b/packages/strapi-helper-plugin/lib/src/components/InputEmailWithErrors/index.js new file mode 100644 index 0000000000..818b2a3908 --- /dev/null +++ b/packages/strapi-helper-plugin/lib/src/components/InputEmailWithErrors/index.js @@ -0,0 +1,257 @@ +/** + * + * InputEmailWithErrors + * + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { includes, isEmpty, isFunction, mapKeys, reject } from 'lodash'; +import cn from 'classnames'; + +// Design +import Label from 'components/Label'; +import InputDescription from 'components/InputDescription'; +import InputErrors from 'components/InputErrors'; +import InputEmail from 'components/InputEmail'; + +import styles from './styles.scss'; + +class InputEmailWithErrors extends React.Component { // eslint-disable-line react/prefer-stateless-function + state = { errors: [], hasInitialValue: false }; + + componentDidMount() { + const { value, errors } = this.props; + + // Prevent the input from displaying an error when the user enters and leaves without filling it + if (value && !isEmpty(value)) { + this.setState({ hasInitialValue: true }); + } + + // Display input error if it already has some + if (!isEmpty(errors)) { + this.setState({ errors }); + } + } + + componentWillReceiveProps(nextProps) { + // Check if errors have been updated during validations + if (nextProps.didCheckErrors !== this.props.didCheckErrors) { + // Remove from the state the errors that have already been set + const errors = isEmpty(nextProps.errors) ? [] : nextProps.errors; + this.setState({ errors }); + } + } + + /** + * Set the errors depending on the validations given to the input + * @param {Object} target + */ + handleBlur = ({ target }) => { + // Prevent from displaying error if the input is initially isEmpty + if (!isEmpty(target.value) || this.state.hasInitialValue) { + const errors = this.validate(target.value); + this.setState({ errors, hasInitialValue: true }); + } + } + + render() { + const { + autoFocus, + deactivateErrorHighlight, + disabled, + errorsClassName, + errorsStyle, + inputClassName, + inputDescription, + inputDescriptionClassName, + inputDescriptionStyle, + inputStyle, + label, + labelClassName, + labelStyle, + name, + noErrorsDescription, + onBlur, + onChange, + onFocus, + placeholder, + style, + tabIndex, + value, + } = this.props; + const handleBlur = isFunction(onBlur) ? onBlur : this.handleBlur; + + let spacer = !isEmpty(inputDescription) ?
:
; + + if (!noErrorsDescription && !isEmpty(this.state.errors)) { + spacer =
; + } + + return ( +
+
+ ); + } + + validate = (value) => { + const emailRegex = new RegExp(/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/); + const requiredError = { id: 'components.Input.error.validation.required' }; + let errors = []; + + mapKeys(this.props.validations, (validationValue, validationKey) => { + switch (validationKey) { + case 'maxLength': { + if (value.length > validationValue) { + errors.push({ id: 'components.Input.error.validation.maxLength' }); + } + break; + } + case 'minLength': { + if (value.length < validationValue) { + errors.push({ id: 'components.Input.error.validation.minLength' }); + } + break; + } + case 'required': { + if (value.length === 0) { + errors.push({ id: 'components.Input.error.validation.required' }); + } + break; + } + case 'regex': { + if (!new RegExp(validationValue).test(value)) { + errors.push({ id: 'components.Input.error.validation.regex' }); + } + break; + } + default: + errors = []; + } + }); + + if (!emailRegex.test(value)) { + errors.push({ id: 'components.Input.error.validation.email' }); + } + + if (includes(errors, requiredError)) { + errors = reject(errors, (error) => error !== requiredError); + } + + return errors; + } +} + +InputEmailWithErrors.defaultProps = { + autoFocus: false, + className: '', + customBootstrapClass: 'col-md-6', + deactivateErrorHighlight: false, + didCheckErrors: false, + disabled: false, + onBlur: false, + onFocus: () => {}, + errors: [], + errorsClassName: '', + errorsStyle: {}, + inputClassName: '', + inputDescription: '', + inputDescriptionClassName: '', + inputDescriptionStyle: {}, + inputStyle: {}, + label: '', + labelClassName: '', + labelStyle: {}, + noErrorsDescription: false, + placeholder: 'app.utils.placeholder.defaultMessage', + style: {}, + tabIndex: '0', + validations: {}, +}; + +InputEmailWithErrors.propTypes = { + autoFocus: PropTypes.bool, + className: PropTypes.string, + customBootstrapClass: PropTypes.string, + deactivateErrorHighlight: PropTypes.bool, + didCheckErrors: PropTypes.bool, + disabled: PropTypes.bool, + errors: PropTypes.array, + errorsClassName: PropTypes.string, + errorsStyle: PropTypes.object, + inputClassName: PropTypes.string, + inputDescription: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.func, + PropTypes.shape({ + id: PropTypes.string, + params: PropTypes.object, + }), + ]), + inputDescriptionClassName: PropTypes.string, + inputDescriptionStyle: PropTypes.object, + inputStyle: PropTypes.object, + label: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.func, + PropTypes.shape({ + id: PropTypes.string, + params: PropTypes.object, + }), + ]), + labelClassName: PropTypes.string, + labelStyle: PropTypes.object, + name: PropTypes.string.isRequired, + noErrorsDescription: PropTypes.bool, + onBlur: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.func, + ]), + onChange: PropTypes.func.isRequired, + onFocus: PropTypes.func, + placeholder: PropTypes.string, + style: PropTypes.object, + tabIndex: PropTypes.string, + validations: PropTypes.object, + value: PropTypes.string.isRequired, +}; + +export default InputEmailWithErrors; diff --git a/packages/strapi-helper-plugin/lib/src/components/InputEmailWithErrors/styles.scss b/packages/strapi-helper-plugin/lib/src/components/InputEmailWithErrors/styles.scss new file mode 100644 index 0000000000..f94650a966 --- /dev/null +++ b/packages/strapi-helper-plugin/lib/src/components/InputEmailWithErrors/styles.scss @@ -0,0 +1,9 @@ +.container { + min-width: 200px; + margin-bottom: 1.5rem; + font-size: 1.3rem; +} + +.spacer { + height: .5rem; +} diff --git a/packages/strapi-helper-plugin/lib/src/components/InputNumberWithErrors/index.js b/packages/strapi-helper-plugin/lib/src/components/InputNumberWithErrors/index.js index 2fc9c42280..3abc7d999d 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputNumberWithErrors/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/InputNumberWithErrors/index.js @@ -52,17 +52,23 @@ class InputNumberWithErrors extends React.Component { // eslint-disable-line rea render() { const { autoFocus, + className, + customBootstrapClass, deactivateErrorHighlight, disabled, errorsClassName, errorsStyle, inputClassName, + inputDescription, inputDescriptionClassName, inputDescriptionStyle, inputStyle, + label, labelClassName, labelStyle, name, + noErrorsDescription, + onBlur, onChange, onFocus, placeholder, @@ -70,20 +76,26 @@ class InputNumberWithErrors extends React.Component { // eslint-disable-line rea tabIndex, value, } = this.props; - const handleBlur = isFunction(this.props.onBlur) ? this.props.onBlur : this.handleBlur; + const handleBlur = isFunction(onBlur) ? onBlur : this.handleBlur; + + let spacer = !isEmpty(inputDescription) ?
:
; + + if (!noErrorsDescription && !isEmpty(this.state.errors)) { + spacer =
; + } return (
); } @@ -178,6 +191,7 @@ InputNumberWithErrors.defaultProps = { label: '', labelClassName: '', labelStyle: {}, + noErrorsDescription: false, placeholder: 'app.utils.placeholder.defaultMessage', style: {}, tabIndex: '0', @@ -217,6 +231,7 @@ InputNumberWithErrors.propTypes = { labelClassName: PropTypes.string, labelStyle: PropTypes.object, name: PropTypes.string.isRequired, + noErrorsDescription: PropTypes.bool, onBlur: PropTypes.oneOfType([ PropTypes.bool, PropTypes.func, diff --git a/packages/strapi-helper-plugin/lib/src/components/InputNumberWithErrors/styles.scss b/packages/strapi-helper-plugin/lib/src/components/InputNumberWithErrors/styles.scss index 5d8578e7d5..f94650a966 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputNumberWithErrors/styles.scss +++ b/packages/strapi-helper-plugin/lib/src/components/InputNumberWithErrors/styles.scss @@ -3,3 +3,7 @@ margin-bottom: 1.5rem; font-size: 1.3rem; } + +.spacer { + height: .5rem; +} diff --git a/packages/strapi-helper-plugin/lib/src/components/InputTextAreaWithErrors/index.js b/packages/strapi-helper-plugin/lib/src/components/InputTextAreaWithErrors/index.js index 99ab883749..c3461c6038 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputTextAreaWithErrors/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/InputTextAreaWithErrors/index.js @@ -52,17 +52,23 @@ class InputTextAreaWithErrors extends React.Component { // eslint-disable-line r render() { const { autoFocus, + className, + customBootstrapClass, deactivateErrorHighlight, disabled, errorsClassName, errorsStyle, inputClassName, + inputDescription, inputDescriptionClassName, inputDescriptionStyle, inputStyle, + label, labelClassName, labelStyle, name, + noErrorsDescription, + onBlur, onChange, onFocus, placeholder, @@ -70,23 +76,29 @@ class InputTextAreaWithErrors extends React.Component { // eslint-disable-line r tabIndex, value, } = this.props; - const handleBlur = isFunction(this.props.onBlur) ? this.props.onBlur : this.handleBlur; + const handleBlur = isFunction(onBlur) ? onBlur : this.handleBlur; + + let spacer = !isEmpty(inputDescription) ?
:
; + + if (!noErrorsDescription && !isEmpty(this.state.errors)) { + spacer =
; + } return (
); } @@ -165,8 +178,6 @@ InputTextAreaWithErrors.defaultProps = { deactivateErrorHighlight: false, didCheckErrors: false, disabled: false, - onBlur: false, - onFocus: () => {}, errors: [], errorsClassName: '', errorsStyle: {}, @@ -178,6 +189,9 @@ InputTextAreaWithErrors.defaultProps = { label: '', labelClassName: '', labelStyle: {}, + noErrorsDescription: false, + onBlur: false, + onFocus: () => {}, placeholder: 'app.utils.placeholder.defaultMessage', style: {}, tabIndex: '0', @@ -217,6 +231,7 @@ InputTextAreaWithErrors.propTypes = { labelClassName: PropTypes.string, labelStyle: PropTypes.object, name: PropTypes.string.isRequired, + noErrorsDescription: PropTypes.bool, onBlur: PropTypes.oneOfType([ PropTypes.bool, PropTypes.func, diff --git a/packages/strapi-helper-plugin/lib/src/components/InputTextWithErrors/index.js b/packages/strapi-helper-plugin/lib/src/components/InputTextWithErrors/index.js index e9a5a7bb10..0724d3d313 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputTextWithErrors/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/InputTextWithErrors/index.js @@ -52,17 +52,23 @@ class InputTextWithErrors extends React.Component { // eslint-disable-line react render() { const { autoFocus, + className, + customBootstrapClass, deactivateErrorHighlight, disabled, errorsClassName, errorsStyle, inputClassName, + inputDescription, inputDescriptionClassName, inputDescriptionStyle, inputStyle, + label, labelClassName, labelStyle, name, + noErrorsDescription, + onBlur, onChange, onFocus, placeholder, @@ -70,20 +76,26 @@ class InputTextWithErrors extends React.Component { // eslint-disable-line react tabIndex, value, } = this.props; - const handleBlur = isFunction(this.props.onBlur) ? this.props.onBlur : this.handleBlur; + const handleBlur = isFunction(onBlur) ? onBlur : this.handleBlur; + + let spacer = !isEmpty(inputDescription) ?
:
; + + if (!noErrorsDescription && !isEmpty(this.state.errors)) { + spacer =
; + } return (
); } @@ -178,6 +191,7 @@ InputTextWithErrors.defaultProps = { label: '', labelClassName: '', labelStyle: {}, + noErrorsDescription: false, placeholder: 'app.utils.placeholder.defaultMessage', style: {}, tabIndex: '0', @@ -217,6 +231,7 @@ InputTextWithErrors.propTypes = { labelClassName: PropTypes.string, labelStyle: PropTypes.object, name: PropTypes.string.isRequired, + noErrorsDescription: PropTypes.bool, onBlur: PropTypes.oneOfType([ PropTypes.bool, PropTypes.func, diff --git a/packages/strapi-helper-plugin/lib/src/components/InputTextWithErrors/styles.scss b/packages/strapi-helper-plugin/lib/src/components/InputTextWithErrors/styles.scss index 5d8578e7d5..f94650a966 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputTextWithErrors/styles.scss +++ b/packages/strapi-helper-plugin/lib/src/components/InputTextWithErrors/styles.scss @@ -3,3 +3,7 @@ margin-bottom: 1.5rem; font-size: 1.3rem; } + +.spacer { + height: .5rem; +} diff --git a/packages/strapi-helper-plugin/lib/src/components/InputToggleWithErrors/index.js b/packages/strapi-helper-plugin/lib/src/components/InputToggleWithErrors/index.js index 3f5d437ba8..ef05c133ee 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputToggleWithErrors/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/InputToggleWithErrors/index.js @@ -54,12 +54,19 @@ class InputToggleWithErrors extends React.Component { labelClassName, labelStyle, name, + noErrorsDescription, onChange, style, tabIndex, value, } = this.props; + let spacer = !isEmpty(inputDescription) ?
:
; + + if (!noErrorsDescription && !isEmpty(this.state.errors)) { + spacer =
; + } + return (
+ {spacer}
); } @@ -121,6 +129,7 @@ InputToggleWithErrors.defaultProps = { label: '', labelClassName: '', labelStyle: {}, + noErrorsDescription: false, style: {}, tabIndex: '0', value: true, @@ -161,6 +170,7 @@ InputToggleWithErrors.propTypes = { labelClassName: PropTypes.string, labelStyle: PropTypes.object, name: PropTypes.string.isRequired, + noErrorsDescription: PropTypes.bool, onChange: PropTypes.func.isRequired, style: PropTypes.object, tabIndex: PropTypes.string, diff --git a/packages/strapi-helper-plugin/lib/src/components/InputToggleWithErrors/styles.scss b/packages/strapi-helper-plugin/lib/src/components/InputToggleWithErrors/styles.scss index 0d108c48ce..da7136b777 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputToggleWithErrors/styles.scss +++ b/packages/strapi-helper-plugin/lib/src/components/InputToggleWithErrors/styles.scss @@ -7,3 +7,7 @@ margin-bottom: 0 !important; font-weight: 500 !important; } + +.spacer { + height: .5rem; +} diff --git a/packages/strapi-helper-plugin/lib/src/components/InputsIndex/index.js b/packages/strapi-helper-plugin/lib/src/components/InputsIndex/index.js index 45b69deb8e..d35f8fb451 100644 --- a/packages/strapi-helper-plugin/lib/src/components/InputsIndex/index.js +++ b/packages/strapi-helper-plugin/lib/src/components/InputsIndex/index.js @@ -7,6 +7,7 @@ import React from 'react'; import PropTypes from 'prop-types'; // Design +import InputEmailWithErrors from 'components/InputEmailWithErrors'; import InputNumberWithErrors from 'components/InputNumberWithErrors'; import InputTextAreaWithErrors from 'components/InputTextAreaWithErrors'; import InputTextWithErrors from 'components/InputTextWithErrors'; @@ -15,6 +16,7 @@ import InputToggleWithErrors from 'components/InputToggleWithErrors'; const DefaultInputError = ({ type }) =>
Your input type: {type} does not exist
const inputs = { + email: InputEmailWithErrors, number: InputNumberWithErrors, string: InputTextWithErrors, text: InputTextWithErrors, @@ -34,6 +36,7 @@ InputsIndex.propTypes = { export default InputsIndex; export { + InputEmailWithErrors, InputNumberWithErrors, InputTextWithErrors, InputTextAreaWithErrors,