diff --git a/docs/lib.md b/docs/lib.md index dcce6846..aba9f099 100644 --- a/docs/lib.md +++ b/docs/lib.md @@ -18,6 +18,7 @@ This component serves as the primary entry point for drawing dynamic forms. | Monaco | `React.ComponentType` | | [MonacoEditor](https://github.com/react-monaco-editor/react-monaco-editor) component for Monaco [Input](./config.md#inputs) | | search | `string \| function` | | A string or function for performing a form search | | withoutInsertFFDebounce | `boolean` | | Flag that disables the delay before inserting data into the final-form store | +| generateRandomValue | `function` | | Function that is necessary to generate a random value | ### Controller diff --git a/docs/spec.md b/docs/spec.md index 2677892b..1cdd4f97 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -97,34 +97,35 @@ type Spec = ArraySpec | BooleanSpec | NumberSpec | ObjectSpec | StringSpec; ### StringSpec -| Property | Type | Required | Description | -| :------------------------- | :----------------------- | :------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| defaultValue | `string` | | Default value | -| type | `"string"` | yes | Entity type | -| required | `boolean` | | Can the value be `undefined` or `null` | -| maxLength | `bigint` | | Maximum string length | -| minLength | `bigint` | | Minimum string length | -| pattern | `string` | | RegExp to check the field value | -| patternError | `string` | | The text of the validation error by `pattern` | -| enum | `string[]` | | An array of valid values, for example for a select | -| description | `Record` | | Easy-to-understand names for values from `enum` | -| validator | `string` | | The key for determining the [validator](./config.md#validators) for the entity, if the value is empty, the base [validator](./config.md#validators) from the entity config will be used | -| viewSpec.disabled | `boolean` | | Is the field available for editing | -| viewSpec.type | `string` | yes | Key to define [Input](./config.md#inputs) for an entity | -| viewSpec.layout | `string` | | Key to define [Layout](./config.md#layouts) for an entity | -| viewSpec.layoutTitle | `string` | | Title for [Layout](./config.md#layouts) | -| viewSpec.layoutDescription | `string` | | Additional description/hint for [Layout](./config.md#layouts) | -| viewSpec.layoutOpen | `boolean` | | Expand [Layout](./config.md#layouts) at the first rendering | -| viewSpec.link | `any` | | A field containing information for forming a [link](#link) for a value | -| viewSpec.hideValues | `string[]` | | Values that are equated to empty, to exclude the rendering of unfilled fields (for example, for `enum` with `_UNSPECIFIED`) | -| viewSpec.sizeParams | `object` | | [Parameters](#sizeparams) that must be passed for an input with dimensions | -| viewSpec.monacoParams | `object` | | [Parameters](#monacoparams) that must be passed to Monaco editor | -| viewSpec.placeholder | `string` | | A short hint displayed in the field before the user enters the value | -| viewSpec.fileInput | `object` | | [Parameters](#FileInput) that must be passed to file input | -| viewSpec.copy | `boolean` | | For `true`, will add a copy value button | -| viewSpec.hidden | `boolean` | | Hide field and view | -| viewSpec.textContentParams | `object` | | [Parameters](#textcontentparams) that must be passed to text content | -| viewSpec.selectParams | `object` | | [Parameters](#selectparams) additional options for the selector | +| Property | Type | Required | Description | +| :--------------------------------- | :----------------------- | :------: | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| defaultValue | `string` | | Default value | +| type | `"string"` | yes | Entity type | +| required | `boolean` | | Can the value be `undefined` or `null` | +| maxLength | `bigint` | | Maximum string length | +| minLength | `bigint` | | Minimum string length | +| pattern | `string` | | RegExp to check the field value | +| patternError | `string` | | The text of the validation error by `pattern` | +| enum | `string[]` | | An array of valid values, for example for a select | +| description | `Record` | | Easy-to-understand names for values from `enum` | +| validator | `string` | | The key for determining the [validator](./config.md#validators) for the entity, if the value is empty, the base [validator](./config.md#validators) from the entity config will be used | +| viewSpec.disabled | `boolean` | | Is the field available for editing | +| viewSpec.type | `string` | yes | Key to define [Input](./config.md#inputs) for an entity | +| viewSpec.layout | `string` | | Key to define [Layout](./config.md#layouts) for an entity | +| viewSpec.layoutTitle | `string` | | Title for [Layout](./config.md#layouts) | +| viewSpec.layoutDescription | `string` | | Additional description/hint for [Layout](./config.md#layouts) | +| viewSpec.layoutOpen | `boolean` | | Expand [Layout](./config.md#layouts) at the first rendering | +| viewSpec.link | `any` | | A field containing information for forming a [link](#link) for a value | +| viewSpec.hideValues | `string[]` | | Values that are equated to empty, to exclude the rendering of unfilled fields (for example, for `enum` with `_UNSPECIFIED`) | +| viewSpec.sizeParams | `object` | | [Parameters](#sizeparams) that must be passed for an input with dimensions | +| viewSpec.monacoParams | `object` | | [Parameters](#monacoparams) that must be passed to Monaco editor | +| viewSpec.placeholder | `string` | | A short hint displayed in the field before the user enters the value | +| viewSpec.fileInput | `object` | | [Parameters](#FileInput) that must be passed to file input | +| viewSpec.copy | `boolean` | | For `true`, will add a copy value button | +| viewSpec.hidden | `boolean` | | Hide field and view | +| viewSpec.textContentParams | `object` | | [Parameters](#textcontentparams) that must be passed to text content | +| viewSpec.selectParams | `object` | | [Parameters](#selectparams) additional options for the selector | +| viewSpec.generateRandomValueButton | `boolean` | | Shows a button that allows you to generate a random value depending on the passed [function generateRandomValue](./lib.md#dynamicfield) | #### SizeParams diff --git a/package-lock.json b/package-lock.json index 6162f358..0945880e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@types/react-is": "^17.0.3", + "@types/uuid": "^9.0.4", "css-loader": "^5.2.6", "eslint": "^8.27.0", "final-form": "^4.20.2", @@ -54,6 +55,7 @@ "npm-run-all": "^4.1.5", "postcss": "^8.4.19", "prettier": "^2.7.1", + "randexp": "^0.5.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-final-form": "^6.5.3", @@ -67,7 +69,8 @@ "stylelint": "^14.15.0", "stylelint-scss": "^4.2.0", "ts-jest": "^29.0.5", - "typescript": "^4.9.5" + "typescript": "^4.9.5", + "uuid": "^9.0.1" }, "peerDependencies": { "@gravity-ui/uikit": "^5.0.0", @@ -737,6 +740,7 @@ "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.18.9", @@ -755,6 +759,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", "dev": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.18.6", @@ -771,6 +776,7 @@ "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.21.0.tgz", "integrity": "sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-static-block instead.", "dev": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.21.0", @@ -788,6 +794,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-dynamic-import instead.", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", @@ -804,6 +811,7 @@ "version": "7.18.9", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.9", @@ -820,6 +828,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-json-strings instead.", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", @@ -836,6 +845,7 @@ "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.20.7.tgz", "integrity": "sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead.", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.20.2", @@ -852,6 +862,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", @@ -868,6 +879,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead.", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", @@ -884,6 +896,7 @@ "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.7.tgz", "integrity": "sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead.", "dev": true, "dependencies": { "@babel/compat-data": "^7.20.5", @@ -903,6 +916,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead.", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.18.6", @@ -919,6 +933,7 @@ "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.20.2", @@ -936,6 +951,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", "dev": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.18.6", @@ -952,6 +968,7 @@ "version": "7.21.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0.tgz", "integrity": "sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.", "dev": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.18.6", @@ -970,6 +987,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead.", "dev": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", @@ -7762,6 +7780,12 @@ "integrity": "sha512-cputDpIbFgLUaGQn6Vqg3/YsJwxUwHLO13v3i5ouxT4lat0khip9AEWxtERujXV9wxIB1EyF97BSJFt6vpdI8g==", "dev": true }, + "node_modules/@types/uuid": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.4.tgz", + "integrity": "sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==", + "dev": true + }, "node_modules/@types/vinyl": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.7.tgz", @@ -11793,6 +11817,15 @@ "node": ">=12" } }, + "node_modules/drange": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz", + "integrity": "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -22436,6 +22469,28 @@ "url": "https://opencollective.com/ramda" } }, + "node_modules/randexp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz", + "integrity": "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==", + "dev": true, + "dependencies": { + "drange": "^1.0.2", + "ret": "^0.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/randexp/node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -26133,10 +26188,14 @@ } }, "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } @@ -32446,6 +32505,12 @@ "integrity": "sha512-cputDpIbFgLUaGQn6Vqg3/YsJwxUwHLO13v3i5ouxT4lat0khip9AEWxtERujXV9wxIB1EyF97BSJFt6vpdI8g==", "dev": true }, + "@types/uuid": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.4.tgz", + "integrity": "sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==", + "dev": true + }, "@types/vinyl": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.7.tgz", @@ -35500,6 +35565,12 @@ "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", "dev": true }, + "drange": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz", + "integrity": "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==", + "dev": true + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -43645,6 +43716,24 @@ "integrity": "sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==", "dev": true }, + "randexp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz", + "integrity": "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==", + "dev": true, + "requires": { + "drange": "^1.0.2", + "ret": "^0.2.0" + }, + "dependencies": { + "ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "dev": true + } + } + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -46460,9 +46549,9 @@ "dev": true }, "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", "dev": true }, "v8-compile-cache": { diff --git a/package.json b/package.json index 9f01603a..f83bb0f2 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@types/react-is": "^17.0.3", + "@types/uuid": "^9.0.4", "css-loader": "^5.2.6", "eslint": "^8.27.0", "final-form": "^4.20.2", @@ -82,6 +83,7 @@ "npm-run-all": "^4.1.5", "postcss": "^8.4.19", "prettier": "^2.7.1", + "randexp": "^0.5.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-final-form": "^6.5.3", @@ -95,7 +97,8 @@ "stylelint": "^14.15.0", "stylelint-scss": "^4.2.0", "ts-jest": "^29.0.5", - "typescript": "^4.9.5" + "typescript": "^4.9.5", + "uuid": "^9.0.1" }, "peerDependencies": { "@gravity-ui/uikit": "^5.0.0", diff --git a/src/lib/core/components/Form/DynamicField.tsx b/src/lib/core/components/Form/DynamicField.tsx index 2fcd3df9..6cf73b1e 100644 --- a/src/lib/core/components/Form/DynamicField.tsx +++ b/src/lib/core/components/Form/DynamicField.tsx @@ -5,7 +5,7 @@ import {isValidElementType} from 'react-is'; import type {MonacoEditorProps} from 'react-monaco-editor/lib/types'; import {isCorrectSpec} from '../../helpers'; -import {Spec} from '../../types'; +import {Spec, StringSpec} from '../../types'; import {Controller} from './Controller'; import { @@ -25,6 +25,7 @@ export interface DynamicFieldProps { config: DynamicFormConfig; Monaco?: React.ComponentType; search?: string | ((spec: Spec, input: FieldValue, name: string) => boolean); + generateRandomValue?: (spec: StringSpec) => string; withoutInsertFFDebounce?: boolean; __mirror?: WonderMirror; } @@ -34,6 +35,7 @@ export const DynamicField: React.FC = ({ spec, config, Monaco, + generateRandomValue, search, withoutInsertFFDebounce, __mirror, @@ -48,10 +50,11 @@ export const DynamicField: React.FC = ({ () => ({ config, Monaco: isValidElementType(Monaco) ? Monaco : undefined, + generateRandomValue, tools, __mirror, }), - [tools, config, Monaco, __mirror], + [tools, config, Monaco, __mirror, generateRandomValue], ); const searchContext = React.useMemo( diff --git a/src/lib/core/components/Form/hooks/index.tsx b/src/lib/core/components/Form/hooks/index.tsx index a2b60b07..66c85e86 100644 --- a/src/lib/core/components/Form/hooks/index.tsx +++ b/src/lib/core/components/Form/hooks/index.tsx @@ -4,6 +4,7 @@ export * from './useCreateContext'; export * from './useDynamicFieldMirror'; export * from './useDynamicFormsCtx'; export * from './useField'; +export * from './useGenerateRandomValue'; export * from './useIntegrationFF'; export * from './useRender'; export * from './useStore'; diff --git a/src/lib/core/components/Form/hooks/useGenerateRandomValue.ts b/src/lib/core/components/Form/hooks/useGenerateRandomValue.ts new file mode 100644 index 00000000..500264fb --- /dev/null +++ b/src/lib/core/components/Form/hooks/useGenerateRandomValue.ts @@ -0,0 +1,3 @@ +import {useDynamicFormsCtx} from './useDynamicFormsCtx'; + +export const useGenerateRandomValue = () => useDynamicFormsCtx().generateRandomValue; diff --git a/src/lib/core/components/Form/types/context.ts b/src/lib/core/components/Form/types/context.ts index f835673d..62eac16d 100644 --- a/src/lib/core/components/Form/types/context.ts +++ b/src/lib/core/components/Form/types/context.ts @@ -2,11 +2,14 @@ import React from 'react'; import type {MonacoEditorProps} from 'react-monaco-editor/lib/types'; +import {StringSpec} from '../../../types'; + import {DynamicFormConfig, FieldValue, ValidateError, WonderMirror} from './'; export interface DynamicFormsContext { config: DynamicFormConfig; Monaco?: React.ComponentType; + generateRandomValue?: (spec: StringSpec) => string; tools: { initialValue: FieldValue; onChange: (name: string, value: FieldValue, errors?: Record) => void; diff --git a/src/lib/core/types/specs.ts b/src/lib/core/types/specs.ts index 435266dd..830336fb 100644 --- a/src/lib/core/types/specs.ts +++ b/src/lib/core/types/specs.ts @@ -149,6 +149,7 @@ export interface StringSpec { filterPlaceholder?: string; meta?: Record; }; + generateRandomValueButton?: boolean; }; } diff --git a/src/lib/kit/components/GenerateRandomValueButton/GenerateRandomValueButton.scss b/src/lib/kit/components/GenerateRandomValueButton/GenerateRandomValueButton.scss new file mode 100644 index 00000000..b5b4a923 --- /dev/null +++ b/src/lib/kit/components/GenerateRandomValueButton/GenerateRandomValueButton.scss @@ -0,0 +1,5 @@ +@import '../../styles/variables'; + +.#{$ns}generate-random-value-button { + margin-left: 8px; +} diff --git a/src/lib/kit/components/GenerateRandomValueButton/GenerateRandomValueButton.tsx b/src/lib/kit/components/GenerateRandomValueButton/GenerateRandomValueButton.tsx new file mode 100644 index 00000000..8ec9bf6d --- /dev/null +++ b/src/lib/kit/components/GenerateRandomValueButton/GenerateRandomValueButton.tsx @@ -0,0 +1,35 @@ +import React from 'react'; + +import {Button} from '@gravity-ui/uikit'; +import _ from 'lodash'; + +import {useGenerateRandomValue} from '../../../core/components/Form/hooks'; +import {StringSpec} from '../../../core/types'; +import i18n from '../../i18n'; +import {block} from '../../utils'; + +import './GenerateRandomValueButton.scss'; + +const b = block('generate-random-value-button'); + +interface GenerateRandomValueButtonProps { + spec: StringSpec; + onChange: (value: string) => void; +} + +export const GenerateRandomValueButton: React.FC = ({ + spec, + onChange, +}) => { + const generateRandomValue = useGenerateRandomValue(); + + if (_.isFunction(generateRandomValue) && spec.viewSpec.generateRandomValueButton) { + return ( + + ); + } + + return null; +}; diff --git a/src/lib/kit/components/GenerateRandomValueButton/index.ts b/src/lib/kit/components/GenerateRandomValueButton/index.ts new file mode 100644 index 00000000..5403a490 --- /dev/null +++ b/src/lib/kit/components/GenerateRandomValueButton/index.ts @@ -0,0 +1 @@ +export * from './GenerateRandomValueButton'; diff --git a/src/lib/kit/components/Inputs/Text/Text.scss b/src/lib/kit/components/Inputs/Text/Text.scss new file mode 100644 index 00000000..df45fa19 --- /dev/null +++ b/src/lib/kit/components/Inputs/Text/Text.scss @@ -0,0 +1,5 @@ +@import '../../../styles/variables'; + +.#{$ns}text { + display: flex; +} diff --git a/src/lib/kit/components/Inputs/Text/Text.tsx b/src/lib/kit/components/Inputs/Text/Text.tsx index e27fa3f9..e6d23fa6 100644 --- a/src/lib/kit/components/Inputs/Text/Text.tsx +++ b/src/lib/kit/components/Inputs/Text/Text.tsx @@ -3,7 +3,13 @@ import React from 'react'; import {TextInput} from '@gravity-ui/uikit'; import _ from 'lodash'; -import {FieldRenderProps, NumberInputProps, StringInputProps} from '../../../../core'; +import {FieldRenderProps, NumberInputProps, StringInputProps, isStringSpec} from '../../../../core'; +import {block} from '../../../utils'; +import {GenerateRandomValueButton} from '../../GenerateRandomValueButton'; + +import './Text.scss'; + +const b = block('text'); export const Text = ({name, input, spec}: T) => { const {value, onBlur, onChange, onFocus} = input; @@ -23,18 +29,45 @@ export const Text = ({name, input return 'text'; }, [spec.viewSpec.type]); - return ( - + const textInput = React.useMemo( + () => ( + + ), + [ + handleChange, + name, + onBlur, + onFocus, + spec.viewSpec.disabled, + spec.viewSpec.placeholder, + type, + value, + ], ); + + const content = React.useMemo(() => { + if (isStringSpec(spec)) { + return ( +
+ {textInput} + +
+ ); + } + + return {textInput}; + }, [handleChange, spec, textInput]); + + return {content}; }; diff --git a/src/lib/kit/components/Inputs/TextArea/TextArea.scss b/src/lib/kit/components/Inputs/TextArea/TextArea.scss new file mode 100644 index 00000000..c137815f --- /dev/null +++ b/src/lib/kit/components/Inputs/TextArea/TextArea.scss @@ -0,0 +1,6 @@ +@import '../../../styles/variables'; + +.#{$ns}text-area { + display: flex; + justify-content: flex-start; +} diff --git a/src/lib/kit/components/Inputs/TextArea/TextArea.tsx b/src/lib/kit/components/Inputs/TextArea/TextArea.tsx index 4749290a..a5b92482 100644 --- a/src/lib/kit/components/Inputs/TextArea/TextArea.tsx +++ b/src/lib/kit/components/Inputs/TextArea/TextArea.tsx @@ -3,22 +3,31 @@ import React from 'react'; import {TextArea as TextAreaBase} from '@gravity-ui/uikit'; import {StringInput} from '../../../../core'; +import {block} from '../../../utils'; +import {GenerateRandomValueButton} from '../../GenerateRandomValueButton'; + +import './TextArea.scss'; + +const b = block('text-area'); export const TextArea: StringInput = ({name, input, spec}) => { const {value, onBlur, onChange, onFocus} = input; return ( - +
+ + +
); }; diff --git a/src/lib/kit/components/index.ts b/src/lib/kit/components/index.ts index 331c18c4..a4f06ef1 100644 --- a/src/lib/kit/components/index.ts +++ b/src/lib/kit/components/index.ts @@ -2,6 +2,7 @@ export * from './AccordeonCard'; export * from './Card'; export * from './CopyButton'; export * from './ErrorWrapper'; +export * from './GenerateRandomValueButton'; export * from './GroupIndent'; export * from './Inputs'; export * from './Layouts'; diff --git a/src/lib/kit/i18n/en.json b/src/lib/kit/i18n/en.json index 997e364b..823e273e 100644 --- a/src/lib/kit/i18n/en.json +++ b/src/lib/kit/i18n/en.json @@ -44,5 +44,6 @@ "label_delete": "Delete", "button_cancel": "Close", "button-upload_file": "Upload file", - "label-data_loaded": "Data uploaded" + "label-data_loaded": "Data uploaded", + "button-generate": "Generate" } diff --git a/src/lib/kit/i18n/ru.json b/src/lib/kit/i18n/ru.json index a69cf391..0d1909e1 100644 --- a/src/lib/kit/i18n/ru.json +++ b/src/lib/kit/i18n/ru.json @@ -44,5 +44,6 @@ "label_delete": "Удалить", "button_cancel": "Закрыть", "button-upload_file": "Загрузить файл", - "label-data_loaded": "Данные загружены" + "label-data_loaded": "Данные загружены", + "button-generate": "Сгенерировать" } diff --git a/src/stories/StringFileInput.stories.tsx b/src/stories/StringFileInput.stories.tsx index 8c86f446..0fe8da85 100644 --- a/src/stories/StringFileInput.stories.tsx +++ b/src/stories/StringFileInput.stories.tsx @@ -33,6 +33,7 @@ const excludeOptions = [ 'viewSpec.placeholder', 'viewSpec.layoutOpen', 'viewSpec.selectParams', + 'viewSpec.generateRandomValueButton', ]; const template = (spec: StringSpec = baseSpec) => { diff --git a/src/stories/StringMonaco.stories.tsx b/src/stories/StringMonaco.stories.tsx index 6845f376..69376e0b 100644 --- a/src/stories/StringMonaco.stories.tsx +++ b/src/stories/StringMonaco.stories.tsx @@ -35,6 +35,7 @@ const excludeOptions = [ 'viewSpec.fileInput', 'viewSpec.copy', 'viewSpec.selectParams', + 'viewSpec.generateRandomValueButton', ]; const template = (spec: StringSpec = baseSpec) => { diff --git a/src/stories/StringNumberWithScale.stories.tsx b/src/stories/StringNumberWithScale.stories.tsx index 98e94011..5de4a136 100644 --- a/src/stories/StringNumberWithScale.stories.tsx +++ b/src/stories/StringNumberWithScale.stories.tsx @@ -45,6 +45,7 @@ const excludeOptions = [ 'viewSpec.textContentParams', 'viewSpec.fileInput', 'viewSpec.selectParams', + 'viewSpec.generateRandomValueButton', ]; const template = (spec: StringSpec = baseSpec) => { diff --git a/src/stories/StringPassword.stories.tsx b/src/stories/StringPassword.stories.tsx index 080fac2c..62901564 100644 --- a/src/stories/StringPassword.stories.tsx +++ b/src/stories/StringPassword.stories.tsx @@ -13,11 +13,14 @@ export default { const baseSpec: StringSpec = { type: SpecTypes.String, + pattern: '/[a-z]{6}/', + patternError: 'Pattern error', viewSpec: { type: 'password', layout: 'row', layoutTitle: 'Password', placeholder: 'placeholder text', + generateRandomValueButton: true, }, }; diff --git a/src/stories/StringSelect.stories.tsx b/src/stories/StringSelect.stories.tsx index 3547ca51..34bfc609 100644 --- a/src/stories/StringSelect.stories.tsx +++ b/src/stories/StringSelect.stories.tsx @@ -58,6 +58,7 @@ const excludeOptions = [ 'viewSpec.monacoParams', 'viewSpec.textContentParams', 'viewSpec.fileInput', + 'viewSpec.generateRandomValueButton', ]; const template = (spec: StringSpec = baseSpec) => { diff --git a/src/stories/StringTextContent.stories.tsx b/src/stories/StringTextContent.stories.tsx index f124101f..fc500008 100644 --- a/src/stories/StringTextContent.stories.tsx +++ b/src/stories/StringTextContent.stories.tsx @@ -43,6 +43,7 @@ const excludeOptions = [ 'viewSpec.fileInput', 'viewSpec.copy', 'viewSpec.selectParams', + 'viewSpec.generateRandomValueButton', ]; const value = 'value'; diff --git a/src/stories/components/DynamicField/DynamicField.tsx b/src/stories/components/DynamicField/DynamicField.tsx index 67f083dd..8a38b84a 100644 --- a/src/stories/components/DynamicField/DynamicField.tsx +++ b/src/stories/components/DynamicField/DynamicField.tsx @@ -1,12 +1,15 @@ import React from 'react'; import _ from 'lodash'; +import RandExp from 'randexp'; import MonacoEditor from 'react-monaco-editor'; +import {v4 as uuidv4} from 'uuid'; import { DynamicField as BaseDynamicField, FieldValue, Spec, + StringSpec, dynamicConfig, prepareSpec, } from '../../../lib'; @@ -33,6 +36,15 @@ export const DynamicField: React.FC = ({ return cfg; }, []); + const generateRandomValue = React.useCallback((spec: StringSpec) => { + if (spec.pattern) { + const randomValue = new RandExp(spec.pattern); + return randomValue.gen(); + } else { + return uuidv4(); + } + }, []); + return ( = ({ config={config} Monaco={MonacoEditor} search={search} + generateRandomValue={generateRandomValue} /> ); }; diff --git a/src/stories/components/InputPreview/constants.ts b/src/stories/components/InputPreview/constants.ts index b98625aa..c37c35ba 100644 --- a/src/stories/components/InputPreview/constants.ts +++ b/src/stories/components/InputPreview/constants.ts @@ -386,6 +386,11 @@ const order: ArraySpec = { viewSpec: {type: 'base', layout: 'accordeon', layoutTitle: 'Order'}, }; +const generateRandomValueButton: BooleanSpec = { + type: SpecTypes.Boolean, + viewSpec: {type: 'base', layout: 'row', layoutTitle: 'Generate Random Value Button'}, +}; + const fileInput: ObjectSpec = { type: SpecTypes.Object, properties: { @@ -627,6 +632,7 @@ export const getStringOptions = (): ObjectSpec => ({ copy, hidden, selectParams, + generateRandomValueButton, }, [ 'disabled', @@ -643,6 +649,7 @@ export const getStringOptions = (): ObjectSpec => ({ 'copy', 'hidden', 'selectParams', + 'generateRandomValueButton', ], ), },