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}
-
+
);
}