├── .babelrc ├── .eslintrc ├── .flowconfig ├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── package.json ├── src ├── form │ ├── Form.js │ ├── FormField.js │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── utils.spec.js.snap │ │ └── utils.spec.js │ └── utils.js ├── index.js └── validation │ ├── BooleanValidations.js │ ├── BrazilValidations.js │ ├── DateValidations.js │ ├── StringValidations.js │ ├── Validation.js │ └── __tests__ │ ├── BooleanValidations.spec.js │ ├── BrazilValidations.spec.js │ ├── DateValidations.spec.js │ ├── StringValidations.spec.js │ ├── Validation.spec.js │ └── __snapshots__ │ ├── BooleanValidations.spec.js.snap │ ├── BrazilValidations.spec.js.snap │ ├── DateValidations.spec.js.snap │ ├── StringValidations.spec.js.snap │ └── Validation.spec.js.snap ├── storybook ├── common │ ├── consts.js │ ├── form │ │ ├── Form.js │ │ ├── FormButton.js │ │ ├── FormGroup.js │ │ ├── FormInput.js │ │ ├── FormReset.js │ │ ├── FormSelect.js │ │ └── FormSubmit.js │ └── index.js ├── config.js └── stories │ ├── Form.Field.story.js │ ├── Form.story.js │ └── Validation.story.js ├── test ├── mocks │ └── localStorage.js └── utils.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["flow", "es2015", "react", "stage-0"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "browser": true, 6 | "jest": true 7 | }, 8 | "settings": { 9 | "import/resolver": "webpack" 10 | }, 11 | "rules": { 12 | "array-callback-return": 0, 13 | "arrow-parens": 0, 14 | "react/jsx-filename-extension": 0, 15 | "import/extensions": 0, 16 | "max-len": [1, 120, 4, {"ignoreRegExpLiterals": true}], 17 | "jsx-a11y/no-static-element-interactions": 0, 18 | "react/no-unescaped-entities": 0, 19 | "react/forbid-prop-types": 0, 20 | "react/jsx-wrap-multilines": 0, 21 | "no-use-before-define": 0, 22 | "react/prop-types": 0, 23 | "react/jsx-boolean-value": 0, 24 | "default-case": 0, 25 | "react/no-children-prop": 0, 26 | "jsx-a11y/img-has-alt": 0, 27 | "comma-dangle": 0, 28 | "no-return-assign": 0, 29 | "consistent-return": 0, 30 | "class-methods-use-this": 0, 31 | "operator-assignment": 0, 32 | "no-plusplus": 0, 33 | "no-mixed-operators": 0, 34 | "import/prefer-default-export": 0, 35 | "react/no-did-mount-set-state": 0, 36 | "prefer-const": [ 2, { "destructuring": "all" }], 37 | "react/prefer-stateless-function": [2, { "ignorePureComponents": true }] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /node_modules/.* 3 | 4 | [include] 5 | 6 | [libs] 7 | 8 | [options] 9 | sharedmemory.hash_table_pow=21 10 | esproposal.export_star_as=enable 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | .idea 5 | storybook-static 6 | coverage 7 | lib 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | .idea 5 | storybook-static 6 | coverage 7 | .babelrc 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Entria Tech Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Formia 2 | 3 | ## Important - Deprecation 4 | 5 | This library is depreacated. We'll move all our devo resources to Formik 6 | [Formik Github Repo](https://github.com/jaredpalmer/formik) 7 | 8 | ## Install 9 | 10 | ``` 11 | npm i @entria/utils lodash.get lodash.isequal lodash.set @entria/formia --save 12 | yarn add @entria/utils lodash.get lodash.isequal lodash.set @entria/formia 13 | ``` 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@entria/formia", 3 | "version": "0.0.10", 4 | "description": "Entria forms", 5 | "keywords": [ 6 | "react", 7 | "entria", 8 | "forms", 9 | "formia" 10 | ], 11 | "license": "ISC", 12 | "homepage": "https://github.com/entria/formia#readme", 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/entria/formia" 16 | }, 17 | "main": "lib/index.js", 18 | "module": "src/index.js", 19 | "jsnext:main": "src/index.js", 20 | "devDependencies": { 21 | "@entria/utils": "^0.0.9", 22 | "@kadira/storybook": "^2.35.3", 23 | "babel-cli": "^6.24.1", 24 | "babel-eslint": "^7.2.3", 25 | "babel-preset-es2015": "^6.24.1", 26 | "babel-preset-flow": "^6.23.0", 27 | "babel-preset-react": "^6.24.1", 28 | "babel-preset-stage-0": "^6.24.1", 29 | "eslint": "^3.19.0", 30 | "eslint-config-airbnb": "^15.0.1", 31 | "eslint-import-resolver-webpack": "^0.8.1", 32 | "eslint-plugin-import": "^2.3.0", 33 | "eslint-plugin-jsx-a11y": "^5.0.3", 34 | "eslint-plugin-react": "^7.1.0", 35 | "flow-bin": "^0.53.0", 36 | "gh-pages": "^1.0.0", 37 | "jest": "^20.0.4", 38 | "lint-staged": "^4.0.0", 39 | "lodash.get": "^4.4.2", 40 | "lodash.isequal": "^4.5.0", 41 | "lodash.set": "^4.3.2", 42 | "pre-commit": "^1.2.2", 43 | "prettier": "^1.4.4", 44 | "prop-types": "^15.5.10", 45 | "react": "^15.6.1", 46 | "react-dom": "^15.6.1" 47 | }, 48 | "dependencies": {}, 49 | "peerDependencies": { 50 | "@entria/utils": ">=0.0.9", 51 | "lodash.get": ">=4.4.2", 52 | "lodash.isequal": ">=4.5.0", 53 | "lodash.set": ">=4.3.2", 54 | "prop-types": ">=15.5.10", 55 | "react": ">=0.14", 56 | "react-dom": ">=0.14" 57 | }, 58 | "jest": { 59 | "setupFiles": [ 60 | "/test/mocks/localStorage.js" 61 | ] 62 | }, 63 | "lint-staged": { 64 | "src/**/*.js": [ 65 | "prettier --write --single-quote true --trailing-comma all --print-width 100", 66 | "yarn lint", 67 | "git add" 68 | ] 69 | }, 70 | "pre-commit": "lint:staged", 71 | "scripts": { 72 | "prepublish": "npm run build", 73 | "build": "babel src -d lib", 74 | "test": "jest --coverage --runInBand --forceExit", 75 | "test:watch": "jest --watch --coverage", 76 | "lint": "eslint --ext js src", 77 | "lint:staged": "lint-staged", 78 | "start": "start-storybook -p 6006 -c storybook", 79 | "demo:build": "build-storybook -c storybook", 80 | "demo:gh-pages": "gh-pages -d storybook-static/", 81 | "demo:publish": "npm run demo:build && npm run demo:gh-pages", 82 | "check": "npm run lint && npm run test", 83 | "release:patch": "npm run check && npm version patch && git push --follow-tags && npm publish --access public", 84 | "release:minor": "npm run check && npm version minor && git push --follow-tags && npm publish --access public", 85 | "release:major": "npm run check && npm version major && git push --follow-tags && npm publish --access public" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/form/Form.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { Component } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import get from 'lodash.get'; 5 | import set from 'lodash.set'; 6 | import isEqual from 'lodash.isequal'; 7 | 8 | import { Validation } from '../'; 9 | import FormField from './FormField'; 10 | import { handleInitialValuesUpdate } from './utils'; 11 | 12 | type Values = { 13 | [string]: any, 14 | }; 15 | 16 | type Validations = { 17 | [string]: Array, 18 | }; 19 | 20 | export type OnChange = (values: Values, errors: FormErrors) => void; 21 | 22 | type FormErrors = { 23 | [string]: FieldErrors, 24 | }; 25 | export type FieldErrors = Array; 26 | type Error = { 27 | code: string, 28 | message: string, 29 | params: Array, 30 | }; 31 | 32 | type Props = { 33 | initialValues?: Values, 34 | validations?: Validations, 35 | onChange?: OnChange, 36 | }; 37 | type State = { 38 | values: Values, 39 | }; 40 | class Form extends Component { 41 | static defaultProps = { 42 | initialValues: {}, 43 | validations: {}, 44 | onChange: () => null, 45 | }; 46 | 47 | static childContextTypes = { 48 | setValue: PropTypes.func, 49 | getValue: PropTypes.func, 50 | getValues: PropTypes.func, 51 | getError: PropTypes.func, 52 | getErrors: PropTypes.func, 53 | isDirty: PropTypes.func, 54 | isPristine: PropTypes.func, 55 | reset: PropTypes.func, 56 | }; 57 | 58 | state = { 59 | values: this.props.initialValues, 60 | }; 61 | 62 | getChildContext() { 63 | return { 64 | setValue: (name: string, value: any, onChange: OnChange) => 65 | this.setValue(name, value, onChange), 66 | getValue: (name: string): any => this.getValue(name), 67 | getValues: (): Values => this.getValues(), 68 | getError: (name: string): FieldErrors => this.getError(name), 69 | getErrors: (): FormErrors => this.getErrors(), 70 | isDirty: (name: string): boolean => this.isDirty(name), 71 | isPristine: (name: string): boolean => this.isPristine(name), 72 | reset: () => this.reset(), 73 | }; 74 | } 75 | 76 | componentWillReceiveProps(nextProps: Props) { 77 | const currentValues = { ...this.state.values }; 78 | const currentInitialvalues = { ...this.props.initialValues }; 79 | const newInitialValues = { ...nextProps.initialValues }; 80 | 81 | const values = handleInitialValuesUpdate(currentValues, currentInitialvalues, newInitialValues); 82 | 83 | if (isEqual(this.state.values, values)) { 84 | return; 85 | } 86 | 87 | this.setState({ values }, () => { 88 | const errors = this.getErrors(); 89 | 90 | this.props.onChange(values, errors); 91 | }); 92 | } 93 | 94 | setValue = (name: string, value: any, onChange: OnChange) => { 95 | const values = { 96 | ...this.state.values, 97 | }; 98 | 99 | set(values, name, value); 100 | 101 | this.setState({ values }, () => { 102 | const errors = this.getErrors(); 103 | 104 | this.props.onChange(values, errors); 105 | onChange(values, errors); 106 | }); 107 | }; 108 | 109 | getValue = (name: string): any => get(this.state.values, name, null); 110 | 111 | getValues = (): Values => this.state.values; 112 | 113 | getError = (name: string): FieldErrors => { 114 | const { validations } = this.props; 115 | if (!validations || !validations[name]) { 116 | return null; 117 | } 118 | 119 | const errors = Validation.validate( 120 | { [name]: this.getValue(name) }, 121 | { [name]: validations[name] }, 122 | ); 123 | 124 | return errors[name] ? errors[name] : null; 125 | }; 126 | 127 | getErrors = (): FormErrors => { 128 | const { validations } = this.props; 129 | const { values } = this.state; 130 | 131 | return Validation.validate(values, validations); 132 | }; 133 | 134 | isDirty = (name: string): boolean => { 135 | const currentValue = this.getValue(name); 136 | const initialValue = get(this.props.initialValues, name, null); 137 | 138 | return !isEqual(currentValue, initialValue); 139 | }; 140 | 141 | isPristine = (name: string): boolean => { 142 | const currentValue = this.getValue(name); 143 | const initialValue = get(this.props.initialValues, name, null); 144 | 145 | return isEqual(currentValue, initialValue); 146 | }; 147 | 148 | reset = () => { 149 | const values = { ...this.props.initialValues }; 150 | 151 | this.setState({ values }, () => { 152 | const errors = this.getErrors(); 153 | 154 | this.props.onChange(values, errors); 155 | }); 156 | }; 157 | 158 | render() { 159 | return this.props.children; 160 | } 161 | } 162 | 163 | Form.Field = FormField; 164 | 165 | export default Form; 166 | -------------------------------------------------------------------------------- /src/form/FormField.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { Component } from 'react'; 3 | import PropTypes from 'prop-types'; 4 | 5 | import type { OnChange, FieldErrors } from './Form'; 6 | 7 | type Props = { 8 | name: string, 9 | component: any, 10 | onChange?: OnChange, 11 | }; 12 | type State = { 13 | value: any, 14 | }; 15 | class FormField extends Component { 16 | static defaultProps = { 17 | onChange: () => null, 18 | }; 19 | 20 | static contextTypes = { 21 | setValue: PropTypes.func, 22 | getValue: PropTypes.func, 23 | getError: PropTypes.func, 24 | isDirty: PropTypes.func, 25 | isPristine: PropTypes.func, 26 | }; 27 | 28 | state = { 29 | value: null, 30 | }; 31 | 32 | setValue = (value: any) => { 33 | const { setValue } = this.context; 34 | const { name, onChange } = this.props; 35 | 36 | setValue(name, value, (values, errors) => { 37 | this.setState({ value }, () => onChange(values, errors)); 38 | }); 39 | }; 40 | 41 | getValue = (): any => { 42 | const { getValue } = this.context; 43 | const { name } = this.props; 44 | 45 | return getValue(name); 46 | }; 47 | 48 | getError = (): FieldErrors => { 49 | const { getError } = this.context; 50 | const { name } = this.props; 51 | 52 | return getError(name); 53 | }; 54 | 55 | isDirty = (): boolean => { 56 | const { isDirty } = this.context; 57 | const { name } = this.props; 58 | 59 | return isDirty(name); 60 | }; 61 | 62 | isPristine = (): boolean => { 63 | const { isPristine } = this.context; 64 | const { name } = this.props; 65 | 66 | return isPristine(name); 67 | }; 68 | 69 | render() { 70 | const CustomComponent = this.props.component; 71 | 72 | return ( 73 | 81 | ); 82 | } 83 | } 84 | 85 | export default FormField; 86 | -------------------------------------------------------------------------------- /src/form/__tests__/__snapshots__/utils.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`handleInitialValuesUpdate - async loading 1`] = ` 4 | Object { 5 | "name": "Barney Stinson", 6 | } 7 | `; 8 | 9 | exports[`handleInitialValuesUpdate - async update if field is dirty 1`] = ` 10 | Object { 11 | "name": "Legendary Barney Stinson", 12 | } 13 | `; 14 | 15 | exports[`handleInitialValuesUpdate - async update if field is pristine 1`] = ` 16 | Object { 17 | "name": "Barney Stinson Awesome", 18 | } 19 | `; 20 | 21 | exports[`handleInitialValuesUpdate - default props 1`] = `Object {}`; 22 | -------------------------------------------------------------------------------- /src/form/__tests__/utils.spec.js: -------------------------------------------------------------------------------- 1 | import { handleInitialValuesUpdate } from '../utils'; 2 | 3 | import { setupTest } from '../../../test/utils'; 4 | 5 | beforeEach(async () => setupTest()); 6 | 7 | it('handleInitialValuesUpdate - default props', () => { 8 | expect(handleInitialValuesUpdate()).toMatchSnapshot(); 9 | }); 10 | 11 | it('handleInitialValuesUpdate - async loading', () => { 12 | const currentValues = {}; 13 | const currentInitialValues = {}; 14 | const newInitialValues = { name: 'Barney Stinson' }; 15 | 16 | expect( 17 | handleInitialValuesUpdate(currentValues, currentInitialValues, newInitialValues), 18 | ).toMatchSnapshot(); 19 | }); 20 | 21 | it('handleInitialValuesUpdate - async update if field is pristine', () => { 22 | const currentValues = { name: 'Barney Stinson' }; 23 | const currentInitialValues = { name: 'Barney Stinson' }; 24 | const newInitialValues = { name: 'Barney Stinson Awesome' }; 25 | 26 | expect( 27 | handleInitialValuesUpdate(currentValues, currentInitialValues, newInitialValues), 28 | ).toMatchSnapshot(); 29 | }); 30 | 31 | it('handleInitialValuesUpdate - async update if field is dirty', () => { 32 | const currentValues = { name: 'Legendary Barney Stinson' }; 33 | const currentInitialValues = { name: 'Barney Stinson' }; 34 | const newInitialValues = { name: 'Barney Stinson Awesome' }; 35 | 36 | expect( 37 | handleInitialValuesUpdate(currentValues, currentInitialValues, newInitialValues), 38 | ).toMatchSnapshot(); 39 | }); 40 | -------------------------------------------------------------------------------- /src/form/utils.js: -------------------------------------------------------------------------------- 1 | import get from 'lodash.get'; 2 | import set from 'lodash.set'; 3 | import isEqual from 'lodash.isequal'; 4 | 5 | export const handleInitialValuesUpdate = ( 6 | currentValues = {}, 7 | currentInitialValues = {}, 8 | newInitialValues = {}, 9 | ) => { 10 | const updatedValues = { ...currentValues }; 11 | 12 | Object.keys(newInitialValues).map(fieldName => { 13 | const fieldCurrentValue = get(currentValues, fieldName, null); 14 | const fieldCurrentInitialValue = get(currentInitialValues, fieldName, null); 15 | const isFieldPristine = isEqual(fieldCurrentValue, fieldCurrentInitialValue); 16 | 17 | if (!isFieldPristine) { 18 | return; 19 | } 20 | 21 | const fieldNewInitialValue = get(newInitialValues, fieldName, null); 22 | set(updatedValues, fieldName, fieldNewInitialValue); 23 | }); 24 | 25 | return updatedValues; 26 | }; 27 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export Form from './form/Form'; 2 | export * as Validation from './validation/Validation'; 3 | -------------------------------------------------------------------------------- /src/validation/BooleanValidations.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { isEmpty } from '@entria/utils'; 3 | 4 | import type { RuleError } from './Validation'; 5 | 6 | export const isTrue = () => (value: any): ?RuleError => { 7 | if (isEmpty(value)) { 8 | return null; 9 | } 10 | 11 | const isValid = value === true; 12 | if (isValid) { 13 | return null; 14 | } 15 | 16 | return { 17 | code: 'Validation.Booleans.isTrue', 18 | message: 'Must be true', 19 | params: [], 20 | }; 21 | }; 22 | 23 | export const isFalse = () => (value: any): ?RuleError => { 24 | if (isEmpty(value)) { 25 | return null; 26 | } 27 | 28 | const isValid = value === false; 29 | if (isValid) { 30 | return null; 31 | } 32 | 33 | return { 34 | code: 'Validation.Booleans.isFalse', 35 | message: 'Must be false', 36 | params: [], 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /src/validation/BrazilValidations.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { isEmpty, Strings } from '@entria/utils'; 3 | 4 | import type { RuleError } from './Validation'; 5 | 6 | export const cpf = () => { 7 | const validateFirstDigit = (value: any): boolean => { 8 | let Soma = 0; 9 | for (let i = 1; i <= 9; i++) { 10 | Soma = Soma + parseInt(value.substring(i - 1, i), 10) * (11 - i); 11 | } 12 | 13 | let Resto = Soma * 10 % 11; 14 | if (Resto === 10 || Resto === 11) { 15 | Resto = 0; 16 | } 17 | 18 | return Resto === parseInt(value.substring(9, 10), 10); 19 | }; 20 | 21 | const validateSecondDigit = (value: any): boolean => { 22 | let Soma = 0; 23 | for (let i = 1; i <= 10; i++) { 24 | Soma = Soma + parseInt(value.substring(i - 1, i), 10) * (12 - i); 25 | } 26 | 27 | let Resto = Soma * 10 % 11; 28 | if (Resto === 10 || Resto === 11) { 29 | Resto = 0; 30 | } 31 | 32 | return Resto === parseInt(value.substring(10, 11), 10); 33 | }; 34 | 35 | return (value: any): ?RuleError => { 36 | if (isEmpty(value)) { 37 | return null; 38 | } 39 | 40 | const sanitizedValue = Strings.numbers(value); 41 | if (sanitizedValue === '00000000000') { 42 | return { 43 | code: 'Validation.Brazil.cpf', 44 | message: 'Invalid CPF', 45 | params: [], 46 | }; 47 | } 48 | 49 | const isFirstDigitValid = validateFirstDigit(sanitizedValue); 50 | const isSecondDigitValid = validateSecondDigit(sanitizedValue); 51 | const isValid = isFirstDigitValid && isSecondDigitValid; 52 | if (isValid) { 53 | return null; 54 | } 55 | 56 | return { 57 | code: 'Validation.Brazil.cpf', 58 | message: 'Invalid CPF', 59 | params: [], 60 | }; 61 | }; 62 | }; 63 | 64 | export const phone = () => (value: any): ?RuleError => { 65 | if (isEmpty(value)) { 66 | return null; 67 | } 68 | 69 | const sanitizedValue = Strings.numbers(value); 70 | const thirdNumber = sanitizedValue.substr(2, 1); 71 | 72 | const validCellphone = sanitizedValue.length === 11 ? thirdNumber === '9' : true; 73 | const validLandline = sanitizedValue.length === 10 ? thirdNumber !== '0' : true; 74 | const isValid = validCellphone && validLandline; 75 | if (isValid) { 76 | return null; 77 | } 78 | 79 | return { 80 | code: 'Validation.Brazil.phone', 81 | message: 'Invalid phone number', 82 | params: [], 83 | }; 84 | }; 85 | -------------------------------------------------------------------------------- /src/validation/DateValidations.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { isEmpty, Dates } from '@entria/utils'; 3 | 4 | import type { RuleError } from './Validation'; 5 | 6 | export const isValid = () => (value: any): ?RuleError => { 7 | if (isEmpty(value)) { 8 | return null; 9 | } 10 | 11 | if (Dates.isValid(value)) { 12 | return null; 13 | } 14 | 15 | return { 16 | code: 'Validation.Dates.isValid', 17 | message: 'Invalid date', 18 | params: [], 19 | }; 20 | }; 21 | 22 | export const isFuture = () => (value: any): ?RuleError => { 23 | if (isEmpty(value)) { 24 | return null; 25 | } 26 | 27 | if (Dates.isFuture(value)) { 28 | return null; 29 | } 30 | 31 | return { 32 | code: 'Validation.Dates.isFuture', 33 | message: 'Must be greater than today', 34 | params: [], 35 | }; 36 | }; 37 | 38 | export const isPast = () => (value: any): ?RuleError => { 39 | if (isEmpty(value)) { 40 | return null; 41 | } 42 | 43 | if (Dates.isPast(value)) { 44 | return null; 45 | } 46 | 47 | return { 48 | code: 'Validation.Dates.isPast', 49 | message: 'Must be lesser than today', 50 | params: [], 51 | }; 52 | }; 53 | 54 | export const isBetween = (minor: Date, major: Date) => (value: any): ?RuleError => { 55 | if (isEmpty(value)) { 56 | return null; 57 | } 58 | 59 | const isBiggerThanMinor = Dates.compare(value, minor) >= 0; 60 | const isLessThanMajor = Dates.compare(value, major) <= 0; 61 | if (isBiggerThanMinor && isLessThanMajor) { 62 | return null; 63 | } 64 | 65 | return { 66 | code: 'Validation.Dates.isBetween', 67 | message: `Must be greater than ${minor.toString()} and lesser than ${major.toString()}`, 68 | params: [minor, major], 69 | }; 70 | }; 71 | -------------------------------------------------------------------------------- /src/validation/StringValidations.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { isEmpty, Strings } from '@entria/utils'; 3 | 4 | import type { RuleError } from './Validation'; 5 | 6 | export const isAlpha = () => (value: any): ?RuleError => { 7 | if (isEmpty(value)) { 8 | return null; 9 | } 10 | 11 | if (Strings.isAlpha(value)) { 12 | return null; 13 | } 14 | 15 | return { 16 | code: 'Validation.Strings.isAlpha', 17 | message: 'Only alpha characters are allowed', 18 | params: [], 19 | }; 20 | }; 21 | 22 | export const isEmail = () => (value: any): ?RuleError => { 23 | if (isEmpty(value)) { 24 | return null; 25 | } 26 | 27 | if (Strings.isEmail(value)) { 28 | return null; 29 | } 30 | 31 | return { 32 | code: 'Validation.Strings.isEmail', 33 | message: 'Invalid email address', 34 | params: [], 35 | }; 36 | }; 37 | 38 | export const minLength = (quantity: number) => (value: any): ?RuleError => { 39 | if (isEmpty(value)) { 40 | return null; 41 | } 42 | 43 | const isValid = value.length >= quantity; 44 | if (isValid) { 45 | return null; 46 | } 47 | 48 | return { 49 | code: 'Validation.Strings.minLength', 50 | message: `Must have at least ${quantity} characters`, 51 | params: [quantity], 52 | }; 53 | }; 54 | 55 | export const maxLength = (quantity: number) => (value: any): ?RuleError => { 56 | if (isEmpty(value)) { 57 | return null; 58 | } 59 | 60 | const isValid = value.length <= quantity; 61 | if (isValid) { 62 | return null; 63 | } 64 | 65 | return { 66 | code: 'Validation.Strings.maxLength', 67 | message: `Must have at most ${quantity} characters`, 68 | params: [quantity], 69 | }; 70 | }; 71 | 72 | export const minWords = (quantity: number) => (value: any): ?RuleError => { 73 | if (isEmpty(value)) { 74 | return null; 75 | } 76 | 77 | const words = value ? value.trim().split(' ') : ''; 78 | const isValid = words.length >= quantity; 79 | if (isValid) { 80 | return null; 81 | } 82 | 83 | return { 84 | code: 'Validation.Strings.minWords', 85 | message: `Must have at least ${quantity} words`, 86 | params: [quantity], 87 | }; 88 | }; 89 | 90 | export const maxWords = (quantity: number) => (value: any): ?RuleError => { 91 | if (isEmpty(value)) { 92 | return null; 93 | } 94 | 95 | const words = value ? value.trim().split(' ') : ''; 96 | const isValid = words.length <= quantity; 97 | if (isValid) { 98 | return null; 99 | } 100 | 101 | return { 102 | code: 'Validation.Strings.maxWords', 103 | message: `Must have at most ${quantity} words`, 104 | params: [quantity], 105 | }; 106 | }; 107 | -------------------------------------------------------------------------------- /src/validation/Validation.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import get from 'lodash.get'; 3 | import { isEmpty } from '@entria/utils'; 4 | 5 | export * as Booleans from './BooleanValidations'; 6 | export * as Brazil from './BrazilValidations'; 7 | export * as Dates from './DateValidations'; 8 | export * as Strings from './StringValidations'; 9 | 10 | export type RuleError = { 11 | code: string, 12 | message: string, 13 | params: Array, 14 | }; 15 | 16 | type ValidationValues = { 17 | [string]: any, 18 | }; 19 | 20 | type ValidationRules = { 21 | [string]: any, 22 | }; 23 | 24 | type ValidationErrors = { 25 | [string]: Array, 26 | }; 27 | 28 | export const validate = ( 29 | values: ValidationValues, 30 | validations: ValidationRules = {}, 31 | ): ValidationErrors => { 32 | const errors = {}; 33 | 34 | Object.keys(validations).forEach(field => { 35 | const value = get(values, field); 36 | const rules = validations[field]; 37 | 38 | const fieldErrors = rules.map(rule => rule(value)).filter(error => error !== null); 39 | 40 | if (fieldErrors.length > 0) { 41 | errors[field] = fieldErrors; 42 | } 43 | }); 44 | 45 | return errors; 46 | }; 47 | 48 | export const required = () => (value: any): ?RuleError => { 49 | const isValid = !isEmpty(value); 50 | if (isValid) { 51 | return null; 52 | } 53 | 54 | return { 55 | code: 'Validation.required', 56 | message: 'Required field', 57 | params: [], 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /src/validation/__tests__/BooleanValidations.spec.js: -------------------------------------------------------------------------------- 1 | import * as Booleans from '../BooleanValidations'; 2 | 3 | import { setupTest } from '../../../test/utils'; 4 | 5 | beforeEach(async () => setupTest()); 6 | 7 | it('isTrue - empty', () => { 8 | expect(Booleans.isTrue()()).toMatchSnapshot(); 9 | }); 10 | 11 | it('isTrue - true', () => { 12 | expect(Booleans.isTrue()(true)).toMatchSnapshot(); 13 | }); 14 | 15 | it('isTrue - another value', () => { 16 | expect(Booleans.isTrue()('anything here')).toMatchSnapshot(); 17 | }); 18 | 19 | it('isFalse - empty', () => { 20 | expect(Booleans.isFalse()()).toMatchSnapshot(); 21 | }); 22 | 23 | it('isFalse - false', () => { 24 | expect(Booleans.isFalse()(false)).toMatchSnapshot(); 25 | }); 26 | 27 | it('isFalse - another value', () => { 28 | expect(Booleans.isFalse()('anything here')).toMatchSnapshot(); 29 | }); 30 | -------------------------------------------------------------------------------- /src/validation/__tests__/BrazilValidations.spec.js: -------------------------------------------------------------------------------- 1 | import * as Brazil from '../BrazilValidations'; 2 | 3 | import { setupTest } from '../../../test/utils'; 4 | 5 | beforeEach(async () => setupTest()); 6 | 7 | it('cpf - empty', () => { 8 | expect(Brazil.cpf()()).toMatchSnapshot(); 9 | }); 10 | 11 | it('cpf - valid document', () => { 12 | expect(Brazil.cpf()('15516405114')).toMatchSnapshot(); 13 | }); 14 | 15 | it('cpf - bunch of zeros', () => { 16 | expect(Brazil.cpf()('00000000000')).toMatchSnapshot(); 17 | }); 18 | 19 | it('cpf - invalid', () => { 20 | expect(Brazil.cpf()('12345678900')).toMatchSnapshot(); 21 | }); 22 | 23 | it('phone - empty', () => { 24 | expect(Brazil.phone()()).toMatchSnapshot(); 25 | }); 26 | 27 | it('phone - valid cellphone', () => { 28 | expect(Brazil.phone()('49991237830')).toMatchSnapshot(); 29 | }); 30 | 31 | it('phone - valid landline', () => { 32 | expect(Brazil.phone()('4936310000')).toMatchSnapshot(); 33 | }); 34 | 35 | it('phone - invalid cellphone', () => { 36 | expect(Brazil.phone()('49012345678')).toMatchSnapshot(); 37 | }); 38 | 39 | it('phone - invalid landline', () => { 40 | expect(Brazil.phone()('4901234566')).toMatchSnapshot(); 41 | }); 42 | -------------------------------------------------------------------------------- /src/validation/__tests__/DateValidations.spec.js: -------------------------------------------------------------------------------- 1 | import * as Dates from '../DateValidations'; 2 | 3 | import { setupTest } from '../../../test/utils'; 4 | 5 | beforeEach(async () => setupTest()); 6 | 7 | it('isValid - empty', () => { 8 | expect(Dates.isValid()()).toMatchSnapshot(); 9 | }); 10 | 11 | it('isValid - valid date', () => { 12 | expect(Dates.isValid()('2000-01-01')).toMatchSnapshot(); 13 | }); 14 | 15 | it('isValid - invalid date', () => { 16 | expect(Dates.isValid()('2000-01-35')).toMatchSnapshot(); 17 | }); 18 | 19 | it('isFuture - empty', () => { 20 | expect(Dates.isFuture()()).toMatchSnapshot(); 21 | }); 22 | 23 | it('isFuture - future date', () => { 24 | expect(Dates.isFuture()('2050-01-01')).toMatchSnapshot(); 25 | }); 26 | 27 | it('isFuture - past date', () => { 28 | expect(Dates.isFuture()('2000-01-01')).toMatchSnapshot(); 29 | }); 30 | 31 | it('isPast - empty', () => { 32 | expect(Dates.isPast()()).toMatchSnapshot(); 33 | }); 34 | 35 | it('isPast - past date', () => { 36 | expect(Dates.isPast()('2000-01-01')).toMatchSnapshot(); 37 | }); 38 | 39 | it('isPast - future date', () => { 40 | expect(Dates.isPast()('2050-01-01')).toMatchSnapshot(); 41 | }); 42 | 43 | it('isBetween - empty', () => { 44 | expect(Dates.isBetween('2000-01-01', '2000-12-31')()).toMatchSnapshot(); 45 | }); 46 | 47 | it('isBetween - is between', () => { 48 | expect(Dates.isBetween('2000-01-01', '2000-12-31')('2000-01-01')).toMatchSnapshot(); 49 | }); 50 | 51 | it('isBetween - is not between', () => { 52 | expect(Dates.isBetween('2000-01-01', '2000-12-31')('2050-01-01')).toMatchSnapshot(); 53 | }); 54 | -------------------------------------------------------------------------------- /src/validation/__tests__/StringValidations.spec.js: -------------------------------------------------------------------------------- 1 | import * as Strings from '../StringValidations'; 2 | 3 | import { setupTest } from '../../../test/utils'; 4 | 5 | beforeEach(async () => setupTest()); 6 | 7 | it('isAlpha - empty', () => { 8 | expect(Strings.isAlpha()()).toMatchSnapshot(); 9 | }); 10 | 11 | it('isAlpha - valid alpha', () => { 12 | expect(Strings.isAlpha()('John Doe')).toMatchSnapshot(); 13 | }); 14 | 15 | it('isAlpha - invalid alpha', () => { 16 | expect(Strings.isAlpha()('John Doe #@*&')).toMatchSnapshot(); 17 | }); 18 | 19 | it('isEmail - empty', () => { 20 | expect(Strings.isEmail()()).toMatchSnapshot(); 21 | }); 22 | 23 | it('isEmail - valid email', () => { 24 | expect(Strings.isEmail()('john@doe.com')).toMatchSnapshot(); 25 | }); 26 | 27 | it('isEmail - invalid email', () => { 28 | expect(Strings.isEmail()('fake@d.c.b')).toMatchSnapshot(); 29 | }); 30 | 31 | it('minLength - empty', () => { 32 | expect(Strings.minLength(10)()).toMatchSnapshot(); 33 | }); 34 | 35 | it('minLength - valid sentence', () => { 36 | expect(Strings.minLength(5)('12345')).toMatchSnapshot(); 37 | }); 38 | 39 | it('minLength - invalid sentence', () => { 40 | expect(Strings.minLength(5)('123')).toMatchSnapshot(); 41 | }); 42 | 43 | it('maxLength - empty', () => { 44 | expect(Strings.maxLength(10)()).toMatchSnapshot(); 45 | }); 46 | 47 | it('maxLength - valid sentence', () => { 48 | expect(Strings.maxLength(5)('12345')).toMatchSnapshot(); 49 | }); 50 | 51 | it('maxLength - invalid sentence', () => { 52 | expect(Strings.maxLength(5)('123456')).toMatchSnapshot(); 53 | }); 54 | 55 | it('minWords - empty', () => { 56 | expect(Strings.minWords(2)()).toMatchSnapshot(); 57 | }); 58 | 59 | it('minWords - valid sentence', () => { 60 | expect(Strings.minWords(2)('John Doe')).toMatchSnapshot(); 61 | }); 62 | 63 | it('minWords - invalid sentence', () => { 64 | expect(Strings.minWords(2)('John')).toMatchSnapshot(); 65 | }); 66 | 67 | it('maxWords - empty', () => { 68 | expect(Strings.maxWords(2)()).toMatchSnapshot(); 69 | }); 70 | 71 | it('maxWords - valid sentence', () => { 72 | expect(Strings.maxWords(2)('John Doe')).toMatchSnapshot(); 73 | }); 74 | 75 | it('maxWords - invalid sentence', () => { 76 | expect(Strings.maxWords(2)('Mr. John Doe')).toMatchSnapshot(); 77 | }); 78 | -------------------------------------------------------------------------------- /src/validation/__tests__/Validation.spec.js: -------------------------------------------------------------------------------- 1 | import * as Validation from '../Validation'; 2 | 3 | import { setupTest } from '../../../test/utils'; 4 | 5 | beforeEach(async () => setupTest()); 6 | 7 | it('validate - empty', () => { 8 | const values = {}; 9 | const validations = {}; 10 | 11 | expect(Validation.validate(values, validations)).toMatchSnapshot(); 12 | }); 13 | 14 | it('validate - valid instance', () => { 15 | const values = { name: 'John Doe' }; 16 | const validations = { name: [Validation.required()] }; 17 | 18 | expect(Validation.validate(values, validations)).toMatchSnapshot(); 19 | }); 20 | 21 | it('validate - invalid instance', () => { 22 | const values = {}; 23 | const validations = { name: [Validation.required()] }; 24 | 25 | expect(Validation.validate(values, validations)).toMatchSnapshot(); 26 | }); 27 | 28 | it('required - empty', () => { 29 | expect(Validation.required()()).toMatchSnapshot(); 30 | }); 31 | 32 | it('required - filled', () => { 33 | expect(Validation.required()('Anything here')).toMatchSnapshot(); 34 | }); 35 | 36 | it('required - filled with spaces', () => { 37 | expect(Validation.required()(' ')).toMatchSnapshot(); 38 | }); 39 | -------------------------------------------------------------------------------- /src/validation/__tests__/__snapshots__/BooleanValidations.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`isFalse - another value 1`] = ` 4 | Object { 5 | "code": "Validation.Booleans.isFalse", 6 | "message": "Must be false", 7 | "params": Array [], 8 | } 9 | `; 10 | 11 | exports[`isFalse - empty 1`] = `null`; 12 | 13 | exports[`isFalse - false 1`] = `null`; 14 | 15 | exports[`isTrue - another value 1`] = ` 16 | Object { 17 | "code": "Validation.Booleans.isTrue", 18 | "message": "Must be true", 19 | "params": Array [], 20 | } 21 | `; 22 | 23 | exports[`isTrue - empty 1`] = `null`; 24 | 25 | exports[`isTrue - true 1`] = `null`; 26 | -------------------------------------------------------------------------------- /src/validation/__tests__/__snapshots__/BrazilValidations.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`cpf - bunch of zeros 1`] = ` 4 | Object { 5 | "code": "Validation.Brazil.cpf", 6 | "message": "Invalid CPF", 7 | "params": Array [], 8 | } 9 | `; 10 | 11 | exports[`cpf - empty 1`] = `null`; 12 | 13 | exports[`cpf - invalid 1`] = ` 14 | Object { 15 | "code": "Validation.Brazil.cpf", 16 | "message": "Invalid CPF", 17 | "params": Array [], 18 | } 19 | `; 20 | 21 | exports[`cpf - valid document 1`] = `null`; 22 | 23 | exports[`phone - empty 1`] = `null`; 24 | 25 | exports[`phone - invalid cellphone 1`] = ` 26 | Object { 27 | "code": "Validation.Brazil.phone", 28 | "message": "Invalid phone number", 29 | "params": Array [], 30 | } 31 | `; 32 | 33 | exports[`phone - invalid landline 1`] = ` 34 | Object { 35 | "code": "Validation.Brazil.phone", 36 | "message": "Invalid phone number", 37 | "params": Array [], 38 | } 39 | `; 40 | 41 | exports[`phone - valid cellphone 1`] = `null`; 42 | 43 | exports[`phone - valid landline 1`] = `null`; 44 | -------------------------------------------------------------------------------- /src/validation/__tests__/__snapshots__/DateValidations.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`isBetween - empty 1`] = `null`; 4 | 5 | exports[`isBetween - is between 1`] = `null`; 6 | 7 | exports[`isBetween - is not between 1`] = ` 8 | Object { 9 | "code": "Validation.Dates.isBetween", 10 | "message": "Must be greater than 2000-01-01 and lesser than 2000-12-31", 11 | "params": Array [ 12 | "2000-01-01", 13 | "2000-12-31", 14 | ], 15 | } 16 | `; 17 | 18 | exports[`isFuture - empty 1`] = `null`; 19 | 20 | exports[`isFuture - future date 1`] = `null`; 21 | 22 | exports[`isFuture - past date 1`] = ` 23 | Object { 24 | "code": "Validation.Dates.isFuture", 25 | "message": "Must be greater than today", 26 | "params": Array [], 27 | } 28 | `; 29 | 30 | exports[`isPast - empty 1`] = `null`; 31 | 32 | exports[`isPast - future date 1`] = ` 33 | Object { 34 | "code": "Validation.Dates.isPast", 35 | "message": "Must be lesser than today", 36 | "params": Array [], 37 | } 38 | `; 39 | 40 | exports[`isPast - past date 1`] = `null`; 41 | 42 | exports[`isValid - empty 1`] = `null`; 43 | 44 | exports[`isValid - invalid date 1`] = ` 45 | Object { 46 | "code": "Validation.Dates.isValid", 47 | "message": "Invalid date", 48 | "params": Array [], 49 | } 50 | `; 51 | 52 | exports[`isValid - valid date 1`] = `null`; 53 | -------------------------------------------------------------------------------- /src/validation/__tests__/__snapshots__/StringValidations.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`isAlpha - empty 1`] = `null`; 4 | 5 | exports[`isAlpha - invalid alpha 1`] = ` 6 | Object { 7 | "code": "Validation.Strings.isAlpha", 8 | "message": "Only alpha characters are allowed", 9 | "params": Array [], 10 | } 11 | `; 12 | 13 | exports[`isAlpha - valid alpha 1`] = `null`; 14 | 15 | exports[`isEmail - empty 1`] = `null`; 16 | 17 | exports[`isEmail - invalid email 1`] = ` 18 | Object { 19 | "code": "Validation.Strings.isEmail", 20 | "message": "Invalid email address", 21 | "params": Array [], 22 | } 23 | `; 24 | 25 | exports[`isEmail - valid email 1`] = `null`; 26 | 27 | exports[`maxLength - empty 1`] = `null`; 28 | 29 | exports[`maxLength - invalid sentence 1`] = ` 30 | Object { 31 | "code": "Validation.Strings.maxLength", 32 | "message": "Must have at most 5 characters", 33 | "params": Array [ 34 | 5, 35 | ], 36 | } 37 | `; 38 | 39 | exports[`maxLength - valid sentence 1`] = `null`; 40 | 41 | exports[`maxWords - empty 1`] = `null`; 42 | 43 | exports[`maxWords - invalid sentence 1`] = ` 44 | Object { 45 | "code": "Validation.Strings.maxWords", 46 | "message": "Must have at most 2 words", 47 | "params": Array [ 48 | 2, 49 | ], 50 | } 51 | `; 52 | 53 | exports[`maxWords - valid sentence 1`] = `null`; 54 | 55 | exports[`minLength - empty 1`] = `null`; 56 | 57 | exports[`minLength - invalid sentence 1`] = ` 58 | Object { 59 | "code": "Validation.Strings.minLength", 60 | "message": "Must have at least 5 characters", 61 | "params": Array [ 62 | 5, 63 | ], 64 | } 65 | `; 66 | 67 | exports[`minLength - valid sentence 1`] = `null`; 68 | 69 | exports[`minWords - empty 1`] = `null`; 70 | 71 | exports[`minWords - invalid sentence 1`] = ` 72 | Object { 73 | "code": "Validation.Strings.minWords", 74 | "message": "Must have at least 2 words", 75 | "params": Array [ 76 | 2, 77 | ], 78 | } 79 | `; 80 | 81 | exports[`minWords - valid sentence 1`] = `null`; 82 | -------------------------------------------------------------------------------- /src/validation/__tests__/__snapshots__/Validation.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`required - empty 1`] = ` 4 | Object { 5 | "code": "Validation.required", 6 | "message": "Required field", 7 | "params": Array [], 8 | } 9 | `; 10 | 11 | exports[`required - filled 1`] = `null`; 12 | 13 | exports[`required - filled with spaces 1`] = ` 14 | Object { 15 | "code": "Validation.required", 16 | "message": "Required field", 17 | "params": Array [], 18 | } 19 | `; 20 | 21 | exports[`validate - empty 1`] = `Object {}`; 22 | 23 | exports[`validate - invalid instance 1`] = ` 24 | Object { 25 | "name": Array [ 26 | Object { 27 | "code": "Validation.required", 28 | "message": "Required field", 29 | "params": Array [], 30 | }, 31 | ], 32 | } 33 | `; 34 | 35 | exports[`validate - valid instance 1`] = `Object {}`; 36 | -------------------------------------------------------------------------------- /storybook/common/consts.js: -------------------------------------------------------------------------------- 1 | export const YES_OR_NO_OPTIONS = [ 2 | { value: true, label: 'Yes' }, 3 | { value: false, label: 'No' }, 4 | ]; 5 | -------------------------------------------------------------------------------- /storybook/common/form/Form.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | import { Form } from '../../../src'; 5 | import FormButton from './FormButton'; 6 | import FormGroup from './FormGroup'; 7 | import FormInput from './FormInput'; 8 | import FormReset from './FormReset'; 9 | import FormSelect from './FormSelect'; 10 | import FormSubmit from './FormSubmit'; 11 | 12 | class CustomForm extends Component { 13 | static contextTypes = { 14 | getValues: PropTypes.func, 15 | getErrors: PropTypes.func, 16 | }; 17 | 18 | handleSubmit = (event) => { 19 | event.preventDefault(); 20 | 21 | const values = this.context.getValues(); 22 | const errors = this.context.getErrors(); 23 | 24 | this.props.onSubmit(values, errors); 25 | } 26 | 27 | render() { 28 | const { children } = this.props; 29 | 30 | return ( 31 |
32 | {children} 33 |
34 | ); 35 | } 36 | } 37 | 38 | const CustomFormWrapper = ({ initialValues, validations, onChange, ...props }) => 39 |
40 | 41 | ; 42 | 43 | CustomFormWrapper.Button = FormButton; 44 | CustomFormWrapper.Group = FormGroup; 45 | CustomFormWrapper.Input = FormInput; 46 | CustomFormWrapper.Reset = FormReset; 47 | CustomFormWrapper.Select = FormSelect; 48 | CustomFormWrapper.Submit = FormSubmit; 49 | 50 | CustomFormWrapper.defaultProps = { 51 | onSubmit: () => null, 52 | onChange: () => null, 53 | initialValues: {}, 54 | validations: {}, 55 | }; 56 | 57 | CustomFormWrapper.propTypes = { 58 | onSubmit: PropTypes.func, 59 | onChange: PropTypes.func, 60 | initialValues: PropTypes.object, 61 | validations: PropTypes.object, 62 | }; 63 | 64 | export default CustomFormWrapper; 65 | -------------------------------------------------------------------------------- /storybook/common/form/FormButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const FormButton = props => 5 |