diff --git a/packages/component-faraday-ui/src/ValidatedFormField.js b/packages/component-faraday-ui/src/ValidatedFormField.js new file mode 100644 index 0000000000000000000000000000000000000000..833223f50d7997496caa61cd11780183691f8146 --- /dev/null +++ b/packages/component-faraday-ui/src/ValidatedFormField.js @@ -0,0 +1,110 @@ +import React, { Fragment } from 'react' +import { get } from 'lodash' +import { Field } from 'formik' +import PropTypes from 'prop-types' +import styled from 'styled-components' +import { th } from '@pubsweet/ui-toolkit' +import { + compose, + withProps, + withHandlers, + shouldUpdate, + setDisplayName, +} from 'recompose' + +const WrappedComponent = compose( + withHandlers({ + onChange: ({ field: { onChange, name }, form: { setFieldValue } }) => v => { + if (typeof v === 'object') { + onChange(v) + } else { + setFieldValue(name, v) + } + }, + }), + withProps(({ form: { errors, touched, submitCount }, name }) => ({ + hasError: (get(touched, name) || submitCount > 0) && get(errors, name), + })), + withProps(({ name, hasError, form: { errors } }) => ({ + error: hasError && get(errors, name), + validationStatus: hasError ? 'error' : 'default', + })), + shouldUpdate( + (prev, next) => + get(prev, 'field.value') !== get(next, 'field.value') || + get(prev, 'error') !== get(next, 'error'), + ), +)( + ({ + error, + onChange, + validationStatus, + component: Component, + field: { name, value, onBlur }, + form: { errors, setFieldValue, touched, values, submitCount }, + ...props + }) => ( + <Fragment> + <Component + name={name} + onBlur={onBlur} + onChange={onChange} + value={value} + {...props} + validationStatus={validationStatus} + /> + <MessageWrapper role="alert"> + {error && <ErrorMessage>{error}</ErrorMessage>} + </MessageWrapper> + </Fragment> + ), +) + +const ValidatedFormField = ({ name, component, validateFn, ...props }) => ( + <Field name={name} validate={validateFn}> + {fieldProps => ( + <WrappedComponent + component={component} + name={name} + {...props} + {...fieldProps} + /> + )} + </Field> +) + +ValidatedFormField.propTypes = { + component: PropTypes.oneOfType([ + PropTypes.node, + PropTypes.func, + PropTypes.string, + ]).isRequired, +} + +export default compose( + setDisplayName('ValidatedFormikField'), + withProps(({ validate = [] }) => ({ + validateFn: (value = '') => + validate.reduce((acc, fn) => acc || fn(value), ''), + })), + shouldUpdate(() => false), +)(ValidatedFormField) + +// #region styles +const MessageWrapper = styled.div` + font-family: ${th('fontInterface')}; + display: flex; +` + +const Message = styled.div` + &:not(:last-child) { + margin-bottom: ${th('gridUnit')}; + } + font-size: ${th('fontSizeBaseSmall')}; + line-height: ${th('lineHeightBaseSmall')}; +` + +const ErrorMessage = styled(Message)` + color: ${th('colorError')}; +` +// #endregion diff --git a/packages/component-faraday-ui/src/ValidatedMenuField.js b/packages/component-faraday-ui/src/ValidatedMenuField.js deleted file mode 100644 index 9221c1025500265c60facb2f0a70d497e8a3a012..0000000000000000000000000000000000000000 --- a/packages/component-faraday-ui/src/ValidatedMenuField.js +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' -import { Field } from 'formik' -import { Menu } from '@pubsweet/ui' - -const ValidatedMenuField = ({ options, name }) => ( - <Field name={name}> - {({ field, form }) => ( - <Menu - onChange={v => { - // field.onChange(v) - form.setFieldValue(field.name, v) - }} - options={options} - value={field.value} - /> - )} - </Field> -) - -export default ValidatedMenuField diff --git a/packages/component-faraday-ui/src/index.js b/packages/component-faraday-ui/src/index.js index 6d5f3b1be3fa8bc2fe83cf3c2bff21242bb75160..8ea795364924d459caccdb1225fea024c9237df4 100644 --- a/packages/component-faraday-ui/src/index.js +++ b/packages/component-faraday-ui/src/index.js @@ -51,7 +51,7 @@ export { default as EditorialReportCard } from './EditorialReportCard' export { default as ReviewerReportAuthor } from './ReviewerReportAuthor' export { default as PasswordValidation } from './PasswordValidation' export { default as MenuCountry } from './MenuCountry' -export { default as ValidatedMenuField } from './ValidatedMenuField' +export { default as ValidatedFormField } from './ValidatedFormField' export { SubmitRevision } from './submissionRevision' diff --git a/packages/component-user/app/components/AdminUserForm.js b/packages/component-user/app/components/AdminUserForm.js index 50a314f428bc0251880441d6a49f18d591d14f96..699883e650990c44caaa162eec660b6730966945 100644 --- a/packages/component-user/app/components/AdminUserForm.js +++ b/packages/component-user/app/components/AdminUserForm.js @@ -3,27 +3,22 @@ import { get } from 'lodash' import { Formik } from 'formik' import styled from 'styled-components' import { th } from '@pubsweet/ui-toolkit' -import { required } from 'xpub-validators' +import { H2, Menu, Button, Spinner, TextField } from '@pubsweet/ui' import { compose, setDisplayName, withHandlers, withProps } from 'recompose' -import { - H2, - Button, - Spinner, - TextField, - ValidatedFieldFormik, -} from '@pubsweet/ui' import { Row, Item, Text, Label, IconButton, + MenuCountry, RowOverrideAlert, ItemOverrideAlert, - ValidatedMenuField, + ValidatedFormField, withRoles, withFetching, withCountries, + validators, } from 'pubsweet-component-faraday-ui' // #region helpers @@ -86,16 +81,21 @@ const FormModal = ({ <Row alignItems="baseline" mb={1} mt={1}> <ItemOverrideAlert mr={1} vertical> <Label required>Email</Label> - <ValidatedFieldFormik + <ValidatedFormField component={TextField} inline name="email" - validate={[required]} + validate={[validators.emailValidator]} /> </ItemOverrideAlert> + <ItemOverrideAlert ml={1} vertical> <Label required>Role</Label> - <ValidatedMenuField name="role" options={roles} /> + <ValidatedFormField + component={Menu} + name="role" + options={roles} + /> </ItemOverrideAlert> </Row> )} @@ -103,15 +103,16 @@ const FormModal = ({ <Row mb={2}> <Item mr={1} vertical> <Label>First Name</Label> - <ValidatedFieldFormik + <ValidatedFormField component={TextField} inline name="firstName" /> </Item> + <Item ml={1} vertical> <Label>Last Name</Label> - <ValidatedFieldFormik + <ValidatedFormField component={TextField} inline name="lastName" @@ -122,11 +123,16 @@ const FormModal = ({ <RowOverrideAlert alignItems="center" mb={2}> <ItemOverrideAlert mr={1} vertical> <Label>Title</Label> - <ValidatedMenuField name="title" options={titles} /> + <ValidatedFormField + component={Menu} + name="title" + options={titles} + /> </ItemOverrideAlert> + <ItemOverrideAlert ml={1} vertical> <Label>Country</Label> - <ValidatedMenuField name="country" options={countries} /> + <ValidatedFormField component={MenuCountry} name="country" /> </ItemOverrideAlert> </RowOverrideAlert> @@ -134,12 +140,16 @@ const FormModal = ({ {edit && ( <ItemOverrideAlert mr={1} vertical> <Label required>Role</Label> - <ValidatedMenuField name="role" options={roles} /> + <ValidatedFormField + component={Menu} + name="role" + options={roles} + /> </ItemOverrideAlert> )} <Item ml={edit && 1} vertical> <Label>Affiliation</Label> - <ValidatedFieldFormik + <ValidatedFormField component={TextField} inline name="affiliation"