Restructured form module

This commit is contained in:
SleepWalker 2016-05-02 10:15:42 +03:00
parent d19e7e5c4f
commit 23ea4d4c47
18 changed files with 317 additions and 292 deletions

View File

@ -4,7 +4,7 @@ import { connect } from 'react-redux';
import { TransitionMotion, spring } from 'react-motion';
import { Panel, PanelBody, PanelFooter, PanelHeader } from 'components/ui/Panel';
import { Form } from 'components/ui/Form';
import { Form } from 'components/ui/form';
import {helpLinks as helpLinksStyles} from 'components/auth/helpLinks.scss';
import panelStyles from 'components/ui/panel.scss';
import icons from 'components/ui/icons.scss';

View File

@ -4,7 +4,7 @@ import { FormattedMessage as Message } from 'react-intl';
import Helmet from 'react-helmet';
import buttons from 'components/ui/buttons.scss';
import { Input } from 'components/ui/Form';
import { Input } from 'components/ui/form';
import BaseAuthBody from 'components/auth/BaseAuthBody';
import styles from './activation.scss';

View File

@ -19,7 +19,7 @@
}
.activationCodeInput {
composes: blueTextField from 'components/ui/form.scss';
composes: blueTextField from 'components/ui/form/form.scss';
text-align: center;
}

View File

@ -4,7 +4,7 @@ import { FormattedMessage as Message } from 'react-intl';
import Helmet from 'react-helmet';
import buttons from 'components/ui/buttons.scss';
import { Input } from 'components/ui/Form';
import { Input } from 'components/ui/form';
import BaseAuthBody from 'components/auth/BaseAuthBody';
import icons from 'components/ui/icons.scss';

View File

@ -4,7 +4,7 @@ import { FormattedMessage as Message } from 'react-intl';
import Helmet from 'react-helmet';
import buttons from 'components/ui/buttons.scss';
import { Input } from 'components/ui/Form';
import { Input } from 'components/ui/form';
import BaseAuthBody from 'components/auth/BaseAuthBody';
import messages from './ForgotPassword.messages';

View File

@ -5,7 +5,7 @@ import Helmet from 'react-helmet';
import { Link } from 'react-router';
import buttons from 'components/ui/buttons.scss';
import { Input } from 'components/ui/Form';
import { Input } from 'components/ui/form';
import BaseAuthBody from 'components/auth/BaseAuthBody';
import passwordMessages from 'components/auth/password/Password.messages';

View File

@ -6,7 +6,7 @@ import { Link } from 'react-router';
import buttons from 'components/ui/buttons.scss';
import icons from 'components/ui/icons.scss';
import { Input, Checkbox } from 'components/ui/Form';
import { Input, Checkbox } from 'components/ui/form';
import BaseAuthBody from 'components/auth/BaseAuthBody';
import styles from './password.scss';

View File

@ -4,7 +4,7 @@ import { FormattedMessage as Message } from 'react-intl';
import Helmet from 'react-helmet';
import buttons from 'components/ui/buttons.scss';
import { Input, Checkbox } from 'components/ui/Form';
import { Input, Checkbox } from 'components/ui/form';
import BaseAuthBody from 'components/auth/BaseAuthBody';
import activationMessages from 'components/auth/activation/Activation.messages';

View File

@ -4,9 +4,8 @@ import { FormattedMessage as Message } from 'react-intl';
import { Link } from 'react-router';
import Helmet from 'react-helmet';
import { Input, Button, Checkbox } from 'components/ui/Form';
import { Input, Button, Checkbox, Form } from 'components/ui/form';
import FormModel from 'models/Form';
import { Form } from 'components/ui/Form';
import styles from 'components/profile/profileForm.scss';
import messages from './ChangePassword.messages';

View File

@ -3,7 +3,7 @@ import React, { Component, PropTypes } from 'react';
import { FormattedMessage as Message } from 'react-intl';
import FormModel from 'models/Form';
import { Form, Button, Input } from 'components/ui/Form';
import { Form, Button, Input } from 'components/ui/form';
import messages from './PasswordRequestForm.messages';

View File

@ -1,280 +0,0 @@
import React, { Component, PropTypes } from 'react';
import classNames from 'classnames';
import { intlShape } from 'react-intl';
import { uniqueId } from 'functions';
import icons from './icons.scss';
import styles from './form.scss';
import buttons from './buttons.scss';
export class Input extends Component {
static displayName = 'Input';
static propTypes = {
placeholder: PropTypes.oneOfType([
PropTypes.shape({
id: PropTypes.string
}),
PropTypes.string
]),
label: PropTypes.oneOfType([
PropTypes.shape({
id: PropTypes.string
}),
PropTypes.string
]).isRequired,
error: PropTypes.string,
icon: PropTypes.string,
skin: PropTypes.oneOf(['dark', 'light']),
color: PropTypes.oneOf(['green', 'blue', 'red', 'lightViolet', 'darkBlue'])
};
static contextTypes = {
intl: intlShape.isRequired
};
render() {
let { icon, color = 'green', skin = 'dark', error, label } = this.props;
const props = {
type: 'text',
...this.props
};
if (label) {
if (!props.id) {
props.id = uniqueId('input');
}
if (label.id) {
label = this.context.intl.formatMessage(label);
}
label = (
<label className={styles.textFieldLabel} htmlFor={props.id}>
{label}
</label>
);
}
if (props.placeholder && props.placeholder.id) {
props.placeholder = this.context.intl.formatMessage(props.placeholder);
}
let baseClass = styles.formRow;
if (icon) {
baseClass = styles.formIconRow;
icon = (
<div className={classNames(styles.textFieldIcon, icons[icon])} />
);
}
if (error) {
error = (
<div className={styles.fieldError}>
error
</div>
);
}
return (
<div className={baseClass}>
{label}
<div className={styles.textFieldContainer}>
<input ref={this.setEl} className={classNames(
styles[`${skin}TextField`],
styles[`${color}TextField`]
)} {...props} />
{icon}
</div>
{error}
</div>
);
}
setEl = (el) => {
this.el = el;
};
getValue() {
return this.el.value;
}
focus() {
this.el.focus();
setTimeout(this.el.focus.bind(this.el), 10);
}
}
export class Checkbox extends Component {
static displayName = 'Checkbox';
static propTypes = {
color: PropTypes.oneOf(['green', 'blue', 'red']),
skin: PropTypes.oneOf(['dark', 'light']),
label: PropTypes.oneOfType([
PropTypes.shape({
id: PropTypes.string
}),
PropTypes.string
]).isRequired
};
static contextTypes = {
intl: intlShape.isRequired
};
render() {
let { label, color = 'green', skin = 'dark' } = this.props;
if (label && label.id) {
label = this.context.intl.formatMessage(label);
}
return (
<div className={classNames(styles[`${color}CheckboxRow`], styles[`${skin}CheckboxRow`])}>
<label className={styles.checkboxContainer}>
<input ref={this.setEl} className={styles.checkboxInput} type="checkbox" {...this.props} />
<div className={styles.checkbox} />
{label}
</label>
</div>
);
}
setEl = (el) => {
this.el = el;
};
getValue() {
return this.el.checked ? 1 : 0;
}
focus() {
this.el.focus();
}
}
export class Button extends Component {
static displayName = 'Button';
static propTypes = {
label: PropTypes.oneOfType([
PropTypes.shape({
id: PropTypes.string
}),
PropTypes.string
]).isRequired,
block: PropTypes.bool,
color: PropTypes.oneOf(['green', 'blue', 'red', 'lightViolet', 'darkBlue'])
};
static contextTypes = {
intl: intlShape.isRequired
};
render() {
const { color = 'green', block } = this.props;
const props = {
...this.props
};
if (props.label.id) {
props.label = this.context.intl.formatMessage(props.label);
}
return (
<button className={classNames(buttons[color], {
[buttons.block]: block
})} {...props}>
{props.label}
</button>
);
}
}
export class Form extends Component {
static displayName = 'Form';
static propTypes = {
id: PropTypes.string, // and id, that uniquely identifies form contents
isLoading: PropTypes.bool,
onSubmit: PropTypes.func,
onInvalid: PropTypes.func,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
])
};
static defaultProps = {
id: 'default',
isLoading: false,
onSubmit() {},
onInvalid() {}
};
state = {
isTouched: false
};
componentWillReceiveProps(nextProps) {
if (nextProps.id !== this.props.id) {
this.setState({
isTouched: false
});
}
}
render() {
const {isLoading} = this.props;
return (
<form
className={classNames(
styles.form,
{
[styles.isFormLoading]: isLoading,
[styles.formTouched]: this.state.isTouched
}
)}
onSubmit={this.onFormSubmit}
noValidate
>
{this.props.children}
</form>
);
}
onFormSubmit = (event) => {
event.preventDefault();
if (!this.state.isTouched) {
this.setState({
isTouched: true
});
}
const form = event.currentTarget;
if (form.checkValidity()) {
this.props.onSubmit();
} else {
const firstError = form.querySelectorAll(':invalid')[0];
firstError.focus();
let errorMessage = firstError.validationMessage;
if (firstError.validity.valueMissing) {
errorMessage = `error.${firstError.name}_required`;
} else if (firstError.validity.typeMismatch) {
errorMessage = `error.${firstError.name}_invalid`;
}
this.props.onInvalid(errorMessage);
}
};
}

View File

@ -0,0 +1,45 @@
import React, { Component, PropTypes } from 'react';
import classNames from 'classnames';
import { intlShape } from 'react-intl';
import buttons from 'components/ui/buttons.scss';
export default class Button extends Component {
static displayName = 'Button';
static propTypes = {
label: PropTypes.oneOfType([
PropTypes.shape({
id: PropTypes.string
}),
PropTypes.string
]).isRequired,
block: PropTypes.bool,
color: PropTypes.oneOf(['green', 'blue', 'red', 'lightViolet', 'darkBlue'])
};
static contextTypes = {
intl: intlShape.isRequired
};
render() {
const { color = 'green', block } = this.props;
const props = {
...this.props
};
if (props.label.id) {
props.label = this.context.intl.formatMessage(props.label);
}
return (
<button className={classNames(buttons[color], {
[buttons.block]: block
})} {...props}>
{props.label}
</button>
);
}
}

View File

@ -0,0 +1,55 @@
import React, { Component, PropTypes } from 'react';
import classNames from 'classnames';
import { intlShape } from 'react-intl';
import styles from './form.scss';
export default class Checkbox extends Component {
static displayName = 'Checkbox';
static propTypes = {
color: PropTypes.oneOf(['green', 'blue', 'red']),
skin: PropTypes.oneOf(['dark', 'light']),
label: PropTypes.oneOfType([
PropTypes.shape({
id: PropTypes.string
}),
PropTypes.string
]).isRequired
};
static contextTypes = {
intl: intlShape.isRequired
};
render() {
let { label, color = 'green', skin = 'dark' } = this.props;
if (label && label.id) {
label = this.context.intl.formatMessage(label);
}
return (
<div className={classNames(styles[`${color}CheckboxRow`], styles[`${skin}CheckboxRow`])}>
<label className={styles.checkboxContainer}>
<input ref={this.setEl} className={styles.checkboxInput} type="checkbox" {...this.props} />
<div className={styles.checkbox} />
{label}
</label>
</div>
);
}
setEl = (el) => {
this.el = el;
};
getValue() {
return this.el.checked ? 1 : 0;
}
focus() {
this.el.focus();
}
}

View File

@ -0,0 +1,87 @@
import React, { Component, PropTypes } from 'react';
import classNames from 'classnames';
import styles from './form.scss';
export default class Form extends Component {
static displayName = 'Form';
static propTypes = {
id: PropTypes.string, // and id, that uniquely identifies form contents
isLoading: PropTypes.bool,
onSubmit: PropTypes.func,
onInvalid: PropTypes.func,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node
])
};
static defaultProps = {
id: 'default',
isLoading: false,
onSubmit() {},
onInvalid() {}
};
state = {
isTouched: false
};
componentWillReceiveProps(nextProps) {
if (nextProps.id !== this.props.id) {
this.setState({
isTouched: false
});
}
}
render() {
const {isLoading} = this.props;
return (
<form
className={classNames(
styles.form,
{
[styles.isFormLoading]: isLoading,
[styles.formTouched]: this.state.isTouched
}
)}
onSubmit={this.onFormSubmit}
noValidate
>
{this.props.children}
</form>
);
}
onFormSubmit = (event) => {
event.preventDefault();
if (!this.state.isTouched) {
this.setState({
isTouched: true
});
}
const form = event.currentTarget;
if (form.checkValidity()) {
this.props.onSubmit();
} else {
const firstError = form.querySelectorAll(':invalid')[0];
firstError.focus();
let errorMessage = firstError.validationMessage;
if (firstError.validity.valueMissing) {
errorMessage = `error.${firstError.name}_required`;
} else if (firstError.validity.typeMismatch) {
errorMessage = `error.${firstError.name}_invalid`;
}
this.props.onInvalid(errorMessage);
}
};
}

View File

@ -0,0 +1,108 @@
import React, { Component, PropTypes } from 'react';
import classNames from 'classnames';
import { intlShape } from 'react-intl';
import { uniqueId } from 'functions';
import icons from 'components/ui/icons.scss';
import styles from './form.scss';
export default class Input extends Component {
static displayName = 'Input';
static propTypes = {
placeholder: PropTypes.oneOfType([
PropTypes.shape({
id: PropTypes.string
}),
PropTypes.string
]),
label: PropTypes.oneOfType([
PropTypes.shape({
id: PropTypes.string
}),
PropTypes.string
]).isRequired,
error: PropTypes.string,
icon: PropTypes.string,
skin: PropTypes.oneOf(['dark', 'light']),
color: PropTypes.oneOf(['green', 'blue', 'red', 'lightViolet', 'darkBlue'])
};
static contextTypes = {
intl: intlShape.isRequired
};
render() {
let { icon, color = 'green', skin = 'dark', error, label } = this.props;
const props = {
type: 'text',
...this.props
};
if (label) {
if (!props.id) {
props.id = uniqueId('input');
}
if (label.id) {
label = this.context.intl.formatMessage(label);
}
label = (
<label className={styles.textFieldLabel} htmlFor={props.id}>
{label}
</label>
);
}
if (props.placeholder && props.placeholder.id) {
props.placeholder = this.context.intl.formatMessage(props.placeholder);
}
let baseClass = styles.formRow;
if (icon) {
baseClass = styles.formIconRow;
icon = (
<div className={classNames(styles.textFieldIcon, icons[icon])} />
);
}
if (error) {
error = (
<div className={styles.fieldError}>
error
</div>
);
}
return (
<div className={baseClass}>
{label}
<div className={styles.textFieldContainer}>
<input ref={this.setEl} className={classNames(
styles[`${skin}TextField`],
styles[`${color}TextField`]
)} {...props} />
{icon}
</div>
{error}
</div>
);
}
setEl = (el) => {
this.el = el;
};
getValue() {
return this.el.value;
}
focus() {
this.el.focus();
setTimeout(this.el.focus.bind(this.el), 10);
}
}

View File

@ -213,7 +213,7 @@
.checkbox {
composes: checkboxPosition;
composes: checkmark from './icons.scss';
composes: checkmark from 'components/ui/icons.scss';
border: 2px #dcd8cd solid;

View File

Before

Width:  |  Height:  |  Size: 317 B

After

Width:  |  Height:  |  Size: 317 B

View File

@ -0,0 +1,11 @@
import Input from './Input';
import Checkbox from './Checkbox';
import Button from './Button';
import Form from './Form';
export {
Input,
Button,
Checkbox,
Form
};