Merge pull request #204 from sharetribe/input-field-component

Input field component
This commit is contained in:
Kimmo Puputti 2017-06-06 10:40:06 +03:00 committed by GitHub
commit 3933353821
12 changed files with 220 additions and 26 deletions

View file

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

View file

@ -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 (
<form onSubmit={handleSubmit}>
<Field
name="input1"
type="text"
label="Label for required input"
placeholder="Placeholder..."
validate={required}
component={InputField}
/>
<Field
name="input2"
type="text"
label="Example input label"
placeholder="Example input placeholder..."
component={InputField}
/>
<Field
name="input3"
type="text"
placeholder="No label in this input..."
component={InputField}
/>
<Field
name="input4"
type="text"
label="Label for input with initial value"
component={InputField}
/>
<Button type="submit" disabled={submitDisabled} style={buttonStyles}>Submit form</Button>
</form>
);
};
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',
};

View file

@ -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 (
<div className={classes}>
{label ? <label className={labelClasses} htmlFor={inputProps.name}>{label}</label> : null}
<input className={inputClasses} {...inputProps} />
{hasError ? <p className={errorClasses}>{error}</p> : null}
</div>
);
}
}
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;

View file

@ -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 ? <div className={classes}>{error}</div> : 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,

View file

@ -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,

View file

@ -55,9 +55,6 @@
width: 100%;
clear: both;
}
.imageRequiredError {
color: red;
}
.submitButton {
margin-top: 1rem;

View file

@ -100,7 +100,7 @@ export class EditListingPhotosFormComponent extends Component {
return (
<div className={css.imageRequiredWrapper}>
<Input {...input} type={type} />
<ValidationError className={css.imageRequiredError} fieldMeta={meta} />
<ValidationError fieldMeta={meta} />
</div>
);
}}

View file

@ -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;

View file

@ -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 {

View file

@ -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() {

View file

@ -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,

View file

@ -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 = {}) => {
<div className={rootClassName}>
{labelInfo}
{component}
<ValidationError className={errorClassName} fieldMeta={meta} />
<ValidationError fieldMeta={meta} />
</div>
);
}