├── .eslintrc ├── .gitignore ├── .gitmodules ├── .nvmrc ├── README.md ├── package-lock.json ├── package.json ├── postcss.config.js ├── server.ts ├── src ├── components │ ├── Button.tsx │ ├── Main.tsx │ ├── Nav.tsx │ ├── Switch.tsx │ ├── controls │ │ ├── FieldControls.tsx │ │ └── FormControls.tsx │ ├── fieldsets │ │ ├── ClubFieldset.tsx │ │ ├── NestedAllMembersFieldset.tsx │ │ ├── NestedHobbyFieldset.tsx │ │ └── NestedMemberFieldset.tsx │ ├── forms │ │ ├── FormCompanySimple.tsx │ │ ├── FormCompanyWidgets.tsx │ │ ├── FormDynamicFieldsSelect.tsx │ │ ├── FormFileUpload.tsx │ │ ├── FormMarkdown.tsx │ │ ├── FormMaterialDatePicker.tsx │ │ ├── FormRegisterMaterial.tsx │ │ ├── FormRegisterSimple.tsx │ │ └── FormWithNestedFields.tsx │ └── inputs │ │ ├── DropZone.tsx │ │ ├── MaterialSwitch.tsx │ │ ├── MaterialTextField.tsx │ │ ├── NestedHobbyInput.tsx │ │ ├── ReactMultiSelect.tsx │ │ ├── SimpleCheckbox.tsx │ │ ├── SimpleFile.tsx │ │ ├── SimpleInput.tsx │ │ ├── SimpleRadio.tsx │ │ ├── SimpleSelect.tsx │ │ ├── SimpleTextarea.tsx │ │ ├── WidgetDatePicker.tsx │ │ ├── WidgetDropdownList.tsx │ │ └── WidgetMultiselect.tsx ├── entry.tsx ├── forms │ ├── _.bindings.ts │ ├── _.extend.ts │ ├── _.forms.ts │ ├── _.hooks.fields.ts │ ├── _.hooks.form.ts │ ├── data │ │ ├── example.md │ │ └── options.ts │ ├── extension │ │ ├── _.async.js │ │ ├── dvr.ts │ │ ├── svk.ts │ │ └── vjf.ts │ └── setup │ │ ├── companySimple.ts │ │ ├── companyWidgets.ts │ │ ├── dynamicFieldsSelect.ts │ │ ├── fileUpload.ts │ │ ├── markdown.ts │ │ ├── nestedFields.ts │ │ ├── registerMaterial.ts │ │ └── registerSimple.ts ├── globals.d.ts ├── index.html ├── menu.ts ├── style.css └── styles.ts ├── tsconfig.json ├── umd ├── umd.html └── umd.js └── webpack.config.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "import/no-extraneous-dependencies": ["error", { "devDependencies": true }] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # These are some examples of commonly ignored file patterns. 2 | # You should customize this list as applicable to your project. 3 | # Learn more about .gitignore: 4 | # https://www.atlassian.com/git/tutorials/saving-changes/gitignore 5 | 6 | # Node artifact files 7 | node_modules/ 8 | dist/ 9 | build/ 10 | modules/ 11 | modules/* 12 | 13 | # Compiled Java class files 14 | *.class 15 | 16 | # Compiled Python bytecode 17 | *.py[cod] 18 | 19 | # Log files 20 | *.log 21 | 22 | # Package files 23 | *.jar 24 | 25 | # Maven 26 | target/ 27 | dist/ 28 | 29 | # JetBrains IDE 30 | .idea/ 31 | 32 | # Unit test reports 33 | TEST*.xml 34 | 35 | # Generated by MacOS 36 | .DS_Store 37 | 38 | # Generated by Windows 39 | Thumbs.db 40 | 41 | # Applications 42 | *.app 43 | *.exe 44 | *.war 45 | 46 | # Large media files 47 | *.mp4 48 | *.tiff 49 | *.avi 50 | *.flv 51 | *.mov 52 | *.wmv 53 | 54 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "modules/mobx-react-form-devtools"] 2 | path = modules/mobx-react-form-devtools 3 | url = git@github.com:foxhound87/mobx-react-form-devtools.git 4 | [submodule "modules/mobx-react-form"] 5 | path = modules/mobx-react-form 6 | url = git@github.com:foxhound87/mobx-react-form.git 7 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.19.1 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [MobX React Form](https://github.com/foxhound87/mobx-react-form) Demo 2 | 3 | # Live Demo 4 | 5 | [https://foxhound87.github.io/mobx-react-form-demo](https://foxhound87.github.io/mobx-react-form-demo) 6 | 7 | # Quick Start 8 | 9 | Run: 10 | 11 | ```bash 12 | ❯ npm run submodules:add 13 | ❯ npm run submodules:fetch 14 | ❯ npm run submodules:install 15 | ❯ npm install --legacy-peer-deps 16 | ❯ npm run dev 17 | ``` 18 | 19 | and go to `http://localhost:8888`. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mobx-react-form-demo", 3 | "version": "1.0.0", 4 | "description": "mobx-react-form-demo", 5 | "main": "server.ts", 6 | "scripts": { 7 | "dev": "cross-env NODE_ENV=development webpack-dev-server --port 8888", 8 | "build": "cross-env NODE_ENV=production webpack", 9 | "start": "cross-env NODE_ENV=production node ./server.ts", 10 | "submodules:remove": "rm -rf modules/*", 11 | "submodules:fetch": "git submodule init && git submodule update --remote", 12 | "submodules:add": "npm run submodules:remove && npm run submodules:add:devtools && npm run submodules:add:form", 13 | "submodules:install": "npm run submodules:install:devtools && npm run submodules:install:form", 14 | "submodules:add:form": "git submodule add -f git@github.com:foxhound87/mobx-react-form.git modules/mobx-react-form", 15 | "submodules:add:devtools": "git submodule add -f git@github.com:foxhound87/mobx-react-form-devtools.git modules/mobx-react-form-devtools", 16 | "submodules:install:form": "cd modules/mobx-react-form && npm i --legacy-peer-deps", 17 | "submodules:install:devtools": "cd modules/mobx-react-form-devtools && npm i --legacy-peer-deps", 18 | "publish": "cd build && rm -rf .git && git init && git commit --allow-empty -m 'update' && git checkout -b gh-pages && git add . && git commit -am 'update' && git push git@github.com:foxhound87/mobx-react-form-demo.git gh-pages --force" 19 | }, 20 | "author": "Claudio Savino", 21 | "license": "MIT", 22 | "devDependencies": { 23 | "@types/mocha": "^10.0.1", 24 | "@types/node": "^18.14.4", 25 | "@types/react": "^18.0.28", 26 | "cross-env": "^7.0.3", 27 | "css-loader": "^6.7.3", 28 | "html-webpack-plugin": "^5.5.0", 29 | "less-loader": "^11.1.0", 30 | "postcss-import": "^15.1.0", 31 | "postcss-loader": "^7.0.2", 32 | "postcss-url": "^10.1.3", 33 | "raw-loader": "^4.0.2", 34 | "style-loader": "^3.3.1", 35 | "terser-webpack-plugin": "^5.3.7", 36 | "ts-loader": "^9.4.2", 37 | "typescript": "^4.9.5", 38 | "webpack": "^5.75.0", 39 | "webpack-cli": "^5.0.1", 40 | "webpack-dev-server": "^4.11.1" 41 | }, 42 | "dependencies": { 43 | "@emotion/react": "^11.10.6", 44 | "@emotion/styled": "^11.10.6", 45 | "@mui/material": "^5.11.11", 46 | "font-awesome": "^4.7.0", 47 | "lodash": "^4.17.21", 48 | "mobx": "^6.7.0", 49 | "mobx-react": "^7.5.3", 50 | "mobx-react-devtools": "^6.1.1", 51 | "mobx-react-form": "^5.4.1", 52 | "mobx-react-form-devtools": "^1.11.1", 53 | "react": "^16.14.0", 54 | "react-dom": "^16.14.0", 55 | "react-dropzone": "^6.2.4", 56 | "react-markdown": "^8.0.5", 57 | "react-select": "^5.7.0", 58 | "react-widgets": "^5.8.4", 59 | "tachyons": "^4.12.0", 60 | "validatorjs": "^3.22.1" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const postcssImport = require('postcss-import'); 2 | const postcssUrl = require('postcss-url'); 3 | 4 | module.exports = { 5 | plugins: [ 6 | postcssImport(), 7 | postcssUrl('inline'), 8 | ] 9 | }; 10 | -------------------------------------------------------------------------------- /server.ts: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | const index = path.resolve(__dirname, 'build', 'index.html'); 6 | const bundle = path.resolve(__dirname, 'build', 'main.js'); 7 | 8 | function send(res: any, err: any, contents: any) { 9 | if (!err) res.end(contents); 10 | else console.dir(err); // eslint-disable-line 11 | } 12 | 13 | function handler(req, res) { 14 | const file = path.basename(req.url); 15 | 16 | if (file === 'main.js') { 17 | fs.readFile(bundle, (err, contents) => send(res, err, contents)); 18 | return; 19 | } 20 | 21 | fs.readFile(index, (err, contents) => send(res, err, contents)); 22 | } 23 | 24 | http.createServer(handler).listen(3000); 25 | -------------------------------------------------------------------------------- /src/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component, MouseEventHandler } from 'react'; 2 | import { observer } from 'mobx-react'; 3 | // import ReactTooltip from 'react-tooltip'; 4 | import _ from 'lodash'; 5 | import { Tooltip } from '@mui/material'; 6 | 7 | const checkLabel = (text, label) => 8 | _.isInteger(_.parseInt(label)) || _.isNil(label) 9 | ? text : `${text} ${label}`; 10 | 11 | export default observer(({ 12 | onlyIcon = false, 13 | disabled = false, 14 | content = null, 15 | type = 'button', 16 | className, 17 | onClick, 18 | text, 19 | label, 20 | icon, 21 | }: { 22 | onlyIcon?: boolean 23 | disabled?: boolean, 24 | content?: JSX.Element, 25 | type?: "button" | "submit" | "reset", 26 | className?: string, 27 | onClick?: MouseEventHandler, 28 | text?: string, 29 | label?: string, 30 | icon?: string, 31 | }) => ( 32 | 33 | 46 | 47 | )); 48 | -------------------------------------------------------------------------------- /src/components/Main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from 'mobx-react'; 3 | import DevTools from 'mobx-react-devtools'; 4 | import _ from 'lodash'; 5 | 6 | // import MobxReactFormDevTools from 'mobx-react-form-devtools'; 7 | // import MobxReactFormDevTools from 'mobx-react-form-devtools'; 8 | import MobxReactFormDevTools from '../../modules/mobx-react-form-devtools/src'; // load from source 9 | // import MobxReactFormDevTools from '../../modules/mobx-react-form-devtools/lib'; // load from build 10 | 11 | import Nav from './Nav'; 12 | import Switch from './Switch'; 13 | 14 | import forms from '../forms/_.forms'; 15 | import menu from '../menu'; 16 | 17 | // selected menu item 18 | const selected = $menu => _.keys(_.pickBy($menu, _.identity))[0]; 19 | const select = val => MobxReactFormDevTools.select(val); 20 | 21 | MobxReactFormDevTools.register(forms); 22 | MobxReactFormDevTools.select(selected(menu)); 23 | MobxReactFormDevTools.open(true); 24 | 25 | const Container = observer(({ content }) => ( 26 |
27 |
28 | {content} 29 |
30 |
31 |
32 | )); 33 | 34 | export default observer(() => ( 35 |
36 | {/* */} 37 | 38 |
41 | )); 42 | -------------------------------------------------------------------------------- /src/components/Nav.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from 'mobx-react'; 3 | import { action } from 'mobx'; 4 | import _ from 'lodash'; 5 | 6 | const switchTo = (menu, select) => (e) => { 7 | e.preventDefault(); 8 | select(e.target.value); 9 | action(() => _.map(menu, ($val, $key) => _.set(menu, $key, false)))(); 10 | action(() => _.set(menu, e.target.value, true))(); 11 | }; 12 | 13 | export default observer(({ menu, select, selected }) => ( 14 |
15 | 46 |
47 | )); 48 | -------------------------------------------------------------------------------- /src/components/Switch.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from 'mobx-react'; 3 | 4 | import FormMarkdown from './forms/FormMarkdown'; 5 | import FormFileUpload from './forms/FormFileUpload'; 6 | import FormWithNestedFields from './forms/FormWithNestedFields'; 7 | import FormRegisterMaterial from './forms/FormRegisterMaterial'; 8 | import FormRegisterSimple from './forms/FormRegisterSimple'; 9 | import FormCompanyWidgets from './forms/FormCompanyWidgets'; 10 | import FormCompanySimple from './forms/FormCompanySimple'; 11 | import FormDynamicFieldsSelect from './forms/FormDynamicFieldsSelect'; 12 | 13 | export default observer(({ menu, forms }) => { 14 | switch (true) { 15 | 16 | case menu.markdown: 17 | return (); 18 | 19 | case menu.fileUpload: 20 | return (); 21 | 22 | case menu.nestedFields: 23 | return (); 24 | 25 | case menu.registerMaterial: 26 | return (); 27 | 28 | case menu.registerSimple: 29 | return (); 30 | 31 | case menu.companyWidgets: 32 | return (); 33 | 34 | case menu.companySimple: 35 | return (); 36 | 37 | case menu.dynamicFieldsSelect: 38 | return (); 39 | 40 | default: return null; 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /src/components/controls/FieldControls.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from 'mobx-react'; 3 | import $ from '../../styles'; 4 | import Button from '../Button'; 5 | 6 | export default observer(({ field, controls = null }) => ( 7 | 8 | 9 | {(!controls || controls.onSubmit) && 10 |
40 | )); 41 | -------------------------------------------------------------------------------- /src/components/fieldsets/ClubFieldset.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from 'mobx-react'; 3 | 4 | import FieldControl from '../controls/FieldControls'; 5 | import MaterialTextField from '../inputs/MaterialTextField'; 6 | 7 | export default observer(({ club }) => ( 8 |
9 | 10 |
11 |
12 | {club.label} 13 |
14 | 15 |
16 | 24 |
25 |
26 | 27 | 28 | 29 | 30 |
31 | 38 |
39 | 40 |
41 | )); 42 | -------------------------------------------------------------------------------- /src/components/fieldsets/NestedAllMembersFieldset.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from 'mobx-react'; 3 | 4 | import NestedMemberFieldset from './NestedMemberFieldset'; 5 | import FieldControl from '../controls/FieldControls'; 6 | 7 | export default observer(({ members }) => ( 8 |
9 | 10 |
11 |
12 | {members.label} 13 |
14 | 15 |
16 | 25 |
26 |
27 | 28 | {members.map(member => 29 | )} 33 | 34 |
35 | )); 36 | -------------------------------------------------------------------------------- /src/components/fieldsets/NestedHobbyFieldset.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from 'mobx-react'; 3 | 4 | import FieldControl from '../controls/FieldControls'; 5 | import NestedHobbyInput from '../inputs/NestedHobbyInput'; 6 | 7 | export default observer(({ hobbies }) => ( 8 |
9 | 10 |
11 |
12 | {[ 13 | hobbies.container().$('firstname').value, 14 | hobbies.container().$('lastname').value, 15 | hobbies.label, 16 | ].join(' ')} 17 |
18 |
19 | 26 |
27 |
28 | 29 | {hobbies.map(hobby => 30 | )} 34 | 35 |
36 | )); 37 | -------------------------------------------------------------------------------- /src/components/fieldsets/NestedMemberFieldset.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from 'mobx-react'; 3 | 4 | import NestedHobbyFieldset from '../fieldsets/NestedHobbyFieldset'; 5 | import FieldControl from '../controls/FieldControls'; 6 | import MaterialTextField from '../inputs/MaterialTextField'; 7 | 8 | export default observer(({ member }) => ( 9 |
10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 26 | 27 | 28 |
29 | 30 | {member.has('hobbies') && 31 | } 34 | 35 | 39 | 40 |
41 | )); 42 | -------------------------------------------------------------------------------- /src/components/forms/FormCompanySimple.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from 'mobx-react'; 3 | 4 | import Input from '../inputs/SimpleInput'; 5 | import Radio from '../inputs/SimpleRadio'; 6 | import Select from '../inputs/SimpleSelect'; 7 | import MultiSelect from '../inputs/ReactMultiSelect'; 8 | import FormControls from '../controls/FormControls'; 9 | 10 | export default observer(({ form }) => ( 11 |
12 |

Form Company

13 | 14 | 15 | 16 | 17 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | )); 24 | -------------------------------------------------------------------------------- /src/components/forms/FormDynamicFieldsSelect.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from 'mobx-react'; 3 | import Creatable from 'react-select/creatable'; 4 | 5 | import FormControls from '../controls/FormControls'; 6 | import MaterialTextField from '../inputs/MaterialTextField'; 7 | 8 | export default observer(({ form }) => ( 9 |
10 |

Dynamic Fields Select

11 |
Select or type new options (then press enter) to add new fields:
12 | 13 | 23 | 24 |

{form.$('dynamicFields').label}


25 | 26 | {form.$('dynamicFields').map(field => 27 | , 31 | )} 32 | 33 | 34 | 35 | )); 36 | -------------------------------------------------------------------------------- /src/components/forms/FormFileUpload.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from 'mobx-react'; 3 | 4 | import SimpleFile from '../inputs/SimpleFile'; 5 | import DropZone from '../inputs/DropZone'; 6 | import FormControls from '../controls/FormControls'; 7 | 8 | export default observer(({ form }) => ( 9 |
10 |

Form File Upload

11 | 12 | 13 |

14 |

Dropzone

15 | 16 |


17 | 18 | 19 | )); 20 | -------------------------------------------------------------------------------- /src/components/forms/FormMarkdown.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { observer } from 'mobx-react'; 3 | 4 | import ReactMarkdown from 'react-markdown'; 5 | import Textarea from '../inputs/SimpleTextarea'; 6 | import FormControls from '../controls/FormControls'; 7 | 8 | export default observer(({ form }) => ( 9 |
10 |

Form Markdown

11 | 12 |