coreui
Version:
Platform Core UI
314 lines (271 loc) • 9.28 kB
JavaScript
import React, { PropTypes } from 'react';
import Button from '../Button';
import TextInput from '../TextInput';
import Shared from '../../Shared';
import defaultTheme from '../../theme/components/Form';
import RFForm from 'coreui-deps/lib/react-formal';
import compose from 'recompose/compose';
import cx from 'classnames/dedupe';
import defaultProps from 'recompose/defaultProps';
import expr from 'property-expr';
import componentFromProp from 'recompose/componentFromProp';
import getContext from 'recompose/getContext';
import lifecycle from 'recompose/lifecycle';
import mapProps from 'recompose/mapProps';
import toClass from 'recompose/toClass';
import withHandlers from 'recompose/withHandlers';
import { assoc, is, partial, merge, path as rPath } from 'ramda';
const { addInputTypes, Button: RFFormButton, Context, Field, Message, Summary, Trigger } = RFForm;
const systemStyles = { };
const handleSubmit = ({ coreuiModalContext, onSubmit }, formValue) => {
if (!coreuiModalContext) {
onSubmit(formValue);
} else {
const event = new CustomEvent(
'coreuiSubmit',
{ bubbles: true, cancelable: true, detail: { stopPropagation: false } },
);
const onHide = coreuiModalContext.onHide;
onSubmit(formValue, event);
if (is(Function, onHide) && !rPath(['detail', 'stopPropagation'], event)) {
onHide(event);
}
}
};
const FormButtonBase = componentFromProp('baseComponent');
const FormButton = mapProps(
(props) => {
const { className, component, type, ...rest } = props;
const isSubmit = type === 'submit';
const baseProps = {
...rest,
baseComponent: isSubmit ? RFFormButton : Button,
className: cx({ 'btn-primary': type === 'submit' }, className),
type,
};
return !isSubmit ? baseProps: assoc('component', component || Button, baseProps);
}
)(FormButtonBase);
const FormField = defaultProps({ errorClass: 'has-danger' })(Field);
const FormMessage = compose(
defaultProps({ errorClass: 'has-danger' }),
mapProps(({ className, ...rest }) => ({
className: cx('form-msg', className),
...rest,
}))
)(Message);
const setup = () => {
const {
CalendarInput, ComboboxInput, DateTimePickerInput, DropdownListInput,
MultiselectInput, NumberPickerInput, SelectListInput,
} = Shared.getRegisteredComponents();
const inputs = [
{ component: CalendarInput, types: ['calendar'] },
{ component: ComboboxInput, types: ['combobox'] },
{ component: DateTimePickerInput,
types: ['date', 'time', 'datepicker', 'datetimepicker', 'timepicker'],
},
{ component: DropdownListInput, types: ['dropdownlist'] },
{ component: MultiselectInput, types: ['array', 'multiselect'] },
{ component: NumberPickerInput, types: ['number'] },
{ component: SelectListInput, types: ['selectlist'] },
{
component: TextInput,
types: ['email', 'password', 'search', 'string', 'tel', 'text', 'textarea', 'url'],
},
];
inputs.forEach(({ component, types }) => {
if (component) {
types.forEach((t) => addInputTypes({ [t]: toClass(component) }));
}
});
};
const FormContainer = compose(
lifecycle({ componentWillMount() { setup(); } }),
getContext({ coreuiModalContext: PropTypes.object }),
mapProps(({ className, sheet, style, theme, ...rest }) => ({
className: cx(sheet.classes.form, theme.classes.form, className),
style: merge(theme.styles.form, style),
...rest,
})),
withHandlers({ onSubmit: (props) => partial(handleSubmit, [props]) }),
)(RFForm);
const StyledForm = Shared.injectSheet(systemStyles)(FormContainer);
const Form = (props) => <StyledForm {...props}>{props.children}</StyledForm>;
Form.Button = FormButton;
Form.Context = Context;
Form.Field = FormField;
Form.Message = FormMessage;
Form.Summary = Summary;
Form.Trigger = Trigger;
Form.addInputTypes = addInputTypes;
const classes = defaultTheme.classes;
const options = defaultTheme.options;
const styles = defaultTheme.styles;
Form.defaultProps = {
component: 'form',
delay: 300,
getter: (path, model = {}) =>(path ? expr.getter(path, true)(model) : model),
strict: false,
theme: { classes, options, styles },
};
Form.displayName = 'Form';
Form.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
/**
* A tag name or Component class the Form should render.
*
* If `null` are `false` the form will simply render it's child. In
* this instance there must only be one child.
*/
component: PropTypes.oneOfType([
PropTypes.func,
PropTypes.string,
PropTypes.oneOf([null, false]),
]).isRequired,
/**
* Time in milliseconds that validations should be debounced. Reduces the amount of validation calls
* made at the expense of a slight delay. Helpful for performance.
*/
delay: PropTypes.number,
/**
* An object hash of field errors for the form. The object should be keyed with paths
* with the values being an array of messages or message objects. Errors can be
* left [uncontrolled](/controllables) (use `defaultErrors` to set an initial value)
* or managed along with the `onError` callback. You can use any object shape you'd like for
* messages, as long as you provide the Form.Message component an `extract` prop that
* understands how to pull out the strings message. By default it understands strings and objects
* with a `'message'` property.
*
* ```js
* <Form errors={{
* "name.first": [
* 'First names are required',
* {
* message: "Names must be at least 2 characters long",
* type: 'min'
* }
* ],
* }}/>
* ```
*/
errors: PropTypes.object,
/**
* A value getter function. `getter` is called with `path` and `value` and
* should return the plain **javascript** value at the path.
*
* ```js
* function(
* path: string,
* value: any,
* ) -> object
* ```
*/
getter: PropTypes.func,
/**
* Turns off input validation for the Form, value updates will continue to work.
*/
noValidate: PropTypes.bool,
/**
* Callback that is called when the `value` prop changes.
*
* ```js
* function(
* value: object,
* updatedPaths: array<string>
* )
* ```
*/
onChange: PropTypes.func,
/**
* Callback that is called when a validation error occurs. It is called with an `errors` object
*
* ```editable
* <Form schema={modelSchema}
* defaultValue={modelSchema.default()}
* errors={this.state ? this.state.errors : {}}
* onError={errors => {
* if( errors.dateOfBirth )
* errors.dateOfBirth = 'hijacked!'
* this.setState({ errors })
* }}>
*
* <Form.Field name='dateOfBirth'/>
* <Form.Message for='dateOfBirth'/>
*
* <Form.Button type='submit'>Submit</Form.Button>
* </Form>
* ```
*/
onError: PropTypes.func,
/**
* Callback that is fired when the native onSubmit event is triggered. Only relevant when
* the `component` prop renders a `<form/>` tag. onInvalidSubmit will trigger only if the form is invalid.
*
* ```js
* function onInvalidSubmit(errors){
* // do something with errors
* }
* ```
*/
onInvalidSubmit: PropTypes.func,
/**
* Callback that is fired when the native onSubmit event is triggered. Only relevant when
* the `component` prop renders a `<form/>` tag. onSubmit will trigger only if the form is valid.
*
* ```js
* function onSubmit(formValue){
* // do something with valid value
* }
* ```
*/
onSubmit: PropTypes.func,
/**
* Callback that is called whenever a validation is triggered.
* It is called _before_ the validation is actually run.
* ```js
* function onValidate(event){
* let { type, fields, args } = event
* }
* ```
*/
onValidate: PropTypes.func,
/**
* A Yup schema that validates the Form `value` prop. Used to validate the form input values
* For more information about the yup api check out: https://github.com/jquense/yup/blob/master/README.md
* @type {YupSchema}
*/
schema: PropTypes.any,
/**
* A value setter function. `setter` is called with `path`, the form `value` and the path `value`.
* The `setter` must return updated form `value`, which allows you to leave the original value unmutated.
*
* The default implementation uses the [react immutability helpers](http://facebook.github.io/react/docs/update.html),
* letting you treat the form `value` as immutable.
* ```js
* function(
* path: string,
* formValue: object,
* pathValue: any
* ) -> object
* ```
*/
setter: PropTypes.func,
/**
* Validations will be strict, making no attempt to coarce input values to the appropriate type.
*/
strict: PropTypes.bool,
theme: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
/**
* Form value object, can be left [uncontrolled](/controllables);
* use the `defaultValue` prop to initialize an uncontrolled form.
*/
value: PropTypes.object,
};
export {
addInputTypes, FormButton as Button, Context, FormField as Field,
FormMessage as Message, Summary, Trigger,
};
Shared.registerComponent('Form', Form);
export default Form;