├── .editorconfig ├── .gitignore ├── .npmrc ├── .nvmrc ├── .prettierrc.yaml ├── LICENSE ├── README.md ├── TODO ├── _config.yml ├── doc ├── img │ ├── demo-screen.png │ └── generator-architecture.png └── ru │ ├── antd renderers API.md │ ├── maintainer notes.md │ └── metadata format.md ├── lerna.json ├── package.json ├── packages ├── antd │ ├── README.md │ ├── config │ │ └── webpack.config.js │ ├── package.json │ ├── src │ │ ├── index.tsx │ │ ├── layouts │ │ │ ├── FieldLayout.tsx │ │ │ ├── FormLayout.tsx │ │ │ └── index.tsx │ │ └── renderers │ │ │ ├── Button.tsx │ │ │ ├── CheckBox.tsx │ │ │ ├── DatePicker.tsx │ │ │ ├── FieldWrapper.tsx │ │ │ ├── Input.tsx │ │ │ ├── MultipleSelect.tsx │ │ │ ├── Password.tsx │ │ │ ├── RadioGroup.tsx │ │ │ ├── Select.tsx │ │ │ ├── Text.tsx │ │ │ ├── TextArea.tsx │ │ │ ├── Upload.tsx │ │ │ └── index.tsx │ └── tsconfig.json ├── bootstrap │ ├── README.md │ ├── config │ │ └── webpack.config.js │ ├── package.json │ ├── src │ │ ├── index.tsx │ │ ├── interfaces.ts │ │ ├── layouts │ │ │ ├── FormGroups.tsx │ │ │ └── index.tsx │ │ └── renderers │ │ │ ├── Button.tsx │ │ │ ├── Checkbox.tsx │ │ │ ├── Dropdown.tsx │ │ │ ├── Radiogroup.tsx │ │ │ ├── Select.tsx │ │ │ ├── Text.tsx │ │ │ ├── TextArea.tsx │ │ │ ├── ValidatableField.tsx │ │ │ └── index.tsx │ └── tsconfig.json ├── core │ ├── __tests__ │ │ ├── components │ │ │ └── GeneratedForm.test.tsx │ │ ├── serializer.test.ts │ │ ├── utils.test.tsx │ │ └── validator.test.ts │ ├── babel.config.js │ ├── config │ │ ├── paths.js │ │ ├── tests │ │ │ ├── __mocks__ │ │ │ │ └── fileMock.js │ │ │ └── jest.config.js │ │ └── webpack.config.js │ ├── examples │ │ └── meta │ │ │ ├── all-renderers.ts │ │ │ ├── field-action.ts │ │ │ └── minimal.ts │ ├── package.json │ ├── src │ │ ├── components │ │ │ ├── Field.tsx │ │ │ ├── Fields.tsx │ │ │ ├── GeneratedForm.tsx │ │ │ ├── Layout.tsx │ │ │ └── renderers │ │ │ │ ├── ListForm.tsx │ │ │ │ └── SubForm.tsx │ │ ├── index.tsx │ │ ├── interfaces.ts │ │ ├── schema.json │ │ ├── serializer.ts │ │ ├── utils.ts │ │ └── validator.ts │ └── tsconfig.json ├── demo │ ├── config │ │ ├── webpack.common.js │ │ ├── webpack.dev.js │ │ └── webpack.prod.js │ ├── package.json │ ├── src │ │ ├── actions.js │ │ ├── app.jsx │ │ ├── components │ │ │ └── CloseButton.jsx │ │ ├── containers │ │ │ └── GeneratedFormExample.jsx │ │ ├── index.ejs │ │ ├── index.jsx │ │ ├── meta │ │ │ ├── complete.json │ │ │ └── complete_bootstrap.json │ │ ├── reducers.js │ │ └── validation │ │ │ └── jsonSchema.json │ └── stats.json ├── metaphor │ ├── README.md │ ├── __tests__ │ │ ├── metaphor.test.ts │ │ └── mocks │ │ │ └── minimal.ts │ ├── babel.config.js │ ├── config │ │ ├── paths.js │ │ ├── tests │ │ │ ├── __mocks__ │ │ │ │ └── fileMock.js │ │ │ └── jest.config.js │ │ └── webpack.config.js │ ├── docs │ │ ├── assets │ │ │ ├── css │ │ │ │ └── main.css │ │ │ ├── images │ │ │ │ ├── icons.png │ │ │ │ ├── icons@2x.png │ │ │ │ ├── widgets.png │ │ │ │ └── widgets@2x.png │ │ │ └── js │ │ │ │ ├── main.js │ │ │ │ └── search.js │ │ ├── classes │ │ │ ├── _lib_fieldpart_.fieldpartgateway.html │ │ │ └── _lib_metaphor_.metaphor.html │ │ ├── globals.html │ │ ├── index.html │ │ ├── interfaces │ │ │ ├── _lib_fieldpart_.fieldpart.html │ │ │ └── _lib_metaphor_.idstoprocess.html │ │ └── modules │ │ │ ├── _index_.html │ │ │ ├── _lib_fieldpart_.html │ │ │ └── _lib_metaphor_.html │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ ├── FieldPart.ts │ │ │ └── Metaphor.ts │ └── tsconfig.json └── validators │ ├── README.md │ ├── babel.config.js │ ├── config │ └── webpack.config.js │ ├── package.json │ ├── src │ ├── ajv.ts │ └── index.ts │ └── tsconfig.json └── yarn.lock /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | access=public 2 | scope=@react-ui-generator 3 | 4 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v10.16.0 2 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | trailingComma: "es5" 2 | tabWidth: 2 3 | semi: false 4 | singleQuote: true 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 2 | Roadmap to v1.0: 3 | ✔ [core] Implement basic form generation. 4 | ✔ [core] Implement bootstrap renderers. 5 | ✔ [core] Implement nested forms (SubForm and List). 6 | ✔ [core] Implement basic validation. @done(17-02-19) 7 | ✔ [validators] Implement JSON Schema validator. (Ajv) @done(17-02-19) 8 | ✔ [bootstrap] Fix implicit type convertion for Select options. @done(18-02-19) 9 | ✔ [core] Implement validation for nested forms. @done(18-02-19) 10 | ✔ [bootstrap] Implement password option for Bootstrap Text renderer. @done(18-02-19 21:39) 11 | ✔ [bootstrap] Fix validation rendering for Bootstrap RadioGroup renderer. @done(18-02-19 21:58) 12 | ✔ Implement basic serializer. @done(18-02-19 22:39) 13 | ✔ Implement serializer for nested forms. @done(18-02-19 22:39) 14 | ✔ [core] Implement isValid argument for form's onChange callback. @done(18-02-19 22:46) 15 | ✔ [general] Split core to few more packages (validators, serializers). @done(18-02-23 09:38) 16 | ✔ [core] Consider moving `errors` into `data`. @done(18-03-01 09:15) -- rejected. Simple props are easy to undestand and manipulate. 17 | ✔ [core] Make `errors` and `validator` optional. @done(18-03-06 09:27) 18 | ✔ [general] Minimize bundle size @done(18-05-15 15:01) 19 | ✔ [antd] Implement AntD renderers. @done(18-05-27 17:44) 20 | ✔ [core] Add checking for `null` in serializer's `if (typeof value === 'object')`. @done(18-05-29 16:43) 21 | ✔ [core] Add `propTypes` and `defaultProps` for renderers @done(18-05-30 16:33) 22 | ✔ [core] Add unit-test for `@react-ui-generator/core` @done(18-06-10) 23 | ✔ [antd] Add support for `config.showAsterix` property for all fields, that can be marked as required. @done(18-08-14 14:34) 24 | ✔ [metaphor] Add initial test suite. @done(19-10-21 17:14) 25 | ✔ [core, metaphor] Replace Enzyme with React Testing Library @done(19-10-21 17:14) 26 | ☐ [core] Change default value of radiogroup field to `false` (instead of "") 27 | ☐ [antd] Add `config.icon` and `config.iconPosition = right | left` to the Button renderer 28 | ☐ [core] Consider adding of the children propagation to the `Field` renderer 29 | ☐ [bootstrap] Add `propTypes` and `defaultProps` for renderers 30 | ☐ Implement dirtify helper. 31 | ✔ Add basic russian documentation. @done(18-09-20) 32 | ☐ Add basic english documentation. 33 | ☐ Add typedocs 34 | ☐ Implement AntD validator. 35 | ☐ Implement LIVR validator. 36 | ☐ Implement async, non-blocking validation. 37 | ☐ Add complete russian documentation. 38 | ☐ Add complete english documentation. 39 | ☐ Add simple JSX-to-JSON convertor, for layout serialization. 40 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-dinky -------------------------------------------------------------------------------- /doc/img/demo-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/true-brains/react-ui-generator/6cda840f55111872b91333c26abb1c8e8c9f6879/doc/img/demo-screen.png -------------------------------------------------------------------------------- /doc/img/generator-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/true-brains/react-ui-generator/6cda840f55111872b91333c26abb1c8e8c9f6879/doc/img/generator-architecture.png -------------------------------------------------------------------------------- /doc/ru/antd renderers API.md: -------------------------------------------------------------------------------- 1 | # API отрисовщиков на базе библиотеки Ant Design 2 | 3 | ## Тип отрисовщика "text" 4 | 5 | Возможные настройки для данного типа отрисовщика: 6 | 7 | ```js 8 | { 9 | "type": "text", 10 | "config": { 11 | "label": "...", 12 | "placeholder": "...", 13 | "showAsterix": true|false, 14 | } 15 | } 16 | ``` 17 | 18 | ## Тип отрисовщика "textarea" 19 | 20 | Возможные настройки для данного типа отрисовщика: 21 | 22 | ```js 23 | { 24 | "type": "textarea", 25 | "config": { 26 | "label": "...", 27 | "placeholder": "...", 28 | "showAsterix": true|false, 29 | "rows": number 30 | } 31 | } 32 | ``` 33 | 34 | ## Тип отрисовщика "checkbox" 35 | 36 | Возможные настройки для данного типа отрисовщика: 37 | 38 | ```js 39 | { 40 | "type": "checkbox", 41 | "config": { 42 | "label": "...", 43 | "title": "...", 44 | "showAsterix": true|false, 45 | } 46 | } 47 | ``` 48 | 49 | ## Тип отрисовщика "radiogroup" 50 | 51 | Возможные настройки для данного типа отрисовщика: 52 | 53 | ```js 54 | { 55 | "type": "radiogroup", 56 | "config": { 57 | "label": "...", 58 | "showAsterix": true|false, 59 | "options": [ 60 | { "id": ..., "title": "..." }, 61 | ... 62 | ] 63 | } 64 | } 65 | ``` 66 | 67 | ## Тип отрисовщика "select" 68 | 69 | Возможные настройки для данного типа отрисовщика: 70 | 71 | ```js 72 | { 73 | "type": "select", 74 | "config": { 75 | "label": "...", 76 | "title": "...", 77 | "showAsterix": true|false, 78 | "options": [ 79 | { "id": ..., "title": "..." }, 80 | ... 81 | ] 82 | } 83 | } 84 | ``` 85 | 86 | ## Тип отрисовщика "button" 87 | 88 | Возможные настройки для данного типа отрисовщика: 89 | 90 | ```js 91 | { 92 | "type": "button", 93 | "config": { 94 | "title": "...", 95 | "color": "primary|ghost|dashed|danger", 96 | "outline": true|false 97 | } 98 | } 99 | ``` 100 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /doc/ru/metadata format.md: -------------------------------------------------------------------------------- 1 | # Формат метаданных 2 | 3 | Описание формы представляет собою объект, в корне которого всегда находится поле `fields`. 4 | 5 | ```js 6 | { 7 | "fields": [ ] 8 | } 9 | ``` 10 | 11 | ## Мета-описание отдельного поля 12 | 13 | Каждый элемент массива `field` содержит мета-описание одного поля формы. В своей минимальной конфигурации такое мета-описание выглядит следующим образом: 14 | 15 | ```js 16 | { 17 | "id": "%id поля%", 18 | } 19 | ``` 20 | 21 | Это эквивалентно следующему мета-описанию: 22 | 23 | ```js 24 | { 25 | "id": "%id поля%", 26 | "renderer": "text" 27 | } 28 | ``` 29 | 30 | где `renderer` - это тип [контрола](https://en.wikipedia.org/wiki/Widget_(GUI)), который будет использован для отрисовки данного поля на форме. 31 | 32 | 33 | ### Простейшая форма описания отрисовщика 34 | 35 | Поле `renderer` может содержать любое тип отрисовщика (*renderer*), однако такой тип должен быть определён в наборе отрисовщиков, подключенных к компоненту ``. Изначально `react-ui-generator` поддерживат два набора отрисовщиков: один на базе компонентов Reactstrap, и второй - на базе компонентов Ant Design. Для Ant Design доступны следующие типы отрисовщиков: 36 | 37 | ```js 38 | { 39 | "id": "%id поля%", 40 | "renderer": "text|textarea|checkbox|radiogroup|select|button|datepicker|upload" 41 | } 42 | ``` 43 | 44 | ### Расширенная форма описания отрисовщика 45 | 46 | Приведенный выше вариант мета-описания поля подходит только для самых простых случаев. Гораздо чаще отрисовщик нуждается в дополнительных настройках (напр. "цвет кнопки", "формат даты" etc). Для такого случая используется следующий формат мета-описания поля: 47 | 48 | ```js 49 | { 50 | "id": "%id поля%", 51 | "renderer": { 52 | "type": "%тип визуального представления поля%", 53 | "config": { 54 | "option1": "value1", 55 | ... 56 | "optionN": "valueN" 57 | } 58 | } 59 | } 60 | ``` 61 | 62 | Где содержимое секции `config` не является строго регламентированным и может содержать любые настройки, которые понимает/поддерживает данный отрисовщик. Конкретный набор настроек для того или иного отрисовщика содержится в описании API соответствующего набора компонентов. 63 | 64 | ### Видимость поля 65 | 66 | Любое поле формы может быть скрыто, независимо от выбранного типа отрисовщика. Решение об отображении/скрытии поля принимается до вызова отрисовщика поля, и в случае скрытия - такого вызова просто не происходит. Управляется видимость поля с помощью настройки `hidden`: 67 | 68 | ```js 69 | { 70 | "id": "%id поля%", 71 | "renderer": "" | { ... }, 72 | ... 73 | "hidden": true|false 74 | } 75 | ``` 76 | 77 | ### Выключенность поля 78 | 79 | Любое поле может быть выключено (*disabled*), независимо от выбранного типа отрисовщика. Здесь имеется ввиду, что никакие взаимодействия с данными полем (*actions*) не будут обрабатьываться компонентом ``. Однако, т.к. визуализация поля в выключенном состоянии - это ответственность отрисовщика данного типа поля, то нет возможности гарантировать на уровне компонента `` что такая визуализация будет всегда корректной. Например, если автор отрисовщика забудет предусмотреть визуализацию состояния `disabled`, то поле будет выглядеть как активное. 80 | 81 | Управляется состояние выключенности с помощью настройки `disabled`: 82 | 83 | ```js 84 | { 85 | "id": "%id поля%", 86 | "renderer": "" | { ... }, 87 | ... 88 | "disabled": true|false 89 | } 90 | ``` 91 | 92 | ### Настройка взаимодействия с полем (actions) 93 | 94 | Мета-описание формы в `react-ui-generator` является декларативным. Таким образом, не предполагается возможности описания логики полей прямо в метаданных. Вместо этого есть возможность указать, какие функции из коллекции функций, подключенных к `` должны вызываться при срабатывании того или иного события компонента: 95 | 96 | ```js 97 | { 98 | "id": "%id поля%", 99 | "renderer": "button", 100 | ... 101 | "actions": { 102 | "onClick": "sendForm" 103 | } 104 | } 105 | ``` 106 | 107 | При этом, к компоненту `` должна быть подключена коллекция функций, содержащая функцию с именем "sendForm": 108 | 109 | ```js 110 | const actions = { 111 | sendForm: (event) => { ... }, 112 | ... 113 | }; 114 | 115 | ... 116 | 117 | 118 | ``` 119 | 120 | ### Полный пример 121 | 122 | Итого, метаописание всей формы будет иметь слелующую структуру: 123 | 124 | ```js 125 | { 126 | "fields": [ 127 | { 128 | "id": "%id поля%", 129 | "renderer": { 130 | "type": "%тип визуального представления поля%", 131 | "config": { 132 | "option1": "value1", 133 | ... 134 | "optionN": "valueN" 135 | } 136 | } 137 | }, 138 | "hidden": true|false, 139 | "disabled": true|false, 140 | "serializer": "some.nested.field", 141 | "actions": { 142 | "onClick": "...", 143 | "onKeyUp": "...", 144 | ... 145 | } 146 | ] 147 | } 148 | ``` 149 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/antd/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: 'antd.js', 11 | path: resolve('out'), 12 | libraryTarget: 'umd' 13 | }, 14 | 15 | // Enable sourcemaps for debugging webpack's output. 16 | devtool: 'source-map', 17 | mode: 'production', 18 | 19 | resolve: { 20 | extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'], 21 | modules: [resolve('src'), 'node_modules'], 22 | plugins: [new TsConfigPathsPlugin({})] 23 | }, 24 | 25 | module: { 26 | rules: [ 27 | // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'. 28 | { 29 | test: /\.tsx?$/, 30 | use: [ 31 | { 32 | loader: 'babel-loader', 33 | options: { 34 | presets: ['@babel/preset-env', '@babel/preset-react'] 35 | } 36 | }, 37 | 'awesome-typescript-loader' 38 | ] 39 | }, 40 | 41 | // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. 42 | { 43 | enforce: 'pre', 44 | test: /\.js$/, 45 | loader: 'source-map-loader' 46 | } 47 | ] 48 | }, 49 | 50 | externals: [ 51 | { 52 | react: { 53 | root: 'React', 54 | commonjs2: 'react', 55 | commonjs: 'react', 56 | amd: 'react' 57 | } 58 | }, 59 | 'moment', 60 | '@react-ui-generator/core', 61 | /^antd\/.+$/, 62 | /^lodash-es(\/.+)?$/ 63 | ], 64 | plugins: [new CleanWebpackPlugin()] 65 | }; 66 | -------------------------------------------------------------------------------- /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/antd/src/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as Renderers, FieldWrapper } from './renderers'; 2 | export { default as Layouts } from './layouts'; 3 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/antd/src/renderers/CheckBox.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Checkbox, { CheckboxChangeEvent } from 'antd/lib/checkbox'; 3 | import PropTypes from 'prop-types' 4 | 5 | import { FieldRenderer, basePropTypes } from '@react-ui-generator/core'; 6 | import { FieldWrapper } from './FieldWrapper'; 7 | 8 | const value: boolean = null; 9 | 10 | export class _Checkbox extends FieldRenderer { 11 | static propTypes = { 12 | ...basePropTypes(), 13 | config: PropTypes.shape({ 14 | label: PropTypes.string, 15 | title: PropTypes.string, 16 | showAsterix: PropTypes.bool 17 | }) 18 | }; 19 | 20 | static defaultProps = { 21 | className: '', 22 | disabled: false, 23 | drity: false, 24 | config: { 25 | label: '', 26 | title: '', 27 | showAsterix: false 28 | }, 29 | data: value 30 | }; 31 | 32 | handleChange = (event: CheckboxChangeEvent): void => { 33 | this.props.onChange(event.target.checked); 34 | }; 35 | 36 | render() { 37 | const { 38 | id, 39 | data, 40 | className, 41 | onChange, 42 | config: { label, title, showAsterix }, 43 | disabled, 44 | ...rest 45 | } = this.props; 46 | 47 | const value: boolean = Boolean(data); 48 | const _label = label || (id.length ? id.charAt(0).toUpperCase() + id.slice(1) : ''); 49 | 50 | return ( 51 | 52 | 58 | {title} 59 | 60 | 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/antd/src/renderers/DatePicker.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import DatePicker from 'antd/lib/date-picker'; 3 | import moment, { Moment } from 'moment'; 4 | import PropTypes from 'prop-types' 5 | 6 | import { FieldRenderer, basePropTypes } from '@react-ui-generator/core'; 7 | 8 | import { FieldWrapper } from './FieldWrapper'; 9 | 10 | const value: string = null; 11 | 12 | export class _DatePicker extends FieldRenderer { 13 | static propTypes = { 14 | ...basePropTypes(), 15 | config: PropTypes.shape({ 16 | label: PropTypes.string, 17 | placeholder: PropTypes.string, 18 | showAsterix: PropTypes.bool, 19 | format: PropTypes.string 20 | }) 21 | }; 22 | 23 | static defaultProps = { 24 | className: '', 25 | disabled: false, 26 | dirty: false, 27 | config: { 28 | label: '', 29 | placeholder: '', 30 | showAsterix: false, 31 | format: 'DD.MM.YYYY' 32 | }, 33 | data: value 34 | }; 35 | 36 | handleChange = (date: Moment, dateString: String): void => { 37 | this.props.onChange(dateString); 38 | }; 39 | 40 | render() { 41 | const { 42 | id, 43 | data, 44 | className, 45 | onChange, 46 | config: { label, placeholder, showAsterix, format }, 47 | disabled, 48 | ...rest 49 | } = this.props; 50 | const _format = format || 'YYYY-MM-DD'; 51 | const momentValue: Moment = data ? moment(String(data), _format) : undefined; 52 | 53 | return ( 54 | 55 | 64 | 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /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/antd/src/renderers/Input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ChangeEvent } from 'react'; 3 | import Input from 'antd/lib/input'; 4 | import PropTypes from 'prop-types' 5 | 6 | import { 7 | FieldRendererProps, 8 | FieldRenderer, 9 | basePropTypes 10 | } from '@react-ui-generator/core'; 11 | 12 | import { FieldWrapper } from './FieldWrapper'; 13 | 14 | export interface InputProps extends FieldRendererProps { 15 | type?: string; 16 | } 17 | 18 | const value: string = ''; 19 | 20 | export class _Input extends FieldRenderer { 21 | static propTypes = { 22 | ...basePropTypes(), 23 | type: PropTypes.string, 24 | config: PropTypes.shape({ 25 | label: PropTypes.string, 26 | placeholder: PropTypes.string, 27 | showAsterix: PropTypes.bool 28 | }) 29 | }; 30 | 31 | static defaultProps = { 32 | type: 'text', 33 | className: '', 34 | disabled: false, 35 | dirty: false, 36 | config: { 37 | label: '', 38 | placeholder: '', 39 | showAsterix: false 40 | }, 41 | data: value 42 | }; 43 | 44 | handleChange(event: ChangeEvent): void { 45 | this.props.onChange(event.target.value); 46 | } 47 | 48 | render() { 49 | const { 50 | type, 51 | id, 52 | data, 53 | className, 54 | onChange, 55 | config: { label, showAsterix, placeholder }, 56 | disabled, 57 | ...rest 58 | } = this.props; 59 | const value: string = String(data); 60 | 61 | return ( 62 | 63 | { 70 | this.handleChange(event); 71 | }} 72 | disabled={disabled} 73 | /> 74 | 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/antd/src/renderers/MultipleSelect.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Select, { SelectValue } from 'antd/lib/select'; 3 | import PropTypes from 'prop-types' 4 | import { 5 | FieldRenderer, 6 | basePropTypes 7 | } from '@react-ui-generator/core'; 8 | 9 | import { FieldWrapper } from './FieldWrapper'; 10 | 11 | const { Option } = Select; 12 | 13 | export type MultipleSelectValue = string[] | number[]; 14 | 15 | export interface SelectItemProps { 16 | id: string | number; 17 | title: string; 18 | } 19 | 20 | const value: SelectValue = undefined; 21 | const options: SelectItemProps[] = []; 22 | 23 | export class MultipleSelect extends FieldRenderer { 24 | static propTypes = { 25 | ...basePropTypes(), 26 | config: PropTypes.shape({ 27 | label: PropTypes.string, 28 | placeholder: PropTypes.string, 29 | showAsterix: PropTypes.bool, 30 | options: PropTypes.arrayOf( 31 | PropTypes.shape({ 32 | id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 33 | title: PropTypes.string 34 | }) 35 | ) 36 | }) 37 | }; 38 | 39 | static defaultProps = { 40 | className: '', 41 | disabled: false, 42 | dirty: false, 43 | config: { 44 | label: '', 45 | placeholder: '', 46 | showAsterix: false, 47 | options 48 | }, 49 | data: value, 50 | }; 51 | 52 | handleChange = (value: MultipleSelectValue): void => { 53 | this.props.onChange(value); 54 | }; 55 | 56 | render() { 57 | const { 58 | id, 59 | config: { label, placeholder, showAsterix, options, allowClear }, 60 | data, 61 | disabled, 62 | className, 63 | onChange, 64 | ...rest 65 | } = this.props; 66 | 67 | let value: SelectValue; 68 | 69 | if (typeof data === 'boolean') { 70 | value = Number(data); 71 | } else if (Array.isArray(data) && (data.length === 0)) { 72 | value = undefined; 73 | } else { 74 | value = data; 75 | } 76 | 77 | return ( 78 | 79 | 95 | 96 | ); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /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/antd/src/renderers/RadioGroup.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types' 3 | import Radio from 'antd/lib/radio'; 4 | import { FieldRenderer, basePropTypes } from '@react-ui-generator/core'; 5 | import { FieldWrapper } from './FieldWrapper'; 6 | 7 | const RadioGroup = Radio.Group; 8 | 9 | export interface RadiogroupItem { 10 | id: string | number; 11 | title: string; 12 | } 13 | 14 | const value: string | number = null; 15 | const options: RadiogroupItem[] = []; 16 | 17 | export class _RadioGroup extends FieldRenderer { 18 | static propTypes = { 19 | ...basePropTypes(), 20 | config: PropTypes.shape({ 21 | label: PropTypes.string, 22 | showAsterix: PropTypes.bool, 23 | options: PropTypes.arrayOf( 24 | PropTypes.shape({ 25 | id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 26 | title: PropTypes.string 27 | }) 28 | ) 29 | }) 30 | }; 31 | 32 | static defaultProps = { 33 | className: '', 34 | disabled: false, 35 | dirty: false, 36 | config: { 37 | label: '', 38 | showAsterix: false, 39 | options 40 | }, 41 | data: value 42 | }; 43 | 44 | handleChange(value: string): void { 45 | this.props.onChange(value); 46 | } 47 | 48 | render() { 49 | const { 50 | id, 51 | data, 52 | className, 53 | onChange, 54 | config: { label, showAsterix, options }, 55 | disabled, 56 | ...rest 57 | } = this.props; 58 | 59 | return ( 60 | 61 | { 65 | this.handleChange(event.target.value); 66 | }} 67 | > 68 | {options.map((item: RadiogroupItem, idx: number) => ( 69 | 70 | {item.title} 71 | 72 | ))} 73 | 74 | 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/antd/src/renderers/Select.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import PropTypes from 'prop-types' 3 | import Select, { SelectValue } from 'antd/lib/select'; 4 | import { FieldRenderer, basePropTypes } from '@react-ui-generator/core'; 5 | 6 | import { FieldWrapper } from './FieldWrapper'; 7 | 8 | const { Option } = Select; 9 | 10 | export interface SelectItemProps { 11 | id: string | number; 12 | title: string; 13 | } 14 | 15 | const value: SelectValue = undefined; 16 | const options: SelectItemProps[] = []; 17 | 18 | export class _Select extends FieldRenderer { 19 | static propTypes = { 20 | ...basePropTypes(), 21 | config: PropTypes.shape({ 22 | label: PropTypes.string, 23 | placeholder: PropTypes.string, 24 | showAsterix: PropTypes.bool, 25 | options: PropTypes.arrayOf( 26 | PropTypes.shape({ 27 | id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 28 | title: PropTypes.string 29 | }) 30 | ) 31 | }) 32 | }; 33 | 34 | static defaultProps = { 35 | className: '', 36 | disabled: false, 37 | dirty: false, 38 | config: { 39 | label: '', 40 | placeholder: '', 41 | showAsterix: false, 42 | options 43 | }, 44 | data: value 45 | }; 46 | 47 | handleChange = (value: SelectValue): void => { 48 | this.props.onChange(value); 49 | }; 50 | 51 | render() { 52 | const { 53 | id, 54 | config: { label, placeholder, showAsterix, title, options, allowClear }, 55 | data, 56 | disabled, 57 | className, 58 | onChange, 59 | ...rest 60 | } = this.props; 61 | 62 | let value: SelectValue; 63 | 64 | if (typeof data === 'boolean') { 65 | value = Number(data); 66 | } else if (data === null) { 67 | value = undefined; 68 | } else { 69 | value = data; 70 | } 71 | 72 | return ( 73 | 74 | 89 | 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /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/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 |