├── .nvmrc ├── _config.yml ├── .npmrc ├── packages ├── validators │ ├── src │ │ ├── index.ts │ │ └── ajv.ts │ ├── README.md │ ├── babel.config.js │ ├── tsconfig.json │ ├── config │ │ └── webpack.config.js │ └── package.json ├── core │ ├── config │ │ ├── tests │ │ │ ├── __mocks__ │ │ │ │ └── fileMock.js │ │ │ └── jest.config.js │ │ ├── paths.js │ │ └── webpack.config.js │ ├── examples │ │ └── meta │ │ │ ├── minimal.ts │ │ │ ├── field-action.ts │ │ │ └── all-renderers.ts │ ├── babel.config.js │ ├── tsconfig.json │ ├── src │ │ ├── index.tsx │ │ ├── validator.ts │ │ ├── components │ │ │ ├── Field.tsx │ │ │ ├── Fields.tsx │ │ │ ├── Layout.tsx │ │ │ ├── renderers │ │ │ │ ├── SubForm.tsx │ │ │ │ └── ListForm.tsx │ │ │ └── GeneratedForm.tsx │ │ ├── serializer.ts │ │ ├── schema.json │ │ ├── interfaces.ts │ │ └── utils.ts │ ├── __tests__ │ │ ├── validator.test.ts │ │ ├── serializer.test.ts │ │ ├── components │ │ │ └── GeneratedForm.test.tsx │ │ └── utils.test.tsx │ └── package.json ├── bootstrap │ ├── src │ │ ├── interfaces.ts │ │ ├── layouts │ │ │ ├── index.tsx │ │ │ └── FormGroups.tsx │ │ ├── index.tsx │ │ └── renderers │ │ │ ├── index.tsx │ │ │ ├── Button.tsx │ │ │ ├── TextArea.tsx │ │ │ ├── Checkbox.tsx │ │ │ ├── Text.tsx │ │ │ ├── ValidatableField.tsx │ │ │ ├── Dropdown.tsx │ │ │ ├── Radiogroup.tsx │ │ │ └── Select.tsx │ ├── README.md │ ├── tsconfig.json │ ├── package.json │ └── config │ │ └── webpack.config.js ├── metaphor │ ├── config │ │ ├── tests │ │ │ ├── __mocks__ │ │ │ │ └── fileMock.js │ │ │ └── jest.config.js │ │ ├── paths.js │ │ └── webpack.config.js │ ├── docs │ │ ├── assets │ │ │ ├── images │ │ │ │ ├── icons.png │ │ │ │ ├── widgets.png │ │ │ │ ├── icons@2x.png │ │ │ │ └── widgets@2x.png │ │ │ └── js │ │ │ │ └── search.js │ │ ├── modules │ │ │ ├── _index_.html │ │ │ └── _lib_fieldpart_.html │ │ ├── globals.html │ │ ├── interfaces │ │ │ └── _lib_fieldpart_.fieldpart.html │ │ └── index.html │ ├── __tests__ │ │ └── mocks │ │ │ └── minimal.ts │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── FieldPart.ts │ │ │ └── Metaphor.ts │ ├── babel.config.js │ ├── tsconfig.json │ ├── README.md │ └── package.json ├── antd │ ├── README.md │ ├── src │ │ ├── index.tsx │ │ ├── layouts │ │ │ ├── index.tsx │ │ │ ├── FormLayout.tsx │ │ │ └── FieldLayout.tsx │ │ └── renderers │ │ │ ├── Text.tsx │ │ │ ├── Password.tsx │ │ │ ├── index.tsx │ │ │ ├── FieldWrapper.tsx │ │ │ ├── Button.tsx │ │ │ ├── TextArea.tsx │ │ │ ├── CheckBox.tsx │ │ │ ├── DatePicker.tsx │ │ │ ├── Input.tsx │ │ │ ├── RadioGroup.tsx │ │ │ ├── Upload.tsx │ │ │ ├── Select.tsx │ │ │ └── MultipleSelect.tsx │ ├── tsconfig.json │ ├── package.json │ └── config │ │ └── webpack.config.js └── demo │ ├── src │ ├── index.jsx │ ├── components │ │ └── CloseButton.jsx │ ├── index.ejs │ ├── actions.js │ ├── validation │ │ └── jsonSchema.json │ ├── app.jsx │ ├── reducers.js │ ├── containers │ │ └── GeneratedFormExample.jsx │ └── meta │ │ ├── complete_bootstrap.json │ │ └── complete.json │ ├── config │ ├── webpack.prod.js │ ├── webpack.dev.js │ └── webpack.common.js │ └── package.json ├── .prettierrc.yaml ├── doc ├── img │ ├── demo-screen.png │ └── generator-architecture.png └── ru │ ├── maintainer notes.md │ ├── antd renderers API.md │ └── metadata format.md ├── .editorconfig ├── lerna.json ├── LICENSE ├── package.json ├── .gitignore ├── README.md └── TODO /.nvmrc: -------------------------------------------------------------------------------- 1 | v10.16.0 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-dinky -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | access=public 2 | scope=@react-ui-generator 3 | 4 | -------------------------------------------------------------------------------- /packages/validators/src/index.ts: -------------------------------------------------------------------------------- 1 | export { buildAjvValidator } from './ajv'; 2 | -------------------------------------------------------------------------------- /packages/core/config/tests/__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /packages/bootstrap/src/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { KeyValue } from '@react-ui-generator/core'; 2 | -------------------------------------------------------------------------------- /packages/metaphor/config/tests/__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | trailingComma: "es5" 2 | tabWidth: 2 3 | semi: false 4 | singleQuote: true 5 | -------------------------------------------------------------------------------- /doc/img/demo-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/true-brains/react-ui-generator/HEAD/doc/img/demo-screen.png -------------------------------------------------------------------------------- /packages/bootstrap/src/layouts/index.tsx: -------------------------------------------------------------------------------- 1 | import FormGroups from './FormGroups'; 2 | 3 | export default { 4 | FormGroups 5 | } -------------------------------------------------------------------------------- /doc/img/generator-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/true-brains/react-ui-generator/HEAD/doc/img/generator-architecture.png -------------------------------------------------------------------------------- /packages/bootstrap/src/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as Renderers } from './renderers'; 2 | export { default as Layouts } from './layouts'; 3 | -------------------------------------------------------------------------------- /packages/antd/README.md: -------------------------------------------------------------------------------- 1 | # react-ui-generator/antd 2 | 3 | ## Getting started 4 | 5 | `npm i @react-ui-generator/antd antd moment --save` 6 | 7 | -------------------------------------------------------------------------------- /packages/bootstrap/README.md: -------------------------------------------------------------------------------- 1 | *DO NOT USE IT IN PRODUCTION FOR NOW* 2 | 3 | It will be ready before v1.0, but after `@react-ui-generator/antd`. 4 | -------------------------------------------------------------------------------- /packages/antd/src/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as Renderers, FieldWrapper } from './renderers'; 2 | export { default as Layouts } from './layouts'; 3 | -------------------------------------------------------------------------------- /packages/metaphor/docs/assets/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/true-brains/react-ui-generator/HEAD/packages/metaphor/docs/assets/images/icons.png -------------------------------------------------------------------------------- /packages/metaphor/docs/assets/images/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/true-brains/react-ui-generator/HEAD/packages/metaphor/docs/assets/images/widgets.png -------------------------------------------------------------------------------- /packages/core/examples/meta/minimal.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | "fields": [ 3 | { "id": "foo" }, 4 | { "id": "bar" }, 5 | { "id": "baz" }, 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/metaphor/docs/assets/images/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/true-brains/react-ui-generator/HEAD/packages/metaphor/docs/assets/images/icons@2x.png -------------------------------------------------------------------------------- /packages/metaphor/docs/assets/images/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/true-brains/react-ui-generator/HEAD/packages/metaphor/docs/assets/images/widgets@2x.png -------------------------------------------------------------------------------- /packages/metaphor/__tests__/mocks/minimal.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | "fields": [ 3 | { "id": "foo" }, 4 | { "id": "bar" }, 5 | { "id": "baz" }, 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/demo/src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './app' 4 | 5 | ReactDOM.render(, document.getElementById('root')) 6 | -------------------------------------------------------------------------------- /packages/metaphor/src/index.ts: -------------------------------------------------------------------------------- 1 | import Metaphor from './lib/Metaphor'; 2 | import FieldPart from './lib/FieldPart'; 3 | 4 | export { 5 | Metaphor as default, 6 | Metaphor, 7 | FieldPart, 8 | }; -------------------------------------------------------------------------------- /packages/antd/src/layouts/index.tsx: -------------------------------------------------------------------------------- 1 | import { FormLayout } from './FormLayout'; 2 | import { FieldLayout } from './FieldLayout'; 3 | 4 | export default { 5 | FormLayout, 6 | FieldLayout 7 | } 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = false 9 | insert_final_newline = false 10 | -------------------------------------------------------------------------------- /packages/validators/README.md: -------------------------------------------------------------------------------- 1 | # react-ui-generator/validators 2 | 3 | ## Getting started 4 | 5 | `npm i @react-ui-generator/validators ajv --save` 6 | 7 | ## How to write your own binding for Y (example). 8 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmClient": "yarn", 3 | "useWorkspaces": true, 4 | "packages": [ 5 | "packages/core", 6 | "packages/validators", 7 | "packages/antd", 8 | "packages/metaphor" 9 | ], 10 | "version": "0.7.5" 11 | } 12 | -------------------------------------------------------------------------------- /packages/metaphor/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | 4 | const presets = ['@babel/preset-env']; 5 | const plugins = []; 6 | 7 | return { 8 | presets, 9 | plugins 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/core/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | 4 | const presets = ['@babel/preset-env', '@babel/preset-react']; 5 | const plugins = []; 6 | 7 | return { 8 | presets, 9 | plugins 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/validators/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | 4 | const presets = ['@babel/preset-env', '@babel/preset-react']; 5 | const plugins = []; 6 | 7 | return { 8 | presets, 9 | plugins 10 | }; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/antd/src/renderers/Text.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { _Input } from './Input'; 3 | import { FieldRenderer, } from '@react-ui-generator/core'; 4 | 5 | export class Text extends FieldRenderer { 6 | render() { 7 | return (<_Input type='text' {...this.props} />); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/antd/src/renderers/Password.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { FieldRenderer } from '@react-ui-generator/core'; 3 | import { _Input } from './Input'; 4 | 5 | export class Password extends FieldRenderer { 6 | render() { 7 | return (<_Input type='password' {...this.props} />); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/bootstrap/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./out/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | "module": "es2015", 7 | "target": "es2015", 8 | "moduleResolution": "node", 9 | "allowSyntheticDefaultImports": true, 10 | "declaration": true, 11 | "jsx": "react", 12 | }, 13 | "include": ["./src/**/*"] 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/examples/meta/field-action.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | "fields": [ 3 | { 4 | "id": "field1", 5 | "actions": { 6 | "onClick": "foo", 7 | "onDoubleClick": "bar" 8 | } 9 | }, 10 | 11 | { 12 | "id": "field2", 13 | "actions": { 14 | "onClick": "foo", 15 | "onDoubleClick": "unknown" 16 | } 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strinc' 2 | 3 | const path = require('path') 4 | const fs = require('fs') 5 | 6 | const appDirectory = fs.realpathSync(process.cwd()) 7 | const resolve = relativePath => path.resolve(appDirectory, relativePath) 8 | 9 | module.exports = { 10 | root: resolve(''), 11 | src: resolve('src'), 12 | tests: resolve('__tests__'), 13 | tsConfig: resolve('tsconfig.json'), 14 | } 15 | -------------------------------------------------------------------------------- /packages/metaphor/config/paths.js: -------------------------------------------------------------------------------- 1 | 'use strinc' 2 | 3 | const path = require('path') 4 | const fs = require('fs') 5 | 6 | const appDirectory = fs.realpathSync(process.cwd()) 7 | const resolve = relativePath => path.resolve(appDirectory, relativePath) 8 | 9 | module.exports = { 10 | root: resolve(''), 11 | src: resolve('src'), 12 | tests: resolve('__tests__'), 13 | tsConfig: resolve('tsconfig.json'), 14 | } 15 | -------------------------------------------------------------------------------- /packages/antd/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./out/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | "module": "es2015", 7 | "target": "es2015", 8 | "moduleResolution": "node", 9 | "allowSyntheticDefaultImports": true, 10 | "declaration": true, 11 | "jsx": "react", 12 | "experimentalDecorators": true 13 | }, 14 | "include": ["./src/**/*"] 15 | } 16 | -------------------------------------------------------------------------------- /packages/metaphor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./out/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | "module": "commonjs", 7 | "target": "es5", 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "declaration": true, 12 | "declarationDir": "./out/", 13 | }, 14 | "include": ["./src/**/*"] 15 | } -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./out/", 4 | "declarationDir": "./out/", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "moduleResolution": "node", 8 | "jsx": "react", 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "declaration": true, 12 | "sourceMap": true, 13 | "noImplicitAny": true 14 | }, 15 | "include": ["./src/**/*"] 16 | } 17 | -------------------------------------------------------------------------------- /packages/validators/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./out/", 4 | "declarationDir": "./out/", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "moduleResolution": "node", 8 | "jsx": "react", 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true, 11 | "declaration": true, 12 | "sourceMap": true, 13 | "noImplicitAny": true 14 | }, 15 | "include": ["./src/**/*"] 16 | } 17 | -------------------------------------------------------------------------------- /doc/ru/maintainer notes.md: -------------------------------------------------------------------------------- 1 | # Maintainer notes 2 | 3 | ## Reactstrap vs AntD 4 | 5 | Настройка layoyut отдельного поля у reactstrap и antd выполняется по-разному. У reactstrap есть отдельный компонент ``. У antd есть компонент ``, который отвечает за все обрамление поля: и label, и help (валидация), и в том числе за раположения label относительно поля. 6 | 7 | Нужно учесть это при выработке единообразного подхода к созданию layout-копонентав и описанию layout в метаданных. 8 | -------------------------------------------------------------------------------- /packages/bootstrap/src/renderers/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from './Button'; 2 | import { Text } from './Text'; 3 | import { TextArea } from './TextArea'; 4 | import { Dropdown } from './Dropdown'; 5 | import { Select } from './Select'; 6 | import { Checkbox } from './Checkbox'; 7 | import { Radiogroup } from './Radiogroup'; 8 | 9 | export default { 10 | button: Button, 11 | text: Text, 12 | textarea: TextArea, 13 | dropdown: Dropdown, 14 | select: Select, 15 | checkbox: Checkbox, 16 | radiogroup: Radiogroup 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/src/index.tsx: -------------------------------------------------------------------------------- 1 | export { GeneratedForm } from './components/GeneratedForm'; 2 | export { Field } from './components/Field'; 3 | export { Fields } from './components/Fields'; 4 | export { withFields } from './components/Layout'; 5 | export { 6 | withDefaults, 7 | findFieldMetaById, 8 | enhanceFieldMeta, 9 | enhanceFormMeta, 10 | makeDirty 11 | } from './utils'; 12 | export { Validator, ValidationResult, buildValidator } from './validator'; 13 | export { serializeToObject } from './serializer'; 14 | export * from './interfaces'; 15 | -------------------------------------------------------------------------------- /packages/antd/src/layouts/FormLayout.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Form from 'antd/lib/form'; 3 | import { FieldRendererProps } from '@react-ui-generator/core'; 4 | 5 | export interface FormLayoutProps { 6 | mode?: 'horizontal' | 'inline' | 'vertical'; 7 | } 8 | 9 | export class FormLayout extends React.PureComponent { 10 | render() { 11 | const { mode='vertical', children } = this.props; 12 | 13 | return ( 14 |
15 | {children} 16 |
17 | ); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/demo/src/components/CloseButton.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import makeClass from 'classnames'; 3 | 4 | class CloseButton extends React.PureComponent { 5 | render() { 6 | const { 7 | actions: { onClick }, 8 | disabled, 9 | className 10 | } = this.props; 11 | 12 | return ( 13 | 21 | ); 22 | } 23 | } 24 | 25 | export default CloseButton; 26 | -------------------------------------------------------------------------------- /packages/metaphor/config/tests/jest.config.js: -------------------------------------------------------------------------------- 1 | const paths = require('../paths'); 2 | 3 | module.exports = { 4 | verbose: true, 5 | rootDir: paths.root, 6 | preset: 'ts-jest', 7 | testRegex: '(/__tests__/.*(test|spec))\\.tsx?$', 8 | moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'], 9 | moduleNameMapper: { 10 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 11 | '/config/tests/__mocks__/fileMock.js', 12 | '\\.(css|scss)$': 'identity-obj-proxy' 13 | }, 14 | transform: { 15 | '^.+\\.js$': 'babel-jest' 16 | }, 17 | transformIgnorePatterns: ['/node_modules/(?!lodash-es)'], 18 | }; 19 | -------------------------------------------------------------------------------- /packages/demo/src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 13 | 14 | 15 | 16 | 27 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /packages/core/config/tests/jest.config.js: -------------------------------------------------------------------------------- 1 | const paths = require('../paths'); 2 | 3 | module.exports = { 4 | verbose: true, 5 | rootDir: paths.root, 6 | preset: 'ts-jest', 7 | testRegex: '(/__tests__/.*(test|spec))\\.tsx?$', 8 | moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx'], 9 | moduleNameMapper: { 10 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 11 | '/config/tests/__mocks__/fileMock.js', 12 | '\\.(css|scss)$': 'identity-obj-proxy' 13 | }, 14 | transform: { 15 | '^.+\\.js$': 'babel-jest' 16 | }, 17 | transformIgnorePatterns: ['/node_modules/(?!lodash-es)'], 18 | globals: { 19 | 'ts-jest': { 20 | diagnostics: false 21 | } 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /packages/antd/src/renderers/index.tsx: -------------------------------------------------------------------------------- 1 | export { FieldWrapper } from './FieldWrapper'; 2 | import { _Button } from './Button'; 3 | import { Text } from './Text'; 4 | import { Password } from './Password'; 5 | import { _TextArea } from './TextArea'; 6 | import { _Select } from './Select'; 7 | import { MultipleSelect } from './MultipleSelect'; 8 | import { _Checkbox } from './CheckBox'; 9 | import { _RadioGroup } from './RadioGroup'; 10 | import { _DatePicker } from './DatePicker'; 11 | import { _Upload } from './Upload'; 12 | 13 | export default { 14 | button: _Button, 15 | text: Text, 16 | password: Password, 17 | textarea: _TextArea, 18 | select: _Select, 19 | multiple: MultipleSelect, 20 | checkbox: _Checkbox, 21 | radiogroup: _RadioGroup, 22 | date: _DatePicker, 23 | upload: _Upload, 24 | } 25 | -------------------------------------------------------------------------------- /packages/bootstrap/src/renderers/Button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Button as RButton } from 'reactstrap'; 3 | 4 | export interface ButtonProps { 5 | className?: string; 6 | actions: { [key: string]: any }; 7 | config: { 8 | title: string; 9 | active?: boolean; 10 | outline?: boolean; 11 | color?: string; 12 | // size?: string; 13 | }; 14 | disabled: boolean; 15 | } 16 | 17 | export class Button extends React.PureComponent { 18 | render() { 19 | const { 20 | actions: { onClick }, 21 | config: { 22 | title, 23 | ...rest 24 | }, 25 | disabled, 26 | className 27 | } = this.props; 28 | 29 | return ( 30 | {title} 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/antd/src/layouts/FieldLayout.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Form from 'antd/lib/form'; 3 | import { FieldRendererProps } from '@react-ui-generator/core'; 4 | 5 | export interface FieldLayoutProps { 6 | labelCol: any; 7 | wrapperCol: any; 8 | } 9 | 10 | export interface ChildNodeProps extends FieldRendererProps { 11 | labelCol: any; 12 | wrapperCol: any; 13 | } 14 | 15 | export type ChildNode = React.ReactElement; 16 | 17 | const FormItem = Form.Item; 18 | 19 | export class FieldLayout extends React.PureComponent { 20 | render() { 21 | const { labelCol, wrapperCol, children } = this.props; 22 | 23 | return React.Children.map(children, (child: ChildNode, idx) => 24 | React.cloneElement(child, { labelCol, wrapperCol }) 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/demo/config/webpack.prod.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpackMerge = require('webpack-merge'); 3 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 4 | const Visualizer = require('webpack-visualizer-plugin'); 5 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); 6 | const baseConfig = require('./webpack.common.js') 7 | 8 | 9 | module.exports = webpackMerge(baseConfig, { 10 | output: { 11 | sourceMapFilename: '[file].map', 12 | }, 13 | 14 | devtool: 'source-map', 15 | mode: 'production', 16 | 17 | plugins: [ 18 | new UglifyJsPlugin({ 19 | sourceMap: true, 20 | parallel: 4, 21 | }), 22 | 23 | new Visualizer({ 24 | filename: './statistics.html' 25 | }), 26 | 27 | new BundleAnalyzerPlugin({ analyzerMode: 'static' }), 28 | ] 29 | }); 30 | -------------------------------------------------------------------------------- /packages/core/src/validator.ts: -------------------------------------------------------------------------------- 1 | import { KeyValue } from './interfaces'; 2 | 3 | export interface ExternalValidator { 4 | (schema: KeyValue, data: KeyValue): ValidationResult; 5 | } 6 | 7 | export interface Validator { 8 | (formValue: KeyValue): ValidationResult; 9 | } 10 | 11 | export interface ValidationResult { 12 | errors: { [key: string]: string[] }; 13 | isValid: boolean; 14 | } 15 | 16 | export function buildValidator( 17 | validatorFn: ExternalValidator, 18 | schema: KeyValue 19 | ): Validator { 20 | return (formValue: KeyValue): ValidationResult => { 21 | const { errors, isValid } = validatorFn(schema, formValue); 22 | const errorsByFields: KeyValue = {}; 23 | 24 | for (let fieldId of Object.keys(formValue)) { 25 | errorsByFields[fieldId] = errors[fieldId] || []; // empty array for valid fields 26 | } 27 | 28 | return { isValid, errors: errorsByFields }; 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /packages/metaphor/README.md: -------------------------------------------------------------------------------- 1 | # Metaphor 2 | 3 | DSL for easing baking of metadata for the react-ui-generator. 4 | 5 | ## Example 6 | 7 | ```js 8 | import Metaphor from '@react-ui-generator/metaphor'; 9 | import BaseMeta from './meta.json'; 10 | 11 | const formToView = new Metaphor(BaseMeta) 12 | .showAll() 13 | .disableAll() 14 | .value(); 15 | 16 | const formToEdit = new Metaphor(BaseMeta) 17 | .enableAll() 18 | .disable(['id', 'createdAt', 'author']) 19 | .config 20 | .set('email', { showAsterix: true }) 21 | .set(['birthDate', 'employmentDate'], { 22 | showAsterix: true, 23 | format: 'DD.MM.YYYY', 24 | }) 25 | .up() 26 | .actions 27 | .set(['btnSave'], { onClick: 'sendFormToServer' }) 28 | .set('btnCancel', { onClick: isFormChanged() ? 'confirmEditCancellation' : 'closeForm' }) 29 | .up() 30 | .hide(calcSecretFieldsByUserRole(), true) 31 | .value(); 32 | ``` 33 | -------------------------------------------------------------------------------- /packages/core/src/components/Field.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { withFields } from './Layout'; 3 | import { findFieldIdx } from '../utils'; 4 | 5 | export interface FieldProps { 6 | id: string; 7 | fields: JSX.Element[]; 8 | updateFields: any; 9 | className?: string; 10 | } 11 | 12 | class _Field extends React.Component { 13 | render(): JSX.Element { 14 | const { id: fieldId, fields, ...rest } = this.props; 15 | const fieldIdx = findFieldIdx(fieldId, fields); 16 | 17 | if (fieldIdx === -1) { 18 | console.warn(`Property "id" of "" contains unknown id "${fieldId}". Check metadata, please.`); 19 | return null; 20 | } 21 | 22 | // TODO: check if this is safe to modify context reference 23 | const [field] = fields.splice(fieldIdx, 1); 24 | 25 | return React.cloneElement(field, { ...rest }); 26 | } 27 | } 28 | 29 | export const Field = withFields(_Field); 30 | -------------------------------------------------------------------------------- /packages/metaphor/src/lib/FieldPart.ts: -------------------------------------------------------------------------------- 1 | import merge from 'lodash-es/merge' 2 | import cloneDeep from 'lodash-es/cloneDeep' 3 | import { Metaphor } from '../' 4 | 5 | export interface FieldPart { 6 | [key: string]: any 7 | } 8 | 9 | class FieldPartGateway { 10 | private form: Metaphor 11 | private path: string 12 | 13 | constructor(form: Metaphor, path: string) { 14 | this.form = form 15 | this.path = path 16 | } 17 | 18 | set(fieldIds: string[] | string, newPart: FieldPart) { 19 | const idsToProcess = Array.isArray(fieldIds) ? fieldIds : [fieldIds] 20 | 21 | for (let fieldId of idsToProcess) { 22 | const path = `${fieldId}.${this.path}` 23 | const fieldPart = this.form.get(path) 24 | const newFieldPart = cloneDeep(fieldPart) 25 | 26 | merge(newFieldPart, newPart) 27 | this.form.set(path, newFieldPart) 28 | } 29 | 30 | return this 31 | } 32 | 33 | up() { 34 | return this.form 35 | } 36 | } 37 | 38 | export default FieldPartGateway 39 | -------------------------------------------------------------------------------- /packages/demo/config/webpack.dev.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const webpackMerge = require('webpack-merge'); 4 | const Visualizer = require('webpack-visualizer-plugin'); 5 | const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); 6 | const baseConfig = require('./webpack.common.js') 7 | 8 | const resolve = dir => path.join(__dirname, '../', dir); 9 | 10 | module.exports = webpackMerge(baseConfig, { 11 | devServer: { 12 | contentBase: resolve('out'), 13 | compress: true, 14 | host: '0.0.0.0', 15 | port: 9007, 16 | historyApiFallback: true, 17 | hot: false, 18 | inline: true, 19 | https: false, 20 | noInfo: true, 21 | open: false 22 | }, 23 | 24 | devtool: 'inline-source-map', 25 | mode: 'development', 26 | 27 | plugins: [ 28 | new webpack.HotModuleReplacementPlugin(), 29 | 30 | new Visualizer({ 31 | filename: './statistics.html' 32 | }), 33 | 34 | new BundleAnalyzerPlugin({ analyzerMode: 'server' }), 35 | ] 36 | }); 37 | -------------------------------------------------------------------------------- /packages/core/src/components/Fields.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { withFields } from './Layout'; 3 | import { findFieldIdx } from '../utils'; 4 | 5 | export interface FieldsProps { 6 | until?: string; 7 | fields: JSX.Element[]; 8 | updateFields: any; 9 | className?: string; 10 | } 11 | 12 | class _Fields extends React.Component { 13 | render(): JSX.Element[] { 14 | const { until, fields, ...rest } = this.props; 15 | const fieldId = until || null; 16 | let idx; 17 | 18 | if (fieldId) { 19 | const maybeIdx = findFieldIdx(fieldId, fields); 20 | 21 | if (maybeIdx === -1) { 22 | console.warn(`Property "until" of "" contains unknown id "${fieldId}". Check metadata, please.`); 23 | idx = fields.length; 24 | } else { 25 | idx = maybeIdx; 26 | } 27 | } else { 28 | idx = fields.length; 29 | } 30 | 31 | return fields 32 | .splice(0, idx) 33 | .map(field => React.cloneElement(field, { ...rest })); 34 | } 35 | } 36 | 37 | export const Fields = withFields(_Fields); 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Alexei Zaviruha 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. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-ui-generator", 3 | "private": true, 4 | "version": "0.0.0", 5 | "description": "Library for generation UI from metadata", 6 | "main": "src/index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "schema:check": " ajv -s node_modules/@react-ui-generator/core/src/schema.json -d node_modules/@react-ui-generator/demo/src/meta/complete.json" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/react-ui-generator/react-ui-generator.git" 14 | }, 15 | "keywords": [ 16 | "react", 17 | "UI", 18 | "react-components", 19 | "form-generator", 20 | "UI-generator" 21 | ], 22 | "author": "Alexei Zaviruha ", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/react-ui-generator/react-ui-generator/issues" 26 | }, 27 | "homepage": "https://github.com/react-ui-generator/react-ui-generator#readme", 28 | "devDependencies": { 29 | "ajv-cli": "^2.1.0", 30 | "lerna": "^3.13.1", 31 | "prettier": "^1.19.1" 32 | }, 33 | "workspaces": [ 34 | "packages/*" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /**/out 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (http://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # Typescript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | -------------------------------------------------------------------------------- /packages/bootstrap/src/renderers/TextArea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import makeClass from 'classnames'; 3 | import { ChangeEvent } from 'react'; 4 | import { FieldRendererProps } from '@react-ui-generator/core'; 5 | import { Input } from 'reactstrap'; 6 | import { ValidatableField } from './ValidatableField'; 7 | 8 | export interface TextAreaProps extends FieldRendererProps {} 9 | 10 | export class TextArea extends React.PureComponent { 11 | handleChange(event: ChangeEvent): void { 12 | this.props.onChange(event.target.value); 13 | } 14 | 15 | render() { 16 | const { id, data, className, onChange, config, disabled, errors } = this.props; 17 | const value: string = String(data); 18 | 19 | return ( 20 | 21 | { 28 | this.handleChange(event); 29 | }} 30 | disabled={disabled} 31 | /> 32 | 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/bootstrap/src/renderers/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ChangeEvent } from 'react'; 3 | import { Input, Label } from 'reactstrap'; 4 | import { FieldRenderer } from '@react-ui-generator/core'; 5 | import { ValidatableField } from './ValidatableField'; 6 | 7 | export class Checkbox extends FieldRenderer { 8 | handleChange(event: ChangeEvent): void { 9 | this.props.onChange(event.target.checked); 10 | } 11 | 12 | render() { 13 | const { id, data, className, onChange, config, disabled, errors } = this.props; 14 | const value: boolean = Boolean(data); 15 | const label = 16 | config.label || (id.length ? id.charAt(0).toUpperCase() + id.slice(1) : ''); 17 | 18 | return ( 19 | 20 | { 25 | this.handleChange(event); 26 | }} 27 | disabled={disabled} 28 | checked={value} 29 | /> 30 | 31 | 32 | 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/bootstrap/src/renderers/Text.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import makeClass from 'classnames'; 3 | import { ChangeEvent } from 'react'; 4 | import { Input } from 'reactstrap'; 5 | import { FieldRendererProps } from '@react-ui-generator/core'; 6 | import { ValidatableField } from './ValidatableField'; 7 | 8 | export interface TextProps extends FieldRendererProps {} 9 | 10 | export class Text extends React.PureComponent { 11 | handleChange(event: ChangeEvent): void { 12 | this.props.onChange(event.target.value); 13 | } 14 | 15 | render() { 16 | const { id, data, className, onChange, config, disabled, errors } = this.props; 17 | const value: string = String(data); 18 | 19 | return ( 20 | 21 | { 28 | this.handleChange(event); 29 | }} 30 | disabled={disabled} 31 | /> 32 | 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-ui-generator a.k.a "CRUD-hammer" 2 | 3 | **WARNING: THIS PROJECT IS UNDER ACTIVE DEVELOPMENT** 4 | 5 | Set of libraries and tools for generation React-based UI from metadata. 6 | Bloody enterprise is not so scaring when you have CRUD-hammer! 7 | 8 |
9 |

Let amazingResult be the result of doing some amazing things.

10 | "DOM Living Standard" 11 |
12 | 13 | ## Example 14 | 15 | ![demo-screenshot](./doc/img/demo-screen.png) 16 | 17 | This is screenshot of the form, generated with bootstrap renderers. Metadata for this form is [here](./packages/demo/src/meta/complete.json). 18 | 19 | 20 | ## Features 21 | 22 | - Easy to generate entire form from the simple metadata description. 23 | - Set up custom layout in JSX (not in metadata, because markup-on-json is pain). 24 | - Easy to add custom types of fields (renderers). 25 | - Easy to add custom layouts. 26 | - Pure! (no state, just props). Easy to integrate with frameworks (Redux, etc). 27 | - Nested forms. Easy to implement dynamic forms (add/remove fields at runtime, etc). 28 | 29 | ## Architecture 30 | 31 | ![demo-screenshot](./doc/img/generator-architecture.png) 32 | 33 | ## TODO 34 | 35 | - complete this README.md 36 | - see [Roadmap](./TODO) 37 | -------------------------------------------------------------------------------- /packages/metaphor/config/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { TsConfigPathsPlugin } = require('awesome-typescript-loader'); 2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 3 | const path = require('path'); 4 | 5 | const resolve = dir => path.join(__dirname, '..', dir); 6 | 7 | module.exports = { 8 | entry: resolve('src/index.ts'), 9 | output: { 10 | filename: 'metaphor.js', 11 | path: resolve('out'), 12 | libraryTarget: 'umd' 13 | }, 14 | 15 | devtool: 'source-map', 16 | mode: 'production', 17 | 18 | resolve: { 19 | extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'], 20 | modules: [resolve('src'), 'node_modules'], 21 | plugins: [new TsConfigPathsPlugin({})] 22 | }, 23 | 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.tsx?$/, 28 | use: ['babel-loader', 'awesome-typescript-loader'], 29 | exclude: /node_modules/ 30 | }, 31 | 32 | // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. 33 | { 34 | enforce: 'pre', 35 | test: /\.js$/, 36 | loader: 'source-map-loader' 37 | } 38 | ] 39 | }, 40 | 41 | externals: [/^lodash-es(\/.+)?$/], 42 | 43 | plugins: [new CleanWebpackPlugin()] 44 | }; 45 | -------------------------------------------------------------------------------- /packages/validators/config/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { TsConfigPathsPlugin } = require('awesome-typescript-loader'); 2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 3 | const path = require('path'); 4 | 5 | const resolve = dir => path.join(__dirname, '..', dir); 6 | 7 | module.exports = { 8 | entry: resolve('src/index.ts'), 9 | output: { 10 | filename: 'validators.js', 11 | path: resolve('out'), 12 | libraryTarget: 'umd' 13 | }, 14 | 15 | devtool: 'source-map', 16 | mode: 'production', 17 | 18 | resolve: { 19 | extensions: ['.ts', '.js', '.json'], 20 | modules: [resolve('src'), 'node_modules'], 21 | plugins: [new TsConfigPathsPlugin({})] 22 | }, 23 | 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.ts$/, 28 | use: ['babel-loader', 'awesome-typescript-loader'], 29 | exclude: /node_modules/ 30 | }, 31 | 32 | // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. 33 | { 34 | enforce: 'pre', 35 | test: /\.js$/, 36 | loader: 'source-map-loader' 37 | } 38 | ] 39 | }, 40 | externals: ['@react-ui-generator/core', /^lodash-es(\/.+)?$/], 41 | plugins: [new CleanWebpackPlugin()] 42 | }; 43 | -------------------------------------------------------------------------------- /packages/antd/src/renderers/FieldWrapper.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Form from 'antd/lib/form'; 3 | 4 | const FormItem = Form.Item; 5 | 6 | export interface FieldWrapperProps { 7 | errors: string[]; 8 | dirty: boolean; 9 | children: JSX.Element | JSX.Element[]; 10 | labelOnly?: boolean; 11 | hasFeedback?: boolean; 12 | showAsterix?: boolean; 13 | label?: string; 14 | labelCol?: any; 15 | wrapperCol?: any; 16 | } 17 | 18 | export class FieldWrapper extends React.PureComponent { 19 | render() { 20 | const { errors, dirty = false, children, labelOnly, hasFeedback, showAsterix, ...rest } = this.props; 21 | const isValidated = Array.isArray(errors) && dirty; 22 | const isValid = !isValidated || (errors.length === 0); 23 | const errorComponents = (errors || []).map((err, idx) => (
{err}
)) 24 | 25 | return( 26 | 33 | {children} 34 | 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/core/src/serializer.ts: -------------------------------------------------------------------------------- 1 | import { FormMetaDescription, KeyValue } from './interfaces'; 2 | 3 | export function serializeToObject(formData: KeyValue): KeyValue { 4 | const serialized: KeyValue = {} 5 | 6 | for (let fieldId of Object.keys(formData)) { 7 | const fieldData = formData[fieldId] 8 | const value = fieldData.value; 9 | 10 | if (Array.isArray(value)) { 11 | serialized[fieldId] = value.map(serializeToObject); 12 | } else if (value && (typeof value === 'object')) { 13 | serialized[fieldId] = serializeToObject(value); 14 | } else { 15 | serialized[fieldId] = value; 16 | } 17 | } 18 | 19 | return serialized; 20 | } 21 | 22 | export function serializeToObject2(formMeta: FormMetaDescription, formData: KeyValue): KeyValue { 23 | const serialized: KeyValue = {} 24 | 25 | for (let { id} of formMeta.fields) { 26 | // const fieldData = formData[fieldId] 27 | // const value = fieldData.value; 28 | 29 | // if (Array.isArray(value)) { 30 | // serialized[fieldId] = value.map(serializeToObject); 31 | // } else if (value && (typeof value === 'object')) { 32 | // serialized[fieldId] = serializeToObject(value); 33 | // } else { 34 | // serialized[fieldId] = value; 35 | // } 36 | } 37 | 38 | return serialized; 39 | } 40 | -------------------------------------------------------------------------------- /packages/core/src/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const { Provider, Consumer } = React.createContext({ 4 | fields: [], 5 | updateFields: (fields: JSX.Element[]) => {} 6 | }); 7 | 8 | export function withFields(Component: React.ComponentClass) { 9 | return function ComponentWithFields(props: any) { 10 | return ( 11 | 12 | {(contextProps) => } 13 | 14 | ); 15 | } 16 | } 17 | 18 | export interface LayoutProps { 19 | fields: JSX.Element[], 20 | children?: React.ReactNode; 21 | } 22 | 23 | export interface LayoutState { 24 | fields: JSX.Element[], 25 | } 26 | 27 | export class Layout extends React.Component { 28 | constructor(props: LayoutProps) { 29 | super(props); 30 | 31 | this.state = { 32 | fields: props.fields 33 | }; 34 | } 35 | 36 | static getDerivedStateFromProps(nextProps: LayoutProps) { 37 | return { fields: nextProps.fields }; 38 | } 39 | 40 | render() { 41 | const { children } = this.props; 42 | const value = { 43 | fields: this.state.fields, 44 | updateFields: (newFields: JSX.Element[]) => { this.setState({ fields: newFields }); } 45 | } 46 | 47 | return ({children}); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/bootstrap/src/renderers/ValidatableField.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { FormFeedback } from 'reactstrap'; 3 | 4 | export interface ValidatableFieldProps { 5 | errors: string[]; 6 | isDirty: boolean; 7 | children: JSX.Element | JSX.Element[]; 8 | labelOnly?: boolean; 9 | } 10 | 11 | export class ValidatableField extends React.PureComponent { 12 | render() { 13 | const { errors, isDirty, children, labelOnly } = this.props; 14 | const isValidated = Boolean(errors) && isDirty; 15 | const isValid = !isValidated || !errors.length; 16 | 17 | const validatedChildren = React.Children.map( 18 | children, 19 | (child: JSX.Element, idx: number) => { 20 | // bootstrap components can be followed by a label, 21 | // so we explicitly process only the first child 22 | return idx === 0 23 | ? React.cloneElement(child, { 24 | valid: isValidated ? isValid : undefined 25 | }) 26 | : child; 27 | } 28 | ); 29 | 30 | if (!isValidated) { 31 | return [validatedChildren]; 32 | } 33 | 34 | const errorMessages = errors.map((error, idx) => ( 35 | {error} 36 | )); 37 | 38 | return [validatedChildren, ...(labelOnly ? [] : errorMessages)]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/core/config/webpack.config.js: -------------------------------------------------------------------------------- 1 | const { TsConfigPathsPlugin } = require('awesome-typescript-loader'); 2 | const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 3 | const path = require('path'); 4 | 5 | const resolve = dir => path.join(__dirname, '..', dir); 6 | 7 | module.exports = { 8 | entry: resolve('src/index.tsx'), 9 | output: { 10 | filename: 'core.js', 11 | path: resolve('out'), 12 | libraryTarget: 'umd' 13 | }, 14 | 15 | devtool: 'source-map', 16 | mode: 'production', 17 | 18 | resolve: { 19 | extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'], 20 | modules: [resolve('src'), 'node_modules'], 21 | plugins: [new TsConfigPathsPlugin({})] 22 | }, 23 | 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.tsx?$/, 28 | use: ['babel-loader', 'awesome-typescript-loader'], 29 | exclude: /node_modules/ 30 | }, 31 | 32 | // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. 33 | { 34 | enforce: 'pre', 35 | test: /\.js$/, 36 | loader: 'source-map-loader' 37 | } 38 | ] 39 | }, 40 | 41 | externals: { 42 | react: { 43 | root: 'React', 44 | commonjs2: 'react', 45 | commonjs: 'react', 46 | amd: 'react' 47 | } 48 | }, 49 | 50 | plugins: [new CleanWebpackPlugin()] 51 | }; 52 | -------------------------------------------------------------------------------- /packages/demo/src/actions.js: -------------------------------------------------------------------------------- 1 | import { buildJSONSerializer } from '@react-ui-generator/serializers'; 2 | 3 | export const UPDATE_FORM = 'UPDATE_FORM'; 4 | export const TOGGLE_SEX = 'TOGGLE_SEX'; 5 | export const FORM_SENDING_START = 'FORM_SENDING_START'; 6 | export const FORM_SENDING_FINISH = 'FORM_SENDING_FINISH'; 7 | export const CLEAR_FORM = 'CLEAR_FORM'; 8 | export const ADD_RELATIVE = 'ADD_RELATIVE'; 9 | export const REMOVE_RELATIVE = 'REMOVE_RELATIVE'; 10 | 11 | export const toggleSex = payload => ({ type: TOGGLE_SEX, payload }); 12 | export const updateForm = payload => ({ type: UPDATE_FORM, payload }); 13 | export const clearForm = () => ({ type: CLEAR_FORM }); 14 | 15 | export const sendForm = () => (dispatch, getState) => { 16 | dispatch({ type: FORM_SENDING_START }); 17 | 18 | const serialize = buildJSONSerializer(); 19 | const { data, isValid } = getState(); 20 | const serializedForm = serialize(data); 21 | 22 | if (isValid) { 23 | setTimeout(() => { 24 | console.log('Serialized data was successfully sent: ', serializedForm); 25 | dispatch({ type: FORM_SENDING_FINISH }); 26 | }, 2000); 27 | } else { 28 | // TODO: add form dirtyfying 29 | console.error('Form is invalid'); 30 | } 31 | }; 32 | 33 | export const addRelative = () => ({ type: ADD_RELATIVE }); 34 | export const removeRelative = idx => ({ type: REMOVE_RELATIVE, payload: idx }); 35 | -------------------------------------------------------------------------------- /packages/validators/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-ui-generator/validators", 3 | "version": "0.7.5", 4 | "description": "Collection of bindings for popular validation libraries to react-ui-generator", 5 | "main": "./out/validators.js", 6 | "types": "./out/index.d.ts", 7 | "repository": "https://github.com/react-ui-generator/react-ui-generator/packages/validators", 8 | "scripts": { 9 | "build": "webpack --config config/webpack.config.js", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "Alexei Zaviruha ", 13 | "license": "MIT", 14 | "peerDependencies": { 15 | "@react-ui-generator/core": "^0.6.0", 16 | "lodash-es": "^4.17.15" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.5.5", 20 | "@babel/preset-env": "^7.0.0", 21 | "@react-ui-generator/core": "^0.7.5", 22 | "@types/jest": "^24.0.15", 23 | "@types/lodash-es": "^4.17.3", 24 | "awesome-typescript-loader": "^5.2.1", 25 | "babel-jest": "^24.8.0", 26 | "babel-loader": "^8.0.6", 27 | "babel-upgrade": "^0.0.24", 28 | "jest": "^24.8.0", 29 | "source-map-loader": "^0.2.3", 30 | "ts-jest": "^24.0.2", 31 | "typescript": "^3.5.3", 32 | "webpack": "^4.36.1", 33 | "webpack-cleanup-plugin": "^0.5.1", 34 | "webpack-cli": "^3.3.6", 35 | "webpack-merge": "^4.1.2" 36 | }, 37 | "gitHead": "f34904c44b092961eef4fa3e5fc9b62b18628838" 38 | } 39 | -------------------------------------------------------------------------------- /packages/antd/src/renderers/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Button, { ButtonType, ButtonSize } from 'antd/lib/button'; 3 | import PropTypes from 'prop-types' 4 | 5 | import { 6 | FieldRenderer, 7 | basePropTypes 8 | } from '@react-ui-generator/core'; 9 | 10 | import { FieldWrapper } from './FieldWrapper'; 11 | 12 | export class _Button extends FieldRenderer { 13 | static propTypes = { 14 | ...basePropTypes(), 15 | config: PropTypes.shape({ 16 | title: PropTypes.string, 17 | outline: PropTypes.bool, 18 | color: PropTypes.oneOf(['default', 'primary', 'ghost', 'dashed', 'danger']), 19 | size: PropTypes.oneOf(['small', 'default', 'large']), 20 | }), 21 | } 22 | 23 | static defaultProps = { 24 | className: '', 25 | disabled: false, 26 | config: { 27 | color: 'default', 28 | size: 'default', 29 | outline: false 30 | } 31 | } 32 | 33 | render() { 34 | const { 35 | actions: { onClick }, 36 | config: { title, outline, color, size }, 37 | disabled, 38 | className, 39 | ...rest 40 | } = this.props; 41 | 42 | const props = { 43 | type: color as ButtonType, 44 | size: size as ButtonSize, 45 | ghost: outline 46 | }; 47 | 48 | return ( 49 | 50 | 53 | 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/demo/config/webpack.common.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | const ProgressBarPlugin = require('progress-bar-webpack-plugin'); 4 | 5 | const resolve = dir => path.join(__dirname, '../', dir); 6 | 7 | module.exports = { 8 | entry: { 9 | app: resolve('src/index.jsx') 10 | }, 11 | 12 | output: { 13 | path: resolve('out'), 14 | filename: '[name].js' 15 | }, 16 | 17 | resolve: { 18 | modules: [resolve('src'), 'node_modules'], 19 | extensions: ['.js', '.jsx', '.json'], 20 | 21 | alias: { 22 | '@': resolve('src'), 23 | '@components': resolve('src/components'), 24 | '@containers': resolve('src/containers'), 25 | '@meta': resolve('src/meta'), 26 | '@validation': resolve('src/validation'), 27 | '@actions': resolve('src/actions') 28 | } 29 | }, 30 | 31 | module: { 32 | rules: [ 33 | { 34 | test: /\.jsx?$/, 35 | include: [resolve('src')], 36 | loader: 'babel-loader', 37 | 38 | options: { 39 | presets: ['@babel/preset-env', '@babel/preset-react'] 40 | } 41 | }, 42 | 43 | { 44 | test: /\.css$/i, 45 | use: ['style-loader', 'css-loader'], 46 | } 47 | ] 48 | }, 49 | 50 | plugins: [ 51 | new ProgressBarPlugin(), 52 | 53 | new HtmlWebpackPlugin({ 54 | title: 'react-form-generator demo', 55 | inject: true, 56 | template: resolve('src/index.ejs') 57 | }) 58 | ] 59 | }; 60 | -------------------------------------------------------------------------------- /packages/demo/src/validation/jsonSchema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | "description": "Validation schema for the Registration form", 4 | "type": "object", 5 | "properties": { 6 | "email": { 7 | "type": "string", 8 | "format": "email" 9 | }, 10 | "password": { 11 | "type": "string", 12 | "pattern": "^[A-Za-z0-9#$.,_-]*$", 13 | "minLength": 5, 14 | "maxLength": 15 15 | }, 16 | "confirmation": { 17 | "const": { "$data": "1/password" } 18 | }, 19 | "sex": { 20 | "enum": ["male", "female"] 21 | }, 22 | "pl": { 23 | "type": "array", 24 | "items": { 25 | "type": "string" 26 | }, 27 | "minItems": 1 28 | }, 29 | "isMarried": { 30 | "_type": "boolean", 31 | "const": true 32 | }, 33 | "aboutMe": { 34 | "type": "string", 35 | "minLength": 20 36 | }, 37 | "relatives": { 38 | "type": "array", 39 | "items": { 40 | "title": "Relative", 41 | "description": "Relative definition", 42 | "type": "object", 43 | "properties": { 44 | "firstName": { 45 | "type": "string", 46 | "pattern": "^[A-Z]+([A-Za-z]+[ -]?)+$" 47 | }, 48 | "lastName": { 49 | "type": "string", 50 | "pattern": "^[A-Z]+([A-Za-z]+[ -]?)+$" 51 | } 52 | }, 53 | "required": ["firstName", "lastName"] 54 | } 55 | }, 56 | "answer": { 57 | "const": 42 58 | } 59 | }, 60 | "required": ["email", "password", "pl", "answer"] 61 | } -------------------------------------------------------------------------------- /packages/bootstrap/src/renderers/Dropdown.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ChangeEvent } from 'react'; 3 | import makeClass from 'classnames'; 4 | import { Dropdown as RDropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap'; 5 | import { FieldRendererProps } from '@react-ui-generator/core'; 6 | 7 | export interface DropdownProps extends FieldRendererProps { 8 | title: string; 9 | caret?: boolean; 10 | isOpen?: boolean; 11 | } 12 | 13 | interface DropdownItemProps { 14 | title: string; 15 | id: string; 16 | header?: boolean; 17 | disabled?: boolean; 18 | divider?: boolean; 19 | } 20 | 21 | export class Dropdown extends React.PureComponent { 22 | render() { 23 | const { 24 | id, 25 | actions: { onToggle }, 26 | config: { title, isOpen, caret, options }, 27 | disabled, 28 | className, 29 | onChange 30 | } = this.props; 31 | 32 | return ( 33 | onToggle(id)}> 34 | 35 | {title} 36 | 37 | 38 | {options.map((item: DropdownItemProps) => { 39 | const { title, ...item_props } = item; 40 | 41 | return ( 42 | onChange(item_props.id)} 45 | {...item_props} 46 | active 47 | > 48 | {title} 49 | 50 | ) 51 | })} 52 | 53 | 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/antd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-ui-generator/antd", 3 | "version": "0.7.5", 4 | "description": "Renderers and layouts for Ant Design", 5 | "main": "./out/antd.js", 6 | "repository": "https://github.com/react-ui-generator/react-ui-generator/packages/antd", 7 | "scripts": { 8 | "build": "webpack --config config/webpack.config.js", 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "Alexei Zaviruha ", 12 | "license": "MIT", 13 | "dependencies": { 14 | "prop-types": "^15.6.1" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.5.5", 18 | "@babel/preset-env": "^7.0.0", 19 | "@babel/preset-react": "^7.0.0", 20 | "@react-ui-generator/core": "^0.7.5", 21 | "@types/jest": "^24.0.15", 22 | "@types/lodash-es": "^4.17.3", 23 | "@types/prop-types": "^15.5.3", 24 | "@types/react": "^16.0.29", 25 | "antd": "^3.4.1", 26 | "awesome-typescript-loader": "^3.4.1", 27 | "babel-jest": "^24.8.0", 28 | "babel-loader": "^8.0.6", 29 | "babel-upgrade": "^0.0.24", 30 | "jest": "^24.8.0", 31 | "moment": "^2.22.1", 32 | "react": "^16.3.0", 33 | "source-map-loader": "^0.2.3", 34 | "ts-jest": "^24.0.2", 35 | "typescript": "^3.5.3", 36 | "webpack": "^4.36.1", 37 | "webpack-cleanup-plugin": "^0.5.1", 38 | "webpack-cli": "^3.3.6", 39 | "webpack-merge": "^4.1.2" 40 | }, 41 | "peerDependencies": { 42 | "@react-ui-generator/core": "^0.6.0", 43 | "antd": "^3.4.1", 44 | "lodash-es": "^4.17.15", 45 | "moment": "^2.22.1", 46 | "react": "^16.3.0" 47 | }, 48 | "gitHead": "f34904c44b092961eef4fa3e5fc9b62b18628838" 49 | } 50 | -------------------------------------------------------------------------------- /packages/bootstrap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-ui-generator/bootstrap", 3 | "private": true, 4 | "version": "0.7.5", 5 | "description": "Renderers and layouts for react-ui-generator, based on Twitter Bootstrap", 6 | "main": "./out/bootstrap.js", 7 | "repository": "https://github.com/react-ui-generator/react-ui-generator/packages/bootstrap", 8 | "scripts": { 9 | "build": "webpack --config config/webpack.config.js", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "Alexei Zaviruha ", 13 | "license": "MIT", 14 | "dependencies": { 15 | "classnames": "^2.2.5", 16 | "reactstrap": "^6.0.1" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.5.5", 20 | "@babel/preset-env": "^7.0.0", 21 | "@babel/preset-react": "^7.0.0", 22 | "@react-ui-generator/core": "^0.7.5", 23 | "@types/classnames": "^2.2.3", 24 | "@types/jest": "^24.0.15", 25 | "@types/lodash-es": "^4.17.3", 26 | "@types/react": "^16.0.29", 27 | "@types/reactstrap": "^5.0.12", 28 | "awesome-typescript-loader": "^3.4.1", 29 | "babel-jest": "^24.8.0", 30 | "babel-loader": "^8.0.6", 31 | "babel-upgrade": "^0.0.24", 32 | "jest": "^24.8.0", 33 | "source-map-loader": "^0.2.3", 34 | "ts-jest": "^24.0.2", 35 | "typescript": "^3.5.3", 36 | "webpack": "^4.36.1", 37 | "webpack-cleanup-plugin": "^0.5.1", 38 | "webpack-cli": "^3.3.6", 39 | "webpack-merge": "^4.1.2" 40 | }, 41 | "peerDependencies": { 42 | "@react-ui-generator/core": "^0.6.0", 43 | "lodash-es": "^4.17.15", 44 | "react": "^16.3.0" 45 | }, 46 | "gitHead": "f34904c44b092961eef4fa3e5fc9b62b18628838" 47 | } 48 | -------------------------------------------------------------------------------- /packages/bootstrap/src/layouts/FormGroups.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import makeClass from 'classnames'; 3 | import { FormGroup, Label } from 'reactstrap'; 4 | import { FieldRendererProps } from '@react-ui-generator/core'; 5 | import Renderers from '../renderers'; 6 | 7 | export interface FormGroupsProps { 8 | className?: string; 9 | } 10 | 11 | interface ChildNodeProps extends FieldRendererProps { 12 | className: string; 13 | } 14 | type ChildNode = React.ReactElement; 15 | 16 | export class FormGroups extends React.PureComponent { 17 | render() { 18 | const { children, className } = this.props; 19 | 20 | return React.Children.map(children, (child: ChildNode, idx) => { 21 | const { id, config } = child.props; 22 | const isCheckbox = child.type.toString() === Renderers.checkbox.toString(); 23 | const isRadiogroup = child.type.toString() === Renderers.radiogroup.toString(); 24 | 25 | const label = config.label; 26 | 27 | return ( 28 | 34 | {isCheckbox ? ( 35 | child 36 | ) : ( 37 | isRadiogroup ? [ 38 | , 39 | child 40 | ] : [ 41 | , 42 | React.cloneElement(child, { className: 'form-control' }) 43 | ])} 44 | 45 | ); 46 | }); 47 | } 48 | } 49 | 50 | export default FormGroups; 51 | -------------------------------------------------------------------------------- /packages/core/src/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema#", 3 | "description": "(!! WORK IN PROGRESS !!) Language Independed UI definition schema", 4 | "type": "object", 5 | "properties": { 6 | "fields": { 7 | "type": "array", 8 | "items": { 9 | "title": "Field", 10 | "description": "Field definition", 11 | "type": "object", 12 | "properties": { 13 | "id": { 14 | "type": "string" 15 | }, 16 | "renderer": { 17 | "oneOf": [ 18 | { "$ref": "#/definitions/rendererSimple" }, 19 | { "$ref": "#/definitions/rendererConfigurable" } 20 | ] 21 | }, 22 | "serializer": { 23 | "type": [ "string", "null" ], 24 | "pattern": "^[^.]+(\\.[^.]+)*$" 25 | }, 26 | "validator": { 27 | "oneOf": [ 28 | { "$ref": "#/definitions/validatorSimple" }, 29 | { "$ref": "#/definitions/validatorComplex" } 30 | ] 31 | }, 32 | "actions": { 33 | "type": "object" 34 | } 35 | }, 36 | "required": [ "id" ] 37 | } 38 | } 39 | }, 40 | "definitions": { 41 | "rendererSimple": { 42 | "type": "string" 43 | }, 44 | "rendererConfigurable": { 45 | "type": "object", 46 | "properties": { 47 | "type": { "type": "string" }, 48 | "config": { "type": "object" } 49 | } 50 | }, 51 | "validatorSimple": { 52 | "type": "string" 53 | }, 54 | "validatorComplex": { 55 | "type": "object", 56 | "properties": { 57 | "rule": { "type": "string" }, 58 | "value": {}, 59 | "isAsync": { "type": "boolean" } 60 | }, 61 | "required": [ "rule" ] 62 | } 63 | }, 64 | "required": [ "fields" ], 65 | "additionalProperties": false 66 | } 67 | -------------------------------------------------------------------------------- /packages/metaphor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-ui-generator/metaphor", 3 | "version": "0.7.5", 4 | "description": "DSL for easing baking of metadata for the react-ui-generator.", 5 | "main": "./out/metaphor.js", 6 | "types": "./out/index.d.ts", 7 | "repository": "https://github.com/react-ui-generator/react-ui-generator/packages/metaphor", 8 | "scripts": { 9 | "build": "webpack --config config/webpack.config.js", 10 | "test": "jest -c config/tests/jest.config.js", 11 | "doc:build": "typedoc --out ./docs/ --exclude ./node_modules/ src/index.ts" 12 | }, 13 | "keywords": [ 14 | "metadata", 15 | "dsl", 16 | "react-ui-generator" 17 | ], 18 | "author": "A.Zaviruha ", 19 | "license": "MIT", 20 | "devDependencies": { 21 | "@babel/core": "^7.5.5", 22 | "@babel/preset-env": "^7.0.0", 23 | "@babel/register": "^7.7.0", 24 | "@react-ui-generator/core": "^0.7.5", 25 | "@types/invariant": "^2.2.30", 26 | "@types/jest": "^24.0.15", 27 | "@types/lodash-es": "^4.17.3", 28 | "awesome-typescript-loader": "^5.2.1", 29 | "babel-jest": "^24.8.0", 30 | "babel-loader": "^8.0.6", 31 | "babel-upgrade": "^0.0.24", 32 | "jest": "^24.8.0", 33 | "ts-jest": "^24.0.2", 34 | "typedock": "^1.0.9", 35 | "typescript": "^3.5.3", 36 | "webpack": "^4.36.1", 37 | "webpack-cleanup-plugin": "^0.5.1", 38 | "webpack-cli": "^3.3.6", 39 | "webpack-merge": "^4.1.2" 40 | }, 41 | "peerDependencies": { 42 | "@react-ui-generator/core": "^0.6.0", 43 | "lodash-es": "^4.17.15" 44 | }, 45 | "gitHead": "f34904c44b092961eef4fa3e5fc9b62b18628838", 46 | "dependencies": { 47 | "invariant": "^2.2.4" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/bootstrap/src/renderers/Radiogroup.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import makeClass from 'classnames'; 3 | import { ChangeEvent } from 'react'; 4 | import { Input, Label, FormGroup } from 'reactstrap'; 5 | import { FieldRendererProps } from '@react-ui-generator/core'; 6 | import { ValidatableField } from './ValidatableField'; 7 | 8 | export interface RadiogroupProps extends FieldRendererProps {} 9 | 10 | export interface RadiogroupItem { 11 | id: string; 12 | title: string; 13 | } 14 | 15 | export class Radiogroup extends React.PureComponent { 16 | handleChange(value: string): void { 17 | this.props.onChange(value); 18 | } 19 | 20 | render() { 21 | const { id, data, className, onChange, config, disabled, errors } = this.props; 22 | const value: string = String(data); 23 | 24 | return ( 25 | config.options.map((item: RadiogroupItem, idx: number) => ( 26 | 27 | 32 | { 38 | this.handleChange(item.id); 39 | }} 40 | disabled={disabled} 41 | checked={value === item.id.toString()} 42 | /> 43 | 44 | 45 | 46 | 47 | )) 48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/antd/src/renderers/TextArea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ChangeEvent } from 'react'; 3 | import PropTypes from 'prop-types' 4 | import Input from 'antd/lib/input'; 5 | 6 | import { FieldRenderer, basePropTypes } from '@react-ui-generator/core'; 7 | import { FieldWrapper } from './FieldWrapper'; 8 | 9 | const { TextArea } = Input; 10 | 11 | const value: string = ''; 12 | 13 | export class _TextArea extends FieldRenderer { 14 | static propTypes = { 15 | ...basePropTypes(), 16 | config: PropTypes.shape({ 17 | label: PropTypes.string, 18 | placeholder: PropTypes.string, 19 | showAsterix: PropTypes.bool 20 | }) 21 | }; 22 | 23 | static defaultProps = { 24 | className: '', 25 | disabled: false, 26 | dirty: false, 27 | config: { 28 | label: '', 29 | placeholder: '', 30 | showAsterix: false 31 | }, 32 | data: value 33 | }; 34 | 35 | handleChange = (event: ChangeEvent): void => { 36 | this.props.onChange(event.target.value); 37 | }; 38 | 39 | render() { 40 | const { 41 | id, 42 | data, 43 | className, 44 | onChange, 45 | config: { label, placeholder, showAsterix }, 46 | disabled, 47 | ...rest 48 | } = this.props; 49 | const value: string = String(data); 50 | 51 | return ( 52 | 53 |