diff --git a/docs/wrapper.js b/docs/wrapper.js index 24ad313..11935e2 100644 --- a/docs/wrapper.js +++ b/docs/wrapper.js @@ -1,9 +1,21 @@ import React, { Component } from 'react'; +import { createGlobalStyle } from 'styled-components'; import { ThemeProvider } from '../src'; +const GlobalStyle = createGlobalStyle ` + * { + box-sizing: border-box; + } +` + class App extends Component { render() { - return {this.props.children}; + return ( + <> + + {this.props.children} + + ); } } diff --git a/package-lock.json b/package-lock.json index 5dbf42e..1cd828b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3463,7 +3463,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -3504,7 +3504,7 @@ "dependencies": { "@babel/code-frame": { "version": "7.0.0-beta.44", - "resolved": "http://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz", "integrity": "sha512-cuAuTTIQ9RqcFRJ/Y8PvTh+paepNcaGxwQwjIDRWPXmzzyAeCO4KqS9ikMvq0MCbRk6GlYKwfzStrcP3/jSL8g==", "dev": true, "requires": { @@ -3513,7 +3513,7 @@ }, "@babel/generator": { "version": "7.0.0-beta.44", - "resolved": "http://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.44.tgz", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.44.tgz", "integrity": "sha512-5xVb7hlhjGcdkKpMXgicAVgx8syK5VJz193k0i/0sLP6DzE6lRrU1K3B/rFefgdo9LPGMAOOOAWW4jycj07ShQ==", "dev": true, "requires": { @@ -3526,7 +3526,7 @@ }, "@babel/helper-function-name": { "version": "7.0.0-beta.44", - "resolved": "http://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.44.tgz", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.44.tgz", "integrity": "sha512-MHRG2qZMKMFaBavX0LWpfZ2e+hLloT++N7rfM3DYOMUOGCD8cVjqZpwiL8a0bOX3IYcQev1ruciT0gdFFRTxzg==", "dev": true, "requires": { @@ -3537,7 +3537,7 @@ }, "@babel/helper-get-function-arity": { "version": "7.0.0-beta.44", - "resolved": "http://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.44.tgz", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.44.tgz", "integrity": "sha512-w0YjWVwrM2HwP6/H3sEgrSQdkCaxppqFeJtAnB23pRiJB5E/O9Yp7JAAeWBl+gGEgmBFinnTyOv2RN7rcSmMiw==", "dev": true, "requires": { @@ -3546,7 +3546,7 @@ }, "@babel/helper-split-export-declaration": { "version": "7.0.0-beta.44", - "resolved": "http://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz", "integrity": "sha512-aQ7QowtkgKKzPGf0j6u77kBMdUFVBKNHw2p/3HX/POt5/oz8ec5cs0GwlgM8Hz7ui5EwJnzyfRmkNF1Nx1N7aA==", "dev": true, "requires": { @@ -3555,7 +3555,7 @@ }, "@babel/highlight": { "version": "7.0.0-beta.44", - "resolved": "http://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.44.tgz", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.44.tgz", "integrity": "sha512-Il19yJvy7vMFm8AVAh6OZzaFoAd0hbkeMZiX3P5HGD+z7dyI7RzndHB0dg6Urh/VAFfHtpOIzDUSxmY6coyZWQ==", "dev": true, "requires": { @@ -3566,7 +3566,7 @@ }, "@babel/template": { "version": "7.0.0-beta.44", - "resolved": "http://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.44.tgz", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.44.tgz", "integrity": "sha512-w750Sloq0UNifLx1rUqwfbnC6uSUk0mfwwgGRfdLiaUzfAOiH0tHJE6ILQIUi3KYkjiCDTskoIsnfqZvWLBDng==", "dev": true, "requires": { @@ -3578,7 +3578,7 @@ }, "@babel/traverse": { "version": "7.0.0-beta.44", - "resolved": "http://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.44.tgz", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.44.tgz", "integrity": "sha512-UHuDz8ukQkJCDASKHf+oDt3FVUzFd+QYfuBIsiNu/4+/ix6pP/C+uQZJ6K1oEfbCMv/IKWbgDEh7fcsnIE5AtA==", "dev": true, "requires": { @@ -3596,7 +3596,7 @@ }, "@babel/types": { "version": "7.0.0-beta.44", - "resolved": "http://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.44.tgz", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.44.tgz", "integrity": "sha512-5eTV4WRmqbaFM3v9gHAIljEQJU4Ssc6fxL61JN+Oe2ga/BwyjzjamwkCVVAQjHGuAX8i0BWo42dshL8eO5KfLQ==", "dev": true, "requires": { @@ -3607,7 +3607,7 @@ }, "babylon": { "version": "7.0.0-beta.44", - "resolved": "http://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.44.tgz", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.44.tgz", "integrity": "sha512-5Hlm13BJVAioCHpImtFqNOF2H3ieTOHd0fmFGMxOJ9jgeFqeAwsv3u5P5cR7CSeFrkgHsT19DgFJkHV0/Mcd8g==", "dev": true }, @@ -8195,7 +8195,7 @@ }, "eslint": { "version": "4.19.1", - "resolved": "http://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", "dev": true, "requires": { @@ -8424,7 +8424,7 @@ }, "load-json-file": { "version": "2.0.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", "dev": true, "requires": { @@ -8873,7 +8873,7 @@ }, "external-editor": { "version": "2.2.0", - "resolved": "http://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", "dev": true, "requires": { @@ -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", @@ -14822,7 +14828,8 @@ "lodash": { "version": "4.17.11", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true }, "lodash._getnative": { "version": "3.9.1", @@ -16997,6 +17004,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", @@ -17337,21 +17350,21 @@ "dev": true }, "react": { - "version": "16.8.2", - "resolved": "https://registry.npmjs.org/react/-/react-16.8.2.tgz", - "integrity": "sha512-aB2ctx9uQ9vo09HVknqv3DGRpI7OIGJhCx3Bt0QqoRluEjHSaObJl+nG12GDdYH6sTgE7YiPJ6ZUyMx9kICdXw==", + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react/-/react-16.8.3.tgz", + "integrity": "sha512-3UoSIsEq8yTJuSu0luO1QQWYbgGEILm+eJl2QN/VLDi7hL+EN18M3q3oVZwmVzzBJ3DkM7RMdRwBmZZ+b4IzSA==", "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.13.2" + "scheduler": "^0.13.3" }, "dependencies": { "scheduler": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.2.tgz", - "integrity": "sha512-qK5P8tHS7vdEMCW5IPyt8v9MJOHqTrOUgPXib7tqm9vh834ibBX5BNhwkplX/0iOzHW5sXyluehYfS9yrkz9+w==", + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.3.tgz", + "integrity": "sha512-UxN5QRYWtpR1egNWzJcVLk8jlegxAugswQc984lD3kU7NuobsO37/sRfbpTdBjtnD5TBNFA2Q2oLV5+UmPSmEQ==", "dev": true, "requires": { "loose-envify": "^1.1.0", @@ -17608,21 +17621,21 @@ } }, "react-dom": { - "version": "16.8.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.2.tgz", - "integrity": "sha512-cPGfgFfwi+VCZjk73buu14pYkYBR1b/SRMSYqkLDdhSEHnSwcuYTPu6/Bh6ZphJFIk80XLvbSe2azfcRzNF+Xg==", + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.8.3.tgz", + "integrity": "sha512-ttMem9yJL4/lpItZAQ2NTFAbV7frotHk5DZEHXUOws2rMmrsvh1Na7ThGT0dTzUIl6pqTOi5tYREfL8AEna3lA==", "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", "prop-types": "^15.6.2", - "scheduler": "^0.13.2" + "scheduler": "^0.13.3" }, "dependencies": { "scheduler": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.2.tgz", - "integrity": "sha512-qK5P8tHS7vdEMCW5IPyt8v9MJOHqTrOUgPXib7tqm9vh834ibBX5BNhwkplX/0iOzHW5sXyluehYfS9yrkz9+w==", + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.3.tgz", + "integrity": "sha512-UxN5QRYWtpR1egNWzJcVLk8jlegxAugswQc984lD3kU7NuobsO37/sRfbpTdBjtnD5TBNFA2Q2oLV5+UmPSmEQ==", "dev": true, "requires": { "loose-envify": "^1.1.0", @@ -18388,7 +18401,7 @@ }, "regexpp": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", "dev": true }, @@ -20489,6 +20502,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 +20827,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 +22613,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..ca0263a 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "release": "standard-version && git push --follow-tags" }, "dependencies": { - "lodash": "^4.17.11", "mitt": "^1.1.3", "polished": "^2.0.0", "popper.js": "^1.14.7", @@ -63,8 +62,8 @@ "jest": "^24.1.0", "jest-styled-components": "^6.3.1", "npm-run-all": "^4.1.5", - "react": "^16.8.2", - "react-dom": "^16.8.2", + "react": "^16.8.3", + "react-dom": "^16.8.3", "react-router-dom": "^4.2.2", "react-test-renderer": "^16.8.2", "rimraf": "^2.6.3", @@ -73,7 +72,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/Checkbox.js b/src/Form/Checkbox.js index cf3a757..32a3736 100644 --- a/src/Form/Checkbox.js +++ b/src/Form/Checkbox.js @@ -4,6 +4,7 @@ import { css } from 'styled-components'; import Icon from '../Icon'; import FormError from '../Form/FormError'; import Flex from '../Flex'; +import { createEasyInput } from './EasyInput'; import { createComponent } from '../utils'; const CheckboxContainer = createComponent({ @@ -44,7 +45,7 @@ const StyledLabel = createComponent({ `, }); -export default class Checkbox extends React.Component { +class Checkbox extends React.Component { static propTypes = { id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, @@ -148,3 +149,5 @@ export default class Checkbox extends React.Component { ); } } + +export default createEasyInput(Checkbox); diff --git a/src/Form/CheckboxGroup.js b/src/Form/CheckboxGroup.js index 3fcbf05..60e03b7 100644 --- a/src/Form/CheckboxGroup.js +++ b/src/Form/CheckboxGroup.js @@ -1,11 +1,12 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import Flex from '../Flex'; +import Box from '../Box'; import Checkbox from './Checkbox'; import FormError from './FormError'; -import Box from '../Box'; -import Flex from '../Flex'; +import { createEasyInput } from './EasyInput'; -export default class CheckboxGroup extends Component { +class CheckboxGroup extends Component { static propTypes = { name: PropTypes.string.isRequired, color: PropTypes.string, @@ -22,6 +23,7 @@ export default class CheckboxGroup extends Component { }; static defaultProps = { + defaultValue: [], color: 'primary', horizontal: false, onChange() {}, @@ -90,3 +92,5 @@ export default class CheckboxGroup extends Component { ); } } + +export default createEasyInput(CheckboxGroup); diff --git a/src/Form/EasyInput.js b/src/Form/EasyInput.js new file mode 100644 index 0000000..582f5bc --- /dev/null +++ b/src/Form/EasyInput.js @@ -0,0 +1,39 @@ +import React, { useContext } from 'react'; +import { Context } from './Formbot'; + +/** + * useContext has some slight performance issues. Every update to context re-renders + * each subscribed component and there's currently no way to bail out of renders if the + * values we care about haven't changed. Below is a sufficient workaround below and redux maintainers are discussing here: https://github.com/facebook/react/issues/14110 + */ +const PureInput = React.memo(({ Component, ...props }) => ); + +function EasyInput({ name, Component, ...props }) { + const state = useContext(Context); + + if (!state) { + return + } + + const value = state.values[name]; + const defaultValue = Component.defaultProps && Component.defaultProps.defaultValue !== undefined + ? Component.defaultProps.defaultValue + : ''; + + return ( + + ); +} + +export const createEasyInput = Component => props => + +export default EasyInput; diff --git a/src/Form/Fieldset.js b/src/Form/Fieldset.js new file mode 100644 index 0000000..33740f7 --- /dev/null +++ b/src/Form/Fieldset.js @@ -0,0 +1,41 @@ +import React from 'react'; +import Box from '../Box'; +import styled, { css } from 'styled-components'; +import { createComponent } from '../utils'; + +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.fieldset` + border: 0; + padding: 0; + margin: 0; + + * + & { + margin-top: 1.5rem; + } +`; + +const Fieldset = ({ legend, children }) => ( + + {legend && {legend}} + + {children} + +); + +export default Fieldset; + diff --git a/src/Form/Form.js b/src/Form/Form.js new file mode 100644 index 0000000..66d728e --- /dev/null +++ b/src/Form/Form.js @@ -0,0 +1,14 @@ +import React, { useContext } from 'react'; +import { Context } from './Formbot'; + +function Form({ children, ...props }) { + const state = useContext(Context); + + return ( +
+ {children} +
+ ) +} + +export default Form; diff --git a/src/Form/Formbot.example.js b/src/Form/Formbot.example.js index 6e232cf..d811184 100644 --- a/src/Form/Formbot.example.js +++ b/src/Form/Formbot.example.js @@ -1,9 +1,11 @@ -import React from 'react'; +import React, { useContext } from 'react'; import Input from './Input'; import Select from './Select'; -import Formbot from './Formbot'; +import Formbot, { Context } from './Formbot'; +import Form from './Form'; import Button from '../Button'; import FormGroup from './FormGroup'; +import Fieldset from './Fieldset'; import CheckboxGroup from './CheckboxGroup'; import RadioGroup from './RadioGroup'; import Switch from './Switch'; @@ -38,6 +40,13 @@ const radioValues = [ }, ]; +const Values = () => { + const state = useContext(Context); + return ( +
{JSON.stringify(state.values, null, 2)}
+ ) +} + export default function() { return ( - {({ values, onSubmit, onChange, errors, onBlur }) => ( -
- - - - - - - - - - - - - - - - - - - -
- )} +
+
+ + + + + + + + +
+ + + + +
); } diff --git a/src/Form/Formbot.js b/src/Form/Formbot.js index 54d5861..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; @@ -23,9 +25,8 @@ const VALIDATIONS = { export default class Formbot extends React.Component { static propTypes = { initialValues: PropTypes.shape(), - validations: PropTypes.shape({ - fieldName: PropTypes.oneOfType([PropTypes.func, PropTypes.shape()]), - }), + validations: PropTypes.object, + 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() {}, @@ -47,8 +49,12 @@ export default class Formbot extends React.Component { errors: {}, }; + get validatableFields() { + return Object.keys(this.props.validationSchema || this.props.validations || {}); + } + get validatable() { - return Object.keys(this.props.validations); + return !!this.validatableFields.length; } get isValid() { @@ -110,15 +116,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 +150,11 @@ export default class Formbot extends React.Component { }); } } catch (err) { - errorMsg = err.message; + if (fromSchema) { + errorMsg = err.errors.length ? err.errors[0] : undefined; + } else { + errorMsg = err.message; + } } finally { this.updateField(field, { validated: true }).then(() => { this.setState( @@ -151,7 +173,7 @@ export default class Formbot extends React.Component { validateAllFields() { return Promise.all( - this.validatable.map(field => this.updateField(field, {}).then(() => this.validateField(field))) + this.validatableFields.map(field => this.updateField(field, {}).then(() => this.validateField(field))) ); } @@ -198,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, @@ -208,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 592ad84..3ab51a8 100644 --- a/src/Form/Formbot.mdx +++ b/src/Form/Formbot.mdx @@ -4,8 +4,11 @@ name: Formbot --- 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' @@ -22,30 +25,21 @@ 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..5690a12 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, @@ -61,6 +61,7 @@ class Switch extends React.Component { }; static defaultProps = { + defaultValue: false, variant: 'primary', size: 16, inset: 8, @@ -96,16 +97,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); diff --git a/src/index.js b/src/index.js index 1d067f0..5a91111 100644 --- a/src/index.js +++ b/src/index.js @@ -12,6 +12,7 @@ export Column from './Grid/Column'; export Container from './Grid/Container'; export Dropdown from './Dropdown'; export Field from './Form/Field'; +export Fieldset from './Form/Fieldset'; export Flex from './Flex'; export Formbot from './Form/Formbot'; export FormError from './Form/FormError';