diff --git a/src/components/InputField/InputField.css b/src/components/InputField/InputField.css new file mode 100644 index 00000000..e1056930 --- /dev/null +++ b/src/components/InputField/InputField.css @@ -0,0 +1,42 @@ +@import '../../marketplace.css'; + +.root { + margin-top: calc(6 * var(--spacingUnit)); + + @media (--desktopViewport) { + margin-top: calc(6 * var(--spacingUnitDesktop)); + } +} + +.label { + margin-top: 0; +} + +.input { + border-color: var(--attentionColor); + + &:hover, + &:focus { + border-color: var(--matterColor); + } +} + +.inputSuccess { + border-color: var(--successColor); +} + +.inputError { + border-color: var(--failColor); +} + +.validationError { + composes: h5Font from '../../marketplace.css'; + + margin-top: calc(2 * var(--spacingUnit)); + color: var(--failColor); + overflow: hidden; + + @media (--desktopViewport) { + margin-top: calc(2 * var(--spacingUnitDesktop)); + } +} diff --git a/src/components/InputField/InputField.example.js b/src/components/InputField/InputField.example.js new file mode 100644 index 00000000..021f984a --- /dev/null +++ b/src/components/InputField/InputField.example.js @@ -0,0 +1,66 @@ +/* eslint-disable no-console */ +import React from 'react'; +import { Field, reduxForm, propTypes as formPropTypes } from 'redux-form'; +import { Button } from '../../components'; +import * as validators from '../../util/validators'; +import InputField from './InputField'; + +const FormComponent = props => { + const { handleSubmit, pristine, submitting } = props; + const required = validators.required('Required value missing'); + const buttonStyles = { marginTop: 16 }; + const submitDisabled = pristine || submitting; + return ( +
+ + + + + + + ); +}; + +FormComponent.propTypes = formPropTypes; + +const defaultFormName = 'Styleguide.InputField'; + +const Form = reduxForm({ + form: defaultFormName, +})(FormComponent); + +export const RequiredValueWithLabel = { + component: Form, + props: { + onSubmit: values => { + console.log('Submit values:', values); + }, + initialValues: { + input4: 'Lorem ipsum', + }, + }, + group: 'fields', +}; diff --git a/src/components/InputField/InputField.js b/src/components/InputField/InputField.js new file mode 100644 index 00000000..cb8b9da7 --- /dev/null +++ b/src/components/InputField/InputField.js @@ -0,0 +1,96 @@ +import React, { Component, PropTypes } from 'react'; +import classNames from 'classnames'; + +import css from './InputField.css'; + +class InputField extends Component { + componentWillUnmount() { + if (this.props.clearOnUnmount) { + this.props.input.onChange(''); + } + } + render() { + const { + rootClassName, + className, + labelRootClassName, + inputRootClassName, + errorRootClassName, + type, + label, + placeholder, + input, + meta, + } = this.props; + const inputProps = { ...input, type, placeholder }; + const { pristine, valid, invalid, touched, error } = meta; + + // Error message and input error styles are only shown if the + // field has been touched and the validation has failed. + const hasError = touched && invalid && error; + + // Input is market as succesful if it has been changed and + // validation has not failed. + const isFilledInAndValid = !pristine && touched && valid; + + const classes = classNames(rootClassName || css.root, className); + const labelClasses = labelRootClassName || css.label; + const inputClasses = classNames(inputRootClassName || css.input, { + [css.inputSuccess]: isFilledInAndValid, + [css.inputError]: hasError, + }); + const errorClasses = errorRootClassName || css.validationError; + + return ( +
+ {label ? : null} + + {hasError ?

{error}

: null} +
+ ); + } +} + +InputField.defaultProps = { + rootClassName: null, + className: null, + labelRootClassName: null, + inputRootClassName: null, + errorRootClassName: null, + clearOnUnmount: false, + label: null, + placeholder: null, +}; + +const { string, shape, bool, func } = PropTypes; + +InputField.propTypes = { + // Allow passing in classes to subcomponents + rootClassName: string, + className: string, + labelRootClassName: string, + inputRootClassName: string, + errorRootClassName: string, + + clearOnUnmount: bool, + + // Extra props passed to the underlying input component + type: string.isRequired, + label: string, + placeholder: string, + + // Objects created by redux-form + input: shape({ + name: string.isRequired, + onChange: func.isRequired, + }).isRequired, + meta: shape({ + pristine: bool.isRequired, + valid: bool.isRequired, + invalid: bool.isRequired, + touched: bool.isRequired, + error: string, + }).isRequired, +}; + +export default InputField; diff --git a/src/components/ValidationError/ValidationError.js b/src/components/ValidationError/ValidationError.js index d90ab6fc..87f08664 100644 --- a/src/components/ValidationError/ValidationError.js +++ b/src/components/ValidationError/ValidationError.js @@ -10,17 +10,18 @@ import css from './ValidationError.css'; * shown. */ const ValidationError = props => { - const { className, fieldMeta } = props; + const { rootClassName, className, fieldMeta } = props; const { touched, error } = fieldMeta; - const classes = classNames(css.root, className); + const classes = classNames(rootClassName || css.root, className); return touched && error ?
{error}
: null; }; -ValidationError.defaultProps = { className: '' }; +ValidationError.defaultProps = { rootClassName: null, className: null }; const { shape, bool, string } = PropTypes; ValidationError.propTypes = { + rootClassName: string, className: string, fieldMeta: shape({ touched: bool.isRequired, diff --git a/src/components/index.js b/src/components/index.js index ae6c69cc..6c91625b 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -17,6 +17,7 @@ import ExternalLink from './ExternalLink/ExternalLink'; import FilterPanel from './FilterPanel/FilterPanel'; import HeroSection from './HeroSection/HeroSection'; import Input from './Input/Input'; +import InputField from './InputField/InputField'; import LabeledField from './LabeledField/LabeledField'; import ListingCard from './ListingCard/ListingCard'; import ListingCardSmall from './ListingCardSmall/ListingCardSmall'; @@ -69,6 +70,7 @@ export { HeroSection, InlineButton, Input, + InputField, LabeledField, ListingCard, ListingCardSmall, diff --git a/src/containers/EditListingPhotosForm/EditListingPhotosForm.css b/src/containers/EditListingPhotosForm/EditListingPhotosForm.css index 67a3be9d..42b01bad 100644 --- a/src/containers/EditListingPhotosForm/EditListingPhotosForm.css +++ b/src/containers/EditListingPhotosForm/EditListingPhotosForm.css @@ -55,9 +55,6 @@ width: 100%; clear: both; } -.imageRequiredError { - color: red; -} .submitButton { margin-top: 1rem; diff --git a/src/containers/EditListingPhotosForm/EditListingPhotosForm.js b/src/containers/EditListingPhotosForm/EditListingPhotosForm.js index 2f03a4ec..4143fd19 100644 --- a/src/containers/EditListingPhotosForm/EditListingPhotosForm.js +++ b/src/containers/EditListingPhotosForm/EditListingPhotosForm.js @@ -100,7 +100,7 @@ export class EditListingPhotosFormComponent extends Component { return (
- +
); }} diff --git a/src/containers/LoginForm/LoginForm.js b/src/containers/LoginForm/LoginForm.js index dac1e837..2e1d8a6f 100644 --- a/src/containers/LoginForm/LoginForm.js +++ b/src/containers/LoginForm/LoginForm.js @@ -11,9 +11,7 @@ import css from './LoginForm.css'; class LoginFormComponent extends Component { constructor(props) { super(props); - this.EnhancedInput = enhancedField('input', { - errorClassName: css.error, - }); + this.EnhancedInput = enhancedField('input'); } render() { const { handleSubmit, pristine, submitting, inProgress, intl } = this.props; diff --git a/src/containers/PayoutDetailsForm/PayoutDetailsForm.js b/src/containers/PayoutDetailsForm/PayoutDetailsForm.js index 1c81067c..52d20d70 100644 --- a/src/containers/PayoutDetailsForm/PayoutDetailsForm.js +++ b/src/containers/PayoutDetailsForm/PayoutDetailsForm.js @@ -40,15 +40,9 @@ CountriesSelect.propTypes = { class PayoutDetailsFormComponent extends Component { constructor(props) { super(props); - this.EnhancedInput = enhancedField('input', { - errorClassName: css.error, - }); - this.EnhancedCountriesDropdown = enhancedField(CountriesSelect, { - errorClassName: css.error, - }); - this.EnhancedBirthdayInput = enhancedField(BirthdayInput, { - errorClassName: css.error, - }); + this.EnhancedInput = enhancedField('input'); + this.EnhancedCountriesDropdown = enhancedField(CountriesSelect); + this.EnhancedBirthdayInput = enhancedField(BirthdayInput); } render() { const { diff --git a/src/containers/SignupForm/SignupForm.js b/src/containers/SignupForm/SignupForm.js index a2ae60b5..0e5d8133 100644 --- a/src/containers/SignupForm/SignupForm.js +++ b/src/containers/SignupForm/SignupForm.js @@ -11,16 +11,12 @@ import css from './SignupForm.css'; class SignupFormComponent extends Component { constructor(props) { super(props); - this.EnhancedInput = enhancedField('input', { - errorClassName: css.error, - }); + this.EnhancedInput = enhancedField('input'); this.EnhancedFirstNameInput = enhancedField('input', { rootClassName: css.firstNameRoot, - errorClassName: css.error, }); this.EnhancedLastNameInput = enhancedField('input', { rootClassName: css.lastNameRoot, - errorClassName: css.error, }); } render() { diff --git a/src/examples.js b/src/examples.js index 0ca09298..90349e69 100644 --- a/src/examples.js +++ b/src/examples.js @@ -5,6 +5,7 @@ import * as BookingInfo from './components/BookingInfo/BookingInfo.example'; import * as CurrencyInput from './components/CurrencyInput/CurrencyInput.example'; import * as DateInput from './components/DateInput/DateInput.example'; import * as EditListingWizard from './components/EditListingWizard/EditListingWizard.example'; +import * as InputField from './components/InputField/InputField.example'; import * as ListingCard from './components/ListingCard/ListingCard.example'; import * as Map from './components/Map/Map.example'; import * as Menu from './components/Menu/Menu.example'; @@ -58,6 +59,7 @@ export { EditListingPhotosForm, EditListingPricingForm, EditListingWizard, + InputField, ListingCard, LocationAutocompleteInput, LoginForm, diff --git a/src/util/forms.js b/src/util/forms.js index 8f05494b..c8c7121c 100644 --- a/src/util/forms.js +++ b/src/util/forms.js @@ -15,7 +15,7 @@ import { Input, ValidationError } from '../components'; * label and possible errors */ export const enhancedField = (Comp, options = {}) => { - const { rootClassName = '', labelClassName = '', errorClassName = '' } = options; + const { rootClassName = '', labelClassName = '' } = options; class EnhancedField extends Component { componentWillUnmount() { @@ -46,7 +46,7 @@ export const enhancedField = (Comp, options = {}) => {
{labelInfo} {component} - +
); }