diff --git a/package.json b/package.json
index cb161307..38f071d5 100644
--- a/package.json
+++ b/package.json
@@ -12,12 +12,15 @@
"classnames": "^2.2.5",
"compression": "^1.7.2",
"cookie-parser": "^1.4.3",
+ "core-js": "^2.5.0",
"decimal.js": "7.2.4",
"dotenv": "4.0.0",
"dotenv-expand": "4.0.1",
"express": "^4.16.3",
"express-enforces-ssl": "^1.1.0",
"express-sitemap": "^1.8.0",
+ "final-form": "^4.4.0",
+ "final-form-arrays": "^1.0.4",
"helmet": "^3.12.0",
"lodash": "^4.17.5",
"moment": "^2.20.1",
@@ -26,21 +29,23 @@
"path-to-regexp": "^2.2.0",
"prop-types": "^15.6.1",
"query-string": "^5.1.1",
+ "raf": "3.4.0",
"raven": "^2.4.2",
"raven-js": "^3.24.0",
- "react": "^15.6.2",
- "react-addons-shallow-compare": "^15.6.2",
+ "react": "^16.3.1",
"react-dates": "^16.0.0",
- "react-dom": "^15.6.2",
+ "react-dom": "^16.3.1",
+ "react-final-form": "^3.1.5",
+ "react-final-form-arrays": "^1.0.4",
"react-google-maps": "^9.4.5",
"react-helmet": "^5.2.0",
"react-intl": "^2.4.0",
"react-moment-proptypes": "^1.5.0",
- "react-redux": "^5.0.6",
+ "react-redux": "^5.0.7",
"react-router-dom": "^4.2.2",
"react-sortable-hoc": "^0.6.8",
"redux": "^3.7.2",
- "redux-form": "^6.8.0",
+ "redux-form": "^7.3.0",
"redux-thunk": "^2.2.0",
"sanitize.css": "^5.0.0",
"sharetribe-scripts": "1.1.2",
@@ -51,14 +56,12 @@
},
"devDependencies": {
"enzyme": "^3.3.0",
- "enzyme-adapter-react-15": "^1.0.5",
+ "enzyme-adapter-react-16": "^1.1.1",
"enzyme-to-json": "^3.3.3",
"nodemon": "^1.17.2",
"nsp": "^3.2.1",
"nsp-preprocessor-yarn": "^1.0.1",
- "prettier": "^1.11.1",
- "react-addons-test-utils": "^15.6.2",
- "react-test-renderer": "^15.6.2"
+ "prettier": "^1.11.1"
},
"scripts": {
"clean": "rm -rf build/*",
diff --git a/src/components/EditListingFeaturesPanel/EditListingFeaturesPanel.js b/src/components/EditListingFeaturesPanel/EditListingFeaturesPanel.js
index ed743977..d912fb1e 100644
--- a/src/components/EditListingFeaturesPanel/EditListingFeaturesPanel.js
+++ b/src/components/EditListingFeaturesPanel/EditListingFeaturesPanel.js
@@ -2,7 +2,6 @@ import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { FormattedMessage } from 'react-intl';
-import { toPairs } from 'lodash';
import { ensureListing } from '../../util/data';
import { EditListingFeaturesForm } from '../../containers';
@@ -38,15 +37,8 @@ const EditListingFeaturesPanel = props => {
);
- const currentFeaturesArray = publicData && publicData.amenities;
- const currentFeatures =
- currentFeaturesArray &&
- currentFeaturesArray.reduce((map, key) => {
- map[key] = true;
- return map;
- }, {});
-
- const initialValues = { [FEATURES_NAME]: currentFeatures };
+ const amenities = publicData && publicData.amenities;
+ const initialValues = { amenities };
return (
@@ -56,9 +48,7 @@ const EditListingFeaturesPanel = props => {
name={FEATURES_NAME}
initialValues={initialValues}
onSubmit={values => {
- const entries = values[FEATURES_NAME] ? toPairs(values[FEATURES_NAME]) : [];
-
- const amenities = entries.filter(entry => entry[1] === true).map(entry => entry[0]);
+ const { amenities = [] } = values;
const updatedValues = {
publicData: { amenities },
diff --git a/src/components/EditListingPoliciesPanel/EditListingPoliciesPanel.js b/src/components/EditListingPoliciesPanel/EditListingPoliciesPanel.js
index e3808494..4444b7db 100644
--- a/src/components/EditListingPoliciesPanel/EditListingPoliciesPanel.js
+++ b/src/components/EditListingPoliciesPanel/EditListingPoliciesPanel.js
@@ -40,10 +40,12 @@ const EditListingPoliciesPanel = props => {
{
+ const { rules = '' } = values;
const updateValues = {
publicData: {
- ...values,
+ rules,
},
};
onSubmit(updateValues);
diff --git a/src/components/FieldBirthdayInput/FieldBirthdayInput.example.js b/src/components/FieldBirthdayInput/FieldBirthdayInput.example.js
index 1fdc217f..f413fc8e 100644
--- a/src/components/FieldBirthdayInput/FieldBirthdayInput.example.js
+++ b/src/components/FieldBirthdayInput/FieldBirthdayInput.example.js
@@ -1,37 +1,51 @@
/* eslint-disable no-console */
import React from 'react';
-import { reduxForm } from 'redux-form';
+import { Form as FinalForm, FormSpy } from 'react-final-form';
import * as validators from '../../util/validators';
import FieldBirthdayInput from './FieldBirthdayInput';
-const formName = 'Styleguide.BirthdayInput.Form';
+const FormComponent = props => (
+ {
+ const { handleSubmit, onChange, values } = fieldRenderProps;
+ const required = validators.required('A valid date is required');
+ const minAge = 18;
+ const minAgeRequired = validators.ageAtLeast(`Age should be at least ${minAge}`, minAge);
-const FormComponent = () => {
- const required = validators.required('A valid date is required');
- const minAge = 18;
- const minAgeRequired = validators.ageAtLeast(`Age should be at least ${minAge}`, minAge);
- return (
-
- );
-};
-
-const Form = reduxForm({
- form: formName,
-})(FormComponent);
+ return (
+
+ );
+ }}
+ />
+);
export const Empty = {
- component: Form,
+ component: FormComponent,
props: {
- onChange: ({ birthday }) => {
- console.log('birthday changed to:', birthday ? birthday.toUTCString() : birthday);
+ onChange: formState => {
+ const birthday = formState.values.birthday;
+ if (birthday) {
+ console.log('birthday changed to:', birthday.toUTCString());
+ }
+ },
+ onSubmit: values => {
+ console.log('BirthdayInput.Form submitted values:', values);
},
},
group: 'custom inputs',
diff --git a/src/components/FieldBirthdayInput/FieldBirthdayInput.js b/src/components/FieldBirthdayInput/FieldBirthdayInput.js
index 39e8519a..af978655 100644
--- a/src/components/FieldBirthdayInput/FieldBirthdayInput.js
+++ b/src/components/FieldBirthdayInput/FieldBirthdayInput.js
@@ -1,6 +1,10 @@
+/**
+ * NOTE this component is part of react-final-form instead of Redux Form.
+ */
+
import React, { Component } from 'react';
import { func, instanceOf, object, node, string, bool } from 'prop-types';
-import { Field } from 'redux-form';
+import { Field } from 'react-final-form';
import { injectIntl, intlShape } from 'react-intl';
import classNames from 'classnames';
import { range } from 'lodash';
@@ -8,7 +12,7 @@ import { ValidationError } from '../../components';
import css from './FieldBirthdayInput.css';
-// Since redux-form tracks the onBlur event for marking the field as
+// Since final-form tracks the onBlur event for marking the field as
// touched (which triggers possible error validation rendering), only
// trigger the event asynchronously when no other input within this
// component has received focus.
@@ -88,14 +92,14 @@ class BirthdayInputComponent extends Component {
this.handleSelectChange = this.handleSelectChange.bind(this);
}
componentWillMount() {
- const value = this.props.value;
+ const value = this.props.valueFromForm;
if (value instanceof Date) {
this.setState({ selected: selectedFromDate(value) });
}
}
componentWillReceiveProps(newProps) {
- const oldValue = this.props.value;
- const newValue = newProps.value;
+ const oldValue = this.props.valueFromForm;
+ const newValue = newProps.valueFromForm;
const valueChanged = oldValue !== newValue;
if (valueChanged && newValue instanceof Date) {
this.setState({ selected: selectedFromDate(newValue) });
@@ -219,7 +223,7 @@ BirthdayInputComponent.defaultProps = {
dateLabel: null,
monthLabel: null,
yearLabel: null,
- value: null,
+ valueFromForm: null,
disabled: false,
};
@@ -231,7 +235,7 @@ BirthdayInputComponent.propTypes = {
dateLabel: node,
monthLabel: node,
yearLabel: node,
- value: instanceOf(Date),
+ valueFromForm: instanceOf(Date),
onChange: func.isRequired,
onFocus: func.isRequired,
onBlur: func.isRequired,
diff --git a/src/components/FieldBoolean/FieldBoolean.example.js b/src/components/FieldBoolean/FieldBoolean.example.js
index fd7ccc91..818161b8 100644
--- a/src/components/FieldBoolean/FieldBoolean.example.js
+++ b/src/components/FieldBoolean/FieldBoolean.example.js
@@ -1,42 +1,50 @@
import React from 'react';
-import { reduxForm, propTypes as formPropTypes } from 'redux-form';
+import { Form as FinalForm, FormSpy } from 'react-final-form';
import * as validators from '../../util/validators';
import { Button } from '../../components';
import FieldBoolean from './FieldBoolean';
const formName = 'Styleguide.FieldBoolean.Form';
-const FormComponent = props => {
- const { form, handleSubmit, invalid, pristine, submitting } = props;
- const required = validators.requiredBoolean('This field is required');
- const submitDisabled = invalid || pristine || submitting;
- return (
-
- );
-};
-
-FormComponent.propTypes = formPropTypes;
-
-const Form = reduxForm({
- form: formName,
-})(FormComponent);
+const FormComponent = props => (
+ {
+ const { form, handleSubmit, onChange, invalid, pristine, submitting } = props;
+ const required = validators.requiredBoolean('This field is required');
+ const submitDisabled = invalid || pristine || submitting;
+ return (
+
+ );
+ }}
+ />
+);
export const YesNo = {
- component: Form,
+ component: FormComponent,
props: {
- onChange(values) {
- console.log('onChange:', values);
+ onChange: formState => {
+ if (Object.keys(formState.values).length > 0) {
+ console.log('form values changed to:', formState.values);
+ }
},
onSubmit(values) {
console.log('onSubmit:', values);
diff --git a/src/components/FieldBoolean/FieldBoolean.js b/src/components/FieldBoolean/FieldBoolean.js
index 63340ac5..3f19edcc 100644
--- a/src/components/FieldBoolean/FieldBoolean.js
+++ b/src/components/FieldBoolean/FieldBoolean.js
@@ -1,6 +1,6 @@
import React from 'react';
import { injectIntl, intlShape } from 'react-intl';
-import { SelectField } from '../../components';
+import { FieldSelect } from '../../components';
const FieldBoolean = props => {
const { placeholder, intl, ...rest } = props;
@@ -30,11 +30,11 @@ const FieldBoolean = props => {
},
};
return (
-
+
-
+
);
};
diff --git a/src/components/FieldCheckbox/FieldCheckbox.example.js b/src/components/FieldCheckbox/FieldCheckbox.example.js
index 5c1f88a8..e5009cdf 100644
--- a/src/components/FieldCheckbox/FieldCheckbox.example.js
+++ b/src/components/FieldCheckbox/FieldCheckbox.example.js
@@ -1,43 +1,47 @@
import React from 'react';
-import { reduxForm, propTypes as formPropTypes } from 'redux-form';
+import { Form as FinalForm, FormSpy } from 'react-final-form';
import { Button } from '../../components';
import FieldCheckbox from './FieldCheckbox';
-import { required } from '../../util/validators';
const formName = 'Styleguide.FieldCheckbox.Form';
-const FormComponent = props => {
- const { form, handleSubmit, invalid, pristine, submitting } = props;
+const FormComponent = props => (
+ {
+ const { form, handleSubmit, onChange, invalid, pristine, submitting } = fieldRenderProps;
- const submitDisabled = invalid || pristine || submitting;
+ const submitDisabled = invalid || pristine || submitting;
- return (
-
- );
-};
-
-FormComponent.propTypes = formPropTypes;
-
-const Form = reduxForm({
- form: formName,
-})(FormComponent);
+
+
+ );
+ }}
+ />
+);
export const Checkbox = {
- component: Form,
+ component: FormComponent,
props: {
+ onChange: formState => {
+ if (Object.keys(formState.values).length > 0) {
+ console.log('form values changed to:', formState.values);
+ }
+ },
onSubmit: values => {
console.log('Submit values of FieldCheckbox: ', values);
},
diff --git a/src/components/FieldCheckbox/FieldCheckbox.js b/src/components/FieldCheckbox/FieldCheckbox.js
index 13a94036..2084bbde 100644
--- a/src/components/FieldCheckbox/FieldCheckbox.js
+++ b/src/components/FieldCheckbox/FieldCheckbox.js
@@ -1,8 +1,11 @@
+/**
+ * NOTE this component is part of react-final-form instead of Redux Form.
+ */
+
import React from 'react';
-import { any, node, string, object, shape } from 'prop-types';
+import { node, string } from 'prop-types';
import classNames from 'classnames';
-import { Field } from 'redux-form';
-import { ValidationError } from '../../components';
+import { Field } from 'react-final-form';
import css from './FieldCheckbox.css';
@@ -35,32 +38,26 @@ IconCheckbox.defaultProps = { className: null };
IconCheckbox.propTypes = { className: string };
const FieldCheckboxComponent = props => {
- const { rootClassName, className, svgClassName, id, label, input, meta, ...rest } = props;
+ const { rootClassName, className, svgClassName, id, label, ...rest } = props;
const classes = classNames(rootClassName || css.root, className);
-
- const { value, ...inputProps } = input;
- const checked = value === true;
-
const checkboxProps = {
id,
className: css.input,
+ component: 'input',
type: 'checkbox',
- checked,
- ...inputProps,
...rest,
};
return (
-
+
-
);
};
@@ -76,16 +73,16 @@ FieldCheckboxComponent.propTypes = {
className: string,
rootClassName: string,
svgClassName: string,
+
+ // Id is needed to connect the label with input.
id: string.isRequired,
label: node,
- // redux-form Field params
- input: shape({ value: any }).isRequired,
- meta: object.isRequired,
+ // Name groups several checkboxes to an array of selected values
+ name: string.isRequired,
+
+ // Checkbox needs a value that is passed forward when user checks the checkbox
+ value: string.isRequired,
};
-const FieldCheckbox = props => {
- return ;
-};
-
-export default FieldCheckbox;
+export default FieldCheckboxComponent;
diff --git a/src/components/FieldCheckboxGroup/FieldCheckboxGroup.css b/src/components/FieldCheckboxGroup/FieldCheckboxGroup.css
new file mode 100644
index 00000000..0550ae37
--- /dev/null
+++ b/src/components/FieldCheckboxGroup/FieldCheckboxGroup.css
@@ -0,0 +1,28 @@
+@import '../../marketplace.css';
+
+.root {
+ margin: 0;
+ padding: 0;
+ border: none;
+}
+
+.list {
+ margin: 0;
+}
+
+.twoColumns {
+ @media (--viewportMedium) {
+ columns: 2;
+ }
+}
+
+.item {
+ padding: 2px 0;
+
+ /* Fix broken multi-column layout in Chrome */
+ page-break-inside: avoid;
+
+ @media (--viewportMedium) {
+ padding: 4px 0;
+ }
+}
diff --git a/src/components/FieldCheckboxGroup/FieldCheckboxGroup.example.js b/src/components/FieldCheckboxGroup/FieldCheckboxGroup.example.js
new file mode 100644
index 00000000..f7ce91f2
--- /dev/null
+++ b/src/components/FieldCheckboxGroup/FieldCheckboxGroup.example.js
@@ -0,0 +1,151 @@
+import React from 'react';
+import { Form as FinalForm, FormSpy } from 'react-final-form';
+import arrayMutators from 'final-form-arrays';
+import { Button } from '../../components';
+import FieldCheckboxGroup from './FieldCheckboxGroup';
+import { requiredFieldArrayCheckbox } from '../../util/validators';
+
+const formName = 'Styleguide.FieldCheckboxGroup';
+const formNameRequired = 'Styleguide.FieldCheckboxGroupRequired';
+
+const label = Amenities
;
+
+const commonProps = {
+ label: label,
+ options: [
+ {
+ key: 'towels',
+ label: 'Towels',
+ },
+ {
+ key: 'bathroom',
+ label: 'Bathroom',
+ },
+ {
+ key: 'swimming_pool',
+ label: 'Swimming pool',
+ },
+ {
+ key: 'own_drinks',
+ label: 'Own drinks allowed',
+ },
+ {
+ key: 'jacuzzi',
+ label: 'Jacuzzi',
+ },
+ {
+ key: 'audiovisual_entertainment',
+ label: 'Audiovisual entertainment',
+ },
+ {
+ key: 'barbeque',
+ label: 'Barbeque',
+ },
+ {
+ key: 'own_food_allowed',
+ label: 'Own food allowed',
+ },
+ ],
+ twoColumns: true,
+};
+
+const optionalProps = {
+ name: 'amenities-optional',
+ id: 'amenities-optional',
+ ...commonProps,
+};
+
+const requiredProps = {
+ name: 'amenities-required',
+ id: `${formNameRequired}.amenities-required`,
+ ...commonProps,
+ validate: requiredFieldArrayCheckbox('this is required'),
+};
+const tosProps = {
+ name: 'terms-of-service',
+ id: `${formNameRequired}.tos-accepted`,
+ options: [
+ {
+ key: 'tos',
+ label: 'Terms of Service',
+ },
+ ],
+ validate: requiredFieldArrayCheckbox('You need to accept Terms of Service'),
+};
+
+const formComponent = country => props => (
+ {
+ const { handleSubmit, invalid, submitting, componentProps, onChange } = fieldRenderProps;
+
+ const submitDisabled = invalid || submitting;
+
+ return (
+
+ );
+ }}
+ />
+);
+
+export const Optional = {
+ component: formComponent(formName),
+ props: {
+ onChange: formState => {
+ if (formState.dirty) {
+ console.log('form values changed to:', formState.values);
+ }
+ },
+ onSubmit: values => {
+ console.log('Submit values: ', values);
+ },
+ initialValues: { [optionalProps.name]: ['jacuzzi', 'towels'] },
+ componentProps: optionalProps,
+ },
+ group: 'inputs',
+};
+
+export const Required = {
+ component: formComponent(formNameRequired),
+ props: {
+ onChange: formState => {
+ if (formState.dirty) {
+ console.log('form values changed to:', formState.values);
+ }
+ },
+ onSubmit: values => {
+ console.log('Submit values: ', values);
+ },
+ componentProps: requiredProps,
+ },
+ group: 'inputs',
+};
+
+export const ToSAccepted = {
+ component: formComponent(formNameRequired),
+ props: {
+ onChange: formState => {
+ if (formState.dirty) {
+ console.log('form values changed to:', formState.values);
+ }
+ },
+ onSubmit: values => {
+ console.log('Submit values: ', values);
+ },
+ componentProps: tosProps,
+ },
+ group: 'inputs',
+};
diff --git a/src/components/FieldCheckboxGroup/FieldCheckboxGroup.js b/src/components/FieldCheckboxGroup/FieldCheckboxGroup.js
new file mode 100644
index 00000000..bd70f769
--- /dev/null
+++ b/src/components/FieldCheckboxGroup/FieldCheckboxGroup.js
@@ -0,0 +1,96 @@
+/**
+ * NOTE this component is part of react-final-form instead of Redux Form.
+ */
+
+/*
+ * Renders a group of checkboxes that can be used to select
+ * multiple values from a set of options.
+ *
+ * The corresponding component when rendering the selected
+ * values is PropertyGroup.
+ *
+ */
+
+import React, { Component } from 'react';
+import { arrayOf, bool, node, shape, string } from 'prop-types';
+import classNames from 'classnames';
+import { FieldArray } from 'react-final-form-arrays';
+import { FieldCheckbox, ValidationError } from '../../components';
+
+import css from './FieldCheckboxGroup.css';
+
+class FieldCheckboxRenderer extends Component {
+ constructor(props) {
+ super(props);
+ this.state = { touched: false };
+ }
+
+ componentWillReceiveProps(nextProps) {
+ // FieldArray doesn't have touched prop, so we'll keep track of it
+ if (!this.state.touched) {
+ this.setState({ touched: nextProps.meta.dirty });
+ }
+ }
+
+ render() {
+ const { className, rootClassName, label, twoColumns, id, fields, options, meta } = this.props;
+
+ const touched = this.state.touched;
+
+ const classes = classNames(rootClassName || css.root, className);
+ const listClasses = twoColumns ? classNames(css.list, css.twoColumns) : css.list;
+
+ return (
+
+ );
+ }
+}
+
+FieldCheckboxRenderer.defaultProps = {
+ rootClassName: null,
+ className: null,
+ label: null,
+ twoColumns: false,
+};
+
+FieldCheckboxRenderer.propTypes = {
+ rootClassName: string,
+ className: string,
+ id: string.isRequired,
+ label: node,
+ options: arrayOf(
+ shape({
+ key: string.isRequired,
+ label: node.isRequired,
+ })
+ ).isRequired,
+ twoColumns: bool,
+};
+
+const FieldCheckboxGroup = props => ;
+
+// Name and component are required fields for FieldArray.
+// Component-prop we define in this file, name needs to be passed in
+FieldCheckboxGroup.propTypes = {
+ name: string.isRequired,
+};
+
+export default FieldCheckboxGroup;
diff --git a/src/components/FieldCurrencyInput/FieldCurrencyInput.example.js b/src/components/FieldCurrencyInput/FieldCurrencyInput.example.js
index 78a18649..9f528f1d 100644
--- a/src/components/FieldCurrencyInput/FieldCurrencyInput.example.js
+++ b/src/components/FieldCurrencyInput/FieldCurrencyInput.example.js
@@ -2,7 +2,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { IntlProvider, addLocaleData } from 'react-intl';
-import { reduxForm, propTypes as formPropTypes } from 'redux-form';
+import { Form as FinalForm, FormSpy } from 'react-final-form';
import en from 'react-intl/locale-data/en';
import fi from 'react-intl/locale-data/fi';
import { currencyConfig } from '../../util/test-data';
@@ -81,36 +81,46 @@ export const defaultValueWithFiEUR = {
group: 'custom inputs',
};
-const formName = 'Styleguide.FieldCurrencyInput.Form';
+const FormComponent = props => (
+ {
+ const { handleSubmit, onChange } = fieldRenderProps;
+ const required = validators.required('This field is required');
-const FormComponent = props => {
- const { form } = props;
- const required = validators.required('This field is required');
- return (
-
- );
-};
-
-FormComponent.propTypes = formPropTypes;
-
-const Form = reduxForm({
- form: formName,
-})(FormComponent);
+ return (
+
+ );
+ }}
+ />
+);
export const FieldInForm = {
- component: Form,
+ component: FormComponent,
props: {
- onChange: values => {
- console.log('form values changed to:', values);
+ onChange: formState => {
+ if (formState.values && formState.values.price) {
+ console.log('form values changed to:', formState.values);
+ }
+ },
+ onSubmit: values => {
+ console.log('FieldInForm submitted values:', values);
+ return false;
},
},
group: 'custom inputs',
diff --git a/src/components/FieldCurrencyInput/FieldCurrencyInput.js b/src/components/FieldCurrencyInput/FieldCurrencyInput.js
index 2f3614d0..4893253a 100644
--- a/src/components/FieldCurrencyInput/FieldCurrencyInput.js
+++ b/src/components/FieldCurrencyInput/FieldCurrencyInput.js
@@ -1,3 +1,7 @@
+/**
+ * NOTE this component is part of react-final-form instead of Redux Form.
+ */
+
/**
* CurrencyInput renders an input field that format it's value according to currency formatting rules
* onFocus: renders given value in unformatted manner: "9999,99"
@@ -6,7 +10,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { intlShape, injectIntl } from 'react-intl';
-import { Field } from 'redux-form';
+import { Field } from 'react-final-form';
import classNames from 'classnames';
import Decimal from 'decimal.js';
import { ValidationError } from '../../components';
@@ -279,7 +283,7 @@ FieldCurrencyInputComponent.propTypes = {
id: string,
label: string,
- // Generated by redux-form's Field component
+ // Generated by final-form's Field component
input: object.isRequired,
meta: object.isRequired,
};
diff --git a/src/components/FieldDateInput/DateInput.js b/src/components/FieldDateInput/DateInput.js
index 6d124faa..5b51dc51 100644
--- a/src/components/FieldDateInput/DateInput.js
+++ b/src/components/FieldDateInput/DateInput.js
@@ -46,6 +46,8 @@ const defaultProps = {
horizontalMargin: 0,
withPortal: false,
withFullScreenPortal: false,
+ appendToBody: false,
+ disableScroll: false,
initialVisibleMonth: null,
firstDayOfWeek: config.i18n.firstDayOfWeek,
numberOfMonths: 1,
@@ -137,12 +139,12 @@ class DateInputComponent extends Component {
onBlur,
onChange,
onFocus,
- onDragStart,
- onDrop,
phrases,
screenReaderInputMessage,
useMobileMargins,
value,
+ children,
+ render,
...datePickerProps
} = this.props;
/* eslint-enable no-unused-vars */
@@ -194,6 +196,7 @@ DateInputComponent.defaultProps = {
DateInputComponent.propTypes = {
className: string,
+ id: string,
focused: bool,
initialDate: instanceOf(Date),
intl: intlShape.isRequired,
@@ -202,8 +205,6 @@ DateInputComponent.propTypes = {
onChange: func.isRequired,
onBlur: func.isRequired,
onFocus: func.isRequired,
- onDragStart: func.isRequired,
- onDrop: func.isRequired,
phrases: shape({
closeDatePicker: string,
clearDate: string,
diff --git a/src/components/FieldDateInput/FieldDateInput.example.js b/src/components/FieldDateInput/FieldDateInput.example.js
index c0f57ef2..11d37741 100644
--- a/src/components/FieldDateInput/FieldDateInput.example.js
+++ b/src/components/FieldDateInput/FieldDateInput.example.js
@@ -1,34 +1,51 @@
/* eslint-disable no-console */
import React from 'react';
-import { reduxForm, propTypes as formPropTypes } from 'redux-form';
+import { Form as FinalForm, FormSpy } from 'react-final-form';
import moment from 'moment';
import { Button } from '../../components';
-import { required, bookingDateRequired } from '../../util/validators';
+import { required, bookingDateRequired, composeValidators } from '../../util/validators';
import FieldDateInput from './FieldDateInput';
-const FormComponent = props => {
- const { form, handleSubmit, pristine, submitting, dateInputProps } = props;
- const submitDisabled = pristine || submitting;
- return (
-
- );
-};
+const FormComponent = props => (
+ {
+ const {
+ form,
+ handleSubmit,
+ onChange,
+ pristine,
+ submitting,
+ dateInputProps,
+ values,
+ } = fieldRenderProps;
+ const submitDisabled = pristine || submitting;
+ if (values && values.bookingDates) {
+ onChange(values.bookingDates);
+ }
-FormComponent.propTypes = formPropTypes;
+ return (
+
+ );
+ }}
+ />
+);
-const defaultFormName = 'Styleguide.DateInput.Form';
-
-const Form = reduxForm({
- form: defaultFormName,
-})(FormComponent);
+const defaultFormName = 'FieldDateInputExampleForm';
export const Empty = {
- component: Form,
+ component: FormComponent,
props: {
dateInputProps: {
name: 'bookingDate',
@@ -37,13 +54,15 @@ export const Empty = {
label: 'Date',
placeholderText: moment().format('ddd, MMMM D'),
format: null,
- validate: [required('Required'), bookingDateRequired('Date is not valid')],
+ validate: composeValidators(required('Required'), bookingDateRequired('Date is not valid')),
onBlur: () => console.log('onBlur called from DateInput props.'),
onFocus: () => console.log('onFocus called from DateInput props.'),
},
- onChange: values => {
- const { date } = values;
- console.log('Changed to', moment(date).format('L'));
+ onChange: formState => {
+ const { date } = formState.values;
+ if (date) {
+ console.log('Changed to', moment(date).format('L'));
+ }
},
onSubmit: values => {
console.log('Submitting a form with values:', values);
diff --git a/src/components/FieldDateInput/FieldDateInput.js b/src/components/FieldDateInput/FieldDateInput.js
index 6be23eb1..a1b3f9e5 100644
--- a/src/components/FieldDateInput/FieldDateInput.js
+++ b/src/components/FieldDateInput/FieldDateInput.js
@@ -1,12 +1,12 @@
/**
- * Provides a date picker for Redux Forms (using https://github.com/airbnb/react-dates)
+ * Provides a date picker for Final Forms (using https://github.com/airbnb/react-dates)
*
* NOTE: If you are using this component inside BookingDatesForm,
* you should convert value.date to start date and end date before submitting it to API
*/
import React, { Component } from 'react';
import { bool, object, string } from 'prop-types';
-import { Field } from 'redux-form';
+import { Field } from 'react-final-form';
import classNames from 'classnames';
import { ValidationError } from '../../components';
@@ -49,6 +49,7 @@ class FieldDateInputComponent extends Component {
onBlur: input.onBlur,
onFocus: input.onFocus,
useMobileMargins,
+ id,
...restOfInput,
...rest,
};
diff --git a/src/components/FieldDateInput/FieldDateInput.test.js b/src/components/FieldDateInput/FieldDateInput.test.js
index cb4ee8a0..cd18f409 100644
--- a/src/components/FieldDateInput/FieldDateInput.test.js
+++ b/src/components/FieldDateInput/FieldDateInput.test.js
@@ -16,8 +16,6 @@ describe('DateInput', () => {
onBlur: noop,
onChange: noop,
onFocus: noop,
- onDragStart: noop,
- onDrop: noop,
id: 'bookingDate',
placeholderText: 'today',
};
diff --git a/src/components/FieldDateRangeInput/DateRangeInput.js b/src/components/FieldDateRangeInput/DateRangeInput.js
index 4edc5ce6..de0c5e13 100644
--- a/src/components/FieldDateRangeInput/DateRangeInput.js
+++ b/src/components/FieldDateRangeInput/DateRangeInput.js
@@ -21,7 +21,7 @@ import css from './DateRangeInput.css';
export const HORIZONTAL_ORIENTATION = 'horizontal';
export const ANCHOR_LEFT = 'left';
-// Since redux-form tracks the onBlur event for marking the field as
+// Since final-form tracks the onBlur event for marking the field as
// touched (which triggers possible error validation rendering), only
// trigger the event asynchronously when no other input within this
// component has received focus.
@@ -89,6 +89,8 @@ const defaultProps = {
horizontalMargin: 0,
withPortal: false,
withFullScreenPortal: false,
+ appendToBody: false,
+ disableScroll: false,
daySize: 38,
isRTL: false,
initialVisibleMonth: null,
@@ -202,12 +204,12 @@ class DateRangeInputComponent extends Component {
onBlur,
onChange,
onFocus,
- onDragStart,
- onDrop,
phrases,
screenReaderInputMessage,
useMobileMargins,
value,
+ children,
+ render,
...datePickerProps
} = this.props;
/* eslint-enable no-unused-vars */
@@ -268,6 +270,8 @@ DateRangeInputComponent.defaultProps = {
DateRangeInputComponent.propTypes = {
className: string,
+ startDateId: string,
+ endDateId: string,
unitType: propTypes.bookingUnitType.isRequired,
focusedInput: oneOf([START_DATE, END_DATE]),
initialDates: instanceOf(Date),
@@ -277,8 +281,6 @@ DateRangeInputComponent.propTypes = {
onChange: func.isRequired,
onBlur: func.isRequired,
onFocus: func.isRequired,
- onDragStart: func.isRequired,
- onDrop: func.isRequired,
phrases: shape({
closeDatePicker: string,
clearDate: string,
diff --git a/src/components/FieldDateRangeInput/FieldDateRangeInput.example.js b/src/components/FieldDateRangeInput/FieldDateRangeInput.example.js
index 9a95d5c9..4a8bcecb 100644
--- a/src/components/FieldDateRangeInput/FieldDateRangeInput.example.js
+++ b/src/components/FieldDateRangeInput/FieldDateRangeInput.example.js
@@ -1,35 +1,48 @@
/* eslint-disable no-console */
import React from 'react';
-import { reduxForm, propTypes as formPropTypes } from 'redux-form';
+import { Form as FinalForm, FormSpy } from 'react-final-form';
import moment from 'moment';
import { Button } from '../../components';
-import { required, bookingDatesRequired } from '../../util/validators';
+import { required, bookingDatesRequired, composeValidators } from '../../util/validators';
import { LINE_ITEM_NIGHT } from '../../util/types';
import FieldDateRangeInput from './FieldDateRangeInput';
-const FormComponent = props => {
- const { form, handleSubmit, pristine, submitting, dateInputProps } = props;
- const submitDisabled = pristine || submitting;
- return (
-
- );
-};
+const FormComponent = props => (
+ {
+ const {
+ form,
+ handleSubmit,
+ onChange,
+ pristine,
+ submitting,
+ dateInputProps,
+ } = fieldRenderProps;
+ const submitDisabled = pristine || submitting;
-FormComponent.propTypes = formPropTypes;
+ return (
+
+ );
+ }}
+ />
+);
-const defaultFormName = 'Styleguide.DateRangeInput.Form';
-
-const Form = reduxForm({
- form: defaultFormName,
-})(FormComponent);
+const defaultFormName = 'FieldDateRangeInputExampleForm';
export const Empty = {
- component: Form,
+ component: FormComponent,
props: {
dateInputProps: {
name: 'bookingDates',
@@ -43,16 +56,18 @@ export const Empty = {
.add(1, 'days')
.format('ddd, MMMM D'),
format: null,
- validate: [
+ validate: composeValidators(
required('Required'),
- bookingDatesRequired('Start date is not valid', 'End date is not valid'),
- ],
+ bookingDatesRequired('Start date is not valid', 'End date is not valid')
+ ),
onBlur: () => console.log('onBlur called from DateRangeInput props.'),
onFocus: () => console.log('onFocus called from DateRangeInput props.'),
},
- onChange: values => {
- const { startDate, endDate } = values;
- console.log('Changed to', moment(startDate).format('L'), moment(endDate).format('L'));
+ onChange: formState => {
+ const { startDate, endDate } = formState.values;
+ if (startDate || endDate) {
+ console.log('Changed to', moment(startDate).format('L'), moment(endDate).format('L'));
+ }
},
onSubmit: values => {
console.log('Submitting a form with values:', values);
diff --git a/src/components/FieldDateRangeInput/FieldDateRangeInput.js b/src/components/FieldDateRangeInput/FieldDateRangeInput.js
index 087cebf4..17b0c07e 100644
--- a/src/components/FieldDateRangeInput/FieldDateRangeInput.js
+++ b/src/components/FieldDateRangeInput/FieldDateRangeInput.js
@@ -1,6 +1,13 @@
+/**
+ * Provides a date picker for Final Forms (using https://github.com/airbnb/react-dates)
+ *
+ * NOTE: If you are using this component inside BookingDatesForm,
+ * you should convert value.date to start date and end date before submitting it to API
+ */
+
import React, { Component } from 'react';
import { bool, func, object, oneOf, string } from 'prop-types';
-import { Field } from 'redux-form';
+import { Field } from 'react-final-form';
import classNames from 'classnames';
import { START_DATE, END_DATE } from '../../util/dates';
import { propTypes } from '../../util/types';
@@ -117,6 +124,8 @@ class FieldDateRangeInputComponent extends Component {
...restOfInput,
...rest,
focusedInput: this.state.focusedInput,
+ startDateId,
+ endDateId,
};
const classes = classNames(rootClassName || css.fieldRoot, className);
const errorClasses = classNames({ [css.mobileMargins]: useMobileMargins });
diff --git a/src/components/FieldDateRangeInput/FieldDateRangeInput.test.js b/src/components/FieldDateRangeInput/FieldDateRangeInput.test.js
index c63903f4..bc2abaf1 100644
--- a/src/components/FieldDateRangeInput/FieldDateRangeInput.test.js
+++ b/src/components/FieldDateRangeInput/FieldDateRangeInput.test.js
@@ -18,8 +18,6 @@ describe('DateRangeInput', () => {
onBlur: noop,
onChange: noop,
onFocus: noop,
- onDragStart: noop,
- onDrop: noop,
startDateId: 'bookingStartDate',
startDatePlaceholderText: 'today',
endDateId: 'bookingEndDate',
diff --git a/src/components/FieldGroupCheckbox/FieldCheckbox.css b/src/components/FieldGroupCheckbox/FieldCheckbox.css
new file mode 100644
index 00000000..a295dbf0
--- /dev/null
+++ b/src/components/FieldGroupCheckbox/FieldCheckbox.css
@@ -0,0 +1,68 @@
+@import '../../marketplace.css';
+
+.root {
+ position: relative;
+}
+
+.input {
+ position: absolute;
+ opacity: 0;
+ height: 0;
+ width: 0;
+
+ /* Highlight the borders if the checkbox is hovered, focused or checked */
+ &:hover + label .box,
+ &:focus + label .box,
+ &:checked + label .box {
+ stroke: var(--marketplaceColor);
+ }
+
+ /* Display the "check" when checked */
+ &:checked + label .checked {
+ display: inline;
+ stroke: var(--marketplaceColor);
+ stroke-width: 1px;
+ }
+
+ /* Hightlight the text on checked, hover and focus */
+ &:focus + label .text,
+ &:hover + label .text,
+ &:checked + label .text {
+ color: var(--matterColorDark);
+ }
+}
+
+.label {
+ display: flex;
+ align-items: center;
+ padding: 0;
+}
+
+.checkboxWrapper {
+ /* This should follow line-height */
+ height: 32px;
+ margin-top: -1px;
+ margin-right: 12px;
+ align-self: baseline;
+
+ display: inline-flex;
+ align-items: center;
+ cursor: pointer;
+}
+
+.checked {
+ display: none;
+ fill: var(--marketplaceColor);
+}
+
+.box {
+ stroke: var(--matterColorAnti);
+}
+
+.text {
+ @apply --marketplaceListingAttributeFontStyles;
+ color: var(--matterColor);
+ margin-top: -1px;
+ margin-bottom: 1px;
+ cursor: pointer;
+}
diff --git a/src/components/FieldGroupCheckbox/FieldCheckbox.js b/src/components/FieldGroupCheckbox/FieldCheckbox.js
new file mode 100644
index 00000000..13a94036
--- /dev/null
+++ b/src/components/FieldGroupCheckbox/FieldCheckbox.js
@@ -0,0 +1,91 @@
+import React from 'react';
+import { any, node, string, object, shape } from 'prop-types';
+import classNames from 'classnames';
+import { Field } from 'redux-form';
+import { ValidationError } from '../../components';
+
+import css from './FieldCheckbox.css';
+
+const IconCheckbox = props => {
+ return (
+
+ );
+};
+
+IconCheckbox.defaultProps = { className: null };
+
+IconCheckbox.propTypes = { className: string };
+
+const FieldCheckboxComponent = props => {
+ const { rootClassName, className, svgClassName, id, label, input, meta, ...rest } = props;
+
+ const classes = classNames(rootClassName || css.root, className);
+
+ const { value, ...inputProps } = input;
+ const checked = value === true;
+
+ const checkboxProps = {
+ id,
+ className: css.input,
+ type: 'checkbox',
+ checked,
+ ...inputProps,
+ ...rest,
+ };
+
+ return (
+
+
+
+
+
+ );
+};
+
+FieldCheckboxComponent.defaultProps = {
+ className: null,
+ rootClassName: null,
+ svgClassName: null,
+ label: null,
+};
+
+FieldCheckboxComponent.propTypes = {
+ className: string,
+ rootClassName: string,
+ svgClassName: string,
+ id: string.isRequired,
+ label: node,
+
+ // redux-form Field params
+ input: shape({ value: any }).isRequired,
+ meta: object.isRequired,
+};
+
+const FieldCheckbox = props => {
+ return ;
+};
+
+export default FieldCheckbox;
diff --git a/src/components/FieldGroupCheckbox/FieldGroupCheckbox.js b/src/components/FieldGroupCheckbox/FieldGroupCheckbox.js
index 39f80445..14876e96 100644
--- a/src/components/FieldGroupCheckbox/FieldGroupCheckbox.js
+++ b/src/components/FieldGroupCheckbox/FieldGroupCheckbox.js
@@ -11,8 +11,9 @@ import React, { Component } from 'react';
import { arrayOf, bool, node, shape, string } from 'prop-types';
import classNames from 'classnames';
import { FieldArray } from 'redux-form';
-import { FieldCheckbox, ValidationError } from '../../components';
+import { ValidationError } from '../../components';
+import FieldCheckbox from './FieldCheckbox';
import css from './FieldGroupCheckbox.css';
class FieldCheckboxRenderer extends Component {
@@ -29,8 +30,7 @@ class FieldCheckboxRenderer extends Component {
}
render() {
- const { className, rootClassName, label, twoColumns, id, options, fields, meta } = this.props;
- const name = fields.name;
+ const { className, rootClassName, label, twoColumns, id, options, meta, name } = this.props;
const touched = this.state.touched;
@@ -77,7 +77,11 @@ FieldCheckboxRenderer.propTypes = {
twoColumns: bool,
};
-const FieldGroupCheckbox = props => ;
+// Redux Form: the name of FieldArray must be unique
+// https://github.com/erikras/redux-form/issues/2740
+const FieldGroupCheckbox = props => (
+
+);
// Name and component are required fields for FieldArray.
// Component-prop we define in this file, name needs to be passed in
diff --git a/src/components/FieldPhoneNumberInput/FieldPhoneNumberInput.example.js b/src/components/FieldPhoneNumberInput/FieldPhoneNumberInput.example.js
index 134a9aa2..d27c0999 100644
--- a/src/components/FieldPhoneNumberInput/FieldPhoneNumberInput.example.js
+++ b/src/components/FieldPhoneNumberInput/FieldPhoneNumberInput.example.js
@@ -1,40 +1,52 @@
import React from 'react';
-import { reduxForm, propTypes as formPropTypes } from 'redux-form';
+import { Form as FinalForm, FormSpy } from 'react-final-form';
import * as validators from '../../util/validators';
import { Button } from '../../components';
import FieldPhoneNumberInput from './FieldPhoneNumberInput';
const formName = 'Styleguide.FieldPhoneNumberInput.Form';
-const FormComponent = props => {
- const { form, handleSubmit, invalid, pristine, submitting } = props;
- const required = validators.required('This field is required');
- const submitDisabled = invalid || pristine || submitting;
- return (
-
- );
-};
-
-FormComponent.propTypes = formPropTypes;
-
-const Form = reduxForm({
- form: formName,
-})(FormComponent);
+const FormComponent = props => (
+ {
+ const { formId, handleSubmit, onChange, invalid, pristine, submitting } = fieldRenderProps;
+ const required = validators.required('This field is required');
+ const submitDisabled = invalid || pristine || submitting;
+ return (
+
+ );
+ }}
+ />
+);
export const PhoneNumber = {
- component: Form,
+ component: FormComponent,
props: {
+ formId: 'PhoneNumberExample',
+ onChange: formState => {
+ if (formState.dirty) {
+ console.log('form values changed to:', formState.values);
+ }
+ },
onSubmit(values) {
console.log('onSubmit:', values);
},
diff --git a/src/components/FieldPhoneNumberInput/FieldPhoneNumberInput.js b/src/components/FieldPhoneNumberInput/FieldPhoneNumberInput.js
index 82c00103..6a44b4a5 100644
--- a/src/components/FieldPhoneNumberInput/FieldPhoneNumberInput.js
+++ b/src/components/FieldPhoneNumberInput/FieldPhoneNumberInput.js
@@ -1,3 +1,7 @@
+/**
+ * DEPRECATED: this component is part of Redux Form - we are migrating to react-final-form.
+ */
+
/**
* A text field with phone number formatting. By default uses formatting
* rules defined in the fiFormatter.js file. To change the formatting
@@ -6,7 +10,7 @@
*/
import React from 'react';
-import { TextInputField } from '../../components';
+import { FieldTextInput } from '../../components';
import { format, parse } from './fiFormatter';
const FieldPhoneNumberInput = props => {
@@ -17,7 +21,7 @@ const FieldPhoneNumberInput = props => {
parse: parse,
};
- return ;
+ return ;
};
export default FieldPhoneNumberInput;
diff --git a/src/components/FieldReviewRating/FieldReviewRating.example.js b/src/components/FieldReviewRating/FieldReviewRating.example.js
index 53512eb0..11a0cbbe 100644
--- a/src/components/FieldReviewRating/FieldReviewRating.example.js
+++ b/src/components/FieldReviewRating/FieldReviewRating.example.js
@@ -1,41 +1,52 @@
/* eslint-disable no-console */
import React from 'react';
-import { reduxForm, propTypes as formPropTypes } from 'redux-form';
+import { Form as FinalForm, FormSpy } from 'react-final-form';
import * as validators from '../../util/validators';
import { Button } from '../../components';
import FieldReviewRating from './FieldReviewRating';
const formName = 'Styleguide.FieldReviewRating.Form';
-const FormComponent = props => {
- const { form, handleSubmit, invalid, pristine, submitting } = props;
- const required = validators.required('This field is required');
- const submitDisabled = invalid || pristine || submitting;
+const FormComponent = props => (
+ {
+ const { form, handleSubmit, onChange, invalid, pristine, submitting } = fieldRenderProps;
+ const required = validators.required('This field is required');
+ const submitDisabled = invalid || pristine || submitting;
- return (
-
- );
-};
-
-FormComponent.propTypes = formPropTypes;
-
-const Form = reduxForm({
- form: formName,
-})(FormComponent);
+ return (
+
+ );
+ }}
+ />
+);
export const StarRating = {
- component: Form,
+ component: FormComponent,
props: {
+ onChange: formState => {
+ if (formState.dirty) {
+ console.log('form values changed to:', formState.values);
+ }
+ },
onSubmit: values => {
console.log('submit values:', values);
},
diff --git a/src/components/FieldReviewRating/FieldReviewRating.js b/src/components/FieldReviewRating/FieldReviewRating.js
index 68ef0c6f..72b4f34a 100644
--- a/src/components/FieldReviewRating/FieldReviewRating.js
+++ b/src/components/FieldReviewRating/FieldReviewRating.js
@@ -1,7 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { intlShape, injectIntl } from 'react-intl';
-import { Field } from 'redux-form';
+import { Field } from 'react-final-form';
import classNames from 'classnames';
import { IconReviewStar, ValidationError } from '../../components';
@@ -13,12 +13,6 @@ class FieldReviewRatingComponent extends Component {
this.handleChange = this.handleChange.bind(this);
}
- componentWillUnmount() {
- if (this.props.clearOnUnmount) {
- this.props.input.onChange('');
- }
- }
-
handleChange(event) {
this.props.input.onChange(event.target.value);
}
@@ -29,7 +23,6 @@ class FieldReviewRatingComponent extends Component {
rootClassName,
className,
inputRootClass,
- clearOnUnmount,
customErrorText,
id,
intl,
@@ -105,17 +98,15 @@ class FieldReviewRatingComponent extends Component {
FieldReviewRatingComponent.defaultProps = {
rootClassName: null,
className: null,
- clearOnUnmount: false,
customErrorText: null,
label: null,
};
-const { string, bool, shape, func, object } = PropTypes;
+const { string, shape, func, object } = PropTypes;
FieldReviewRatingComponent.propTypes = {
rootClassName: string,
className: string,
- clearOnUnmount: bool,
id: string.isRequired,
label: string,
diff --git a/src/components/FieldSelect/FieldSelect.css b/src/components/FieldSelect/FieldSelect.css
new file mode 100644
index 00000000..0aa58c74
--- /dev/null
+++ b/src/components/FieldSelect/FieldSelect.css
@@ -0,0 +1,18 @@
+@import '../../marketplace.css';
+
+.root {
+}
+
+.select {
+ color: var(--matterColorAnti);
+ border-bottom-color: var(--attentionColor);
+}
+
+.selectSuccess {
+ color: var(--matterColor);
+ border-bottom-color: var(--successColor);
+}
+
+.selectError {
+ border-bottom-color: var(--failColor);
+}
diff --git a/src/components/FieldSelect/FieldSelect.example.js b/src/components/FieldSelect/FieldSelect.example.js
new file mode 100644
index 00000000..de6fae6a
--- /dev/null
+++ b/src/components/FieldSelect/FieldSelect.example.js
@@ -0,0 +1,51 @@
+/* eslint-disable no-console */
+import React from 'react';
+import { Form as FinalForm, FormSpy } from 'react-final-form';
+import * as validators from '../../util/validators';
+import { Button } from '../../components';
+import FieldSelect from './FieldSelect';
+
+const FormComponent = props => (
+ {
+ const { form, handleSubmit, onChange, invalid, pristine, submitting } = fieldRenderProps;
+ const required = validators.required('This field is required');
+ const submitDisabled = invalid || pristine || submitting;
+ return (
+
+ );
+ }}
+ />
+);
+
+export const Select = {
+ component: FormComponent,
+ props: {
+ onChange: formState => {
+ if (formState.values.select1) {
+ console.log('form values changed to:', formState.values);
+ }
+ },
+ onSubmit: values => {
+ console.log('submit values:', values);
+ return false;
+ },
+ },
+ group: 'inputs',
+};
diff --git a/src/components/FieldSelect/FieldSelect.js b/src/components/FieldSelect/FieldSelect.js
new file mode 100644
index 00000000..dd13589d
--- /dev/null
+++ b/src/components/FieldSelect/FieldSelect.js
@@ -0,0 +1,72 @@
+/**
+ * NOTE this component is part of react-final-form instead of Redux Form.
+ */
+
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Field } from 'react-final-form';
+import classNames from 'classnames';
+import { ValidationError } from '../../components';
+
+import css from './FieldSelect.css';
+
+const FieldSelectComponent = props => {
+ const { rootClassName, className, id, label, input, meta, children, ...rest } = props;
+
+ if (label && !id) {
+ throw new Error('id required when a label is given');
+ }
+
+ const { 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;
+
+ const selectClasses = classNames(css.select, {
+ [css.selectSuccess]: valid,
+ [css.selectError]: hasError,
+ });
+ const selectProps = { className: selectClasses, id, ...input, ...rest };
+
+ const classes = classNames(rootClassName || css.root, className);
+ return (
+
+ {label ? : null}
+
+
+
+ );
+};
+
+FieldSelectComponent.defaultProps = {
+ rootClassName: null,
+ className: null,
+ id: null,
+ label: null,
+ children: null,
+};
+
+const { string, object, node } = PropTypes;
+
+FieldSelectComponent.propTypes = {
+ rootClassName: string,
+ className: string,
+
+ // Label is optional, but if it is given, an id is also required so
+ // the label can reference the input in the `for` attribute
+ id: string,
+ label: string,
+
+ // Generated by final-form's Field component
+ input: object.isRequired,
+ meta: object.isRequired,
+
+ children: node,
+};
+
+const FieldSelect = props => {
+ return ;
+};
+
+export default FieldSelect;
diff --git a/src/components/FieldTextInput/FieldTextInput.css b/src/components/FieldTextInput/FieldTextInput.css
new file mode 100644
index 00000000..2294a88d
--- /dev/null
+++ b/src/components/FieldTextInput/FieldTextInput.css
@@ -0,0 +1,19 @@
+@import '../../marketplace.css';
+
+.root {
+}
+
+.input {
+ border-bottom-color: var(--attentionColor);
+}
+
+.inputSuccess {
+ border-bottom-color: var(--successColor);
+}
+
+.inputError {
+ border-bottom-color: var(--failColor);
+}
+
+.textarea {
+}
diff --git a/src/components/FieldTextInput/FieldTextInput.example.css b/src/components/FieldTextInput/FieldTextInput.example.css
new file mode 100644
index 00000000..9fe2d13d
--- /dev/null
+++ b/src/components/FieldTextInput/FieldTextInput.example.css
@@ -0,0 +1,7 @@
+.field {
+ margin-top: 24px;
+}
+
+.submit {
+ margin-top: 24px;
+}
diff --git a/src/components/FieldTextInput/FieldTextInput.example.js b/src/components/FieldTextInput/FieldTextInput.example.js
new file mode 100644
index 00000000..68cd4e2e
--- /dev/null
+++ b/src/components/FieldTextInput/FieldTextInput.example.js
@@ -0,0 +1,90 @@
+/* eslint-disable no-console */
+import React from 'react';
+import { Form as FinalForm, FormSpy } from 'react-final-form';
+import * as validators from '../../util/validators';
+import { Button } from '../../components';
+import FieldTextInput from './FieldTextInput';
+
+import css from './FieldTextInput.example.css';
+
+const FormComponent = props => (
+ {
+ const { handleSubmit, onChange, invalid, pristine, submitting, formName } = fieldRenderProps;
+ const required = validators.required('This field is required');
+ const submitDisabled = invalid || pristine || submitting;
+ return (
+
+ );
+ }}
+ />
+);
+
+export const Inputs = {
+ component: FormComponent,
+ props: {
+ formName: 'Inputs',
+ onChange: formState => {
+ if (Object.keys(formState.values).length > 0) {
+ console.log('form values changed to:', formState.values);
+ }
+ },
+ onSubmit: values => {
+ console.log('submit values:', values);
+ },
+ },
+ group: 'inputs',
+};
diff --git a/src/components/FieldTextInput/FieldTextInput.js b/src/components/FieldTextInput/FieldTextInput.js
new file mode 100644
index 00000000..9af3262c
--- /dev/null
+++ b/src/components/FieldTextInput/FieldTextInput.js
@@ -0,0 +1,121 @@
+/**
+ * NOTE this component is part of react-final-form instead of Redux Form.
+ */
+
+import React, { Component } from 'react';
+import { func, object, shape, string } from 'prop-types';
+import { Field } from 'react-final-form';
+import classNames from 'classnames';
+import { ValidationError, ExpandingTextarea } from '../../components';
+
+import css from './FieldTextInput.css';
+
+const CONTENT_MAX_LENGTH = 5000;
+
+class FieldTextInputComponent extends Component {
+ render() {
+ /* eslint-disable no-unused-vars */
+ const {
+ rootClassName,
+ className,
+ inputRootClass,
+ customErrorText,
+ id,
+ label,
+ type,
+ input,
+ meta,
+ onUnmount,
+ ...rest
+ } = this.props;
+ /* eslint-enable no-unused-vars */
+
+ if (label && !id) {
+ throw new Error('id required when a label is given');
+ }
+
+ const { valid, invalid, touched, error } = meta;
+ const isTextarea = type === 'textarea';
+
+ const errorText = customErrorText || error;
+
+ // Error message and input error styles are only shown if the
+ // field has been touched and the validation has failed.
+ const hasError = !!customErrorText || !!(touched && invalid && error);
+
+ const fieldMeta = { touched: hasError, error: errorText };
+
+ const inputClasses =
+ inputRootClass ||
+ classNames(css.input, {
+ [css.inputSuccess]: valid,
+ [css.inputError]: hasError,
+ [css.textarea]: isTextarea,
+ });
+ const inputProps = isTextarea
+ ? { className: inputClasses, id, rows: 1, maxLength: CONTENT_MAX_LENGTH, ...input, ...rest }
+ : { className: inputClasses, id, type, ...input, ...rest };
+
+ const classes = classNames(rootClassName || css.root, className);
+ return (
+
+ {label ? : null}
+ {isTextarea ? : }
+
+
+ );
+ }
+}
+
+FieldTextInputComponent.defaultProps = {
+ rootClassName: null,
+ className: null,
+ inputRootClass: null,
+ onUnmount: null,
+ customErrorText: null,
+ id: null,
+ label: null,
+};
+
+FieldTextInputComponent.propTypes = {
+ rootClassName: string,
+ className: string,
+ inputRootClass: string,
+
+ onUnmount: func,
+
+ // Error message that can be manually passed to input field,
+ // overrides default validation message
+ customErrorText: string,
+
+ // Label is optional, but if it is given, an id is also required so
+ // the label can reference the input in the `for` attribute
+ id: string,
+ label: string,
+
+ // Either 'textarea' or something that is passed to the input element
+ type: string.isRequired,
+
+ // Generated by redux-form's Field component
+ input: shape({
+ onChange: func.isRequired,
+ }).isRequired,
+ meta: object.isRequired,
+};
+
+class FieldTextInput extends Component {
+ componentWillUnmount() {
+ // Unmounting happens too late if it is done inside Field component
+ // (Then Form has already registered its (new) fields and
+ // changing the value without corresponding field is prohibited in Final Form
+ if (this.props.onUnmount) {
+ this.props.onUnmount();
+ }
+ }
+
+ render() {
+ return ;
+ }
+}
+
+export default FieldTextInput;
diff --git a/src/components/LocationAutocompleteInput/LocationAutocompleteInput.js b/src/components/LocationAutocompleteInput/LocationAutocompleteInput.js
index 8bca821b..9a628118 100644
--- a/src/components/LocationAutocompleteInput/LocationAutocompleteInput.js
+++ b/src/components/LocationAutocompleteInput/LocationAutocompleteInput.js
@@ -1,6 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
-import { Field } from 'redux-form';
+import { Field } from 'react-final-form';
import { debounce } from 'lodash';
import classNames from 'classnames';
import { propTypes } from '../../util/types';
@@ -494,18 +494,14 @@ LocationAutocompleteInput.propTypes = {
export default LocationAutocompleteInput;
class LocationAutocompleteInputFieldComponent extends Component {
- componentWillUnmount() {
- if (this.props.clearOnUnmount) {
- this.props.input.onChange('');
- }
- }
-
render() {
/* eslint-disable no-unused-vars */
- const { rootClassName, labelClassName, clearOnUnmount, ...restProps } = this.props;
- const { input, label, meta, ...otherProps } = restProps;
+ const { rootClassName, labelClassName, ...restProps } = this.props;
+ const { input, label, meta, valueFromForm, ...otherProps } = restProps;
/* eslint-enable no-unused-vars */
+ const value = typeof valueFromForm !== 'undefined' ? valueFromForm : input.value;
+ const locationAutocompleteProps = { label, meta, ...otherProps, input: { ...input, value } };
const labelInfo = label ? (