├── .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 `
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 `