From 6725c09b09c9611dd3b24ac3b1898be55e7a4966 Mon Sep 17 00:00:00 2001 From: Hurshal Patel Date: Fri, 22 Feb 2019 15:42:06 -0800 Subject: [PATCH 01/20] Feat(formbot): support validation via yup schema --- src/Form/Formbot.js | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/Form/Formbot.js b/src/Form/Formbot.js index 54d5861..b317106 100644 --- a/src/Form/Formbot.js +++ b/src/Form/Formbot.js @@ -26,6 +26,7 @@ export default class Formbot extends React.Component { validations: PropTypes.shape({ fieldName: PropTypes.oneOfType([PropTypes.func, PropTypes.shape()]), }), + validationSchema: PropTypes.object, onFocus: PropTypes.func, onChange: PropTypes.func, onBlur: PropTypes.func, @@ -35,6 +36,7 @@ export default class Formbot extends React.Component { static defaultProps = { initialValues: {}, validations: {}, + validationSchema: null, onFocus() {}, onChange() {}, onBlur() {}, @@ -48,7 +50,7 @@ export default class Formbot extends React.Component { }; get validatable() { - return Object.keys(this.props.validations); + return Object.keys(this.props.validationSchema || this.props.validations); } get isValid() { @@ -110,15 +112,27 @@ export default class Formbot extends React.Component { validateField(field) { return new Promise(resolve => { - const validation = this.props.validations[field]; + const fieldState = this.state.fields[field] || {}; + if (fieldState.validated) { + resolve(); + return; + } + + const fromSchema = !!this.props.validationSchema; + const validation = (fromSchema ? this.props.validationSchema : this.props.validations)[field]; - if (!validation) return; + if (!validation) { + resolve(); + return; + } const fieldValue = this.state.values[field]; let errorMsg; try { - if (typeof validation === 'function') { + if (fromSchema) { + validation.validateSync(fieldValue, { values: this.state.values }); + } else if (typeof validation === 'function') { validation(fieldValue); } else { Object.keys(validation).forEach(method => { @@ -132,7 +146,11 @@ export default class Formbot extends React.Component { }); } } catch (err) { - errorMsg = err.message; + if (fromSchema) { + errorMsg = err ? err.errors[0] : undefined; + } else { + errorMsg = err.message; + } } finally { this.updateField(field, { validated: true }).then(() => { this.setState( From 79eaf73b10af40d7dff09f67b361be53da604bff Mon Sep 17 00:00:00 2001 From: Hurshal Patel Date: Fri, 22 Feb 2019 16:12:36 -0800 Subject: [PATCH 02/20] Fix validatable = false case --- src/Form/Formbot.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Form/Formbot.js b/src/Form/Formbot.js index b317106..239c9c0 100644 --- a/src/Form/Formbot.js +++ b/src/Form/Formbot.js @@ -26,7 +26,9 @@ export default class Formbot extends React.Component { validations: PropTypes.shape({ fieldName: PropTypes.oneOfType([PropTypes.func, PropTypes.shape()]), }), - validationSchema: PropTypes.object, + validationSchema: PropTypes.shape({ + fieldName: PropTypes.object, + }), onFocus: PropTypes.func, onChange: PropTypes.func, onBlur: PropTypes.func, @@ -36,7 +38,7 @@ export default class Formbot extends React.Component { static defaultProps = { initialValues: {}, validations: {}, - validationSchema: null, + validationSchema: {}, onFocus() {}, onChange() {}, onBlur() {}, From 334f3a9352595540720a3d4bc5e4dbc63ec9b329 Mon Sep 17 00:00:00 2001 From: Hurshal Patel Date: Fri, 22 Feb 2019 16:20:37 -0800 Subject: [PATCH 03/20] Add yup example --- package-lock.json | 55 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 ++- src/Form/Formbot.mdx | 7 +++--- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5dbf42e..4189c34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9549,6 +9549,12 @@ "readable-stream": "^2.0.4" } }, + "fn-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fn-name/-/fn-name-2.0.1.tgz", + "integrity": "sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc=", + "dev": true + }, "follow-redirects": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.6.1.tgz", @@ -16997,6 +17003,12 @@ } } }, + "property-expr": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-1.5.1.tgz", + "integrity": "sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g==", + "dev": true + }, "property-information": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.0.1.tgz", @@ -20489,6 +20501,12 @@ "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", "dev": true }, + "synchronous-promise": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.6.tgz", + "integrity": "sha512-TyOuWLwkmtPL49LHCX1caIwHjRzcVd62+GF6h8W/jHOeZUFHpnd2XJDVuUlaTaLPH1nuu2M69mfHr5XbQJnf/g==", + "dev": true + }, "table": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", @@ -20808,6 +20826,12 @@ } } }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA=", + "dev": true + }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -22588,6 +22612,37 @@ "dev": true } } + }, + "yup": { + "version": "0.26.10", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.26.10.tgz", + "integrity": "sha512-keuNEbNSnsOTOuGCt3UJW69jDE3O4P+UHAakO7vSeFMnjaitcmlbij/a3oNb9g1Y1KvSKH/7O1R2PQ4m4TRylw==", + "dev": true, + "requires": { + "@babel/runtime": "7.0.0", + "fn-name": "~2.0.1", + "lodash": "^4.17.10", + "property-expr": "^1.5.0", + "synchronous-promise": "^2.0.5", + "toposort": "^2.0.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0.tgz", + "integrity": "sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.12.0" + } + }, + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==", + "dev": true + } + } } } } diff --git a/package.json b/package.json index 4a3c100..fb86b34 100644 --- a/package.json +++ b/package.json @@ -73,7 +73,8 @@ "rollup-plugin-commonjs": "^9.2.0", "rollup-plugin-filesize": "^6.0.1", "standard-version": "^5.0.0", - "styled-components": "^4.1.3" + "styled-components": "^4.1.3", + "yup": "^0.26.10" }, "resolutions": { "babel-core": "^7.0.0-bridge.0" diff --git a/src/Form/Formbot.mdx b/src/Form/Formbot.mdx index 592ad84..bf7e31c 100644 --- a/src/Form/Formbot.mdx +++ b/src/Form/Formbot.mdx @@ -4,6 +4,7 @@ name: Formbot --- import { Playground, PropsTable } from 'docz' +import * as yup from 'yup'; import FormExample from './Formbot.example' import Formbot from './Formbot' import Button from '../Button' @@ -23,10 +24,8 @@ Quickly build a form using a Formbot and pre-configured form components. Formbot ### Formbot {({ values, onSubmit, onChange, errors, onBlur }) => (
From 4e0c0210ebaec41255480abdd7b7a476c9bade7a Mon Sep 17 00:00:00 2001 From: Hurshal Patel Date: Fri, 22 Feb 2019 16:24:59 -0800 Subject: [PATCH 04/20] Actually fix validatable --- src/Form/Formbot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Form/Formbot.js b/src/Form/Formbot.js index 239c9c0..f43c89f 100644 --- a/src/Form/Formbot.js +++ b/src/Form/Formbot.js @@ -52,7 +52,7 @@ export default class Formbot extends React.Component { }; get validatable() { - return Object.keys(this.props.validationSchema || this.props.validations); + return Object.keys(this.props.validationSchema || this.props.validations).length !== 0; } get isValid() { From 622f235956de84fb59f6947aa64fd41693d43cc4 Mon Sep 17 00:00:00 2001 From: Hurshal Patel Date: Fri, 22 Feb 2019 17:02:30 -0800 Subject: [PATCH 05/20] Add legend to formgroup --- src/Form/FormGroup.js | 32 +++++++++++++++++++++++++++++++- src/Form/Formbot.example.js | 20 ++++++++------------ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/src/Form/FormGroup.js b/src/Form/FormGroup.js index 286a6a9..5016872 100644 --- a/src/Form/FormGroup.js +++ b/src/Form/FormGroup.js @@ -1,6 +1,36 @@ import React from 'react'; import Box from '../Box'; +import styled, { css } from 'styled-components'; +import { createComponent } from '../utils'; -const FormGroup = ({ children, padding = 3 }) => {children}; +const Legend = createComponent({ + name: 'Legend', + tag: 'legend', + style: ({ + theme, + color = theme.colors.primary, + }) => css` + font-weight: 700; + margin-bottom: 1rem; + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.25px; + color: ${color}; + `, +}); + +const Container = styled(Box)` + * + & { + margin-top: 1.5rem; + } +`; + +const FormGroup = ({ legend, children, padding = 3 }) => ( + + {legend && {legend}} + + {children} + +); export default FormGroup; diff --git a/src/Form/Formbot.example.js b/src/Form/Formbot.example.js index 6e232cf..8910865 100644 --- a/src/Form/Formbot.example.js +++ b/src/Form/Formbot.example.js @@ -68,7 +68,7 @@ export default function() { }}> {({ values, onSubmit, onChange, errors, onBlur }) => ( - + - - + - - + - - + - - + - - + - - )} +
+
+ + + + + + + + +
+ + + + +
); } diff --git a/src/Form/Formbot.js b/src/Form/Formbot.js index 3b20127..d006bbe 100644 --- a/src/Form/Formbot.js +++ b/src/Form/Formbot.js @@ -1,6 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; +export const Context = React.createContext(null); + const VALIDATIONS = { required: (val, isRequired) => { if (!isRequired) return; @@ -218,8 +220,8 @@ export default class Formbot extends React.Component { }); }; - render() { - return this.props.children({ + getContext() { + return { ...this.props, values: this.state.values, errors: this.state.errors, @@ -228,6 +230,16 @@ export default class Formbot extends React.Component { onBlur: this.onBlur, onSubmit: this.onSubmit, reset: this.reset, - }); + } + } + + render() { + const { children, ...props } = this.props; + + return ( + + {typeof children === 'function' ? children(this.getContext()) : children} + + ) } } diff --git a/src/Form/Formbot.mdx b/src/Form/Formbot.mdx index 7d2b172..3ab51a8 100644 --- a/src/Form/Formbot.mdx +++ b/src/Form/Formbot.mdx @@ -7,6 +7,8 @@ import { Playground, PropsTable } from 'docz' import * as yup from 'yup'; import FormExample from './Formbot.example' import Formbot from './Formbot' +import Form from './Form'; +import Field from './Field'; import Button from '../Button' import FormGroup from './FormGroup' import Input from './Input' @@ -24,30 +26,20 @@ Quickly build a form using a Formbot and pre-configured form components. Formbot ### Formbot - {({ values, onSubmit, onChange, errors, onBlur }) => ( -
- - - - - -
- )} + +
diff --git a/src/Form/Input.js b/src/Form/Input.js index 5eba758..ab07b49 100644 --- a/src/Form/Input.js +++ b/src/Form/Input.js @@ -4,6 +4,7 @@ import { css } from 'styled-components'; import Field from './Field'; import StyledLabel from './Label'; import FormError from './FormError'; +import { createEasyInput } from './EasyInput'; import { createComponent } from '../utils'; const InputContainer = createComponent({ @@ -71,7 +72,7 @@ const validateValueProp = (props, propName, componentName) => { return null; }; -export default class Input extends Component { +class Input extends Component { static propTypes = { value: validateValueProp, type: PropTypes.string, @@ -277,3 +278,5 @@ export default class Input extends Component { ); } } + +export default createEasyInput(Input); diff --git a/src/Form/RadioGroup.js b/src/Form/RadioGroup.js index a560ddf..70fe646 100644 --- a/src/Form/RadioGroup.js +++ b/src/Form/RadioGroup.js @@ -1,10 +1,11 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import Flex from '../Flex'; +import Box from '../Box'; import Checkbox from './Checkbox'; import Label from './Label'; import FormError from './FormError'; -import Flex from '../Flex'; -import Box from '../Box'; +import { createEasyInput } from './EasyInput'; import { createComponent } from '../utils'; const StyledRadioGroup = createComponent({ @@ -12,7 +13,7 @@ const StyledRadioGroup = createComponent({ as: Box, }); -export default class RadioGroup extends Component { +class RadioGroup extends Component { static propTypes = { name: PropTypes.string, onChange: PropTypes.func, @@ -53,9 +54,9 @@ export default class RadioGroup extends Component { // Bail out if value is the same if (this.state.value === value) return; - this.setState({ value }); - - this.props.onChange(this.props.name, value); + this.setState({ value }, () => { + this.props.onChange(this.props.name, value); + }); }; render() { @@ -83,6 +84,7 @@ export default class RadioGroup extends Component { label={choiceLabel} value={this.state.value} valueTrue={value} + valueFalse={value} iconOn="radiobox-marked" iconOff="radiobox-blank" onChange={this.handleChange} @@ -96,3 +98,5 @@ export default class RadioGroup extends Component { ); } } + +export default createEasyInput(RadioGroup); diff --git a/src/Form/Select.js b/src/Form/Select.js index a22260f..c9a0e05 100644 --- a/src/Form/Select.js +++ b/src/Form/Select.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { Component } from 'react'; import styled, { css } from 'styled-components'; import PropTypes from 'prop-types'; import Field from './Field'; @@ -6,6 +6,7 @@ import FormError from './FormError'; import Icon from '../Icon'; import Flex from '../Flex'; import Label from './Label'; +import { createEasyInput } from './EasyInput'; import { createComponent } from '../utils'; const SelectContain = createComponent({ @@ -26,7 +27,7 @@ const SelectContain = createComponent({ font-size: ${theme.fontSizes[size]}px; vertical-align: middle; - ${value && + ${!value && css` color: ${p => p.theme.colors.grayMid}; select { @@ -55,48 +56,74 @@ const IconContain = styled(Flex)` z-index: 1; `; -function Select({ id, name = id, options, placeholder, value, error, onChange, onBlur, size = 'md', label }) { - return ( - - {label && } - - - - - - - {error && {error}} - - ); -} +class Select extends Component { + static propTypes = { + name: PropTypes.string.isRequired, + options: PropTypes.array.isRequired, + placeholder: PropTypes.string, + value: PropTypes.string, + error: PropTypes.string, + onChange: PropTypes.func, + onBlur: PropTypes.func, + size: PropTypes.string, + label: PropTypes.string, + } + + static defaultProps = { + onChange() {}, + onBlur() {}, + } + + static getDerivedStateFromProps(props, state) { + if (props.value !== undefined && props.value !== state.value) { + return { + value: props.value, + }; + } + return null; + } + + state = { + value: "", + }; -Select.propTypes = { - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - options: PropTypes.array.isRequired, - placeholder: PropTypes.string, - value: PropTypes.string, - error: PropTypes.string, - onChange: PropTypes.func, - onBlur: PropTypes.func, - size: PropTypes.string, - label: PropTypes.string, -}; + handleChange = e => { + this.setState({ value: e.target.value }); + this.props.onChange(e.target.name, e.target.value); + } + + handleBlur = e => { + this.props.onBlur(e.target.name) + } + + render() { + const { id, name, options, placeholder, error, size = 'md', label, ...props } = this.props; + const { value } = this.state; + + return ( + + {label && } + + + + + + + {error && {error}} + + ) + } +} -export default Select; +export default createEasyInput(Select); diff --git a/src/Form/Switch.js b/src/Form/Switch.js index 9944c5c..73d3435 100644 --- a/src/Form/Switch.js +++ b/src/Form/Switch.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import styled, { css } from 'styled-components'; +import { createEasyInput } from './EasyInput'; const SwitchContain = styled.label` position: relative; @@ -52,7 +53,6 @@ const SwitchThumb = styled.span` class Switch extends React.Component { static propTypes = { - id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, value: PropTypes.bool, size: PropTypes.number, @@ -96,16 +96,16 @@ class Switch extends React.Component { }; render() { - const { id, name, size, variant, inset } = this.props; + const { name, size, variant, inset, ...props } = this.props; const { on } = this.state; return ( - - + + ); } } -export default Switch; +export default createEasyInput(Switch); From 259b0e8412fa6d6010a468e9133e2bc94e5343fd Mon Sep 17 00:00:00 2001 From: Hurshal Patel Date: Mon, 25 Feb 2019 14:58:09 -0800 Subject: [PATCH 18/20] createEasyInput: configurable default value --- src/Form/CheckboxGroup.js | 2 +- src/Form/EasyInput.js | 8 ++++++-- src/Form/Formbot.example.js | 5 ----- src/Form/Switch.js | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/Form/CheckboxGroup.js b/src/Form/CheckboxGroup.js index 80613b0..85093d5 100644 --- a/src/Form/CheckboxGroup.js +++ b/src/Form/CheckboxGroup.js @@ -92,4 +92,4 @@ class CheckboxGroup extends Component { } } -export default createEasyInput(CheckboxGroup); +export default createEasyInput(CheckboxGroup, []); diff --git a/src/Form/EasyInput.js b/src/Form/EasyInput.js index 899f535..66e988e 100644 --- a/src/Form/EasyInput.js +++ b/src/Form/EasyInput.js @@ -15,10 +15,13 @@ function EasyInput({ name, Component, ...props }) { return } + const value = state.values[name]; + const defaultValue = props.defaultValue !== undefined ? props.defaultValue : ''; + return ( props => +export const createEasyInput = (Component, defaultValue) => props => + export default EasyInput; diff --git a/src/Form/Formbot.example.js b/src/Form/Formbot.example.js index 5cb7a4c..d811184 100644 --- a/src/Form/Formbot.example.js +++ b/src/Form/Formbot.example.js @@ -50,11 +50,6 @@ const Values = () => { export default function() { return ( { if (val !== 'Bob') { diff --git a/src/Form/Switch.js b/src/Form/Switch.js index 73d3435..d769ae8 100644 --- a/src/Form/Switch.js +++ b/src/Form/Switch.js @@ -108,4 +108,4 @@ class Switch extends React.Component { } } -export default createEasyInput(Switch); +export default createEasyInput(Switch, false); From 880b104314d68e66b62130635aa457cf8fb78ef7 Mon Sep 17 00:00:00 2001 From: Hurshal Patel Date: Mon, 25 Feb 2019 15:02:34 -0800 Subject: [PATCH 19/20] Do it more better-er --- src/Form/CheckboxGroup.js | 3 ++- src/Form/EasyInput.js | 7 ++++--- src/Form/Switch.js | 3 ++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/Form/CheckboxGroup.js b/src/Form/CheckboxGroup.js index 85093d5..60e03b7 100644 --- a/src/Form/CheckboxGroup.js +++ b/src/Form/CheckboxGroup.js @@ -23,6 +23,7 @@ class CheckboxGroup extends Component { }; static defaultProps = { + defaultValue: [], color: 'primary', horizontal: false, onChange() {}, @@ -92,4 +93,4 @@ class CheckboxGroup extends Component { } } -export default createEasyInput(CheckboxGroup, []); +export default createEasyInput(CheckboxGroup); diff --git a/src/Form/EasyInput.js b/src/Form/EasyInput.js index 66e988e..dc9cd6a 100644 --- a/src/Form/EasyInput.js +++ b/src/Form/EasyInput.js @@ -16,7 +16,9 @@ function EasyInput({ name, Component, ...props }) { } const value = state.values[name]; - const defaultValue = props.defaultValue !== undefined ? props.defaultValue : ''; + const defaultValue = Component.defaultProps.defaultValue !== undefined + ? Component.defaultProps.defaultValue + : ''; return ( props => - +export const createEasyInput = Component => props => export default EasyInput; diff --git a/src/Form/Switch.js b/src/Form/Switch.js index d769ae8..5690a12 100644 --- a/src/Form/Switch.js +++ b/src/Form/Switch.js @@ -61,6 +61,7 @@ class Switch extends React.Component { }; static defaultProps = { + defaultValue: false, variant: 'primary', size: 16, inset: 8, @@ -108,4 +109,4 @@ class Switch extends React.Component { } } -export default createEasyInput(Switch, false); +export default createEasyInput(Switch); From e32d5d8fc805f8202349efa2b13aeaff928d21ef Mon Sep 17 00:00:00 2001 From: Hurshal Patel Date: Mon, 25 Feb 2019 15:24:49 -0800 Subject: [PATCH 20/20] Handle missing defaultprops on component --- src/Form/EasyInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Form/EasyInput.js b/src/Form/EasyInput.js index dc9cd6a..582f5bc 100644 --- a/src/Form/EasyInput.js +++ b/src/Form/EasyInput.js @@ -16,7 +16,7 @@ function EasyInput({ name, Component, ...props }) { } const value = state.values[name]; - const defaultValue = Component.defaultProps.defaultValue !== undefined + const defaultValue = Component.defaultProps && Component.defaultProps.defaultValue !== undefined ? Component.defaultProps.defaultValue : '';