├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── lerna.json ├── package-lock.json ├── package.json └── packages ├── create-react-app-demo ├── .gitignore ├── .vscode │ └── settings.json ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── index.tsx │ └── layout │ │ └── views │ │ └── components │ │ ├── App │ │ ├── App.tsx │ │ └── index.ts │ │ └── Theme │ │ ├── Theme.tsx │ │ └── index.ts ├── tsconfig.json ├── tsconfig.prod.json ├── tsconfig.test.json ├── tslint.json └── typings │ └── jss.d.ts ├── react-mst-form-demo ├── .gitignore ├── .vscode │ └── settings.json ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src │ ├── Designer │ │ ├── Designer.tsx │ │ └── index.ts │ ├── Editor │ │ ├── Editor.tsx │ │ └── index.ts │ ├── Form │ │ ├── Form.tsx │ │ └── index.ts │ ├── Iconer │ │ ├── Iconer.tsx │ │ └── index.ts │ ├── index.ts │ └── schemas │ │ ├── array.json │ │ ├── comment.json │ │ ├── complex.json │ │ ├── date.json │ │ ├── deep.json │ │ ├── everything.json │ │ ├── gender.json │ │ ├── multiple.json │ │ └── select.json ├── tsconfig.json └── tslint.json ├── react-mst-form ├── .gitignore ├── .npmignore ├── .vscode │ └── settings.json ├── LICENSE ├── README.md ├── demo │ ├── form-validation.png │ └── sections.png ├── package-lock.json ├── package.json ├── src │ ├── components │ │ ├── Cancel │ │ │ ├── Cancel.tsx │ │ │ └── index.ts │ │ ├── Content │ │ │ ├── Content.tsx │ │ │ └── index.ts │ │ ├── Dialog │ │ │ ├── Dialog.tsx │ │ │ └── index.ts │ │ ├── Footer │ │ │ ├── Footer.tsx │ │ │ └── index.ts │ │ ├── Form │ │ │ ├── Form.tsx │ │ │ └── index.ts │ │ ├── Header │ │ │ ├── Header.tsx │ │ │ └── index.ts │ │ ├── Inline │ │ │ ├── Inline.tsx │ │ │ └── index.ts │ │ ├── Submit │ │ │ ├── Submit.tsx │ │ │ └── index.ts │ │ ├── Type │ │ │ ├── Array │ │ │ │ ├── Array.tsx │ │ │ │ └── index.ts │ │ │ ├── Boolean │ │ │ │ ├── Boolean.tsx │ │ │ │ ├── Checkbox │ │ │ │ │ ├── Checkbox.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Radios │ │ │ │ │ ├── Radios.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Select │ │ │ │ │ ├── Select.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Switch │ │ │ │ │ ├── Switch.tsx │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ ├── Error │ │ │ │ ├── Error.tsx │ │ │ │ └── index.ts │ │ │ ├── Number │ │ │ │ ├── Number.tsx │ │ │ │ ├── Numeric │ │ │ │ │ ├── Numeric.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Radios │ │ │ │ │ ├── Radios.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Range │ │ │ │ │ ├── Range.tsx │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ ├── Object │ │ │ │ ├── Object.tsx │ │ │ │ └── index.ts │ │ │ ├── Renderer │ │ │ │ ├── Icon │ │ │ │ │ ├── Renderer.tsx │ │ │ │ │ └── index.ts │ │ │ │ └── Type │ │ │ │ │ ├── Renderer.tsx │ │ │ │ │ └── index.ts │ │ │ ├── String │ │ │ │ ├── Color │ │ │ │ │ ├── Color.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── Multiline │ │ │ │ │ ├── Multiline.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── String.tsx │ │ │ │ ├── Text │ │ │ │ │ ├── Text.tsx │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ ├── Type │ │ │ │ ├── Type.tsx │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ └── index.ts │ ├── index.ts │ ├── material.ts │ ├── models │ │ ├── Form │ │ │ ├── Form.test.ts │ │ │ ├── Form.ts │ │ │ └── index.ts │ │ ├── Section │ │ │ ├── Section.ts │ │ │ └── index.ts │ │ └── index.ts │ └── utils │ │ ├── assign.ts │ │ ├── common.ts │ │ ├── decimals.ts │ │ ├── flatArray.ts │ │ ├── flatMap.ts │ │ ├── index.ts │ │ ├── merge.ts │ │ ├── regex.ts │ │ └── unique.ts ├── tsconfig.json └── tslint.json └── typescript-react-app-demo ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── demo ├── form-validation.png └── sections.png ├── package-lock.json ├── package.json ├── src ├── App.tsx ├── Theme.tsx ├── client.js ├── client.js.map ├── client.tsx ├── form-validation.png ├── form.png ├── index.html ├── sections.png └── server.ts ├── tsconfig.json ├── tslint.json ├── typings └── misc.d.ts └── webpack.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build 3 | lib 4 | lib-esm 5 | .DS_Store 6 | *.tgz 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | dist 11 | stats.json -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 naguvan 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-mst-form 2 | 3 | Library for generating React forms from [JSON schema](https://json-schema.org/) using the [react](https://github.com/facebook/react), [material-ui](https://github.com/mui-org/material-ui), [mobx](https://github.com/mobxjs/mobx) and [mobx-state-tree](https://github.com/mobxjs/mobx-state-tree). 4 | 5 | **https://naguvan.github.io/react-mst-form/packages/typescript-react-app-demo/src/index.html** 6 | 7 | # Running the demo 8 | 9 | To run the `demo`, clone this repository, then run: 10 | 11 | ```bash 12 | lerna bootstrap 13 | 14 | cd packages/typescript-react-app-demo or cd packages/create-react-app-demo 15 | 16 | npm run start 17 | ``` 18 | 19 | # Basic usage 20 | 21 | ```jsx 22 | import React from "react"; 23 | import { render } from "react-dom"; 24 | 25 | import { create } from "jss"; 26 | import preset from "jss-preset-default"; 27 | import JssProvider from "react-jss/lib/JssProvider"; 28 | 29 | import MuiThemeProvider from "material-ui/styles/MuiThemeProvider"; 30 | import createMuiTheme from "material-ui/styles/createMuiTheme"; 31 | 32 | import { Form } from "react-mst-form"; 33 | 34 | const schema = { 35 | type: "object", 36 | properties: { 37 | name: { 38 | type: "object", 39 | properties: { 40 | first: { 41 | type: "string", 42 | title: "First", 43 | minLength: 5 44 | }, 45 | middle: { 46 | type: "string", 47 | title: "Middle", 48 | minLength: 5 49 | }, 50 | last: { 51 | type: "string", 52 | title: "Last", 53 | minLength: 5 54 | }, 55 | age: { 56 | type: "number", 57 | title: "Age", 58 | maximum: 10, 59 | minimum: 3 60 | } 61 | } 62 | }, 63 | birthdate: { 64 | format: "date", 65 | type: "string", 66 | title: "Birth date" 67 | }, 68 | ipv4: { 69 | type: "string", 70 | title: "ipv4", 71 | minLength: 5, 72 | maxLength: 20, 73 | format: "ipv4" 74 | }, 75 | color: { 76 | type: "string", 77 | title: "In which color", 78 | format: "color" 79 | }, 80 | size: { 81 | type: "number", 82 | title: "Size", 83 | maximum: 10, 84 | minimum: 3, 85 | multipleOf: 3 86 | }, 87 | type: { 88 | type: "number", 89 | title: "Select a type", 90 | enum: [1, 2, 3] 91 | }, 92 | agree: { 93 | type: "boolean", 94 | title: "I agree with your terms", 95 | const: true 96 | }, 97 | array: { 98 | type: "array", 99 | title: "Array", 100 | items: { 101 | type: "object", 102 | properties: { 103 | name: { 104 | type: "string", 105 | title: "name", 106 | minLength: 3 107 | }, 108 | age: { 109 | type: "number", 110 | title: "age", 111 | multipleOf: 2, 112 | minimum: 2 113 | } 114 | } 115 | }, 116 | minItems: 2, 117 | maxItems: 4 118 | } 119 | } 120 | }; 121 | 122 | const meta = { 123 | type: "object", 124 | properties: { 125 | name: { 126 | layout: [["first", "last"], "middle", "age"], 127 | type: "object", 128 | properties: { 129 | first: { 130 | sequence: 1, 131 | icon: "face", 132 | iconAlign: "start", 133 | type: "string" 134 | }, 135 | middle: { 136 | sequence: 1, 137 | type: "string" 138 | }, 139 | last: { 140 | sequence: 2, 141 | type: "string" 142 | }, 143 | age: { 144 | sequence: 2, 145 | icon: "build", 146 | type: "number" 147 | } 148 | } 149 | }, 150 | birthdate: { 151 | component: "date", 152 | icon: "date-range", 153 | iconAlign: "end", 154 | type: "string" 155 | }, 156 | color: { 157 | component: "color", 158 | type: "string" 159 | }, 160 | size: { 161 | component: "range", 162 | step: 1, 163 | type: "number" 164 | }, 165 | type: { 166 | error: "should not be empty", 167 | options: [ 168 | { label: "One", value: 1 }, 169 | { label: "Two", value: 2 }, 170 | { label: "Three", value: 3 } 171 | ], 172 | type: "number" 173 | }, 174 | agree: { 175 | type: "boolean" 176 | }, 177 | array: { 178 | type: "array", 179 | items: { 180 | properties: { 181 | age: { 182 | type: "number" 183 | } 184 | }, 185 | type: "object" 186 | } 187 | } 188 | } 189 | }; 190 | 191 | const config = { 192 | title: "Test Form", 193 | cancel: "Cancel", 194 | submit: "create", 195 | sections: [ 196 | { 197 | title: "Basic", 198 | layout: ["name", "birthdate", ["size", "color"]] 199 | }, 200 | { 201 | title: "Others", 202 | layout: ["ipv4", "type", "agree", "array"] 203 | } 204 | ] 205 | }; 206 | 207 | const snapshot = { 208 | name: { 209 | first: "naguvan", 210 | middle: "sk", 211 | last: "sk", 212 | age: 1 213 | }, 214 | birthdate: "2018-10-29", 215 | size: 5, 216 | agree: false 217 | }; 218 | 219 | const onSubmit = values => { 220 | window.alert(`submitted values:\n\n${JSON.stringify(values, null, 2)}`); 221 | }; 222 | 223 | const jss = create(preset()); 224 | 225 | render( 226 | 227 | 228 |
235 | 236 | , 237 | document.getElementById("form-holder") 238 | ); 239 | ``` 240 | 241 | And, provided that you have a `
`, you should see something like this: 242 | 243 | ![](https://raw.githubusercontent.com/naguvan/react-mst-form/master/packages/react-mst-form/demo/sections.png) 244 | 245 | And when the form has validation errors.. 246 | 247 | ![](https://raw.githubusercontent.com/naguvan/react-mst-form/master/packages/react-mst-form/demo/form-validation.png) 248 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*"], 3 | "version": "independent" 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "repository": "https://github.com/naguvan/react-mst-form.git", 4 | "author": "naguvan ", 5 | "license": "MIT", 6 | "private": false, 7 | "workspaces": [ 8 | "packages/*" 9 | ], 10 | "devDependencies": { 11 | "lerna": "3.4.3", 12 | "typescript": "3.1.6" 13 | }, 14 | "scripts": { 15 | "bootstrap": "lerna bootstrap" 16 | }, 17 | "dependencies": {} 18 | } 19 | -------------------------------------------------------------------------------- /packages/create-react-app-demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | /lib 13 | /lib-esm 14 | 15 | # misc 16 | .DS_Store 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | -------------------------------------------------------------------------------- /packages/create-react-app-demo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /packages/create-react-app-demo/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 naguvan 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. -------------------------------------------------------------------------------- /packages/create-react-app-demo/README.md: -------------------------------------------------------------------------------- 1 | # create-react-app-demo 2 | -------------------------------------------------------------------------------- /packages/create-react-app-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-react-app-demo", 3 | "version": "2.0.0", 4 | "description": "create-react-app demo", 5 | "author": "naguvan ", 6 | "license": "MIT", 7 | "private": false, 8 | "scripts": { 9 | "audit": "npm audit fix", 10 | "prepare": "npm run build", 11 | "prebuild": "npm run lint && npm run clean", 12 | "clean": "rimraf build", 13 | "start": "react-scripts-ts start", 14 | "build": "react-scripts-ts build", 15 | "test": "react-scripts-ts test --env=jsdom", 16 | "eject": "react-scripts-ts eject", 17 | "lint": "tslint -p .", 18 | "precommit": "npm run lint && pretty-quick --staged", 19 | "pretty": "prettier --write src/**/*.{ts,tsx}" 20 | }, 21 | "dependencies": { 22 | "@material-ui/core": "3.4.0", 23 | "classnames": "2.2.6", 24 | "jss-preset-default": "4.5.0", 25 | "react": "16.6.1", 26 | "react-dom": "16.6.1", 27 | "react-jss": "8.6.1", 28 | "reflect-metadata": "0.1.12", 29 | "react-mst-form-demo": "2.0.0" 30 | }, 31 | "devDependencies": { 32 | "@types/classnames": "2.2.6", 33 | "@types/react": "16.7.1", 34 | "@types/react-dom": "16.0.9", 35 | "@types/react-jss": "8.6.0", 36 | "cross-env": "5.2.0", 37 | "husky": "1.1.3", 38 | "jest": "23.6.0", 39 | "prettier": "1.15.1", 40 | "pretty-quick": "1.8.0", 41 | "rimraf": "2.6.2", 42 | "ts-jest": "23.10.4", 43 | "ts-loader": "5.3.0", 44 | "ts-node": "7.0.1", 45 | "tslint": "5.11.0", 46 | "tslint-config-prettier": "1.15.0", 47 | "tslint-loader": "3.5.4", 48 | "tslint-react": "3.6.0", 49 | "typescript": "3.1.6", 50 | "react-scripts-ts": "3.1.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/create-react-app-demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naguvan/react-mst-form/5bb72d852d02dd9448dac969a5ffa6ca647060f8/packages/create-react-app-demo/public/favicon.ico -------------------------------------------------------------------------------- /packages/create-react-app-demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /packages/create-react-app-demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /packages/create-react-app-demo/src/index.tsx: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | 3 | import * as React from "react"; 4 | import { render } from "react-dom"; 5 | 6 | import App from "./layout/views/components/App"; 7 | 8 | async function main() { 9 | render(, document.getElementById("root") as HTMLElement); 10 | } 11 | 12 | (async () => { 13 | try { 14 | await main(); 15 | } catch (ex) { 16 | // tslint:disable-next-line:no-console 17 | console.error(ex); 18 | } 19 | })(); 20 | -------------------------------------------------------------------------------- /packages/create-react-app-demo/src/layout/views/components/App/App.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Component, ReactNode } from "react"; 3 | 4 | import { Designer } from "react-mst-form-demo"; 5 | 6 | import Theme from "../Theme"; 7 | 8 | // tslint:disable-next-line:no-empty-interface 9 | export interface IAppProps {} 10 | 11 | // tslint:disable-next-line:no-empty-interface 12 | export interface IAppStates {} 13 | 14 | export default class App extends Component { 15 | public render(): ReactNode { 16 | return ( 17 | 18 | 19 | 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/create-react-app-demo/src/layout/views/components/App/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./App"; 2 | export { default } from "./App"; 3 | -------------------------------------------------------------------------------- /packages/create-react-app-demo/src/layout/views/components/Theme/Theme.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Component, ReactNode } from "react"; 3 | 4 | import { create } from "jss"; 5 | 6 | import preset from "jss-preset-default"; 7 | 8 | import JssProvider from "react-jss/lib/JssProvider"; 9 | 10 | import { Theme as ITheme } from "@material-ui/core/styles"; 11 | import MuiThemeProvider from "@material-ui/core/styles/MuiThemeProvider"; 12 | 13 | import createMuiTheme from "@material-ui/core/styles/createMuiTheme"; 14 | 15 | import createGenerateClassName from "@material-ui/core/styles/createGenerateClassName"; 16 | 17 | import Switch from "@material-ui/core/Switch"; 18 | 19 | import CssBaseline from "@material-ui/core/CssBaseline"; 20 | 21 | export interface IThemeProps { 22 | theme: "dark" | "light"; 23 | } 24 | 25 | export interface IThemeStates { 26 | theme: "dark" | "light"; 27 | } 28 | 29 | // Create a JSS instance with the default preset of plugins. 30 | // It's optional. 31 | 32 | const jss = create(preset()); 33 | 34 | const generateClassName = createGenerateClassName(); 35 | 36 | export default class Theme extends Component { 37 | constructor(props: IThemeProps) { 38 | super(props); 39 | this.state = { theme: props.theme }; 40 | } 41 | 42 | public render(): ReactNode { 43 | const { children } = this.props; 44 | const { theme } = this.state; 45 | return ( 46 | 47 | 48 | 49 | 54 | {children} 55 | 56 | 57 | ); 58 | } 59 | 60 | private toggleTheme = () => { 61 | this.setState(({ theme }) => ({ 62 | theme: theme === "light" ? "dark" : "light" 63 | })); 64 | }; 65 | 66 | private getTheme(theme: "dark" | "light"): ITheme { 67 | return createMuiTheme({ 68 | palette: { 69 | type: theme 70 | }, 71 | typography: { 72 | useNextVariants: true 73 | } 74 | // palette: { 75 | // // primary: green, 76 | // // accent: red, 77 | // shades: theme || 'light' 78 | // }}); 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/create-react-app-demo/src/layout/views/components/Theme/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Theme"; 2 | export { default } from "./Theme"; 3 | -------------------------------------------------------------------------------- /packages/create-react-app-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "build/dist", 5 | "module": "esnext", 6 | "target": "es5", 7 | "types": ["reflect-metadata", "node"], 8 | "lib": ["es6", "dom", "es2015", "es2016", "es2017"], 9 | "sourceMap": true, 10 | "allowJs": true, 11 | "jsx": "react", 12 | "moduleResolution": "node", 13 | "rootDir": "src", 14 | "typeRoots": ["./node_modules/@types"], 15 | "forceConsistentCasingInFileNames": true, 16 | "noImplicitReturns": true, 17 | "noImplicitThis": true, 18 | "noImplicitAny": true, 19 | "strictNullChecks": true, 20 | "suppressImplicitAnyIndexErrors": true, 21 | "noUnusedLocals": true, 22 | "experimentalDecorators": true, 23 | "emitDecoratorMetadata": true 24 | }, 25 | "exclude": [ 26 | "node_modules", 27 | "build", 28 | "scripts", 29 | "acceptance-tests", 30 | "webpack", 31 | "jest", 32 | "src/setupTests.ts" 33 | ], 34 | "include": ["typings"] 35 | } 36 | -------------------------------------------------------------------------------- /packages/create-react-app-demo/tsconfig.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/create-react-app-demo/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/create-react-app-demo/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], 3 | "linterOptions": { 4 | "exclude": ["config/**/*.js", "node_modules/**/*.ts"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/create-react-app-demo/typings/jss.d.ts: -------------------------------------------------------------------------------- 1 | declare module "jss-preset-default"; 2 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build 3 | lib 4 | lib-esm 5 | .DS_Store 6 | *.tgz 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | dist 11 | stats.json -------------------------------------------------------------------------------- /packages/react-mst-form-demo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 naguvan 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. -------------------------------------------------------------------------------- /packages/react-mst-form-demo/README.md: -------------------------------------------------------------------------------- 1 | #react-mst-form-demo 2 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-mst-form-demo", 3 | "version": "2.0.0", 4 | "description": "react-mst-form-demo", 5 | "main": "./lib/index.js", 6 | "typings": "./lib/index.d.ts", 7 | "repository": "https://github.com/naguvan/react-mst-form.git", 8 | "author": "naguvan ", 9 | "license": "MIT", 10 | "private": false, 11 | "bugs": { 12 | "url": "https://github.com/naguvan/react-mst-form/issues" 13 | }, 14 | "files": [ 15 | "lib/", 16 | "lib-esm/" 17 | ], 18 | "peerDependencies": { 19 | "@material-ui/core": "^3.1.2", 20 | "react": "^16.5.2", 21 | "react-dom": "^16.5.2" 22 | }, 23 | "dependencies": { 24 | "@material-ui/core": "3.4.0", 25 | "@material-ui/icons": "3.0.1", 26 | "classnames": "2.2.6", 27 | "react": "16.6.1", 28 | "react-dom": "16.6.1", 29 | "react-mst-form": "2.0.0", 30 | "react-flow-layout": "1.5.0" 31 | }, 32 | "devDependencies": { 33 | "@types/classnames": "2.2.6", 34 | "@types/jest": "23.3.9", 35 | "@types/react": "16.7.1", 36 | "@types/react-dom": "16.0.9", 37 | "cross-env": "5.2.0", 38 | "husky": "1.1.3", 39 | "jest": "23.6.0", 40 | "lerna": "3.4.3", 41 | "prettier": "1.15.1", 42 | "pretty-quick": "1.8.0", 43 | "rimraf": "2.6.2", 44 | "ts-jest": "23.10.4", 45 | "ts-loader": "5.3.0", 46 | "ts-node": "7.0.1", 47 | "tslint": "5.11.0", 48 | "tslint-config-prettier": "1.15.0", 49 | "tslint-loader": "3.5.4", 50 | "tslint-react": "3.6.0", 51 | "typescript": "3.1.6" 52 | }, 53 | "keywords": [ 54 | "react", 55 | "flow", 56 | "layout", 57 | "typescript" 58 | ], 59 | "jest": { 60 | "transform": { 61 | "^.+\\.tsx?$": "ts-jest" 62 | }, 63 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 64 | "moduleFileExtensions": [ 65 | "ts", 66 | "tsx", 67 | "js", 68 | "jsx", 69 | "json", 70 | "node" 71 | ], 72 | "modulePaths": [ 73 | "/src/" 74 | ], 75 | "roots": [ 76 | "/src/" 77 | ] 78 | }, 79 | "scripts": { 80 | "audit": "npm audit fix", 81 | "prepare": "npm run build", 82 | "prebuild": "npm run lint && npm run clean", 83 | "clean": "rimraf {lib,lib-esm}", 84 | "lint": "tslint -p .", 85 | "precommit": "npm run lint && pretty-quick --staged", 86 | "pretty": "prettier --write 'src/**/*.{ts,tsx}'", 87 | "test": "cross-env NODE_ENV=production TS_NODE_COMPILER_OPTIONS={\\\"module\\\":\\\"commonjs\\\",\\\"declaration\\\":false} jest --runInBand", 88 | "env": "cross-env NODE_ENV=production TS_NODE_COMPILER_OPTIONS={\\\"module\\\":\\\"commonjs\\\"}", 89 | "dev:tsc": "tsc && tsc -m es6 --outDir lib-esm", 90 | "build": "npm run dev:tsc" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/Designer/Designer.tsx: -------------------------------------------------------------------------------- 1 | // tslint:disable:max-file-line-count 2 | 3 | // tslint:disable:object-literal-sort-keys 4 | 5 | // tslint:disable:no-console 6 | 7 | import * as React from "react"; 8 | import { Component, ReactNode } from "react"; 9 | 10 | import Button from "@material-ui/core/Button"; 11 | import Paper from "@material-ui/core/Paper"; 12 | import { CSSProperties, WithStyles } from "@material-ui/core/styles/withStyles"; 13 | import withStyles from "@material-ui/core/styles/withStyles"; 14 | 15 | import classNames from "classnames"; 16 | import Layout from "react-flow-layout"; 17 | import { IFormConfig, IMetaConfig, ISchemaConfig } from "react-mst-form"; 18 | 19 | import Editor from "../Editor"; 20 | import Form from "../Form"; 21 | 22 | export interface IDesignerStyles { 23 | root: CSSProperties; 24 | action: CSSProperties; 25 | container: CSSProperties; 26 | designer: CSSProperties; 27 | schema: CSSProperties; 28 | formItem: CSSProperties; 29 | form: CSSProperties; 30 | stretch: CSSProperties; 31 | } 32 | 33 | export interface IDesignerStyleProps 34 | extends WithStyles {} 35 | 36 | export interface IDesignerProps { 37 | style?: CSSProperties; 38 | className?: string; 39 | } 40 | 41 | export interface IDesignerStates { 42 | width: string; 43 | height: string; 44 | meta: string; 45 | schema: string; 46 | config: string; 47 | snapshot: string; 48 | open: boolean; 49 | form: { 50 | schema?: ISchemaConfig; 51 | config?: IFormConfig; 52 | snapshot?: {}; 53 | meta?: IMetaConfig; 54 | }; 55 | } 56 | 57 | const schema: ISchemaConfig = { 58 | type: "object", 59 | properties: { 60 | name: { 61 | type: "object", 62 | properties: { 63 | first: { 64 | type: "string", 65 | title: "First", 66 | minLength: 5 67 | }, 68 | middle: { 69 | type: "string", 70 | title: "Middle", 71 | minLength: 5 72 | }, 73 | last: { 74 | type: "string", 75 | title: "Last", 76 | minLength: 5 77 | }, 78 | age: { 79 | type: "number", 80 | title: "Age", 81 | maximum: 10, 82 | minimum: 3 83 | } 84 | } 85 | }, 86 | birthdate: { 87 | format: "date", 88 | type: "string", 89 | title: "Birth date" 90 | }, 91 | ipv4: { 92 | type: "string", 93 | title: "ipv4", 94 | minLength: 5, 95 | maxLength: 20, 96 | format: "ipv4" 97 | }, 98 | color: { 99 | type: "string", 100 | title: "In which color", 101 | format: "color" 102 | }, 103 | size: { 104 | type: "number", 105 | title: "Size", 106 | maximum: 10, 107 | minimum: 3, 108 | multipleOf: 3 109 | }, 110 | type: { 111 | type: "number", 112 | title: "Select a type", 113 | enum: [1, 2, 3] 114 | }, 115 | agree: { 116 | type: "boolean", 117 | title: "I agree with your terms", 118 | const: true 119 | }, 120 | array: { 121 | type: "array", 122 | title: "Array", 123 | items: { 124 | type: "object", 125 | properties: { 126 | name: { 127 | type: "string", 128 | title: "name", 129 | minLength: 3 130 | }, 131 | age: { 132 | type: "number", 133 | title: "age", 134 | multipleOf: 2, 135 | minimum: 2 136 | } 137 | } 138 | }, 139 | minItems: 2, 140 | maxItems: 4 141 | } 142 | } 143 | }; 144 | 145 | const meta: IMetaConfig = { 146 | type: "object", 147 | properties: { 148 | name: { 149 | layout: [["first", "last"], "middle", "age"], 150 | type: "object", 151 | properties: { 152 | first: { 153 | sequence: 1, 154 | icon: "face", 155 | iconAlign: "start", 156 | type: "string" 157 | }, 158 | middle: { 159 | sequence: 1, 160 | type: "string" 161 | }, 162 | last: { 163 | sequence: 2, 164 | type: "string" 165 | }, 166 | age: { 167 | sequence: 2, 168 | icon: "build", 169 | type: "number" 170 | } 171 | } 172 | }, 173 | birthdate: { 174 | component: "date", 175 | icon: "date-range", 176 | iconAlign: "end", 177 | type: "string" 178 | }, 179 | color: { 180 | component: "color", 181 | type: "string" 182 | }, 183 | size: { 184 | component: "range", 185 | step: 1, 186 | type: "number" 187 | }, 188 | type: { 189 | error: "should not be empty", 190 | options: [ 191 | { label: "One", value: 1 }, 192 | { label: "Two", value: 2 }, 193 | { label: "Three", value: 3 } 194 | ], 195 | type: "number" 196 | }, 197 | agree: { 198 | type: "boolean" 199 | }, 200 | array: { 201 | type: "array", 202 | items: { 203 | properties: { 204 | age: { 205 | type: "number" 206 | } 207 | }, 208 | type: "object" 209 | } 210 | } 211 | } 212 | }; 213 | 214 | const config: IFormConfig = { 215 | title: "Test Form", 216 | cancel: "Cancel", 217 | submit: "create", 218 | sections: [ 219 | { 220 | title: "Basic", 221 | layout: ["name", "birthdate", ["size", "color"]] 222 | }, 223 | { 224 | title: "Others", 225 | layout: ["ipv4", "type", "agree", "array"] 226 | } 227 | ] 228 | }; 229 | 230 | const snapshot = { 231 | name: { 232 | first: "naguvan", 233 | middle: "sk", 234 | last: "sk", 235 | age: 1 236 | }, 237 | birthdate: "2018-10-29", 238 | size: 5, 239 | agree: false 240 | }; 241 | 242 | export class Designer extends Component< 243 | IDesignerProps & IDesignerStyleProps, 244 | IDesignerStates 245 | > { 246 | public state: IDesignerStates = { 247 | width: "100%", 248 | height: "100%", 249 | config: JSON.stringify(config, null, 2), 250 | open: false, 251 | meta: JSON.stringify(meta, null, 2), 252 | schema: JSON.stringify(schema, null, 2), 253 | snapshot: JSON.stringify(snapshot, null, 2), 254 | form: { 255 | config, 256 | schema, 257 | meta, 258 | snapshot 259 | } 260 | }; 261 | 262 | public render(): ReactNode { 263 | const { className: clazz, classes, style } = this.props; 264 | const { width, height, open } = this.state; 265 | // tslint:disable-next-line:no-shadowed-variable 266 | const { config, meta, schema, form, snapshot } = this.state; 267 | const className: string = classNames(classes!.root, clazz); 268 | return ( 269 |
270 | 271 | 275 | 278 | 281 |
, 282 | [ 283 | [ 284 | [ 285 | , 290 | 295 | ], 296 | [ 297 | , 302 | 307 | ] 308 | ], 309 | 310 | 319 | 320 | ] 321 | ]} 322 | > 323 | {(item: ReactNode) =>
{item}
} 324 | 325 | 326 |
327 | ); 328 | } 329 | 330 | private getConfig(): IFormConfig | undefined { 331 | return this.parse(this.state.config); 332 | } 333 | 334 | private getSchema(): ISchemaConfig | undefined { 335 | return this.parse(this.state.schema); 336 | } 337 | 338 | private getMeta(): IMetaConfig | undefined { 339 | return this.parse(this.state.meta); 340 | } 341 | 342 | private getSnapshot(): {} | undefined { 343 | return this.parse(this.state.snapshot); 344 | } 345 | 346 | private parse(value: string): T | undefined { 347 | try { 348 | return JSON.parse(value); 349 | } catch (e) { 350 | return undefined; 351 | } 352 | } 353 | 354 | private handleClickOpen = () => { 355 | this.setState({ open: true }); 356 | }; 357 | 358 | private handleRender = () => { 359 | let form = this.state.form; 360 | // tslint:disable-next-line:no-shadowed-variable 361 | const config = this.getConfig(); 362 | if (config) { 363 | form = { ...form, config }; 364 | } 365 | // tslint:disable-next-line:no-shadowed-variable 366 | const schema = this.getSchema(); 367 | if (schema) { 368 | form = { ...form, schema }; 369 | } 370 | // tslint:disable-next-line:no-shadowed-variable 371 | const meta = this.getMeta(); 372 | if (meta && Object.keys(meta).length) { 373 | form = { ...form, meta }; 374 | } 375 | // tslint:disable-next-line:no-shadowed-variable 376 | const snapshot = this.getSnapshot(); 377 | if (snapshot) { 378 | form = { ...form, snapshot }; 379 | } 380 | 381 | this.setState({ form }); 382 | }; 383 | 384 | private handleClose = () => { 385 | this.setState({ open: false }); 386 | }; 387 | 388 | // tslint:disable-next-line:no-shadowed-variable 389 | private onConfig = (config: string) => { 390 | this.setState(() => ({ config })); 391 | }; 392 | 393 | // tslint:disable-next-line:no-shadowed-variable 394 | private onSchema = (schema: string) => { 395 | this.setState(() => ({ schema })); 396 | }; 397 | 398 | // tslint:disable-next-line:no-shadowed-variable 399 | private onSnapshot = (snapshot: string) => { 400 | this.setState(() => ({ snapshot })); 401 | }; 402 | 403 | // tslint:disable-next-line:no-shadowed-variable 404 | private onMeta = (meta: string) => { 405 | this.setState(() => ({ meta })); 406 | }; 407 | } 408 | 409 | export default withStyles({ 410 | action: { 411 | display: "flex", 412 | flexDirection: "row", 413 | justifyContent: "space-between" 414 | }, 415 | container: { 416 | // minWidth: 1000 417 | }, 418 | designer: { 419 | justifyContent: "space-around" 420 | }, 421 | form: { 422 | // height: 450 423 | }, 424 | formItem: { 425 | // flexDirection: "column", 426 | // flex: 0, 427 | // height: 450, 428 | // minWidth: 520, 429 | padding: 10 430 | }, 431 | root: { 432 | // display: "flex", 433 | // justifyContent: "center", 434 | margin: 20 435 | }, 436 | schema: { 437 | // flexDirection: "column", 438 | // flex: 0, 439 | // minWidth: 520 440 | }, 441 | stretch: { 442 | padding: 10, 443 | width: "100%" 444 | } 445 | })(Designer); 446 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/Designer/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Designer"; 2 | export { default } from "./Designer"; 3 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/Editor/Editor.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ChangeEvent, Component, ReactNode } from "react"; 3 | 4 | import { CSSProperties, WithStyles } from "@material-ui/core/styles/withStyles"; 5 | import withStyles from "@material-ui/core/styles/withStyles"; 6 | 7 | import Card from "@material-ui/core/Card"; 8 | import CardContent from "@material-ui/core/CardContent"; 9 | import CardHeader from "@material-ui/core/CardHeader"; 10 | import InputBase from "@material-ui/core/InputBase"; 11 | 12 | export interface IEditorStyles { 13 | editor: CSSProperties; 14 | card: CSSProperties; 15 | content: CSSProperties; 16 | header: CSSProperties; 17 | } 18 | 19 | export interface IEditorStyleProps extends WithStyles {} 20 | 21 | export interface IEditorProps { 22 | style?: CSSProperties; 23 | className?: string; 24 | title: string; 25 | value: string; 26 | onChange(value: string): void; 27 | } 28 | 29 | // tslint:disable-next-line:no-empty-interface 30 | export interface IEditorStates {} 31 | 32 | export class Editor extends Component< 33 | IEditorProps & IEditorStyleProps, 34 | IEditorStates 35 | > { 36 | public render(): ReactNode { 37 | const { className, classes, style } = this.props; 38 | const { value, title } = this.props; 39 | return ( 40 |
41 | 42 | 43 | 44 | 52 | 53 | 54 |
55 | ); 56 | } 57 | 58 | private onChange = (event: ChangeEvent) => { 59 | const value = event.currentTarget.value; 60 | const { onChange } = this.props; 61 | if (onChange) { 62 | onChange(value); 63 | } 64 | }; 65 | } 66 | 67 | export default withStyles({ 68 | card: {}, 69 | content: { 70 | paddingTop: 2 71 | }, 72 | editor: { 73 | height: 300, 74 | padding: 10 75 | }, 76 | header: { 77 | paddingBottom: 2 78 | } 79 | })(Editor); 80 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/Editor/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Editor"; 2 | export { default } from "./Editor"; 3 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/Form/Form.tsx: -------------------------------------------------------------------------------- 1 | // tslint:disable:object-literal-sort-keys 2 | 3 | // tslint:disable:no-console 4 | 5 | import * as React from "react"; 6 | import { Component, ReactNode } from "react"; 7 | 8 | import { CSSProperties, WithStyles } from "@material-ui/core/styles/withStyles"; 9 | 10 | import withStyles from "@material-ui/core/styles/withStyles"; 11 | import classNames from "classnames"; 12 | 13 | import { 14 | FormDialog, 15 | FormInline, 16 | IFormConfig, 17 | IMetaConfig, 18 | ISchemaConfig 19 | } from "react-mst-form"; 20 | 21 | import Iconer from "../Iconer"; 22 | 23 | export interface IFormStyles { 24 | root: CSSProperties; 25 | dialog: CSSProperties; 26 | } 27 | 28 | export interface IFormStyleProps extends WithStyles {} 29 | 30 | export interface IFormProps { 31 | style?: CSSProperties; 32 | className?: string; 33 | meta?: IMetaConfig; 34 | schema?: ISchemaConfig; 35 | config: IFormConfig; 36 | snapshot?: {}; 37 | open?: boolean; 38 | onClose?: () => void; 39 | } 40 | 41 | // tslint:disable-next-line:no-empty-interface 42 | export interface IFormStates {} 43 | 44 | export class Form extends Component { 45 | public render(): ReactNode { 46 | const { className, classes, style } = this.props; 47 | const root: string = classNames(classes!.root, className); 48 | const { config, open, onClose } = this.props; 49 | const { schema, meta, snapshot } = this.props; 50 | const iconer = new Iconer(); 51 | 52 | const props = { config, schema, meta, iconer, snapshot }; 53 | const { onCancel, onSubmit, onErrors, onPatch, onSnapshot } = this; 54 | const events = { onCancel, onSubmit, onErrors, onPatch, onSnapshot }; 55 | 56 | return ( 57 |
58 | 59 | {open && ( 60 | 68 | )} 69 |
70 | ); 71 | } 72 | 73 | private onCancel = () => { 74 | window.alert(`form cancelled`); 75 | }; 76 | 77 | private onSubmit = (values: { [key: string]: any }) => { 78 | console.info(values); 79 | window.alert(`submitted values:\n\n${JSON.stringify(values, null, 2)}`); 80 | }; 81 | 82 | private onErrors = (errors: any) => { 83 | console.error(errors); 84 | window.alert(`errors:\n\n${JSON.stringify(errors, null, 2)}`); 85 | }; 86 | 87 | private onPatch = (patch: { 88 | op: "replace" | "add" | "remove"; 89 | path: string; 90 | value?: any; 91 | }): void => { 92 | console.info(patch); 93 | }; 94 | 95 | private onSnapshot = (snapshot: {}): void => { 96 | console.info(snapshot); 97 | }; 98 | } 99 | 100 | export default withStyles({ 101 | dialog: { 102 | width: 500, 103 | height: 460 104 | }, 105 | root: { 106 | margin: "0 auto", 107 | height: 460 108 | } 109 | })(Form); 110 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/Form/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Form"; 2 | export { default } from "./Form"; 3 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/Iconer/Iconer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { ReactNode } from "react"; 4 | 5 | import { IIconRenderer } from "react-mst-form"; 6 | 7 | import AccountBalance from "@material-ui/icons/AccountBalanceSharp"; 8 | import Assignment from "@material-ui/icons/AssignmentSharp"; 9 | import Build from "@material-ui/icons/BuildSharp"; 10 | import DateRange from "@material-ui/icons/DateRangeSharp"; 11 | import Face from "@material-ui/icons/FaceSharp"; 12 | 13 | export default class IconRenderer implements IIconRenderer { 14 | private static icons: { [key: string]: ReactNode } = { 15 | "account-balance": , 16 | assignment: , 17 | build: , 18 | "date-range": , 19 | face: 20 | }; 21 | 22 | public render(icon: string): ReactNode { 23 | return IconRenderer.icons[icon] || icon; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/Iconer/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Iconer"; 2 | export { default } from "./Iconer"; 3 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Designer"; 2 | export { default as Designer } from "./Designer"; 3 | 4 | export * from "./Form"; 5 | export { default as Form } from "./Form"; 6 | 7 | export * from "./Editor"; 8 | export { default as Editor } from "./Editor"; 9 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/schemas/array.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Test Form", 3 | "schema": { 4 | "type": "object", 5 | "title": "Comment", 6 | "value": { 7 | "comments": [ 8 | { 9 | "name": "sk", 10 | "email": "sk@sk.com", 11 | "spam": true, 12 | "comment": "skskskks" 13 | } 14 | ] 15 | }, 16 | "required": ["comments"], 17 | "properties": { 18 | "comments": { 19 | "type": "array", 20 | "maxItems": 2, 21 | "items": { 22 | "type": "object", 23 | "properties": { 24 | "name": { 25 | "title": "Name", 26 | "type": "string" 27 | }, 28 | "email": { 29 | "title": "Email", 30 | "type": "string", 31 | "format": "email", 32 | "description": "Email will be used for evil." 33 | }, 34 | "spam": { 35 | "title": "Spam", 36 | "type": "boolean", 37 | "default": true 38 | }, 39 | "comment": { 40 | "title": "Comment", 41 | "type": "string", 42 | "maxLength": 20, 43 | "validationMessage": "Don't be greedy!" 44 | } 45 | }, 46 | "required": ["name", "comment"] 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/schemas/comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Test Form", 3 | "schema": { 4 | "type": "object", 5 | "properties": { 6 | "name": { 7 | "title": "Name", 8 | "type": "string", 9 | "mandatory": true 10 | }, 11 | "email": { 12 | "title": "Email", 13 | "type": "string", 14 | "format": "email", 15 | "description": "Email will be used for evil." 16 | }, 17 | "comment": { 18 | "title": "Comment", 19 | "type": "string", 20 | "maxLength": 20, 21 | "minLength": 3, 22 | "validationMessage": "Don't be greedy!" 23 | } 24 | }, 25 | "required": ["name", "email", "comment"] 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/schemas/complex.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Test Form", 3 | "schema": { 4 | "type": "object", 5 | "title": "Complex Key Support", 6 | "properties": { 7 | "a[\"b\"].c": { 8 | "type": "string" 9 | }, 10 | "simple": { 11 | "type": "object", 12 | "properties": { 13 | "prøp": { 14 | "title": "UTF8 in both dot and bracket notation", 15 | "type": "string" 16 | } 17 | } 18 | }, 19 | "array-key": { 20 | "type": "array", 21 | "items": { 22 | "type": "object", 23 | "properties": { 24 | "a'rr[\"l": { 25 | "title": "Control Characters", 26 | "type": "string" 27 | }, 28 | "˙∆∂∞˚¬": { 29 | "type": "string" 30 | } 31 | }, 32 | "required": ["a'rr[\"l", "˙∆∂∞˚¬"] 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/schemas/date.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Test Form", 3 | "schema": { 4 | "type": "object", 5 | "properties": { 6 | "name": { 7 | "title": "Task name", 8 | "type": "string", 9 | "minLength": 2 10 | }, 11 | "description": { 12 | "title": "Description", 13 | "type": "string", 14 | "component": "textarea" 15 | }, 16 | "dueTo": { 17 | "title": "Due to", 18 | "type": "string", 19 | "component": "datetime", 20 | "format": "date-time" 21 | } 22 | }, 23 | "required": ["name"] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/schemas/deep.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Test Form", 3 | "schema": { 4 | "type": "object", 5 | "properties": { 6 | "friends": { 7 | "type": "array", 8 | "items": { 9 | "type": "object", 10 | "title": "Friend", 11 | "properties": { 12 | "nick": { 13 | "type": "string", 14 | "title": "Nickname" 15 | }, 16 | "animals": { 17 | "type": "array", 18 | "items": { 19 | "type": "string", 20 | "title": "Animal name" 21 | } 22 | } 23 | } 24 | } 25 | } 26 | }, 27 | "value": { 28 | "friends": [ 29 | { 30 | "nick": "sk", 31 | "animals": ["cow", "dog", "cat"] 32 | } 33 | ] 34 | } 35 | }, 36 | "layout": ["friends"] 37 | } 38 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/schemas/everything.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Test Form", 3 | "schema": { 4 | "type": "object", 5 | "properties": { 6 | "choice": { 7 | "type": "string", 8 | "enum": ["foo", "bar"] 9 | }, 10 | "string": { 11 | "type": "string" 12 | }, 13 | "checkbox": { 14 | "type": "boolean" 15 | }, 16 | "color": { 17 | "type": "string", 18 | "widget": "color" 19 | }, 20 | "date": { 21 | "type": "string", 22 | "widget": "date" 23 | }, 24 | "datetime": { 25 | "type": "string", 26 | "widget": "datetime" 27 | }, 28 | "compatible-date": { 29 | "type": "string", 30 | "widget": "compatible-date", 31 | "format": "date" 32 | }, 33 | "compatible-datetime": { 34 | "type": "string", 35 | "widget": "compatible-datetime", 36 | "format": "date-time" 37 | }, 38 | "email": { 39 | "type": "string", 40 | "widget": "email", 41 | "format": "email" 42 | }, 43 | "file": { 44 | "type": "string", 45 | "widget": "file" 46 | }, 47 | "money": { 48 | "type": "string", 49 | "widget": "money" 50 | }, 51 | "number": { 52 | "type": "number", 53 | "widget": "number" 54 | }, 55 | "password": { 56 | "type": "string", 57 | "widget": "password" 58 | }, 59 | "percent": { 60 | "type": "number", 61 | "widget": "percent" 62 | }, 63 | "search": { 64 | "type": "string", 65 | "widget": "search" 66 | }, 67 | "textarea": { 68 | "type": "string", 69 | "widget": "textarea" 70 | }, 71 | "url": { 72 | "type": "string", 73 | "widget": "url" 74 | }, 75 | "tasks": { 76 | "type": "array", 77 | "title": "A list of objects", 78 | "items": { 79 | "type": "object", 80 | "properties": { 81 | "name": { 82 | "type": "string", 83 | "title": "Name of the Task" 84 | }, 85 | "dueTo": { 86 | "type": "string", 87 | "title": "Due To", 88 | "widget": "datetime", 89 | "format": "date-time" 90 | } 91 | } 92 | } 93 | }, 94 | "multiple": { 95 | "type": "array", 96 | "title": "Multiple choices", 97 | "items": { 98 | "type": "string", 99 | "enum": ["1", "2"], 100 | "enum_titles": ["one", "two"] 101 | }, 102 | "uniqueItems": true 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/schemas/gender.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Test Form", 3 | "schema": { 4 | "type": "object", 5 | "properties": { 6 | "name": { 7 | "title": "Name", 8 | "description": "Nickname allowed", 9 | "type": "string" 10 | }, 11 | "gender": { 12 | "type": "string", 13 | "title": "Gender", 14 | "enum": ["male", "female", "alien"], 15 | "value": "alien" 16 | } 17 | }, 18 | "required": ["name", "gender"] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/schemas/multiple.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Test Form", 3 | "schema": { 4 | "type": "object", 5 | "properties": { 6 | "tasks": { 7 | "type": "array", 8 | "title": "A list of objects", 9 | "items": { 10 | "type": "object", 11 | "properties": { 12 | "name": { 13 | "type": "string", 14 | "title": "Name of the Task" 15 | }, 16 | "dueTo": { 17 | "type": "string", 18 | "title": "Due To", 19 | "widget": "datetime", 20 | "format": "date-time" 21 | } 22 | } 23 | } 24 | }, 25 | "multiple": { 26 | "type": "array", 27 | "title": "Multiple choices", 28 | "items": { 29 | "type": "string", 30 | "enum": ["1", "2"], 31 | "enum_titles": ["one", "two"] 32 | }, 33 | "uniqueItems": true 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/src/schemas/select.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Test Form", 3 | "schema": { 4 | "type": "object", 5 | "properties": { 6 | "select": { 7 | "title": "Select without titleMap", 8 | "type": "string", 9 | "enum": ["a", "b", "c"] 10 | }, 11 | "select2": { 12 | "title": "Select with titleMap (old style)", 13 | "type": "string", 14 | "enum": ["a", "b", "c"] 15 | }, 16 | "noenum": { 17 | "type": "string", 18 | "title": "No enum, but forms says it's a select" 19 | }, 20 | "array": { 21 | "title": "Array with enum defaults to 'checkboxes'", 22 | "type": "array", 23 | "items": { 24 | "type": "string", 25 | "enum": ["a", "b", "c"] 26 | } 27 | }, 28 | "array2": { 29 | "title": "Array with titleMap", 30 | "type": "array", 31 | "default": ["b", "c"], 32 | "items": { 33 | "type": "string", 34 | "enum": ["a", "b", "c"] 35 | } 36 | }, 37 | "radios": { 38 | "title": "Basic radio button example", 39 | "type": "string", 40 | "enum": ["a", "b", "c"] 41 | }, 42 | "radiobuttons": { 43 | "title": "Radio buttons used to switch a boolean", 44 | "type": "boolean", 45 | "default": false 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */, 5 | "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 6 | "lib": [ 7 | "es6", 8 | "dom", 9 | "esnext", 10 | "es2015", 11 | "scripthost", 12 | "es2015.promise", 13 | "es2015.generator", 14 | "es2015.iterable", 15 | "es2015.collection" 16 | ] /* Specify library files to be included in the compilation: */, 17 | // "allowJs": true, /* Allow javascript files to be compiled. */ 18 | // "checkJs": true, /* Report errors in .js files. */ 19 | "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */, 20 | "declaration": true /* Generates corresponding '.d.ts' file. */, 21 | "sourceMap": true /* Generates corresponding '.map' file. */, 22 | // "outFile": "./", /* Concatenate and emit output to single file. */ 23 | "outDir": "lib/" /* Redirect output structure to the directory. */, 24 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 25 | // "removeComments": true, /* Do not emit comments to output. */ 26 | // "noEmit": true, /* Do not emit outputs. */ 27 | // "importHelpers": true /* Import emit helpers from 'tslib'. */, 28 | "downlevelIteration": true /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */, 29 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 30 | 31 | /* Strict Type-Checking Options */ 32 | "strict": true /* Enable all strict type-checking options. */, 33 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 34 | // "strictNullChecks": true, /* Enable strict null checks. */ 35 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 36 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 37 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 38 | 39 | /* Additional Checks */ 40 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 41 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 42 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 43 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 44 | 45 | /* Module Resolution Options */ 46 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 47 | "baseUrl": "./" /* Base directory to resolve non-absolute module names. */, 48 | // "paths": { 49 | // } /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */, 50 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 51 | "typeRoots": [ 52 | "./node_modules/@types" 53 | ] /* List of folders to include type definitions from. */, 54 | // "types": [ 55 | // "node", 56 | // "webpack-env" 57 | // ] /* Type declaration files to be included in compilation. */, 58 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, 59 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 60 | "esModuleInterop": true, 61 | /* Source Map Options */ 62 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 63 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 64 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 65 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 66 | 67 | /* Experimental Options */ 68 | "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */ 69 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 70 | }, 71 | // "files": ["src/index.ts", "typings/misc.d.ts"], 72 | "exclude": ["node_modules", "lib", "lib-esm", "demo", "webpack.config.*"] 73 | } 74 | -------------------------------------------------------------------------------- /packages/react-mst-form-demo/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-config-prettier"], 3 | "linterOptions": { 4 | "exclude": ["config/**/*.js", "node_modules/**/*.ts"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/react-mst-form/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build 3 | lib 4 | lib-esm 5 | .DS_Store 6 | *.tgz 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | dist 11 | stats.json -------------------------------------------------------------------------------- /packages/react-mst-form/.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | **/tsconfig.json 3 | **/tslint.json 4 | **/webpack.config.js 5 | node_modules 6 | src 7 | typings 8 | demo -------------------------------------------------------------------------------- /packages/react-mst-form/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } 4 | -------------------------------------------------------------------------------- /packages/react-mst-form/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 naguvan 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. -------------------------------------------------------------------------------- /packages/react-mst-form/README.md: -------------------------------------------------------------------------------- 1 | # react-mst-form 2 | 3 | Library for generating React forms from [JSON schema](https://json-schema.org/) using the [react](https://github.com/facebook/react), [material-ui](https://github.com/mui-org/material-ui), [mobx](https://github.com/mobxjs/mobx) and [mobx-state-tree](https://github.com/mobxjs/mobx-state-tree). 4 | 5 | **https://naguvan.github.io/react-mst-form/packages/typescript-react-app-demo/src/index.html** 6 | 7 | # Running the demo 8 | 9 | To run the `demo`, clone this repository, then run: 10 | 11 | ```bash 12 | lerna bootstrap 13 | 14 | cd packages/typescript-react-app-demo or cd packages/create-react-app-demo 15 | 16 | npm run start 17 | ``` 18 | 19 | # Basic usage 20 | 21 | ```jsx 22 | import React from "react"; 23 | import { render } from "react-dom"; 24 | 25 | import { create } from "jss"; 26 | import preset from "jss-preset-default"; 27 | import JssProvider from "react-jss/lib/JssProvider"; 28 | 29 | import MuiThemeProvider from "material-ui/styles/MuiThemeProvider"; 30 | import createMuiTheme from "material-ui/styles/createMuiTheme"; 31 | 32 | import { Form } from "react-mst-form"; 33 | 34 | const schema = { 35 | type: "object", 36 | properties: { 37 | name: { 38 | type: "object", 39 | properties: { 40 | first: { 41 | type: "string", 42 | title: "First", 43 | minLength: 5 44 | }, 45 | middle: { 46 | type: "string", 47 | title: "Middle", 48 | minLength: 5 49 | }, 50 | last: { 51 | type: "string", 52 | title: "Last", 53 | minLength: 5 54 | }, 55 | age: { 56 | type: "number", 57 | title: "Age", 58 | maximum: 10, 59 | minimum: 3 60 | } 61 | } 62 | }, 63 | birthdate: { 64 | format: "date", 65 | type: "string", 66 | title: "Birth date" 67 | }, 68 | ipv4: { 69 | type: "string", 70 | title: "ipv4", 71 | minLength: 5, 72 | maxLength: 20, 73 | format: "ipv4" 74 | }, 75 | color: { 76 | type: "string", 77 | title: "In which color", 78 | format: "color" 79 | }, 80 | size: { 81 | type: "number", 82 | title: "Size", 83 | maximum: 10, 84 | minimum: 3, 85 | multipleOf: 3 86 | }, 87 | type: { 88 | type: "number", 89 | title: "Select a type", 90 | enum: [1, 2, 3] 91 | }, 92 | agree: { 93 | type: "boolean", 94 | title: "I agree with your terms", 95 | const: true 96 | }, 97 | array: { 98 | type: "array", 99 | title: "Array", 100 | items: { 101 | type: "object", 102 | properties: { 103 | name: { 104 | type: "string", 105 | title: "name", 106 | minLength: 3 107 | }, 108 | age: { 109 | type: "number", 110 | title: "age", 111 | multipleOf: 2, 112 | minimum: 2 113 | } 114 | } 115 | }, 116 | minItems: 2, 117 | maxItems: 4 118 | } 119 | } 120 | }; 121 | 122 | const meta = { 123 | type: "object", 124 | properties: { 125 | name: { 126 | layout: [["first", "last"], "middle", "age"], 127 | type: "object", 128 | properties: { 129 | first: { 130 | sequence: 1, 131 | icon: "face", 132 | iconAlign: "start", 133 | type: "string" 134 | }, 135 | middle: { 136 | sequence: 1, 137 | type: "string" 138 | }, 139 | last: { 140 | sequence: 2, 141 | type: "string" 142 | }, 143 | age: { 144 | sequence: 2, 145 | icon: "build", 146 | type: "number" 147 | } 148 | } 149 | }, 150 | birthdate: { 151 | component: "date", 152 | icon: "date-range", 153 | iconAlign: "end", 154 | type: "string" 155 | }, 156 | color: { 157 | component: "color", 158 | type: "string" 159 | }, 160 | size: { 161 | component: "range", 162 | step: 1, 163 | type: "number" 164 | }, 165 | type: { 166 | error: "should not be empty", 167 | options: [ 168 | { label: "One", value: 1 }, 169 | { label: "Two", value: 2 }, 170 | { label: "Three", value: 3 } 171 | ], 172 | type: "number" 173 | }, 174 | agree: { 175 | type: "boolean" 176 | }, 177 | array: { 178 | type: "array", 179 | items: { 180 | properties: { 181 | age: { 182 | type: "number" 183 | } 184 | }, 185 | type: "object" 186 | } 187 | } 188 | } 189 | }; 190 | 191 | const config = { 192 | title: "Test Form", 193 | cancel: "Cancel", 194 | submit: "create", 195 | sections: [ 196 | { 197 | title: "Basic", 198 | layout: ["name", "birthdate", ["size", "color"]] 199 | }, 200 | { 201 | title: "Others", 202 | layout: ["ipv4", "type", "agree", "array"] 203 | } 204 | ] 205 | }; 206 | 207 | const snapshot = { 208 | name: { 209 | first: "naguvan", 210 | middle: "sk", 211 | last: "sk", 212 | age: 1 213 | }, 214 | birthdate: "2018-10-29", 215 | size: 5, 216 | agree: false 217 | }; 218 | 219 | const onSubmit = values => { 220 | window.alert(`submitted values:\n\n${JSON.stringify(values, null, 2)}`); 221 | }; 222 | 223 | const jss = create(preset()); 224 | 225 | render( 226 | 227 | 228 | 235 | 236 | , 237 | document.getElementById("form-holder") 238 | ); 239 | ``` 240 | 241 | And, provided that you have a `
`, you should see something like this: 242 | 243 | ![](https://raw.githubusercontent.com/naguvan/react-mst-form/master/packages/react-mst-form/demo/sections.png) 244 | 245 | And when the form has validation errors.. 246 | 247 | ![](https://raw.githubusercontent.com/naguvan/react-mst-form/master/packages/react-mst-form/demo/form-validation.png) 248 | -------------------------------------------------------------------------------- /packages/react-mst-form/demo/form-validation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naguvan/react-mst-form/5bb72d852d02dd9448dac969a5ffa6ca647060f8/packages/react-mst-form/demo/form-validation.png -------------------------------------------------------------------------------- /packages/react-mst-form/demo/sections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/naguvan/react-mst-form/5bb72d852d02dd9448dac969a5ffa6ca647060f8/packages/react-mst-form/demo/sections.png -------------------------------------------------------------------------------- /packages/react-mst-form/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-mst-form", 3 | "version": "2.0.0", 4 | "description": "react-mobx-state-tree-form", 5 | "main": "./lib/index.js", 6 | "typings": "./lib/index.d.ts", 7 | "repository": "https://github.com/naguvan/react-mst-form.git", 8 | "author": "naguvan ", 9 | "license": "MIT", 10 | "private": false, 11 | "bugs": { 12 | "url": "https://github.com/naguvan/react-mst-form/issues" 13 | }, 14 | "files": [ 15 | "lib/", 16 | "lib-esm/" 17 | ], 18 | "peerDependencies": { 19 | "@material-ui/core": "^3.x.x", 20 | "@material-ui/lab": "^3.x.x", 21 | "@material-ui/icons": "^3.0.1", 22 | "mobx": "^4.5.0", 23 | "mobx-react": "^5.2.5", 24 | "mobx-state-tree": "^2.2.0", 25 | "react": "^16.5.2", 26 | "react-dom": "^16.5.2" 27 | }, 28 | "dependencies": { 29 | "@material-ui/core": "3.4.0", 30 | "@material-ui/icons": "3.0.1", 31 | "@material-ui/lab": "3.0.0-alpha.23", 32 | "classnames": "2.2.6", 33 | "mobx": "4.5.0", 34 | "mobx-react": "5.3.6", 35 | "mobx-state-tree": "2.2.0", 36 | "react": "16.6.1", 37 | "react-dom": "16.6.1", 38 | "react-flow-layout": "1.5.0", 39 | "reactive-json-schema": "2.1.0" 40 | }, 41 | "devDependencies": { 42 | "@types/classnames": "2.2.6", 43 | "@types/jest": "23.3.9", 44 | "@types/react": "16.7.1", 45 | "@types/react-dom": "16.0.9", 46 | "cross-env": "5.2.0", 47 | "husky": "1.1.3", 48 | "jest": "23.6.0", 49 | "lerna": "3.4.3", 50 | "prettier": "1.15.1", 51 | "pretty-quick": "1.8.0", 52 | "rimraf": "2.6.2", 53 | "ts-jest": "23.10.4", 54 | "ts-loader": "5.3.0", 55 | "ts-node": "7.0.1", 56 | "tslint": "5.11.0", 57 | "tslint-config-prettier": "1.15.0", 58 | "tslint-loader": "3.5.4", 59 | "tslint-react": "3.6.0", 60 | "typescript": "3.1.6" 61 | }, 62 | "keywords": [ 63 | "react", 64 | "form", 65 | "mobx-state-tree", 66 | "mst", 67 | "mobx", 68 | "json", 69 | "schema", 70 | "typescript" 71 | ], 72 | "jest": { 73 | "transform": { 74 | "^.+\\.tsx?$": "ts-jest" 75 | }, 76 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 77 | "moduleFileExtensions": [ 78 | "ts", 79 | "tsx", 80 | "js", 81 | "jsx", 82 | "json", 83 | "node" 84 | ], 85 | "modulePaths": [ 86 | "/src/" 87 | ], 88 | "roots": [ 89 | "/src/" 90 | ] 91 | }, 92 | "scripts": { 93 | "audit": "npm audit fix", 94 | "prepare": "npm run build", 95 | "prebuild": "npm run lint && npm run test && npm run clean", 96 | "clean": "rimraf {lib,lib-esm}", 97 | "lint": "tslint -p .", 98 | "precommit": "npm run lint && pretty-quick --staged", 99 | "pretty": "prettier --write 'src/**/*.{ts,tsx}'", 100 | "test": "cross-env NODE_ENV=production TS_NODE_COMPILER_OPTIONS={\\\"module\\\":\\\"commonjs\\\",\\\"declaration\\\":false} jest --runInBand", 101 | "env": "cross-env NODE_ENV=production TS_NODE_COMPILER_OPTIONS={\\\"module\\\":\\\"commonjs\\\"}", 102 | "dev:tsc": "tsc && tsc -m es6 --outDir lib-esm", 103 | "build": "npm run dev:tsc" 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Cancel/Cancel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Component, MouseEvent, ReactNode } from "react"; 3 | 4 | import { observer } from "mobx-react"; 5 | 6 | import { CSSProperties, WithStyles } from "@material-ui/core/styles/withStyles"; 7 | import withStyles from "@material-ui/core/styles/withStyles"; 8 | import classNames from "classnames"; 9 | 10 | import Button from "@material-ui/core/Button"; 11 | 12 | import { IButtonProps } from "../../material"; 13 | 14 | import { IForm } from "../../models/Form"; 15 | 16 | export interface ICancelStyles { 17 | root: CSSProperties; 18 | } 19 | 20 | export interface ICancelStyleProps extends WithStyles {} 21 | 22 | export interface ICancelProps { 23 | style?: CSSProperties; 24 | className?: string; 25 | form: IForm; 26 | reset?: boolean; 27 | label?: string; 28 | onCancel: (form?: IForm) => void; 29 | } 30 | 31 | // tslint:disable-next-line:no-empty-interface 32 | export interface ICancelStates {} 33 | 34 | @observer 35 | export class Cancel extends Component< 36 | ICancelProps & IButtonProps & ICancelStyleProps, 37 | ICancelStates 38 | > { 39 | public render(): ReactNode { 40 | const { 41 | className: clazz, 42 | classes, 43 | form, 44 | label, 45 | variant = "contained", 46 | color = "primary", 47 | onCancel, 48 | ...others 49 | } = this.props; 50 | const onClick = this.onCancel; 51 | const className: string = classNames(classes!.root, clazz); 52 | return ( 53 | 56 | ); 57 | } 58 | 59 | private onCancel = (e: MouseEvent) => { 60 | const { form, reset = false, onCancel } = this.props; 61 | if (reset) { 62 | form.reset(); 63 | } 64 | onCancel(form); 65 | }; 66 | } 67 | 68 | export default withStyles({ 69 | root: {} 70 | })(Cancel); 71 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Cancel/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Cancel"; 2 | export { default } from "./Cancel"; 3 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Content/Content.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Component, ReactNode } from "react"; 3 | 4 | import { observer } from "mobx-react"; 5 | 6 | import { CSSProperties, WithStyles } from "@material-ui/core/styles/withStyles"; 7 | import withStyles from "@material-ui/core/styles/withStyles"; 8 | import classNames from "classnames"; 9 | 10 | import { IIconRenderer } from "../Type/Renderer/Icon"; 11 | import { ITypeRenderer } from "../Type/Renderer/Type"; 12 | 13 | import { IForm } from "../../models/Form"; 14 | 15 | export interface IContentStyles { 16 | root: CSSProperties; 17 | } 18 | 19 | export interface IContentStyleProps extends WithStyles {} 20 | 21 | export interface IContentProps { 22 | form: IForm; 23 | style?: CSSProperties; 24 | className?: string; 25 | typer: ITypeRenderer; 26 | iconer: IIconRenderer; 27 | } 28 | 29 | // tslint:disable-next-line:no-empty-interface 30 | export interface IContentStates {} 31 | 32 | @observer 33 | export class Content extends Component< 34 | IContentProps & IContentStyleProps, 35 | IContentStates 36 | > { 37 | public render(): ReactNode { 38 | const { form, typer, iconer } = this.props; 39 | const { className: clazz, classes, style } = this.props; 40 | const className: string = classNames(classes!.root, clazz); 41 | const layout = form.selected ? form.selected.layout : undefined; 42 | return ( 43 |
44 | {typer.render({ 45 | form, 46 | iconer, 47 | layout, 48 | type: form.schema 49 | })} 50 |
51 | ); 52 | } 53 | } 54 | 55 | export default withStyles({ 56 | root: {} 57 | })(Content); 58 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Content/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Content"; 2 | export { default } from "./Content"; 3 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Dialog/Dialog.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Component, ReactNode } from "react"; 3 | 4 | import classNames from "classnames"; 5 | 6 | import { CSSProperties, WithStyles } from "@material-ui/core/styles/withStyles"; 7 | import withStyles from "@material-ui/core/styles/withStyles"; 8 | 9 | import Dialog, { DialogClassKey, DialogProps } from "@material-ui/core/Dialog"; 10 | import DialogActions from "@material-ui/core/DialogActions"; 11 | import DialogContent from "@material-ui/core/DialogContent"; 12 | import DialogTitle from "@material-ui/core/DialogTitle"; 13 | 14 | import FormContent from "../Content"; 15 | import FormFooter from "../Footer"; 16 | import FormHeader from "../Header"; 17 | 18 | import IconRenderer, { IIconRenderer } from "../Type/Renderer/Icon"; 19 | import TypeRenderer, { ITypeRenderer } from "../Type/Renderer/Type"; 20 | 21 | import Form, { IFormProps } from "../Form"; 22 | 23 | import { observer } from "mobx-react"; 24 | import { IForm } from "../../models"; 25 | 26 | import { IFieldErrors } from "reactive-json-schema"; 27 | 28 | export interface IFormDialogStyles { 29 | root: CSSProperties; 30 | header: CSSProperties; 31 | content: CSSProperties; 32 | footer: CSSProperties; 33 | title: CSSProperties; 34 | } 35 | 36 | export type IFormDialogClassKey = keyof IFormDialogStyles | DialogClassKey; 37 | 38 | export interface IFormDialogStyleProps 39 | extends WithStyles {} 40 | 41 | export interface IFormDialogProps extends IFormProps, DialogProps { 42 | className?: string; 43 | style?: CSSProperties; 44 | children?: null; 45 | typer?: ITypeRenderer; 46 | iconer?: IIconRenderer; 47 | onCancel?: (form?: IForm) => void; 48 | onErrors?: (errors: IFieldErrors) => void; 49 | onSubmit: (values: { [key: string]: any }) => void; 50 | } 51 | 52 | // tslint:disable-next-line:no-empty-interface 53 | export interface IFormDialogStates {} 54 | 55 | @observer 56 | export class FormDialog extends Component< 57 | IFormDialogProps & IFormDialogStyleProps, 58 | IFormDialogStates 59 | > { 60 | constructor(props: IFormDialogProps & IFormDialogStyleProps) { 61 | super(props); 62 | } 63 | 64 | public render(): ReactNode { 65 | const { 66 | // form props 67 | config, 68 | schema, 69 | meta, 70 | snapshot, 71 | onPatch, 72 | onSnapshot, 73 | 74 | // dialog props 75 | className: clazz, 76 | classes, 77 | style, 78 | onCancel, 79 | onSubmit, 80 | onErrors, 81 | iconer = new IconRenderer(), 82 | typer = new TypeRenderer(), 83 | open, 84 | scroll = "paper", 85 | ...others 86 | } = this.props; 87 | 88 | const { content, title, footer, header, ...rest } = classes; 89 | 90 | const className: string = classNames(classes!.root, clazz); 91 | const {} = this.props; 92 | return ( 93 | 103 | {(form: IForm) => ( 104 | 114 | 115 | 121 | 122 | 123 | 131 | 132 | 133 | 142 | 143 | 144 | )} 145 | 146 | ); 147 | } 148 | } 149 | 150 | export default withStyles({ 151 | content: {}, 152 | footer: {}, 153 | header: {}, 154 | root: {}, 155 | title: { 156 | paddingBottom: 0 157 | }, 158 | 159 | paper: {}, 160 | paperFullScreen: {}, 161 | paperFullWidth: {}, 162 | paperScrollBody: {}, 163 | paperScrollPaper: {}, 164 | paperWidthMd: {}, 165 | paperWidthSm: {}, 166 | paperWidthXs: {}, 167 | scrollBody: {}, 168 | scrollPaper: {} 169 | })(FormDialog); 170 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Dialog/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Dialog"; 2 | export { default } from "./Dialog"; 3 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Component, ReactNode } from "react"; 3 | 4 | import classNames from "classnames"; 5 | 6 | import { CSSProperties, WithStyles } from "@material-ui/core/styles/withStyles"; 7 | import withStyles from "@material-ui/core/styles/withStyles"; 8 | 9 | import Grid from "@material-ui/core/Grid"; 10 | 11 | import FormCancel from "../Cancel"; 12 | import FormSubmit from "../Submit"; 13 | 14 | import { IForm } from "../../models/Form"; 15 | 16 | import { observer } from "mobx-react"; 17 | import { IFieldErrors } from "reactive-json-schema"; 18 | 19 | export interface IFooterStyles { 20 | root: CSSProperties; 21 | item: CSSProperties; 22 | } 23 | 24 | export interface IFooterStyleProps extends WithStyles {} 25 | 26 | export interface IFooterProps { 27 | style?: CSSProperties; 28 | className?: string; 29 | form: IForm; 30 | onCancel?: (form?: IForm) => void; 31 | onSubmit: (values: { [key: string]: any }) => void; 32 | onErrors?: (errors: IFieldErrors) => void; 33 | } 34 | 35 | // tslint:disable-next-line:no-empty-interface 36 | export interface IFooterStates {} 37 | 38 | @observer 39 | export class Footer extends Component< 40 | IFooterProps & IFooterStyleProps, 41 | IFooterStates 42 | > { 43 | public render(): ReactNode { 44 | const { form } = this.props; 45 | const { cancel, submit } = form; 46 | const { className: clazz, classes, style } = this.props; 47 | const { onCancel, onSubmit, onErrors } = this.props; 48 | const className: string = classNames(classes!.root, clazz); 49 | return ( 50 | 51 | {onCancel && 52 | cancel && ( 53 | 54 | 55 | 56 | )} 57 | 58 | 59 | 60 | 61 | ); 62 | } 63 | } 64 | 65 | export default withStyles({ 66 | item: { 67 | padding: 10 68 | }, 69 | root: { 70 | display: "flex", 71 | justifyContent: "flex-end" 72 | } 73 | })(Footer); 74 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Footer/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Footer"; 2 | export { default } from "./Footer"; 3 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Form/Form.tsx: -------------------------------------------------------------------------------- 1 | import { Component, ReactNode } from "react"; 2 | 3 | import FormModel, { IForm, IFormConfig } from "../../models/Form"; 4 | 5 | import { onPatch, onSnapshot } from "mobx-state-tree"; 6 | 7 | import { 8 | ITypeConfig, 9 | ITypeMetaProps, 10 | metafy, 11 | valuefy 12 | } from "reactive-json-schema"; 13 | 14 | import { deepmerge } from "../../utils"; 15 | 16 | export type ISchemaConfig = ITypeConfig; 17 | 18 | export type IMetaConfig = ITypeMetaProps; 19 | 20 | export interface IFormProps { 21 | schema?: ISchemaConfig; 22 | meta?: IMetaConfig; 23 | config: IFormConfig; 24 | snapshot?: {}; 25 | onPatch?: ( 26 | patch: { 27 | op: "replace" | "add" | "remove"; 28 | path: string; 29 | value?: any; 30 | } 31 | ) => void; 32 | onSnapshot?: (snapshot: {}) => void; 33 | } 34 | 35 | export interface IFormRenderProps { 36 | children: (form: IForm) => ReactNode; 37 | } 38 | 39 | export interface IFormStates { 40 | form: IForm; 41 | } 42 | 43 | export default class Form extends Component< 44 | IFormProps & IFormRenderProps, 45 | IFormStates 46 | > { 47 | private static getDerivedStateFromPropsFix( 48 | props: Readonly, 49 | state?: IFormStates 50 | ): IFormStates { 51 | const { schema: pschema, meta, snapshot } = props; 52 | const { config, onPatch: xonPatch, onSnapshot: xonSnapshot } = props; 53 | let schema = deepmerge( 54 | { ...config.schema! }, 55 | { ...pschema! } 56 | ); 57 | if (meta) { 58 | schema = metafy(schema, meta); 59 | } 60 | if (snapshot) { 61 | schema = valuefy(schema, snapshot); 62 | } 63 | const form = FormModel.create({ ...config, schema }); 64 | if (xonSnapshot) { 65 | onSnapshot(form, xonSnapshot); 66 | } 67 | if (xonPatch) { 68 | onPatch(form, xonPatch); 69 | } 70 | return { form }; 71 | } 72 | 73 | constructor(props: IFormProps & IFormRenderProps) { 74 | super(props); 75 | this.state = Form.getDerivedStateFromPropsFix(props); 76 | } 77 | 78 | public componentWillReceiveProps( 79 | next: Readonly 80 | ): void { 81 | const { config, schema, meta, snapshot } = this.props; 82 | if ( 83 | config !== next.config || 84 | schema !== next.schema || 85 | meta !== next.schema || 86 | snapshot !== next.snapshot 87 | ) { 88 | this.setState(state => Form.getDerivedStateFromPropsFix(next, state)); 89 | } 90 | } 91 | 92 | public render(): ReactNode { 93 | const { children } = this.props; 94 | const { form } = this.state; 95 | return children(form); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Form/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Form"; 2 | export { default } from "./Form"; 3 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Component, ReactNode } from "react"; 3 | 4 | import { observer } from "mobx-react"; 5 | 6 | import { CSSProperties, WithStyles } from "@material-ui/core/styles/withStyles"; 7 | import withStyles from "@material-ui/core/styles/withStyles"; 8 | import classNames from "classnames"; 9 | 10 | import Tab from "@material-ui/core/Tab"; 11 | import Tabs from "@material-ui/core/Tabs"; 12 | import Typography from "@material-ui/core/Typography"; 13 | 14 | import { ILayout } from "reactive-json-schema"; 15 | 16 | import { IForm } from "../../models/Form"; 17 | import { ISection } from "../../models/Section"; 18 | 19 | export interface IHeaderStyles { 20 | root: CSSProperties; 21 | title: CSSProperties; 22 | secondary: CSSProperties; 23 | } 24 | 25 | export interface IHeaderStyleProps extends WithStyles {} 26 | 27 | export interface IHeaderProps { 28 | form: IForm; 29 | style?: CSSProperties; 30 | className?: string; 31 | } 32 | 33 | // tslint:disable-next-line:no-empty-interface 34 | export interface IHeaderStates {} 35 | 36 | @observer 37 | export class Header extends Component< 38 | IHeaderProps & IHeaderStyleProps, 39 | IHeaderStates 40 | > { 41 | public render(): ReactNode { 42 | const { className: clazz, classes, style } = this.props; 43 | const { form } = this.props; 44 | if (!form.selected) { 45 | return ( 46 | 47 | {form.title} 48 | 49 | ); 50 | } 51 | const className: string = classNames(classes!.root, clazz); 52 | const selected: ISection = form.selected; 53 | const hasError = this.hasSectionError(selected, form); 54 | return ( 55 | 62 | {form.sections.map(section => ( 63 | 71 | ))} 72 | 73 | ); 74 | } 75 | 76 | protected hasSectionError(section: ISection, form: IForm): boolean { 77 | return this.hasLayoutError(section.layout, form); 78 | } 79 | 80 | protected hasLayoutError(layout: ILayout, form: IForm): boolean { 81 | return layout.some(item => { 82 | if (typeof item === "string") { 83 | return !form.get(item)!.valid; 84 | } 85 | return this.hasLayoutError(item as ILayout, form); 86 | }); 87 | } 88 | 89 | protected handleChange = (event: any, selected: ISection) => { 90 | const { form } = this.props; 91 | form.makeSelection(selected); 92 | }; 93 | } 94 | 95 | export default withStyles(theme => ({ 96 | root: {}, 97 | secondary: { 98 | color: theme.palette.secondary.main 99 | }, 100 | title: {} 101 | }))(Header); 102 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Header/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Header"; 2 | export { default } from "./Header"; 3 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Inline/Inline.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Component, ReactNode } from "react"; 3 | 4 | import classNames from "classnames"; 5 | 6 | import { CSSProperties, WithStyles } from "@material-ui/core/styles/withStyles"; 7 | import withStyles from "@material-ui/core/styles/withStyles"; 8 | 9 | import FormContent from "../Content"; 10 | import FormFooter from "../Footer"; 11 | import FormHeader from "../Header"; 12 | 13 | import IconRenderer, { IIconRenderer } from "../Type/Renderer/Icon"; 14 | import TypeRenderer, { ITypeRenderer } from "../Type/Renderer/Type"; 15 | 16 | import Form, { IFormProps } from "../Form"; 17 | 18 | import { IForm } from "../../models"; 19 | 20 | import { observer } from "mobx-react"; 21 | import { IFieldErrors } from "reactive-json-schema"; 22 | 23 | export interface IFormInlineStyles { 24 | root: CSSProperties; 25 | header: CSSProperties; 26 | content: CSSProperties; 27 | footer: CSSProperties; 28 | title: CSSProperties; 29 | } 30 | 31 | export interface IFormInlineStyleProps 32 | extends WithStyles {} 33 | 34 | export interface IFormInlineProps extends IFormProps { 35 | className?: string; 36 | style?: CSSProperties; 37 | children?: null; 38 | typer?: ITypeRenderer; 39 | iconer?: IIconRenderer; 40 | onCancel?: (form?: IForm) => void; 41 | onErrors?: (errors: IFieldErrors) => void; 42 | onSubmit: (values: { [key: string]: any }) => void; 43 | } 44 | 45 | // tslint:disable-next-line:no-empty-interface 46 | export interface IFormInlineStates {} 47 | 48 | @observer 49 | export class FormInline extends Component< 50 | IFormInlineProps & IFormInlineStyleProps, 51 | IFormInlineStates 52 | > { 53 | public render(): ReactNode { 54 | const { className: clazz, classes, style } = this.props; 55 | const { 56 | // form props 57 | config, 58 | schema, 59 | meta, 60 | snapshot, 61 | onPatch, 62 | onSnapshot, 63 | 64 | // inline props 65 | onCancel, 66 | onSubmit, 67 | onErrors, 68 | iconer = new IconRenderer(), 69 | typer = new TypeRenderer() 70 | } = this.props; 71 | 72 | const className: string = classNames(classes!.root, clazz); 73 | return ( 74 |
84 | {(form: IForm) => ( 85 |
86 | 93 | 96 | 105 |
106 | )} 107 |
108 | ); 109 | } 110 | } 111 | 112 | export default withStyles({ 113 | content: { 114 | order: 3, 115 | overflowY: "auto", 116 | padding: 10 117 | }, 118 | footer: { 119 | flexBasis: 80, 120 | flexShrink: 0, 121 | order: 3, 122 | padding: 10 123 | }, 124 | header: { 125 | flexBasis: 48, 126 | flexShrink: 0, 127 | order: 1 128 | }, 129 | root: { 130 | display: "flex", 131 | flexDirection: "column", 132 | height: "100%", 133 | margin: 0, 134 | minHeight: "100%", 135 | padding: 10 136 | }, 137 | title: { 138 | paddingLeft: 10 139 | } 140 | })(FormInline); 141 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Inline/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Inline"; 2 | export { default } from "./Inline"; 3 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Submit/Submit.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Component, MouseEvent, ReactNode } from "react"; 3 | 4 | import { observer } from "mobx-react"; 5 | import { IFieldErrors } from "reactive-json-schema"; 6 | 7 | import { CSSProperties, WithStyles } from "@material-ui/core/styles/withStyles"; 8 | import withStyles from "@material-ui/core/styles/withStyles"; 9 | import classNames from "classnames"; 10 | 11 | import Button from "@material-ui/core/Button"; 12 | 13 | import { IButtonProps } from "../../material"; 14 | 15 | import { IForm } from "../../models/Form"; 16 | 17 | export interface ISubmitStyles { 18 | root: CSSProperties; 19 | } 20 | 21 | export interface ISubmitStyleProps extends WithStyles {} 22 | 23 | export interface ISubmitProps { 24 | style?: CSSProperties; 25 | className?: string; 26 | form: IForm; 27 | label?: string; 28 | onSubmit?: (values: { [key: string]: any }) => void; 29 | onErrors?: (errors: IFieldErrors) => void; 30 | } 31 | 32 | // tslint:disable-next-line:no-empty-interface 33 | export interface ISubmitStates {} 34 | 35 | @observer 36 | export class Submit extends Component< 37 | ISubmitProps & ISubmitStyleProps & IButtonProps, 38 | ISubmitStates 39 | > { 40 | public render(): ReactNode { 41 | const { 42 | className: clazz, 43 | classes, 44 | form, 45 | label, 46 | variant = "contained", 47 | color = "primary", 48 | onErrors, 49 | onSubmit, 50 | ...others 51 | } = this.props; 52 | const onClick = this.onSubmit; 53 | const className: string = classNames(classes!.root, clazz); 54 | return ( 55 | 61 | ); 62 | } 63 | 64 | private onSubmit = (e: MouseEvent) => { 65 | const { form, onSubmit, onErrors } = this.props; 66 | form.validate(); 67 | if (form.valid) { 68 | if (onSubmit) { 69 | onSubmit(form.values); 70 | } 71 | } else if (onErrors) { 72 | onErrors(form.fieldErrors); 73 | } 74 | }; 75 | } 76 | 77 | export default withStyles({ 78 | root: {} 79 | })(Submit); 80 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Submit/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Submit"; 2 | export { default } from "./Submit"; 3 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Type/Array/Array.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Fragment, MouseEvent, ReactNode } from "react"; 3 | 4 | import { observer } from "mobx-react"; 5 | import { IArray } from "reactive-json-schema"; 6 | import { ITypeRenderer } from "../Renderer/Type"; 7 | 8 | import Error from "../Error"; 9 | import Type, { ITypeProps, ITypeStates } from "../Type"; 10 | 11 | import FormControl from "@material-ui/core/FormControl"; 12 | import FormLabel from "@material-ui/core/FormLabel"; 13 | import IconButton from "@material-ui/core/IconButton"; 14 | import ActionAdd from "@material-ui/icons/Add"; 15 | import ActionClear from "@material-ui/icons/Clear"; 16 | 17 | import { toNumber } from "../../../utils"; 18 | 19 | import { IRenderContext } from "../Renderer/Type"; 20 | 21 | export interface IArrayProps extends ITypeProps { 22 | typer: ITypeRenderer; 23 | } 24 | 25 | export interface IArrayStates extends ITypeStates {} 26 | 27 | @observer 28 | export default class Array extends Type { 29 | protected renderType(context: IRenderContext): ReactNode { 30 | const { type } = context; 31 | const { typer } = this.props; 32 | return ( 33 | 38 | 39 | {type.title} 40 | 41 | {type.elements.map((element, index) => ( 42 | 43 | {type.dynamic && ( 44 | 53 | 54 | 55 | )} 56 | {typer.render({ ...context, type: element })} 57 | 58 | ))} 59 | {type.dynamic && ( 60 | 61 | 62 | 63 | )} 64 | 65 | 66 | ); 67 | } 68 | 69 | private onRemove = (event: MouseEvent): void => { 70 | const index = event.currentTarget.getAttribute("data-index") || ""; 71 | this.props.context.type.remove(toNumber(index, 0)); 72 | }; 73 | 74 | private onPush = (event: MouseEvent): void => { 75 | this.props.context.type.push(); 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Type/Array/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Array"; 2 | export { default } from "./Array"; 3 | -------------------------------------------------------------------------------- /packages/react-mst-form/src/components/Type/Boolean/Boolean.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { ReactNode } from "react"; 3 | 4 | import { observer } from "mobx-react"; 5 | import { IBoolean } from "reactive-json-schema"; 6 | 7 | import Type, { ITypeProps, ITypeStates } from "../Type"; 8 | 9 | import Checkbox from "./Checkbox"; 10 | import Radios from "./Radios"; 11 | import Select from "./Select"; 12 | import Switch from "./Switch"; 13 | 14 | import { IRenderContext } from "../Renderer/Type"; 15 | 16 | export interface IBooleanProps extends ITypeProps {} 17 | 18 | export interface IBooleanStates extends ITypeStates {} 19 | 20 | @observer 21 | export default class Boolean extends Type< 22 | IBoolean, 23 | IBooleanProps, 24 | IBooleanStates 25 | > { 26 | protected renderType(context: IRenderContext): ReactNode { 27 | const { type } = context; 28 | switch (type.meta.component) { 29 | case "select": 30 | return