diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index f4b8ef9af3..c06eaf0585 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -27,6 +27,7 @@ body: - bootstrap-4 - chakra-ui - fluent-ui + - mantine - material-ui - mui - semantic-ui diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 9d3a1b8db1..cf9415b472 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -21,6 +21,7 @@ body: - bootstrap-4 - chakra-ui - fluent-ui + - mantine - material-ui - mui - semantic-ui diff --git a/.github/ISSUE_TEMPLATE/question_issue.yml b/.github/ISSUE_TEMPLATE/question_issue.yml index 8f46bc297c..3324b82cc0 100644 --- a/.github/ISSUE_TEMPLATE/question_issue.yml +++ b/.github/ISSUE_TEMPLATE/question_issue.yml @@ -21,6 +21,7 @@ body: - bootstrap-4 - chakra-ui - fluent-ui + - mantine - material-ui - mui - semantic-ui diff --git a/README.md b/README.md index 2fc11268d9..1f1e5f532d 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ - [Chakra UI](https://github.com/rjsf-team/react-jsonschema-form/tree/main/packages/chakra-ui) - [Fluent UI](https://github.com/rjsf-team/react-jsonschema-form/tree/main/packages/fluent-ui) - [Fluent UI 9](https://github.com/rjsf-team/react-jsonschema-form/tree/main/packages/fluentui-rc) +- [Mantine](https://github.com/rjsf-team/react-jsonschema-form/tree/main/packages/mantine) - [Material UI 4](https://github.com/rjsf-team/react-jsonschema-form/tree/main/packages/material-ui) - [Material UI 5](https://github.com/rjsf-team/react-jsonschema-form/tree/main/packages/mui) - [Semantic UI](https://github.com/rjsf-team/react-jsonschema-form/tree/main/packages/semantic-ui) diff --git a/package-lock.json b/package-lock.json index a3e2d0a951..83cae21518 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "packages/docs", "packages/fluent-ui", "packages/fluentui-rc", + "packages/mantine", "packages/material-ui", "packages/mui", "packages/playground", @@ -4590,12 +4591,28 @@ "@floating-ui/utils": "^0.2.1" } }, + "node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@floating-ui/react-dom": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", - "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.6.1" + "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0", @@ -4603,9 +4620,10 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", - "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" }, "node_modules/@fluentui/date-time-utilities": { "version": "8.5.16", @@ -7423,6 +7441,105 @@ "node": ">=10" } }, + "node_modules/@mantine/core": { + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.16.3.tgz", + "integrity": "sha512-cxhIpfd2i0Zmk9TKdejYAoIvWouMGhzK3OOX+VRViZ5HEjnTQCGl2h3db56ThqB6NfVPCno6BPbt5lwekTtmuQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.28", + "clsx": "^2.1.1", + "react-number-format": "^5.4.3", + "react-remove-scroll": "^2.6.2", + "react-textarea-autosize": "8.5.6", + "type-fest": "^4.27.0" + }, + "peerDependencies": { + "@mantine/hooks": "7.16.3", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/core/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@mantine/core/node_modules/react-remove-scroll": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", + "integrity": "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mantine/core/node_modules/type-fest": { + "version": "4.34.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.34.1.tgz", + "integrity": "sha512-6kSc32kT0rbwxD6QL1CYe8IqdzN/J/ILMrNK+HMQCKH3insCDRY/3ITb0vcBss0a3t72fzh2YSzj8ko1HgwT3g==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mantine/dates": { + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@mantine/dates/-/dates-7.16.3.tgz", + "integrity": "sha512-YI8aW3GFBmLJz0eu9Q6tFFXaubZIZMccA+6Jla8K8K8KMdx5wEPBrCCOHZnL3r0RcVLhARLa0VEmEyaQB+DTjQ==", + "license": "MIT", + "dependencies": { + "clsx": "^2.1.1" + }, + "peerDependencies": { + "@mantine/core": "7.16.3", + "@mantine/hooks": "7.16.3", + "dayjs": ">=1.0.0", + "react": "^18.x || ^19.x", + "react-dom": "^18.x || ^19.x" + } + }, + "node_modules/@mantine/dates/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@mantine/hooks": { + "version": "7.16.3", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.16.3.tgz", + "integrity": "sha512-B94FBWk5Sc81tAjV+B3dGh/gKzfqzpzVC/KHyBRWOOyJRqeeRbI/FAaJo4zwppyQo1POSl5ArdyjtDRrRIj2SQ==", + "license": "MIT", + "peerDependencies": { + "react": "^18.x || ^19.x" + } + }, "node_modules/@material-ui/core": { "version": "4.12.4", "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.12.4.tgz", @@ -9600,6 +9717,10 @@ "resolved": "packages/fluentui-rc", "link": true }, + "node_modules/@rjsf/mantine": { + "resolved": "packages/mantine", + "link": true + }, "node_modules/@rjsf/material-ui": { "resolved": "packages/material-ui", "link": true @@ -28052,6 +28173,16 @@ "url": "https://github.com/sponsors/gregberge" } }, + "node_modules/react-number-format": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.3.tgz", + "integrity": "sha512-VCY5hFg/soBighAoGcdE+GagkJq0230qN6jcS5sp8wQX1qy1fYN/RX7/BXkrs0oyzzwqR8/+eSUrqXbGeywdUQ==", + "license": "MIT", + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-overlays": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.2.1.tgz", @@ -28141,19 +28272,20 @@ } }, "node_modules/react-remove-scroll-bar": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", - "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", "dependencies": { - "react-style-singleton": "^2.2.1", + "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "engines": { "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -28253,20 +28385,20 @@ } }, "node_modules/react-style-singleton": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", - "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", "dependencies": { "get-nonce": "^1.0.0", - "invariant": "^2.2.4", "tslib": "^2.0.0" }, "engines": { "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -28289,9 +28421,10 @@ } }, "node_modules/react-textarea-autosize": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz", - "integrity": "sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.6.tgz", + "integrity": "sha512-aT3ioKXMa8f6zHYGebhbdMD2L00tKeRX1zuVuDx9YQK/JLLRSaSxq3ugECEmUB9z2kvk6bFSIoRHLkkUv0RJiw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.13", "use-composed-ref": "^1.3.0", @@ -28301,7 +28434,7 @@ "node": ">=10" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/react-transform-catch-errors": { @@ -31005,6 +31138,12 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, "node_modules/tabster": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/tabster/-/tabster-6.0.0.tgz", @@ -32486,9 +32625,10 @@ } }, "node_modules/use-callback-ref": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.1.tgz", - "integrity": "sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", "dependencies": { "tslib": "^2.0.0" }, @@ -32496,8 +32636,8 @@ "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -32554,9 +32694,10 @@ } }, "node_modules/use-sidecar": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", - "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" @@ -32565,8 +32706,8 @@ "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -34952,6 +35093,51 @@ "react": "^16.14.0 || >=17" } }, + "packages/mantine": { + "name": "@rjsf/mantine", + "version": "5.24.3", + "license": "Apache-2.0", + "devDependencies": { + "@babel/core": "^7.23.9", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", + "@babel/preset-env": "^7.23.9", + "@babel/preset-react": "^7.23.3", + "@babel/preset-typescript": "^7.23.3", + "@rjsf/core": "^5.24.3", + "@rjsf/snapshot-tests": "^5.24.3", + "@rjsf/utils": "^5.24.3", + "@rjsf/validator-ajv8": "^5.24.3", + "@types/jest": "^29.5.12", + "@types/lodash": "^4.14.202", + "@types/react": "^18.2.58", + "@types/react-dom": "^18.2.19", + "@types/react-test-renderer": "^18.0.7", + "atob": "^2.1.2", + "babel-jest": "^29.7.0", + "eslint": "^8.56.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "nanoid": "^3.3.7", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-test-renderer": "^18.2.0", + "rimraf": "^5.0.5", + "rollup": "^3.29.4", + "typescript": "^4.9.5" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@mantine/core": ">=7", + "@mantine/dates": ">=7", + "@mantine/hooks": ">=7", + "@rjsf/core": "^5.24.x", + "@rjsf/utils": "^5.24.x", + "react": "^16.14.0 || >=17" + } + }, "packages/material-ui": { "name": "@rjsf/material-ui", "version": "5.24.3", @@ -35055,6 +35241,9 @@ "@chakra-ui/icons": "^1.1.7", "@chakra-ui/react": "^1.8.9", "@fluentui/react": "^8.115.3", + "@mantine/core": "^7.16.3", + "@mantine/dates": "^7.16.3", + "@mantine/hooks": "^7.16.3", "@material-ui/core": "^4.12.4", "@mui/base": "5.0.0-beta.28", "@mui/material": "6.0.2", diff --git a/package.json b/package.json index 5852ea996c..4541224717 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "packages/docs", "packages/fluent-ui", "packages/fluentui-rc", + "packages/mantine", "packages/material-ui", "packages/mui", "packages/playground", diff --git a/packages/docs/docs/usage/themes.md b/packages/docs/docs/usage/themes.md index 779d742401..9172ae852c 100644 --- a/packages/docs/docs/usage/themes.md +++ b/packages/docs/docs/usage/themes.md @@ -5,17 +5,18 @@ meaning that you must load the Bootstrap stylesheet on the page to view the form ## Supported themes -| Theme Name | Status | Package Name / Link | -| --------------------- | --------- | ------------------- | -| antd | Published | `@rjsf/antd` | -| Bootstrap 3 (default) | Published | `@rjsf/core` | -| Bootstrap 4 | Published | `@rjsf/bootstrap-4` | -| Chakra UI | Published | `@rjsf/chakra-ui` | -| fluent-ui | Published | `@rjsf/fluent-ui` | -| fluentui-rc | Published | `@rjsf/fluentui-rc` | -| material-ui 4 | Published | `@rjsf/material-ui` | -| material-ui 5 | Published | `@rjsf/mui` | -| Semantic UI | Published | `@rjsf/semantic-ui` | +| Theme Name | Status | Package Name / Link | +| --------------------- | ------------- | ------------------- | +| antd | Published | `@rjsf/antd` | +| Bootstrap 3 (default) | Published | `@rjsf/core` | +| Bootstrap 4 | Published | `@rjsf/bootstrap-4` | +| Chakra UI | Published | `@rjsf/chakra-ui` | +| fluent-ui | Published | `@rjsf/fluent-ui` | +| fluentui-rc | Published | `@rjsf/fluentui-rc` | +| mantine | Not Published | `@rjsf/mantine` | +| material-ui 4 | Published | `@rjsf/material-ui` | +| material-ui 5 | Published | `@rjsf/mui` | +| Semantic UI | Published | `@rjsf/semantic-ui` | ## Using themes diff --git a/packages/mantine/.eslintrc b/packages/mantine/.eslintrc new file mode 100644 index 0000000000..726b68b788 --- /dev/null +++ b/packages/mantine/.eslintrc @@ -0,0 +1,4 @@ +{ + "extends": "../../.eslintrc", + "plugins": ["@typescript-eslint", "jsx-a11y", "react", "import"] +} diff --git a/packages/mantine/README.md b/packages/mantine/README.md new file mode 100644 index 0000000000..9a19a2ffc9 --- /dev/null +++ b/packages/mantine/README.md @@ -0,0 +1,153 @@ +[![Build Status][build-shield]][build-url] +[![npm][npm-shield]][npm-url] +[![npm downloads][npm-dl-shield]][npm-dl-url] +[![Contributors][contributors-shield]][contributors-url] +[![Apache 2.0 License][license-shield]][license-url] + + +
+

+ + Logo + + +

rjsf-mantine

+ +

+ Mantine theme, fields and widgets for react-jsonschema-form. +
+ Explore the docs » +
+
+ View Playground + · + Report Bug + · + Request Feature +

+

+ + + +## Table of Contents + +- [Table of Contents](#table-of-contents) +- [About The Project](#about-the-project) + - [Built With](#built-with) +- [Getting Started](#getting-started) + - [Prerequisites](#prerequisites) + - [Installation](#installation) +- [Usage](#usage) +- [Optional Mantine Theme properties](#optional-mantine-theme-properties) + - [Mantine Widget Optional Properties](#mantine-widget-optional-properties) +- [Roadmap](#roadmap) +- [Contributing](#contributing) +- [Contact](#contact) + + + +## About The Project + +`Mantine` theme, fields and widgets for `react-jsonschema-form`. + +### Built With + +- [react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form/) +- [Mantine](https://mantine.dev/) + + + +## Getting Started + +- See the [getting started guide](https://mantine.dev/getting-started/) on Mantine documentation. + +### Prerequisites + +- `@mantine/core >= 7` +- `@mantine/hooks >= 7` +- `@mantine/dates >= 7` +- `dayjs >= 1.8.0` +- `@rjsf/core >= 2.0.0` + +```sh +npm install @mantine/core @mantine/hooks @mantine/dates dayjs @rjsf/core +``` + +### Installation + +```sh +npm install @rjsf/mantine +``` + + + +## Usage + +```javascript +import Form from '@rjsf/mantine'; +``` + +or + +```javascript +import { withTheme } from '@rjsf/core'; +import { Theme as MantineTheme } from '@rjsf/mantine'; + +// Make modifications to the theme with your own fields and widgets + +const Form = withTheme(MantineTheme); +``` + +## Optional Mantine Theme properties + +- To pass additional properties to widgets, see this [guide](https://rjsf-team.github.io/react-jsonschema-form/docs/usage/objects#additional-properties). + +#### Mantine Widget Optional Properties + +- [Mantine props for CheckboxWidget](https://mantine.dev/core/checkbox/?t=props) +- [Mantine props for ColorWidget](https://mantine.dev/core/color-input/?t=props) +- [Mantine props for DateWidget](https://mantine.dev/dates/date-input/?t=props) +- [Mantine props for DateTimeWidget](https://mantine.dev/dates/date-input/?t=props) +- [Mantine props for PasswordWidget](https://mantine.dev/core/password-input/?t=props) +- [Mantine props for RadioWidget](https://mantine.dev/core/radio/?t=props) +- [Mantine props for RangeWidget](https://mantine.dev/core/slider/?t=props) +- [Mantine props for SelectWidget](https://mantine.dev/core/select/?t=props) +- [Mantine props for UpDownWidget](https://mantine.dev/core/number-input/?t=props) +- [Mantine props for TextWidget](https://mantine.dev/core/text-input/?t=props) +- [Mantine props for TextAreaWidget](https://mantine.dev/core/textarea/?t=props) +- [Mantine props for TimeWidget](https://mantine.dev/dates/time-input/?t=props) + + + +## Roadmap + +See the [open issues](https://github.com/rjsf-team/react-jsonschema-form/issues) for a list of proposed features (and known issues). + + + +## Contributing + +Read our [contributors' guide](https://rjsf-team.github.io/react-jsonschema-form/docs/contributing/) to get started. + + + +## Contact + +rjsf team: [https://github.com/orgs/rjsf-team/people](https://github.com/orgs/rjsf-team/people) + +GitHub repository: [https://github.com/rjsf-team/react-jsonschema-form](https://github.com/rjsf-team/react-jsonschema-form) + + + + +[build-shield]: https://github.com/rjsf-team/react-jsonschema-form/workflows/CI/badge.svg +[build-url]: https://github.com/rjsf-team/react-jsonschema-form/actions +[contributors-shield]: https://img.shields.io/github/contributors/rjsf-team/react-jsonschema-form.svg +[contributors-url]: https://github.com/rjsf-team/react-jsonschema-form/graphs/contributors +[license-shield]: https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat-square +[license-url]: https://choosealicense.com/licenses/apache-2.0/ +[npm-shield]: https://img.shields.io/npm/v/@rjsf/mantine/latest.svg?style=flat-square +[npm-url]: https://www.npmjs.com/package/@rjsf/mantine +[npm-dl-shield]: https://img.shields.io/npm/dm/@rjsf/mantine.svg?style=flat-square +[npm-dl-url]: https://www.npmjs.com/package/@rjsf/mantine +[product-screenshot]: https://raw.githubusercontent.com/rjsf-team/react-jsonschema-form/59a8206e148474bea854bbb004f624143fbcbac8/packages/mantine/screenshot.png diff --git a/packages/mantine/babel.config.js b/packages/mantine/babel.config.js new file mode 100644 index 0000000000..5f772a56c4 --- /dev/null +++ b/packages/mantine/babel.config.js @@ -0,0 +1,3 @@ +const defaultConfig = require('../../babel.config'); + +module.exports = defaultConfig; diff --git a/packages/mantine/jest.config.js b/packages/mantine/jest.config.js new file mode 100644 index 0000000000..6e7a8ee681 --- /dev/null +++ b/packages/mantine/jest.config.js @@ -0,0 +1,10 @@ +module.exports = { + rootDir: './', + snapshotSerializers: ['/test/cleanSnapshotSerializer.ts'], + verbose: true, + testEnvironment: 'jsdom', + testEnvironmentOptions: { + browsers: ['chrome', 'firefox', 'safari'], + }, + transformIgnorePatterns: [`/node_modules/(?!nanoid)`], +}; diff --git a/packages/mantine/logo.png b/packages/mantine/logo.png new file mode 100644 index 0000000000..1da1cc6141 Binary files /dev/null and b/packages/mantine/logo.png differ diff --git a/packages/mantine/package.json b/packages/mantine/package.json new file mode 100644 index 0000000000..7aef8d615a --- /dev/null +++ b/packages/mantine/package.json @@ -0,0 +1,97 @@ +{ + "name": "@rjsf/mantine", + "version": "5.24.3", + "main": "dist/index.js", + "module": "lib/index.js", + "typings": "lib/index.d.ts", + "description": "Mantine theme, fields and widgets for react-jsonschema-form", + "files": [ + "dist", + "lib", + "src" + ], + "engineStrict": false, + "engines": { + "node": ">=14" + }, + "scripts": { + "build:ts": "tsc -b", + "build:cjs": "esbuild ./src/index.ts --bundle --outfile=dist/index.js --sourcemap --packages=external --format=cjs", + "build:esm": "esbuild ./src/index.ts --bundle --outfile=dist/mantine.esm.js --sourcemap --packages=external --format=esm", + "build:umd": "rollup dist/mantine.esm.js --format=umd --file=dist/mantine.umd.js --name=@rjsf/mantine", + "build": "npm run build:ts && npm run build:cjs && npm run build:esm && npm run build:umd", + "cs-check": "prettier -l \"{src,test}/**/*.ts?(x)\"", + "cs-format": "prettier \"{src,test}/**/*.ts?(x)\" --write", + "lint": "eslint src test", + "precommit": "lint-staged", + "test": "jest", + "test:update": "jest --u" + }, + "lint-staged": { + "{src,test}/**/*.ts(x)": [ + "eslint --fix" + ] + }, + "peerDependencies": { + "@rjsf/core": "^5.24.x", + "@rjsf/utils": "^5.24.x", + "react": "^16.14.0 || >=17", + "@mantine/core": ">=7", + "@mantine/hooks": ">=7", + "@mantine/dates": ">=7" + }, + "devDependencies": { + "@babel/core": "^7.23.9", + "@babel/plugin-proposal-class-properties": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", + "@babel/preset-env": "^7.23.9", + "@babel/preset-react": "^7.23.3", + "@babel/preset-typescript": "^7.23.3", + "@rjsf/core": "^5.24.3", + "@rjsf/snapshot-tests": "^5.24.3", + "@rjsf/utils": "^5.24.3", + "@rjsf/validator-ajv8": "^5.24.3", + "@types/jest": "^29.5.12", + "@types/lodash": "^4.14.202", + "@types/react": "^18.2.58", + "@types/react-dom": "^18.2.19", + "@types/react-test-renderer": "^18.0.7", + "atob": "^2.1.2", + "babel-jest": "^29.7.0", + "eslint": "^8.56.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "nanoid": "^3.3.7", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-test-renderer": "^18.2.0", + "rimraf": "^5.0.5", + "rollup": "^3.29.4", + "typescript": "^4.9.5" + }, + "publishConfig": { + "access": "public" + }, + "author": "Farhad Zare ", + "contributors": [ + "Heath Chiavettone (): ComponentType> { + return withTheme(generateTheme()); +} + +export default generateForm(); diff --git a/packages/mantine/src/Theme/index.ts b/packages/mantine/src/Theme/index.ts new file mode 100644 index 0000000000..e23141183f --- /dev/null +++ b/packages/mantine/src/Theme/index.ts @@ -0,0 +1,18 @@ +import { ThemeProps } from '@rjsf/core'; +import { FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; + +import { generateTemplates } from '../templates'; +import { generateWidgets } from '../widgets'; + +export function generateTheme< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(): ThemeProps { + return { + templates: generateTemplates(), + widgets: generateWidgets(), + }; +} + +export default generateTheme(); diff --git a/packages/mantine/src/index.ts b/packages/mantine/src/index.ts new file mode 100644 index 0000000000..7cdb4fbd94 --- /dev/null +++ b/packages/mantine/src/index.ts @@ -0,0 +1,8 @@ +import Form, { generateForm } from './Form'; +import Templates, { generateTemplates } from './templates'; +import Theme, { generateTheme } from './Theme'; +import Widgets, { generateWidgets } from './widgets'; + +export { Form, Templates, Theme, Widgets, generateForm, generateTemplates, generateTheme, generateWidgets }; + +export default Form; diff --git a/packages/mantine/src/templates/ArrayFieldItemTemplate.tsx b/packages/mantine/src/templates/ArrayFieldItemTemplate.tsx new file mode 100644 index 0000000000..87314da5ff --- /dev/null +++ b/packages/mantine/src/templates/ArrayFieldItemTemplate.tsx @@ -0,0 +1,83 @@ +import { ArrayFieldTemplateItemType, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; +import { Box, Flex, Group } from '@mantine/core'; + +/** The `ArrayFieldItemTemplate` component is the template used to render an items of an array. + * + * @param props - The `ArrayFieldTemplateItemType` props for the component + */ +export default function ArrayFieldItemTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: ArrayFieldTemplateItemType) { + const { + disabled, + className, + hasCopy, + hasMoveDown, + hasMoveUp, + hasRemove, + hasToolbar, + index, + onCopyIndexClick, + onDropIndexClick, + onReorderClick, + readonly, + uiSchema, + registry, + children, + } = props; + const { CopyButton, MoveDownButton, MoveUpButton, RemoveButton } = registry.templates.ButtonTemplates; + + return ( + + + {children} + {hasToolbar && ( + + {(hasMoveUp || hasMoveDown) && ( + + )} + {(hasMoveUp || hasMoveDown) && ( + + )} + {hasCopy && ( + + )} + {hasRemove && ( + + )} + + )} + + + ); +} diff --git a/packages/mantine/src/templates/ArrayFieldTemplate.tsx b/packages/mantine/src/templates/ArrayFieldTemplate.tsx new file mode 100644 index 0000000000..8bc87e49df --- /dev/null +++ b/packages/mantine/src/templates/ArrayFieldTemplate.tsx @@ -0,0 +1,100 @@ +import { + getTemplate, + getUiOptions, + ArrayFieldTemplateProps, + ArrayFieldTemplateItemType, + FormContextType, + RJSFSchema, + StrictRJSFSchema, +} from '@rjsf/utils'; +import { Fieldset, Box, Group } from '@mantine/core'; + +/** The `ArrayFieldTemplate` component is the template used to render all items in an array. + * + * @param props - The `ArrayFieldTemplateItemType` props for the component + */ +export default function ArrayFieldTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: ArrayFieldTemplateProps) { + const { + canAdd, + className, + disabled, + idSchema, + items, + onAddClick, + readonly, + required, + schema, + uiSchema, + title, + registry, + } = props; + + const uiOptions = getUiOptions(uiSchema); + const ArrayFieldDescriptionTemplate = getTemplate<'ArrayFieldDescriptionTemplate', T, S, F>( + 'ArrayFieldDescriptionTemplate', + registry, + uiOptions + ); + const ArrayFieldItemTemplate = getTemplate<'ArrayFieldItemTemplate', T, S, F>( + 'ArrayFieldItemTemplate', + registry, + uiOptions + ); + const ArrayFieldTitleTemplate = getTemplate<'ArrayFieldTitleTemplate', T, S, F>( + 'ArrayFieldTitleTemplate', + registry, + uiOptions + ); + // Button templates are not overridden in the uiSchema + const { + ButtonTemplates: { AddButton }, + } = registry.templates; + + const legend = (uiOptions.title || title) && ( + + ); + + return ( +
+ {(uiOptions.description || schema.description) && ( + + )} + + + {items && + items.map(({ key, ...itemProps }: ArrayFieldTemplateItemType) => ( + + ))} + + + {canAdd && ( + + + + )} +
+ ); +} diff --git a/packages/mantine/src/templates/ArrayFieldTitleTemplate.tsx b/packages/mantine/src/templates/ArrayFieldTitleTemplate.tsx new file mode 100644 index 0000000000..67b09cd4e9 --- /dev/null +++ b/packages/mantine/src/templates/ArrayFieldTitleTemplate.tsx @@ -0,0 +1,33 @@ +import { + getUiOptions, + titleId, + ArrayFieldTitleProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, +} from '@rjsf/utils'; +import { Title } from '@mantine/core'; + +/** The `ArrayFieldTitleTemplate` component renders a `TitleFieldTemplate` with an `id` derived from + * the `idSchema`. + * + * @param props - The `ArrayFieldTitleProps` for the component + */ +export default function ArrayFieldTitleTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: ArrayFieldTitleProps) { + const { idSchema, title, uiSchema, registry } = props; + + const options = getUiOptions(uiSchema, registry.globalUiOptions); + const { label: displayLabel = true } = options; + if (!title || !displayLabel) { + return null; + } + return ( + (idSchema)} order={4} fw='normal'> + {title} + + ); +} diff --git a/packages/mantine/src/templates/BaseInputTemplate.tsx b/packages/mantine/src/templates/BaseInputTemplate.tsx new file mode 100644 index 0000000000..a45f5d34dc --- /dev/null +++ b/packages/mantine/src/templates/BaseInputTemplate.tsx @@ -0,0 +1,107 @@ +import React from 'react'; +import { + ariaDescribedByIds, + BaseInputTemplateProps, + examplesId, + getInputProps, + labelValue, + FormContextType, + RJSFSchema, + StrictRJSFSchema, +} from '@rjsf/utils'; +import { TextInput, NumberInput } from '@mantine/core'; + +export default function BaseInputTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: BaseInputTemplateProps) { + const { + id, + type, + schema, + value, + placeholder, + required, + disabled, + readonly, + autofocus, + label, + hideLabel, + onChange, + onChangeOverride, + onBlur, + onFocus, + options, + rawErrors, + } = props; + + const inputProps = getInputProps(schema, type, options, false); + + const handleNumberChange = (value: number | string) => onChange(value); + + const handleChange = onChangeOverride + ? onChangeOverride + : (e: React.ChangeEvent) => + onChange(e.target.value === '' ? options.emptyValue ?? '' : e.target.value); + + const handleBlur = (e: React.FocusEvent) => onBlur(id, e.target && e.target.value); + + const handleFocus = (e: React.FocusEvent) => onFocus(id, e.target && e.target.value); + + const input = + inputProps.type === 'number' || inputProps.type === 'integer' ? ( + 0 ? rawErrors.join('\n') : undefined} + list={schema.examples ? examplesId(id) : undefined} + {...inputProps} + step={typeof inputProps.step === 'number' ? inputProps.step : 1} + type='text' + value={value} + aria-describedby={ariaDescribedByIds(id, !!schema.examples)} + /> + ) : ( + 0 ? rawErrors.join('\n') : undefined} + list={schema.examples ? examplesId(id) : undefined} + {...inputProps} + value={value} + aria-describedby={ariaDescribedByIds(id, !!schema.examples)} + /> + ); + + return ( + <> + {input} + {Array.isArray(schema.examples) && ( + (id)}> + {(schema.examples as string[]) + .concat(schema.default && !schema.examples.includes(schema.default) ? ([schema.default] as string[]) : []) + .map((example) => { + return + )} + + ); +} diff --git a/packages/mantine/src/templates/ButtonTemplates/AddButton.tsx b/packages/mantine/src/templates/ButtonTemplates/AddButton.tsx new file mode 100644 index 0000000000..2ac8796ef1 --- /dev/null +++ b/packages/mantine/src/templates/ButtonTemplates/AddButton.tsx @@ -0,0 +1,14 @@ +import { FormContextType, IconButtonProps, RJSFSchema, StrictRJSFSchema, TranslatableString } from '@rjsf/utils'; +import IconButton from './IconButton'; +import { Plus } from '../icons'; + +export default function AddButton( + props: IconButtonProps +) { + const { + registry: { translateString }, + } = props; + return ( + } /> + ); +} diff --git a/packages/mantine/src/templates/ButtonTemplates/IconButton.tsx b/packages/mantine/src/templates/ButtonTemplates/IconButton.tsx new file mode 100644 index 0000000000..4b44326808 --- /dev/null +++ b/packages/mantine/src/templates/ButtonTemplates/IconButton.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { ActionIcon, ActionIconProps } from '@mantine/core'; +import { FormContextType, IconButtonProps, RJSFSchema, StrictRJSFSchema, TranslatableString } from '@rjsf/utils'; +import { Copy, ChevronDown, ChevronUp, X } from '../icons'; + +export type MantineIconButtonProps< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +> = IconButtonProps & Omit; + +export default function IconButton( + props: MantineIconButtonProps +) { + const { icon, iconType, color, onClick, uiSchema, registry, ...otherProps } = props; + return ( + & React.MouseEventHandler} + {...otherProps} + > + {icon} + + ); +} + +export function CopyButton( + props: MantineIconButtonProps +) { + const { + registry: { translateString }, + } = props; + return ( + } /> + ); +} + +export function MoveDownButton( + props: MantineIconButtonProps +) { + const { + registry: { translateString }, + } = props; + return ( + } + /> + ); +} + +export function MoveUpButton( + props: MantineIconButtonProps +) { + const { + registry: { translateString }, + } = props; + return ( + } + /> + ); +} + +export function RemoveButton( + props: MantineIconButtonProps +) { + const { + registry: { translateString }, + } = props; + return ( + } + /> + ); +} diff --git a/packages/mantine/src/templates/ButtonTemplates/SubmitButton.tsx b/packages/mantine/src/templates/ButtonTemplates/SubmitButton.tsx new file mode 100644 index 0000000000..1e22e8fec0 --- /dev/null +++ b/packages/mantine/src/templates/ButtonTemplates/SubmitButton.tsx @@ -0,0 +1,20 @@ +import { Button } from '@mantine/core'; +import { getSubmitButtonOptions, FormContextType, RJSFSchema, StrictRJSFSchema, SubmitButtonProps } from '@rjsf/utils'; + +/** The `SubmitButton` renders a button that represent the `Submit` action on a form + */ +export default function SubmitButton< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>({ uiSchema }: SubmitButtonProps) { + const { submitText, norender, props: submitButtonProps = {} } = getSubmitButtonOptions(uiSchema); + if (norender) { + return null; + } + return ( + + ); +} diff --git a/packages/mantine/src/templates/ButtonTemplates/index.ts b/packages/mantine/src/templates/ButtonTemplates/index.ts new file mode 100644 index 0000000000..aaf7c6fa23 --- /dev/null +++ b/packages/mantine/src/templates/ButtonTemplates/index.ts @@ -0,0 +1,21 @@ +import { FormContextType, RJSFSchema, StrictRJSFSchema, TemplatesType } from '@rjsf/utils'; +import SubmitButton from './SubmitButton'; +import AddButton from './AddButton'; +import { CopyButton, MoveDownButton, MoveUpButton, RemoveButton } from './IconButton'; + +function buttonTemplates< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(): TemplatesType['ButtonTemplates'] { + return { + SubmitButton, + AddButton, + CopyButton, + MoveDownButton, + MoveUpButton, + RemoveButton, + }; +} + +export default buttonTemplates; diff --git a/packages/mantine/src/templates/ErrorList.tsx b/packages/mantine/src/templates/ErrorList.tsx new file mode 100644 index 0000000000..4b4020d832 --- /dev/null +++ b/packages/mantine/src/templates/ErrorList.tsx @@ -0,0 +1,35 @@ +import { ErrorListProps, FormContextType, RJSFSchema, StrictRJSFSchema, TranslatableString } from '@rjsf/utils'; +import { Alert, Title, List } from '@mantine/core'; +import { ExclamationCircle } from './icons'; + +/** The `ErrorList` component is the template that renders the all the errors associated with the fields in the `Form` + * + * @param props - The `ErrorListProps` for this component + */ +export default function ErrorList({ + errors, + registry, +}: ErrorListProps) { + const { translateString } = registry; + + return ( + + {translateString(TranslatableString.ErrorsLabel)} + + } + icon={} + > + + {errors.map((error, index) => ( + + {error.stack} + + ))} + + + ); +} diff --git a/packages/mantine/src/templates/FieldErrorTemplate.tsx b/packages/mantine/src/templates/FieldErrorTemplate.tsx new file mode 100644 index 0000000000..5fb11b8be0 --- /dev/null +++ b/packages/mantine/src/templates/FieldErrorTemplate.tsx @@ -0,0 +1,27 @@ +import { errorId, FieldErrorProps, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; +import { Box, List } from '@mantine/core'; + +/** The `FieldErrorTemplate` component renders the errors local to the particular field + * + * @param props - The `FieldErrorProps` for the errors being rendered + */ +export default function FieldErrorTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>({ errors, idSchema }: FieldErrorProps) { + if (!errors || !errors.length) { + return null; + } + // In mantine, errors are handled directly in each component, so there is no need to render a separate error template. + const id = errorId(idSchema); + return ( + + + {errors.map((error, index) => ( + {error} + ))} + + + ); +} diff --git a/packages/mantine/src/templates/FieldHelpTemplate.tsx b/packages/mantine/src/templates/FieldHelpTemplate.tsx new file mode 100644 index 0000000000..043e926616 --- /dev/null +++ b/packages/mantine/src/templates/FieldHelpTemplate.tsx @@ -0,0 +1,18 @@ +import { helpId, FieldHelpProps, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; +import { Text } from '@mantine/core'; + +export default function FieldHelpTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: FieldHelpProps) { + const { idSchema, help } = props; + + const id = helpId(idSchema); + + return !help ? null : ( + + {help} + + ); +} diff --git a/packages/mantine/src/templates/FieldTemplate.tsx b/packages/mantine/src/templates/FieldTemplate.tsx new file mode 100644 index 0000000000..adb312776d --- /dev/null +++ b/packages/mantine/src/templates/FieldTemplate.tsx @@ -0,0 +1,46 @@ +import { + FieldTemplateProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + getTemplate, + getUiOptions, +} from '@rjsf/utils'; +import { Box } from '@mantine/core'; + +export default function FieldTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: FieldTemplateProps) { + const { id, classNames, style, label, errors, help, hidden, schema, uiSchema, registry, children, ...otherProps } = + props; + + const uiOptions = getUiOptions(uiSchema); + const WrapIfAdditionalTemplate = getTemplate<'WrapIfAdditionalTemplate', T, S, F>( + 'WrapIfAdditionalTemplate', + registry, + uiOptions + ); + + if (hidden) { + return {children}; + } + + return ( + + {children} + {help} + {errors} + + ); +} diff --git a/packages/mantine/src/templates/ObjectFieldTemplate.tsx b/packages/mantine/src/templates/ObjectFieldTemplate.tsx new file mode 100644 index 0000000000..be1a40b4ad --- /dev/null +++ b/packages/mantine/src/templates/ObjectFieldTemplate.tsx @@ -0,0 +1,94 @@ +import { + FormContextType, + ObjectFieldTemplateProps, + ObjectFieldTemplatePropertyType, + RJSFSchema, + StrictRJSFSchema, + canExpand, + descriptionId, + getTemplate, + getUiOptions, + titleId, +} from '@rjsf/utils'; +import { Container, Box, SimpleGrid, MantineSpacing } from '@mantine/core'; + +export default function ObjectFieldTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: ObjectFieldTemplateProps) { + const { + title, + description, + disabled, + properties, + onAddClick, + readonly, + required, + schema, + uiSchema, + idSchema, + formData, + registry, + } = props; + const uiOptions = getUiOptions(uiSchema); + const TitleFieldTemplate = getTemplate<'TitleFieldTemplate', T, S, F>('TitleFieldTemplate', registry, uiOptions); + const DescriptionFieldTemplate = getTemplate<'DescriptionFieldTemplate', T, S, F>( + 'DescriptionFieldTemplate', + registry, + uiOptions + ); + // Button templates are not overridden in the uiSchema + const { + ButtonTemplates: { AddButton }, + } = registry.templates; + const gridCols = (typeof uiOptions?.gridCols === 'number' && uiOptions?.gridCols) || undefined; + const gridSpacing = uiOptions?.gridSpacing; + const gridVerticalSpacing = uiOptions?.gridVerticalSpacing; + + return ( + + {title && ( + (idSchema)} + title={title} + required={required} + schema={schema} + uiSchema={uiSchema} + registry={registry} + /> + )} + {description && ( + (idSchema)} + description={description} + schema={schema} + uiSchema={uiSchema} + registry={registry} + /> + )} + + {properties + .filter((e) => !e.hidden) + .map((element: ObjectFieldTemplatePropertyType) => ( + {element.content} + ))} + + + {canExpand(schema, uiSchema, formData) && ( + + + + )} + + ); +} diff --git a/packages/mantine/src/templates/TitleField.tsx b/packages/mantine/src/templates/TitleField.tsx new file mode 100644 index 0000000000..1a85acbdc5 --- /dev/null +++ b/packages/mantine/src/templates/TitleField.tsx @@ -0,0 +1,13 @@ +import { FormContextType, TitleFieldProps, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; +import { Title } from '@mantine/core'; + +export default function TitleField( + props: TitleFieldProps +) { + const { id, title } = props; + return title ? ( + + {title} + + ) : null; +} diff --git a/packages/mantine/src/templates/WrapIfAdditionalTemplate.tsx b/packages/mantine/src/templates/WrapIfAdditionalTemplate.tsx new file mode 100644 index 0000000000..3614699777 --- /dev/null +++ b/packages/mantine/src/templates/WrapIfAdditionalTemplate.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { + ADDITIONAL_PROPERTY_FLAG, + UI_OPTIONS_KEY, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + TranslatableString, + WrapIfAdditionalTemplateProps, +} from '@rjsf/utils'; +import { Flex, Grid, TextInput } from '@mantine/core'; + +/** The `WrapIfAdditional` component is used by the `FieldTemplate` to rename, or remove properties that are + * part of an `additionalProperties` part of a schema. + * + * @param props - The `WrapIfAdditionalProps` for this component + */ +export default function WrapIfAdditionalTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: WrapIfAdditionalTemplateProps) { + const { + id, + classNames, + style, + label, + required, + readonly, + disabled, + schema, + uiSchema, + onKeyChange, + onDropPropertyClick, + registry, + children, + } = props; + const { templates, translateString } = registry; + // Button templates are not overridden in the uiSchema + const { RemoveButton } = templates.ButtonTemplates; + const keyLabel = translateString(TranslatableString.KeyLabel, [label]); + const additional = ADDITIONAL_PROPERTY_FLAG in schema; + + if (!additional) { + return ( +
+ {children} +
+ ); + } + + const handleBlur = ({ target }: React.FocusEvent) => onKeyChange(target && target.value); + + // The `block` prop is not part of the `IconButtonProps` defined in the template, so put it into the uiSchema instead + const uiOptions = uiSchema ? uiSchema[UI_OPTIONS_KEY] : {}; + const buttonUiOptions = { + ...uiSchema, + [UI_OPTIONS_KEY]: { ...uiOptions, block: true }, + }; + + return ( +
+ + + +
+ +
+
+ + {children} + +
+ +
+
+ ); +} diff --git a/packages/mantine/src/templates/icons.tsx b/packages/mantine/src/templates/icons.tsx new file mode 100644 index 0000000000..7f84ea10bc --- /dev/null +++ b/packages/mantine/src/templates/icons.tsx @@ -0,0 +1,141 @@ +import React from 'react'; + +interface IconProps extends React.ComponentPropsWithoutRef<'svg'> { + size?: number | string; +} + +export function Plus({ size, style, ...others }: IconProps) { + return ( + + + + + + ); +} + +export function Copy({ size, style, ...others }: IconProps) { + return ( + + + + + + ); +} + +export function ChevronDown({ size, style, ...others }: IconProps) { + return ( + + + + + ); +} + +export function ChevronUp({ size, style, ...others }: IconProps) { + return ( + + + + + ); +} + +export function X({ size, style, ...others }: IconProps) { + return ( + + + + + + ); +} + +export function ExclamationCircle({ size, style, ...others }: IconProps) { + return ( + + + + + + ); +} diff --git a/packages/mantine/src/templates/index.ts b/packages/mantine/src/templates/index.ts new file mode 100644 index 0000000000..36db30fe98 --- /dev/null +++ b/packages/mantine/src/templates/index.ts @@ -0,0 +1,37 @@ +import { FormContextType, RJSFSchema, StrictRJSFSchema, TemplatesType } from '@rjsf/utils'; + +import ArrayFieldItemTemplate from './ArrayFieldItemTemplate'; +import ArrayFieldTemplate from './ArrayFieldTemplate'; +import ArrayFieldTitleTemplate from './ArrayFieldTitleTemplate'; +import BaseInputTemplate from './BaseInputTemplate'; +import ErrorList from './ErrorList'; +import ButtonTemplates from './ButtonTemplates'; +import FieldErrorTemplate from './FieldErrorTemplate'; +import FieldTemplate from './FieldTemplate'; +import FieldHelpTemplate from './FieldHelpTemplate'; +import ObjectFieldTemplate from './ObjectFieldTemplate'; +import TitleField from './TitleField'; +import WrapIfAdditionalTemplate from './WrapIfAdditionalTemplate'; + +export function generateTemplates< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(): Partial> { + return { + ArrayFieldItemTemplate, + ArrayFieldTemplate, + ArrayFieldTitleTemplate, + BaseInputTemplate, + ButtonTemplates: ButtonTemplates(), + ErrorListTemplate: ErrorList, + FieldErrorTemplate, + FieldTemplate, + FieldHelpTemplate, + ObjectFieldTemplate, + TitleFieldTemplate: TitleField, + WrapIfAdditionalTemplate, + }; +} + +export default generateTemplates(); diff --git a/packages/mantine/src/tsconfig.json b/packages/mantine/src/tsconfig.json new file mode 100644 index 0000000000..01834bf1ab --- /dev/null +++ b/packages/mantine/src/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../../tsconfig.base.json", + "include": [ + "./" + ], + "compilerOptions": { + "rootDir": "./", + "outDir": "../lib", + "baseUrl": "../", + "jsx": "react-jsx" + }, + "references": [ + { + "path": "../../core" + }, + { + "path": "../../utils" + }, + { + "path": "../../validator-ajv8" + } + ] +} diff --git a/packages/mantine/src/widgets/CheckboxWidget.tsx b/packages/mantine/src/widgets/CheckboxWidget.tsx new file mode 100644 index 0000000000..57e64c7bd6 --- /dev/null +++ b/packages/mantine/src/widgets/CheckboxWidget.tsx @@ -0,0 +1,79 @@ +import React, { useCallback } from 'react'; +import { + StrictRJSFSchema, + RJSFSchema, + FormContextType, + WidgetProps, + labelValue, + ariaDescribedByIds, +} from '@rjsf/utils'; +import { Checkbox } from '@mantine/core'; + +export default function CheckboxWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: WidgetProps): React.ReactElement { + const { + id, + name, + value = false, + required, + disabled, + readonly, + autofocus, + label, + hideLabel, + rawErrors, + options, + onChange, + onBlur, + onFocus, + } = props; + + const handleCheckboxChange = useCallback( + (e: React.ChangeEvent) => { + if (!disabled && !readonly && onChange) { + onChange(e.currentTarget.checked); + } + }, + [onChange, disabled, readonly] + ); + + const handleBlur = useCallback( + ({ target }: React.FocusEvent) => { + if (onBlur) { + onBlur(id, target.checked); + } + }, + [onBlur, id] + ); + + const handleFocus = useCallback( + ({ target }: React.FocusEvent) => { + if (onFocus) { + onFocus(id, target.checked); + } + }, + [onFocus, id] + ); + + return ( + 0 ? rawErrors.join('\n') : undefined} + aria-describedby={ariaDescribedByIds(id)} + {...options} + classNames={typeof options?.classNames === 'object' ? options.classNames : undefined} + /> + ); +} diff --git a/packages/mantine/src/widgets/CheckboxesWidget.tsx b/packages/mantine/src/widgets/CheckboxesWidget.tsx new file mode 100644 index 0000000000..1a66e67f35 --- /dev/null +++ b/packages/mantine/src/widgets/CheckboxesWidget.tsx @@ -0,0 +1,110 @@ +import React, { useCallback } from 'react'; +import { + ariaDescribedByIds, + enumOptionsValueForIndex, + enumOptionsIndexForValue, + optionId, + titleId, + FormContextType, + WidgetProps, + RJSFSchema, + StrictRJSFSchema, +} from '@rjsf/utils'; +import { Checkbox, Flex, Input } from '@mantine/core'; + +/** The `CheckboxesWidget` is a widget for rendering checkbox groups. + * It is typically used to represent an array of enums. + * + * @param props - The `WidgetProps` for this component + */ +export default function CheckboxesWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: WidgetProps) { + const { + id, + value, + required, + disabled, + readonly, + autofocus, + label, + hideLabel, + rawErrors, + options, + onChange, + onBlur, + onFocus, + } = props; + + const { enumOptions, enumDisabled, inline, emptyValue } = options; + + const handleChange = useCallback( + (nextValue: any) => { + if (!disabled && !readonly && onChange) { + onChange(enumOptionsValueForIndex(nextValue, enumOptions, emptyValue)); + } + }, + [onChange, disabled, readonly, enumOptions, emptyValue] + ); + + const handleBlur = useCallback( + ({ target }: React.FocusEvent) => { + if (onBlur) { + onBlur(id, enumOptionsValueForIndex(target.value, enumOptions, emptyValue)); + } + }, + [onBlur, id, enumOptions, emptyValue] + ); + + const handleFocus = useCallback( + ({ target }: React.FocusEvent) => { + if (onFocus) { + onFocus(id, enumOptionsValueForIndex(target.value, enumOptions, emptyValue)); + } + }, + [onFocus, id, enumOptions, emptyValue] + ); + + const selectedIndexes = enumOptionsIndexForValue(value, enumOptions, true) as string[]; + + return Array.isArray(enumOptions) && enumOptions.length > 0 ? ( + <> + {!hideLabel && !!label && ( + (id)} required={required}> + {label} + + )} + 0 ? rawErrors.join('\n') : undefined} + aria-describedby={ariaDescribedByIds(id)} + {...options} + classNames={typeof options?.classNames === 'object' ? options.classNames : undefined} + > + {Array.isArray(enumOptions) ? ( + + {enumOptions.map((option, i) => ( + + ))} + + ) : null} + + + ) : null; +} diff --git a/packages/mantine/src/widgets/ColorWidget.tsx b/packages/mantine/src/widgets/ColorWidget.tsx new file mode 100644 index 0000000000..80b4a892da --- /dev/null +++ b/packages/mantine/src/widgets/ColorWidget.tsx @@ -0,0 +1,84 @@ +import React, { useCallback } from 'react'; +import { + FormContextType, + RJSFSchema, + StrictRJSFSchema, + WidgetProps, + labelValue, + ariaDescribedByIds, +} from '@rjsf/utils'; +import { ColorInput } from '@mantine/core'; + +/** The `ColorWidget` component uses the `ColorInput` from Mantine, allowing users to pick a color. + * + * @param props - The `WidgetProps` for this component + */ +export default function ColorWidget( + props: WidgetProps +) { + const { + id, + name, + value, + placeholder, + required, + disabled, + readonly, + autofocus, + label, + hideLabel, + rawErrors, + options, + onChange, + onBlur, + onFocus, + } = props; + + const emptyValue = options.emptyValue || ''; + + const handleChange = useCallback( + (nextValue: string) => { + onChange(nextValue); + }, + [onChange, emptyValue] + ); + + const handleBlur = useCallback( + ({ target }: React.FocusEvent) => { + if (onBlur) { + onBlur(id, target && target.value); + } + }, + [onBlur, id] + ); + + const handleFocus = useCallback( + ({ target }: React.FocusEvent) => { + if (onFocus) { + onFocus(id, target && target.value); + } + }, + [onFocus, id] + ); + + return ( + 0 ? rawErrors.join('\n') : undefined} + {...options} + aria-describedby={ariaDescribedByIds(id)} + popoverProps={{ withinPortal: false }} + classNames={typeof options?.classNames === 'object' ? options.classNames : undefined} + /> + ); +} diff --git a/packages/mantine/src/widgets/DateTime/AltDateTimeWidget.tsx b/packages/mantine/src/widgets/DateTime/AltDateTimeWidget.tsx new file mode 100644 index 0000000000..9f87fe000a --- /dev/null +++ b/packages/mantine/src/widgets/DateTime/AltDateTimeWidget.tsx @@ -0,0 +1,17 @@ +import { FormContextType, RJSFSchema, StrictRJSFSchema, WidgetProps } from '@rjsf/utils'; + +import _AltDateWidget from './AltDateWidget'; + +export default function AltDateTimeWidget< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any +>(props: WidgetProps) { + const { AltDateWidget } = props.registry.widgets; + return ; +} + +AltDateTimeWidget.defaultProps = { + ..._AltDateWidget?.defaultProps, + showTime: true, +}; diff --git a/packages/mantine/src/widgets/DateTime/AltDateWidget.tsx b/packages/mantine/src/widgets/DateTime/AltDateWidget.tsx new file mode 100644 index 0000000000..e317499f4b --- /dev/null +++ b/packages/mantine/src/widgets/DateTime/AltDateWidget.tsx @@ -0,0 +1,138 @@ +import { useCallback, useEffect, useState } from 'react'; +import { + ariaDescribedByIds, + dateRangeOptions, + parseDateString, + toDateString, + getDateElementProps, + titleId, + DateObject, + type DateElementFormat, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + TranslatableString, + WidgetProps, +} from '@rjsf/utils'; +import { Flex, Box, Group, Button, Select, Input } from '@mantine/core'; + +function readyForChange(state: DateObject) { + return Object.values(state).every((value) => value !== -1); +} + +/** The `AltDateWidget` is an alternative widget for rendering date properties. + * @param props - The `WidgetProps` for this component + */ +function AltDateWidget( + props: WidgetProps +) { + const { + id, + value, + required, + disabled, + readonly, + label, + hideLabel, + rawErrors, + options, + onChange, + showTime = false, + registry, + } = props; + + const { translateString } = registry; + + const [lastValue, setLastValue] = useState(value); + const [state, setState] = useState(parseDateString(value, showTime)); + + useEffect(() => { + const stateValue = toDateString(state, showTime); + if (lastValue !== value) { + // We got a new value in the props + setLastValue(value); + setState(parseDateString(value, showTime)); + } else if (readyForChange(state) && stateValue !== value) { + // Selected date is ready to be submitted + onChange(stateValue); + setLastValue(stateValue); + } + }, [showTime, value, onChange, state, lastValue]); + + const handleChange = useCallback((property: keyof DateObject, nextValue: any) => { + setState((prev) => ({ ...prev, [property]: typeof nextValue === 'undefined' ? -1 : nextValue })); + }, []); + + const handleSetNow = useCallback(() => { + if (!disabled && !readonly) { + const nextState = parseDateString(new Date().toJSON(), showTime); + onChange(toDateString(nextState, showTime)); + } + }, [disabled, readonly, showTime]); + + const handleClear = useCallback(() => { + if (!disabled && !readonly) { + onChange(''); + } + }, [disabled, readonly, onChange]); + + return ( + <> + {!hideLabel && !!label && ( + (id)} required={required}> + {label} + + )} + + {getDateElementProps( + state, + showTime, + options.yearsRange as [number, number] | undefined, + options.format as DateElementFormat | undefined + ).map((elemProps, i) => { + const elemId = id + '_' + elemProps.type; + return ( + +