├── .gitignore ├── README.md ├── examples ├── file-upload-server │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── server.js │ └── static │ │ └── uploader-example.png ├── with-formik │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ ├── src │ │ ├── App.css │ │ ├── App.test.tsx │ │ ├── App.tsx │ │ ├── form.json │ │ ├── index.css │ │ ├── index.tsx │ │ ├── logo.svg │ │ ├── react-app-env.d.ts │ │ ├── reportWebVitals.ts │ │ ├── setupTests.ts │ │ ├── useBookingForm.ts │ │ └── validators.ts │ └── tsconfig.json ├── with-nextjs │ └── with-nextjs-v15 │ │ ├── .eslintrc.json │ │ ├── .gitignore │ │ ├── README.md │ │ ├── next.config.ts │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ ├── file.svg │ │ └── globe.svg │ │ ├── src │ │ └── app │ │ │ ├── builder │ │ │ └── page.tsx │ │ │ ├── common │ │ │ ├── actions.ts │ │ │ ├── form.json │ │ │ ├── styles.module.css │ │ │ └── validators.ts │ │ │ ├── components │ │ │ ├── message.module.css │ │ │ └── message.tsx │ │ │ ├── favicon.ico │ │ │ ├── fonts │ │ │ ├── GeistMonoVF.woff │ │ │ └── GeistVF.woff │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ └── viewer │ │ │ └── page.tsx │ │ └── tsconfig.json └── with-remix │ └── with-remix-v2 │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── README.md │ ├── app │ ├── common │ │ ├── actions.ts │ │ ├── form.json │ │ ├── styles.module.css │ │ └── validators.ts │ ├── components │ │ ├── builder.client.tsx │ │ ├── message.module.css │ │ ├── message.tsx │ │ └── viewer.client.tsx │ ├── entry.client.tsx │ ├── entry.server.tsx │ ├── root.tsx │ ├── routes │ │ ├── _index.tsx │ │ ├── builder._index.tsx │ │ └── viewer._index.tsx │ └── tailwind.css │ ├── package-lock.json │ ├── package.json │ ├── postcss.config.js │ ├── public │ ├── favicon.ico │ ├── logo-dark.svg │ └── logo-light.svg │ ├── tailwind.config.ts │ ├── tsconfig.json │ └── vite.config.ts ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── form-builder.png ├── form-viewer.png ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── screenshots ├── builder.png └── viewer.png ├── src ├── ErrorPage.tsx ├── components │ ├── FormBuilderExample.tsx │ ├── FormViewerExample.tsx │ ├── Main.tsx │ └── SampleForm.json ├── index.css ├── index.tsx ├── react-app-env.d.ts ├── reportWebVitals.ts ├── routes │ └── root.tsx └── setupTests.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Form Builder example application 2 | 3 | React Form Builder is a Drag & Drop Form Builder library for React. 4 | 5 | Develop front-end drag and drop forms with ease, resulting in cost savings and reduced development timing. 6 | 7 | [Documentation website](https://formengine.io/documentation) 8 | 9 | ## Packages 10 | 11 | The product consists of several packages, some packages are distributed under the MIT license, some under the commercial license. 12 | 13 | Important: Some features and modules (e.g., Form Designer) are only available under a commercial license. 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 33 | 34 | 35 | 36 | 39 | 44 | 45 | 46 | 47 | 50 | 55 | 58 | 59 | 60 | 63 | 68 | 71 | 72 | 73 | 76 | 81 | 84 | 85 | 86 | 89 | 95 | 98 | 99 | 100 | 103 | 110 | 113 | 114 | 115 | 118 | 123 | 126 | 127 | 128 |
PackageBadgesDescription
26 | @react-form-builder/core 27 | 29 | 30 | npm @react-form-builder/core 31 | license @react-form-builder/core 32 | The main package responsible for rendering forms.
37 | @react-form-builder/components-rsuite 38 | 40 | 41 | npm @react-form-builder/components-rsuite 42 | license @react-form-builder/components-rsuite 43 | The package with visual components based on React Suite.
48 | @react-form-builder/viewer-bundle 49 | 51 | 52 | npm @react-form-builder/viewer-bundle 53 | license @react-form-builder/viewer-bundle 54 | The @react-form-builder/core and @react-form-builder/components-rsuite packages built for use on an HTML 56 | page without React. 57 |
61 | @react-form-builder/designer 62 | 64 | 65 | npm @react-form-builder/designer 66 | license @react-form-builder/designer 67 | 69 | This package contains a visual editor for React components. 70 |
74 | @react-form-builder/designer-bundle 75 | 77 | 78 | npm @react-form-builder/designer-bundle 79 | license @react-form-builder/designer-bundle 80 | The @react-form-builder/designer and @react-form-builder/components-rsuite packages built for use on an 82 | HTML page without React. 83 |
87 | @react-form-builder/components-rich-text 88 | 90 | 91 | npm @react-form-builder/components-rich-text 92 | license @react-form-builder/components-rich-text 94 | 96 | This package contains visual components based on React Quill. 97 |
101 | @react-form-builder/components-google-map 102 | 104 | 105 | npm @react-form-builder/components-google-map 107 | license @react-form-builder/components-google-map 109 | 111 | The package contains visual components for Google Maps integration. 112 |
116 | @react-form-builder/components-fast-qr 117 | 119 | 120 | npm @react-form-builder/components-fast-qr 121 | license @react-form-builder/components-fast-qr 122 | 124 | The package contains visual components based on Fast QR. 125 |
129 | 130 | ## How to start 131 | 132 | ```bash 133 | npm install 134 | npm run start 135 | ``` 136 | 137 | ## Key Features 138 | 139 | - **UI-Agnostic Components:** Works seamlessly with any UI 140 | library ([MUI](https://mui.com/), [Ant Design](https://ant.design/), [shadcn/ui](https://ui.shadcn.com/) 141 | and [others](https://formengine.io/documentation/custom-components)) 142 | - **Pre-Built React Suite Integration:** Includes a ready-to-use component 143 | library – [@react-form-builder/components-rsuite](https://www.npmjs.com/package/@react-form-builder/components-rsuite). 144 | - Framework Support: 145 | - **Next.js Integration**: Seamlessly works with [Next.js](https://formengine.io/documentation/usage-with-nextjs). 146 | - **Remix Compatibility**: Fully supports [Remix](https://formengine.io/documentation/usage-with-remix). 147 | - **Framework-Agnostic**: Can also be used [without any framework](https://formengine.io/documentation/installation#cdn) via CDN. 148 | - **Multi-Database Support:** Compatible with MySQL, PostgreSQL, MongoDB, SQLite, and more. 149 | - **Built-in Validation with Zod:** Includes pre-configured validation rules powered by [Zod](https://github.com/colinhacks/zod). 150 | - **Extensible Validation Support:** Works 151 | with [Yup](https://github.com/jquense/yup), [AJV](https://github.com/ajv-validator/ajv), [Zod](https://github.com/colinhacks/zod), 152 | [Superstruct](https://github.com/ianstormtaylor/superstruct), 153 | [Joi](https://github.com/hapijs/joi), and other custom validation libraries. 154 | - **Responsive Layouts**: Build forms that automatically [adapt](https://formengine.io/documentation/adaptive-layout) to all screen sizes. 155 | - **Custom Actions**: Enhance forms with interactive logic through [custom JavaScript code](https://formengine.io/documentation/actions). 156 | - **Dynamic Properties**: Implement real-time component changes with [MobX](https://github.com/mobxjs/mobx)-powered reactive properties. 157 | - **Flexible Storage Options**: 158 | - Store complete form definitions as JSON. 159 | - Programmatically generate forms [via code](https://formengine.io/documentation/building-forms-via-code). 160 | 161 | ## Screenshots 162 | 163 | [![Form Builder](./screenshots/builder.png "Form Builder")](https://demo.formengine.io) 164 | 165 | [![Form Viewer](./screenshots/viewer.png "Form Viewer")](https://demo.formengine.io) 166 | 167 | ## Information 168 | 169 | - Website - [formengine.io](https://formengine.io). 170 | - Demo - [demo.formengine.io](https://demo.formengine.io). 171 | - Documentation - [formengine.io/documentation](https://formengine.io/documentation). 172 | 173 | For commercial use, please contact [sales@optimajet.com](mailto:sales@optimajet.com). 174 | -------------------------------------------------------------------------------- /examples/file-upload-server/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | uploads 5 | .env 6 | -------------------------------------------------------------------------------- /examples/file-upload-server/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to the File Uploader Server! 2 | 3 | This project is a simple Node.js server built with `express`, `multer`, and `cors` to handle file uploads and serve static files. 4 | 5 | ## 📖 Features 6 | 7 | - Handles file uploads with unique filenames to avoid overwriting. 8 | - Serves uploaded files for download via a static endpoint. 9 | - Uses `cors` to allow cross-origin requests. 10 | 11 | ## Installation 12 | 13 | 1. Navigate to the project directory: 14 | ```file-upload-server``` 15 | 2. Install dependencies: 16 | ```npm install``` 17 | 18 | ## Development 19 | 20 | Run the server: 21 | 22 | ``` 23 | npm run start 24 | ``` 25 | 26 | By default, the server runs on http://localhost:3001. You can upload files via the `/upload` endpoint and access uploaded files via 27 | the `/uploads` endpoint. 28 | 29 | ## Endpoints 30 | 31 | ### Upload Files 32 | 33 | **POST** `/upload` 34 | 35 | - Accepts a single file in the `file` field of the request. 36 | - Returns the uploaded file's public path on success. 37 | 38 | Example Response: 39 | 40 | ```json 41 | { 42 | "success": true, 43 | "message": "File uploaded successfully", 44 | "filePath": "/uploads/" 45 | } 46 | ``` 47 | 48 | ### Access Uploaded Files 49 | 50 | **GET** `/uploads/` 51 | 52 | - Serves static files from the `uploads` directory. 53 | 54 | You can now use the server to handle file uploads and serve them to clients. 55 | 56 | ### 📦 Use Example 57 | 58 | You can integrate the uploader server using the **Uploader** component. 59 | For detailed documentation, refer to 60 | the [Uploader Documentation](https://formengine.io/documentation/components-library/fields-components/uploader). 61 | 62 | 1. **Set the Action URL:** 63 | In the component's `Action URL` property, provide the URL to your server's upload endpoint. 64 | Example: http://localhost:3001/upload 65 | 66 | 2. **Upload Workflow:** 67 | The component will send the file to the server via the specified `Action URL`. 68 | 69 | ![Uploader Example](./static/uploader-example.png) 70 | -------------------------------------------------------------------------------- /examples/file-upload-server/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "file-uploader-server", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "file-uploader-server", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "cors": "^2.8.5", 13 | "express": "^4.21.2", 14 | "multer": "^2.0.0" 15 | } 16 | }, 17 | "node_modules/accepts": { 18 | "version": "1.3.8", 19 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 20 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 21 | "license": "MIT", 22 | "dependencies": { 23 | "mime-types": "~2.1.34", 24 | "negotiator": "0.6.3" 25 | }, 26 | "engines": { 27 | "node": ">= 0.6" 28 | } 29 | }, 30 | "node_modules/append-field": { 31 | "version": "1.0.0", 32 | "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", 33 | "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", 34 | "license": "MIT" 35 | }, 36 | "node_modules/array-flatten": { 37 | "version": "1.1.1", 38 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 39 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", 40 | "license": "MIT" 41 | }, 42 | "node_modules/body-parser": { 43 | "version": "1.20.3", 44 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", 45 | "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", 46 | "license": "MIT", 47 | "dependencies": { 48 | "bytes": "3.1.2", 49 | "content-type": "~1.0.5", 50 | "debug": "2.6.9", 51 | "depd": "2.0.0", 52 | "destroy": "1.2.0", 53 | "http-errors": "2.0.0", 54 | "iconv-lite": "0.4.24", 55 | "on-finished": "2.4.1", 56 | "qs": "6.13.0", 57 | "raw-body": "2.5.2", 58 | "type-is": "~1.6.18", 59 | "unpipe": "1.0.0" 60 | }, 61 | "engines": { 62 | "node": ">= 0.8", 63 | "npm": "1.2.8000 || >= 1.4.16" 64 | } 65 | }, 66 | "node_modules/buffer-from": { 67 | "version": "1.1.2", 68 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", 69 | "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", 70 | "license": "MIT" 71 | }, 72 | "node_modules/busboy": { 73 | "version": "1.6.0", 74 | "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", 75 | "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", 76 | "dependencies": { 77 | "streamsearch": "^1.1.0" 78 | }, 79 | "engines": { 80 | "node": ">=10.16.0" 81 | } 82 | }, 83 | "node_modules/bytes": { 84 | "version": "3.1.2", 85 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 86 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 87 | "license": "MIT", 88 | "engines": { 89 | "node": ">= 0.8" 90 | } 91 | }, 92 | "node_modules/call-bind-apply-helpers": { 93 | "version": "1.0.1", 94 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", 95 | "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", 96 | "license": "MIT", 97 | "dependencies": { 98 | "es-errors": "^1.3.0", 99 | "function-bind": "^1.1.2" 100 | }, 101 | "engines": { 102 | "node": ">= 0.4" 103 | } 104 | }, 105 | "node_modules/call-bound": { 106 | "version": "1.0.3", 107 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", 108 | "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", 109 | "license": "MIT", 110 | "dependencies": { 111 | "call-bind-apply-helpers": "^1.0.1", 112 | "get-intrinsic": "^1.2.6" 113 | }, 114 | "engines": { 115 | "node": ">= 0.4" 116 | }, 117 | "funding": { 118 | "url": "https://github.com/sponsors/ljharb" 119 | } 120 | }, 121 | "node_modules/concat-stream": { 122 | "version": "1.6.2", 123 | "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", 124 | "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", 125 | "engines": [ 126 | "node >= 0.8" 127 | ], 128 | "license": "MIT", 129 | "dependencies": { 130 | "buffer-from": "^1.0.0", 131 | "inherits": "^2.0.3", 132 | "readable-stream": "^2.2.2", 133 | "typedarray": "^0.0.6" 134 | } 135 | }, 136 | "node_modules/content-disposition": { 137 | "version": "0.5.4", 138 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 139 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 140 | "license": "MIT", 141 | "dependencies": { 142 | "safe-buffer": "5.2.1" 143 | }, 144 | "engines": { 145 | "node": ">= 0.6" 146 | } 147 | }, 148 | "node_modules/content-type": { 149 | "version": "1.0.5", 150 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 151 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 152 | "license": "MIT", 153 | "engines": { 154 | "node": ">= 0.6" 155 | } 156 | }, 157 | "node_modules/cookie": { 158 | "version": "0.7.1", 159 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", 160 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", 161 | "license": "MIT", 162 | "engines": { 163 | "node": ">= 0.6" 164 | } 165 | }, 166 | "node_modules/cookie-signature": { 167 | "version": "1.0.6", 168 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 169 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", 170 | "license": "MIT" 171 | }, 172 | "node_modules/core-util-is": { 173 | "version": "1.0.3", 174 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", 175 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", 176 | "license": "MIT" 177 | }, 178 | "node_modules/cors": { 179 | "version": "2.8.5", 180 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 181 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 182 | "license": "MIT", 183 | "dependencies": { 184 | "object-assign": "^4", 185 | "vary": "^1" 186 | }, 187 | "engines": { 188 | "node": ">= 0.10" 189 | } 190 | }, 191 | "node_modules/debug": { 192 | "version": "2.6.9", 193 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 194 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 195 | "license": "MIT", 196 | "dependencies": { 197 | "ms": "2.0.0" 198 | } 199 | }, 200 | "node_modules/depd": { 201 | "version": "2.0.0", 202 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 203 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 204 | "license": "MIT", 205 | "engines": { 206 | "node": ">= 0.8" 207 | } 208 | }, 209 | "node_modules/destroy": { 210 | "version": "1.2.0", 211 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 212 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 213 | "license": "MIT", 214 | "engines": { 215 | "node": ">= 0.8", 216 | "npm": "1.2.8000 || >= 1.4.16" 217 | } 218 | }, 219 | "node_modules/dunder-proto": { 220 | "version": "1.0.1", 221 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 222 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 223 | "license": "MIT", 224 | "dependencies": { 225 | "call-bind-apply-helpers": "^1.0.1", 226 | "es-errors": "^1.3.0", 227 | "gopd": "^1.2.0" 228 | }, 229 | "engines": { 230 | "node": ">= 0.4" 231 | } 232 | }, 233 | "node_modules/ee-first": { 234 | "version": "1.1.1", 235 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 236 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 237 | "license": "MIT" 238 | }, 239 | "node_modules/encodeurl": { 240 | "version": "2.0.0", 241 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 242 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 243 | "license": "MIT", 244 | "engines": { 245 | "node": ">= 0.8" 246 | } 247 | }, 248 | "node_modules/es-define-property": { 249 | "version": "1.0.1", 250 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 251 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 252 | "license": "MIT", 253 | "engines": { 254 | "node": ">= 0.4" 255 | } 256 | }, 257 | "node_modules/es-errors": { 258 | "version": "1.3.0", 259 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 260 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 261 | "license": "MIT", 262 | "engines": { 263 | "node": ">= 0.4" 264 | } 265 | }, 266 | "node_modules/es-object-atoms": { 267 | "version": "1.0.1", 268 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.1.tgz", 269 | "integrity": "sha512-BPOBuyUF9QIVhuNLhbToCLHP6+0MHwZ7xLBkPPCZqK4JmpJgGnv10035STzzQwFpqdzNFMB3irvDI63IagvDwA==", 270 | "license": "MIT", 271 | "dependencies": { 272 | "es-errors": "^1.3.0" 273 | }, 274 | "engines": { 275 | "node": ">= 0.4" 276 | } 277 | }, 278 | "node_modules/escape-html": { 279 | "version": "1.0.3", 280 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 281 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 282 | "license": "MIT" 283 | }, 284 | "node_modules/etag": { 285 | "version": "1.8.1", 286 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 287 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 288 | "license": "MIT", 289 | "engines": { 290 | "node": ">= 0.6" 291 | } 292 | }, 293 | "node_modules/express": { 294 | "version": "4.21.2", 295 | "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", 296 | "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", 297 | "license": "MIT", 298 | "dependencies": { 299 | "accepts": "~1.3.8", 300 | "array-flatten": "1.1.1", 301 | "body-parser": "1.20.3", 302 | "content-disposition": "0.5.4", 303 | "content-type": "~1.0.4", 304 | "cookie": "0.7.1", 305 | "cookie-signature": "1.0.6", 306 | "debug": "2.6.9", 307 | "depd": "2.0.0", 308 | "encodeurl": "~2.0.0", 309 | "escape-html": "~1.0.3", 310 | "etag": "~1.8.1", 311 | "finalhandler": "1.3.1", 312 | "fresh": "0.5.2", 313 | "http-errors": "2.0.0", 314 | "merge-descriptors": "1.0.3", 315 | "methods": "~1.1.2", 316 | "on-finished": "2.4.1", 317 | "parseurl": "~1.3.3", 318 | "path-to-regexp": "0.1.12", 319 | "proxy-addr": "~2.0.7", 320 | "qs": "6.13.0", 321 | "range-parser": "~1.2.1", 322 | "safe-buffer": "5.2.1", 323 | "send": "0.19.0", 324 | "serve-static": "1.16.2", 325 | "setprototypeof": "1.2.0", 326 | "statuses": "2.0.1", 327 | "type-is": "~1.6.18", 328 | "utils-merge": "1.0.1", 329 | "vary": "~1.1.2" 330 | }, 331 | "engines": { 332 | "node": ">= 0.10.0" 333 | }, 334 | "funding": { 335 | "type": "opencollective", 336 | "url": "https://opencollective.com/express" 337 | } 338 | }, 339 | "node_modules/finalhandler": { 340 | "version": "1.3.1", 341 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", 342 | "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", 343 | "license": "MIT", 344 | "dependencies": { 345 | "debug": "2.6.9", 346 | "encodeurl": "~2.0.0", 347 | "escape-html": "~1.0.3", 348 | "on-finished": "2.4.1", 349 | "parseurl": "~1.3.3", 350 | "statuses": "2.0.1", 351 | "unpipe": "~1.0.0" 352 | }, 353 | "engines": { 354 | "node": ">= 0.8" 355 | } 356 | }, 357 | "node_modules/forwarded": { 358 | "version": "0.2.0", 359 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 360 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 361 | "license": "MIT", 362 | "engines": { 363 | "node": ">= 0.6" 364 | } 365 | }, 366 | "node_modules/fresh": { 367 | "version": "0.5.2", 368 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 369 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 370 | "license": "MIT", 371 | "engines": { 372 | "node": ">= 0.6" 373 | } 374 | }, 375 | "node_modules/function-bind": { 376 | "version": "1.1.2", 377 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 378 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 379 | "license": "MIT", 380 | "funding": { 381 | "url": "https://github.com/sponsors/ljharb" 382 | } 383 | }, 384 | "node_modules/get-intrinsic": { 385 | "version": "1.2.7", 386 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", 387 | "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", 388 | "license": "MIT", 389 | "dependencies": { 390 | "call-bind-apply-helpers": "^1.0.1", 391 | "es-define-property": "^1.0.1", 392 | "es-errors": "^1.3.0", 393 | "es-object-atoms": "^1.0.0", 394 | "function-bind": "^1.1.2", 395 | "get-proto": "^1.0.0", 396 | "gopd": "^1.2.0", 397 | "has-symbols": "^1.1.0", 398 | "hasown": "^2.0.2", 399 | "math-intrinsics": "^1.1.0" 400 | }, 401 | "engines": { 402 | "node": ">= 0.4" 403 | }, 404 | "funding": { 405 | "url": "https://github.com/sponsors/ljharb" 406 | } 407 | }, 408 | "node_modules/get-proto": { 409 | "version": "1.0.1", 410 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 411 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 412 | "license": "MIT", 413 | "dependencies": { 414 | "dunder-proto": "^1.0.1", 415 | "es-object-atoms": "^1.0.0" 416 | }, 417 | "engines": { 418 | "node": ">= 0.4" 419 | } 420 | }, 421 | "node_modules/gopd": { 422 | "version": "1.2.0", 423 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 424 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 425 | "license": "MIT", 426 | "engines": { 427 | "node": ">= 0.4" 428 | }, 429 | "funding": { 430 | "url": "https://github.com/sponsors/ljharb" 431 | } 432 | }, 433 | "node_modules/has-symbols": { 434 | "version": "1.1.0", 435 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 436 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 437 | "license": "MIT", 438 | "engines": { 439 | "node": ">= 0.4" 440 | }, 441 | "funding": { 442 | "url": "https://github.com/sponsors/ljharb" 443 | } 444 | }, 445 | "node_modules/hasown": { 446 | "version": "2.0.2", 447 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 448 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 449 | "license": "MIT", 450 | "dependencies": { 451 | "function-bind": "^1.1.2" 452 | }, 453 | "engines": { 454 | "node": ">= 0.4" 455 | } 456 | }, 457 | "node_modules/http-errors": { 458 | "version": "2.0.0", 459 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 460 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 461 | "license": "MIT", 462 | "dependencies": { 463 | "depd": "2.0.0", 464 | "inherits": "2.0.4", 465 | "setprototypeof": "1.2.0", 466 | "statuses": "2.0.1", 467 | "toidentifier": "1.0.1" 468 | }, 469 | "engines": { 470 | "node": ">= 0.8" 471 | } 472 | }, 473 | "node_modules/iconv-lite": { 474 | "version": "0.4.24", 475 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 476 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 477 | "license": "MIT", 478 | "dependencies": { 479 | "safer-buffer": ">= 2.1.2 < 3" 480 | }, 481 | "engines": { 482 | "node": ">=0.10.0" 483 | } 484 | }, 485 | "node_modules/inherits": { 486 | "version": "2.0.4", 487 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 488 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 489 | "license": "ISC" 490 | }, 491 | "node_modules/ipaddr.js": { 492 | "version": "1.9.1", 493 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 494 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 495 | "license": "MIT", 496 | "engines": { 497 | "node": ">= 0.10" 498 | } 499 | }, 500 | "node_modules/isarray": { 501 | "version": "1.0.0", 502 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 503 | "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", 504 | "license": "MIT" 505 | }, 506 | "node_modules/math-intrinsics": { 507 | "version": "1.1.0", 508 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 509 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 510 | "license": "MIT", 511 | "engines": { 512 | "node": ">= 0.4" 513 | } 514 | }, 515 | "node_modules/media-typer": { 516 | "version": "0.3.0", 517 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 518 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 519 | "license": "MIT", 520 | "engines": { 521 | "node": ">= 0.6" 522 | } 523 | }, 524 | "node_modules/merge-descriptors": { 525 | "version": "1.0.3", 526 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", 527 | "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", 528 | "license": "MIT", 529 | "funding": { 530 | "url": "https://github.com/sponsors/sindresorhus" 531 | } 532 | }, 533 | "node_modules/methods": { 534 | "version": "1.1.2", 535 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 536 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 537 | "license": "MIT", 538 | "engines": { 539 | "node": ">= 0.6" 540 | } 541 | }, 542 | "node_modules/mime": { 543 | "version": "1.6.0", 544 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 545 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 546 | "license": "MIT", 547 | "bin": { 548 | "mime": "cli.js" 549 | }, 550 | "engines": { 551 | "node": ">=4" 552 | } 553 | }, 554 | "node_modules/mime-db": { 555 | "version": "1.52.0", 556 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 557 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 558 | "license": "MIT", 559 | "engines": { 560 | "node": ">= 0.6" 561 | } 562 | }, 563 | "node_modules/mime-types": { 564 | "version": "2.1.35", 565 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 566 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 567 | "license": "MIT", 568 | "dependencies": { 569 | "mime-db": "1.52.0" 570 | }, 571 | "engines": { 572 | "node": ">= 0.6" 573 | } 574 | }, 575 | "node_modules/minimist": { 576 | "version": "1.2.8", 577 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 578 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 579 | "license": "MIT", 580 | "funding": { 581 | "url": "https://github.com/sponsors/ljharb" 582 | } 583 | }, 584 | "node_modules/mkdirp": { 585 | "version": "0.5.6", 586 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", 587 | "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", 588 | "license": "MIT", 589 | "dependencies": { 590 | "minimist": "^1.2.6" 591 | }, 592 | "bin": { 593 | "mkdirp": "bin/cmd.js" 594 | } 595 | }, 596 | "node_modules/ms": { 597 | "version": "2.0.0", 598 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 599 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", 600 | "license": "MIT" 601 | }, 602 | "node_modules/multer": { 603 | "version": "2.0.0", 604 | "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.0.tgz", 605 | "integrity": "sha512-bS8rPZurbAuHGAnApbM9d4h1wSoYqrOqkE+6a64KLMK9yWU7gJXBDDVklKQ3TPi9DRb85cRs6yXaC0+cjxRtRg==", 606 | "license": "MIT", 607 | "dependencies": { 608 | "append-field": "^1.0.0", 609 | "busboy": "^1.0.0", 610 | "concat-stream": "^1.5.2", 611 | "mkdirp": "^0.5.4", 612 | "object-assign": "^4.1.1", 613 | "type-is": "^1.6.4", 614 | "xtend": "^4.0.0" 615 | }, 616 | "engines": { 617 | "node": ">= 10.16.0" 618 | } 619 | }, 620 | "node_modules/negotiator": { 621 | "version": "0.6.3", 622 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 623 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 624 | "license": "MIT", 625 | "engines": { 626 | "node": ">= 0.6" 627 | } 628 | }, 629 | "node_modules/object-assign": { 630 | "version": "4.1.1", 631 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 632 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", 633 | "license": "MIT", 634 | "engines": { 635 | "node": ">=0.10.0" 636 | } 637 | }, 638 | "node_modules/object-inspect": { 639 | "version": "1.13.3", 640 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", 641 | "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", 642 | "license": "MIT", 643 | "engines": { 644 | "node": ">= 0.4" 645 | }, 646 | "funding": { 647 | "url": "https://github.com/sponsors/ljharb" 648 | } 649 | }, 650 | "node_modules/on-finished": { 651 | "version": "2.4.1", 652 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 653 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 654 | "license": "MIT", 655 | "dependencies": { 656 | "ee-first": "1.1.1" 657 | }, 658 | "engines": { 659 | "node": ">= 0.8" 660 | } 661 | }, 662 | "node_modules/parseurl": { 663 | "version": "1.3.3", 664 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 665 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 666 | "license": "MIT", 667 | "engines": { 668 | "node": ">= 0.8" 669 | } 670 | }, 671 | "node_modules/path-to-regexp": { 672 | "version": "0.1.12", 673 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", 674 | "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", 675 | "license": "MIT" 676 | }, 677 | "node_modules/process-nextick-args": { 678 | "version": "2.0.1", 679 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 680 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", 681 | "license": "MIT" 682 | }, 683 | "node_modules/proxy-addr": { 684 | "version": "2.0.7", 685 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 686 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 687 | "license": "MIT", 688 | "dependencies": { 689 | "forwarded": "0.2.0", 690 | "ipaddr.js": "1.9.1" 691 | }, 692 | "engines": { 693 | "node": ">= 0.10" 694 | } 695 | }, 696 | "node_modules/qs": { 697 | "version": "6.13.0", 698 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", 699 | "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", 700 | "license": "BSD-3-Clause", 701 | "dependencies": { 702 | "side-channel": "^1.0.6" 703 | }, 704 | "engines": { 705 | "node": ">=0.6" 706 | }, 707 | "funding": { 708 | "url": "https://github.com/sponsors/ljharb" 709 | } 710 | }, 711 | "node_modules/range-parser": { 712 | "version": "1.2.1", 713 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 714 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 715 | "license": "MIT", 716 | "engines": { 717 | "node": ">= 0.6" 718 | } 719 | }, 720 | "node_modules/raw-body": { 721 | "version": "2.5.2", 722 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 723 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 724 | "license": "MIT", 725 | "dependencies": { 726 | "bytes": "3.1.2", 727 | "http-errors": "2.0.0", 728 | "iconv-lite": "0.4.24", 729 | "unpipe": "1.0.0" 730 | }, 731 | "engines": { 732 | "node": ">= 0.8" 733 | } 734 | }, 735 | "node_modules/readable-stream": { 736 | "version": "2.3.8", 737 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", 738 | "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", 739 | "license": "MIT", 740 | "dependencies": { 741 | "core-util-is": "~1.0.0", 742 | "inherits": "~2.0.3", 743 | "isarray": "~1.0.0", 744 | "process-nextick-args": "~2.0.0", 745 | "safe-buffer": "~5.1.1", 746 | "string_decoder": "~1.1.1", 747 | "util-deprecate": "~1.0.1" 748 | } 749 | }, 750 | "node_modules/readable-stream/node_modules/safe-buffer": { 751 | "version": "5.1.2", 752 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 753 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 754 | "license": "MIT" 755 | }, 756 | "node_modules/safe-buffer": { 757 | "version": "5.2.1", 758 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 759 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 760 | "funding": [ 761 | { 762 | "type": "github", 763 | "url": "https://github.com/sponsors/feross" 764 | }, 765 | { 766 | "type": "patreon", 767 | "url": "https://www.patreon.com/feross" 768 | }, 769 | { 770 | "type": "consulting", 771 | "url": "https://feross.org/support" 772 | } 773 | ], 774 | "license": "MIT" 775 | }, 776 | "node_modules/safer-buffer": { 777 | "version": "2.1.2", 778 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 779 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 780 | "license": "MIT" 781 | }, 782 | "node_modules/send": { 783 | "version": "0.19.0", 784 | "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", 785 | "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", 786 | "license": "MIT", 787 | "dependencies": { 788 | "debug": "2.6.9", 789 | "depd": "2.0.0", 790 | "destroy": "1.2.0", 791 | "encodeurl": "~1.0.2", 792 | "escape-html": "~1.0.3", 793 | "etag": "~1.8.1", 794 | "fresh": "0.5.2", 795 | "http-errors": "2.0.0", 796 | "mime": "1.6.0", 797 | "ms": "2.1.3", 798 | "on-finished": "2.4.1", 799 | "range-parser": "~1.2.1", 800 | "statuses": "2.0.1" 801 | }, 802 | "engines": { 803 | "node": ">= 0.8.0" 804 | } 805 | }, 806 | "node_modules/send/node_modules/encodeurl": { 807 | "version": "1.0.2", 808 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 809 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 810 | "license": "MIT", 811 | "engines": { 812 | "node": ">= 0.8" 813 | } 814 | }, 815 | "node_modules/send/node_modules/ms": { 816 | "version": "2.1.3", 817 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 818 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 819 | "license": "MIT" 820 | }, 821 | "node_modules/serve-static": { 822 | "version": "1.16.2", 823 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", 824 | "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", 825 | "license": "MIT", 826 | "dependencies": { 827 | "encodeurl": "~2.0.0", 828 | "escape-html": "~1.0.3", 829 | "parseurl": "~1.3.3", 830 | "send": "0.19.0" 831 | }, 832 | "engines": { 833 | "node": ">= 0.8.0" 834 | } 835 | }, 836 | "node_modules/setprototypeof": { 837 | "version": "1.2.0", 838 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 839 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 840 | "license": "ISC" 841 | }, 842 | "node_modules/side-channel": { 843 | "version": "1.1.0", 844 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 845 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 846 | "license": "MIT", 847 | "dependencies": { 848 | "es-errors": "^1.3.0", 849 | "object-inspect": "^1.13.3", 850 | "side-channel-list": "^1.0.0", 851 | "side-channel-map": "^1.0.1", 852 | "side-channel-weakmap": "^1.0.2" 853 | }, 854 | "engines": { 855 | "node": ">= 0.4" 856 | }, 857 | "funding": { 858 | "url": "https://github.com/sponsors/ljharb" 859 | } 860 | }, 861 | "node_modules/side-channel-list": { 862 | "version": "1.0.0", 863 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 864 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 865 | "license": "MIT", 866 | "dependencies": { 867 | "es-errors": "^1.3.0", 868 | "object-inspect": "^1.13.3" 869 | }, 870 | "engines": { 871 | "node": ">= 0.4" 872 | }, 873 | "funding": { 874 | "url": "https://github.com/sponsors/ljharb" 875 | } 876 | }, 877 | "node_modules/side-channel-map": { 878 | "version": "1.0.1", 879 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 880 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 881 | "license": "MIT", 882 | "dependencies": { 883 | "call-bound": "^1.0.2", 884 | "es-errors": "^1.3.0", 885 | "get-intrinsic": "^1.2.5", 886 | "object-inspect": "^1.13.3" 887 | }, 888 | "engines": { 889 | "node": ">= 0.4" 890 | }, 891 | "funding": { 892 | "url": "https://github.com/sponsors/ljharb" 893 | } 894 | }, 895 | "node_modules/side-channel-weakmap": { 896 | "version": "1.0.2", 897 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 898 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 899 | "license": "MIT", 900 | "dependencies": { 901 | "call-bound": "^1.0.2", 902 | "es-errors": "^1.3.0", 903 | "get-intrinsic": "^1.2.5", 904 | "object-inspect": "^1.13.3", 905 | "side-channel-map": "^1.0.1" 906 | }, 907 | "engines": { 908 | "node": ">= 0.4" 909 | }, 910 | "funding": { 911 | "url": "https://github.com/sponsors/ljharb" 912 | } 913 | }, 914 | "node_modules/statuses": { 915 | "version": "2.0.1", 916 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 917 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 918 | "license": "MIT", 919 | "engines": { 920 | "node": ">= 0.8" 921 | } 922 | }, 923 | "node_modules/streamsearch": { 924 | "version": "1.1.0", 925 | "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", 926 | "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", 927 | "engines": { 928 | "node": ">=10.0.0" 929 | } 930 | }, 931 | "node_modules/string_decoder": { 932 | "version": "1.1.1", 933 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 934 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 935 | "license": "MIT", 936 | "dependencies": { 937 | "safe-buffer": "~5.1.0" 938 | } 939 | }, 940 | "node_modules/string_decoder/node_modules/safe-buffer": { 941 | "version": "5.1.2", 942 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 943 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 944 | "license": "MIT" 945 | }, 946 | "node_modules/toidentifier": { 947 | "version": "1.0.1", 948 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 949 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 950 | "license": "MIT", 951 | "engines": { 952 | "node": ">=0.6" 953 | } 954 | }, 955 | "node_modules/type-is": { 956 | "version": "1.6.18", 957 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 958 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 959 | "license": "MIT", 960 | "dependencies": { 961 | "media-typer": "0.3.0", 962 | "mime-types": "~2.1.24" 963 | }, 964 | "engines": { 965 | "node": ">= 0.6" 966 | } 967 | }, 968 | "node_modules/typedarray": { 969 | "version": "0.0.6", 970 | "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", 971 | "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", 972 | "license": "MIT" 973 | }, 974 | "node_modules/unpipe": { 975 | "version": "1.0.0", 976 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 977 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 978 | "license": "MIT", 979 | "engines": { 980 | "node": ">= 0.8" 981 | } 982 | }, 983 | "node_modules/util-deprecate": { 984 | "version": "1.0.2", 985 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 986 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 987 | "license": "MIT" 988 | }, 989 | "node_modules/utils-merge": { 990 | "version": "1.0.1", 991 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 992 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 993 | "license": "MIT", 994 | "engines": { 995 | "node": ">= 0.4.0" 996 | } 997 | }, 998 | "node_modules/vary": { 999 | "version": "1.1.2", 1000 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1001 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1002 | "license": "MIT", 1003 | "engines": { 1004 | "node": ">= 0.8" 1005 | } 1006 | }, 1007 | "node_modules/xtend": { 1008 | "version": "4.0.2", 1009 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 1010 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 1011 | "license": "MIT", 1012 | "engines": { 1013 | "node": ">=0.4" 1014 | } 1015 | } 1016 | } 1017 | } 1018 | -------------------------------------------------------------------------------- /examples/file-upload-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "file-uploader-server", 3 | "version": "1.0.0", 4 | "description": "This project is a simple Node.js server built with `express`, `multer`, and `cors` to handle file uploads and serve static files.", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js" 8 | }, 9 | "keywords": [], 10 | "author": "Optimajet", 11 | "homepage": "https://formengine.io/", 12 | "license": "ISC", 13 | "dependencies": { 14 | "cors": "^2.8.5", 15 | "express": "^4.21.2", 16 | "multer": "^2.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/file-upload-server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const multer = require('multer'); 3 | const cors = require('cors'); 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | 7 | const app = express(); 8 | const PORT = 3001; 9 | 10 | // Enable CORS to allow cross-origin requests 11 | app.use(cors()); 12 | 13 | // Folder to store uploaded files 14 | const uploadFolder = path.join(__dirname, 'uploads'); 15 | if (!fs.existsSync(uploadFolder)) { 16 | // Create the folder if it doesn't exist 17 | fs.mkdirSync(uploadFolder, {recursive: true}); 18 | } 19 | 20 | // Configure Multer for file handling 21 | const storage = multer.diskStorage({ 22 | destination: (req, file, cb) => { 23 | // Specify the destination folder 24 | cb(null, uploadFolder); 25 | }, 26 | filename: (req, file, cb) => { 27 | // Create a unique filename using the current timestamp 28 | cb(null, `${Date.now()}-${file.originalname}`); 29 | } 30 | }); 31 | const upload = multer({storage}); 32 | 33 | // Endpoint to handle file uploads 34 | app.post('/upload', upload.single('file'), (req, res) => { 35 | if (req.file) { 36 | // Respond with success if the file is uploaded 37 | res.json({ 38 | success: true, 39 | message: 'File uploaded successfully', 40 | filePath: `/uploads/${req.file.filename}` // File's accessible path 41 | }); 42 | } else { 43 | // Respond with an error if the upload fails 44 | res.status(400).json({ 45 | success: false, 46 | message: 'Error during file upload' 47 | }); 48 | } 49 | }); 50 | 51 | app.get('/', (req, res) => { 52 | res.send('Server is running'); 53 | }); 54 | 55 | // Serve static files from the uploads directory 56 | app.use('/uploads', express.static(uploadFolder)); 57 | 58 | // Start the server 59 | app.listen(PORT, () => { 60 | console.log(`Server is running at http://localhost:${PORT}`); 61 | }); 62 | -------------------------------------------------------------------------------- /examples/file-upload-server/static/uploader-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimajet/formengine/1d25e89317e9b3c99db2f00f328712328a673713/examples/file-upload-server/static/uploader-example.png -------------------------------------------------------------------------------- /examples/with-formik/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .env -------------------------------------------------------------------------------- /examples/with-formik/README.md: -------------------------------------------------------------------------------- 1 | # Formik integration example 2 | 3 | This code corresponds to Formik [integration example](https://formengine.io/documentation/formik-integration). 4 | 5 | ## How to run 6 | 7 | ```shell 8 | git clone git@github.com:optimajet/formengine.git 9 | cd examples/with-formik 10 | npm install 11 | npm run start 12 | ``` 13 | -------------------------------------------------------------------------------- /examples/with-formik/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "with-formik", 3 | "version": "0.1.0", 4 | "private": true, 5 | "author": "Optimajet", 6 | "homepage": "https://formengine.io/", 7 | "dependencies": { 8 | "@react-form-builder/components-rsuite": "^4.2.0", 9 | "@react-form-builder/core": "^4.2.0", 10 | "@react-form-builder/designer": "^4.2.0", 11 | "@testing-library/jest-dom": "^5.17.0", 12 | "@testing-library/react": "^13.4.0", 13 | "@testing-library/user-event": "^13.5.0", 14 | "@types/jest": "^27.5.2", 15 | "@types/node": "^16.18.107", 16 | "@types/react": "^18.3.5", 17 | "@types/react-dom": "^18.3.0", 18 | "formik": "^2.4.6", 19 | "react": "^18.3.1", 20 | "react-dom": "^18.3.1", 21 | "react-scripts": "5.0.1", 22 | "typescript": "^4.9.5", 23 | "web-vitals": "^2.1.4", 24 | "yup": "^1.4.0" 25 | }, 26 | "scripts": { 27 | "start": "react-scripts start", 28 | "build": "react-scripts build", 29 | "test": "react-scripts test", 30 | "eject": "react-scripts eject" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app", 35 | "react-app/jest" 36 | ] 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/with-formik/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimajet/formengine/1d25e89317e9b3c99db2f00f328712328a673713/examples/with-formik/public/favicon.ico -------------------------------------------------------------------------------- /examples/with-formik/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/with-formik/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimajet/formengine/1d25e89317e9b3c99db2f00f328712328a673713/examples/with-formik/public/logo192.png -------------------------------------------------------------------------------- /examples/with-formik/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimajet/formengine/1d25e89317e9b3c99db2f00f328712328a673713/examples/with-formik/public/logo512.png -------------------------------------------------------------------------------- /examples/with-formik/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /examples/with-formik/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /examples/with-formik/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/with-formik/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {render, screen} from '@testing-library/react' 3 | import App from './App' 4 | 5 | test('renders learn react link', () => { 6 | render() 7 | const linkElement = screen.getByText(/learn react/i) 8 | expect(linkElement).toBeInTheDocument() 9 | }) 10 | -------------------------------------------------------------------------------- /examples/with-formik/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | formEngineRsuiteCssLoader, 3 | ltrCssLoader, 4 | RsLocalizationWrapper, 5 | rSuiteComponents, 6 | rtlCssLoader 7 | } from '@react-form-builder/components-rsuite' 8 | import {ActionDefinition, BiDi, ComponentData, IFormData, RuleValidatorResult, Validators} from '@react-form-builder/core' 9 | import {BuilderView, FormBuilder, IFormStorage} from '@react-form-builder/designer' 10 | import debounce from 'lodash/debounce' 11 | import React, {useMemo} from 'react' 12 | import {Schema} from 'yup' 13 | import form from './form.json' 14 | import {useBookingForm} from './useBookingForm' 15 | import * as validator from './validators' 16 | 17 | const componentsMetadata = rSuiteComponents.map(definer => definer.build()) 18 | 19 | const formName = 'formikForm' 20 | 21 | const formStorage: IFormStorage = { 22 | getForm: async () => localStorage.getItem(formName) || JSON.stringify(form), 23 | saveForm: async (_, form) => localStorage.setItem(formName, form), 24 | getFormNames: () => Promise.resolve([formName]), 25 | removeForm: () => Promise.resolve() 26 | } 27 | 28 | const loadForm = () => formStorage.getForm('') 29 | 30 | const builderView = new BuilderView(componentsMetadata) 31 | .withViewerWrapper(RsLocalizationWrapper) 32 | .withCssLoader(BiDi.LTR, ltrCssLoader) 33 | .withCssLoader(BiDi.RTL, rtlCssLoader) 34 | .withCssLoader('common', formEngineRsuiteCssLoader) 35 | 36 | // We're hiding the form panel because it's not fully functional in this example 37 | const customization = { 38 | Forms_Tab: { 39 | hidden: true 40 | } 41 | } 42 | 43 | const toFormEngineValidate = (yupValidator: typeof Schema.prototype) => async (value: unknown): Promise => { 44 | let err: RuleValidatorResult = true 45 | try { 46 | await yupValidator.validate(value) 47 | } catch (e) { 48 | err = (e as Error).message 49 | } 50 | return err 51 | } 52 | 53 | const customValidators: Validators = { 54 | 'string': { 55 | 'isFullName': { 56 | validate: toFormEngineValidate(validator.fullName) 57 | }, 58 | }, 59 | 'date': { 60 | 'dateInTheFuture': { 61 | validate: toFormEngineValidate(validator.dateTodayOrInTheFuture) 62 | } 63 | }, 64 | 'number': { 65 | 'checkGuestCount': { 66 | validate: toFormEngineValidate(validator.checkGuestsCount) 67 | } 68 | } 69 | } 70 | 71 | function App() { 72 | const [formik] = useBookingForm() 73 | 74 | const setFormikValues = useMemo(() => debounce(async (form: IFormData) => { 75 | const {fields} = form as ComponentData 76 | 77 | for (const [key, {value, error}] of fields) { 78 | const field = formik.getFieldProps(key) 79 | if (value !== field.value) { 80 | try { 81 | await formik.setFieldValue(key, value) 82 | formik.setFieldError(key, error) 83 | } catch (e) { 84 | console.warn(e) 85 | } 86 | } 87 | } 88 | }, 400), [formik]) 89 | 90 | return { 100 | try { 101 | await e.store.formData.validate() 102 | } catch (e) { 103 | console.warn(e) 104 | } 105 | if (Object.keys(e.store.formData.errors).length < 1) { 106 | await formik.submitForm() 107 | } 108 | }) 109 | }} 110 | /> 111 | } 112 | 113 | export default App 114 | -------------------------------------------------------------------------------- /examples/with-formik/src/form.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "form": { 4 | "key": "Screen", 5 | "type": "Screen", 6 | "props": {}, 7 | "children": [ 8 | { 9 | "key": "fullName", 10 | "type": "RsInput", 11 | "props": { 12 | "label": { 13 | "value": "Full name" 14 | } 15 | }, 16 | "schema": { 17 | "validations": [ 18 | { 19 | "key": "isFullName", 20 | "type": "custom" 21 | } 22 | ] 23 | } 24 | }, 25 | { 26 | "key": "checkinDate", 27 | "type": "RsDatePicker", 28 | "props": { 29 | "label": { 30 | "value": "Check-in date" 31 | } 32 | }, 33 | "schema": { 34 | "validations": [ 35 | { 36 | "key": "dateInTheFuture", 37 | "type": "custom" 38 | } 39 | ] 40 | } 41 | }, 42 | { 43 | "key": "guestCount", 44 | "type": "RsNumberFormat", 45 | "props": { 46 | "label": { 47 | "value": "Guest count" 48 | } 49 | }, 50 | "schema": { 51 | "validations": [ 52 | { 53 | "key": "checkGuestCount", 54 | "type": "custom" 55 | } 56 | ] 57 | } 58 | }, 59 | { 60 | "key": "rsButton1", 61 | "type": "RsButton", 62 | "props": { 63 | "children": { 64 | "value": "Send" 65 | } 66 | }, 67 | "events": { 68 | "onClick": [ 69 | { 70 | "name": "submitForm", 71 | "type": "custom" 72 | } 73 | ] 74 | } 75 | } 76 | ] 77 | }, 78 | "localization": {}, 79 | "languages": [ 80 | { 81 | "code": "en", 82 | "dialect": "US", 83 | "name": "English", 84 | "description": "American English", 85 | "bidi": "ltr" 86 | } 87 | ], 88 | "defaultLanguage": "en-US" 89 | } -------------------------------------------------------------------------------- /examples/with-formik/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /examples/with-formik/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import './index.css' 4 | import App from './App' 5 | import reportWebVitals from './reportWebVitals' 6 | 7 | const root = ReactDOM.createRoot( 8 | document.getElementById('root') as HTMLElement 9 | ) 10 | root.render( 11 | 12 | 13 | 14 | ) 15 | 16 | // If you want to start measuring performance in your app, pass a function 17 | // to log results (for example: reportWebVitals(console.log)) 18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 19 | reportWebVitals() 20 | -------------------------------------------------------------------------------- /examples/with-formik/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/with-formik/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /examples/with-formik/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import {ReportHandler} from 'web-vitals' 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => { 6 | getCLS(onPerfEntry) 7 | getFID(onPerfEntry) 8 | getFCP(onPerfEntry) 9 | getLCP(onPerfEntry) 10 | getTTFB(onPerfEntry) 11 | }) 12 | } 13 | } 14 | 15 | export default reportWebVitals 16 | -------------------------------------------------------------------------------- /examples/with-formik/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom' 6 | -------------------------------------------------------------------------------- /examples/with-formik/src/useBookingForm.ts: -------------------------------------------------------------------------------- 1 | import {useFormik} from 'formik'; 2 | import {useMemo, useState} from 'react'; 3 | import {FormikProps} from 'formik/dist/types'; 4 | 5 | export type BookingForm = Partial<{ 6 | fullName: string, 7 | guestCount: number, 8 | checkinDate: Date 9 | }> 10 | 11 | export type BookingFormErrors = Partial> 12 | 13 | export const useBookingForm = (): [FormikProps, BookingForm] => { 14 | const [formData, setFormData] = useState({}); 15 | 16 | const initialValues = useMemo(() => ({ 17 | fullName: '', 18 | guestCount: 1, 19 | checkinDate: new Date() 20 | }), []); 21 | 22 | const formik = useFormik({ 23 | initialValues, 24 | // validate: ({checkinDate, fullName, guestCount}) => { 25 | // let errors: Partial> = {}; 26 | // 27 | // if (!fullName) { 28 | // errors.fullName = 'Name is required.'; 29 | // } else if (fullName.trim().split(' ').length < 2) { 30 | // errors.fullName = 'Please enter a full name.' 31 | // } 32 | // 33 | // if (!checkinDate) { 34 | // errors.checkinDate = 'Date is required.'; 35 | // } 36 | // 37 | // if (!isFinite(guestCount as number) || (guestCount as number) < 1) { 38 | // errors.guestCount = 'No guest entered.'; 39 | // } 40 | // 41 | // return errors; 42 | // }, 43 | onSubmit: (data) => { 44 | setFormData(data); 45 | console.log(data); 46 | } 47 | }) 48 | 49 | return [formik, formData] 50 | } 51 | -------------------------------------------------------------------------------- /examples/with-formik/src/validators.ts: -------------------------------------------------------------------------------- 1 | import * as Yup from 'yup' 2 | 3 | export const fullName = Yup.string().required().test({ 4 | message: 'Please enter a full name', 5 | test: (value: string) => !!value && value.trim().split(' ').length > 1 6 | }) 7 | 8 | export const dateTodayOrInTheFuture = Yup.date().required().test({ 9 | message: 'Dates in the past are impossible to book', 10 | test: (value: Date) => { 11 | const today = new Date() 12 | const date = new Date(value) 13 | 14 | today.setHours(0, 0, 0, 0) 15 | date.setHours(0, 0, 0, 0) 16 | 17 | return +date >= +today 18 | } 19 | }) 20 | 21 | export const checkGuestsCount = Yup.number().required().min(1).max(6) 22 | -------------------------------------------------------------------------------- /examples/with-formik/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["next/core-web-vitals", "next/typescript"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # env files (can opt-in for commiting if needed) 33 | .env* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 37 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | }; 6 | 7 | export default nextConfig; 8 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "formengine-nextjs-v15", 3 | "description": "FormEngine with Next.js v15 example", 4 | "version": "0.1.0", 5 | "private": true, 6 | "author": "Optimajet", 7 | "homepage": "https://formengine.io/", 8 | "scripts": { 9 | "dev": "next dev --turbopack", 10 | "build": "next build", 11 | "start": "next start", 12 | "lint": "next lint" 13 | }, 14 | "dependencies": { 15 | "@react-form-builder/components-rsuite": "^1.14.0", 16 | "@react-form-builder/core": "^1.14.0", 17 | "@react-form-builder/designer": "^1.14.0", 18 | "next": "15.2.4", 19 | "react": "18.3.1", 20 | "react-dom": "18.3.1" 21 | }, 22 | "devDependencies": { 23 | "@types/node": "^20", 24 | "@types/react": "^18", 25 | "@types/react-dom": "^18", 26 | "eslint": "^8", 27 | "eslint-config-next": "15.0.1", 28 | "typescript": "^5" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/src/app/builder/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { 4 | formEngineRsuiteCssLoader, 5 | ltrCssLoader, 6 | RsLocalizationWrapper, 7 | rSuiteComponents, 8 | rsErrorMessage, 9 | rtlCssLoader, 10 | rsTooltip 11 | } from '@react-form-builder/components-rsuite' 12 | import {BiDi, BuilderView} from '@react-form-builder/core' 13 | import {IFormStorage} from '@react-form-builder/designer' 14 | 15 | import {actions} from '@/app/common/actions' 16 | import form from '@/app/common/form.json' 17 | import {customValidators} from '@/app/common/validators' 18 | import dynamic from 'next/dynamic' 19 | 20 | const FormBuilder = dynamic(() => import('@react-form-builder/designer').then((mod) => mod.FormBuilder), { 21 | ssr: false 22 | }) 23 | 24 | const components = [...rSuiteComponents, rsErrorMessage] 25 | .map(definer => definer.build()) 26 | 27 | const formName = 'nextForm' 28 | 29 | const formStorage: IFormStorage = { 30 | getForm: async () => localStorage.getItem(formName) || JSON.stringify(form), 31 | saveForm: async (_, form) => localStorage.setItem(formName, form), 32 | getFormNames: () => Promise.resolve([formName]), 33 | removeForm: () => Promise.resolve() 34 | } 35 | 36 | const loadForm = () => formStorage.getForm('') 37 | 38 | const view = new BuilderView(components) 39 | .withErrorMeta(rsErrorMessage.build()) 40 | .withTooltipMeta(rsTooltip.build()) 41 | .withViewerWrapper(RsLocalizationWrapper) 42 | .withCssLoader(BiDi.LTR, ltrCssLoader) 43 | .withCssLoader(BiDi.RTL, rtlCssLoader) 44 | .withCssLoader('common', formEngineRsuiteCssLoader) 45 | .withErrorMeta(rsErrorMessage.build()) 46 | .withTooltipMeta(rsTooltip.build()) 47 | 48 | // We're hiding the form panel because it's not fully functional in this example 49 | const customization = { 50 | Forms_Tab: { 51 | hidden: true 52 | } 53 | } 54 | 55 | export default function Builder() { 56 | return ( 57 | 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/src/app/common/actions.ts: -------------------------------------------------------------------------------- 1 | import {ActionDefinition} from '@react-form-builder/core' 2 | 3 | export const actions = { 4 | submitForm: ActionDefinition.functionalAction(async (e) => { 5 | try { 6 | await e.store.formData.validate() 7 | } catch (e) { 8 | console.warn(e) 9 | } 10 | if (Object.keys(e.store.formData.errors).length < 1) { 11 | console.log(e.store.formData.data) 12 | } else { 13 | console.error(e.store.formData.errors) 14 | } 15 | }) 16 | }; 17 | 18 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/src/app/common/form.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "errorType": "RsErrorMessage", 4 | "form": { 5 | "key": "Screen", 6 | "type": "Screen", 7 | "props": {}, 8 | "children": [ 9 | { 10 | "key": "fullName", 11 | "type": "RsInput", 12 | "props": { 13 | "label": { 14 | "value": "Full name" 15 | } 16 | }, 17 | "schema": { 18 | "validations": [ 19 | { 20 | "key": "isFullName", 21 | "type": "custom" 22 | } 23 | ] 24 | } 25 | }, 26 | { 27 | "key": "checkinDate", 28 | "type": "RsDatePicker", 29 | "props": { 30 | "label": { 31 | "value": "Check-in date" 32 | } 33 | }, 34 | "schema": { 35 | "validations": [ 36 | { 37 | "key": "dateInTheFuture", 38 | "type": "custom" 39 | } 40 | ] 41 | } 42 | }, 43 | { 44 | "key": "guestCount", 45 | "type": "RsNumberFormat", 46 | "props": { 47 | "label": { 48 | "value": "Guest count" 49 | } 50 | }, 51 | "schema": { 52 | "validations": [ 53 | { 54 | "key": "checkGuestCount", 55 | "type": "custom" 56 | } 57 | ] 58 | } 59 | }, 60 | { 61 | "key": "rsButton1", 62 | "type": "RsButton", 63 | "props": { 64 | "children": { 65 | "value": "Send" 66 | } 67 | }, 68 | "events": { 69 | "onClick": [ 70 | { 71 | "name": "submitForm", 72 | "type": "custom" 73 | } 74 | ] 75 | } 76 | } 77 | ] 78 | }, 79 | "localization": {}, 80 | "languages": [ 81 | { 82 | "code": "en", 83 | "dialect": "US", 84 | "name": "English", 85 | "description": "American English", 86 | "bidi": "ltr" 87 | } 88 | ], 89 | "defaultLanguage": "en-US" 90 | } 91 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/src/app/common/styles.module.css: -------------------------------------------------------------------------------- 1 | .page { 2 | --gray-rgb: 0, 0, 0; 3 | --gray-alpha-200: rgba(var(--gray-rgb), 0.08); 4 | --gray-alpha-100: rgba(var(--gray-rgb), 0.05); 5 | 6 | --button-primary-hover: #383838; 7 | --button-secondary-hover: #f2f2f2; 8 | 9 | display: grid; 10 | grid-template-rows: auto; 11 | align-items: center; 12 | justify-items: center; 13 | padding: 80px; 14 | gap: 64px; 15 | font-family: var(--font-geist-sans); 16 | } 17 | 18 | @media (prefers-color-scheme: dark) { 19 | .page { 20 | --gray-rgb: 255, 255, 255; 21 | --gray-alpha-200: rgba(var(--gray-rgb), 0.145); 22 | --gray-alpha-100: rgba(var(--gray-rgb), 0.06); 23 | 24 | --button-primary-hover: #ccc; 25 | --button-secondary-hover: #1a1a1a; 26 | } 27 | } 28 | 29 | .main { 30 | display: flex; 31 | flex-direction: column; 32 | justify-content: space-around; 33 | align-items: center; 34 | gap: 32px; 35 | grid-row-start: 2; 36 | height: 100svh; 37 | max-height: 100svh; 38 | min-height: 100svh; 39 | } 40 | 41 | .main code { 42 | font-family: inherit; 43 | background: var(--gray-alpha-100); 44 | padding: 2px 4px; 45 | border-radius: 4px; 46 | font-weight: 600; 47 | } 48 | 49 | .ctas { 50 | display: flex; 51 | gap: 16px; 52 | } 53 | 54 | .ctas a { 55 | appearance: none; 56 | border-radius: 128px; 57 | height: 48px; 58 | padding: 0 20px; 59 | border: none; 60 | border: 1px solid transparent; 61 | transition: 62 | background 0.2s, 63 | color 0.2s, 64 | border-color 0.2s; 65 | cursor: pointer; 66 | display: flex; 67 | align-items: center; 68 | justify-content: center; 69 | font-size: 16px; 70 | line-height: 20px; 71 | font-weight: 500; 72 | } 73 | 74 | a.primary { 75 | background: var(--foreground); 76 | color: var(--background); 77 | gap: 8px; 78 | } 79 | 80 | a.secondary { 81 | border-color: var(--gray-alpha-200); 82 | min-width: 180px; 83 | } 84 | 85 | .footer { 86 | grid-row-start: 3; 87 | display: flex; 88 | gap: 2em; 89 | height: 3em; 90 | } 91 | 92 | .footer a { 93 | display: flex; 94 | align-items: center; 95 | gap: 8px; 96 | } 97 | 98 | .footer img { 99 | flex-shrink: 0; 100 | } 101 | 102 | /* Enable hover only on non-touch devices */ 103 | @media (hover: hover) and (pointer: fine) { 104 | a.primary:hover { 105 | background: var(--button-primary-hover); 106 | border-color: transparent; 107 | } 108 | 109 | a.secondary:hover { 110 | background: var(--button-secondary-hover); 111 | border-color: transparent; 112 | } 113 | 114 | .footer a:hover { 115 | text-decoration: underline; 116 | text-underline-offset: 4px; 117 | } 118 | } 119 | 120 | @media (max-width: 600px) { 121 | .page { 122 | padding: 32px; 123 | padding-bottom: 80px; 124 | } 125 | 126 | .main { 127 | align-items: center; 128 | } 129 | 130 | .main ol { 131 | text-align: center; 132 | } 133 | 134 | .ctas { 135 | flex-direction: column; 136 | } 137 | 138 | .ctas a { 139 | font-size: 14px; 140 | height: 40px; 141 | padding: 0 16px; 142 | } 143 | 144 | a.secondary { 145 | min-width: auto; 146 | } 147 | 148 | .footer { 149 | flex-wrap: wrap; 150 | align-items: center; 151 | justify-content: center; 152 | } 153 | } 154 | 155 | .booking-form { 156 | max-width: 600px; 157 | margin: 20px auto; 158 | padding: 20px; 159 | border-radius: 8px; 160 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); 161 | background-color: #fff; 162 | } 163 | .booking-form-item { 164 | margin-bottom: 20px; 165 | } 166 | 167 | .debug-output { 168 | max-width: 600px; 169 | margin: 20px auto; 170 | padding: 10px; 171 | border: 1px solid #ddd; 172 | border-radius: 4px; 173 | background-color: #f9f9f9; 174 | font-family: monospace; 175 | font-size: 12px; 176 | } 177 | .debug-output pre { 178 | margin: 0; 179 | padding: 0; 180 | border: none; 181 | background-color: transparent; 182 | font-size: inherit; 183 | } 184 | 185 | .underlined { 186 | text-decoration: underline; 187 | } 188 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/src/app/common/validators.ts: -------------------------------------------------------------------------------- 1 | import {RuleValidatorResult, Validators} from '@react-form-builder/core' 2 | 3 | export const customValidators: Validators = { 4 | 'string': { 5 | 'isFullName': { 6 | validate: (value: string, _event, _args): RuleValidatorResult => { 7 | const valid = !!value && value.trim().split(' ').length > 1 8 | 9 | return valid ? true : 'Please enter a full name' 10 | } 11 | }, 12 | 'emailAddressValid': { 13 | validate: (value: string, _event, _args) => { 14 | const pattern = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/ 15 | 16 | return pattern.test(value) 17 | } 18 | } 19 | }, 20 | 'number': { 21 | 'checkGuestCount': { 22 | validate: (value: string, _event, _args) => { 23 | const num = parseInt(value) 24 | 25 | if (isFinite(num) && num > 0 && num < 7) return true 26 | 27 | return 'Guest count must be from 1 to 6' 28 | } 29 | } 30 | }, 31 | 'date': { 32 | 'dateInTheFuture': { 33 | validate: (value: Date) => { 34 | const today = new Date() 35 | const date = new Date(value) 36 | 37 | today.setHours(0, 0, 0, 0) 38 | date.setHours(0, 0, 0, 0) 39 | 40 | if(+date >= +today) return true 41 | 42 | return 'Please select valid date' 43 | } 44 | } 45 | }, 46 | 47 | } 48 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/src/app/components/message.module.css: -------------------------------------------------------------------------------- 1 | dialog.dialog { 2 | border: none; 3 | border-radius: 8px; 4 | padding: 20px; 5 | max-width: 400px; 6 | width: 100%; 7 | transform: translate(50%); 8 | background-color: #fff; 9 | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); 10 | font-family: Arial, sans-serif; 11 | } 12 | dialog.dialog p { 13 | margin: 0 0 20px; 14 | font-size: 16px; 15 | color: #333; 16 | } 17 | dialog.dialog form { 18 | display: flex; 19 | justify-content: flex-end; 20 | } 21 | dialog.dialog button { 22 | padding: 10px 15px; 23 | border: none; 24 | border-radius: 4px; 25 | background-color: #4caf50; /* Green for OK button */ 26 | color: white; 27 | cursor: pointer; 28 | transition: background-color 0.3s; 29 | } 30 | dialog.dialog button:hover { 31 | background-color: #388e3c; /* Darker green on hover */ 32 | } 33 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/src/app/components/message.tsx: -------------------------------------------------------------------------------- 1 | import styles from './message.module.css' 2 | import {createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState} from 'react' 3 | 4 | interface UseToggle { 5 | isOpen: boolean, 6 | toggle: () => void, 7 | setOpen: (isOpen: boolean) => void, 8 | open: () => void, 9 | close: () => void 10 | } 11 | 12 | export const useToggle = (): UseToggle => { 13 | const [isOpen, setOpen] = useState(false) 14 | 15 | const toggle = useCallback(() => { 16 | setOpen((isOpen) => !isOpen) 17 | }, []) 18 | 19 | const close = useCallback(() => setOpen(false), []) 20 | const open = useCallback(() => setOpen(true), []) 21 | 22 | return useMemo(() => ({ 23 | isOpen, 24 | toggle, 25 | setOpen, 26 | close, 27 | open 28 | }), [isOpen, toggle, setOpen, close, open]) 29 | } 30 | 31 | interface MessageContext { 32 | open: (message: string) => void, 33 | close: () => void, 34 | isOpen: boolean, 35 | setOpen: (isOpen: boolean) => void, 36 | message: string 37 | } 38 | 39 | const MessageContext = createContext({ 40 | open: (_: string) => {}, 41 | close: () => {}, 42 | isOpen: false, 43 | setOpen: (_: boolean) => {}, 44 | message: '' 45 | }); 46 | 47 | export const MessageProvider = ({ children }: {children: ReactNode}) => { 48 | const {isOpen, close, setOpen} = useToggle() 49 | const [message, setMessage] = useState('') 50 | 51 | const openDialog = useCallback((msg: string) => { 52 | setMessage(msg) 53 | setOpen(true) 54 | }, []) 55 | 56 | const contextValue = useMemo(() => ({ 57 | setOpen, isOpen, open: openDialog, close, message 58 | }), [isOpen, setOpen, openDialog, close, message]) 59 | 60 | return ( 61 | 62 | {children} 63 | 64 | ); 65 | }; 66 | 67 | export const useMessage = (): MessageContext => useContext(MessageContext); 68 | 69 | export const Message = ({open: propsOpen = false}: { 70 | open?: boolean 71 | }) => { 72 | const {close, isOpen, setOpen, message} = useMessage() 73 | 74 | useEffect(() => { 75 | setOpen(propsOpen) 76 | }, [propsOpen]) 77 | 78 | return 79 |

80 |

81 | 82 |
83 |
84 | } 85 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimajet/formengine/1d25e89317e9b3c99db2f00f328712328a673713/examples/with-nextjs/with-nextjs-v15/src/app/favicon.ico -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/src/app/fonts/GeistMonoVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimajet/formengine/1d25e89317e9b3c99db2f00f328712328a673713/examples/with-nextjs/with-nextjs-v15/src/app/fonts/GeistMonoVF.woff -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/src/app/fonts/GeistVF.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimajet/formengine/1d25e89317e9b3c99db2f00f328712328a673713/examples/with-nextjs/with-nextjs-v15/src/app/fonts/GeistVF.woff -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/src/app/globals.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --background: #ffffff; 3 | --foreground: #171717; 4 | } 5 | 6 | @media (prefers-color-scheme: dark) { 7 | :root { 8 | --background: #0a0a0a; 9 | --foreground: #ededed; 10 | } 11 | } 12 | 13 | html, 14 | body { 15 | max-width: 100vw; 16 | overflow-x: hidden; 17 | } 18 | 19 | body { 20 | color: var(--foreground); 21 | background: var(--background); 22 | font-family: Arial, Helvetica, sans-serif; 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | * { 28 | box-sizing: border-box; 29 | padding: 0; 30 | margin: 0; 31 | } 32 | 33 | a { 34 | color: inherit; 35 | text-decoration: none; 36 | } 37 | 38 | @media (prefers-color-scheme: dark) { 39 | html { 40 | color-scheme: dark; 41 | } 42 | } 43 | 44 | div.optimajet-formbuilder { 45 | position: fixed; 46 | top: 0; 47 | left: 0; 48 | min-height: 800px; 49 | width: 100vw; 50 | } 51 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type {Metadata} from 'next' 2 | import localFont from 'next/font/local' 3 | 4 | import styles from '@/app/common/styles.module.css' 5 | import './globals.css' 6 | import Image from 'next/image' 7 | 8 | const geistSans = localFont({ 9 | src: './fonts/GeistVF.woff', 10 | variable: '--font-geist-sans', 11 | weight: '100 900', 12 | }) 13 | const geistMono = localFont({ 14 | src: './fonts/GeistMonoVF.woff', 15 | variable: '--font-geist-mono', 16 | weight: '100 900', 17 | }) 18 | 19 | export const metadata: Metadata = { 20 | title: 'FormEngine with Next.js', 21 | description: 'Generated by create next app', 22 | } 23 | 24 | export default function RootLayout({ 25 | children, 26 | }: Readonly<{ 27 | children: React.ReactNode; 28 | }>) { 29 | return ( 30 | 31 | 32 |
33 |
34 | {children} 35 |
36 | 80 |
81 | 82 | 83 | ) 84 | } 85 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import styles from '@/app/common/styles.module.css' 2 | 3 | export default function Home() { 4 | return ( 5 | <> 6 |
7 |

Quickly navigate to either FormViewer or FormBuilder.

8 |

Links to the documentation are in the footer.

9 |
10 | 24 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/src/app/viewer/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import {Message, MessageProvider, useMessage} from '@/app/components/message' 4 | import { 5 | formEngineRsuiteCssLoader, 6 | ltrCssLoader, 7 | RsLocalizationWrapper, 8 | rSuiteComponents, 9 | rsErrorMessage, 10 | rtlCssLoader 11 | } from '@react-form-builder/components-rsuite' 12 | import {ActionDefinition, BiDi, createView} from '@react-form-builder/core' 13 | import {IFormStorage} from '@react-form-builder/designer' 14 | 15 | import form from '@/app/common/form.json' 16 | import {customValidators} from '@/app/common/validators' 17 | import dynamic from 'next/dynamic' 18 | import React, {ReactNode, useMemo} from 'react' 19 | 20 | const FormViewer = dynamic(() => import('@react-form-builder/core').then((mod) => mod.FormViewer), { 21 | ssr: false 22 | }) 23 | 24 | const viewerComponents = rSuiteComponents.map(c => c.build().model) 25 | viewerComponents.push(rsErrorMessage.build().model) 26 | 27 | const formName = 'nextForm' 28 | 29 | const formStorage: IFormStorage = { 30 | getForm: async () => localStorage.getItem(formName) || JSON.stringify(form), 31 | saveForm: async (_, form) => localStorage.setItem(formName, form), 32 | getFormNames: () => Promise.resolve([formName]), 33 | removeForm: () => Promise.resolve() 34 | } 35 | 36 | const loadForm = () => formStorage.getForm('') 37 | 38 | const view = createView(viewerComponents) 39 | .withViewerWrapper(RsLocalizationWrapper) 40 | .withCssLoader(BiDi.LTR, ltrCssLoader) 41 | .withCssLoader(BiDi.RTL, rtlCssLoader) 42 | .withCssLoader('common', formEngineRsuiteCssLoader) 43 | 44 | const ViewerWrap = ({children}: { children: ReactNode }) => ( 45 | 46 | {children} 47 | 48 | 49 | ) 50 | 51 | const Viewer = () => { 52 | const {open, setOpen} = useMessage() 53 | 54 | const actions = useMemo(() => ({ 55 | submitForm: ActionDefinition.functionalAction(async (e) => { 56 | const formData = e.store.formData 57 | 58 | setOpen(false) 59 | 60 | try { 61 | await formData.validate() 62 | } catch (e) { 63 | open(String(e)) 64 | } 65 | if (Object.keys(formData.errors).length < 1) { 66 | open('Thank you!') 67 | } else { 68 | const message = Object.entries(formData.errors).reduce>((acc, [k, v]) => { 69 | acc.push([k, v].join(' - ')) 70 | return acc 71 | }, []).join('
') 72 | open(message) 73 | } 74 | }) 75 | }), []) 76 | 77 | return ( 78 | 85 | ) 86 | } 87 | 88 | export default function ViewerClient() { 89 | return 90 | } 91 | -------------------------------------------------------------------------------- /examples/with-nextjs/with-nextjs-v15/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * This is intended to be a basic starting point for linting in your app. 3 | * It relies on recommended configs out of the box for simplicity, but you can 4 | * and should modify this configuration to best suit your team's needs. 5 | */ 6 | 7 | /** @type {import('eslint').Linter.Config} */ 8 | module.exports = { 9 | root: true, 10 | parserOptions: { 11 | ecmaVersion: "latest", 12 | sourceType: "module", 13 | ecmaFeatures: { 14 | jsx: true, 15 | }, 16 | }, 17 | env: { 18 | browser: true, 19 | commonjs: true, 20 | es6: true, 21 | }, 22 | ignorePatterns: ["!**/.server", "!**/.client"], 23 | 24 | // Base config 25 | extends: ["eslint:recommended"], 26 | 27 | overrides: [ 28 | // React 29 | { 30 | files: ["**/*.{js,jsx,ts,tsx}"], 31 | plugins: ["react", "jsx-a11y"], 32 | extends: [ 33 | "plugin:react/recommended", 34 | "plugin:react/jsx-runtime", 35 | "plugin:react-hooks/recommended", 36 | "plugin:jsx-a11y/recommended", 37 | ], 38 | settings: { 39 | react: { 40 | version: "detect", 41 | }, 42 | formComponents: ["Form"], 43 | linkComponents: [ 44 | { name: "Link", linkAttribute: "to" }, 45 | { name: "NavLink", linkAttribute: "to" }, 46 | ], 47 | "import/resolver": { 48 | typescript: {}, 49 | }, 50 | }, 51 | }, 52 | 53 | // Typescript 54 | { 55 | files: ["**/*.{ts,tsx}"], 56 | plugins: ["@typescript-eslint", "import"], 57 | parser: "@typescript-eslint/parser", 58 | settings: { 59 | "import/internal-regex": "^~/", 60 | "import/resolver": { 61 | node: { 62 | extensions: [".ts", ".tsx"], 63 | }, 64 | typescript: { 65 | alwaysTryTypes: true, 66 | }, 67 | }, 68 | }, 69 | extends: [ 70 | "plugin:@typescript-eslint/recommended", 71 | "plugin:import/recommended", 72 | "plugin:import/typescript", 73 | ], 74 | }, 75 | 76 | // Node 77 | { 78 | files: [".eslintrc.cjs"], 79 | env: { 80 | node: true, 81 | }, 82 | }, 83 | ], 84 | }; 85 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | /.cache 4 | /build 5 | .env 6 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to Remix! 2 | 3 | - 📖 [Remix docs](https://remix.run/docs) 4 | 5 | ## Development 6 | 7 | Run the dev server: 8 | 9 | ```shellscript 10 | npm run dev 11 | ``` 12 | 13 | ## Deployment 14 | 15 | First, build your app for production: 16 | 17 | ```sh 18 | npm run build 19 | ``` 20 | 21 | Then run the app in production mode: 22 | 23 | ```sh 24 | npm start 25 | ``` 26 | 27 | Now you'll need to pick a host to deploy it to. 28 | 29 | ### DIY 30 | 31 | If you're familiar with deploying Node applications, the built-in Remix app server is production-ready. 32 | 33 | Make sure to deploy the output of `npm run build` 34 | 35 | - `build/server` 36 | - `build/client` 37 | 38 | ## Styling 39 | 40 | This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever css framework you prefer. See the [Vite docs on css](https://vitejs.dev/guide/features.html#css) for more information. 41 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/app/common/actions.ts: -------------------------------------------------------------------------------- 1 | import {ActionDefinition} from '@react-form-builder/core' 2 | 3 | export const actions = { 4 | submitForm: ActionDefinition.functionalAction(async (e) => { 5 | try { 6 | await e.store.formData.validate() 7 | } catch (e) { 8 | console.warn(e) 9 | } 10 | if (Object.keys(e.store.formData.errors).length < 1) { 11 | console.log(e.store.formData.data) 12 | } else { 13 | console.error(e.store.formData.errors) 14 | } 15 | }) 16 | }; 17 | 18 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/app/common/form.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "errorType": "RsErrorMessage", 4 | "form": { 5 | "key": "Screen", 6 | "type": "Screen", 7 | "props": {}, 8 | "children": [ 9 | { 10 | "key": "fullName", 11 | "type": "RsInput", 12 | "props": { 13 | "label": { 14 | "value": "Full name" 15 | } 16 | }, 17 | "schema": { 18 | "validations": [ 19 | { 20 | "key": "isFullName", 21 | "type": "custom" 22 | } 23 | ] 24 | } 25 | }, 26 | { 27 | "key": "checkinDate", 28 | "type": "RsDatePicker", 29 | "props": { 30 | "label": { 31 | "value": "Check-in date" 32 | } 33 | }, 34 | "schema": { 35 | "validations": [ 36 | { 37 | "key": "dateInTheFuture", 38 | "type": "custom" 39 | } 40 | ] 41 | } 42 | }, 43 | { 44 | "key": "guestCount", 45 | "type": "RsNumberFormat", 46 | "props": { 47 | "label": { 48 | "value": "Guest count" 49 | } 50 | }, 51 | "schema": { 52 | "validations": [ 53 | { 54 | "key": "checkGuestCount", 55 | "type": "custom" 56 | } 57 | ] 58 | } 59 | }, 60 | { 61 | "key": "rsButton1", 62 | "type": "RsButton", 63 | "props": { 64 | "children": { 65 | "value": "Send" 66 | } 67 | }, 68 | "events": { 69 | "onClick": [ 70 | { 71 | "name": "submitForm", 72 | "type": "custom" 73 | } 74 | ] 75 | } 76 | } 77 | ] 78 | }, 79 | "localization": {}, 80 | "languages": [ 81 | { 82 | "code": "en", 83 | "dialect": "US", 84 | "name": "English", 85 | "description": "American English", 86 | "bidi": "ltr" 87 | } 88 | ], 89 | "defaultLanguage": "en-US" 90 | } 91 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/app/common/styles.module.css: -------------------------------------------------------------------------------- 1 | .page { 2 | --gray-rgb: 0, 0, 0; 3 | --gray-alpha-200: rgba(var(--gray-rgb), 0.08); 4 | --gray-alpha-100: rgba(var(--gray-rgb), 0.05); 5 | 6 | --button-primary-hover: #383838; 7 | --button-secondary-hover: #f2f2f2; 8 | 9 | display: grid; 10 | grid-template-rows: auto; 11 | align-items: center; 12 | justify-items: center; 13 | padding: 80px; 14 | gap: 64px; 15 | font-family: var(--font-geist-sans); 16 | } 17 | 18 | @media (prefers-color-scheme: dark) { 19 | .page { 20 | --gray-rgb: 255, 255, 255; 21 | --gray-alpha-200: rgba(var(--gray-rgb), 0.145); 22 | --gray-alpha-100: rgba(var(--gray-rgb), 0.06); 23 | 24 | --button-primary-hover: #ccc; 25 | --button-secondary-hover: #1a1a1a; 26 | } 27 | } 28 | 29 | .main { 30 | display: flex; 31 | flex-direction: column; 32 | justify-content: space-around; 33 | align-items: center; 34 | gap: 32px; 35 | grid-row-start: 2; 36 | height: 100svh; 37 | max-height: 100svh; 38 | min-height: 100svh; 39 | } 40 | 41 | .main code { 42 | font-family: inherit; 43 | background: var(--gray-alpha-100); 44 | padding: 2px 4px; 45 | border-radius: 4px; 46 | font-weight: 600; 47 | } 48 | 49 | .ctas { 50 | display: flex; 51 | gap: 16px; 52 | } 53 | 54 | .ctas a { 55 | appearance: none; 56 | border-radius: 128px; 57 | height: 48px; 58 | padding: 0 20px; 59 | border: none; 60 | border: 1px solid transparent; 61 | transition: 62 | background 0.2s, 63 | color 0.2s, 64 | border-color 0.2s; 65 | cursor: pointer; 66 | display: flex; 67 | align-items: center; 68 | justify-content: center; 69 | font-size: 16px; 70 | line-height: 20px; 71 | font-weight: 500; 72 | } 73 | 74 | a.primary { 75 | background: var(--foreground); 76 | color: var(--background); 77 | gap: 8px; 78 | } 79 | 80 | a.secondary { 81 | border-color: var(--gray-alpha-200); 82 | min-width: 180px; 83 | } 84 | 85 | .footer { 86 | grid-row-start: 3; 87 | display: flex; 88 | gap: 2em; 89 | height: 3em; 90 | } 91 | 92 | .footer a { 93 | display: flex; 94 | align-items: center; 95 | gap: 8px; 96 | } 97 | 98 | .footer img { 99 | flex-shrink: 0; 100 | } 101 | 102 | /* Enable hover only on non-touch devices */ 103 | @media (hover: hover) and (pointer: fine) { 104 | a.primary:hover { 105 | background: var(--button-primary-hover); 106 | border-color: transparent; 107 | } 108 | 109 | a.secondary:hover { 110 | background: var(--button-secondary-hover); 111 | border-color: transparent; 112 | } 113 | 114 | .footer a:hover { 115 | text-decoration: underline; 116 | text-underline-offset: 4px; 117 | } 118 | } 119 | 120 | @media (max-width: 600px) { 121 | .page { 122 | padding: 32px; 123 | padding-bottom: 80px; 124 | } 125 | 126 | .main { 127 | align-items: center; 128 | } 129 | 130 | .main ol { 131 | text-align: center; 132 | } 133 | 134 | .ctas { 135 | flex-direction: column; 136 | } 137 | 138 | .ctas a { 139 | font-size: 14px; 140 | height: 40px; 141 | padding: 0 16px; 142 | } 143 | 144 | a.secondary { 145 | min-width: auto; 146 | } 147 | 148 | .footer { 149 | flex-wrap: wrap; 150 | align-items: center; 151 | justify-content: center; 152 | } 153 | } 154 | 155 | .booking-form { 156 | max-width: 600px; 157 | margin: 20px auto; 158 | padding: 20px; 159 | border-radius: 8px; 160 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); 161 | background-color: #fff; 162 | } 163 | .booking-form-item { 164 | margin-bottom: 20px; 165 | } 166 | 167 | .debug-output { 168 | max-width: 600px; 169 | margin: 20px auto; 170 | padding: 10px; 171 | border: 1px solid #ddd; 172 | border-radius: 4px; 173 | background-color: #f9f9f9; 174 | font-family: monospace; 175 | font-size: 12px; 176 | } 177 | .debug-output pre { 178 | margin: 0; 179 | padding: 0; 180 | border: none; 181 | background-color: transparent; 182 | font-size: inherit; 183 | } 184 | 185 | .underlined { 186 | text-decoration: underline; 187 | } 188 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/app/common/validators.ts: -------------------------------------------------------------------------------- 1 | import {RuleValidatorResult, Validators} from '@react-form-builder/core' 2 | 3 | export const customValidators: Validators = { 4 | 'string': { 5 | 'isFullName': { 6 | validate: (value: string): RuleValidatorResult => { 7 | const valid = !!value && value.trim().split(' ').length > 1 8 | 9 | return valid ? true : 'Please enter a full name' 10 | } 11 | }, 12 | 'emailAddressValid': { 13 | validate: (value: string) => { 14 | const pattern = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$/ 15 | 16 | return pattern.test(value) 17 | } 18 | } 19 | }, 20 | 'number': { 21 | 'checkGuestCount': { 22 | validate: (value: string) => { 23 | const num = parseInt(value) 24 | 25 | if (isFinite(num) && num > 0 && num < 7) return true 26 | 27 | return 'Guest count must be from 1 to 6' 28 | } 29 | } 30 | }, 31 | 'date': { 32 | 'dateInTheFuture': { 33 | validate: (value: Date) => { 34 | const today = new Date() 35 | const date = new Date(value) 36 | 37 | today.setHours(0, 0, 0, 0) 38 | date.setHours(0, 0, 0, 0) 39 | 40 | if(+date >= +today) return true 41 | 42 | return 'Please select valid date' 43 | } 44 | } 45 | }, 46 | 47 | } 48 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/app/components/builder.client.tsx: -------------------------------------------------------------------------------- 1 | import {FormBuilder, IFormStorage} from '@react-form-builder/designer' 2 | import {actions} from '~/common/actions.js' 3 | import {customValidators} from '~/common/validators.js' 4 | 5 | import form from '~/common/form.json' 6 | 7 | import { 8 | formEngineRsuiteCssLoader, 9 | ltrCssLoader, 10 | RsLocalizationWrapper, 11 | rSuiteComponents, 12 | rsErrorMessage, 13 | rtlCssLoader, rsTooltip 14 | } from '@react-form-builder/components-rsuite' 15 | import {BiDi, BuilderView} from '@react-form-builder/core' 16 | 17 | const components = [...rSuiteComponents, rsErrorMessage] 18 | .map(definer => definer.build()) 19 | 20 | const formName = 'nextForm' 21 | 22 | const formStorage: IFormStorage = { 23 | getForm: async () => localStorage.getItem(formName) || JSON.stringify(form), 24 | saveForm: async (_, form) => localStorage.setItem(formName, form), 25 | getFormNames: () => Promise.resolve([formName]), 26 | removeForm: () => Promise.resolve() 27 | } 28 | 29 | const loadForm = () => formStorage.getForm('') 30 | 31 | const view = new BuilderView(components) 32 | .withErrorMeta(rsErrorMessage.build()) 33 | .withTooltipMeta(rsTooltip.build()) 34 | .withViewerWrapper(RsLocalizationWrapper) 35 | .withCssLoader(BiDi.LTR, ltrCssLoader) 36 | .withCssLoader(BiDi.RTL, rtlCssLoader) 37 | .withCssLoader('common', formEngineRsuiteCssLoader) 38 | 39 | .withErrorMeta(rsErrorMessage.build()) 40 | .withTooltipMeta(rsTooltip.build()) 41 | 42 | // We're hiding the form panel because it's not fully functional in this example 43 | const customization = { 44 | Forms_Tab: { 45 | hidden: true 46 | } 47 | } 48 | 49 | export default function BuilderClient() { 50 | return
51 | 60 |
61 | } 62 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/app/components/message.module.css: -------------------------------------------------------------------------------- 1 | dialog.dialog { 2 | border: none; 3 | border-radius: 8px; 4 | padding: 20px; 5 | max-width: 400px; 6 | width: 100%; 7 | transform: translate(50%); 8 | background-color: #fff; 9 | box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); 10 | font-family: Arial, sans-serif; 11 | } 12 | dialog.dialog p { 13 | margin: 0 0 20px; 14 | font-size: 16px; 15 | color: #333; 16 | } 17 | dialog.dialog form { 18 | display: flex; 19 | justify-content: flex-end; 20 | } 21 | dialog.dialog button { 22 | padding: 10px 15px; 23 | border: none; 24 | border-radius: 4px; 25 | background-color: #4caf50; /* Green for OK button */ 26 | color: white; 27 | cursor: pointer; 28 | transition: background-color 0.3s; 29 | } 30 | dialog.dialog button:hover { 31 | background-color: #388e3c; /* Darker green on hover */ 32 | } 33 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/app/components/message.tsx: -------------------------------------------------------------------------------- 1 | import styles from './message.module.css' 2 | import {createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState} from 'react' 3 | 4 | interface UseToggle { 5 | isOpen: boolean, 6 | toggle: () => void, 7 | setOpen: (isOpen: boolean) => void, 8 | open: () => void, 9 | close: () => void 10 | } 11 | 12 | export const useToggle = (): UseToggle => { 13 | const [isOpen, setOpen] = useState(false) 14 | 15 | const toggle = useCallback(() => { 16 | setOpen((isOpen) => !isOpen) 17 | }, []) 18 | 19 | const close = useCallback(() => setOpen(false), []) 20 | const open = useCallback(() => setOpen(true), []) 21 | 22 | return useMemo(() => ({ 23 | isOpen, 24 | toggle, 25 | setOpen, 26 | close, 27 | open 28 | }), [isOpen, toggle, setOpen, close, open]) 29 | } 30 | 31 | interface MessageContext { 32 | open: (message: string) => void, 33 | close: () => void, 34 | isOpen: boolean, 35 | setOpen: (isOpen: boolean) => void, 36 | message: string 37 | } 38 | 39 | const MessageContext = createContext({ 40 | open: (_: string) => {}, 41 | close: () => {}, 42 | isOpen: false, 43 | setOpen: (_: boolean) => {}, 44 | message: '' 45 | }); 46 | 47 | export const MessageProvider = ({ children }: {children: ReactNode}) => { 48 | const {isOpen, close, setOpen} = useToggle() 49 | const [message, setMessage] = useState('') 50 | 51 | const openDialog = useCallback((msg: string) => { 52 | setMessage(msg) 53 | setOpen(true) 54 | }, [setOpen]) 55 | 56 | const contextValue = useMemo(() => ({ 57 | setOpen, isOpen, open: openDialog, close, message 58 | }), [isOpen, setOpen, openDialog, close, message]) 59 | 60 | return ( 61 | 62 | {children} 63 | 64 | ); 65 | }; 66 | 67 | export const useMessage = (): MessageContext => useContext(MessageContext); 68 | 69 | export const Message = ({open: propsOpen = false}: { 70 | open?: boolean 71 | }) => { 72 | const {close, isOpen, setOpen, message} = useMessage() 73 | 74 | useEffect(() => { 75 | setOpen(propsOpen) 76 | }, [propsOpen, setOpen]) 77 | 78 | return 79 |

80 |

81 | 82 |
83 |
84 | } 85 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/app/components/viewer.client.tsx: -------------------------------------------------------------------------------- 1 | import {ReactNode, useMemo} from 'react' 2 | import { 3 | formEngineRsuiteCssLoader, 4 | ltrCssLoader, 5 | RsLocalizationWrapper, 6 | rSuiteComponents, 7 | rsErrorMessage, 8 | rtlCssLoader 9 | } from '@react-form-builder/components-rsuite' 10 | import {ActionDefinition, BiDi, createView, FormViewer} from '@react-form-builder/core' 11 | import {IFormStorage} from '@react-form-builder/designer' 12 | 13 | import {Message, MessageProvider, useMessage} from '~/components/message' 14 | import form from '~/common/form.json' 15 | import {customValidators} from '~/common/validators' 16 | 17 | const viewerComponents = rSuiteComponents.map(c => c.build().model) 18 | viewerComponents.push(rsErrorMessage.build().model) 19 | 20 | const formName = 'nextForm' 21 | 22 | const formStorage: IFormStorage = { 23 | getForm: async () => localStorage.getItem(formName) || JSON.stringify(form), 24 | saveForm: async (_, form) => localStorage.setItem(formName, form), 25 | getFormNames: () => Promise.resolve([formName]), 26 | removeForm: () => Promise.resolve() 27 | } 28 | 29 | const loadForm = () => formStorage.getForm('') 30 | 31 | const view = createView(viewerComponents) 32 | .withViewerWrapper(RsLocalizationWrapper) 33 | .withCssLoader(BiDi.LTR, ltrCssLoader) 34 | .withCssLoader(BiDi.RTL, rtlCssLoader) 35 | .withCssLoader('common', formEngineRsuiteCssLoader) 36 | 37 | const ViewerWrap = ({children}: { children: ReactNode }) => ( 38 | 39 | {children} 40 | 41 | 42 | ) 43 | 44 | const ViewerInner = () => { 45 | const {open, setOpen} = useMessage() 46 | 47 | const actions = useMemo(() => ({ 48 | submitForm: ActionDefinition.functionalAction(async (e) => { 49 | const formData = e.store.formData 50 | 51 | setOpen(false) 52 | 53 | try { 54 | await formData.validate() 55 | } catch (e) { 56 | open(String(e)) 57 | } 58 | if (Object.keys(formData.errors).length < 1) { 59 | open('Thank you!') 60 | } else { 61 | const message = Object.entries(formData.errors).reduce>((acc, [k, v]) => { 62 | acc.push([k, v].join(' - ')) 63 | return acc 64 | }, []).join('
') 65 | open(message) 66 | } 67 | }) 68 | }), [open, setOpen]) 69 | 70 | return ( 71 | 78 | ) 79 | } 80 | 81 | export default function ViewerClient() { 82 | return
83 |
84 | 85 | 86 | 87 |
88 |
89 | } 90 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/app/entry.client.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * By default, Remix will handle hydrating your app on the client for you. 3 | * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ 4 | * For more information, see https://remix.run/file-conventions/entry.client 5 | */ 6 | 7 | import { RemixBrowser } from "@remix-run/react"; 8 | import { startTransition, StrictMode } from "react"; 9 | import { hydrateRoot } from "react-dom/client"; 10 | 11 | startTransition(() => { 12 | hydrateRoot( 13 | document, 14 | 15 | 16 | 17 | ); 18 | }); 19 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/app/entry.server.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * By default, Remix will handle generating the HTTP Response for you. 3 | * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨ 4 | * For more information, see https://remix.run/file-conventions/entry.server 5 | */ 6 | 7 | import { PassThrough } from "node:stream"; 8 | 9 | import type { AppLoadContext, EntryContext } from "@remix-run/node"; 10 | import { createReadableStreamFromReadable } from "@remix-run/node"; 11 | import { RemixServer } from "@remix-run/react"; 12 | import { isbot } from "isbot"; 13 | import { renderToPipeableStream } from "react-dom/server"; 14 | 15 | const ABORT_DELAY = 5_000; 16 | 17 | export default function handleRequest( 18 | request: Request, 19 | responseStatusCode: number, 20 | responseHeaders: Headers, 21 | remixContext: EntryContext, 22 | // This is ignored so we can keep it in the template for visibility. Feel 23 | // free to delete this parameter in your app if you're not using it! 24 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 25 | loadContext: AppLoadContext 26 | ) { 27 | return isbot(request.headers.get("user-agent") || "") 28 | ? handleBotRequest( 29 | request, 30 | responseStatusCode, 31 | responseHeaders, 32 | remixContext 33 | ) 34 | : handleBrowserRequest( 35 | request, 36 | responseStatusCode, 37 | responseHeaders, 38 | remixContext 39 | ); 40 | } 41 | 42 | function handleBotRequest( 43 | request: Request, 44 | responseStatusCode: number, 45 | responseHeaders: Headers, 46 | remixContext: EntryContext 47 | ) { 48 | return new Promise((resolve, reject) => { 49 | let shellRendered = false; 50 | const { pipe, abort } = renderToPipeableStream( 51 | , 56 | { 57 | onAllReady() { 58 | shellRendered = true; 59 | const body = new PassThrough(); 60 | const stream = createReadableStreamFromReadable(body); 61 | 62 | responseHeaders.set("Content-Type", "text/html"); 63 | 64 | resolve( 65 | new Response(stream, { 66 | headers: responseHeaders, 67 | status: responseStatusCode, 68 | }) 69 | ); 70 | 71 | pipe(body); 72 | }, 73 | onShellError(error: unknown) { 74 | reject(error); 75 | }, 76 | onError(error: unknown) { 77 | responseStatusCode = 500; 78 | // Log streaming rendering errors from inside the shell. Don't log 79 | // errors encountered during initial shell rendering since they'll 80 | // reject and get logged in handleDocumentRequest. 81 | if (shellRendered) { 82 | console.error(error); 83 | } 84 | }, 85 | } 86 | ); 87 | 88 | setTimeout(abort, ABORT_DELAY); 89 | }); 90 | } 91 | 92 | function handleBrowserRequest( 93 | request: Request, 94 | responseStatusCode: number, 95 | responseHeaders: Headers, 96 | remixContext: EntryContext 97 | ) { 98 | return new Promise((resolve, reject) => { 99 | let shellRendered = false; 100 | const { pipe, abort } = renderToPipeableStream( 101 | , 106 | { 107 | onShellReady() { 108 | shellRendered = true; 109 | const body = new PassThrough(); 110 | const stream = createReadableStreamFromReadable(body); 111 | 112 | responseHeaders.set("Content-Type", "text/html"); 113 | 114 | resolve( 115 | new Response(stream, { 116 | headers: responseHeaders, 117 | status: responseStatusCode, 118 | }) 119 | ); 120 | 121 | pipe(body); 122 | }, 123 | onShellError(error: unknown) { 124 | reject(error); 125 | }, 126 | onError(error: unknown) { 127 | responseStatusCode = 500; 128 | // Log streaming rendering errors from inside the shell. Don't log 129 | // errors encountered during initial shell rendering since they'll 130 | // reject and get logged in handleDocumentRequest. 131 | if (shellRendered) { 132 | console.error(error); 133 | } 134 | }, 135 | } 136 | ); 137 | 138 | setTimeout(abort, ABORT_DELAY); 139 | }); 140 | } 141 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/app/root.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Links, 3 | Meta, 4 | Outlet, 5 | Scripts, 6 | ScrollRestoration, 7 | } from "@remix-run/react"; 8 | import type { LinksFunction } from "@remix-run/node"; 9 | 10 | import "./tailwind.css"; 11 | 12 | export const links: LinksFunction = () => [ 13 | { rel: "preconnect", href: "https://fonts.googleapis.com" }, 14 | { 15 | rel: "preconnect", 16 | href: "https://fonts.gstatic.com", 17 | crossOrigin: "anonymous", 18 | }, 19 | { 20 | rel: "stylesheet", 21 | href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap", 22 | }, 23 | ]; 24 | 25 | export function Layout({ children }: { children: React.ReactNode }) { 26 | return ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | {children} 36 | 37 | 38 | 39 | 40 | ); 41 | } 42 | 43 | export default function App() { 44 | return ; 45 | } 46 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/app/routes/_index.tsx: -------------------------------------------------------------------------------- 1 | import type { MetaFunction } from "@remix-run/node"; 2 | 3 | export const meta: MetaFunction = () => { 4 | return [ 5 | { title: "FormEngine with Remix" }, 6 | { name: "description", content: "Welcome to FormEngine!" }, 7 | ]; 8 | }; 9 | 10 | export default function Index() { 11 | return ( 12 |
13 |
14 |
15 |

16 | Welcome to FormEngine 17 |

18 |
19 | Designer 24 | Viewer 29 |
30 |
31 | 41 | 61 |
62 |
63 | ); 64 | } 65 | 66 | const resources = [ 67 | { 68 | href: 'https://formengine.io/documentation/', 69 | text: 'Documentation', 70 | icon: ( 71 | 72 | 75 | 76 | ), 77 | }, 78 | { 79 | href: 'https://formengine.io/documentation/usage-with-remix', 80 | text: 'With Remix', 81 | icon: ( 82 | 83 | 86 | 87 | ), 88 | }, 89 | { 90 | href: 'https://formengine.io/pricing', 91 | text: 'FormEngine', 92 | icon: ( 93 | 94 | 95 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | ), 106 | }, 107 | ]; 108 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/app/routes/builder._index.tsx: -------------------------------------------------------------------------------- 1 | import {lazy, useEffect, useState} from 'react' 2 | 3 | const BuilderClient = lazy(() => import("~/components/builder.client.js")); 4 | 5 | export default function Builder_index() { 6 | const [mounted, setMounted] = useState(false); 7 | 8 | useEffect(() => { 9 | setMounted(true); 10 | }, []); 11 | 12 | return mounted ? : null; 13 | } 14 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/app/routes/viewer._index.tsx: -------------------------------------------------------------------------------- 1 | import {lazy, useEffect, useState} from 'react' 2 | 3 | const ViewerClient = lazy(() => import("~/components/viewer.client.js")); 4 | 5 | export default function Viewer_index() { 6 | const [mounted, setMounted] = useState(false); 7 | 8 | useEffect(() => { 9 | setMounted(true); 10 | }, []); 11 | 12 | return mounted ? : null; 13 | } 14 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/app/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html, 6 | body { 7 | @apply bg-white dark:bg-gray-950; 8 | 9 | @media (prefers-color-scheme: dark) { 10 | color-scheme: dark; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "formengine-with-remix-v2", 3 | "private": true, 4 | "author": "Optimajet", 5 | "homepage": "https://formengine.io/", 6 | "sideEffects": false, 7 | "type": "module", 8 | "scripts": { 9 | "build": "remix vite:build", 10 | "dev": "remix vite:dev", 11 | "lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .", 12 | "start": "remix-serve ./build/server/index.js", 13 | "typecheck": "tsc" 14 | }, 15 | "dependencies": { 16 | "@react-form-builder/components-rsuite": "^2.1.0", 17 | "@react-form-builder/core": "^2.1.0", 18 | "@react-form-builder/designer": "^2.1.0", 19 | "@remix-run/node": "^2.14.0", 20 | "@remix-run/react": "^2.14.0", 21 | "@remix-run/serve": "^2.16.4", 22 | "isbot": "^4.1.0", 23 | "react": "^18.2.0", 24 | "react-dom": "^18.2.0" 25 | }, 26 | "devDependencies": { 27 | "@remix-run/dev": "^2.14.0", 28 | "@types/react": "^18.2.20", 29 | "@types/react-dom": "^18.2.7", 30 | "@typescript-eslint/eslint-plugin": "^6.7.4", 31 | "@typescript-eslint/parser": "^6.7.4", 32 | "autoprefixer": "^10.4.19", 33 | "eslint": "^8.38.0", 34 | "eslint-import-resolver-typescript": "^3.6.1", 35 | "eslint-plugin-import": "^2.28.1", 36 | "eslint-plugin-jsx-a11y": "^6.7.1", 37 | "eslint-plugin-react": "^7.33.2", 38 | "eslint-plugin-react-hooks": "^4.6.0", 39 | "postcss": "^8.4.38", 40 | "tailwindcss": "^3.4.4", 41 | "typescript": "^5.1.6", 42 | "vite": "^5.4.19", 43 | "vite-tsconfig-paths": "^4.2.1" 44 | }, 45 | "engines": { 46 | "node": ">=20.0.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimajet/formengine/1d25e89317e9b3c99db2f00f328712328a673713/examples/with-remix/with-remix-v2/public/favicon.ico -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/public/logo-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/public/logo-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | export default { 4 | content: ["./app/**/{**,.client,.server}/**/*.{js,jsx,ts,tsx}"], 5 | theme: { 6 | extend: { 7 | fontFamily: { 8 | sans: [ 9 | "Inter", 10 | "ui-sans-serif", 11 | "system-ui", 12 | "sans-serif", 13 | "Apple Color Emoji", 14 | "Segoe UI Emoji", 15 | "Segoe UI Symbol", 16 | "Noto Color Emoji", 17 | ], 18 | }, 19 | }, 20 | }, 21 | plugins: [], 22 | } satisfies Config; 23 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "**/*.ts", 4 | "**/*.tsx", 5 | "**/.server/**/*.ts", 6 | "**/.server/**/*.tsx", 7 | "**/.client/**/*.ts", 8 | "**/.client/**/*.tsx" 9 | ], 10 | "compilerOptions": { 11 | "lib": ["DOM", "DOM.Iterable", "ES2022"], 12 | "types": ["@remix-run/node", "vite/client"], 13 | "isolatedModules": true, 14 | "esModuleInterop": true, 15 | "jsx": "react-jsx", 16 | "module": "ESNext", 17 | "moduleResolution": "Bundler", 18 | "resolveJsonModule": true, 19 | "target": "ES2022", 20 | "strict": true, 21 | "allowJs": true, 22 | "skipLibCheck": true, 23 | "forceConsistentCasingInFileNames": true, 24 | "baseUrl": ".", 25 | "paths": { 26 | "~/*": ["./app/*"] 27 | }, 28 | 29 | // Vite takes care of building everything, not tsc. 30 | "noEmit": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/with-remix/with-remix-v2/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { vitePlugin as remix } from "@remix-run/dev"; 2 | import { defineConfig } from "vite"; 3 | import tsconfigPaths from "vite-tsconfig-paths"; 4 | 5 | declare module "@remix-run/node" { 6 | interface Future { 7 | v3_singleFetch: true; 8 | } 9 | } 10 | 11 | export default defineConfig({ 12 | plugins: [ 13 | remix({ 14 | future: { 15 | v3_fetcherPersist: true, 16 | v3_relativeSplatPath: true, 17 | v3_throwAbortReason: true, 18 | v3_singleFetch: true, 19 | v3_lazyRouteDiscovery: true, 20 | }, 21 | }), 22 | tsconfigPaths(), 23 | ], 24 | ssr: { 25 | noExternal: [ 26 | 'monaco-editor', 27 | 'monaco-editor/react', 28 | "@react-form-builder/designer", 29 | "@react-form-builder/core", 30 | "@react-form-builder/components-rsuite" 31 | ], 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "formengine", 3 | "version": "4.2.0", 4 | "description": "Example application", 5 | "author": "Optimajet", 6 | "homepage": "https://formengine.io/", 7 | "private": true, 8 | "dependencies": { 9 | "@react-form-builder/components-rsuite": "^4.2.0", 10 | "@react-form-builder/core": "^4.2.0", 11 | "@react-form-builder/designer": "^4.2.0", 12 | "@testing-library/jest-dom": "^5.17.0", 13 | "@testing-library/react": "^13.4.0", 14 | "@testing-library/user-event": "^13.5.0", 15 | "@types/jest": "^27.5.2", 16 | "@types/node": "^16.18.58", 17 | "@types/react": "^18.2.28", 18 | "@types/react-dom": "^18.2.13", 19 | "localforage": "^1.10.0", 20 | "match-sorter": "^6.3.1", 21 | "react": "^18.2.0", 22 | "react-dom": "^18.2.0", 23 | "react-router-dom": "^6.16.0", 24 | "react-scripts": "5.0.1", 25 | "sort-by": "^1.2.0", 26 | "typescript": "^4.9.5", 27 | "web-vitals": "^2.1.4" 28 | }, 29 | "scripts": { 30 | "start": "react-scripts start", 31 | "build": "react-scripts build", 32 | "test": "react-scripts test", 33 | "eject": "react-scripts eject" 34 | }, 35 | "eslintConfig": { 36 | "extends": [ 37 | "react-app", 38 | "react-app/jest" 39 | ] 40 | }, 41 | "browserslist": { 42 | "production": [ 43 | ">0.2%", 44 | "not dead", 45 | "not op_mini all" 46 | ], 47 | "development": [ 48 | "last 1 chrome version", 49 | "last 1 firefox version", 50 | "last 1 safari version" 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimajet/formengine/1d25e89317e9b3c99db2f00f328712328a673713/public/favicon.ico -------------------------------------------------------------------------------- /public/form-builder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimajet/formengine/1d25e89317e9b3c99db2f00f328712328a673713/public/form-builder.png -------------------------------------------------------------------------------- /public/form-viewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimajet/formengine/1d25e89317e9b3c99db2f00f328712328a673713/public/form-viewer.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React Form Builder example application 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimajet/formengine/1d25e89317e9b3c99db2f00f328712328a673713/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimajet/formengine/1d25e89317e9b3c99db2f00f328712328a673713/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Form Builder example", 3 | "name": "React Form Builder example application", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /screenshots/builder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimajet/formengine/1d25e89317e9b3c99db2f00f328712328a673713/screenshots/builder.png -------------------------------------------------------------------------------- /screenshots/viewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/optimajet/formengine/1d25e89317e9b3c99db2f00f328712328a673713/screenshots/viewer.png -------------------------------------------------------------------------------- /src/ErrorPage.tsx: -------------------------------------------------------------------------------- 1 | import {useRouteError} from 'react-router-dom' 2 | 3 | export const ErrorPage = () => { 4 | const error: any = useRouteError() 5 | console.error(error) 6 | 7 | return ( 8 |
9 |

{error.statusText || error.message}

10 |

Sorry, an unexpected error has occurred.

11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/components/FormBuilderExample.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | formEngineRsuiteCssLoader, 3 | ltrCssLoader, 4 | rsErrorMessage, 5 | RsLocalizationWrapper, 6 | rsTooltip, 7 | rSuiteComponents, 8 | rtlCssLoader 9 | } from '@react-form-builder/components-rsuite' 10 | import {BiDi} from '@react-form-builder/core' 11 | import {BuilderView, FormBuilder} from '@react-form-builder/designer' 12 | import React from 'react' 13 | import * as SampleForm from './SampleForm.json' 14 | 15 | const builderComponents = rSuiteComponents.map(c => c.build()) 16 | const builderView = new BuilderView(builderComponents) 17 | .withErrorMeta(rsErrorMessage.build()) 18 | .withTooltipMeta(rsTooltip.build()) 19 | .withViewerWrapper(RsLocalizationWrapper) 20 | .withCssLoader(BiDi.LTR, ltrCssLoader) 21 | .withCssLoader(BiDi.RTL, rtlCssLoader) 22 | .withCssLoader('common', formEngineRsuiteCssLoader) 23 | 24 | const getForm = (_?: string) => JSON.stringify(SampleForm) 25 | 26 | export const FormBuilderExample = () => { 27 | return 28 | } 29 | -------------------------------------------------------------------------------- /src/components/FormViewerExample.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | formEngineRsuiteCssLoader, 3 | ltrCssLoader, 4 | rsErrorMessage, 5 | RsLocalizationWrapper, 6 | rSuiteComponents, 7 | rtlCssLoader 8 | } from '@react-form-builder/components-rsuite' 9 | import {BiDi, createView, FormViewer} from '@react-form-builder/core' 10 | import React from 'react' 11 | import * as SampleForm from './SampleForm.json' 12 | 13 | const viewerComponents = rSuiteComponents.map(c => c.build().model) 14 | viewerComponents.push(rsErrorMessage.build().model) 15 | 16 | const view = createView(viewerComponents) 17 | .withViewerWrapper(RsLocalizationWrapper) 18 | .withCssLoader(BiDi.LTR, ltrCssLoader) 19 | .withCssLoader(BiDi.RTL, rtlCssLoader) 20 | .withCssLoader('common', formEngineRsuiteCssLoader) 21 | 22 | const getForm = (_?: string) => JSON.stringify(SampleForm) 23 | 24 | export const FormViewerExample = () => { 25 | return
26 | 27 |
28 | } 29 | -------------------------------------------------------------------------------- /src/components/Main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const Main = () => { 4 | return <> 5 |

Drag & Drop Form Builder Library for React

6 |

Develop front-end drag and drop forms with ease, resulting in cost savings and reduced development 7 | timing

8 |

You can reach out more information on formengine.io.

9 |
10 |
11 |
12 | 13 | Form Builder 14 | 15 |
16 |
Form Builder
17 |
18 |
19 |
20 | 21 | Form Example 22 | 23 |
24 |
Form Example
25 |
26 |
27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/components/SampleForm.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1", 3 | "actions": { 4 | "loadData": { 5 | "body": "\n const [searchValue, loadCallback, currentDataLength] = e.args\n\n fetch ('https://gist.githubusercontent.com/rogargon/5534902/raw/434445021e155240ca78e378f10f70391dd594ea/countries.json')\n .then (data => data.json())\n .then (data => {\n const preparedData = data\n .filter(value => value.toLowerCase().includes(searchValue.toLowerCase()))\n .slice(currentDataLength, currentDataLength + 20)\n .map(value => ({value, label: value}))\n\n loadCallback(preparedData) \n })\n", 6 | "params": {} 7 | }, 8 | "showValidationResult": { 9 | "body": " const setVisible = (key, visible) => {\n const componentStore = e.store.form.componentTree.findByKey(key)?.store\n if (componentStore) {\n componentStore.renderWhen = {value: visible}\n }\n }\n const hasErrors = e.store.form.componentTree.hasErrors\n setVisible('errorMessage', hasErrors)\n setVisible('successMessage', !hasErrors)", 10 | "params": {} 11 | } 12 | }, 13 | "tooltipType": "RsTooltip", 14 | "errorType": "RsErrorMessage", 15 | "form": { 16 | "key": "screen 1", 17 | "type": "Screen", 18 | "props": { 19 | "error": { 20 | "value": {} 21 | } 22 | }, 23 | "children": [ 24 | { 25 | "key": "Header", 26 | "type": "RsHeader", 27 | "props": { 28 | "content": { 29 | "value": "Complex Master Form" 30 | }, 31 | "headerSize": { 32 | "value": "h4" 33 | } 34 | } 35 | }, 36 | { 37 | "key": "Email hint", 38 | "type": "RsLabel", 39 | "props": { 40 | "text": { 41 | "value": "Use a permanent address where you can receive mail." 42 | } 43 | } 44 | }, 45 | { 46 | "key": "Main container", 47 | "type": "RsContainer", 48 | "props": {}, 49 | "css": { 50 | "desktop": { 51 | "string": "border: 1px solid lightgray;\nborder-radius: 6px;\npadding: 20px;" 52 | } 53 | }, 54 | "children": [ 55 | { 56 | "key": "Name container", 57 | "type": "RsContainer", 58 | "props": {}, 59 | "css": { 60 | "any": { 61 | "object": { 62 | "flexDirection": "row" 63 | } 64 | } 65 | }, 66 | "children": [ 67 | { 68 | "key": "First name", 69 | "type": "RsInput", 70 | "props": { 71 | "label": { 72 | "value": "First name" 73 | } 74 | }, 75 | "schema": { 76 | "type": "string", 77 | "validations": [ 78 | { 79 | "key": "required" 80 | } 81 | ], 82 | "autoValidate": true 83 | }, 84 | "tooltipProps": { 85 | "text": { 86 | "value": "Enter your first name here" 87 | } 88 | } 89 | }, 90 | { 91 | "key": "Last name", 92 | "type": "RsInput", 93 | "props": { 94 | "label": { 95 | "value": "Last name" 96 | } 97 | }, 98 | "schema": { 99 | "type": "string", 100 | "validations": [ 101 | { 102 | "key": "required" 103 | } 104 | ] 105 | } 106 | } 107 | ] 108 | }, 109 | { 110 | "key": "Email", 111 | "type": "RsInput", 112 | "props": { 113 | "label": { 114 | "value": "Email address" 115 | } 116 | }, 117 | "schema": { 118 | "type": "string", 119 | "validations": [ 120 | { 121 | "key": "email" 122 | }, 123 | { 124 | "key": "required" 125 | } 126 | ] 127 | } 128 | }, 129 | { 130 | "key": "Country 1", 131 | "type": "RsDropdown", 132 | "props": { 133 | "label": { 134 | "value": "Country / Region" 135 | }, 136 | "data": { 137 | "value": [] 138 | }, 139 | "defaultValue": { 140 | "value": "" 141 | }, 142 | "value": { 143 | "value": "" 144 | }, 145 | "placeholder": { 146 | "value": "United States" 147 | } 148 | }, 149 | "schema": { 150 | "type": "string", 151 | "validations": [] 152 | }, 153 | "events": { 154 | "onLoadData": [ 155 | { 156 | "name": "loadData", 157 | "type": "code" 158 | } 159 | ] 160 | } 161 | }, 162 | { 163 | "key": "Subheader", 164 | "type": "RsHeader", 165 | "props": { 166 | "content": { 167 | "value": "Subheader inside the form" 168 | }, 169 | "headerSize": { 170 | "value": "h5" 171 | } 172 | } 173 | }, 174 | { 175 | "key": "Email hint 2", 176 | "type": "RsLabel", 177 | "props": { 178 | "text": { 179 | "value": "Use a permanent address where you can receive mail." 180 | } 181 | } 182 | }, 183 | { 184 | "key": "Street address", 185 | "type": "RsInput", 186 | "props": { 187 | "label": { 188 | "value": "Street address" 189 | } 190 | } 191 | }, 192 | { 193 | "key": "Adress container", 194 | "type": "RsContainer", 195 | "props": {}, 196 | "css": { 197 | "any": { 198 | "object": { 199 | "flexDirection": "row" 200 | } 201 | } 202 | }, 203 | "children": [ 204 | { 205 | "key": "City", 206 | "type": "RsInput", 207 | "props": { 208 | "placeholder": { 209 | "value": "" 210 | }, 211 | "label": { 212 | "value": "City" 213 | } 214 | } 215 | }, 216 | { 217 | "key": "State", 218 | "type": "RsInput", 219 | "props": { 220 | "placeholder": { 221 | "value": "" 222 | }, 223 | "label": { 224 | "value": "State / Province" 225 | } 226 | } 227 | }, 228 | { 229 | "key": "ZIP", 230 | "type": "RsInput", 231 | "props": { 232 | "placeholder": { 233 | "value": "" 234 | }, 235 | "label": { 236 | "value": "ZIP / Postal" 237 | } 238 | } 239 | } 240 | ] 241 | }, 242 | { 243 | "key": "Country container", 244 | "type": "RsContainer", 245 | "props": {}, 246 | "css": { 247 | "any": { 248 | "object": { 249 | "flexDirection": "row" 250 | } 251 | } 252 | }, 253 | "children": [ 254 | { 255 | "key": "Country 2", 256 | "type": "RsDropdown", 257 | "props": { 258 | "label": { 259 | "value": "Country / Region" 260 | }, 261 | "data": { 262 | "value": [] 263 | }, 264 | "defaultValue": { 265 | "value": "" 266 | }, 267 | "placeholder": { 268 | "value": "United States" 269 | } 270 | }, 271 | "events": { 272 | "onLoadData": [ 273 | { 274 | "name": "loadData", 275 | "type": "code" 276 | } 277 | ] 278 | }, 279 | "schema": { 280 | "type": "string", 281 | "validations": [] 282 | } 283 | }, 284 | { 285 | "key": "Country 3", 286 | "type": "RsDropdown", 287 | "props": { 288 | "label": { 289 | "value": "Country / Region" 290 | }, 291 | "data": { 292 | "value": [] 293 | }, 294 | "defaultValue": { 295 | "value": "" 296 | }, 297 | "placeholder": { 298 | "value": "United States" 299 | } 300 | }, 301 | "events": { 302 | "onLoadData": [ 303 | { 304 | "name": "loadData", 305 | "type": "code" 306 | } 307 | ] 308 | }, 309 | "schema": { 310 | "type": "string", 311 | "validations": [] 312 | } 313 | }, 314 | { 315 | "key": "Country 4", 316 | "type": "RsDropdown", 317 | "props": { 318 | "label": { 319 | "value": "Country / Region" 320 | }, 321 | "data": { 322 | "value": [] 323 | }, 324 | "defaultValue": { 325 | "value": "" 326 | }, 327 | "placeholder": { 328 | "value": "United States" 329 | } 330 | }, 331 | "events": { 332 | "onLoadData": [ 333 | { 334 | "name": "loadData", 335 | "type": "code" 336 | } 337 | ] 338 | }, 339 | "schema": { 340 | "type": "string", 341 | "validations": [] 342 | } 343 | } 344 | ] 345 | }, 346 | { 347 | "key": "Website", 348 | "type": "RsInput", 349 | "props": { 350 | "label": { 351 | "value": "Website" 352 | }, 353 | "placeholder": { 354 | "value": "https://www.example.com" 355 | } 356 | }, 357 | "schema": { 358 | "type": "string", 359 | "validations": [ 360 | { 361 | "key": "url" 362 | } 363 | ] 364 | } 365 | }, 366 | { 367 | "key": "About", 368 | "type": "RsTextArea", 369 | "props": { 370 | "placeholder": { 371 | "value": "you@example.com" 372 | }, 373 | "label": { 374 | "value": "About" 375 | }, 376 | "rows": { 377 | "value": 4 378 | } 379 | } 380 | }, 381 | { 382 | "key": "About hint", 383 | "type": "RsLabel", 384 | "props": { 385 | "text": { 386 | "value": "Brief description for your profile. URLs are hyperlinked. " 387 | } 388 | } 389 | }, 390 | { 391 | "key": "Photo label", 392 | "type": "RsLabel", 393 | "props": { 394 | "text": { 395 | "value": "Photo" 396 | } 397 | } 398 | }, 399 | { 400 | "key": "Photo upload", 401 | "type": "RsUploader", 402 | "props": {} 403 | }, 404 | { 405 | "key": "Notifications header", 406 | "type": "RsHeader", 407 | "props": { 408 | "content": { 409 | "value": "Notifications" 410 | }, 411 | "headerSize": { 412 | "value": "h6" 413 | } 414 | } 415 | }, 416 | { 417 | "key": "Notifications hint", 418 | "type": "RsLabel", 419 | "props": { 420 | "text": { 421 | "value": "Use a permanent address where you can receive mail." 422 | } 423 | } 424 | }, 425 | { 426 | "key": "Email header", 427 | "type": "RsLabel", 428 | "props": { 429 | "text": { 430 | "value": "By Email" 431 | } 432 | }, 433 | "css": { 434 | "any": { 435 | "string": "font-weight: bold;" 436 | } 437 | } 438 | }, 439 | { 440 | "key": "Checkbox container 1", 441 | "type": "RsContainer", 442 | "props": {}, 443 | "css": { 444 | "desktop": { 445 | "string": "gap: 0;" 446 | } 447 | }, 448 | "children": [ 449 | { 450 | "key": "Comments checkbox", 451 | "type": "RsCheckbox", 452 | "props": { 453 | "children": { 454 | "value": "Comments" 455 | }, 456 | "checked": { 457 | "value": false 458 | }, 459 | "title": { 460 | "value": "" 461 | } 462 | } 463 | }, 464 | { 465 | "key": "Comments label", 466 | "type": "RsLabel", 467 | "props": { 468 | "text": { 469 | "value": "Get notified when someones posts a comment on a posting." 470 | } 471 | }, 472 | "wrapperCss": { 473 | "desktop": { 474 | "string": "margin-left: 35px" 475 | } 476 | } 477 | } 478 | ] 479 | }, 480 | { 481 | "key": "Checkbox container 2", 482 | "type": "RsContainer", 483 | "props": {}, 484 | "css": { 485 | "desktop": { 486 | "string": "gap: 0;" 487 | } 488 | }, 489 | "children": [ 490 | { 491 | "key": "Candidates checkbox", 492 | "type": "RsCheckbox", 493 | "props": { 494 | "checked": { 495 | "value": false 496 | }, 497 | "children": { 498 | "value": "Candidates" 499 | } 500 | } 501 | }, 502 | { 503 | "key": "Candidates label", 504 | "type": "RsLabel", 505 | "props": { 506 | "text": { 507 | "value": "Get notified when a candidate applies for a job." 508 | } 509 | }, 510 | "wrapperCss": { 511 | "desktop": { 512 | "string": "margin-left: 35px;" 513 | } 514 | } 515 | } 516 | ] 517 | }, 518 | { 519 | "key": "Checkbox container 3", 520 | "type": "RsContainer", 521 | "props": {}, 522 | "css": { 523 | "desktop": { 524 | "string": "gap: 0;" 525 | } 526 | }, 527 | "children": [ 528 | { 529 | "key": "Offers checkbox", 530 | "type": "RsCheckbox", 531 | "props": { 532 | "children": { 533 | "value": "Offers" 534 | }, 535 | "checked": { 536 | "value": false 537 | } 538 | } 539 | }, 540 | { 541 | "key": "Offers label", 542 | "type": "RsLabel", 543 | "props": { 544 | "text": { 545 | "value": "Get notified when a candidate accepts or rejects an offer." 546 | } 547 | }, 548 | "wrapperCss": { 549 | "desktop": { 550 | "string": "margin-left: 35px;" 551 | } 552 | } 553 | } 554 | ] 555 | }, 556 | { 557 | "key": "Push header", 558 | "type": "RsLabel", 559 | "props": { 560 | "text": { 561 | "value": "Push Notifications" 562 | } 563 | }, 564 | "css": { 565 | "desktop": { 566 | "any": "font-weight: bold;" 567 | } 568 | } 569 | }, 570 | { 571 | "key": "Push hint", 572 | "type": "RsLabel", 573 | "props": { 574 | "text": { 575 | "value": "These are delivered via SMS to your mobile phone." 576 | } 577 | } 578 | }, 579 | { 580 | "key": "Push select", 581 | "type": "RsRadioGroup", 582 | "props": { 583 | "label": { 584 | "value": "" 585 | }, 586 | "items": { 587 | "value": [ 588 | { 589 | "value": "Everything", 590 | "label": "Everything" 591 | }, 592 | { 593 | "value": "Same as email", 594 | "label": "Same as email" 595 | }, 596 | { 597 | "value": "No push notifications", 598 | "label": "No push notifications" 599 | } 600 | ] 601 | } 602 | } 603 | }, 604 | { 605 | "key": "Save button", 606 | "type": "RsButton", 607 | "props": { 608 | "block": { 609 | "value": false 610 | }, 611 | "appearance": { 612 | "value": "primary" 613 | }, 614 | "children": { 615 | "value": "Save" 616 | }, 617 | "size": { 618 | "value": "sm" 619 | }, 620 | "loading": { 621 | "computeType": "function", 622 | "fnSource": " return form.isValidating" 623 | } 624 | }, 625 | "events": { 626 | "onClick": [ 627 | { 628 | "name": "validate", 629 | "type": "common" 630 | }, 631 | { 632 | "name": "showValidationResult", 633 | "type": "code" 634 | } 635 | ] 636 | } 637 | }, 638 | { 639 | "key": "errorMessage", 640 | "type": "RsStaticContent", 641 | "props": { 642 | "content": { 643 | "value": "Not all fields of the form are filled in correctly. Correct the values of the form fields and try again." 644 | } 645 | }, 646 | "css": { 647 | "any": { 648 | "string": "color: red;" 649 | } 650 | }, 651 | "renderWhen": { 652 | "value": "false" 653 | } 654 | }, 655 | { 656 | "key": "successMessage", 657 | "type": "RsStaticContent", 658 | "props": { 659 | "content": { 660 | "value": "The form has been sent, thank you!" 661 | } 662 | }, 663 | "css": { 664 | "any": { 665 | "string": "color: green;" 666 | } 667 | }, 668 | "renderWhen": { 669 | "value": "false" 670 | } 671 | } 672 | ] 673 | } 674 | ] 675 | }, 676 | "localization": {}, 677 | "languages": [ 678 | { 679 | "code": "en", 680 | "dialect": "US", 681 | "name": "English", 682 | "description": "American English", 683 | "bidi": "ltr" 684 | } 685 | ], 686 | "defaultLanguage": "en-US" 687 | } 688 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | .card-container { 2 | margin-top: 5vh; 3 | display: flex; 4 | flex-direction: row; 5 | align-items: flex-start; 6 | height: 50vh; 7 | justify-content: space-around; 8 | } 9 | 10 | .page-title { 11 | text-align: center; 12 | font-size: 24px; 13 | display: block; 14 | font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; 15 | } 16 | 17 | .page-subtitle { 18 | text-align: center; 19 | font-size: 16px; 20 | display: block; 21 | font-weight: normal; 22 | font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; 23 | } 24 | 25 | .card { 26 | align-self: stretch; 27 | width: 45vw 28 | } 29 | 30 | .card-image { 31 | width: 100%; 32 | height: auto; 33 | border-radius: 1px; 34 | box-shadow: 2px 2px 16px 0 #88a5bf7a, -2px -2px 4px 0 #fffc; 35 | } 36 | 37 | .image-caption { 38 | font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace; 39 | text-align: center; 40 | } 41 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import {createBrowserRouter, RouterProvider} from 'react-router-dom' 4 | import {FormBuilderExample} from './components/FormBuilderExample' 5 | import {FormViewerExample} from './components/FormViewerExample' 6 | import {Main} from './components/Main' 7 | import {ErrorPage} from './ErrorPage' 8 | import './index.css' 9 | import reportWebVitals from './reportWebVitals' 10 | import {Root} from './routes/root' 11 | 12 | const root = ReactDOM.createRoot( 13 | document.getElementById('root') as HTMLElement 14 | ) 15 | 16 | const router = createBrowserRouter([ 17 | { 18 | path: '/', 19 | element: , 20 | errorElement: , 21 | children: [ 22 | { 23 | path: '', 24 | element:
, 25 | }, 26 | { 27 | path: 'form-builder', 28 | element: , 29 | }, 30 | { 31 | path: 'form-viewer', 32 | element: , 33 | }, 34 | ], 35 | }, 36 | ]) 37 | 38 | root.render( 39 | 40 | 41 | 42 | ) 43 | 44 | // If you want to start measuring performance in your app, pass a function 45 | // to log results (for example: reportWebVitals(console.log)) 46 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 47 | reportWebVitals() 48 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /src/routes/root.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {Outlet} from 'react-router-dom' 3 | 4 | export const Root = () => 5 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------