mirror of
https://github.com/kingomarnajjar/flex-template-web.git
synced 2026-07-29 05:00:43 +10:00
Merge pull request #204 from sharetribe/input-field-component
Input field component
This commit is contained in:
commit
3933353821
12 changed files with 220 additions and 26 deletions
42
src/components/InputField/InputField.css
Normal file
42
src/components/InputField/InputField.css
Normal 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));
|
||||
}
|
||||
}
|
||||
66
src/components/InputField/InputField.example.js
Normal file
66
src/components/InputField/InputField.example.js
Normal 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',
|
||||
};
|
||||
96
src/components/InputField/InputField.js
Normal file
96
src/components/InputField/InputField.js
Normal 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;
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -55,9 +55,6 @@
|
|||
width: 100%;
|
||||
clear: both;
|
||||
}
|
||||
.imageRequiredError {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.submitButton {
|
||||
margin-top: 1rem;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue