diff --git a/.gitignore b/.gitignore index 0cf35acfd8..defceba7b5 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,9 @@ coverage # Dependency directories node_modules/ +# Optional npm cache directory +.nx/cache/ + # Optional npm cache directory .npm @@ -144,3 +147,7 @@ lodashReplacer.js muiReplacer.js ajvReplacer.js +# jest +build_tmp/ +__mocks__/ + diff --git a/CHANGELOG.md b/CHANGELOG.md index c52a964bda..8882e96be9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,22 @@ should change the heading of the (upcoming) version to include a major version b --> +# 6.0.0 + +## @rjsf/uswds + +- Added new USWDS theme, mapping [trussworks/react-uswds](https://github.com/trussworks/react-uswds) types to `@rjsf/core` and `@rjsf/utils` types. +- new theme package, `@rjsf/uswds`, implements the [U.S. Web Design System (USWDS) v3](designsystem.digital.gov) for `react-jsonschema-form`. + +Key features include: +* Templates for all standard RJSF fields (`Object`, `Array`, `String`, `Number`, `Boolean`, etc.) styled according to USWDS. +* Widgets leveraging USWDS form controls (`usa-input`, `usa-textarea`, `usa-checkbox`, `usa-radio`, `usa-select`, etc.). +* Array and Object field templates using USWDS `fieldset`, `legend`, and grid layout (`grid-row`, `grid-col`). +* Action buttons (`Add`, `Remove`, `Move Up`, `Move Down`) using USWDS button styles (`usa-button`, `usa-button--outline`, `usa-button--unstyled`) and icons loaded from the official USWDS CDN. +* Error display using USWDS error states (`usa-form-group--error`, `usa-label--error`, `usa-error-message`). +* Required field indication using `usa-label--required`. +* Refactored button implementation for better maintainability. + # 5.24.9 ## @rjsf/antd diff --git a/package-lock.json b/package-lock.json index adfe9ae0c2..286446ef8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "packages/mui", "packages/playground", "packages/semantic-ui", + "packages/uswds", "packages/utils", "packages/validator-ajv6", "packages/validator-ajv8", @@ -392,81 +393,19 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", - "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", "dependencies": { - "@babel/highlight": "^7.23.4", - "chalk": "^2.4.2" + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/code-frame/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/code-frame/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/code-frame/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/code-frame/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/compat-data": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", @@ -782,17 +721,19 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", - "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", - "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -819,99 +760,26 @@ } }, "node_modules/@babel/helpers": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", - "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", - "dependencies": { - "@babel/template": "^7.23.9", - "@babel/traverse": "^7.23.9", - "@babel/types": "^7.23.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.23.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", - "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", + "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.20", - "chalk": "^2.4.2", - "js-tokens": "^4.0.0" + "@babel/template": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/@babel/highlight/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/@babel/parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.1.tgz", + "integrity": "sha512-I0dZ3ZpCrJ1c04OqlNsQcKiZlsrXf/kkE4FXzID9rIOYICsAbA8mMDzhW/luRNAHdCNt7os/u8wenklZDlUVUQ==", + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "@babel/types": "^7.27.1" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", "bin": { "parser": "bin/babel-parser.js" }, @@ -2352,13 +2220,14 @@ } }, "node_modules/@babel/template": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", - "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.1.tgz", + "integrity": "sha512-Fyo3ghWMqkHHpHQCoBs2VnYjR4iWFFjguTDEqA5WgZDOrFesVjMhMM2FSqTKSoUSDO1VQtavj8NFpdRBEvJTtg==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.23.9", - "@babel/types": "^7.23.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -2393,13 +2262,13 @@ } }, "node_modules/@babel/types": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", - "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.23.4", - "@babel/helper-validator-identifier": "^7.22.20", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -9686,6 +9555,10 @@ "resolved": "packages/snapshot-tests", "link": true }, + "node_modules/@rjsf/uswds": { + "resolved": "packages/uswds", + "link": true + }, "node_modules/@rjsf/utils": { "resolved": "packages/utils", "link": true @@ -10441,6 +10314,20 @@ "react-dom": "^18.0.0" } }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", @@ -10450,6 +10337,21 @@ "node": ">= 10" } }, + "node_modules/@trussworks/react-uswds": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@trussworks/react-uswds/-/react-uswds-6.2.0.tgz", + "integrity": "sha512-B7Yi9hy+dF4A3XiXUB3BofvcNekhcb/NTldaAIM4Lt+jEd7wIFbTJc03v1inIJHUMLcke0nYFx4gOmixpivPZg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 16.20.0" + }, + "peerDependencies": { + "@uswds/uswds": "^3.7.1", + "react": "^16.x || ^17.x || ^18.x", + "react-dom": "^16.x || ^17.x || ^18.x" + } + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -11795,10 +11697,11 @@ } }, "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -13592,6 +13495,13 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, + "node_modules/classlist-polyfill": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz", + "integrity": "sha512-GzIjNdcEtH4ieA2S8NmrSxv7DfEV5fmixQeyTmqmRmRJPGpRBaSnA2a0VrCjyT8iW8JjEdMbKzDotAJf+ajgaQ==", + "dev": true, + "license": "Unlicense" + }, "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", @@ -14652,9 +14562,10 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -15489,10 +15400,11 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } @@ -15625,6 +15537,12 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/domready": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/domready/-/domready-1.0.8.tgz", + "integrity": "sha512-uIzsOJUNk+AdGE9a6VDeessoMCzF8RrZvJCX/W8QtyfgdR6Uofn/MvRonih3OtCO79b2VDzDOymuiABrQ4z3XA==", + "dev": true + }, "node_modules/domutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", @@ -15711,6 +15629,16 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.653.tgz", "integrity": "sha512-wA2A2LQCqnEwQAvwADQq3KpMpNwgAUBnRmrFgRzHnPhbQUFArTR32Ab46f4p0MovDLcg4uqd4nCsN2hTltslpA==" }, + "node_modules/element-closest": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/element-closest/-/element-closest-2.0.2.tgz", + "integrity": "sha512-QCqAWP3kwj8Gz9UXncVXQGdrhnWxD8SQBSeZp5pOsyCcQ6RpL738L1/tfuwBiMi6F1fYkxqPnBrFBR4L+f49Cg==", + "dev": true, + "license": "CC0-1.0", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/email-addresses": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz", @@ -21458,6 +21386,13 @@ "resolved": "https://registry.npmjs.org/keyboard-key/-/keyboard-key-1.1.0.tgz", "integrity": "sha512-qkBzPTi3rlAKvX7k0/ub44sqOfXeLc/jcnGGmj5c7BJpU8eDrEVPyhCvNYAaoubbsLm9uGWwQJO1ytQK1a9/dQ==" }, + "node_modules/keyboardevent-key-polyfill": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keyboardevent-key-polyfill/-/keyboardevent-key-polyfill-1.1.0.tgz", + "integrity": "sha512-NTDqo7XhzL1fqmUzYroiyK2qGua7sOMzLav35BfNA/mPUSCtw8pZghHFMTYR9JdnJ23IQz695FcaM6EE6bpbFQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/keyborg": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/keyborg/-/keyborg-2.5.0.tgz", @@ -22997,6 +22932,13 @@ "react": ">= 0.14.0" } }, + "node_modules/matches-selector": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/matches-selector/-/matches-selector-1.2.0.tgz", + "integrity": "sha512-c4vLwYWyl+Ji+U43eU/G5FwxWd4ZH0ePUsFs5y0uwD9HUEFBXUQ1zUUan+78IpRD+y4pUfG0nAzNM292K7ItvA==", + "dev": true, + "license": "MIT" + }, "node_modules/mdast-squeeze-paragraphs": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz", @@ -23776,32 +23718,32 @@ } }, "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, "bin": { "_mocha": "bin/_mocha", @@ -23809,10 +23751,34 @@ }, "engines": { "node": ">= 14.0.0" + } + }, + "node_modules/mocha/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mocha/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/mocha/node_modules/emoji-regex": { @@ -23822,37 +23788,26 @@ "dev": true }, "node_modules/mocha/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/mocha/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -23863,10 +23818,11 @@ } }, "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -23874,42 +23830,12 @@ "node": ">=10" } }, - "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/mocha/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "node_modules/mocha/node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/mocha/node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/mocha/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -23958,10 +23884,11 @@ } }, "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -24062,15 +23989,16 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -26047,9 +25975,10 @@ } }, "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", + "license": "MIT", "dependencies": { "isarray": "0.0.1" } @@ -26063,9 +25992,10 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -28937,6 +28867,19 @@ "resolved": "https://registry.npmjs.org/reading-time/-/reading-time-1.5.0.tgz", "integrity": "sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==" }, + "node_modules/receptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/receptor/-/receptor-1.0.0.tgz", + "integrity": "sha512-yvVEqVQDNzEmGkluCkEdbKSXqZb3WGxotI/VukXIQ+4/BXEeXVjWtmC6jWaR1BIsmEAGYQy3OTaNgDj2Svr01w==", + "dev": true, + "license": "CC0-1.0", + "dependencies": { + "element-closest": "^2.0.1", + "keyboardevent-key-polyfill": "^1.0.2", + "matches-selector": "^1.0.0", + "object-assign": "^4.1.0" + } + }, "node_modules/rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -29451,6 +29394,13 @@ "node": ">=8" } }, + "node_modules/resolve-id-refs": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/resolve-id-refs/-/resolve-id-refs-0.1.0.tgz", + "integrity": "sha512-hNS03NEmVpJheF7yfyagNh57XuKc0z+NkSO0oBbeO67o6IJKoqlDfnNIxhjp7aTWwjmSWZQhtiGrOgZXVyM90w==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/resolve-pathname": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", @@ -29575,10 +29525,11 @@ } }, "node_modules/rollup": { - "version": "3.29.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", - "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", "dev": true, + "license": "MIT", "bin": { "rollup": "dist/bin/rollup" }, @@ -31570,14 +31521,6 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, "node_modules/to-readable-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", @@ -32820,6 +32763,23 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/uswds": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/uswds/-/uswds-2.14.0.tgz", + "integrity": "sha512-r5ebL3av9jOPcbXTxKnGiT5k5NVIAWmtYcO8LeJkA5Svh2qc90YRhzCUWbz9TbGdXJriMCPb7whiULPDxmRhxg==", + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "classlist-polyfill": "1.2.0", + "domready": "1.0.8", + "object-assign": "4.1.1", + "receptor": "1.0.0", + "resolve-id-refs": "0.1.0" + }, + "engines": { + "node": ">= 4" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -33736,10 +33696,11 @@ "dev": true }, "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/wrap-ansi": { "version": "8.1.0", @@ -34665,6 +34626,7 @@ "@rjsf/material-ui": "^5.24.8", "@rjsf/mui": "^5.24.8", "@rjsf/semantic-ui": "^5.24.8", + "@rjsf/uswds": "^6.0.0", "@rjsf/utils": "^5.24.8", "@rjsf/validator-ajv6": "^5.24.8", "@rjsf/validator-ajv8": "^5.24.8", @@ -34678,6 +34640,7 @@ "dayjs": "^1.11.10", "deep-freeze-es6": "^1.4.1", "framer-motion": "^5.6.0", + "husky": "^9.1.7", "jss": "^10.10.0", "lodash": "^4.17.21", "monaco-editor": "^0.38.0", @@ -34751,6 +34714,21 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "packages/playground/node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "packages/playground/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -34829,6 +34807,142 @@ "react-test-renderer": "^17.0.2" } }, + "packages/uswds": { + "name": "@rjsf/uswds", + "version": "6.0.0", + "license": "Apache-2.0", + "dependencies": { + "@rjsf/utils": "^5.24.8", + "@trussworks/react-uswds": "^5.0.0 || ^6.0.0" + }, + "devDependencies": { + "@babel/core": "^7.22.8", + "@babel/preset-env": "^7.22.7", + "@babel/preset-react": "^7.22.5", + "@babel/preset-typescript": "^7.22.5", + "@rjsf/core": "^5.24.8", + "@rjsf/snapshot-tests": "^5.24.8", + "@rjsf/utils": "^5.24.8", + "@rjsf/validator-ajv8": "^5.24.8", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.4.2", + "@testing-library/react": "^14.2.1", + "@testing-library/user-event": "^14.5.2", + "@trussworks/react-uswds": "^5.0.0 || ^6.0.0", + "@types/jest": "^29.5.3", + "@types/react": "^18.2.14", + "@types/react-dom": "^18.2.6", + "eslint": "^8.44.0", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.6.1", + "prettier": "^3.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-test-renderer": "^18.2.0", + "rimraf": "^5.0.5", + "tsc-alias": "^1.8.10", + "typescript": "^5.1.6", + "uswds": "^2.14.0" + }, + "peerDependencies": { + "@rjsf/core": "^5.24.8", + "@rjsf/utils": "^5.24.8", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, + "packages/uswds/node_modules/@testing-library/dom": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", + "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "chalk": "^4.1.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "packages/uswds/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "packages/uswds/node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, + "packages/uswds/node_modules/prettier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", + "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "packages/uswds/node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "packages/uswds/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, + "packages/uswds/node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "packages/utils": { "name": "@rjsf/utils", "version": "5.24.8", diff --git a/package.json b/package.json index 457e4ee156..8966137563 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "packages/mui", "packages/playground", "packages/semantic-ui", + "packages/uswds", "packages/utils", "packages/validator-ajv6", "packages/validator-ajv8", diff --git a/packages/playground/package.json b/packages/playground/package.json index b8b850150c..bb92cb9b67 100644 --- a/packages/playground/package.json +++ b/packages/playground/package.json @@ -51,6 +51,7 @@ "@rjsf/material-ui": "^5.24.8", "@rjsf/mui": "^5.24.8", "@rjsf/semantic-ui": "^5.24.8", + "@rjsf/uswds": "^6.0.0", "@rjsf/utils": "^5.24.8", "@rjsf/validator-ajv6": "^5.24.8", "@rjsf/validator-ajv8": "^5.24.8", @@ -64,6 +65,7 @@ "dayjs": "^1.11.10", "deep-freeze-es6": "^1.4.1", "framer-motion": "^5.6.0", + "husky": "^9.1.7", "jss": "^10.10.0", "lodash": "^4.17.21", "monaco-editor": "^0.38.0", diff --git a/packages/playground/src/app.tsx b/packages/playground/src/app.tsx index 208bf3c474..20ecb9595c 100644 --- a/packages/playground/src/app.tsx +++ b/packages/playground/src/app.tsx @@ -6,6 +6,7 @@ import { Theme as SuiTheme } from '@rjsf/semantic-ui'; import { Theme as AntdTheme } from '@rjsf/antd'; import { Theme as Bootstrap4Theme } from '@rjsf/bootstrap-4'; import { Theme as ChakraUITheme } from '@rjsf/chakra-ui'; +import { Theme as UswdsTheme } from '@rjsf/uswds'; import v8Validator, { customizeValidator } from '@rjsf/validator-ajv8'; import v6Validator from '@rjsf/validator-ajv6'; import localize_es from 'ajv-i18n/localize/es'; @@ -125,6 +126,10 @@ const themes: PlaygroundProps['themes'] = { stylesheet: '//cdn.jsdelivr.net/npm/semantic-ui@2.4.2/dist/semantic.min.css', theme: SuiTheme, }, + uswds: { + stylesheet: '//cdn.jsdelivr.net/npm/uswds/dist/css/uswds.min.css', + theme: UswdsTheme, + }, }; export default function App() { diff --git a/packages/playground/vite.config.ts b/packages/playground/vite.config.ts index cd3e413c15..456e2374d4 100644 --- a/packages/playground/vite.config.ts +++ b/packages/playground/vite.config.ts @@ -24,6 +24,7 @@ export default defineConfig({ '@rjsf/material-ui': path.resolve(__dirname, '../material-ui/src'), '@rjsf/mui': path.resolve(__dirname, '../mui/src'), '@rjsf/semantic-ui': path.resolve(__dirname, '../semantic-ui/src'), + '@rjsf/uswds': path.resolve(__dirname, '../uswds/src'), '@rjsf/utils': path.resolve(__dirname, '../utils/src'), '@rjsf/validator-ajv8': path.resolve(__dirname, '../validator-ajv8/src'), // validator-ajv6 can not be mapped directly to the sources, because that causes wrong ajv version resolution @@ -31,6 +32,6 @@ export default defineConfig({ // // the difference when mapping directly vs mapping to src folder - @vitejs/plugin-react can not be applied in the 2nd case '@rjsf/validator-ajv6': '@rjsf/validator-ajv6/src', - }, + }, }, -}); + }); diff --git a/packages/uswds/.eslintrc b/packages/uswds/.eslintrc new file mode 100644 index 0000000000..8a8b1f6bc6 --- /dev/null +++ b/packages/uswds/.eslintrc @@ -0,0 +1,19 @@ +{ + "extends": [ + "../../.eslintrc" + ], + "rules": { + "prettier/prettier": ["error", { + "singleQuote": true, + "trailingComma": "all", + "arrowParens": "always" + }], + "@typescript-eslint/no-unused-vars": "off", + "no-unused-vars": "off", + "@typescript-eslint/no-empty-function": "warn", + "@typescript-eslint/no-var-requires": "warn" + }, + "env": { + "jest": true + } +} diff --git a/packages/uswds/.prettierrc b/packages/uswds/.prettierrc new file mode 100644 index 0000000000..312d57ab4d --- /dev/null +++ b/packages/uswds/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "arrowParens": "always", + "printWidth": 100 +} diff --git a/packages/uswds/README.md b/packages/uswds/README.md new file mode 100644 index 0000000000..abcf548d6c --- /dev/null +++ b/packages/uswds/README.md @@ -0,0 +1,123 @@ +[Github](https://github.com/uswds/uswds) +[NPM](https://www.npmjs.com/package/uswds) +[jsDeliver](https://www.jsdelivr.com/package/npm/uswds) +[Website](https://designsystem.digital.gov/) + +# @rjsf/uswds + +USWDS theme, fields and widgets for [`react-jsonschema-form`](https://github.com/rjsf-team/react-jsonschema-form/). + +## Features + +* Templates for all standard RJSF fields (Object, Array, String, Number, Boolean, etc.) styled according to USWDS. +* Widgets leveraging USWDS form controls (`usa-input`, `usa-textarea`, `usa-checkbox`, `usa-radio`, `usa-select`, `usa-combo-box`, `usa-date-picker`, `usa-file-input`, `usa-range`, etc.). +* Support for specific input types (`email`, `url`, `color`, `date`, `datetime-local`, `hidden`). +* Array and Object field templates using USWDS `fieldset`, `legend`, and grid layout (`grid-row`, `grid-col`). +* Action buttons (Add, Remove, Move Up, Move Down) using USWDS button styles (`usa-button`, `usa-button--outline`, `usa-button--unstyled`) and icons. +* Error display using USWDS error states (`usa-form-group--error`, `usa-label--error`, `usa-error-message`). +* Required field indication using `usa-label--required`. +* `CheckboxesWidget` implementation. + +## Installation + +```bash +npm install @rjsf/core @rjsf/uswds @trussworks/react-uswds +``` +or +```bash +yarn add @rjsf/core @rjsf/uswds @trussworks/react-uswds +``` + +## Usage + +## Prerequisites: USWDS CSS + +**This package does not bundle USWDS CSS.** You must include the USWDS stylesheet in your project. Choose one method: + +1. **Import CSS/Sass:** + ```javascript + // In your application's entry point (e.g., index.js or App.js) + import '@uswds/uswds/dist/css/uswds.min.css'; + + // Or if using Sass: + // @import "~@uswds/uswds/dist/scss/uswds"; // Adjust path as needed + ``` + +2. **Link in HTML:** + ```html + + + ``` + +### Option 1: Use the themed Form component + +This is the simplest way to use the theme. + +```jsx +import Form from '@rjsf/uswds'; +import validator from '@rjsf/validator-ajv8'; // Or your chosen validator + +const MyForm = () => { + const schema = { /* your schema */ }; + const uiSchema = { /* your uiSchema */ }; + const formData = { /* your data */ }; + + return
; +} + +// Ensure USWDS CSS is loaded as described in Prerequisites. +``` + +### Option 2: Use `withTheme` for customization + +If you need to customize the theme with your own widgets or fields: + +```jsx +import { withTheme } from '@rjsf/core'; +import { Theme as UswdsTheme } from '@rjsf/uswds'; +import validator from '@rjsf/validator-ajv8'; + +// Optional: Define custom widgets/fields +const myWidgets = { + // myCustomWidget: MyCustomWidgetComponent, +}; + +const myFields = { + // myCustomField: MyCustomFieldComponent, +}; + +// Merge customizations with the USWDS theme +const MyTheme = { + ...UswdsTheme, + widgets: { + ...UswdsTheme.widgets, + ...myWidgets, + }, + fields: { + ...UswdsTheme.fields, + ...myFields, + }, +}; + +const Form = withTheme(MyTheme); + +const MyForm = () => { + const schema = { /* your schema */ }; + return ; +} + +// Ensure USWDS CSS is loaded as described in Prerequisites. +``` + +## Specific Options + +Currently, this theme primarily focuses on applying standard USWDS classes and structure. There are no theme-specific `uiSchema` options beyond those provided by `@rjsf/core`. + +## Contributing + +See the [main RJSF contributing guide](https://rjsf-team.github.io/react-jsonschema-form/docs/contributing/). + +## TODO + +* [ ] Refine styling for edge cases and ensure perfect USWDS alignment. +* [ ] Improve accessibility (ARIA attributes, focus management). \ No newline at end of file diff --git a/packages/uswds/babel.config.json b/packages/uswds/babel.config.json new file mode 100644 index 0000000000..983b1eb298 --- /dev/null +++ b/packages/uswds/babel.config.json @@ -0,0 +1,7 @@ +{ + "presets": [ + ["@babel/preset-env", { "targets": { "node": "current" } }], + "@babel/preset-react", + "@babel/preset-typescript" + ] + } \ No newline at end of file diff --git a/packages/uswds/jest.config.jsc b/packages/uswds/jest.config.jsc new file mode 100644 index 0000000000..9f850c0f2e --- /dev/null +++ b/packages/uswds/jest.config.jsc @@ -0,0 +1,5 @@ +module.exports = { + verbose: true, + testEnvironment: 'jsdom', + transformIgnorePatterns: [`/node_modules/(?!nanoid)`], +}; diff --git a/packages/uswds/package.json b/packages/uswds/package.json new file mode 100644 index 0000000000..07a1ef0d7d --- /dev/null +++ b/packages/uswds/package.json @@ -0,0 +1,136 @@ +{ + "name": "@rjsf/uswds", + "version": "6.0.0", + "description": "USWDS theme, fields and widgets for react-jsonschema-form", + "main": "dist/index.js", + "module": "dist/uswds.esm.js", + "types": "lib/index.d.ts", + "type": "module", + "unpkg": "dist/uswds.umd.js", + "jsdelivr": "dist/uswds.umd.js", + "exports": { + ".": { + "require": "./dist/index.js", + "import": "./lib/index.js", + "types": "./lib/index.d.ts" + }, + "./lib": { + "require": "./dist/index.js", + "import": "./lib/index.js", + "types": "./lib/index.d.ts" + }, + "./lib/*.js": { + "require": "./dist/*.js", + "import": "./lib/*.js", + "types": "./lib/*.d.ts" + }, + "./dist": { + "require": "./dist/index.js", + "import": "./lib/index.js", + "types": "./lib/index.d.ts" + }, + "./dist/*.js": { + "require": "./dist/*.js", + "import": "./lib/*.js", + "types": "./lib/*.d.ts" + } + }, + "scripts": { + "compileReplacer": "tsc -p tsconfig.replacer.json", + "build:ts": "npm run compileReplacer && rimraf ./lib && tsc -b tsconfig.build.json && tsc-alias -p tsconfig.build.json", + "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/uswds.esm.js --sourcemap --packages=external --format=esm", + "build:umd": "rollup dist/uswds.esm.js --format=umd --file=dist/uswds.umd.js --name=@rjsf/uswds", + "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", + "lint:fix": "eslint src test --fix", + "precommit": "lint-staged", + "test": "jest", + "test:clean": "jest --clearCache", + "test:update": "jest -u", + "test:watch": "jest --watch", + "test:skip-snapshots": "jest --testPathIgnorePatterns=\"test/formTests.tsx\" --testPathIgnorePatterns=\"test/ArrayField.test.tsx\" --testPathIgnorePatterns=\"test/BooleanField.test.tsx\" --testPathIgnorePatterns=\"test/Form.test.tsx\" --testPathIgnorePatterns=\"test/FormTemplate.test.tsx\" --testPathIgnorePatterns=\"test/ObjectField.test.tsx\" --testPathIgnorePatterns=\"test/StringField.test.tsx\" --testPathIgnorePatterns=\"test/NumberField.test.tsx\"", + "test:widgets": "jest --testMatch=\"**/test/widgets/*.test.ts?(x)\"", + "test:templates": "jest --testMatch=\"**/test/templates/*.test.ts?(x)\"", + "test:skip-form": "jest --testPathIgnorePatterns=\"/node_modules/|/dist/|/lib/|test/Form.test.tsx\"", + "build:test": "npm run build && npm run test:skip-form" + }, + "lint-staged": { + "*.{ts,tsx}": [ + "prettier --write", + "eslint --fix" + ], + "*.{js,jsx}": [ + "prettier --write", + "eslint --fix" + ] + }, + "dependencies": { + "@rjsf/utils": "^5.24.8" + }, + "devDependencies": { + "@babel/core": "^7.22.8", + "@babel/preset-env": "^7.22.7", + "@babel/preset-react": "^7.22.5", + "@babel/preset-typescript": "^7.22.5", + "@rjsf/core": "^5.24.8", + "@rjsf/snapshot-tests": "^5.24.8", + "@rjsf/utils": "^5.24.8", + "@rjsf/validator-ajv8": "^5.24.8", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.4.2", + "@testing-library/react": "^14.2.1", + "@testing-library/user-event": "^14.5.2", + "@types/jest": "^29.5.3", + "@types/react": "^18.2.14", + "@types/react-dom": "^18.2.6", + "@trussworks/react-uswds": "^5.0.0 || ^6.0.0", + "eslint": "^8.44.0", + "esbuild": "^0.19.10", + "esbuild-node-externals": "^1.13.1", + "glob": "^10.3.10", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.6.1", + "prettier": "^3.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-test-renderer": "^18.2.0", + "rimraf": "^5.0.5", + "tsc-alias": "^1.8.10", + "typescript": "^5.1.6", + "uswds": "^2.14.0", + "babel-jest": "^29.7.0", + "identity-obj-proxy": "^3.0.0", + "rollup": "^2.0.0" + }, + "peerDependencies": { + "@rjsf/core": "^5.24.8", + "@rjsf/utils": "^5.24.8", + "@trussworks/react-uswds": "^5.0.0 || ^6.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "publishConfig": { + "access": "public" + }, + "author": "John H. Jediny ", + "contributors": [], + "keywords": [ + "react-jsonschema-form", + "jsonschema", + "json-schema", + "json", + "schema", + "form", + "react", + "uswds", + "ui" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/rjsf-team/react-jsonschema-form.git" + }, + "license": "Apache-2.0" +} diff --git a/packages/uswds/src/Templates/AnyOfFieldTemplate.tsx b/packages/uswds/src/Templates/AnyOfFieldTemplate.tsx new file mode 100644 index 0000000000..22b681687a --- /dev/null +++ b/packages/uswds/src/Templates/AnyOfFieldTemplate.tsx @@ -0,0 +1,43 @@ +import { FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; +import { Select } from '@trussworks/react-uswds'; + +interface Option { + label: string; + value: string; +} + +interface AnyOfFieldProps< + T extends string | number | readonly string[] = string, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +> { + options: Option[]; + id: string; + value?: T; + onChange: (value: T) => void; + schema: S; + uiSchema: F; +} + +export default function AnyOfField< + T extends string | number | readonly string[] = string, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: AnyOfFieldProps) { + const { id, options = [], value, onChange } = props; + + return ( + + ); +} diff --git a/packages/uswds/src/Templates/ArrayFieldDescriptionTemplate.tsx b/packages/uswds/src/Templates/ArrayFieldDescriptionTemplate.tsx new file mode 100644 index 0000000000..2dd6a86a0b --- /dev/null +++ b/packages/uswds/src/Templates/ArrayFieldDescriptionTemplate.tsx @@ -0,0 +1,31 @@ +import { + ArrayFieldDescriptionProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + descriptionId, +} from '@rjsf/utils'; + +/** The `ArrayFieldDescriptionTemplate` component renders a description for an array field + * + * @param props - The `ArrayFieldDescriptionProps` for the component + */ +export default function ArrayFieldDescriptionTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: ArrayFieldDescriptionProps) { + // Remove unused schema, uiSchema, registry + const { description, idSchema } = props; + const id = descriptionId(idSchema); + + if (!description) { + return null; + } + + return ( +

+ {description} +

+ ); +} diff --git a/packages/uswds/src/Templates/ArrayFieldItemTemplate.tsx b/packages/uswds/src/Templates/ArrayFieldItemTemplate.tsx new file mode 100644 index 0000000000..69b46696a2 --- /dev/null +++ b/packages/uswds/src/Templates/ArrayFieldItemTemplate.tsx @@ -0,0 +1,64 @@ +import { + ArrayFieldTemplateItemType, + FormContextType, + RJSFSchema, + StrictRJSFSchema, +} from '@rjsf/utils'; +import { Grid } from '@trussworks/react-uswds'; + +export default function ArrayFieldItemTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: ArrayFieldTemplateItemType) { + const { + children, + disabled, + hasToolbar, + hasMoveDown, + hasMoveUp, + hasRemove, + index, + onDropIndexClick, + onReorderClick, + readonly, + registry, // Add registry to props destructuring + } = props; + + // Retrieve button components from registry + const { MoveUpButton, MoveDownButton, RemoveButton } = registry.templates.ButtonTemplates; + + return ( + + {children} + {hasToolbar && ( + + {(hasMoveUp || hasMoveDown) && + MoveUpButton && ( // Check if button component exists + + )} + {(hasMoveUp || hasMoveDown) && + MoveDownButton && ( // Check if button component exists + + )} + {hasRemove && + RemoveButton && ( // Check if button component exists + + )} + + )} + + ); +} diff --git a/packages/uswds/src/Templates/ArrayFieldTemplate.tsx b/packages/uswds/src/Templates/ArrayFieldTemplate.tsx new file mode 100644 index 0000000000..d41b09c976 --- /dev/null +++ b/packages/uswds/src/Templates/ArrayFieldTemplate.tsx @@ -0,0 +1,37 @@ +import { + ArrayFieldTemplateItemType, + ArrayFieldTemplateProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, +} from '@rjsf/utils'; +import ArrayFieldItemTemplate from './ArrayFieldItemTemplate'; + +export default function ArrayFieldTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: ArrayFieldTemplateProps) { + const { canAdd, disabled, items, onAddClick, readonly, required, schema, title } = props; + + return ( +
+ {(title || schema.title) && ( + + {title || schema.title} + {required && *} + + )} + {items.map((itemProps: ArrayFieldTemplateItemType) => ( +
+ +
+ ))} + {canAdd && !readonly && !disabled && ( + + )} +
+ ); +} diff --git a/packages/uswds/src/Templates/ArrayFieldTitleTemplate.tsx b/packages/uswds/src/Templates/ArrayFieldTitleTemplate.tsx new file mode 100644 index 0000000000..dd6532c7da --- /dev/null +++ b/packages/uswds/src/Templates/ArrayFieldTitleTemplate.tsx @@ -0,0 +1,28 @@ +import { + ArrayFieldTitleProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + titleId, +} from '@rjsf/utils'; + +/** The `ArrayFieldTitleTemplate` component renders a title for an array field + * + * @param props - The `ArrayFieldTitleProps` for the component + */ +export default function ArrayFieldTitleTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>({ idSchema, title, required }: ArrayFieldTitleProps) { + if (!title) { + return null; + } + const id = titleId(idSchema); + return ( + + {title} + {required && *} + + ); +} diff --git a/packages/uswds/src/Templates/BaseInputTemplate.tsx b/packages/uswds/src/Templates/BaseInputTemplate.tsx new file mode 100644 index 0000000000..ca1eb605b5 --- /dev/null +++ b/packages/uswds/src/Templates/BaseInputTemplate.tsx @@ -0,0 +1,81 @@ +import { + BaseInputTemplateProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + ariaDescribedByIds, +} from '@rjsf/utils'; +import { TextInput } from '@trussworks/react-uswds'; +import React from 'react'; + +export default function BaseInputTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: BaseInputTemplateProps) { + const { + id, + value, + type, + placeholder, + required, + disabled, + readonly, + onChange, + onBlur, + onFocus, + options, + schema, + rawErrors = [], + autofocus, + registry, + ...rest + } = props; + + const _onChange = ({ target }: React.ChangeEvent) => { + onChange(target.value === '' ? options.emptyValue : target.value); + }; + const _onBlur = ({ target }: React.FocusEvent) => { + onBlur(id, target.value === '' ? options.emptyValue : target.value); + }; + const _onFocus = ({ target }: React.FocusEvent) => { + onFocus(id, target.value === '' ? options.emptyValue : target.value); + }; + + const hasErrors = rawErrors.length > 0; + + const inputProps: React.ComponentProps = { + id: id, + name: id, + type: type === 'string' ? 'text' : type, + value: value ?? '', + placeholder: placeholder, + required: required, + disabled: disabled || readonly, + autoFocus: autofocus, + 'aria-invalid': hasErrors ? true : undefined, + 'aria-describedby': ariaDescribedByIds(id, hasErrors), + step: options.step || rest.step, + min: options.min || rest.min, + max: options.max || rest.max, + list: schema.examples ? `${id}__examples` : undefined, + }; + + const examples = schema.examples as string[] | undefined; + const defaultExample = schema.default !== undefined ? String(schema.default) : undefined; + + return ( + <> + + {examples && ( + + {examples + .concat(defaultExample && !examples.includes(defaultExample) ? [defaultExample] : []) + .map((example: string) => { + return + )} + + ); +} diff --git a/packages/uswds/src/Templates/Button/AddButton.tsx b/packages/uswds/src/Templates/Button/AddButton.tsx new file mode 100644 index 0000000000..1296d16c9f --- /dev/null +++ b/packages/uswds/src/Templates/Button/AddButton.tsx @@ -0,0 +1,33 @@ +import { Button, Icon } from '@trussworks/react-uswds'; // Import Icon +import { + IconButtonProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + TranslatableString, +} from '@rjsf/utils'; + +/** The `AddButton` renders a button that adds a new item to an array field. + * + * @param props - The `IconButtonProps` for the component + */ +export default function AddButton< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: IconButtonProps) { + const { icon, iconType, registry, className, ...otherProps } = props; // Extract className + const translatedLabel = registry.translateString(TranslatableString.AddItemButton); + return ( + + ); +} diff --git a/packages/uswds/src/Templates/Button/CopyButton.tsx b/packages/uswds/src/Templates/Button/CopyButton.tsx new file mode 100644 index 0000000000..bb700b76d9 --- /dev/null +++ b/packages/uswds/src/Templates/Button/CopyButton.tsx @@ -0,0 +1,33 @@ +import { Button, Icon } from '@trussworks/react-uswds'; +import { + IconButtonProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + TranslatableString, +} from '@rjsf/utils'; + +/** The `CopyButton` renders a button that copies the data for an array item. + * + * @param props - The `IconButtonProps` for the component + */ +export default function CopyButton< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: IconButtonProps) { + const { icon, iconType, registry, className, ...otherProps } = props; + const translatedLabel = registry.translateString(TranslatableString.CopyButton); + return ( + + ); +} diff --git a/packages/uswds/src/Templates/Button/IconButton.tsx b/packages/uswds/src/Templates/Button/IconButton.tsx new file mode 100644 index 0000000000..124125474c --- /dev/null +++ b/packages/uswds/src/Templates/Button/IconButton.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { IconButtonProps, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; +import { Button } from '@trussworks/react-uswds'; + +// Helper to get a string representation for aria-label if it's an element +const getAriaLabel = (label: React.ReactNode): string | undefined => { + if (typeof label === 'string') { + return label; + } + // Add more sophisticated logic here if needed to extract text from React elements + return undefined; +}; + +// Map icon names or classes to button types +const ICON_MAP = { + 'arrow-up': 'arrow_upward', + 'arrow-down': 'arrow_downward', + remove: 'delete', + plus: 'add', +}; + +export default function IconButton< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: IconButtonProps) { + const { icon, iconType, className, uiSchema, registry, ...otherProps } = props; + const translatedIcon = ICON_MAP[icon as keyof typeof ICON_MAP] || icon; // Translate generic icon name + + return ( + + ); +} diff --git a/packages/uswds/src/Templates/Button/MoveDownButton.tsx b/packages/uswds/src/Templates/Button/MoveDownButton.tsx new file mode 100644 index 0000000000..9786b33e1e --- /dev/null +++ b/packages/uswds/src/Templates/Button/MoveDownButton.tsx @@ -0,0 +1,32 @@ +import { Button, Icon } from '@trussworks/react-uswds'; // Import Icon +import { + IconButtonProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + TranslatableString, +} from '@rjsf/utils'; + +/** The `MoveDownButton` renders a button that moves the item down in an array. + * + * @param props - The `IconButtonProps` for the component + */ +export default function MoveDownButton< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: IconButtonProps) { + const { icon, iconType, registry, ...otherProps } = props; + const translatedLabel = registry.translateString(TranslatableString.MoveDownButton); + return ( + + ); +} diff --git a/packages/uswds/src/Templates/Button/MoveUpButton.tsx b/packages/uswds/src/Templates/Button/MoveUpButton.tsx new file mode 100644 index 0000000000..78b629eefa --- /dev/null +++ b/packages/uswds/src/Templates/Button/MoveUpButton.tsx @@ -0,0 +1,32 @@ +import { Button, Icon } from '@trussworks/react-uswds'; // Import Icon +import { + IconButtonProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + TranslatableString, +} from '@rjsf/utils'; + +/** The `MoveUpButton` renders a button that moves the item up in an array. + * + * @param props - The `IconButtonProps` for the component + */ +export default function MoveUpButton< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: IconButtonProps) { + const { icon, iconType, registry, ...otherProps } = props; + const translatedLabel = registry.translateString(TranslatableString.MoveUpButton); + return ( + + ); +} diff --git a/packages/uswds/src/Templates/Button/RemoveButton.tsx b/packages/uswds/src/Templates/Button/RemoveButton.tsx new file mode 100644 index 0000000000..d246cfc31a --- /dev/null +++ b/packages/uswds/src/Templates/Button/RemoveButton.tsx @@ -0,0 +1,35 @@ +import { Button, Icon } from '@trussworks/react-uswds'; // Import Icon +import { + IconButtonProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + TranslatableString, +} from '@rjsf/utils'; + +/** The `RemoveButton` renders a button that removes the item from an array. + * + * @param props - The `IconButtonProps` for the component + */ +export default function RemoveButton< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: IconButtonProps) { + const { icon, iconType, registry, ...otherProps } = props; + const translatedLabel = registry.translateString(TranslatableString.RemoveButton); + return ( + + ); +} diff --git a/packages/uswds/src/Templates/Button/SubmitButton.tsx b/packages/uswds/src/Templates/Button/SubmitButton.tsx new file mode 100644 index 0000000000..2236734814 --- /dev/null +++ b/packages/uswds/src/Templates/Button/SubmitButton.tsx @@ -0,0 +1,33 @@ +import { Button } from '@trussworks/react-uswds'; +import { + FormContextType, + RJSFSchema, + StrictRJSFSchema, + SubmitButtonProps, + getSubmitButtonOptions, +} from '@rjsf/utils'; + +/** The `SubmitButton` renders a button that submits the form when clicked */ +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/uswds/src/Templates/Button/index.tsx b/packages/uswds/src/Templates/Button/index.tsx new file mode 100644 index 0000000000..89869959b5 --- /dev/null +++ b/packages/uswds/src/Templates/Button/index.tsx @@ -0,0 +1,24 @@ +import AddButton from './AddButton'; +import CopyButton from './CopyButton'; +import MoveDownButton from './MoveDownButton'; +import MoveUpButton from './MoveUpButton'; +import RemoveButton from './RemoveButton'; +import SubmitButton from './SubmitButton'; // Import SubmitButton +import { FormContextType, RJSFSchema, StrictRJSFSchema, TemplatesType } from '@rjsf/utils'; + +export function generateButtonTemplates< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(): TemplatesType['ButtonTemplates'] { + return { + AddButton, + CopyButton, + MoveDownButton, + MoveUpButton, + RemoveButton, + SubmitButton, // Export SubmitButton + }; +} + +export default generateButtonTemplates(); diff --git a/packages/uswds/src/Templates/DescriptionField.tsx b/packages/uswds/src/Templates/DescriptionField.tsx new file mode 100644 index 0000000000..9a7fb15b87 --- /dev/null +++ b/packages/uswds/src/Templates/DescriptionField.tsx @@ -0,0 +1,29 @@ +import { DescriptionFieldProps, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; + +export default function DescriptionField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>({ id, description, uiSchema }: DescriptionFieldProps) { + if (!description) { + return null; + } + + // For markdown rendering, check ui:options + const markdownEnabled = + uiSchema?.['ui:options']?.enableMarkdown || + uiSchema?.['ui:options']?.enableMarkdownInDescription !== false; + + // Simple implementation - if markdown support is needed, consider adding react-markdown as a dependency + return ( +
+ {typeof description === 'string' && markdownEnabled ? ( + // Basic implementation for markdown-enabled text +
+ ) : ( + // Regular text + description + )} +
+ ); +} diff --git a/packages/uswds/src/Templates/DescriptionFieldTemplate.tsx b/packages/uswds/src/Templates/DescriptionFieldTemplate.tsx new file mode 100644 index 0000000000..3a9321b654 --- /dev/null +++ b/packages/uswds/src/Templates/DescriptionFieldTemplate.tsx @@ -0,0 +1,23 @@ +import { DescriptionFieldProps, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; + +/** The `DescriptionFieldTemplate` is the template to use to render the description of a field + * + * @param props - The `DescriptionFieldProps` for this component + */ +export default function DescriptionFieldTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: DescriptionFieldProps) { + const { id, description } = props; + + if (!description) { + return null; + } + + return ( +
+ {description} +
+ ); +} diff --git a/packages/uswds/src/Templates/DescriptionTemplate.tsx b/packages/uswds/src/Templates/DescriptionTemplate.tsx new file mode 100644 index 0000000000..c667d773dd --- /dev/null +++ b/packages/uswds/src/Templates/DescriptionTemplate.tsx @@ -0,0 +1,25 @@ +import { DescriptionFieldProps, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; + +export default function Description< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>({ description, id }: DescriptionFieldProps) { + if (!description) { + return null; + } + + if (typeof description === 'string') { + return ( +
+ {description} +
+ ); + } + + return ( +
+ {description} +
+ ); +} diff --git a/packages/uswds/src/Templates/ErrorListTemplate.tsx b/packages/uswds/src/Templates/ErrorListTemplate.tsx new file mode 100644 index 0000000000..23586705b9 --- /dev/null +++ b/packages/uswds/src/Templates/ErrorListTemplate.tsx @@ -0,0 +1,52 @@ +import { Alert } from '@trussworks/react-uswds'; // Import Alert +import { + ErrorListProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + TranslatableString, +} from '@rjsf/utils'; // Import TranslatableString + +/** 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 ErrorListTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>({ errors, registry }: ErrorListProps) { + // Destructure registry + const { translateString } = registry; // Get translateString from registry + + if (errors.length === 0) { + return null; + } + + // Use the implementation from ErrorList.tsx + return ( +
+ {' '} + {/* Keep or remove outer div as needed */} + +
    + {' '} + {/* Use USWDS recommended class or keep error-detail */} + {errors.map((error, index) => { + return ( + // Use USWDS recommended class or keep text-danger +
  • + {error.stack} +
  • + ); + })} +
+
+
+ ); +} diff --git a/packages/uswds/src/Templates/Field.tsx b/packages/uswds/src/Templates/Field.tsx new file mode 100644 index 0000000000..cc18921b03 --- /dev/null +++ b/packages/uswds/src/Templates/Field.tsx @@ -0,0 +1,31 @@ +import { FieldTemplateProps, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; +import { FormGroup, Label } from '@trussworks/react-uswds'; + +export default function Field< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: FieldTemplateProps) { + const { id, label, help, required, description, errors, children, hidden } = props; + + if (hidden) { + return children; + } + + const hasErrors = Array.isArray(errors) && errors.length > 0; + + return ( + + {label && ( + + )} + {description &&
{description}
} + {children} + {errors} + {help &&
{help}
} +
+ ); +} diff --git a/packages/uswds/src/Templates/FieldErrorTemplate.tsx b/packages/uswds/src/Templates/FieldErrorTemplate.tsx new file mode 100644 index 0000000000..d2f4a5c417 --- /dev/null +++ b/packages/uswds/src/Templates/FieldErrorTemplate.tsx @@ -0,0 +1,32 @@ +import { FieldErrorProps, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; + +/** 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, +>(props: FieldErrorProps) { + const { errors = [], idSchema } = props; + + if (errors.length === 0) { + return null; + } + const id = `${idSchema.$id}__error`; + + return ( +
    + {errors + .filter((elem) => !!elem) + .map((error, index: number) => { + return ( +
  • + {error} +
  • + ); + })} +
+ ); +} diff --git a/packages/uswds/src/Templates/FieldHelpTemplate.tsx b/packages/uswds/src/Templates/FieldHelpTemplate.tsx new file mode 100644 index 0000000000..20e4c3699b --- /dev/null +++ b/packages/uswds/src/Templates/FieldHelpTemplate.tsx @@ -0,0 +1,22 @@ +import { FieldHelpProps, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; + +/** The `FieldHelpTemplate` component renders any help desired for a field + * + * @param props - The `FieldHelpProps` to be rendered + */ +export default function FieldHelpTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: FieldHelpProps) { + const { idSchema, help } = props; + if (!help) { + return null; + } + const id = `${idSchema.$id}__help`; + return ( + + {help} + + ); +} diff --git a/packages/uswds/src/Templates/FieldTemplate.tsx b/packages/uswds/src/Templates/FieldTemplate.tsx new file mode 100644 index 0000000000..15356dc569 --- /dev/null +++ b/packages/uswds/src/Templates/FieldTemplate.tsx @@ -0,0 +1,74 @@ +import { + FieldTemplateProps, + FormContextType, + RJSFSchema, + StrictRJSFSchema, + getTemplate, +} from '@rjsf/utils'; +import { FormGroup, Label } from '@trussworks/react-uswds'; + +export default function FieldTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: FieldTemplateProps) { + const { + id, + label, + children, + required, + hidden, + classNames, + style, + displayLabel, + rawErrors = [], + rawHelp, + rawDescription, + registry, + uiSchema, + schema, + } = props; + + const uiOptions = uiSchema?.['ui:options']; + + const DescriptionFieldTemplate = getTemplate<'DescriptionFieldTemplate', T, S, F>( + 'DescriptionFieldTemplate', + registry, + uiOptions, + ); + + if (hidden) { + return
{children}
; + } + + const hasErrors = rawErrors.length > 0; + const showLabel = displayLabel && !!label; + + return ( +
+ + {showLabel && ( + + )} + {rawDescription && DescriptionFieldTemplate && ( + + )} + {children} + {rawHelp && ( + + {rawHelp} + + )} + +
+ ); +} diff --git a/packages/uswds/src/Templates/Fields/ArrayField.tsx b/packages/uswds/src/Templates/Fields/ArrayField.tsx new file mode 100644 index 0000000000..36f5b6206b --- /dev/null +++ b/packages/uswds/src/Templates/Fields/ArrayField.tsx @@ -0,0 +1,16 @@ +import { FormContextType, RJSFSchema, StrictRJSFSchema, getTemplate } from '@rjsf/utils'; + +export default function ArrayField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: any) { + const { registry, uiSchema } = props; + const ArrayFieldTemplate = getTemplate<'ArrayFieldTemplate', T, S, F>( + 'ArrayFieldTemplate', + registry, + uiSchema, + ); + + return ; +} diff --git a/packages/uswds/src/Templates/Fields/BooleanField.tsx b/packages/uswds/src/Templates/Fields/BooleanField.tsx new file mode 100644 index 0000000000..4f0285302d --- /dev/null +++ b/packages/uswds/src/Templates/Fields/BooleanField.tsx @@ -0,0 +1,12 @@ +import { FormContextType, RJSFSchema, StrictRJSFSchema, getTemplate } from '@rjsf/utils'; + +export default function BooleanField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: any) { + const { registry, uiSchema } = props; + const FieldTemplate = getTemplate<'FieldTemplate', T, S, F>('FieldTemplate', registry, uiSchema); + + return ; +} diff --git a/packages/uswds/src/Templates/Fields/NumberField.tsx b/packages/uswds/src/Templates/Fields/NumberField.tsx new file mode 100644 index 0000000000..1f05c71efb --- /dev/null +++ b/packages/uswds/src/Templates/Fields/NumberField.tsx @@ -0,0 +1,12 @@ +import { FormContextType, RJSFSchema, StrictRJSFSchema, getTemplate } from '@rjsf/utils'; + +export default function NumberField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: any) { + const { registry, uiSchema } = props; + const FieldTemplate = getTemplate<'FieldTemplate', T, S, F>('FieldTemplate', registry, uiSchema); + + return ; +} diff --git a/packages/uswds/src/Templates/Fields/ObjectField.tsx b/packages/uswds/src/Templates/Fields/ObjectField.tsx new file mode 100644 index 0000000000..5e96944ac7 --- /dev/null +++ b/packages/uswds/src/Templates/Fields/ObjectField.tsx @@ -0,0 +1,16 @@ +import { FormContextType, RJSFSchema, StrictRJSFSchema, getTemplate } from '@rjsf/utils'; + +export default function ObjectField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: any) { + const { registry, uiSchema } = props; + const ObjectFieldTemplate = getTemplate<'ObjectFieldTemplate', T, S, F>( + 'ObjectFieldTemplate', + registry, + uiSchema, + ); + + return ; +} diff --git a/packages/uswds/src/Templates/Fields/StringField.tsx b/packages/uswds/src/Templates/Fields/StringField.tsx new file mode 100644 index 0000000000..e96e9ef01d --- /dev/null +++ b/packages/uswds/src/Templates/Fields/StringField.tsx @@ -0,0 +1,12 @@ +import { FormContextType, RJSFSchema, StrictRJSFSchema, getTemplate } from '@rjsf/utils'; + +export default function StringField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: any) { + const { registry, uiSchema } = props; + const FieldTemplate = getTemplate<'FieldTemplate', T, S, F>('FieldTemplate', registry, uiSchema); + + return ; +} diff --git a/packages/uswds/src/Templates/Fields/index.tsx b/packages/uswds/src/Templates/Fields/index.tsx new file mode 100644 index 0000000000..fb59d11081 --- /dev/null +++ b/packages/uswds/src/Templates/Fields/index.tsx @@ -0,0 +1,15 @@ +import ArrayField from './ArrayField'; +import BooleanField from './BooleanField'; +import NumberField from './NumberField'; +import ObjectField from './ObjectField'; +import StringField from './StringField'; + +export const Fields = { + ArrayField, + BooleanField, + NumberField, + ObjectField, + StringField, +} as const; + +export default Fields; diff --git a/packages/uswds/src/Templates/GridTemplate.tsx b/packages/uswds/src/Templates/GridTemplate.tsx new file mode 100644 index 0000000000..195b1fc585 --- /dev/null +++ b/packages/uswds/src/Templates/GridTemplate.tsx @@ -0,0 +1,21 @@ +import { GenericObjectType } from '@rjsf/utils'; // Removed unused types +import { Grid } from '@trussworks/react-uswds'; +import { PropsWithChildren } from 'react'; // Keep only PropsWithChildren + +// Assuming this template simply wraps children in a Grid +// Adjust props if it needs more context like FieldTemplateProps +export default function GridTemplate({ + children, + classNames, + uiSchema, +}: PropsWithChildren<{ classNames?: string; uiSchema?: GenericObjectType }>) { + // Simplified props + const uiOptions = uiSchema?.['ui:options'] || {}; + const { col = 12, ...gridProps } = uiOptions; // Example: Get grid options from uiSchema + + return ( + + {children} + + ); +} diff --git a/packages/uswds/src/Templates/HelpTemplate.tsx b/packages/uswds/src/Templates/HelpTemplate.tsx new file mode 100644 index 0000000000..a5aaf3d6ab --- /dev/null +++ b/packages/uswds/src/Templates/HelpTemplate.tsx @@ -0,0 +1,30 @@ +import { FieldHelpProps, FormContextType, RJSFSchema, StrictRJSFSchema } from '@rjsf/utils'; +import { Alert } from '@trussworks/react-uswds'; + +/** The `HelpTemplate` component renders any help desired for a field + * + * @param props - The `FieldHelpProps` to be rendered + */ +export default function HelpTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: FieldHelpProps) { + const { help, idSchema } = props; + if (!help) { + return null; + } + const id = `${idSchema.$id}__help`; + return ( + + {help} + + ); +} diff --git a/packages/uswds/src/Templates/MultiSelectTemplate.tsx b/packages/uswds/src/Templates/MultiSelectTemplate.tsx new file mode 100644 index 0000000000..c03a077304 --- /dev/null +++ b/packages/uswds/src/Templates/MultiSelectTemplate.tsx @@ -0,0 +1,78 @@ +import React from 'react'; +import { WidgetProps, EnumOptionsType } from '@rjsf/utils'; +import { Select as UswdsSelect } from '@trussworks/react-uswds'; + +// Assuming this is intended to be a Widget, using WidgetProps +export default function MultiSelectTemplate(props: WidgetProps) { + const { + id, + options, + value, + required, + disabled, + readonly, + autofocus = false, + onChange, + onBlur, + onFocus, + // placeholder, // Placeholder typically not used for multi-select + // rawErrors = [], + } = props; + const { enumOptions, enumDisabled } = options; + + const _onChange = (event: React.ChangeEvent) => { + const { target } = event; + const selectedValues = Array.from(target.selectedOptions).map( + (option: HTMLOptionElement) => option.value, + ); + // RJSF expects array for multi-select + onChange(selectedValues); + }; + + const _onBlur = ({ target }: React.FocusEvent) => { + const selectedValues = Array.from(target.selectedOptions).map( + (option: HTMLOptionElement) => option.value, + ); + onBlur(id, selectedValues); + }; + + const _onFocus = ({ target }: React.FocusEvent) => { + const selectedValues = Array.from(target.selectedOptions).map( + (option: HTMLOptionElement) => option.value, + ); + onFocus(id, selectedValues); + }; + + // Ensure value is an array + const selectValue = Array.isArray(value) + ? value + : value !== undefined && value !== null + ? [String(value)] + : []; + + return ( + + {(enumOptions as EnumOptionsType[]).map( + ({ value: optionValue, label: optionLabel }: EnumOptionsType, i: number) => { + const disabledOpt = enumDisabled && enumDisabled.includes(optionValue); + return ( + + ); + }, + )} + + ); +} diff --git a/packages/uswds/src/Templates/ObjectFieldTemplate.tsx b/packages/uswds/src/Templates/ObjectFieldTemplate.tsx new file mode 100644 index 0000000000..ef92b6fad4 --- /dev/null +++ b/packages/uswds/src/Templates/ObjectFieldTemplate.tsx @@ -0,0 +1,33 @@ +import { + FormContextType, + ObjectFieldTemplateProps, + RJSFSchema, + StrictRJSFSchema, +} from '@rjsf/utils'; +import { Grid, GridContainer } from '@trussworks/react-uswds'; + +/** The `ObjectFieldTemplate` is the template to use to render all the inner properties of an object along with the + * title and description if available. Since this will wrap rendered content, and was really more of a concept of + * RJSF than a React template, the name of the component is `ObjectField` instead of `ObjectFieldTemplate`. + * + * @param props - The `ObjectFieldTemplateProps` for this component + */ +export default function ObjectField< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>({ description, title, properties }: ObjectFieldTemplateProps) { + return ( + + {title &&

{title}

} + {description &&
{description}
} + + {properties.map((prop) => ( + + {prop.content} + + ))} + +
+ ); +} diff --git a/packages/uswds/src/Templates/OneOfFieldTemplate.tsx b/packages/uswds/src/Templates/OneOfFieldTemplate.tsx new file mode 100644 index 0000000000..0921694c2d --- /dev/null +++ b/packages/uswds/src/Templates/OneOfFieldTemplate.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { WidgetProps, EnumOptionsType } from '@rjsf/utils'; // Assuming it acts like a widget +import { Select as UswdsSelect } from '@trussworks/react-uswds'; + +// Assuming this template acts as a widget to select the 'oneOf' option index +export default function OneOfFieldTemplate(props: WidgetProps) { + const { + id, + options, // Contains enumOptions for the oneOf selection + value, // Current selected index (or undefined) + required, + disabled, + readonly, + autofocus = false, + onChange, + onBlur, + onFocus, + // schema, // Schema might be complex here + // placeholder, + // rawErrors = [], + } = props; + const { enumOptions, emptyValue } = options; // enumOptions here represent the oneOf choices + + const _onChange = ({ target: { value: targetValue } }: React.ChangeEvent) => { + // onChange likely expects the index or a value representing the chosen schema + onChange(targetValue === '' ? emptyValue || '' : targetValue); + }; + const _onBlur = ({ target: { value: targetValue } }: React.FocusEvent) => + onBlur(id, targetValue === '' ? emptyValue || '' : targetValue); + const _onFocus = ({ target: { value: targetValue } }: React.FocusEvent) => + onFocus(id, targetValue === '' ? emptyValue || '' : targetValue); + + return ( + + {/* Add a default empty option? */} + + {(enumOptions as EnumOptionsType[]).map( + ({ value: optionValue, label: optionLabel }: EnumOptionsType, i: number) => { + // Assuming optionValue might be the index or a specific identifier + return ( + + ); + }, + )} + + ); +} diff --git a/packages/uswds/src/Templates/RangeInputTemplate.tsx b/packages/uswds/src/Templates/RangeInputTemplate.tsx new file mode 100644 index 0000000000..0f133af028 --- /dev/null +++ b/packages/uswds/src/Templates/RangeInputTemplate.tsx @@ -0,0 +1,17 @@ +import { RangeInput as UswdsRange } from '@trussworks/react-uswds'; + +export default function RangeInput(props: any) { + const { id, value, min, max, step, disabled, readonly, onChange } = props; + return ( + + ); +} diff --git a/packages/uswds/src/Templates/SelectTemplate.tsx b/packages/uswds/src/Templates/SelectTemplate.tsx new file mode 100644 index 0000000000..9c5df2c039 --- /dev/null +++ b/packages/uswds/src/Templates/SelectTemplate.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { WidgetProps, EnumOptionsType } from '@rjsf/utils'; // Import EnumOptionsType +import { Select as UswdsSelect } from '@trussworks/react-uswds'; + +export default function SelectTemplate(props: WidgetProps) { + // Renamed to SelectTemplate + const { + schema, + id, + options, + value, + required, + disabled, + readonly, + multiple = false, + autofocus = false, + onChange, + onBlur, + onFocus, + placeholder, + // rawErrors = [], // Not typically used directly in select widget + } = props; + const { enumOptions, enumDisabled, emptyValue } = options; + + const _onChange = (event: React.ChangeEvent) => { + // Get event object + const { target } = event; // Destructure target from event + // Handle multiple select + if (multiple) { + // Use target here + const selectedValues = Array.from(target.selectedOptions).map( + (option: HTMLOptionElement) => option.value, + ); // Added type annotation + onChange(selectedValues); + } else { + onChange(target.value === '' ? emptyValue || '' : target.value); + } + }; + const _onBlur = ({ target: { value: targetValue } }: React.FocusEvent) => + onBlur(id, targetValue === '' ? emptyValue || '' : targetValue); + const _onFocus = ({ target: { value: targetValue } }: React.FocusEvent) => + onFocus(id, targetValue === '' ? emptyValue || '' : targetValue); + + // Ensure value is correctly formatted for multiple select + const selectValue = + multiple && !Array.isArray(value) + ? value !== undefined && value !== null + ? [String(value)] + : [] + : value; + + return ( + + {/* Add placeholder option if not multiple and placeholder exists */} + {!multiple && schema.default === undefined && placeholder && ( + + )} + {/* Map enumOptions to + ); + }, + )} + + ); +} + +// You might need separate MultiSelect.tsx and OneOfField.tsx files +// that import and use this SelectTemplate or adapt its logic. +// For now, this addresses the errors in SelectTemplate.tsx itself. +// If MultiSelect.tsx and OneOfField.tsx exist, they need similar refactoring. diff --git a/packages/uswds/src/Templates/SubmitTemplate.tsx b/packages/uswds/src/Templates/SubmitTemplate.tsx new file mode 100644 index 0000000000..eac9222132 --- /dev/null +++ b/packages/uswds/src/Templates/SubmitTemplate.tsx @@ -0,0 +1,11 @@ +import { SubmitButtonProps, getSubmitButtonOptions } from '@rjsf/utils'; +import { Button } from '@trussworks/react-uswds'; + +export default function SubmitButton(props: SubmitButtonProps) { + const { submitText, props: submitButtonProps } = getSubmitButtonOptions(props.uiSchema); + return ( + + ); +} diff --git a/packages/uswds/src/Templates/Templates.tsx b/packages/uswds/src/Templates/Templates.tsx new file mode 100644 index 0000000000..11bd9294ab --- /dev/null +++ b/packages/uswds/src/Templates/Templates.tsx @@ -0,0 +1,101 @@ +import { ChangeEvent, FocusEvent } from 'react'; +import { + BaseInputTemplateProps, + ariaDescribedByIds, + examplesId, + FormContextType, + getInputProps, + labelValue, + RJSFSchema, + StrictRJSFSchema, +} from '@rjsf/utils'; +import { Label, TextInput } from '@trussworks/react-uswds'; + +/** The `BaseInputTemplate` is the template to use to render the basic `` component for the `core` theme. + * It is used as the template for rendering many of the based widgets that differ by `type` and options only. + * It can be customized/overridden for other themes or individual implementations as needed. + * + * @param props - The `WidgetProps` for this template + */ +export default function BaseInputTemplate< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>(props: BaseInputTemplateProps) { + const { + id, + placeholder, + required, + readonly, + disabled, + label, + hideLabel, + value, + onChange, + onChangeOverride, + onBlur, + onFocus, + autofocus = false, + options, + schema, + type, + rawErrors = [], + } = props; + + const inputProps = getInputProps(schema, type, options); + + const _onChange = ({ target: { value: eventValue } }: ChangeEvent) => { + onChange(eventValue === '' ? options.emptyValue : eventValue); + }; + const _onBlur = ({ target: { value: eventValue } }: FocusEvent) => + onBlur(id, eventValue); + const _onFocus = ({ target: { value: eventValue } }: FocusEvent) => + onFocus(id, eventValue); + const InputElement = type === 'number' || type === 'integer' ? TextInput : TextInput; + + const hasError = rawErrors.length > 0; + + return ( + <> + {labelValue( + , + hideLabel, + )} + {schema.description && ( + + {schema.description} + + )} + (id) : undefined} + {...inputProps} + value={value || value === 0 ? value : ''} + onChange={onChangeOverride || _onChange} + onBlur={_onBlur} + onFocus={_onFocus} + aria-describedby={ariaDescribedByIds(id, !!schema.examples)} // Pass only id and hasExamples + type={type} + /> + {Array.isArray(schema.examples) && ( + (id)}> + {(schema.examples as string[]) + .concat(schema.default ? ([schema.default] as string[]) : []) + .map((example: any) => { + return + )} + + ); +} diff --git a/packages/uswds/src/Templates/TextAreaTemplate.tsx b/packages/uswds/src/Templates/TextAreaTemplate.tsx new file mode 100644 index 0000000000..762b9b108d --- /dev/null +++ b/packages/uswds/src/Templates/TextAreaTemplate.tsx @@ -0,0 +1,39 @@ +import { ChangeEvent, FocusEvent } from 'react'; +import { FormContextType, RJSFSchema, StrictRJSFSchema, WidgetProps } from '@rjsf/utils'; +import { Textarea } from '@trussworks/react-uswds'; + +export default function TextArea< + T = any, + S extends StrictRJSFSchema = RJSFSchema, + F extends FormContextType = any, +>({ + id, + value, + required, + disabled, + readonly, + autofocus, + onChange, + onBlur, + onFocus, + options, +}: WidgetProps) { + const _onChange = ({ target: { value: v } }: ChangeEvent) => onChange(v); + const _onBlur = ({ target: { value: v } }: FocusEvent) => onBlur(id, v); + const _onFocus = ({ target: { value: v } }: FocusEvent) => onFocus(id, v); + + return ( +