diff --git a/package-lock.json b/package-lock.json index c3d756549..6f2fed42e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@helpscout/hsds-react", - "version": "3.32.0", + "version": "3.33.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -25,7 +25,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, "requires": { "@babel/highlight": "^7.12.13" } @@ -33,14 +32,12 @@ "@babel/compat-data": { "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz", - "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==", - "dev": true + "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==" }, "@babel/core": { "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.0.tgz", "integrity": "sha512-8YqpRig5NmIHlMLw09zMlPTvUVMILjqCOtVgu+TVNWEBvy9b5I3RRyhqnrV4hjgEK7n8P9OqvkWJAFmEL6Wwfw==", - "dev": true, "requires": { "@babel/code-frame": "^7.12.13", "@babel/generator": "^7.14.0", @@ -63,7 +60,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -71,14 +67,12 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -86,7 +80,6 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.0.tgz", "integrity": "sha512-C6u00HbmsrNPug6A+CiNl8rEys7TsdcXwg12BHi2ca5rUfAs3+UwZsuDQSXnc+wCElCXMB8gMaJ3YXDdh8fAlg==", - "dev": true, "requires": { "@babel/types": "^7.14.0", "jsesc": "^2.5.1", @@ -97,7 +90,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", - "dev": true, "requires": { "@babel/types": "^7.12.13" } @@ -106,7 +98,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz", "integrity": "sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA==", - "dev": true, "requires": { "@babel/helper-explode-assignable-expression": "^7.12.13", "@babel/types": "^7.12.13" @@ -116,7 +107,6 @@ "version": "7.13.16", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz", "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==", - "dev": true, "requires": { "@babel/compat-data": "^7.13.15", "@babel/helper-validator-option": "^7.12.17", @@ -127,8 +117,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -136,7 +125,6 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.0.tgz", "integrity": "sha512-6pXDPguA5zC40Y8oI5mqr+jEUpjMJonKvknvA+vD8CYDz5uuXEwWBK8sRAsE/t3gfb1k15AQb9RhwpscC4nUJQ==", - "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.12.13", "@babel/helper-function-name": "^7.12.13", @@ -150,7 +138,6 @@ "version": "7.12.17", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz", "integrity": "sha512-p2VGmBu9oefLZ2nQpgnEnG0ZlRPvL8gAGvPUMQwUdaE8k49rOMuZpOwdQoy5qJf6K8jL3bcAMhVUlHAjIgJHUg==", - "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.12.13", "regexpu-core": "^4.7.1" @@ -160,7 +147,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.0.tgz", "integrity": "sha512-JT8tHuFjKBo8NnaUbblz7mIu1nnvUDiHVjXXkulZULyidvo/7P6TY7+YqpV37IfF+KUFxmlK04elKtGKXaiVgw==", - "dev": true, "requires": { "@babel/helper-compilation-targets": "^7.13.0", "@babel/helper-module-imports": "^7.12.13", @@ -176,7 +162,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -184,14 +169,12 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -199,7 +182,6 @@ "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz", "integrity": "sha512-qS0peLTDP8kOisG1blKbaoBg/o9OSa1qoumMjTK5pM+KDTtpxpsiubnCGP34vK8BXGcb2M9eigwgvoJryrzwWA==", - "dev": true, "requires": { "@babel/types": "^7.13.0" } @@ -208,7 +190,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", - "dev": true, "requires": { "@babel/helper-get-function-arity": "^7.12.13", "@babel/template": "^7.12.13", @@ -219,7 +200,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, "requires": { "@babel/types": "^7.12.13" } @@ -228,7 +208,6 @@ "version": "7.13.16", "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.16.tgz", "integrity": "sha512-1eMtTrXtrwscjcAeO4BVK+vvkxaLJSPFz1w1KLawz6HLNi9bPFGBNwwDyVfiu1Tv/vRRFYfoGaKhmAQPGPn5Wg==", - "dev": true, "requires": { "@babel/traverse": "^7.13.15", "@babel/types": "^7.13.16" @@ -238,7 +217,6 @@ "version": "7.13.12", "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", - "dev": true, "requires": { "@babel/types": "^7.13.12" } @@ -247,7 +225,6 @@ "version": "7.13.12", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", - "dev": true, "requires": { "@babel/types": "^7.13.12" } @@ -256,7 +233,6 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.0.tgz", "integrity": "sha512-L40t9bxIuGOfpIGA3HNkJhU9qYrf4y5A5LUSw7rGMSn+pcG8dfJ0g6Zval6YJGd2nEjI7oP00fRdnhLKndx6bw==", - "dev": true, "requires": { "@babel/helper-module-imports": "^7.13.12", "@babel/helper-replace-supers": "^7.13.12", @@ -272,7 +248,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", - "dev": true, "requires": { "@babel/types": "^7.12.13" } @@ -280,14 +255,12 @@ "@babel/helper-plugin-utils": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", - "dev": true + "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==" }, "@babel/helper-remap-async-to-generator": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz", "integrity": "sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg==", - "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.12.13", "@babel/helper-wrap-function": "^7.13.0", @@ -298,7 +271,6 @@ "version": "7.13.12", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz", "integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==", - "dev": true, "requires": { "@babel/helper-member-expression-to-functions": "^7.13.12", "@babel/helper-optimise-call-expression": "^7.12.13", @@ -310,7 +282,6 @@ "version": "7.13.12", "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", - "dev": true, "requires": { "@babel/types": "^7.13.12" } @@ -319,7 +290,6 @@ "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", - "dev": true, "requires": { "@babel/types": "^7.12.1" } @@ -328,7 +298,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, "requires": { "@babel/types": "^7.12.13" } @@ -336,20 +305,17 @@ "@babel/helper-validator-identifier": { "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true + "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==" }, "@babel/helper-validator-option": { "version": "7.12.17", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", - "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", - "dev": true + "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==" }, "@babel/helper-wrap-function": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz", "integrity": "sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA==", - "dev": true, "requires": { "@babel/helper-function-name": "^7.12.13", "@babel/template": "^7.12.13", @@ -361,7 +327,6 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz", "integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==", - "dev": true, "requires": { "@babel/template": "^7.12.13", "@babel/traverse": "^7.14.0", @@ -372,7 +337,6 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", - "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.14.0", "chalk": "^2.0.0", @@ -383,7 +347,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -417,14 +380,12 @@ "@babel/parser": { "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.0.tgz", - "integrity": "sha512-AHbfoxesfBALg33idaTBVUkLnfXtsgvJREf93p4p0Lwsz4ppfE7g1tpEXVm4vrxUcH4DVhAa9Z1m1zqf9WUC7Q==", - "dev": true + "integrity": "sha512-AHbfoxesfBALg33idaTBVUkLnfXtsgvJREf93p4p0Lwsz4ppfE7g1tpEXVm4vrxUcH4DVhAa9Z1m1zqf9WUC7Q==" }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { "version": "7.13.12", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz", "integrity": "sha512-d0u3zWKcoZf379fOeJdr1a5WPDny4aOFZ6hlfKivgK0LY7ZxNfoaHL2fWwdGtHyVvra38FC+HVYkO+byfSA8AQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", @@ -435,7 +396,6 @@ "version": "7.13.15", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.15.tgz", "integrity": "sha512-VapibkWzFeoa6ubXy/NgV5U2U4MVnUlvnx6wo1XhlsaTrLYWE0UFpDQsVrmn22q5CzeloqJ8gEMHSKxuee6ZdA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0", "@babel/helper-remap-async-to-generator": "^7.13.0", @@ -446,7 +406,6 @@ "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.13.0.tgz", "integrity": "sha512-KnTDjFNC1g+45ka0myZNvSBFLhNCLN+GeGYLDEA8Oq7MZ6yMgfLoIRh86GRT0FjtJhZw8JyUskP9uvj5pHM9Zg==", - "dev": true, "requires": { "@babel/helper-create-class-features-plugin": "^7.13.0", "@babel/helper-plugin-utils": "^7.13.0" @@ -456,7 +415,6 @@ "version": "7.13.11", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.13.11.tgz", "integrity": "sha512-fJTdFI4bfnMjvxJyNuaf8i9mVcZ0UhetaGEUHaHV9KEnibLugJkZAtXikR8KcYj+NYmI4DZMS8yQAyg+hvfSqg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0", "@babel/plugin-syntax-class-static-block": "^7.12.13" @@ -683,7 +641,6 @@ "version": "7.13.8", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.13.8.tgz", "integrity": "sha512-ONWKj0H6+wIRCkZi9zSbZtE/r73uOhMVHh256ys0UzfM7I3d4n+spZNWjOnJv2gzopumP2Wxi186vI8N0Y2JyQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0", "@babel/plugin-syntax-dynamic-import": "^7.8.3" @@ -711,7 +668,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.13.tgz", "integrity": "sha512-INAgtFo4OnLN3Y/j0VwAgw3HDXcDtX+C/erMvWzuV9v71r7urb6iyMXu7eM9IgLr1ElLlOkaHjJ0SbCmdOQ3Iw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" @@ -721,7 +677,6 @@ "version": "7.13.8", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.13.8.tgz", "integrity": "sha512-w4zOPKUFPX1mgvTmL/fcEqy34hrQ1CRcGxdphBc6snDnnqJ47EZDIyop6IwXzAC8G916hsIuXB2ZMBCExC5k7Q==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0", "@babel/plugin-syntax-json-strings": "^7.8.3" @@ -731,7 +686,6 @@ "version": "7.13.8", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.13.8.tgz", "integrity": "sha512-aul6znYB4N4HGweImqKn59Su9RS8lbUIqxtXTOcAGtNIDczoEFv+l1EhmX8rUBp3G1jMjKJm8m0jXVp63ZpS4A==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" @@ -741,7 +695,6 @@ "version": "7.13.8", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.13.8.tgz", "integrity": "sha512-iePlDPBn//UhxExyS9KyeYU7RM9WScAG+D3Hhno0PLJebAEpDZMocbDe64eqynhNAnwz/vZoL/q/QB2T1OH39A==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" @@ -751,7 +704,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.13.tgz", "integrity": "sha512-O1jFia9R8BUCl3ZGB7eitaAPu62TXJRHn7rh+ojNERCFyqRwJMTmhz+tJ+k0CwI6CLjX/ee4qW74FSqlq9I35w==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-numeric-separator": "^7.10.4" @@ -761,7 +713,6 @@ "version": "7.13.8", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz", "integrity": "sha512-DhB2EuB1Ih7S3/IRX5AFVgZ16k3EzfRbq97CxAVI1KSYcW+lexV8VZb7G7L8zuPVSdQMRn0kiBpf/Yzu9ZKH0g==", - "dev": true, "requires": { "@babel/compat-data": "^7.13.8", "@babel/helper-compilation-targets": "^7.13.8", @@ -774,7 +725,6 @@ "version": "7.13.8", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.13.8.tgz", "integrity": "sha512-0wS/4DUF1CuTmGo+NiaHfHcVSeSLj5S3e6RivPTg/2k3wOv3jO35tZ6/ZWsQhQMvdgI7CwphjQa/ccarLymHVA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" @@ -784,7 +734,6 @@ "version": "7.13.12", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.13.12.tgz", "integrity": "sha512-fcEdKOkIB7Tf4IxrgEVeFC4zeJSTr78no9wTdBuZZbqF64kzllU0ybo2zrzm7gUQfxGhBgq4E39oRs8Zx/RMYQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", @@ -795,7 +744,6 @@ "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz", "integrity": "sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q==", - "dev": true, "requires": { "@babel/helper-create-class-features-plugin": "^7.13.0", "@babel/helper-plugin-utils": "^7.13.0" @@ -805,7 +753,6 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.14.0.tgz", "integrity": "sha512-59ANdmEwwRUkLjB7CRtwJxxwtjESw+X2IePItA+RGQh+oy5RmpCh/EvVVvh5XQc3yxsm5gtv0+i9oBZhaDNVTg==", - "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.12.13", "@babel/helper-create-class-features-plugin": "^7.14.0", @@ -817,7 +764,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz", "integrity": "sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg==", - "dev": true, "requires": { "@babel/helper-create-regexp-features-plugin": "^7.12.13", "@babel/helper-plugin-utils": "^7.12.13" @@ -827,7 +773,6 @@ "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -845,7 +790,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -854,7 +798,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.12.13.tgz", "integrity": "sha512-ZmKQ0ZXR0nYpHZIIuj9zE7oIqCx2hw9TKi+lIo73NNrMPAZGHfS92/VRV0ZmPj6H2ffBgyFHXvJ5NYsNeEaP2A==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -880,7 +823,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -906,7 +848,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.3" } @@ -915,7 +856,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.12.13.tgz", "integrity": "sha512-J/RYxnlSLXZLVR7wTRsozxKT8qbsx1mNKJzXEEjQ0Kjx1ZACcyHgbanNWNCFtc36IzuWhYWPpvJFFoexoOWFmA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -933,7 +873,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -951,7 +890,6 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" } @@ -960,7 +898,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -969,7 +906,6 @@ "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" } @@ -978,7 +914,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -987,7 +922,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -996,7 +930,6 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" } @@ -1005,7 +938,6 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.0.tgz", "integrity": "sha512-bda3xF8wGl5/5btF794utNOL0Jw+9jE5C1sLZcoK7c4uonE/y3iQiyG+KbkF3WBV/paX58VCpjhxLPkdj5Fe4w==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0" } @@ -1014,7 +946,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -1023,7 +954,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.13.tgz", "integrity": "sha512-cHP3u1JiUiG2LFDKbXnwVad81GvfyIOmCD6HIEId6ojrY0Drfy2q1jw7BwN7dE84+kTnBjLkXoL3IEy/3JPu2w==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -1032,7 +962,6 @@ "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz", "integrity": "sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0" } @@ -1041,7 +970,6 @@ "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz", "integrity": "sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg==", - "dev": true, "requires": { "@babel/helper-module-imports": "^7.12.13", "@babel/helper-plugin-utils": "^7.13.0", @@ -1052,7 +980,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz", "integrity": "sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -1061,7 +988,6 @@ "version": "7.13.16", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.13.16.tgz", "integrity": "sha512-ad3PHUxGnfWF4Efd3qFuznEtZKoBp0spS+DgqzVzRPV7urEBvPLue3y2j80w4Jf2YLzZHj8TOv/Lmvdmh3b2xg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0" } @@ -1070,7 +996,6 @@ "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz", "integrity": "sha512-9BtHCPUARyVH1oXGcSJD3YpsqRLROJx5ZNP6tN5vnk17N0SVf9WCtf8Nuh1CFmgByKKAIMstitKduoCmsaDK5g==", - "dev": true, "requires": { "@babel/helper-annotate-as-pure": "^7.12.13", "@babel/helper-function-name": "^7.12.13", @@ -1085,7 +1010,6 @@ "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz", "integrity": "sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0" } @@ -1094,7 +1018,6 @@ "version": "7.13.17", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.17.tgz", "integrity": "sha512-UAUqiLv+uRLO+xuBKKMEpC+t7YRNVRqBsWWq1yKXbBZBje/t3IXCiSinZhjn/DC3qzBfICeYd2EFGEbHsh5RLA==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0" } @@ -1103,7 +1026,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz", "integrity": "sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ==", - "dev": true, "requires": { "@babel/helper-create-regexp-features-plugin": "^7.12.13", "@babel/helper-plugin-utils": "^7.12.13" @@ -1113,7 +1035,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz", "integrity": "sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -1122,7 +1043,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz", "integrity": "sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA==", - "dev": true, "requires": { "@babel/helper-builder-binary-assignment-operator-visitor": "^7.12.13", "@babel/helper-plugin-utils": "^7.12.13" @@ -1132,7 +1052,6 @@ "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.13.0.tgz", "integrity": "sha512-EXAGFMJgSX8gxWD7PZtW/P6M+z74jpx3wm/+9pn+c2dOawPpBkUX7BrfyPvo6ZpXbgRIEuwgwDb/MGlKvu2pOg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0", "@babel/plugin-syntax-flow": "^7.12.13" @@ -1142,7 +1061,6 @@ "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz", "integrity": "sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0" } @@ -1151,7 +1069,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz", "integrity": "sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ==", - "dev": true, "requires": { "@babel/helper-function-name": "^7.12.13", "@babel/helper-plugin-utils": "^7.12.13" @@ -1161,7 +1078,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz", "integrity": "sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -1170,7 +1086,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz", "integrity": "sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -1179,7 +1094,6 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.0.tgz", "integrity": "sha512-CF4c5LX4LQ03LebQxJ5JZes2OYjzBuk1TdiF7cG7d5dK4lAdw9NZmaxq5K/mouUdNeqwz3TNjnW6v01UqUNgpQ==", - "dev": true, "requires": { "@babel/helper-module-transforms": "^7.14.0", "@babel/helper-plugin-utils": "^7.13.0", @@ -1190,7 +1104,6 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.0.tgz", "integrity": "sha512-EX4QePlsTaRZQmw9BsoPeyh5OCtRGIhwfLquhxGp5e32w+dyL8htOcDwamlitmNFK6xBZYlygjdye9dbd9rUlQ==", - "dev": true, "requires": { "@babel/helper-module-transforms": "^7.14.0", "@babel/helper-plugin-utils": "^7.13.0", @@ -1202,7 +1115,6 @@ "version": "7.13.8", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz", "integrity": "sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A==", - "dev": true, "requires": { "@babel/helper-hoist-variables": "^7.13.0", "@babel/helper-module-transforms": "^7.13.0", @@ -1215,7 +1127,6 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.0.tgz", "integrity": "sha512-nPZdnWtXXeY7I87UZr9VlsWme3Y0cfFFE41Wbxz4bbaexAjNMInXPFUpRRUJ8NoMm0Cw+zxbqjdPmLhcjfazMw==", - "dev": true, "requires": { "@babel/helper-module-transforms": "^7.14.0", "@babel/helper-plugin-utils": "^7.13.0" @@ -1225,7 +1136,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz", "integrity": "sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA==", - "dev": true, "requires": { "@babel/helper-create-regexp-features-plugin": "^7.12.13" } @@ -1234,7 +1144,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz", "integrity": "sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -1243,7 +1152,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz", "integrity": "sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13", "@babel/helper-replace-supers": "^7.12.13" @@ -1253,7 +1161,6 @@ "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.13.0.tgz", "integrity": "sha512-Jt8k/h/mIwE2JFEOb3lURoY5C85ETcYPnbuAJ96zRBzh1XHtQZfs62ChZ6EP22QlC8c7Xqr9q+e1SU5qttwwjw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0" } @@ -1262,7 +1169,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz", "integrity": "sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -1312,7 +1218,6 @@ "version": "7.13.15", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz", "integrity": "sha512-Bk9cOLSz8DiurcMETZ8E2YtIVJbFCPGW28DJWUakmyVWtQSm6Wsf0p4B4BfEr/eL2Nkhe/CICiUiMOCi1TPhuQ==", - "dev": true, "requires": { "regenerator-transform": "^0.14.2" } @@ -1321,7 +1226,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz", "integrity": "sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -1352,7 +1256,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz", "integrity": "sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -1361,7 +1264,6 @@ "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz", "integrity": "sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" @@ -1371,7 +1273,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz", "integrity": "sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -1380,7 +1281,6 @@ "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz", "integrity": "sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0" } @@ -1389,7 +1289,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz", "integrity": "sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -1398,7 +1297,6 @@ "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.13.0.tgz", "integrity": "sha512-elQEwluzaU8R8dbVuW2Q2Y8Nznf7hnjM7+DSCd14Lo5fF63C9qNLbwZYbmZrtV9/ySpSUpkRpQXvJb6xyu4hCQ==", - "dev": true, "requires": { "@babel/helper-create-class-features-plugin": "^7.13.0", "@babel/helper-plugin-utils": "^7.13.0", @@ -1409,7 +1307,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz", "integrity": "sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" } @@ -1418,7 +1315,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz", "integrity": "sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA==", - "dev": true, "requires": { "@babel/helper-create-regexp-features-plugin": "^7.12.13", "@babel/helper-plugin-utils": "^7.12.13" @@ -1428,7 +1324,6 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.0.tgz", "integrity": "sha512-GWRCdBv2whxqqaSi7bo/BEXf070G/fWFMEdCnmoRg2CZJy4GK06ovFuEjJrZhDRXYgBsYtxVbG8GUHvw+UWBkQ==", - "dev": true, "requires": { "@babel/compat-data": "^7.14.0", "@babel/helper-compilation-targets": "^7.13.16", @@ -1508,8 +1403,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -1517,7 +1411,6 @@ "version": "7.13.13", "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.13.13.tgz", "integrity": "sha512-MDtwtamMifqq3R2mC7l3A3uFalUb3NH5TIBQWjN/epEPlZktcLq4se3J+ivckKrLMGsR7H9LW8+pYuIUN9tsKg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0", "@babel/helper-validator-option": "^7.12.17", @@ -1528,7 +1421,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", @@ -1555,7 +1447,6 @@ "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.13.0.tgz", "integrity": "sha512-LXJwxrHy0N3f6gIJlYbLta1D9BDtHpQeqwzM0LIfjDlr6UE/D5Mc7W4iDiQzaE+ks0sTjT26ArcHWnJVt0QiHw==", - "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.13.0", "@babel/helper-validator-option": "^7.12.17", @@ -1566,7 +1457,6 @@ "version": "7.13.16", "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.13.16.tgz", "integrity": "sha512-dh2t11ysujTwByQjXNgJ48QZ2zcXKQVdV8s0TbeMI0flmtGWCdTwK9tJiACHXPLmncm5+ktNn/diojA45JE4jg==", - "dev": true, "requires": { "clone-deep": "^4.0.1", "find-cache-dir": "^2.0.0", @@ -1597,7 +1487,6 @@ "version": "7.12.13", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, "requires": { "@babel/code-frame": "^7.12.13", "@babel/parser": "^7.12.13", @@ -1608,7 +1497,6 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.0.tgz", "integrity": "sha512-dZ/a371EE5XNhTHomvtuLTUyx6UEoJmYX+DT5zBCQN3McHemsuIaKKYqsc/fs26BEkHs/lBZy0J571LP5z9kQA==", - "dev": true, "requires": { "@babel/code-frame": "^7.12.13", "@babel/generator": "^7.14.0", @@ -1624,7 +1512,6 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -1632,8 +1519,7 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" } } }, @@ -1641,7 +1527,6 @@ "version": "7.14.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.0.tgz", "integrity": "sha512-O2LVLdcnWplaGxiPBz12d0HcdN8QdxdsWYhz5LSeuukV/5mn2xUUc3gBeU4QBYPJ18g/UToe8F532XJ608prmg==", - "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.14.0", "to-fast-properties": "^2.0.0" @@ -2024,9 +1909,9 @@ } }, "@helpscout/colorway": { - "version": "0.9.7", - "resolved": "https://registry.npmjs.org/@helpscout/colorway/-/colorway-0.9.7.tgz", - "integrity": "sha512-OCujSRhTek+YQcMRvW4hPqLDbwVpNwt8ickKbTxiaRenN2kNxFOm326h5uzhPVDwgG6plunarGRwLp6vfelhEQ==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@helpscout/colorway/-/colorway-0.10.0.tgz", + "integrity": "sha512-24r2rgkBBp4s7NtsohHcqEsUoQ2FvSmumysknW66g3xiJpWyW/kLx5aYaT2CUlRNdKnh9FnffAhWVftSN260Vw==", "dev": true, "requires": { "fast-glob": "^3.2.4", @@ -2939,7 +2824,6 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==", - "dev": true, "requires": { "@nodelib/fs.stat": "2.0.4", "run-parallel": "^1.1.9" @@ -2948,14 +2832,12 @@ "@nodelib/fs.stat": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz", - "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==", - "dev": true + "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==" }, "@nodelib/fs.walk": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz", "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==", - "dev": true, "requires": { "@nodelib/fs.scandir": "2.1.4", "fastq": "^1.6.0" @@ -5904,6 +5786,51 @@ "pretty-format": "^26.0.0" } }, + "@types/jscodeshift": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@types/jscodeshift/-/jscodeshift-0.11.3.tgz", + "integrity": "sha512-pM0JD9kWVDH9DQp5Y6td16924V3MwZHei8P3cTeuFhXpzpk0K+iWraBZz8wF61QkFs9fZeAQNX0q8SG0+TFm2w==", + "dev": true, + "requires": { + "ast-types": "^0.14.1", + "recast": "^0.20.3" + }, + "dependencies": { + "ast-types": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", + "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", + "dev": true, + "requires": { + "tslib": "^2.0.1" + } + }, + "recast": { + "version": "0.20.5", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.20.5.tgz", + "integrity": "sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ==", + "dev": true, + "requires": { + "ast-types": "0.14.2", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tslib": "^2.0.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==", + "dev": true + } + } + }, "@types/json-schema": { "version": "7.0.7", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", @@ -5967,8 +5894,7 @@ "@types/minimist": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.1.tgz", - "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==", - "dev": true + "integrity": "sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg==" }, "@types/node": { "version": "15.0.1", @@ -5989,8 +5915,7 @@ "@types/normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", - "dev": true + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==" }, "@types/npmlog": { "version": "4.1.3", @@ -6660,14 +6585,12 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "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==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -6773,26 +6696,22 @@ "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" }, "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" }, "array-differ": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", - "dev": true + "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=" }, "array-filter": { "version": "1.0.0", @@ -6839,20 +6758,17 @@ "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" }, "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, "array.prototype.find": { "version": "2.1.1", @@ -7037,14 +6953,12 @@ "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, "ast-types": { "version": "0.13.3", "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.3.tgz", - "integrity": "sha512-XTZ7xGML849LkQP86sWdQzfhwbt3YwIO6MqbX9mUNYY98VKaaVZP7YNNm70IpwecbkkxmfC5IYAzOQ/2p29zRA==", - "dev": true + "integrity": "sha512-XTZ7xGML849LkQP86sWdQzfhwbt3YwIO6MqbX9mUNYY98VKaaVZP7YNNm70IpwecbkkxmfC5IYAzOQ/2p29zRA==" }, "ast-types-flow": { "version": "0.0.7", @@ -7098,8 +7012,7 @@ "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "autoprefixer": { "version": "9.8.6", @@ -7152,8 +7065,7 @@ "babel-core": { "version": "7.0.0-bridge.0", "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "dev": true + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==" }, "babel-eslint": { "version": "10.1.0", @@ -7373,7 +7285,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, "requires": { "object.assign": "^4.1.0" } @@ -7520,7 +7431,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.0.tgz", "integrity": "sha512-9bNwiR0dS881c5SHnzCmmGlMkJLl0OUZvxrxHo9w/iNoRuqaPjqlvBf4HrovXtQs/au5yKkpcdgfT1cC5PAZwg==", - "dev": true, "requires": { "@babel/compat-data": "^7.13.11", "@babel/helper-define-polyfill-provider": "^0.2.0", @@ -7530,8 +7440,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -7539,7 +7448,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.0.tgz", "integrity": "sha512-zZyi7p3BCUyzNxLx8KV61zTINkkV65zVkDAFNZmrTCRVhjo1jAS+YLvDJ9Jgd/w2tsAviCwFHReYfxO3Iql8Yg==", - "dev": true, "requires": { "@babel/helper-define-polyfill-provider": "^0.2.0", "core-js-compat": "^3.9.1" @@ -7549,7 +7457,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.0.tgz", "integrity": "sha512-J7vKbCuD2Xi/eEHxquHN14bXAW9CXtecwuLrOIDJtcZzTaPzV1VdEfoUf9AzcRBMolKUQKM9/GVojeh0hFiqMg==", - "dev": true, "requires": { "@babel/helper-define-polyfill-provider": "^0.2.0" } @@ -7657,14 +7564,12 @@ "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, "requires": { "cache-base": "^1.0.1", "class-utils": "^0.3.5", @@ -7679,7 +7584,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -7688,7 +7592,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -7697,7 +7600,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -7706,7 +7608,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -7952,7 +7853,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7962,7 +7862,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, "requires": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", @@ -7980,7 +7879,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -8095,7 +7993,6 @@ "version": "4.16.5", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.5.tgz", "integrity": "sha512-C2HAjrM1AI/djrpAUU/tr4pml1DqLIzJKSLDBXBrNErl9ZCCTXdhwxdJjYc16953+mBWf7Lw+uUJgpgb8cN71A==", - "dev": true, "requires": { "caniuse-lite": "^1.0.30001214", "colorette": "^1.2.2", @@ -8133,8 +8030,7 @@ "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" }, "buffer-xor": { "version": "1.0.3", @@ -8421,7 +8317,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, "requires": { "collection-visit": "^1.0.0", "component-emitter": "^1.2.1", @@ -8532,7 +8427,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -8571,8 +8465,7 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "camelcase-css": { "version": "2.0.1", @@ -8584,7 +8477,6 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", - "dev": true, "requires": { "camelcase": "^5.3.1", "map-obj": "^4.0.0", @@ -8605,8 +8497,7 @@ "caniuse-lite": { "version": "1.0.30001282", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001282.tgz", - "integrity": "sha512-YhF/hG6nqBEllymSIjLtR2iWDDnChvhnVJqp+vloyt2tEHFG1yBR+ac2B/rOw0qOK0m0lEXU2dv4E/sMk5P9Kg==", - "dev": true + "integrity": "sha512-YhF/hG6nqBEllymSIjLtR2iWDDnChvhnVJqp+vloyt2tEHFG1yBR+ac2B/rOw0qOK0m0lEXU2dv4E/sMk5P9Kg==" }, "capture-exit": { "version": "2.0.0", @@ -8639,7 +8530,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -8651,14 +8541,12 @@ "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" } } }, @@ -8689,8 +8577,7 @@ "chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "check-dependencies": { "version": "1.1.0", @@ -9018,7 +8905,6 @@ "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, "requires": { "arr-union": "^3.1.0", "define-property": "^0.2.5", @@ -9030,7 +8916,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -9144,8 +9029,7 @@ "cli-width": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" }, "cliui": { "version": "6.0.0", @@ -9237,7 +9121,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, "requires": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", @@ -9305,7 +9188,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, "requires": { "map-visit": "^1.0.0", "object-visit": "^1.0.0" @@ -9315,7 +9197,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -9323,20 +9204,17 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "colorette": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", - "dev": true + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==" }, "colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" }, "combined-stream": { "version": "1.0.8", @@ -9368,8 +9246,7 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" }, "compare-versions": { "version": "3.6.0", @@ -9380,8 +9257,7 @@ "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "compressible": { "version": "2.0.18", @@ -9423,8 +9299,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", @@ -9518,7 +9393,6 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, "requires": { "safe-buffer": "~5.1.1" } @@ -9572,8 +9446,7 @@ "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "copy-to-clipboard": { "version": "3.3.1", @@ -9593,7 +9466,6 @@ "version": "3.11.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.11.1.tgz", "integrity": "sha512-aZ0e4tmlG/aOBHj92/TuOuZwp6jFvn1WNabU5VOVixzhu5t5Ao+JZkQOPlgNXu6ynwLrwJxklT4Gw1G1VGEh+g==", - "dev": true, "requires": { "browserslist": "^4.16.5", "semver": "7.0.0" @@ -9602,8 +9474,7 @@ "semver": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==" } } }, @@ -9911,7 +9782,6 @@ "version": "2.2.5", "resolved": "https://registry.npmjs.org/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz", "integrity": "sha1-hF/wwINKPe2dFg2sptOQkGuyiMw=", - "dev": true, "requires": { "lru-cache": "^4.0.0", "which": "^1.2.8" @@ -9921,7 +9791,6 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -9930,8 +9799,7 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" } } }, @@ -10432,7 +10300,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -10440,14 +10307,12 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decamelize-keys": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", - "dev": true, "requires": { "decamelize": "^1.1.0", "map-obj": "^1.0.0" @@ -10456,8 +10321,7 @@ "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", - "dev": true + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" } } }, @@ -10470,8 +10334,7 @@ "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, "decompress-response": { "version": "3.3.0", @@ -10539,7 +10402,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -10548,7 +10410,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, "requires": { "is-descriptor": "^1.0.2", "isobject": "^3.0.1" @@ -10558,7 +10419,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -10567,7 +10427,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -10576,7 +10435,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -10745,7 +10603,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, "requires": { "path-type": "^4.0.0" } @@ -11010,8 +10867,7 @@ "electron-to-chromium": { "version": "1.3.723", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.723.tgz", - "integrity": "sha512-L+WXyXI7c7+G1V8ANzRsPI5giiimLAUDC6Zs1ojHHPhYXb3k/iTABFmWjivEtsWrRQymjnO66/rO2ZTABGdmWg==", - "dev": true + "integrity": "sha512-L+WXyXI7c7+G1V8ANzRsPI5giiimLAUDC6Zs1ojHHPhYXb3k/iTABFmWjivEtsWrRQymjnO66/rO2ZTABGdmWg==" }, "elegant-spinner": { "version": "1.0.1", @@ -11109,7 +10965,6 @@ "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, "requires": { "once": "^1.4.0" } @@ -11262,7 +11117,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -11362,8 +11216,7 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, "escape-goat": { "version": "2.1.1", @@ -11380,8 +11233,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "2.0.0", @@ -12013,8 +11865,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { "version": "1.4.0", @@ -12070,8 +11921,7 @@ "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" }, "etag": { "version": "1.8.1", @@ -12137,7 +11987,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, "requires": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", @@ -12154,7 +12003,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -12165,7 +12013,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, "requires": { "pump": "^3.0.0" } @@ -12173,20 +12020,17 @@ "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==" }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, "requires": { "path-key": "^3.0.0" } @@ -12195,7 +12039,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "requires": { "mimic-fn": "^2.1.0" } @@ -12203,14 +12046,12 @@ "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -12218,14 +12059,12 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -12274,7 +12113,6 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, "requires": { "debug": "^2.3.3", "define-property": "^0.2.5", @@ -12289,7 +12127,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -12298,7 +12135,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -12416,7 +12252,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -12426,7 +12261,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -12437,7 +12271,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, "requires": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -12448,7 +12281,6 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -12457,7 +12289,6 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, "requires": { "os-tmpdir": "~1.0.2" } @@ -12468,7 +12299,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, "requires": { "array-unique": "^0.3.2", "define-property": "^1.0.0", @@ -12484,7 +12314,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -12493,7 +12322,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -12502,7 +12330,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -12511,7 +12338,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -12520,7 +12346,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -12579,7 +12404,6 @@ "version": "3.2.5", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", - "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -12593,7 +12417,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -12602,7 +12425,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -12611,7 +12433,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -12619,14 +12440,12 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "micromatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, "requires": { "braces": "^3.0.1", "picomatch": "^2.2.3" @@ -12636,7 +12455,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -12677,7 +12495,6 @@ "version": "1.11.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", - "dev": true, "requires": { "reusify": "^1.0.4" } @@ -12829,7 +12646,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, "requires": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", @@ -12841,7 +12657,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -12867,7 +12682,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, "requires": { "commondir": "^1.0.1", "make-dir": "^2.0.0", @@ -12884,7 +12698,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, "requires": { "locate-path": "^3.0.0" } @@ -12940,8 +12753,7 @@ "flow-parser": { "version": "0.150.0", "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.150.0.tgz", - "integrity": "sha512-kYoNG9BH4RlYwEt0A9+30ZiUiYBNB9KLDeDI4rSBb0QsoH5F12ywNUOHp5JD2KVYz6nhapbn/kCIZALSmd3q6w==", - "dev": true + "integrity": "sha512-kYoNG9BH4RlYwEt0A9+30ZiUiYBNB9KLDeDI4rSBb0QsoH5F12ywNUOHp5JD2KVYz6nhapbn/kCIZALSmd3q6w==" }, "flush-write-stream": { "version": "1.1.1", @@ -12962,8 +12774,7 @@ "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" }, "foreground-child": { "version": "2.0.0", @@ -13079,7 +12890,6 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, "requires": { "map-cache": "^0.2.2" } @@ -13176,8 +12986,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "2.1.3", @@ -13188,8 +12997,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "function.prototype.name": { "version": "1.1.4", @@ -13240,8 +13048,7 @@ "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==" }, "get-caller-file": { "version": "2.0.5", @@ -13253,7 +13060,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -13300,8 +13106,7 @@ "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" }, "getos": { "version": "3.2.1", @@ -13346,7 +13151,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -13482,8 +13286,7 @@ "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "globalthis": { "version": "1.0.2", @@ -13498,7 +13301,6 @@ "version": "11.0.3", "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", - "dev": true, "requires": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -13511,8 +13313,7 @@ "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" } } }, @@ -13553,8 +13354,7 @@ "graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" }, "growly": { "version": "1.3.0", @@ -13597,14 +13397,12 @@ "hard-rejection": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", - "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", - "dev": true + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==" }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -13613,7 +13411,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -13627,8 +13424,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-glob": { "version": "1.0.0", @@ -13659,8 +13455,7 @@ "has-symbols": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==" }, "has-to-string-tag-x": { "version": "1.4.1", @@ -13690,7 +13485,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, "requires": { "get-value": "^2.0.6", "has-values": "^1.0.0", @@ -13701,7 +13495,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, "requires": { "is-number": "^3.0.0", "kind-of": "^4.0.0" @@ -13711,7 +13504,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -13918,8 +13710,7 @@ "hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" }, "html-element-map": { "version": "1.3.0", @@ -14125,8 +13916,7 @@ "human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==" }, "husky": { "version": "4.2.5", @@ -14278,8 +14068,7 @@ "ignore": { "version": "5.1.8", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", - "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", - "dev": true + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==" }, "ignore-walk": { "version": "3.0.4", @@ -14378,8 +14167,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "indent-string": { "version": "3.2.0", @@ -14397,7 +14185,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -14406,8 +14193,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.8", @@ -14425,7 +14211,6 @@ "version": "7.3.3", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, "requires": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.0", @@ -14446,7 +14231,6 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, "requires": { "type-fest": "^0.21.3" } @@ -14454,14 +14238,12 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -14470,7 +14252,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -14480,7 +14261,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, "requires": { "restore-cursor": "^3.1.0" } @@ -14489,7 +14269,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -14497,20 +14276,17 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, "requires": { "escape-string-regexp": "^1.0.5" } @@ -14518,26 +14294,22 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "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", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "requires": { "mimic-fn": "^2.1.0" } @@ -14546,7 +14318,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, "requires": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" @@ -14556,7 +14327,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -14567,7 +14337,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, "requires": { "ansi-regex": "^5.0.0" } @@ -14576,7 +14345,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -14584,8 +14352,7 @@ "type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" } } }, @@ -14768,7 +14535,6 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -14777,7 +14543,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -14813,8 +14578,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-bigint": { "version": "1.0.1", @@ -14844,8 +14608,7 @@ "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-callable": { "version": "1.2.3", @@ -14866,7 +14629,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.3.0.tgz", "integrity": "sha512-xSphU2KG9867tsYdLD4RWQ1VqdFl4HTO9Thf3I/3dLEfr0dbPTWKsuCKrgqMljg4nPE+Gq0VCnzT3gr0CyBmsw==", - "dev": true, "requires": { "has": "^1.0.3" } @@ -14875,7 +14637,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -14884,7 +14645,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -14907,7 +14667,6 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", @@ -14917,8 +14676,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" } } }, @@ -14941,14 +14699,12 @@ "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -14975,7 +14731,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-git-clean/-/is-git-clean-1.1.0.tgz", "integrity": "sha1-E6vW3acRuwiq/UJgTaSHhF3c+I0=", - "dev": true, "requires": { "execa": "^0.4.0", "is-obj": "^1.0.1", @@ -14986,7 +14741,6 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.4.0.tgz", "integrity": "sha1-TrZGejaglfq7KXD/nV4/t7zm68M=", - "dev": true, "requires": { "cross-spawn-async": "^2.1.1", "is-stream": "^1.1.0", @@ -14999,14 +14753,12 @@ "is-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, "npm-run-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-1.0.0.tgz", "integrity": "sha1-9cMr9ZX+ga6Sfa7FLoL4sACsPI8=", - "dev": true, "requires": { "path-key": "^1.0.0" } @@ -15014,8 +14766,7 @@ "path-key": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-1.0.0.tgz", - "integrity": "sha1-XVPVeAGWRsDWiADbThRua9wqx68=", - "dev": true + "integrity": "sha1-XVPVeAGWRsDWiADbThRua9wqx68=" } } }, @@ -15023,7 +14774,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -15081,7 +14831,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -15090,7 +14839,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -15151,14 +14899,12 @@ "is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", - "dev": true + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "requires": { "isobject": "^3.0.1" } @@ -15292,8 +15038,7 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" }, "is-word-character": { "version": "1.0.4", @@ -15316,20 +15061,17 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "isomorphic-fetch": { "version": "2.2.1", @@ -17473,7 +17215,6 @@ "version": "0.7.1", "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.7.1.tgz", "integrity": "sha512-YMkZSyoc8zg5woZL23cmWlnFLPH/mHilonGA7Qbzs7H6M4v4PH0Qsn4jeDyw+CHhVoAnm9UxQyB0Yw1OT+mktA==", - "dev": true, "requires": { "@babel/core": "^7.1.6", "@babel/parser": "^7.1.6", @@ -17499,7 +17240,6 @@ "version": "0.18.10", "resolved": "https://registry.npmjs.org/recast/-/recast-0.18.10.tgz", "integrity": "sha512-XNvYvkfdAN9QewbrxeTOjgINkdY/odTgTS56ZNEWL9Ml0weT4T3sFtvnTuF+Gxyu46ANcRm1ntrF6F5LAJPAaQ==", - "dev": true, "requires": { "ast-types": "0.13.3", "esprima": "~4.0.0", @@ -17510,14 +17250,12 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, "write-file-atomic": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "dev": true, "requires": { "graceful-fs": "^4.1.11", "imurmurhash": "^0.1.4", @@ -17594,8 +17332,7 @@ "jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, "json-buffer": { "version": "3.0.0", @@ -17612,8 +17349,7 @@ "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "json-schema": { "version": "0.2.3", @@ -17643,7 +17379,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, "requires": { "minimist": "^1.2.5" } @@ -17703,8 +17438,7 @@ "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "klaw": { "version": "1.3.1", @@ -17809,8 +17543,7 @@ "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" }, "listr": { "version": "0.14.3", @@ -17968,7 +17701,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -17977,8 +17709,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash._reinterpolate": { "version": "3.0.0", @@ -18205,7 +17936,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, "requires": { "pify": "^4.0.1", "semver": "^5.6.0" @@ -18232,14 +17962,12 @@ "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" }, "map-obj": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.2.1.tgz", - "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==", - "dev": true + "integrity": "sha512-+WA2/1sPmDj1dlvvJmB5G6JKfY9dpn7EVBUL06+y6PoljPkh+6V1QihwxNkbcGxCRjt2b0F9K0taiCuo7MbdFQ==" }, "map-or-similar": { "version": "1.5.0", @@ -18257,7 +17985,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, "requires": { "object-visit": "^1.0.0" } @@ -18448,7 +18175,6 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/meow/-/meow-6.1.1.tgz", "integrity": "sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==", - "dev": true, "requires": { "@types/minimist": "^1.2.0", "camelcase-keys": "^6.2.2", @@ -18466,8 +18192,7 @@ "type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==" } } }, @@ -18480,14 +18205,12 @@ "merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" }, "methods": { "version": "1.1.2", @@ -18532,7 +18255,6 @@ "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -18612,8 +18334,7 @@ "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==" }, "mini-create-react-context": { "version": "0.4.1", @@ -18641,7 +18362,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -18649,14 +18369,12 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minimist-options": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", - "dev": true, "requires": { "arrify": "^1.0.1", "is-plain-obj": "^1.1.0", @@ -18666,8 +18384,7 @@ "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" } } }, @@ -18739,7 +18456,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" @@ -18749,7 +18465,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -18823,14 +18538,12 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "multimatch": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", - "dev": true, "requires": { "array-differ": "^1.0.0", "array-union": "^1.0.1", @@ -18842,7 +18555,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dev": true, "requires": { "array-uniq": "^1.0.1" } @@ -18850,16 +18562,14 @@ "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" } } }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, "nan": { "version": "2.14.2", @@ -18872,7 +18582,6 @@ "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -18937,8 +18646,7 @@ "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "nested-error-stacks": { "version": "2.1.0", @@ -19040,7 +18748,6 @@ "version": "0.1.17", "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", "integrity": "sha1-X1Zl2TNRM1yqvvjxxVRRbPXx5OU=", - "dev": true, "requires": { "minimatch": "^3.0.2" } @@ -19112,8 +18819,7 @@ "node-modules-regexp": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=" }, "node-notifier": { "version": "8.0.2", @@ -19181,14 +18887,12 @@ "node-releases": { "version": "1.1.71", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", - "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", - "dev": true + "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==" }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, "requires": { "hosted-git-info": "^2.1.4", "resolve": "^1.10.0", @@ -20058,7 +19762,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, "requires": { "copy-descriptor": "^0.1.0", "define-property": "^0.2.5", @@ -20069,7 +19772,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -20078,7 +19780,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -20104,14 +19805,12 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, "requires": { "isobject": "^3.0.0" } @@ -20120,7 +19819,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3", @@ -20167,7 +19865,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, "requires": { "isobject": "^3.0.1" } @@ -20209,7 +19906,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -20285,8 +19981,7 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { "version": "0.1.5", @@ -20428,7 +20123,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "requires": { "p-try": "^2.0.0" } @@ -20437,7 +20131,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, "requires": { "p-limit": "^2.0.0" } @@ -20487,8 +20180,7 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "package-hash": { "version": "4.0.0", @@ -20597,7 +20289,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -20653,8 +20344,7 @@ "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" }, "path-browserify": { "version": "0.0.1", @@ -20671,14 +20361,12 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "2.0.1", @@ -20689,8 +20377,7 @@ "path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "path-to-regexp": { "version": "2.4.0", @@ -20700,8 +20387,7 @@ "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, "pause-stream": { "version": "0.0.11", @@ -20746,20 +20432,17 @@ "picomatch": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", - "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", - "dev": true + "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==" }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, "pirates": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, "requires": { "node-modules-regexp": "^1.0.0" } @@ -20768,7 +20451,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, "requires": { "find-up": "^3.0.0" } @@ -20812,8 +20494,7 @@ "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, "postcss": { "version": "7.0.39", @@ -21307,8 +20988,7 @@ "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" }, "process": { "version": "0.11.10", @@ -21572,8 +21252,7 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.8.0", @@ -21719,7 +21398,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -21854,14 +21532,12 @@ "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" }, "quick-lru": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", - "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", - "dev": true + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==" }, "raf": { "version": "3.4.1", @@ -22600,7 +22276,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, "requires": { "@types/normalize-package-data": "^2.4.0", "normalize-package-data": "^2.5.0", @@ -22611,8 +22286,7 @@ "type-fest": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" } } }, @@ -22620,7 +22294,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, "requires": { "find-up": "^4.1.0", "read-pkg": "^5.2.0", @@ -22631,7 +22304,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -22641,7 +22313,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -22650,7 +22321,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -22658,8 +22328,7 @@ "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" } } }, @@ -22732,7 +22401,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, "requires": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" @@ -22741,8 +22409,7 @@ "indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==" } } }, @@ -22774,14 +22441,12 @@ "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==" }, "regenerate-unicode-properties": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", - "dev": true, "requires": { "regenerate": "^1.4.0" } @@ -22795,7 +22460,6 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", - "dev": true, "requires": { "@babel/runtime": "^7.8.4" } @@ -22804,7 +22468,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, "requires": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" @@ -22830,7 +22493,6 @@ "version": "4.7.1", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", - "dev": true, "requires": { "regenerate": "^1.4.0", "regenerate-unicode-properties": "^8.2.0", @@ -22861,14 +22523,12 @@ "regjsgen": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", - "dev": true + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==" }, "regjsparser": { "version": "0.6.9", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.9.tgz", "integrity": "sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==", - "dev": true, "requires": { "jsesc": "~0.5.0" }, @@ -22876,8 +22536,7 @@ "jsesc": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=" } } }, @@ -23422,14 +23081,12 @@ "repeat-element": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==" }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "request": { "version": "2.88.2", @@ -23529,7 +23186,6 @@ "version": "1.20.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, "requires": { "is-core-module": "^2.2.0", "path-parse": "^1.0.6" @@ -23569,8 +23225,7 @@ "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, "responselike": { "version": "1.0.2", @@ -23594,14 +23249,12 @@ "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, "rimraf": { "version": "3.0.2", @@ -23641,14 +23294,12 @@ "run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, "requires": { "queue-microtask": "^1.2.2" } @@ -23666,7 +23317,6 @@ "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, "requires": { "tslib": "^1.9.0" } @@ -23674,14 +23324,12 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, "requires": { "ret": "~0.1.10" } @@ -23776,8 +23424,7 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "semver-compare": { "version": "1.0.0", @@ -23895,7 +23542,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -23907,7 +23553,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -23939,7 +23584,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, "requires": { "kind-of": "^6.0.2" } @@ -24011,8 +23655,7 @@ "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, "simplebar": { "version": "5.3.6", @@ -24086,7 +23729,6 @@ "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, "requires": { "base": "^0.11.1", "debug": "^2.2.0", @@ -24102,7 +23744,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -24111,7 +23752,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -24122,7 +23762,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, "requires": { "define-property": "^1.0.0", "isobject": "^3.0.0", @@ -24133,7 +23772,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -24142,7 +23780,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -24151,7 +23788,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -24160,7 +23796,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -24173,7 +23808,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, "requires": { "kind-of": "^3.2.0" }, @@ -24182,7 +23816,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -24207,14 +23840,12 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-resolve": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, "requires": { "atob": "^2.1.2", "decode-uri-component": "^0.2.0", @@ -24227,7 +23858,6 @@ "version": "0.5.19", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -24236,16 +23866,14 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, "source-map-url": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==" }, "space-separated-tokens": { "version": "1.1.5", @@ -24297,7 +23925,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -24306,14 +23933,12 @@ "spdx-exceptions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" }, "spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -24322,8 +23947,7 @@ "spdx-license-ids": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", - "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", - "dev": true + "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==" }, "specificity": { "version": "0.4.1", @@ -24344,7 +23968,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, "requires": { "extend-shallow": "^3.0.0" } @@ -24556,7 +24179,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, "requires": { "define-property": "^0.2.5", "object-copy": "^0.1.0" @@ -24566,7 +24188,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -24909,7 +24530,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -24923,20 +24543,17 @@ "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" }, "strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, "requires": { "min-indent": "^1.0.0" } @@ -25419,7 +25036,6 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -25738,7 +25354,6 @@ "version": "0.8.4", "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", - "dev": true, "requires": { "rimraf": "~2.6.2" }, @@ -25747,7 +25362,6 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, "requires": { "glob": "^7.1.3" } @@ -26097,8 +25711,7 @@ "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "through2": { "version": "2.0.5", @@ -26180,14 +25793,12 @@ "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -26196,7 +25807,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -26213,7 +25823,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, "requires": { "define-property": "^2.0.2", "extend-shallow": "^3.0.2", @@ -26225,7 +25834,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" @@ -26271,8 +25879,7 @@ "trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", - "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", - "dev": true + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==" }, "trim-trailing-lines": { "version": "1.1.4", @@ -26330,8 +25937,7 @@ "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "tty-browserify": { "version": "0.0.0", @@ -26372,8 +25978,7 @@ "type-fest": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==" }, "type-is": { "version": "1.6.18", @@ -26436,14 +26041,12 @@ "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", - "dev": true + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==" }, "unicode-match-property-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "dev": true, "requires": { "unicode-canonical-property-names-ecmascript": "^1.0.4", "unicode-property-aliases-ecmascript": "^1.0.4" @@ -26452,14 +26055,12 @@ "unicode-match-property-value-ecmascript": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", - "dev": true + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==" }, "unicode-property-aliases-ecmascript": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", - "dev": true + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==" }, "unified": { "version": "9.2.0", @@ -26493,7 +26094,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", @@ -26631,7 +26231,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, "requires": { "has-value": "^0.3.1", "isobject": "^3.0.0" @@ -26641,7 +26240,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, "requires": { "get-value": "^2.0.3", "has-values": "^0.1.4", @@ -26652,7 +26250,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, "requires": { "isarray": "1.0.0" } @@ -26662,8 +26259,7 @@ "has-values": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" } } }, @@ -26854,8 +26450,7 @@ "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" }, "url": { "version": "0.11.0", @@ -26923,8 +26518,7 @@ "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, "use-composed-ref": { "version": "1.1.0", @@ -27073,7 +26667,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -27542,7 +27135,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -27706,8 +27298,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write-file-atomic": { "version": "3.0.3", @@ -27860,7 +27451,6 @@ "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" diff --git a/package.json b/package.json index fe6d42439..1391b06ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@helpscout/hsds-react", - "version": "3.32.0", + "version": "3.33.0", "private": false, "main": "dist/index.js", "module": "dist/index.es.js", @@ -95,15 +95,22 @@ "@helpscout/wedux": "0.0.11", "@tippyjs/react": "^4.0.2", "array-move": "2.1.0", + "chalk": "^1.1.3", "classnames": "^2.3.1", "compute-scroll-into-view": "1.0.11", "dash-get": "1.0.2", "downshift": "^6.1.1", + "execa": "^4.0.0", "fast-deep-equal": "^2.0.1", + "jscodeshift": "^0.7.0", + "globby": "^11.0.0", + "inquirer": "^7.0.4", + "is-git-clean": "^1.1.0", "invariant": "2.2.4", "lodash.debounce": "^4.0.8", "lodash.get": "^4.4.2", "lodash.throttle": "^4.1.1", + "meow": "^6.0.1", "path-to-regexp": "2.4.0", "prismjs": "^1.25.0", "react-frame-component": "4.1.1", @@ -121,7 +128,7 @@ "@babel/plugin-transform-runtime": "^7.4.3", "@babel/preset-env": "^7.0.0", "@babel/preset-react": "^7.0.0", - "@helpscout/colorway": "0.9.7", + "@helpscout/colorway": "0.10.0", "@helpscout/helix": "0.2.0", "@helpscout/prestart": "^0.0.9", "@storybook/addon-a11y": "6.3.12", @@ -139,6 +146,7 @@ "@testing-library/user-event": "^12.1.5", "@types/enzyme": "^3.1.14", "@types/jest": "^26.0.4", + "@types/jscodeshift": "^0.11.3", "@types/react": "^16.4.14", "@types/react-dom": "^16.0.8", "@types/react-transition-group": "^2.0.14", @@ -150,7 +158,6 @@ "babel-plugin-prismjs": "^2.0.1", "babel-plugin-styled-components": "^1.10.6", "babel-plugin-tester": "^9.2.0", - "chalk": "^1.1.3", "confusing-browser-globals": "^1.0.7", "coveralls": "3.0.9", "cross-env": "5.1.0", @@ -166,17 +173,11 @@ "eslint-plugin-prettier": "^3.1.4", "eslint-plugin-react": "^7.18.3", "eslint-plugin-react-hooks": "^4.1.0", - "execa": "^4.0.0", "fs-extra": "^3.0.1", "glob": "^7.1.3", - "globby": "^11.0.0", "husky": "4.2.5", - "inquirer": "^7.0.4", - "is-git-clean": "^1.1.0", "jest": "^26.1.0", "jest-watch-typeahead": "^0.3.0", - "jscodeshift": "^0.7.0", - "meow": "^6.0.1", "np": "^7.5.0", "nyc": "^15.0.0", "prettier": "2.0.5", diff --git a/scripts/codemod/cli.js b/scripts/codemod/cli.js index 555d106fa..cc48e6bb4 100644 --- a/scripts/codemod/cli.js +++ b/scripts/codemod/cli.js @@ -148,6 +148,10 @@ const TRANSFORMER_INQUIRER_CHOICES = [ '3.0 ReplaceImports: Replace all hsds-react import with the next release', value: 'ReplaceImportsTransform', }, + { + name: '3.31 Button: Button standardization', + value: 'ButtonStandardizationTransform', + }, ] function expandFilePathsIfNeeded(filesBeforeExpansion) { @@ -225,7 +229,7 @@ function run() { type: 'input', name: 'moduleName', message: 'Enter the original package name', - when: function(answers) { + when: function (answers) { return ( answers.transformer === 'all' || answers.transformer === 'ReplaceImportsTransform' @@ -237,7 +241,7 @@ function run() { type: 'input', name: 'moduleNameTarget', message: 'Enter the new package name', - when: function(answers) { + when: function (answers) { const isReplaceImports = answers.transformer === 'all' || answers.transformer === 'ReplaceImportsTransform' diff --git a/scripts/codemod/transforms/ButtonStandardizationTransform.js b/scripts/codemod/transforms/ButtonStandardizationTransform.js new file mode 100644 index 000000000..b8827ca8e --- /dev/null +++ b/scripts/codemod/transforms/ButtonStandardizationTransform.js @@ -0,0 +1,236 @@ +import { API, FileInfo } from 'jscodeshift' + +/** + * + * @param {FileInfo} fileInfo + * @param {API} api + * @returns + */ +export default function buttonStandardizationTransform(fileInfo, api) { + const j = api.jscodeshift + const root = j(fileInfo.source) + const isCss = fileInfo.path.endsWith('.css.js') + + if ( + fileInfo.path.endsWith('.test.js') || + fileInfo.path.includes('.stories') || + fileInfo.path.includes('components/Button/Button.jsx') || + fileInfo.path.includes('components/Button/Button.css.js') + ) + return root.toSource() + + const processClassnames = p => { + const { node } = p + + ;[ + { before: 'is-default', after: 'is-default-to-refactor' }, + { before: 'is-xxl', after: 'is-size-xxl' }, + { before: 'is-xl', after: 'is-size-xl' }, + { before: 'is-lg', after: 'is-size-lg' }, + { before: 'is-md', after: 'is-size-md' }, + { before: 'is-sm', after: 'is-size-sm' }, + { before: 'is-xs', after: 'is-size-xs' }, + { before: 'is-xxs', after: 'is-size-xxs' }, + { before: 'is-primary', after: 'is-theme-blue' }, + { before: 'is-secondary', after: 'is-theme-grey' }, + { before: 'is-success', after: 'is-theme-green' }, + { before: 'is-danger', after: 'is-theme-red' }, + ].forEach(({ before, after }) => { + const quasis = node.quasis.map(q => + j.templateElement( + { + cooked: q.value.cooked.replace(before, after), + raw: q.value.raw.replace(before, after), + }, + false + ) + ) + node.quasis = quasis + }) + + return node + } + + const getAttribute = (attributes, name) => { + const attr = attributes.find(a => { + if (!a || !a.name || !a.name.name) return null + return a.name.name === name + }) + if (!attr) return { value: { value: null, expression: null } } + + return { name: attr.name.name, value: attr.value } + } + + const filterAttributes = (attributes, extraAttrsToRemoves = []) => { + const toRemove = [ + 'allowContentEventPropagation', + 'disableOnLoading', + 'fetch', + 'spinButtonOnLoading', + 'canRenderFocus', + 'isBlock', + ...extraAttrsToRemoves, + ] + + return attributes.filter(a => { + if (!a || !a.name || !a.name.name) return true + return !toRemove.includes(a.name.name) + }) + } + + const createAttribute = (name, value) => { + if (value === true) { + return j.jsxAttribute(j.jsxIdentifier(name), null) + } else if (value === false) { + j.jsxAttribute( + j.jsxIdentifier(name), + j.jsxExpressionContainer({ + type: 'Literal', + value: false, + }) + ) + } + + if (typeof value === 'string') { + return j.jsxAttribute(j.jsxIdentifier(name), j.literal(value)) + } + + return j.jsxAttribute(j.jsxIdentifier(name), value) + } + + const renameAttribute = (attributes, before, after) => { + return attributes.map(a => { + if (a && a.name && a.name.name && a.name.name === before) { + a.name.name = after + } + return a + }) + } + + const processAttributes = jSXElement => { + const { attributes } = jSXElement + const { value: kind } = getAttribute(attributes, 'kind') + const { value: state } = getAttribute(attributes, 'state') + const { value: size } = getAttribute(attributes, 'size') + const { value: shape } = getAttribute(attributes, 'shape') + const { value: theme } = getAttribute(attributes, 'theme') + + const toValidateAttr = createAttribute('data-button-tovalidate', true) + const themeDefaultAttr = createAttribute('theme', 'blue') + const sizeDefaultAttr = createAttribute('size', 'lg') + + const outlinedAttr = createAttribute('outlined', true) + const roundedAttr = createAttribute('rounded', true) + const linkedAttr = createAttribute('linked', true) + + const extraAttrsToRemoves = [ + 'kind', + 'size', + 'state', + 'shape', + 'withCaret', + 'isBorderless', + 'iconSize', + ] + + // if theme exists, do nothing + if (theme.value) return attributes + // if there is no attribute at all, we'll add a data-tovalidate to list the button as a change and let developer decided what they need to do + if (!kind.value && !state.value && !size.value && !size.expression) { + return [ + toValidateAttr, + themeDefaultAttr, + sizeDefaultAttr, + linkedAttr, + ...filterAttributes(attributes, extraAttrsToRemoves), + ] + } + + const nextAttributes = [] + if (size.value === 'xl' || size.value === 'lgxl') { + nextAttributes.push(createAttribute('size', 'xxl')) + } else if (size.value || size.expression) { + nextAttributes.push(createAttribute('size', size)) + } + + if (state.value === 'danger') { + nextAttributes.push(createAttribute('theme', 'red')) + } + if (state.value === 'success' || kind.value === 'tertiary') { + nextAttributes.push(createAttribute('theme', 'green')) + } + if (state.value === 'grey') { + nextAttributes.push(createAttribute('theme', 'grey')) + } + + if (kind.value === 'primary' && !state.value) { + nextAttributes.push(createAttribute('theme', 'blue')) + } + if (kind.value === 'secondary' && !state.value) { + nextAttributes.push(createAttribute('theme', 'grey')) + } + + if (kind.value === 'link') { + nextAttributes.push(createAttribute('theme', 'grey')) + nextAttributes.push(linkedAttr) + } + + if (kind.value === 'secondary' || kind.value === 'tertiary') { + nextAttributes.push(outlinedAttr) + } + if (shape.value === 'rounded') { + nextAttributes.push(roundedAttr) + } + + if (!kind.value || kind.value === 'default') { + nextAttributes.push(linkedAttr) + } + + let updatedAttributes = renameAttribute(attributes, 'innerRef', 'ref') + updatedAttributes = renameAttribute(attributes, 'buttonRef', 'ref') + updatedAttributes = renameAttribute(attributes, 'isLoading', 'loading') + + return [ + ...nextAttributes, + ...filterAttributes(updatedAttributes, extraAttrsToRemoves), + ] + } + + const processButtonComponent = p => { + const jSXElement = p.value + + const isIcon = jSXElement.name.name.includes('Icon') + + // jSXElement.attributes = jSXElement.attributes.filter(a => { + // if (a.type === 'JSXSpreadAttribute') return true + // return a && a.name && a.name.name !== 'version' + // }) + + jSXElement.attributes = processAttributes(jSXElement) + } + + //replace classNames + const cssElements = root.find(j.TemplateLiteral).filter(p => { + const hasVariable = + p.value.quasis.length > 0 && + p.value.quasis.some(n => { + const rawString = n.value.raw.toString() + return rawString.includes('Button') + }) + return hasVariable + }) + + cssElements.replaceWith(processClassnames) + + // replace jsx component + const elements = root.find(j.JSXOpeningElement).filter(el => { + if (el.node.name.name) { + return el.node.name.name.includes('Button') + } + return false + }) + + elements.forEach(processButtonComponent) + + return root.toSource() +} diff --git a/src/components/Accordion/__tests__/Accordion.Link.test.js b/src/components/Accordion/__tests__/Accordion.Link.test.js index dbef4b959..ccec26ed4 100644 --- a/src/components/Accordion/__tests__/Accordion.Link.test.js +++ b/src/components/Accordion/__tests__/Accordion.Link.test.js @@ -3,10 +3,14 @@ import { mount } from 'enzyme' import Link from '../Accordion.Link' import Title from '../Accordion.Title' import Section from '../Accordion.Section' +import { MemoryRouter as Router } from 'react-router-dom' + +const wrap = fn => Component => fn({Component}) +const mountWithRouter = wrap(mount) describe('Section', () => { test('Renders the link variant of Section if to/href are defined', () => { - const wrapper = mount() + const wrapper = mountWithRouter() const el = wrapper.find(Section) expect(el.prop('isLink')).toBe(true) @@ -22,14 +26,14 @@ describe('Section', () => { describe('Title', () => { test('Passes to prop to Title', () => { - const wrapper = mount() + const wrapper = mountWithRouter() const el = wrapper.find(Title) expect(el.prop('to')).toBe('/page') }) test('Passes href prop to Title', () => { - const wrapper = mount() + const wrapper = mountWithRouter() const el = wrapper.find(Title) expect(el.prop('href')).toBe('/page') diff --git a/src/components/Accordion/__tests__/Accordion.Title.test.js b/src/components/Accordion/__tests__/Accordion.Title.test.js index dc4d7d1f3..73595d075 100644 --- a/src/components/Accordion/__tests__/Accordion.Title.test.js +++ b/src/components/Accordion/__tests__/Accordion.Title.test.js @@ -4,6 +4,10 @@ import Accordion, { AccordionContext } from '../Accordion' import Section, { SectionContext } from '../Accordion.Section' import Title, { classNameStrings as classNames } from '../Accordion.Title' import Keys from '../../../constants/Keys' +import { MemoryRouter as Router } from 'react-router-dom' + +const wrap = fn => Component => fn({Component}) +const mountWithRouter = wrap(mount) describe('ClassNames', () => { test('Has default className', () => { @@ -180,7 +184,7 @@ describe('setSectionState', () => { describe('Link', () => { test('Renders a link, if to is defined', () => { - const wrapper = mount( + const wrapper = mountWithRouter( </SectionContext.Provider> @@ -191,7 +195,7 @@ describe('Link', () => { }) test('Renders a link, if href is defined', () => { - const wrapper = mount( + const wrapper = mountWithRouter( <SectionContext.Provider value={{ isOpen: true }}> <Title href="/" /> </SectionContext.Provider> @@ -202,7 +206,7 @@ describe('Link', () => { }) test('Adjusts caret size, if link', () => { - const wrapper = mount(<Title />) + const wrapper = mountWithRouter(<Title />) let icon = wrapper.find('Icon').first() expect(icon.prop('size')).toBe(14) @@ -216,7 +220,7 @@ describe('Link', () => { describe('isOpen', () => { test('Renders open styles, if defined', () => { - const wrapper = mount( + const wrapper = mountWithRouter( <SectionContext.Provider value={{ isOpen: true }}> <Title /> </SectionContext.Provider> @@ -227,7 +231,7 @@ describe('isOpen', () => { }) test('Always render non-open styles, if isLink', () => { - const wrapper = mount( + const wrapper = mountWithRouter( <SectionContext.Provider value={{ isOpen: true }}> <Title to="/" /> </SectionContext.Provider> @@ -241,7 +245,7 @@ describe('isOpen', () => { describe('Events', () => { test('onClick callback works', () => { const spy = jest.fn() - const wrapper = mount(<Title onClick={spy} />) + const wrapper = mountWithRouter(<Title onClick={spy} />) wrapper.simulate('click') @@ -250,7 +254,7 @@ describe('Events', () => { test('onClick callback works for links', () => { const spy = jest.fn() - const wrapper = mount(<Title onClick={spy} to="/" />) + const wrapper = mountWithRouter(<Title onClick={spy} to="/" />) wrapper.simulate('click') diff --git a/src/components/ActionSelect/ActionSelect.stories.mdx b/src/components/ActionSelect/ActionSelect.stories.mdx index b96488358..f2ef83c8d 100644 --- a/src/components/ActionSelect/ActionSelect.stories.mdx +++ b/src/components/ActionSelect/ActionSelect.stories.mdx @@ -21,7 +21,7 @@ import Button from '../Button' </div> <Example /> <div style={{ height: 40 }} /> - <Page.Actions primary={<Button kind="primary">Save Changes</Button>} /> + <Page.Actions primary={<Button theme="blue">Save Changes</Button>} /> </Page> </div> </Story> diff --git a/src/components/Alert/Alert.stories.mdx b/src/components/Alert/Alert.stories.mdx index 8765884ef..e38eefc69 100644 --- a/src/components/Alert/Alert.stories.mdx +++ b/src/components/Alert/Alert.stories.mdx @@ -108,7 +108,7 @@ An Alert component provides contextual feedback messages for user actions with t <div> <Alert actionRight={ - <Button size="sm" kind="secondary"> + <Button size="sm" theme="grey" outlined> Action! </Button> } diff --git a/src/components/Button/Button.config.js b/src/components/Button/Button.config.js index 55798a1a3..e74f09823 100644 --- a/src/components/Button/Button.config.js +++ b/src/components/Button/Button.config.js @@ -2,206 +2,108 @@ import { getColor } from '../../styles/utilities/color' const config = { borderRadius: 3, - fontWeight: 'normal', + fontWeight: 500, iconOffset: 4, - primary: { - backgroundColor: getColor('blue.500'), - backgroundColorHover: getColor('blue.600'), - backgroundColorActive: getColor('blue.700'), - borderColor: getColor('blue.500'), - borderColorHover: getColor('blue.600'), - borderColorActive: getColor('blue.700'), - color: 'white', - disabledBackgroundColor: getColor('grey.500'), - disabledBorderColor: getColor('grey.500'), - disabledColor: 'white', - fontWeight: 500, - danger: { - backgroundColor: getColor('red.500'), - backgroundColorHover: getColor('red.600'), - backgroundColorActive: getColor('red.700'), - borderColor: getColor('red.500'), - borderColorHover: getColor('red.600'), - borderColorActive: getColor('red.700'), - color: 'white', - colorHover: 'white', - colorActive: 'white', - }, - gray: { - backgroundColor: getColor('charcoal.200'), - backgroundColorHover: getColor('charcoal.300'), - backgroundColorActive: getColor('charcoal.400'), - borderColor: getColor('charcoal.200'), - borderColorHover: getColor('charcoal.300'), - borderColorActive: getColor('charcoal.400'), - color: 'white', - colorHover: 'white', - colorActive: 'white', + focusOutlineWidth: 2, + focusOutlineOffset: 3, + focusOutlineColor: getColor('blue.500'), + textColor: 'white', + theme: { + blue: { + mainColor: getColor('blue.500'), + hoverColor: getColor('blue.600'), + outline: { + borderColor: getColor('blue.500'), + textColor: getColor('blue.600'), + textHoverColor: getColor('blue.700'), + hoverColor: getColor('blue.100'), + hoverColorIconOnly: getColor('blue.200'), + textColorSeamlessHover: getColor('blue.800'), + }, }, - success: { - backgroundColor: getColor('green.500'), - backgroundColorHover: getColor('green.600'), - backgroundColorActive: getColor('green.700'), - borderColor: getColor('green.500'), - borderColorHover: getColor('green.600'), - borderColorActive: getColor('green.700'), - color: 'white', - colorHover: 'white', - colorActive: 'white', + grey: { + mainColor: getColor('charcoal.400'), + hoverColor: getColor('charcoal.500'), + outline: { + borderColor: getColor('grey.700'), + borderHoverColor: getColor('grey.800'), + textColor: getColor('charcoal.400'), + textHoverColor: getColor('charcoal.500'), + hoverColor: getColor('grey.200'), + hoverColorIconOnly: getColor('grey.300'), + textColorSeamlessHover: getColor('charcoal.800'), + }, }, - warning: { - backgroundColor: getColor('yellow.600'), - backgroundColorHover: getColor('yellow.700'), - backgroundColorActive: getColor('yellow.800'), - borderColor: getColor('yellow.600'), - borderColorHover: getColor('yellow.700'), - borderColorActive: getColor('yellow.800'), - color: 'white', - colorHover: 'white', - colorActive: 'white', + red: { + mainColor: getColor('pink.950'), + hoverColor: getColor('pink.1000'), + outline: { + borderColor: getColor('pink.900'), + textColor: getColor('pink.950'), + textHoverColor: getColor('pink.1000'), + hoverColorIconOnly: getColor('pink.200'), + hoverColor: getColor('pink.100'), + }, }, - }, - secondary: { - backgroundColor: 'white', - backgroundColorHover: getColor('grey.200'), - backgroundColorActive: getColor('grey.300'), - borderColor: getColor('grey.700'), - borderColorHover: getColor('charcoal.200'), - borderColorActive: getColor('charcoal.200'), - color: getColor('charcoal.400'), - disabledBackgroundColor: 'white', - disabledBorderColor: getColor('grey.500'), - disabledColor: getColor('grey.600'), - }, - tertiary: { - backgroundColor: 'white', - backgroundColorHover: getColor('green.100'), - backgroundColorActive: getColor('green.200'), - borderColor: getColor('green.500'), - borderColorHover: getColor('green.600'), - borderColorActive: getColor('green.700'), - color: getColor('green.500'), - disabledBackgroundColor: 'white', - disabledBorderColor: getColor('grey.500'), - disabledColor: getColor('grey.600'), - }, - default: { - backgroundColor: 'transparent', - backgroundColorHover: 'transparent', - backgroundColorActive: 'transparent', - borderColor: 'transparent', - borderColorHover: 'transparent', - borderColorActive: 'transparent', - color: getColor('charcoal.200'), - colorHover: getColor('charcoal.300'), - colorActive: getColor('charcoal.400'), - disabledBackgroundColor: 'transparent', - disabledBorderColor: 'transparent', - disabledColor: getColor('grey.700'), - minWidth: 'initial', - padding: 0, - textDecoration: 'none', - textDecorationHover: 'underline', - textDecorationActive: 'underline', - textDecorationFocus: 'underline', - danger: { - backgroundColor: 'transparent', - backgroundColorHover: 'transparent', - backgroundColorActive: 'transparent', - borderColor: 'transparent', - borderColorHover: 'transparent', - borderColorActive: 'transparent', - color: getColor('red.500'), - colorHover: getColor('red.600'), - colorActive: getColor('red.700'), + green: { + mainColor: getColor('green.750'), + hoverColor: getColor('green.800'), + outline: { + borderColor: getColor('green.500'), + textColor: getColor('green.750'), + textHoverColor: getColor('green.800'), + hoverColor: getColor('green.100'), + hoverColorIconOnly: getColor('green.200'), + textColorSeamlessHover: getColor('green.900'), + }, }, }, - link: { - backgroundColor: 'transparent', - backgroundColorHover: 'transparent', - backgroundColorActive: 'transparent', - borderColor: 'transparent', - borderColorHover: 'transparent', - borderColorActive: 'transparent', - color: getColor('link.base'), - colorHover: getColor('link.hover'), - colorActive: getColor('link.active'), - disabledBackgroundColor: 'transparent', - disabledBorderColor: 'transparent', - disabledColor: getColor('grey.700'), - minWidth: 'initial', - padding: 0, - textDecoration: 'none', - textDecorationHover: 'underline', - textDecorationActive: 'underline', - textDecorationFocus: 'underline', - }, - disabled: { - backgroundColor: getColor('grey.500'), - }, - focusOutlineWidth: 2, - focusOutlineOffset: 3, - focusOutlineColor: getColor('blue.500'), - shape: { - default: '3px', - circle: '9999px', - rounded: '16px', - }, size: { + xxl: { + fontSize: 14, + height: '50px', + padding: '30px', + roundedPadding: '20px', + minWidth: '200px', + }, xl: { fontSize: 14, - height: 50, - minWidth: 'initial', - padding: 30, + height: '44px', + padding: '30px', + roundedPadding: '20px', + minWidth: '120px', }, lg: { fontSize: 14, - height: 40, - minWidth: 'initial', - padding: 30, + height: '40px', + padding: '30px', + roundedPadding: '20px', + minWidth: '120px', }, md: { fontSize: 14, - height: 35, - minWidth: 'initial', - padding: 30, + height: '35px', + padding: '30px', + roundedPadding: '20px', + minWidth: '90px', }, sm: { fontSize: 13, - fontWeight: 500, - height: 30, - minWidth: 'initial', - padding: 15, + height: '30px', + padding: '15px', + minWidth: '90px', }, xs: { fontSize: 13, - fontWeight: 500, - height: 24, - minWidth: 'initial', - padding: 15, + height: '24px', + padding: '15px', }, xxs: { fontSize: 11, - fontWeight: 500, - height: 20, - minWidth: 'initial', - padding: 6, + height: '20px', + padding: '6px', }, }, - suffix: { - backgroundColor: getColor('grey.200'), - backgroundColorHover: getColor('grey.300'), - backgroundColorActive: getColor('grey.400'), - borderColor: getColor('grey.700'), - borderColorHover: getColor('charcoal.200'), - borderColorActive: getColor('charcoal.200'), - color: getColor('charcoal.400'), - disabledBackgroundColor: getColor('grey.300'), - disabledBorderColor: getColor('grey.500'), - disabledColor: getColor('grey.600'), - minWidth: 'initial', - padding: '8px', - }, } export default config diff --git a/src/components/Button/Button.css.js b/src/components/Button/Button.css.js index b054cfc42..86f95c8a7 100644 --- a/src/components/Button/Button.css.js +++ b/src/components/Button/Button.css.js @@ -1,35 +1,85 @@ -import styled from 'styled-components' +import styled, { css } from 'styled-components' import get from 'dash-get' + import Spinner from '../Spinner' +import Icon from '../Icon' + import { getColor } from '../../styles/utilities/color' -import forEach from '../../styles/utilities/forEach' import variableFontSize from '../../styles/utilities/variableFontSize' +import { focusRing } from '../../styles/mixins/focusRing.css' + import config from './Button.config' +function mapBrandColorToConfig(theme) { + if (!theme) return {} + + const mainColor = theme.backgroundColorUI || theme.brandColor + const textColor = theme.textColorInteractive || theme.brandTextColor + + const hoverColor = theme.backgroundColorUIHover + + return { + mainColor, + hoverColor, + textColor, + } +} + export const ButtonUI = styled.button` - appearance: none; + --buttonMainColor: ${config.theme.blue.mainColor}; + --buttonBackgroundColor: ${config.theme.blue.mainColor}; + --buttonBackgroundColorHover: ${config.theme.blue.hoverColor}; + --buttonBorderColor: ${config.theme.blue.mainColor}; + --buttonBorderColorHover: ${config.theme.blue.mainColor}; + + --buttonColor: white; + --buttonColorHover: white; + + --buttonTextDecoration: none; + --buttonTextDecorationHover: none; + + --buttonPadding: 0px; + --buttonMinWidth: initial; + --buttonHeight: ${config.size.lg.height}px; + --buttonFontWeight: ${config.fontWeight}; + --buttonRadius: ${config.borderRadius}px; + + ${focusRing}; + --focusRingOffset: -${config.focusOutlineOffset}px; + --focusRingRadius: var(--buttonRadius); + align-items: center; - border: 1px solid transparent; + appearance: none; + background: ${config.theme.blue.mainColor}; + background: var(--buttonBackgroundColor); + border-radius: 3px; + border-radius: var(--buttonRadius); + border: 1px solid ${config.theme.blue.mainColor}; + border-color: var(--buttonBorderColor); + color: white; + color: var(--buttonColor); cursor: pointer; display: inline-flex; - font-weight: normal; - height: ${config.size.md.height}px; + font-family: var(--HSDSGlobalFontFamily); + font-weight: 500; + font-weight: var(--buttonFontWeight); + height: 44px; + height: var(--buttonHeight); justify-content: center; line-height: 1; - min-width: ${config.size.md.minWidth}; + min-width: var(--buttonMinWidth); outline: none; - padding: 0 ${config.size.md.padding}px; + padding: 0 30px; + padding: 0 var(--buttonPadding); position: relative; text-align: center; text-decoration: none; + text-decoration: var(--buttonTextDecoration); - ${({ allowContentEventPropogation }) => - allowContentEventPropogation && - ` - * { - pointer-events: none; - } - `}; + /* Allow content events to pass through, https://github.com/helpscout/hsds-react/pull/394 */ + * { + pointer-events: none; + } &:hover, &:active, @@ -38,19 +88,53 @@ export const ButtonUI = styled.button` text-decoration: none; } - &:active, - &:active:focus { - .c-ButtonFocus { + &:focus { + color: white; + color: var(--buttonColor); + text-decoration: none; + text-decoration: var(--buttonTextDecoration); + } + + &:hover, + &.is-active, + &:active { + background: ${config.theme.blue.hoverColor}; + background: var(--buttonBackgroundColorHover); + border-color: ${config.theme.blue.hoverColor}; + border-color: var(--buttonBorderColorHover); + color: white; + color: var(--buttonColorHover); + text-decoration: none; + text-decoration: var(--buttonTextDecorationHover); + } + + &.is-disabled, + &[disabled] { + pointer-events: none; + user-select: none; + background: ${getColor('grey.500')} !important; + border-color: ${getColor('grey.500')} !important; + color: white !important; + + &.is-style-outlined { + background: white !important; + border-color: ${getColor('grey.600')} !important; + color: ${getColor('grey.800')} !important; + } + &.is-style-link { + background: transparent !important; + border-color: transparent !important; + color: ${getColor('grey.800')} !important; + } + + &:before { display: none; } } - &.is-focused, - &:focus { - z-index: 2; - .c-ButtonFocus { - display: block; - } + &.is-rounded { + border-radius: 100px; + --focusRingRadius: 100px; } &.is-first { @@ -65,187 +149,157 @@ export const ButtonUI = styled.button` border-bottom-left-radius: 0; } - &.is-block { - display: flex; - width: 100%; - } + /* sizes */ + ${makeButtonSizeStyles('xxl', config.size.xxl)}; + ${makeButtonSizeStyles('xl', config.size.xl)}; + ${makeButtonSizeStyles('lg', config.size.lg)}; + ${makeButtonSizeStyles('md', config.size.md)}; + ${makeButtonSizeStyles('sm', config.size.sm)}; + ${makeButtonSizeStyles('xs', config.size.xs)}; + ${makeButtonSizeStyles('xxs', config.size.xxs)}; + + /* theme */ + ${makeButtonThemeStyles('blue', config.theme.blue)}; + ${makeButtonThemeStyles('green', config.theme.green)}; + ${makeButtonThemeStyles('red', config.theme.red)}; + ${makeButtonThemeStyles('grey', config.theme.grey)}; + ${({ theme }) => + theme && theme.brandColor + ? makeButtonThemeStyles('brand', mapBrandColorToConfig(theme.brandColor)) + : ''}; +` - &.is-loading { - &.is-spinButtonOnLoading { - animation: SpinButtonOnLoadAnimation 700ms linear infinite; - will-change: transform; - @keyframes SpinButtonOnLoadAnimation { - 100% { - transform: rotate(360deg); +function makeButtonThemeStyles(theme, config) { + return css` + &.is-theme-${theme} { + --buttonMainColor: ${config.mainColor}; + --buttonBackgroundColor: ${config.mainColor}; + --buttonBackgroundColorHover: ${config.hoverColor}; + --buttonBorderColor: ${config.mainColor}; + --buttonBorderColorHover: ${config.hoverColor}; + ${renderPropStyle(config, 'textColor', '--buttonColor')}; + ${renderPropStyle(config, 'textColor', '--buttonColorHover')}; + + ${() => { + if (config.outline) { + return css` + &.is-style-outlined { + --buttonBackgroundColor: white; + + ${renderPropStyle( + config, + 'outline.hoverColor', + '--buttonBackgroundColorHover' + )}; + + ${renderPropStyle(config, 'outline.textColor', '--buttonColor')}; + ${renderPropStyle( + config, + 'outline.textHoverColor', + '--buttonColorHover', + 'textColor' + )}; + + ${renderPropStyle( + config, + 'outline.borderColor', + '--buttonBorderColor', + 'mainColor' + )}; + ${renderPropStyle( + config, + 'outline.borderHoverColor', + '--buttonBorderColorHover', + 'outline.borderColor' + )}; + + &.has-icon-only { + ${renderPropStyle( + config, + 'outline.hoverColorIconOnly', + '--buttonBackgroundColorHover' + )}; + } + + &.is-seamless { + ${renderPropStyle( + config, + 'outline.textColorSeamlessHover', + '--buttonColorHover' + )}; + + background: transparent !important; + border-color: transparent !important; + } + } + ` } + }} + + &.is-style-link { + --buttonMinWidth: 0; + --buttonPadding: 0; + --buttonBackgroundColor: transparent; + --buttonBackgroundColorHover: transparent; + --buttonBorderColor: transparent; + --buttonBorderColorHover: transparent; + --buttonTextDecorationHover: underline; + --buttonFontWeight: 400; + + ${renderPropStyle(config, 'outline.textColor', '--buttonColor')}; + ${renderPropStyle( + config, + 'outline.textHoverColor', + '--buttonColorHover', + 'textColor' + )}; } } - } - - ${makeButtonShapeStyles()}; - ${makeButtonSizeStyles()}; - - ${props => makePrimaryStyles('primary', props)}; - ${makeButtonKindStyles('secondary', config.secondary)}; - ${makeButtonKindStyles('tertiary', config.tertiary)}; - ${makeButtonKindStyles('default', config.default)}; - ${makeButtonKindStyles('link', config.link)}; - ${makeButtonKindStyles('suffix', config.suffix)}; - - /* some overwrite */ - &.is-primary { - &.is-lg, - &.is-xl { - min-width: 120px; - } - } -` - -function makePrimaryStyles(name = 'primary', props = {}) { - const theme = get(props, 'theme.brandColor', {}) - const backgroundColor = - theme.backgroundColorUI || theme.brandColor || config[name].backgroundColor - const color = - theme.textColorInteractive || theme.brandTextColor || config[name].color - - const backgroundColorHover = - theme.backgroundColorUIHover || config[name].backgroundColorHover - const borderColorHover = backgroundColorHover - const backgroundColorActive = - theme.backgroundColorUIActive || config[name].backgroundColorActive - const borderColorActive = backgroundColorActive - - return makeButtonKindStyles(name, { - ...config[name], - backgroundColor, - borderColor: backgroundColor, - color, - backgroundColorHover, - borderColorHover, - backgroundColorActive, - borderColorActive, - }) + ` } - -function makeButtonKindStyles(kind, config) { - return ` - &.is-${kind} { - background: ${config.backgroundColor}; - border-color: ${config.borderColor}; - color: ${config.color}; - ${renderStyleForProp(config, 'fontWeight', 'font-weight')} - ${renderStyleForProp(config, 'minWidth', 'min-width')} - ${renderStyleForProp(config, 'padding', 'padding-left')} - ${renderStyleForProp(config, 'padding', 'padding-right')} - ${renderStyleForProp(config, 'textDecoration', 'text-decoration')} - - &:hover, - &.is-hovered { - background: ${config.backgroundColorHover}; - border-color: ${config.borderColorHover}; - ${renderStyleForProp(config, 'colorHover', 'color')} - ${renderStyleForProp(config, 'textDecorationHover', 'text-decoration')} +function makeButtonSizeStyles(size, config) { + return css` + &.is-size-${size} { + ${variableFontSize({ fontSize: config.fontSize })}; + ${renderPropStyle(config, 'height', '--buttonHeight')}; + ${renderPropStyle(config, 'padding', '--buttonPadding')}; + ${renderPropStyle(config, 'fontWeight', '--buttonFontWeight')}; + ${renderPropStyle(config, 'minWidth', '--buttonMinWidth')}; + + &.is-rounded { + ${renderPropStyle(config, 'roundedPadding', '--buttonPadding')}; + --buttonMinWidth: 0px; } - &:active, - &.is-active { - background: ${config.backgroundColorActive}; - border-color: ${config.borderColorActive}; - ${renderStyleForProp(config, 'colorActive', 'color')} - ${renderStyleForProp(config, 'textDecorationActive', 'text-decoration')} + &.has-prefix-icon { + padding-left: calc(var(--buttonPadding) - 8px); + --buttonMinWidth: 0px; } - - &:focus, - &.is-focused { - ${renderStyleForProp(config, 'colorFocus', 'color')} - ${renderStyleForProp(config, 'textDecorationFocus', 'text-decoration')} + &.has-suffix-icon { + padding-right: calc(var(--buttonPadding) - 8px); + --buttonMinWidth: 0px; } - ${makeButtonStateStyles(config, 'danger')} - ${makeButtonStateStyles(config, 'gray')} - ${makeButtonStateStyles(config, 'success')} - ${makeButtonStateStyles(config, 'warning')} - - ${makeDisabledStyles(` - background: ${config.disabledBackgroundColor} !important; - border-color: ${config.disabledBorderColor} !important; - color: ${config.disabledColor} !important; - `)} - } - ` -} - -function makeButtonStateStyles(config, state) { - if (!config.hasOwnProperty(state)) return '' - - return ` - &.is-${state} { - background-color: ${config[state].backgroundColor}; - border-color: ${config[state].borderColor}; - color: ${config[state].color}; - - &:hover, - &.is-hovered { - background-color: ${config[state].backgroundColorHover}; - border-color: ${config[state].borderColorHover}; - color: ${config[state].colorHover}; - } - &:active, - &.is-active { - background-color: ${config[state].backgroundColorActive}; - border-color: ${config[state].borderColorActive}; - color: ${config[state].colorActive}; + &.is-style-link.is-inlined { + --buttonPadding: 0; + --buttonMinWidth: 0; + --buttonHeight: auto; } } ` } -function makeDisabledStyles(content) { - return ` - &.is-disabled, - &[disabled] { - pointer-events: none; - user-select: none; - ${content} - } - ` -} - -function makeButtonShapeStyles() { - return forEach( - config.shape, - (shape, value) => ` - &.is-shape-${shape} { - border-radius: ${value}; - } - ` - ) -} +function renderPropStyle(config, prop, attribute, fallback = null) { + const attr = attribute || prop -function makeButtonSizeStyles() { - return forEach( - config.size, - (size, props) => ` - &.is-${size} { - ${variableFontSize({ fontSize: props.fontSize })}; - ${renderStyleForProp(props, 'fontWeight', 'font-weight')}; - height: ${props.height}px; - min-width: ${props.minWidth}; - padding-left: ${props.padding}px; - padding-right: ${props.padding}px; - } - ` - ) -} + let value = get(config, prop) + if (!value && fallback) value = get(config, fallback) -function renderStyleForProp(config, prop, attribute) { - const attr = attribute || prop + if (!value) return '' - return config.hasOwnProperty(prop) - ? ` - ${attr}: ${config[prop]}; + return css` + ${attr}: ${value}; ` - : '' } export const LoadingWrapperUI = styled.span` @@ -257,43 +311,8 @@ export const LoadingWrapperUI = styled.span` opacity: 0; ` -export const FocusUI = styled('span')` - animation: ButtonFocusFadeIn 200ms; - bottom: -${config.focusOutlineOffset}px; - box-shadow: 0 0 0 ${config.focusOutlineWidth}px ${config.focusOutlineColor}; - display: none; - left: -${config.focusOutlineOffset}px; - pointer-events: none; - position: absolute; - right: -${config.focusOutlineOffset}px; - top: -${config.focusOutlineOffset}px; - - &.is-first { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } - &.is-notOnly { - border-radius: 0; - } - &.is-last { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } - - @keyframes ButtonFocusFadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } - } - - ${makeButtonShapeStyles()}; -` - export const SpinnerUI = styled(Spinner)` - color: ${getColor('charcoal.500')}; + color: inherit; margin: -6px 0 0 -6px; position: absolute; z-index: 1; @@ -301,6 +320,13 @@ export const SpinnerUI = styled(Spinner)` left: 50%; ` +export const PrefixIconUI = styled(Icon)` + color: inherit; +` +export const SuffixIconUI = styled(Icon)` + color: inherit; +` + SpinnerUI.defaultProps = { size: 12, } diff --git a/src/components/Button/Button.jsx b/src/components/Button/Button.jsx index ac2459a73..550a69a49 100644 --- a/src/components/Button/Button.jsx +++ b/src/components/Button/Button.jsx @@ -1,237 +1,99 @@ -import React from 'react' +import React, { forwardRef } from 'react' import PropTypes from 'prop-types' -import getValidProps from '@helpscout/react-utils/dist/getValidProps' -import classNames from 'classnames' -import { includes } from '../../utilities/arrays' -import { noop } from '../../utilities/other' -import RouteWrapper from '../RouteWrapper' -import { ButtonUI, LoadingWrapperUI, FocusUI, SpinnerUI } from './Button.css' -import Icon from '../Icon' -export class Button extends React.PureComponent { - isLink() { - // TODO: Resolve data-bypass - // const { href, 'data-bypass': dataBypass } = this.props - // return href || dataBypass - - return this.props.href - } - - shouldShowFocus = () => { - const paddedButtonKinds = ['primary', 'secondary', 'tertiary'] - - return ( - !this.props.disabled && - this.props.canRenderFocus && - includes(paddedButtonKinds, this.props.kind) - ) - } - - getFocusMarkup = () => { - const { isFirst, isNotOnly, isLast, shape } = this.props - - const focusClassName = classNames( - 'c-ButtonFocus', - isFirst && 'is-first', - isNotOnly && 'is-notOnly', - isLast && 'is-last', - shape && `is-shape-${shape}` - ) - - if (!this.shouldShowFocus()) return null - - return <FocusUI className={focusClassName} role="presentation" /> - } - - setRef = ref => { - this.props.innerRef(ref) - this.props.buttonRef(ref) - } - - getChildrenMarkup = () => { - const { children } = this.props - - return React.Children.map(children, (child, index) => { - if (!child) return - if (!child.hasOwnProperty('type')) return child - if (child.type !== Icon) return child - - const len = React.Children.count(children) - const isFirst = index === 0 - const isLast = index === len - 1 - const isOnly = isFirst && isLast - - return React.cloneElement(child, { - offsetLeft: isFirst && !isOnly, - offsetRight: isLast && !isOnly, - }) - }) - } - - render() { - const { - allowContentEventPropogation, - children, - className, - disabled, - disableOnLoading, - kind, - innerRef, - isActive, - isBlock, - isFirst, - isFocused, - isHovered, - isNotOnly, - isLast, - isLoading, - isSuffix, - shape, - size, - spinButtonOnLoading, - state, - submit, - theme, - // Deprecating - buttonRef, - ...rest - } = this.props - - const isDisabled = disabled || (isLoading && disableOnLoading) - - const componentClassName = classNames( - 'c-Button', - isActive && 'is-active', - isBlock && 'is-block', - isDisabled && 'is-disabled', - isFirst && 'is-first', - isFocused && 'is-focused', - isHovered && 'is-hovered', - isNotOnly && 'is-notOnly', - isLast && 'is-last', - isLoading && 'is-loading', - isSuffix && 'is-suffix', - kind && `is-${kind}`, - shape && `is-shape-${shape}`, - size && `is-${size === 'lgxl' ? 'xl' : size}`, // remapping lgxl to xl - spinButtonOnLoading && 'is-spinButtonOnLoading', - state && `is-${state}`, - theme && `is-${theme}`, - className - ) - - const type = submit ? 'submit' : 'button' - const selector = this.isLink() ? 'a' : 'button' - const childrenMarkup = this.getChildrenMarkup() - - return ( - <ButtonUI - {...getValidProps(rest)} - className={componentClassName} - disabled={isDisabled} - ref={this.setRef} - type={type} - as={selector} - allowContentEventPropogation={allowContentEventPropogation} - > - {isLoading && <SpinnerUI className="c-Button--spinner" />} - {isLoading ? ( - <LoadingWrapperUI>{childrenMarkup}</LoadingWrapperUI> - ) : ( - childrenMarkup - )} - {this.getFocusMarkup()} - </ButtonUI> - ) - } -} - -Button.defaultProps = { - allowContentEventPropogation: true, - buttonRef: noop, - canRenderFocus: true, +import { + ButtonUI, + LoadingWrapperUI, + SpinnerUI, + PrefixIconUI, + SuffixIconUI, +} from './Button.css' +import { useButton } from './Button.utils' + +export const WrappedButton = forwardRef(function Button(props, ref) { + const { + loading, + children, + prefixIcon, + suffixIcon, + ...buttonProps + } = useButton(props) + + const content = ( + <> + {prefixIcon && ( + <PrefixIconUI name={prefixIcon} isWithHiddenTitle={false} size="24" /> + )} + {children} + {suffixIcon && ( + <SuffixIconUI name={suffixIcon} isWithHiddenTitle={false} size="24" /> + )} + </> + ) + + return ( + <ButtonUI {...buttonProps} ref={ref}> + {loading && ( + <> + <SpinnerUI className="c-Button--spinner" /> + <LoadingWrapperUI>{content}</LoadingWrapperUI> + </> + )} + {!loading && content} + </ButtonUI> + ) +}) + +WrappedButton.defaultProps = { 'data-cy': 'Button', - disable: false, - disableOnLoading: true, - kind: 'default', - innerRef: noop, - isActive: false, - isBlock: false, - isFocused: false, + disabled: false, isFirst: false, - isHovered: false, - isNotOnly: false, isLast: false, - isSuffix: false, - shape: 'default', - size: 'md', - spinButtonOnLoading: false, + linked: false, + isNotOnly: false, + rounded: false, + loading: false, + size: 'lg', submit: false, + theme: 'blue', } -Button.propTypes = { - /** Enables child events to pass through to Button.*/ - allowContentEventPropagation: PropTypes.bool, +WrappedButton.propTypes = { + /** Change the html element used for the button component. */ + as: PropTypes.string, /** Custom class names to be added to the component. */ className: PropTypes.string, /** Disable the button so it can't be clicked. */ disabled: PropTypes.bool, - /** Disables the button when `isLoading` is true.*/ - disableOnLoading: PropTypes.bool, - /** function which returns a promise, will be invoked before routing the `to` route */ - fetch: PropTypes.func, /** Hyperlink for the button. This transforms the button to a `<a>` selector. */ href: PropTypes.string, - /** Retrieves the `button` DOM node. */ - buttonRef: PropTypes.func, - /** Renders the focused style. */ - isFocused: PropTypes.bool, + /** rel html attribute */ + rel: PropTypes.string, + /** target html attribute */ + target: PropTypes.string, + /** Renders a button with the link styles */ + linked: PropTypes.bool, + /** Renders a link button without any size styles (height, padding, min-width) */ + inlined: PropTypes.bool, /** Renders a loading `Spinner`. */ - isLoading: PropTypes.bool, - /** Renders suffix styles. */ - isSuffix: PropTypes.bool, - /** Applies the specified style to the button. - * 'primary': Blue button. Used for primary actions. - * 'secondary': White button with a border. Used for secondary actions. - * 'tertiary': White button with a green border. Used for secondary actions. - * 'default': Borderless button. Used for subtle/tertiary actions. - * 'link': Button that looks like a `Link`. Used for subtle/tertiary actions. - */ - kind: PropTypes.oneOf([ - 'primary', - 'secondary', - 'tertiary', - 'default', - 'link', - ]), + loading: PropTypes.bool, /** Sets the size of the button. */ - size: PropTypes.oneOf(['sm', 'md', 'lg', 'xl', 'lgxl', 'xs', 'xxs']), - /** A special property that... spins the button if `isLoading`. */ - spinButtonOnLoading: PropTypes.bool, - /** Applies state styles to the button. - * 'danger': red. - * 'success': green. - * 'gray': gray. - * 'warning': orange. - */ - state: PropTypes.string, + size: PropTypes.oneOf(['xxl', 'xl', 'lg', 'md', 'sm', 'xs', 'xxs']), /** Sets the `type` of the button to `"submit"`. */ submit: PropTypes.bool, /** Applies a theme based style to the button. */ - theme: PropTypes.string, + theme: PropTypes.oneOf(['blue', 'red', 'green', 'grey']), /** React Router path to navigate on click. */ to: PropTypes.string, - canRenderFocus: PropTypes.bool, - innerRef: PropTypes.any, - isActive: PropTypes.bool, - isBlock: PropTypes.bool, + isFirst: PropTypes.bool, - isHovered: PropTypes.bool, isNotOnly: PropTypes.bool, isLast: PropTypes.bool, - shape: PropTypes.string, + + /** Sets the button radius to 100%. */ + rounded: PropTypes.bool, /** Data attr for Cypress tests. */ 'data-cy': PropTypes.string, } -export default RouteWrapper(Button) +export default WrappedButton diff --git a/src/components/Button/Button.stories.mdx b/src/components/Button/Button.stories.mdx index 95e01485a..f56334a8a 100644 --- a/src/components/Button/Button.stories.mdx +++ b/src/components/Button/Button.stories.mdx @@ -1,7 +1,10 @@ import { Meta, Story, ArgsTable, Canvas } from '@storybook/addon-docs/blocks' import { boolean, select, text } from '@storybook/addon-knobs' import Button from './Button' +import { SIZES, THEMES } from './Button.utils' +import { getColor, makeBrandColors } from '../../styles/utilities/color' import { ControlGroup } from '../index' +import { ThemeProvider } from 'styled-components' <Meta title="Components/Buttons/Button" @@ -24,54 +27,15 @@ Actionable HTML buttons with different styles and support for React Router. <Button children={text('children', 'Button')} disabled={boolean('disabled', false)} - disableOnLoading={boolean('disableOnLoading', true)} - isActive={boolean('isActive', false)} - isHovered={boolean('isHovered', false)} - isBlock={boolean('isBlock', false)} - isLoading={boolean('isLoading', false)} - kind={select( - 'kind', - { - primary: 'primary', - secondary: 'secondary', - tertiary: 'tertiary', - default: 'default', - link: 'link', - }, - 'secondary' - )} - size={select( - 'size', - { - xl: 'xl', - lgxl: 'lgxl', - lg: 'lg', - md: 'md', - sm: 'sm', - xs: 'xs', - }, - 'lg' - )} - state={select( - 'state', - { - default: null, - danger: 'danger', - success: 'success', - warning: 'warning', - }, - '' - )} - shape={select( - 'shape', - { - default: 'default', - circle: 'circle', - rounded: 'rounded', - }, - 'default' - )} - spinButtonOnLoading={boolean('spinButtonOnLoading', false)} + loading={boolean('loading', false)} + rounded={boolean('rounded', false)} + linked={boolean('linked', false)} + inlined={boolean('inlined', false)} + outlined={boolean('outlined', false)} + theme={select('theme', THEMES, THEMES[0])} + size={select('size', SIZES, SIZES[0])} + prefixIcon={text('prefixIcon', '')} + suffixIcon={text('suffixIcon', '')} /> </Story> </Canvas> @@ -97,14 +61,61 @@ Actionable HTML buttons with different styles and support for React Router. <Story name="Button Group"> <ControlGroup> <ControlGroup.Item> - <Button kind="secondary">Button</Button> + <Button theme="grey" outlined> + Button + </Button> </ControlGroup.Item> <ControlGroup.Item> - <Button kind="secondary">Button</Button> + <Button theme="grey" outlined> + Button + </Button> </ControlGroup.Item> <ControlGroup.Item> - <Button kind="secondary">Button</Button> + <Button theme="gray" outlined> + Button + </Button> </ControlGroup.Item> </ControlGroup> </Story> </Canvas> + +#### Button Brand + +In some rare cases, we want to overwrite all colors for a button with a given theme/brand. For example, it is something use on beacon to use the company color within the beacon render. + +`theme.brandColor` needs to received an object generated by `makeBrandColors`. + +<Canvas> + <Story name="Button Brand"> + <ThemeProvider + theme={{ + brandColor: makeBrandColors( + getColor( + select( + 'brandColor', + [ + 'purple.500', + 'red.200', + 'yellow.700', + 'pink.300', + 'lavender.400', + ], + 'purple.500' + ) + ) + ), + }} + > + <Button + children={text('children', 'Button')} + disabled={boolean('disabled', false)} + loading={boolean('loading', false)} + rounded={boolean('rounded', false)} + linked={boolean('linked', false)} + outlined={boolean('outlined', false)} + theme={select('theme', THEMES, THEMES[0])} + size={select('size', SIZES, SIZES[0])} + /> + </ThemeProvider> + </Story> +</Canvas> diff --git a/src/components/Button/Button.test.js b/src/components/Button/Button.test.js index 6b0f9547f..793dc8ad4 100644 --- a/src/components/Button/Button.test.js +++ b/src/components/Button/Button.test.js @@ -1,300 +1,205 @@ import React from 'react' -import { mount } from 'enzyme' +import { render, fireEvent } from '@testing-library/react' import Button from './Button' import Icon from '../Icon' +import userEvent from '@testing-library/user-event' +import { MemoryRouter as Router } from 'react-router-dom' + +const wrap = fn => Component => fn(<Router>{Component}</Router>) +const renderWithRouter = wrap(render) describe('ClassNames', () => { test('Accepts custom className', () => { - const wrapper = mount(<Button className="foo bar baz">Click Me</Button>) - const classNames = wrapper.find('button.c-Button').prop('className') + const { getByTestId } = render( + <Button className="foo bar baz">Click Me</Button> + ) - expect(classNames).toContain('foo') - expect(classNames).toContain('bar') - expect(classNames).toContain('baz') + expect(getByTestId('Button')).toHaveClass('foo') + expect(getByTestId('Button')).toHaveClass('bar') + expect(getByTestId('Button')).toHaveClass('baz') }) }) -describe('Kind', () => { +describe('Theme', () => { test('Adds the respective classNames', () => { - const primary = mount(<Button kind="primary">Primary</Button>) - const link = mount(<Button kind="link">Plain</Button>) + const { getByTestId, rerender } = render(<Button theme="blue">blue</Button>) - expect(primary.find('button.c-Button').hasClass('is-primary')).toBe(true) - expect(link.find('button.c-Button').hasClass('is-link')).toBe(true) - }) + expect(getByTestId('Button')).toHaveClass('is-theme-blue') - test('Creates a button with type="submit"', () => { - const wrapper = mount(<Button submit>Submit</Button>) + rerender(<Button theme="red">Red</Button>) + expect(getByTestId('Button')).toHaveClass('is-theme-red') - expect(wrapper.find('button').prop('type')).toBe('submit') - }) + rerender(<Button theme="green">Red</Button>) + expect(getByTestId('Button')).toHaveClass('is-theme-green') - test('Can create block buttons, if specified', () => { - const wrapper = mount(<Button isBlock>Button</Button>) + rerender(<Button linked>Plain</Button>) - expect(wrapper.find('button.c-Button').hasClass('is-block')).toBe(true) + expect(getByTestId('Button')).toHaveClass('is-style-link') }) - test('Does not render Block style, if spefied', () => { - const wrapper = mount(<Button isBlock={false}>Button</Button>) + test('Creates a button with type="submit"', () => { + const { getByTestId } = render(<Button submit>Submit</Button>) - expect(wrapper.find('button.c-Button').hasClass('is-block')).toBe(false) + expect(getByTestId('Button')).toHaveAttribute('type', 'submit') }) }) describe('Sizes', () => { test('Adds the respective classNames', () => { - const lg = mount(<Button size="lg">Large</Button>) - const md = mount(<Button size="md">Medium</Button>) - const sm = mount(<Button size="sm">Small</Button>) - const xl = mount(<Button size="lgxl">ExtraLarge</Button>) - - expect(lg.find('button.c-Button').hasClass('is-lg')).toBe(true) - expect(md.find('button.c-Button').hasClass('is-md')).toBe(true) - expect(sm.find('button.c-Button').hasClass('is-sm')).toBe(true) - expect(xl.find('button.c-Button').hasClass('is-xl')).toBe(true) + const { getByText } = render( + <> + <Button size="xl">ExtraLarge</Button> + <Button size="lg">Large</Button> + <Button size="md">Medium</Button> + <Button size="sm">Small</Button> + <Button size="xs">ExtraSmall</Button> + <Button size="xxs">ExtraExtraSmall</Button> + <Button>Default</Button> + </> + ) + expect(getByText('Default')).toHaveClass('is-size-lg') + expect(getByText('ExtraExtraSmall')).toHaveClass('is-size-xxs') + expect(getByText('ExtraSmall')).toHaveClass('is-size-xs') + expect(getByText('Small')).toHaveClass('is-size-sm') + expect(getByText('Medium')).toHaveClass('is-size-md') + expect(getByText('Large')).toHaveClass('is-size-lg') + expect(getByText('ExtraLarge')).toHaveClass('is-size-xl') }) }) describe('States', () => { - test('Adds the respective classNames', () => { - const success = mount(<Button state="success">Success</Button>) - const danger = mount(<Button state="danger">Danger</Button>) - const warning = mount(<Button state="warning">Warning</Button>) - - expect(success.find('button.c-Button').hasClass('is-success')).toBe(true) - expect(danger.find('button.c-Button').hasClass('is-danger')).toBe(true) - expect(warning.find('button.c-Button').hasClass('is-warning')).toBe(true) - }) - - test('Adds the active classNames', () => { - const wrapper = mount(<Button isActive>Button</Button>) - - expect(wrapper.find('button.c-Button').hasClass('is-active')).toBe(true) - }) - - test('Adds the focus classNames', () => { - const wrapper = mount(<Button isFocused>Button</Button>) - - expect(wrapper.find('button.c-Button').hasClass('is-focused')).toBe(true) - }) - - test('Adds the hover classNames', () => { - const wrapper = mount(<Button isHovered>Button</Button>) - - expect(wrapper.find('button.c-Button').hasClass('is-hovered')).toBe(true) - }) - test('Disables the button', () => { const callback = jest.fn() - const disabledButton = mount( + const { getByTestId } = render( <Button disabled onClick={callback}> Disabled </Button> ) - disabledButton.simulate('click') - - expect(disabledButton.prop('disabled')).toBe(true) - expect(callback).not.toBeCalled() - }) -}) - -describe('Styles', () => { - test('Applies suffix styles', () => { - const wrapper = mount(<Button isSuffix>Click Me</Button>) - - expect(wrapper.find('button.c-Button').hasClass('is-suffix')).toBe(true) - }) -}) - -describe('Themes', () => { - test('Does not have a theme className by default', () => { - const wrapper = mount(<Button />) + userEvent.click(getByTestId('Button')) - expect(wrapper.props().theme).not.toBeTruthy() + expect(getByTestId('Button')).toBeDisabled() + expect(callback).not.toHaveBeenCalled() }) - test('Can add theme className', () => { - const wrapper = mount(<Button theme="editing" />) + test('primary lg & xl have a minimum width of 120px', () => { + const { getByTestId } = render( + <Button theme="blue" size="lg"> + Button + </Button> + ) - expect(wrapper.find('button.c-Button').hasClass('is-editing')).toBeTruthy() + const styles = window.getComputedStyle(getByTestId('Button')) + expect(styles.getPropertyValue('min-width')).toBe('var(--buttonMinWidth)') + expect(styles.getPropertyValue('--buttonMinWidth')).toBe('120px') }) }) describe('Styles', () => { test('Renders isFirst styles', () => { - const wrapper = mount(<Button isFirst />) + const { getByTestId } = render(<Button isFirst />) - expect(wrapper.find('button.c-Button').hasClass('is-first')).toBe(true) + expect(getByTestId('Button')).toHaveClass('is-first') }) test('Renders isNotOnly styles', () => { - const wrapper = mount(<Button isNotOnly />) + const { getByTestId } = render(<Button isNotOnly />) - expect(wrapper.find('button.c-Button').hasClass('is-notOnly')).toBe(true) + expect(getByTestId('Button')).toHaveClass('is-notOnly') }) test('Renders isLast styles', () => { - const wrapper = mount(<Button isLast />) + const { getByTestId } = render(<Button isLast />) - expect(wrapper.find('button.c-Button').hasClass('is-last')).toBe(true) + expect(getByTestId('Button')).toHaveClass('is-last') }) }) describe('Events', () => { test('Fires onBlur callback', () => { const spy = jest.fn() - const wrapper = mount(<Button onBlur={spy} />) + const { getByTestId } = render(<Button onBlur={spy} />) - wrapper.simulate('blur') + fireEvent.blur(getByTestId('Button')) expect(spy).toHaveBeenCalled() }) test('Fires onClick callback', () => { const spy = jest.fn() - const wrapper = mount(<Button onClick={spy} />) + const { getByTestId } = render(<Button onClick={spy} />) - wrapper.simulate('click') + userEvent.click(getByTestId('Button')) expect(spy).toHaveBeenCalled() }) test('Fires onFocus callback', () => { const spy = jest.fn() - const wrapper = mount(<Button onFocus={spy} />) + const { getByTestId } = render(<Button onFocus={spy} />) - wrapper.simulate('focus') + getByTestId('Button').focus() expect(spy).toHaveBeenCalled() }) }) -describe('Focus', () => { - test('Renders FocusUI on focus', () => { - const wrapper = mount(<Button kind="primary" />) - wrapper.simulate('focus') - - const o = wrapper.find('span.c-ButtonFocus') - - expect(o.length).toBe(1) - }) - - test('Does not render FocusUI on certain buttons', () => { - const wrapper = mount(<Button kind="link" />) - wrapper.simulate('focus') - - const o = wrapper.find('span.c-ButtonFocus') - - expect(o.length).toBe(0) - }) - - test('Does not render FocusUI if disabled', () => { - const wrapper = mount(<Button kind="primary" disabled />) - wrapper.simulate('focus') - - const o = wrapper.find('span.c-ButtonFocus') - - expect(o.length).toBe(0) - }) - - test('Can be rendered with prop', () => { - const wrapper = mount(<Button kind="primary" isFocused />) - - const o = wrapper.find('span.c-ButtonFocus') - - expect(o.length).toBe(1) - }) - - test('Passes isFirst, isNotOnly, and isLast props', () => { - const wrapper = mount( - <Button kind="primary" isFocused isFirst isNotOnly isLast /> - ) - - const o = wrapper.find('span.c-ButtonFocus') - - expect(o.length).toBe(1) - expect(o.hasClass('is-first')) - expect(o.hasClass('is-notOnly')) - expect(o.hasClass('is-last')) - }) -}) - -describe('Ref', () => { - test('Can retrieve button ref from ref prop', () => { - let ref - mount(<Button kind="primary" innerRef={node => (ref = node)} />) - - expect(ref).toBeTruthy() - }) - - test('Can retrieve button ref from buttonRef prop', () => { - let ref - mount(<Button kind="primary" buttonRef={node => (ref = node)} />) - - expect(ref).toBeTruthy() - expect(ref.tagName).toBe('BUTTON') - }) -}) - describe('Icon', () => { test('Can render an Icon', () => { - const wrapper = mount( + const { container } = render( <Button> <Icon /> </Button> ) - expect(wrapper.find('Icon').length).toBe(1) + expect(container.querySelector('.c-Icon')).toBeTruthy() }) test('Can render an Icon + Text', () => { - const wrapper = mount( + const { getByTestId, container } = render( <Button> <Icon /> News </Button> ) - expect(wrapper.find('Icon').length).toBe(1) - expect(wrapper.text()).toContain('News') + expect(container.querySelector('.c-Icon')).toBeTruthy() + expect(getByTestId('Button')).toHaveTextContent('News') }) test('Provides Icon with offsetLeft prop', () => { - const wrapper = mount( + const { getByTestId, container } = render( <Button> <Icon /> News </Button> ) - expect(wrapper.find('Icon').prop('offsetLeft')).toBe(true) - expect(wrapper.find('Icon').prop('offsetRight')).toBe(false) - expect(wrapper.text()).toContain('News') + expect(container.querySelector('.c-Icon')).toHaveClass('is-offsetLeft') + expect(container.querySelector('.c-Icon')).not.toHaveClass('is-offsetRight') + + expect(getByTestId('Button')).toHaveTextContent('News') }) test('Provides Icon with offsetRight prop', () => { - const wrapper = mount( + const { getByTestId, container } = render( <Button> News <Icon /> </Button> ) - expect(wrapper.find('Icon').prop('offsetLeft')).toBe(false) - expect(wrapper.find('Icon').prop('offsetRight')).toBe(true) - expect(wrapper.text()).toContain('News') + expect(container.querySelector('.c-Icon')).not.toHaveClass('is-offsetLeft') + expect(container.querySelector('.c-Icon')).toHaveClass('is-offsetRight') + expect(getByTestId('Button')).toHaveTextContent('News') }) }) describe('Content event propagation', () => { test('Allows content event propagation by default', () => { const spy = jest.fn() - const wrapper = mount( + const { container } = render( <Button onClick={spy}> <Icon /> </Button> ) - const el = wrapper.find('Icon').last() - - el.simulate('click') + userEvent.click(container.querySelector('.c-Icon')) expect(spy).toHaveBeenCalled() }) @@ -302,79 +207,61 @@ describe('Content event propagation', () => { describe('Link', () => { test('Can render a link, if href is defined', () => { - const wrapper = mount(<Button href="/" />) + const { getByText } = renderWithRouter(<Button href="/" />) - expect(wrapper.find('a').length).toBeTruthy() - expect(wrapper.find('button').length).toBeFalsy() + expect( + getByText((content, element) => { + return element.tagName.toLowerCase() === 'a' + }) + ).toBeTruthy() }) test('Can render a link, if to is defined', () => { - const wrapper = mount(<Button to="/" />) - - expect(wrapper.find('a').length).toBeTruthy() - expect(wrapper.find('button').length).toBeFalsy() + const { getByText } = renderWithRouter(<Button to="/" />) + expect( + getByText((content, element) => { + return element.tagName.toLowerCase() === 'a' + }) + ).toBeTruthy() }) test('Can render a link based props', () => { - const wrapper = mount(<Button href="/" target="_blank" />) - const el = wrapper.find('a').first() + const { getByTestId } = renderWithRouter( + <Button href="/" target="_blank" /> + ) - expect(el.length).toBeTruthy() - expect(el.prop('target')).toBe('_blank') + expect(getByTestId('Button')).toHaveAttribute('target', '_blank') }) - test('Changes back to <button>, if href is removed', () => { - const wrapper = mount(<Button href="/" />) - - expect(wrapper.find('a').length).toBeTruthy() - - wrapper.setProps({ href: null }) - - expect(wrapper.find('a').length).toBeFalsy() - expect(wrapper.find('button').length).toBeTruthy() + test('Render a button element type', () => { + const { getByText } = renderWithRouter(<Button />) + expect( + getByText((content, element) => { + return element.tagName.toLowerCase() === 'button' + }) + ).toBeTruthy() }) }) describe('Loading', () => { test('Add loading className, if isLoading', () => { - const wrapper = mount(<Button isLoading />) - const el = wrapper.find('button') + const { getByTestId } = render(<Button loading />) - expect(el.hasClass('is-loading')).toBeTruthy() + expect(getByTestId('Button')).toHaveClass('is-loading') }) test('Renders a spinner if isLoading', () => { - const wrapper = mount(<Button isLoading />) - const el = wrapper.find('div.c-Spinner') - - expect(el.length).toBeTruthy() + const { container } = render(<Button loading />) + expect(container.querySelector('.c-Spinner')).toBeTruthy() }) test('Does not renders a spinner if not isLoading', () => { - const wrapper = mount(<Button isLoading={false} />) - const el = wrapper.find('div.c-Spinner') - - expect(el.length).toBeFalsy() + const { container } = render(<Button />) + expect(container.querySelector('.c-Spinner')).toBeFalsy() }) test('Becomes disabled if isLoading, by default', () => { - const wrapper = mount(<Button isLoading />) - const el = wrapper.find('button') - - expect(el.prop('disabled')).toBe(true) - }) - - test('Does not become disabled, if specified', () => { - const wrapper = mount(<Button isLoading disableOnLoading={false} />) - const el = wrapper.find('button') - - expect(el.prop('disabled')).toBe(false) - }) - - test('Add special spinButtonOnLoading, if isLoading and enabled', () => { - const wrapper = mount(<Button isLoading spinButtonOnLoading />) - const el = wrapper.find('button') - - expect(el.hasClass('is-spinButtonOnLoading')).toBeTruthy() + const { getByTestId } = render(<Button loading />) + expect(getByTestId('Button')).toBeDisabled() }) }) diff --git a/src/components/Button/Button.utils.js b/src/components/Button/Button.utils.js new file mode 100644 index 000000000..bcc3ca0f4 --- /dev/null +++ b/src/components/Button/Button.utils.js @@ -0,0 +1,201 @@ +import React, { useMemo } from 'react' +import { useTheme } from 'styled-components' + +import getValidProps from '@helpscout/react-utils/dist/getValidProps' +import classNames from 'classnames' +import Icon from '../Icon' +import { Link as ReactRouterLink } from 'react-router-dom' + +export const SIZE_XXL = 'xxl' +export const SIZE_XL = 'xl' +export const SIZE_LG = 'lg' +export const SIZE_MD = 'md' +export const SIZE_SM = 'sm' +export const SIZE_XS = 'xs' +export const SIZE_XXS = 'xxs' + +export const THEME_BLUE = 'blue' +export const THEME_RED = 'red' +export const THEME_GREEN = 'green' +export const THEME_GREY = 'grey' +export const THEME_BRAND = 'brand' + +export const STYLE_FILLED = 'filled' +export const STYLE_LINK = 'link' +export const STYLE_OUTLINED = 'outlined' + +export const SIZES = [ + SIZE_XXL, + SIZE_XL, + SIZE_LG, + SIZE_MD, + SIZE_SM, + SIZE_XS, + SIZE_XXS, +] +export const THEMES = [THEME_BLUE, THEME_RED, THEME_GREEN, THEME_GREY] + +const useButtonTheme = ({ theme }) => { + const styledTheme = useTheme() + + if (styledTheme && styledTheme.brandColor) { + return THEME_BRAND + } + if (THEMES.includes(theme)) return theme + if (theme === 'gray') return THEME_GREY + return THEME_BLUE +} +const useButtonSize = ({ size }) => { + if (SIZES.includes(size)) return size + if (size === 'lgxl') return SIZE_XL // old overwrite + return SIZE_LG +} +const useButtonStyle = ({ filled, outlined, linked }) => { + if (filled) return STYLE_FILLED + if (outlined) return STYLE_OUTLINED + if (linked) return STYLE_LINK + return STYLE_FILLED +} + +const useButtonChildren = ({ children }) => { + return useMemo(() => { + return React.Children.map(children, (child, index) => { + if (!child) return + if (!child.hasOwnProperty('type')) return child + if (child.type !== Icon) return child + + const len = React.Children.count(children) + const isFirst = index === 0 + const isLast = index === len - 1 + const isOnly = isFirst && isLast + + return React.cloneElement(child, { + offsetLeft: isFirst && !isOnly, + offsetRight: isLast && !isOnly, + }) + }) + }, [children]) +} + +export const useButtonClassnames = (defaultClassname, props) => { + const { + className, + disabled, + inlined, + isFirst, + isLast, + isNotOnly, + loading, + prefixIcon, + rounded, + seamless, + suffixIcon, + } = props + + const theme = useButtonTheme(props) + const size = useButtonSize(props) + const style = useButtonStyle(props) + + const isDisabled = disabled || loading + + return classNames( + defaultClassname, + seamless && `is-seamless`, + isDisabled && 'is-disabled', + isFirst && 'is-first', + isLast && 'is-last', + isNotOnly && 'is-notOnly', + loading && 'is-loading', + rounded && 'is-rounded', + size && `is-size-${size}`, + style && `is-style-${style}`, + theme && `is-theme-${theme}`, + inlined && 'is-inlined', + prefixIcon && 'has-prefix-icon', + suffixIcon && 'has-suffix-icon', + className + ) +} + +export const useButtonAs = ({ + as, + disabled, + href, + loading, + rel, + tabIndex, + submit, + target, + to, +}) => { + const isDisabled = disabled || loading + const type = submit ? 'submit' : 'button' + const hrefValue = href || to + const selector = as || Boolean(hrefValue) ? 'a' : 'button' + + if (selector === 'button') { + return { + as: to ? ReactRouterLink : selector, + to: to ? to : undefined, + type, + disabled: isDisabled, + } + } + + return { + as: to ? ReactRouterLink : selector, + role: 'button', + tabIndex: isDisabled ? undefined : tabIndex || 0, + href: selector === 'a' && isDisabled ? undefined : hrefValue, + to: to ? to : undefined, + target: selector === 'a' ? target : undefined, + 'aria-disabled': !isDisabled ? undefined : isDisabled, + rel: selector === 'a' ? rel : undefined, + } +} + +export const useButton = props => { + const { + as, + className, + disabled, + href, + isFirst, + isLast, + isNotOnly, + loading, + rel, + rounded, + submit, + target, + to, + ...otherProps + } = props + + const children = useButtonChildren(props) + const componentClassName = useButtonClassnames('c-Button', props) + + const additionalProps = useButtonAs({ + as, + disabled, + href, + loading, + rel, + submit, + target, + to, + }) + + const { prefixIcon, suffixIcon, ...rest } = otherProps + + return { + 'data-testid': 'Button', + ...getValidProps(rest), + ...additionalProps, + className: componentClassName, + children, + loading, + prefixIcon, + suffixIcon, + } +} diff --git a/src/components/Condition/Condition.AddButton.jsx b/src/components/Condition/Condition.AddButton.jsx index 1c0011a00..33457a9d0 100644 --- a/src/components/Condition/Condition.AddButton.jsx +++ b/src/components/Condition/Condition.AddButton.jsx @@ -72,11 +72,11 @@ class AddButton extends React.PureComponent { onTypeChanged, selectableType, onClick, + disabled, ...rest } = this.props const isAnd = type.toLowerCase() === 'and' const align = isAnd ? 'center' : 'left' - const iconSize = isAnd ? 24 : 20 const label = isAnd ? 'and' : 'or' const size = isAnd ? 'sm' : 'xxs' @@ -90,28 +90,30 @@ class AddButton extends React.PureComponent { selection={dropListItems.find(item => item.id === type)} toggler={ <SplittedButtonUI + theme="green" + outlined {...getValidProps(rest)} text={label} - kind="tertiary" - actionButtonProps={{ disabled: rest.disabled }} + actionButtonProps={{ disabled }} togglerButtonProps={{ - kind: rest.disabled ? 'secondary' : 'tertiary', + theme: disabled ? 'grey' : 'green', flipChevron: true, }} - size={'xxs'} + size="xxs" onActionClick={this.handleOnClick} /> } /> ) : ( <ButtonUI + theme="green" + outlined + size={size} {...getValidProps(rest)} className={this.getClassName()} - kind="tertiary" onClick={this.handleOnClick} - size={size} + prefixIcon="plus-small" > - <Icon name="plus-small" isWithHiddenTitle={false} size={iconSize} /> {label} </ButtonUI> )} diff --git a/src/components/Condition/Condition.css.js b/src/components/Condition/Condition.css.js index 5f415d04b..2602e7e03 100644 --- a/src/components/Condition/Condition.css.js +++ b/src/components/Condition/Condition.css.js @@ -72,7 +72,9 @@ export const SplittedButtonUI = styled(SplittedButton)` & .SplitButton__Toggler { /* There's already important used in a component, so I have to use it to overwrite */ - min-width: 20px !important; + /* min-width: 20px !important; */ + /* width: 20px; */ + --buttonPadding: 0; width: 20px; } @@ -95,27 +97,6 @@ export const ButtonUI = styled(Button)` margin-bottom: ${config.operatorBorderWidth}; margin-top: ${config.operatorBorderWidth}; text-transform: uppercase; - - &.is-borderless { - box-shadow: none; - margin-bottom: 0; - margin-top: 0; - } - - .c-Icon { - margin: 0 0 0 -8px !important; - } - - &.is-or { - .c-Button__content { - position: relative; - top: -1px; - } - - .c-Icon { - margin: -0.5px -2px -0.5px -6px !important; - } - } ` export const OperatorWrapperBaseUI = styled(Centralize)` diff --git a/src/components/ConditionField/ConditionField.css.js b/src/components/ConditionField/ConditionField.css.js index d45b2326b..f00eba5e0 100644 --- a/src/components/ConditionField/ConditionField.css.js +++ b/src/components/ConditionField/ConditionField.css.js @@ -2,7 +2,7 @@ import styled from 'styled-components' import Flexy from '../Flexy' export const FieldCloseWrapperUI = styled(Flexy.Item)` - margin: 2.5px 0; + align-item: center; ` export const AddButtonContentUI = styled('div')` diff --git a/src/components/ConditionField/ConditionField.jsx b/src/components/ConditionField/ConditionField.jsx index 5e1978654..b7bc4085b 100644 --- a/src/components/ConditionField/ConditionField.jsx +++ b/src/components/ConditionField/ConditionField.jsx @@ -65,6 +65,8 @@ export class ConditionField extends React.PureComponent { withTriggerWrapper={false} > <IconButton + seamless + size="lg" data-cy="ConditionFieldRemoveButton" icon={closeIcon} onClick={onRemove} diff --git a/src/components/ConditionField/ConditionField.test.js b/src/components/ConditionField/ConditionField.test.js index 8d47563ac..f943a92b8 100644 --- a/src/components/ConditionField/ConditionField.test.js +++ b/src/components/ConditionField/ConditionField.test.js @@ -240,19 +240,21 @@ describe('With selectable conjunction', () => { describe('onRemove', () => { test('Renders a remove button', () => { - const { getByRole, getByTitle } = render(<ConditionField />) + const { getByRole, container } = render(<ConditionField />) expect(getByRole('button')).toBeInTheDocument() - expect(getByTitle('collapse')).toBeInTheDocument() + expect( + container.querySelector('[data-icon-name="collapse"]') + ).toBeInTheDocument() }) test('Does not render a remove button', () => { - const { queryByRole, queryByTitle } = render( + const { queryByRole, container } = render( <ConditionField isWithRemove={false} /> ) expect(queryByRole('button')).toBe(null) - expect(queryByTitle('collapse')).toBe(null) + expect(container.querySelector('[data-icon-name="collapse"]')).toBe(null) }) test('Fires onRemove callback when remove button is clicked', async () => { diff --git a/src/components/CopyButton/CopyButton.css.js b/src/components/CopyButton/CopyButton.css.js index 1df72beb6..c54274ac8 100644 --- a/src/components/CopyButton/CopyButton.css.js +++ b/src/components/CopyButton/CopyButton.css.js @@ -3,13 +3,6 @@ import { getColor } from '../../styles/utilities/color' import Button from '../Button' import Icon from '../Icon' -export const config = { - copyConfirmed: { - background: getColor('blue.500'), - color: 'white', - }, -} - export const TextUI = styled.span` display: inline-flex; transition: opacity linear 150ms; @@ -32,33 +25,16 @@ export const ConfirmationIconWrapperUI = styled('div')` opacity: 0; transition: opacity linear 150ms; color: white; - background: ${config.copyConfirmed.background} !important; + background: var(--confirmColor); ` export const CopyButtonUI = styled(Button)` + --confirmColor: var(--buttonMainColor); + --confirmBorderColor: var(buttonBorderColor); position: relative; - overflow: hidden; - - &.c-CopyButton { - min-width: 60px; - - &.is-with-icon { - min-width: auto; - max-width: auto; - width: 40px; - padding: 0; - text-align: center; - background: ${getColor('grey.200')}; - color: ${getColor('charcoal.300')}; - - &:hover { - background: ${getColor('blue.100')}; - } - } - } &.is-copyConfirmed { - border-color: ${config.copyConfirmed.background} !important; + border-color: var(--confirmColor) !important; ${TextUI} { opacity: 0; } diff --git a/src/components/CopyButton/CopyButton.jsx b/src/components/CopyButton/CopyButton.jsx index 0d9d4af06..6165c93c4 100644 --- a/src/components/CopyButton/CopyButton.jsx +++ b/src/components/CopyButton/CopyButton.jsx @@ -4,6 +4,9 @@ import getValidProps from '@helpscout/react-utils/dist/getValidProps' import Icon from '../Icon' import classNames from 'classnames' import { noop } from '../../utilities/other' + +import { useCopyConfirmation } from './CopyButton.utils' + import { CopyButtonUI, ConfirmationIconWrapperUI, @@ -11,128 +14,79 @@ import { IconUI, } from './CopyButton.css' -class CopyButton extends React.PureComponent { - state = { - shouldRenderConfirmation: false, - } - - _isMounted = false - confirmationTimeout = null - - componentDidMount() { - this._isMounted = true - } - - componentWillUnmount() { - this.clearConfirmationTimeout() - this._isMounted = false - } - - safeSetState = state => { - if (this._isMounted) { - this.setState(state) - } - } - - startConfirmationTimeout = () => { - const { onReset, resetTimeout } = this.props - - this.safeSetState({ - shouldRenderConfirmation: true, - }) - - this.confirmationTimeout = setTimeout(() => { - this.safeSetState({ - shouldRenderConfirmation: false, - }) - onReset() - }, resetTimeout) - } - - clearConfirmationTimeout = () => { - clearTimeout(this.confirmationTimeout) - } - - handleOnClick = event => { - this.clearConfirmationTimeout() - this.startConfirmationTimeout() - this.props.onClick(event) - } - - render() { - const { className, kind, size, icon, label, ...rest } = this.props - const { shouldRenderConfirmation } = this.state - - const componentClassName = classNames( - 'c-CopyButton', - shouldRenderConfirmation && 'is-copyConfirmed', - icon && 'is-with-icon', - className - ) - const iconSize = size === 'sm' ? '20' : '24' - - return ( - <CopyButtonUI - {...getValidProps(rest)} - kind={kind} - onClick={this.handleOnClick} - size={size} - className={componentClassName} - > - <ConfirmationIconWrapperUI> - <Icon - className="c-CopyButton__iconConfirmation" - name="checkmark" - size={iconSize} - /> - </ConfirmationIconWrapperUI> - {icon && <IconUI size={iconSize} name={icon} />} - {label && <TextUI>{label}</TextUI>} - </CopyButtonUI> - ) - } -} - -CopyButton.defaultProps = { - canRenderFocus: false, +const WrappedCopybutton = React.forwardRef(function CopyButton(props, ref) { + const { + className, + icon, + label, + onClick, + outlined, + size, + theme, + ...rest + } = props + + const [shouldRenderConfirmation, handleClick] = useCopyConfirmation(props) + + const componentClassName = classNames( + 'c-CopyButton', + shouldRenderConfirmation && 'is-copyConfirmed', + icon && 'is-with-icon', + className + ) + const iconSize = size === 'sm' ? '20' : '24' + + return ( + <CopyButtonUI + size={size} + theme={theme} + outlined={outlined} + {...getValidProps(rest)} + onClick={handleClick} + className={componentClassName} + ref={ref} + > + <ConfirmationIconWrapperUI> + <Icon + className="c-CopyButton__iconConfirmation" + name="checkmark" + size={iconSize} + /> + </ConfirmationIconWrapperUI> + {icon && <IconUI size={iconSize} name={icon} />} + {label && <TextUI>{label}</TextUI>} + </CopyButtonUI> + ) +}) + +WrappedCopybutton.defaultProps = { 'data-cy': 'CopyButton', - kind: 'secondary', + theme: 'blue', label: 'Copy', onClick: noop, onReset: noop, resetTimeout: 2000, size: 'sm', + outlined: true, } -CopyButton.propTypes = { - canRenderFocus: PropTypes.bool, +WrappedCopybutton.propTypes = { /** Custom class names to be added to the component. */ className: PropTypes.string, /** Data attr for Cypress tests. */ 'data-cy': PropTypes.string, - /** Applies the specified style to the button. - * 'primary': Blue button. Used for primary actions. - * 'primaryAlt': Purple button. Used for primary actions. - * 'secondary': White button with a border. Used for secondary actions. - * 'secondaryAlt': White button with a green border. Used for secondary actions. - * 'default': Borderless button. Used for subtle/tertiary actions. - * 'link': Button that looks like a `Link`. Used for subtle/tertiary actions. - */ - kind: PropTypes.oneOf([ - 'primary', - 'primaryAlt', - 'secondary', - 'secondaryAlt', - 'default', - 'link', - ]), label: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), onClick: PropTypes.func, onReset: PropTypes.func, resetTimeout: PropTypes.number, - /** Sets the size of the button. Can be one of `"sm"`, `"md"` or `"lg"`. */ - size: PropTypes.any, + /** Sets the button size. */ + size: PropTypes.string, + /** Sets the button theme. */ + theme: PropTypes.string, title: PropTypes.string, + + /** Set the outlined style to the button. */ + outlined: PropTypes.bool, } -export default CopyButton +export default WrappedCopybutton diff --git a/src/components/CopyButton/CopyButton.test.js b/src/components/CopyButton/CopyButton.test.js index bf7610d2c..c53142010 100644 --- a/src/components/CopyButton/CopyButton.test.js +++ b/src/components/CopyButton/CopyButton.test.js @@ -1,44 +1,44 @@ import React from 'react' -import { mount } from 'enzyme' +import { render, act } from '@testing-library/react' import CopyButton from './CopyButton' +import userEvent from '@testing-library/user-event' jest.useFakeTimers() describe('ClassName', () => { test('Has default component className', () => { - const wrapper = mount(<CopyButton />) - const el = wrapper.find('button.c-CopyButton') + const { getByTestId } = render(<CopyButton />) - expect(el.hasClass('c-CopyButton')).toBe(true) + expect(getByTestId('Button')).toHaveClass('c-CopyButton') }) test('Applies custom className if specified', () => { const className = 'gator' - const wrapper = mount(<CopyButton className={className} />) - const el = wrapper.find('button.c-CopyButton') + const { getByTestId } = render(<CopyButton className={className} />) - expect(el.hasClass(className)).toBe(true) + expect(getByTestId('Button')).toHaveClass(className) }) }) describe('Timeout', () => { test('Clears timeout on unmount', () => { const spy = jest.spyOn(window, 'clearTimeout') - const wrapper = mount(<CopyButton />) - - wrapper.unmount() - + const { getByTestId, unmount } = render(<CopyButton />) + act(() => { + userEvent.click(getByTestId('Button')) + unmount() + }) expect(spy).toHaveBeenCalled() - spy.mockRestore() }) - test('Clears timeout on click', () => { + test('Clears timeout on the second click', () => { const spy = jest.spyOn(window, 'clearTimeout') - const wrapper = mount(<CopyButton />) - const el = wrapper.find('button') - - el.simulate('click') + const { getByTestId } = render(<CopyButton />) + act(() => { + userEvent.click(getByTestId('Button')) + userEvent.click(getByTestId('Button')) + }) expect(spy).toHaveBeenCalled() @@ -46,26 +46,26 @@ describe('Timeout', () => { }) test('Renders, then resets confirmation UI on click', () => { - const wrapper = mount(<CopyButton />) - const el = wrapper.find('button') - - el.simulate('click') - - expect(wrapper.state().shouldRenderConfirmation).toBe(true) - - jest.runAllTimers() - - expect(wrapper.state().shouldRenderConfirmation).toBe(false) + const { getByTestId } = render(<CopyButton />) + act(() => { + userEvent.click(getByTestId('Button')) + }) + expect(getByTestId('Button')).toHaveClass('is-copyConfirmed') + + act(() => { + jest.runAllTimers() + }) + expect(getByTestId('Button')).not.toHaveClass('is-copyConfirmed') }) test('Fires onReset callback when timeout completes', () => { const spy = jest.fn() - const wrapper = mount(<CopyButton onReset={spy} />) - const el = wrapper.find('button') - - el.simulate('click') - jest.runAllTimers() + const { getByTestId } = render(<CopyButton onReset={spy} />) + act(() => { + userEvent.click(getByTestId('Button')) + jest.runAllTimers() + }) expect(spy).toHaveBeenCalled() }) }) diff --git a/src/components/CopyButton/CopyButton.utils.js b/src/components/CopyButton/CopyButton.utils.js new file mode 100644 index 000000000..12fea4769 --- /dev/null +++ b/src/components/CopyButton/CopyButton.utils.js @@ -0,0 +1,42 @@ +import { useState, useRef, useEffect, useCallback } from 'react' + +export const useCopyConfirmation = props => { + const { onClick, onReset, resetTimeout = 2000 } = props + + const [shouldRenderConfirmation, setConfirmation] = useState(false) + const confirmationTimeout = useRef() + const isCancelled = useRef(false) + + const handleClick = useCallback( + e => { + if (confirmationTimeout.current) { + clearTimeout(confirmationTimeout) + } + + setConfirmation(true) + onClick && onClick(e) + + confirmationTimeout.current = setTimeout(() => { + if (!isCancelled.current) { + setConfirmation(false) + onReset && onReset() + } + }, resetTimeout) + }, + [onClick, onReset, resetTimeout] + ) + + useEffect(() => { + if (confirmationTimeout.current) { + clearTimeout(confirmationTimeout) + } + return () => { + isCancelled.current = true + if (confirmationTimeout.current) { + clearTimeout(confirmationTimeout) + } + } + }, []) + + return [shouldRenderConfirmation, handleClick] +} diff --git a/src/components/CopyCode/CopyCode.jsx b/src/components/CopyCode/CopyCode.jsx index 1451f987a..d1edab841 100644 --- a/src/components/CopyCode/CopyCode.jsx +++ b/src/components/CopyCode/CopyCode.jsx @@ -104,12 +104,7 @@ class CopyCode extends React.PureComponent { > {this.getCodeMarkup()} </CopyCodeUI> - <CopyButtonUI - kind="secondary" - onClick={this.handleCopyClick} - canRenderFocus - size={buttonSize} - > + <CopyButtonUI size={buttonSize} onClick={this.handleCopyClick}> Copy </CopyButtonUI> </WrapperUI> diff --git a/src/components/CopyInput/CopyInput.css.js b/src/components/CopyInput/CopyInput.css.js index 2439f07fc..3012d5100 100644 --- a/src/components/CopyInput/CopyInput.css.js +++ b/src/components/CopyInput/CopyInput.css.js @@ -2,6 +2,9 @@ import styled from 'styled-components' import { getColor } from '../../styles/utilities/color' import Input from '../Input' +import CopyButton from '../CopyButton' +import { TextUI } from '../CopyButton/CopyButton.css' + export const CopyInputUI = styled(Input)` input::-moz-selection { background: ${getColor('blue.200')}; @@ -11,3 +14,18 @@ export const CopyInputUI = styled(Input)` background: ${getColor('blue.200')}; } ` + +export const CopyButtonUI = styled(CopyButton)` + &.is-size-lg { + --confirmColor: ${getColor('blue.500')}; + --confirmBorderColor: ${getColor('blue.500')}; + --buttonMinWidth: 40px; + --buttonPadding: 0; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + ${TextUI} { + padding: 0 30px; + } +` diff --git a/src/components/CopyInput/CopyInput.jsx b/src/components/CopyInput/CopyInput.jsx index 6e5790f68..589e083a7 100644 --- a/src/components/CopyInput/CopyInput.jsx +++ b/src/components/CopyInput/CopyInput.jsx @@ -1,11 +1,10 @@ import React from 'react' import PropTypes from 'prop-types' -import CopyButton from '../CopyButton' import getValidProps from '@helpscout/react-utils/dist/getValidProps' import classNames from 'classnames' import { copyToClipboard, selectText } from '../../utilities/clipboard' import { noop } from '../../utilities/other' -import { CopyInputUI } from './CopyInput.css' +import { CopyInputUI, CopyButtonUI } from './CopyInput.css' class CopyInput extends React.PureComponent { inputNode @@ -52,13 +51,13 @@ class CopyInput extends React.PureComponent { } }} suffix={ - <CopyButton + <CopyButtonUI + size="lg" + theme="grey" onClick={this.handleCopyClick.bind(this)} isLast - size="lg" - style={{ borderTopLeftRadius: 0, borderBottomLeftRadius: 0 }} tabIndex={'-1'} - innerRef={node => (this.copyButtonNode = node)} + ref={node => (this.copyButtonNode = node)} icon={buttonLabel ? null : 'copy-small'} label={buttonLabel} /> diff --git a/src/components/CopyValue/CopyValue.css.js b/src/components/CopyValue/CopyValue.css.js new file mode 100644 index 000000000..30b4365a0 --- /dev/null +++ b/src/components/CopyValue/CopyValue.css.js @@ -0,0 +1,92 @@ +import styled from 'styled-components' +import IconButton from '../IconButton' +import Text from '../Text' +import { getColor } from '../../styles/utilities/color' + +export const ConfirmationIconWrapperUI = styled.span` + display: flex; + align-items: center; + justify-content: center; + position: absolute; + bottom: 0; + left: 0; + right: 0; + top: 0; + z-index: 2; + opacity: 0; + transition: opacity linear 150ms; + color: white; + background: var(--buttonMainColor); + border-radius: 100px; +` + +export const IconButtonUI = styled(IconButton)` + margin-left: 5px; + + &&& { + --buttonHeight: 32px; + --buttonPadding: 0px; + + --buttonBackgroundColor: transparent; + --buttonBorderColor: transparent; + --buttonBackgroundColorHover: var(--buttonMainColor); + --buttonBorderColorHover: var(--buttonMainColor); + + --buttonColor: var(--iconTextColor); + + width: var(--buttonHeight); + min-width: 0; + + &:focus, + &.is-focused { + --buttonBackgroundColor: var(--buttonMainColor); + --buttonBorderColor: var(--buttonMainColor); + --buttonColor: white; + } + + &:focus:not(:focus-visible) { + --buttonBackgroundColor: transparent; + --buttonBorderColor: transparent; + --buttonColor: var(--iconTextColor); + } + + &:focus-visible, + &.is-copyConfirmed { + --buttonColor: white; + --buttonBackgroundColor: var(--buttonMainColor); + --buttonBorderColor: var(--buttonMainColor); + } + + .has-icon { + margin: 0; + } + + &.is-copyConfirmed ${ConfirmationIconWrapperUI} { + opacity: 1; + } + } +` + +export const CopyValueUI = styled.div` + display: flex; + align-items: center; + + --iconActiveColor: ${getColor('charcoal.500')}; + --iconInactiveColor: transparent; + --iconTextColor: var(--iconInactiveColor); + + &:focus-within, + &:hover { + --iconTextColor: var(--iconActiveColor); + } +` + +export const ValueUI = styled(Text)` + font-family: var(--HSDSGlobalFontFamilySystem); + color: ${getColor('charcoal.400')}; +` + +export const PrefixUI = styled.span` + color: ${getColor('grey.800')}; + margin-right: 5px; +` diff --git a/src/components/CopyValue/CopyValue.js b/src/components/CopyValue/CopyValue.js new file mode 100644 index 000000000..e1a148e48 --- /dev/null +++ b/src/components/CopyValue/CopyValue.js @@ -0,0 +1,75 @@ +import React, { forwardRef } from 'react' +import PropTypes from 'prop-types' + +import { + CopyValueUI, + ValueUI, + IconButtonUI, + PrefixUI, + ConfirmationIconWrapperUI, +} from './CopyValue.css' +import { useCopyConfirmation } from '../CopyButton/CopyButton.utils' +import getValidProps from '@helpscout/react-utils/dist/getValidProps' +import classNames from 'classnames' +import Icon from '../Icon' + +const WrappedCopyValue = forwardRef(function CopyValue(props, ref) { + const { className, prefix, value, renderValue, ...rest } = props + const [shouldRenderConfirmation, handleClick] = useCopyConfirmation(props) + + const componentClassName = classNames('c-CopyValue', className) + const iconButtonClassName = classNames( + shouldRenderConfirmation && 'is-copyConfirmed' + ) + + return ( + <CopyValueUI className={componentClassName} data-testid="CopyValue"> + {renderValue ? ( + renderValue(value) + ) : ( + <ValueUI weight={400} size={13}> + {prefix && <PrefixUI>{prefix}</PrefixUI>} + {value} + </ValueUI> + )} + + <IconButtonUI + {...getValidProps(rest)} + filled + icon="copy-small" + size="lg" + theme="blue" + onClick={e => { + navigator.clipboard.writeText(value) + e.target.blur() + handleClick(e) + }} + className={iconButtonClassName} + ref={ref} + > + <ConfirmationIconWrapperUI> + <Icon + className="c-CopyValue__iconConfirmation" + name="checkmark" + iconSize="24" + /> + </ConfirmationIconWrapperUI> + </IconButtonUI> + </CopyValueUI> + ) +}) + +WrappedCopyValue.defaultProps = { + resetTimeout: 1000, +} + +WrappedCopyValue.propTypes = { + onClick: PropTypes.func, + onReset: PropTypes.func, + prefix: PropTypes.string, + renderValue: PropTypes.func, + resetTimeout: PropTypes.number, + value: PropTypes.string.isRequired, +} + +export default WrappedCopyValue diff --git a/src/components/CopyValue/CopyValue.stories.mdx b/src/components/CopyValue/CopyValue.stories.mdx new file mode 100644 index 000000000..c22eecb0e --- /dev/null +++ b/src/components/CopyValue/CopyValue.stories.mdx @@ -0,0 +1,67 @@ +import { Meta, Story, ArgsTable, Canvas } from '@storybook/addon-docs/blocks' +import { action } from '@storybook/addon-actions' +import { text } from '@storybook/addon-knobs' +import CopyValue from './' +import Text from '../Text' + +<Meta + title="Components/Forms/CopyValue" + component={CopyValue} + parameters={{ + design: { + type: 'figma', + url: + 'https://www.figma.com/file/Wp9mDxTvWicSTWtezj2ImkMP/HSDS-Product?node-id=10004%3A27239', + }, + }} +/> + +# CopyValue + +A component to copy a give value + +<Canvas> + <Story name="default"> + <CopyValue + value={text('value', '123123123')} + onClick={action('Click')} + onReset={action('Reset')} + prefix={text('prefix', '#')} + /> + </Story> +</Canvas> + +#### Reference + +- **Designer**: Buzz +- **Engineering**: Juan Pablo +- **Usage**: Everywhere + +<br /> +<br /> + +### Props + +<ArgsTable of={CopyValue} /> + +### Stories + +#### CopyValue with custom render + +<Canvas> + <Story name="custom-render"> + <CopyValue + value={text('value', '123123123')} + onClick={action('Click')} + onReset={action('Reset')} + prefix={text('prefix', '#')} + renderValue={value => { + return ( + <Text weight={700} size={48}> + {value} + </Text> + ) + }} + /> + </Story> +</Canvas> diff --git a/src/components/CopyValue/CopyValue.test.js b/src/components/CopyValue/CopyValue.test.js new file mode 100644 index 000000000..841ac77b8 --- /dev/null +++ b/src/components/CopyValue/CopyValue.test.js @@ -0,0 +1,123 @@ +import React from 'react' +import { render, act } from '@testing-library/react' +import CopyValue from './CopyValue' +import userEvent from '@testing-library/user-event' + +jest.useFakeTimers() + +beforeAll(() => { + Object.assign(navigator, { + clipboard: { + writeText: () => {}, + }, + }) +}) + +describe('ClassName', () => { + test('Has default component className', () => { + const { getByTestId } = render(<CopyValue value="test" />) + + expect(getByTestId('CopyValue')).toHaveClass('c-CopyValue') + }) + + test('Applies custom className if specified', () => { + const className = 'gator' + const { getByTestId } = render( + <CopyValue className={className} value="test" /> + ) + + expect(getByTestId('CopyValue')).toHaveClass(className) + }) +}) + +describe('Timeout', () => { + test('Clears timeout on unmount', () => { + const spy = jest.spyOn(window, 'clearTimeout') + const { getByTestId, unmount } = render(<CopyValue value="test" />) + act(() => { + userEvent.click(getByTestId('IconButton')) + unmount() + }) + expect(spy).toHaveBeenCalled() + spy.mockRestore() + }) + + test('Clears timeout on the second click', () => { + const spy = jest.spyOn(window, 'clearTimeout') + const { getByTestId } = render(<CopyValue value="test" />) + act(() => { + userEvent.click(getByTestId('IconButton')) + userEvent.click(getByTestId('IconButton')) + }) + + expect(spy).toHaveBeenCalled() + + spy.mockRestore() + }) + + test('Renders, then resets confirmation UI on click', () => { + const { getByTestId } = render(<CopyValue value="test" />) + act(() => { + userEvent.click(getByTestId('IconButton')) + }) + expect(getByTestId('IconButton')).toHaveClass('is-copyConfirmed') + + act(() => { + jest.runAllTimers() + }) + expect(getByTestId('IconButton')).not.toHaveClass('is-copyConfirmed') + }) + + test('Fires onReset callback when timeout completes', () => { + const spy = jest.fn() + const { getByTestId } = render(<CopyValue value="test" onReset={spy} />) + act(() => { + userEvent.click(getByTestId('IconButton')) + + jest.runAllTimers() + }) + expect(spy).toHaveBeenCalled() + }) +}) + +describe('clipboard', () => { + test('Clicking the button will copy the value to the clipboard', () => { + const spy = jest.spyOn(window.navigator.clipboard, 'writeText') + const { getByTestId } = render(<CopyValue value="test" />) + act(() => { + userEvent.click(getByTestId('IconButton')) + }) + expect(spy).toHaveBeenCalledWith('test') + spy.mockRestore() + }) +}) + +describe('renderValue', () => { + test('renders the value through the renderValue', () => { + const renderValue = value => <span data-testid="customRender">{value}</span> + + const { getByTestId } = render( + <CopyValue value="test" renderValue={renderValue} /> + ) + expect(getByTestId('customRender')).toBeTruthy() + expect(getByTestId('customRender')).toHaveTextContent('test') + }) +}) + +describe('prefix', () => { + test('renders a prefix before the value', () => { + const { getByTestId } = render(<CopyValue value="test" prefix="#" />) + expect(getByTestId('CopyValue')).toHaveTextContent('#') + }) +}) + +describe('onClick', () => { + test('onClick will be executed when the button is clicked', () => { + const spy = jest.fn() + const { getByTestId } = render( + <CopyValue onClick={spy} value="test" prefix="#" /> + ) + userEvent.click(getByTestId('IconButton')) + expect(spy).toHaveBeenCalled() + }) +}) diff --git a/src/components/CopyValue/index.js b/src/components/CopyValue/index.js new file mode 100644 index 000000000..5934bdf03 --- /dev/null +++ b/src/components/CopyValue/index.js @@ -0,0 +1,3 @@ +import CopyValue from './CopyValue' + +export default CopyValue diff --git a/src/components/Depth/Depth.stories.mdx b/src/components/Depth/Depth.stories.mdx index 1efc18c93..0978b9d0a 100644 --- a/src/components/Depth/Depth.stories.mdx +++ b/src/components/Depth/Depth.stories.mdx @@ -41,7 +41,9 @@ UI convenience component that can be used to apply "depth" styles according to [ 100 )} > - <Button kind="secondary">OK</Button> + <Button theme="grey" outlined> + OK + </Button> <Button kind="tertiary">Cancel</Button> </ContainerWithDepth> </Story> diff --git a/src/components/DropList/DropList.stories.mdx b/src/components/DropList/DropList.stories.mdx index ed5f87178..45c286f41 100644 --- a/src/components/DropList/DropList.stories.mdx +++ b/src/components/DropList/DropList.stories.mdx @@ -910,8 +910,7 @@ The `toggler` prop accepts any React component, so you can provide your own cust onClick={() => { console.log('Clicked from story') }} - withCaret={boolean('caret', true)} - shape={select('Shape', ['square', 'circle'], 'square')} + seamless={boolean('seamless', false)} /> } /> diff --git a/src/components/DropList/DropList.togglers.css.js b/src/components/DropList/DropList.togglers.css.js index 764d44c8a..14cac3ea6 100644 --- a/src/components/DropList/DropList.togglers.css.js +++ b/src/components/DropList/DropList.togglers.css.js @@ -1,14 +1,9 @@ import styled from 'styled-components' import { getColor } from '../../styles/utilities/color' import HSDSButton from '../Button' +import IconButton from '../IconButton' -import { FocusUI } from '../Button/Button.css' -import config from '../Button/Button.config' - -import { - focusRing, - focusShadowWithInset, -} from '../../styles/mixins/focusRing.css' +import { focusShadowWithInset } from '../../styles/mixins/focusRing.css' export const NavLinkTogglerUI = styled('button')` display: flex; @@ -33,44 +28,40 @@ export const NavLinkTogglerUI = styled('button')` ` export const SplitButtonUI = styled(HSDSButton)` - &.is-primary { + &.is-theme-blue { + --focusRingShadow: ${focusShadowWithInset}; margin-right: 0; - ${FocusUI} { - box-shadow: ${focusShadowWithInset}; - } } ` export const SplitButtonTogglerUI = styled(HSDSButton)` - &.SplitButton__Toggler { - min-width: 30px !important; - padding: 0; + pointer-events: all; + --focusRingShadow: ${focusShadowWithInset}; - pointer-events: all; - - &.is-primary { + &:not(.is-style-outlined) { + &.is-theme-blue { box-shadow: -1px 0 0 ${getColor('blue.600')}; - - ${FocusUI} { - box-shadow: ${focusShadowWithInset}; - } - - &.is-success { - box-shadow: -1px 0 0 ${getColor('green.600')}; - } - &.is-danger { - box-shadow: -1px 0 0 ${getColor('red.600')}; - } - &[disabled] { - box-shadow: -1px 0 0 ${getColor('grey.600')}; - } } - - .c-Button__content { - padding-top: 2px; - width: 16px; + &.is-theme-green { + box-shadow: -1px 0 0 ${getColor('green.600')}; + } + &.is-theme-red { + box-shadow: -1px 0 0 ${getColor('red.600')}; + } + &[disabled] { + box-shadow: -1px 0 0 ${getColor('grey.600')}; } } + + &.is-size-lg { + --buttonMinWidth: 30px; + padding: 0; + } + + .c-Button__content { + padding-top: 2px; + width: 16px; + } ` export const SelectUI = styled('button')` @@ -137,64 +128,13 @@ export const SelectErrorTooltipIconUI = styled('div')` margin-left: 8px; ` -export const MeatButtonUI = styled('button')` - width: 24px; - height: 24px; - padding: 0.5px 0px 0px 0.5px; - border: 0; - border-radius: 3px; - background-color: transparent; - - &:hover { - cursor: pointer; - } - - &:focus { - outline: 0; - box-shadow: inset 0 0 0 2px ${getColor('blue.500')}; - } -` - -export const IconButtonUI = styled('button')` - ${focusRing} - - display: flex; - justify-content: center; - align-items: center; - width: 45px; - height: 34px; - padding: 5px; - border: 0; - border-radius: 3px; - background-color: white; - color: ${getColor('charcoal.400')}; - - &.is-circle { - border-radius: 200%; - width: 36px; - height: 36px; - padding: 4px; - } - - &:hover { - cursor: pointer; - background-color: ${getColor('grey.300')}; - } - - &:hover { - color: ${getColor('charcoal.500')}; - } - +export const IconButtonUI = styled(IconButton)` &.is-active, &[aria-expanded='true'] { - color: ${getColor('charcoal.700')}; + color: var(--buttonColorHover); &:not(:focus-visible) { - background-color: ${getColor('grey.300')}; + background-color: var(--buttonBackgroundColorHover); } } - - .is-iconName-caret-down { - margin-left: -3px; - } ` diff --git a/src/components/DropList/DropList.togglers.jsx b/src/components/DropList/DropList.togglers.jsx index 7efd01e47..814e0779d 100644 --- a/src/components/DropList/DropList.togglers.jsx +++ b/src/components/DropList/DropList.togglers.jsx @@ -4,12 +4,11 @@ import { noop } from '../../utilities/other' import ControlGroup from '../ControlGroup' import HSDSButton from '../Button' import Icon from '../Icon' -import VisuallyHidden from '../VisuallyHidden' import { STATES } from '../../constants' import Tooltip from '../Tooltip' + import { IconButtonUI, - MeatButtonUI, NavLinkTogglerUI, SelectArrowsUI, SelectErrorTooltipIconUI, @@ -17,6 +16,7 @@ import { SplitButtonTogglerUI, SplitButtonUI, } from './DropList.togglers.css' +import { THEME_BLUE, THEME_GREY, SIZE_LG } from '../Button/Button.utils' export const SimpleButton = forwardRef( ( @@ -24,9 +24,9 @@ export const SimpleButton = forwardRef( a11yLabel, className = '', isActive = false, - kind = 'primary', + theme = THEME_BLUE, onClick = noop, - size = 'lg', + size = SIZE_LG, text = '', ...rest }, @@ -37,7 +37,7 @@ export const SimpleButton = forwardRef( aria-label={a11yLabel || 'toggle menu'} aria-haspopup="true" aria-expanded={isActive} - buttonRef={ref} + ref={ref} className={classNames( className, 'ButtonToggler', @@ -45,11 +45,9 @@ export const SimpleButton = forwardRef( )} data-cy="DropList.ButtonToggler" data-testid="DropList.ButtonToggler" - isActive={isActive} - kind={kind} - onClick={onClick} + theme={theme} size={size} - type="button" + onClick={onClick} {...rest} > <span>{text}</span> @@ -68,7 +66,7 @@ export const NavLink = forwardRef( isActive = false, kind = 'primary', onClick = noop, - size = 'lg', + size = SIZE_LG, text = '', ...rest }, @@ -106,12 +104,14 @@ export const SplittedButton = forwardRef( actionButtonProps = {}, className = '', isActive = false, - kind = 'primary', + theme = THEME_BLUE, onActionClick = noop, onClick = noop, - size = 'lg', + size = SIZE_LG, text = '', togglerButtonProps = {}, + disabled = false, + outlined = false, ...rest }, ref @@ -124,12 +124,14 @@ export const SplittedButton = forwardRef( > <ControlGroup.Item> <SplitButtonUI + theme={theme} + size={size} + disabled={disabled} + outlined={outlined} className="SplitButton__Action" data-cy="DropList.SplitButtonAction" data-testid="DropList.SplitButtonAction" - kind={kind} onClick={onActionClick} - size={size} type="button" {...actionButtonProps} > @@ -138,21 +140,22 @@ export const SplittedButton = forwardRef( </ControlGroup.Item> <ControlGroup.Item> <SplitButtonTogglerUI + theme={theme} + size={size} + disabled={disabled} + outlined={outlined} aria-label={a11yLabel || 'toggle menu'} aria-haspopup="true" aria-expanded={isActive} - buttonRef={ref} + ref={ref} className={classNames( 'SplitButton__Toggler', isActive && 'is-active' )} data-cy="DropList.SplitButtonToggler" data-testid="DropList.SplitButtonToggler" - isActive={isActive} isLast - kind={kind} onClick={onClick} - size={size} type="button" {...togglerButtonProps} > @@ -243,8 +246,9 @@ export const MeatButton = forwardRef( a11yLabel = '', className = '', isActive = false, - iconSize = '24', meatIcon = 'kebab', + size = SIZE_LG, + theme = THEME_GREY, onClick = noop, withTooltip = false, tooltipProps, @@ -254,8 +258,8 @@ export const MeatButton = forwardRef( ) => { const tooltipRef = useRef() - return ( - <MeatButtonUI + const component = ( + <IconButtonUI aria-haspopup="true" aria-expanded={isActive} className={classNames( @@ -265,33 +269,33 @@ export const MeatButton = forwardRef( )} data-cy="DropList.MeatButtonToggler" data-testid="DropList.MeatButtonToggler" - isActive={isActive} onClick={onClick} ref={ref} - type="button" + icon={meatIcon} + title={a11yLabel} + theme={theme} + size={size} {...rest} + /> + ) + + return withTooltip ? ( + <Tooltip + animationDelay={0} + animationDuration={0} + getTippyInstance={instance => { + tooltipRef.current = instance.reference + }} + placement="top-end" + title={a11yLabel} + triggerTarget={tooltipRef.current && tooltipRef.current.parentElement} + withTriggerWrapper={false} + {...tooltipProps} > - {withTooltip ? ( - <Tooltip - animationDelay={0} - animationDuration={0} - getTippyInstance={instance => { - tooltipRef.current = instance.reference - }} - placement="top-end" - title={a11yLabel} - triggerTarget={ - tooltipRef.current && tooltipRef.current.parentElement - } - {...tooltipProps} - > - <Icon name={meatIcon} size={iconSize} /> - </Tooltip> - ) : ( - <Icon name={meatIcon} size={iconSize} /> - )} - {a11yLabel ? <VisuallyHidden>{a11yLabel}</VisuallyHidden> : null} - </MeatButtonUI> + {component} + </Tooltip> + ) : ( + component ) } ) @@ -302,43 +306,38 @@ export const IconBtn = forwardRef( ( { a11yLabel = '', - caretSize = '14', className = '', isActive = false, iconName = 'assign', - iconSize = '24', onClick = noop, - shape = 'square', - withCaret = true, withTooltip = false, tooltipProps, + theme = THEME_GREY, + size = SIZE_LG, ...rest }, ref ) => { const component = ( <IconButtonUI - aria-label="toggle menu" + aria-label={a11yLabel || 'toggle-menu'} aria-haspopup="true" aria-expanded={isActive} className={classNames( className, 'IconButtonToggler', - isActive && 'is-active', - shape && `is-${shape}` + isActive && 'is-active' )} data-cy="DropList.IconButtonToggler" data-testid="DropList.IconButtonToggler" isActive={isActive} onClick={onClick} + icon={iconName} ref={ref} - type="button" + theme={theme} + size={size} {...rest} - > - <Icon name={iconName} size={iconSize} /> - {a11yLabel ? <VisuallyHidden>{a11yLabel}</VisuallyHidden> : null} - {withCaret ? <Icon name="caret-down" size={caretSize} /> : null} - </IconButtonUI> + /> ) return withTooltip ? ( diff --git a/src/components/EmojiPicker/EmojiPicker.css.js b/src/components/EmojiPicker/EmojiPicker.css.js index 43acc0f9e..41ae58650 100644 --- a/src/components/EmojiPicker/EmojiPicker.css.js +++ b/src/components/EmojiPicker/EmojiPicker.css.js @@ -43,6 +43,8 @@ export const EmojiItemUI = styled('div')` text-align: center; cursor: pointer; transform: scale(1); + display: flex; + align-items: center; .is-highlighted & { transform: scale(1.075); @@ -66,7 +68,9 @@ export const EmojiPickerUI = styled('div')` align-items: center; width: 100%; height: ${({ emojiSize }) => SIZES.LIST_HEIGHT[emojiSize]}; - padding: 0 5px; + padding: 0; + overflow: hidden; + line-height: 1; } ${EmojiItemUI} { diff --git a/src/components/Form/Form.jsx b/src/components/Form/Form.jsx index 3e057f6ea..a02a54cba 100644 --- a/src/components/Form/Form.jsx +++ b/src/components/Form/Form.jsx @@ -31,10 +31,9 @@ export class Form extends React.PureComponent { const saveButton = ( <Button - className="save-button" - kind="primary" size="lg" - version={2} + theme="blue" + className="save-button" submit={true} {...commonButtonProps} {...saveButtonProps} @@ -45,9 +44,10 @@ export class Form extends React.PureComponent { const cancelButton = onCancel && ( <Button + size="lg" className="cancel-button" - size="md" - version={2} + linked + theme="grey" onClick={onCancel} {...commonButtonProps} {...cancelButtonProps} @@ -58,10 +58,10 @@ export class Form extends React.PureComponent { const destroyButton = onDestroy && ( <Button + size="lg" + theme="red" + linked className="delete-button" - state="danger" - size="md" - version={2} onClick={onDestroy} {...commonButtonProps} {...destroyButtonProps} diff --git a/src/components/Form/Form.test.js b/src/components/Form/Form.test.js index ab524de83..4d8e3eca1 100644 --- a/src/components/Form/Form.test.js +++ b/src/components/Form/Form.test.js @@ -112,11 +112,11 @@ describe('Actions', () => { const wrapper = mount( <Form actionTabbable={false} - cancelButtonProps={{ isLoading: true }} - destroyButtonProps={{ isLoading: true }} + cancelButtonProps={{ loading: true }} + destroyButtonProps={{ loading: true }} onCancel={() => {}} onDestroy={() => {}} - saveButtonProps={{ isLoading: true }} + saveButtonProps={{ loading: true }} /> ) diff --git a/src/components/Frame/Frame.stories.mdx b/src/components/Frame/Frame.stories.mdx index 25d7ed0e2..2cb63f64d 100644 --- a/src/components/Frame/Frame.stories.mdx +++ b/src/components/Frame/Frame.stories.mdx @@ -25,7 +25,7 @@ A frame component will wrap the content inside an iFrame. It will leverage the S <div> <Modal trigger={ - <Button kind="primary" onClick={e => e.preventDefault()}> + <Button theme="blue" onClick={e => e.preventDefault()}> Open Modal </Button> } diff --git a/src/components/HSDS/GlobalStyle.js b/src/components/HSDS/GlobalStyle.js index f2917fe02..24b38064b 100644 --- a/src/components/HSDS/GlobalStyle.js +++ b/src/components/HSDS/GlobalStyle.js @@ -4,6 +4,9 @@ import { createGlobalStyle } from 'styled-components' export const FONT_FAMILY = '"Aktiv Grotesk", "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"' +export const FONT_FAMILY_SYSTEM = + '-apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"' + export const FONT_FAMILY_MONO = '"SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace' @@ -13,6 +16,8 @@ export default createGlobalStyle` :root{ --HSDSGlobalFontFamily: ${props => props.fontFamily ? props.fontFamily : FONT_FAMILY}; + --HSDSGlobalFontFamilySystem: ${props => + props.fontFamilySystem ? props.fontFamilySystem : FONT_FAMILY_SYSTEM}; --HSDSGlobalFontFamilyMono: ${props => props.fontFamilyMono ? props.fontFamilyMono : FONT_FAMILY_MONO}; --HSDSGlobalFontSize: ${props => diff --git a/src/components/Icon/Icon.jsx b/src/components/Icon/Icon.jsx index 72f5bc9e4..4107ca7eb 100644 --- a/src/components/Icon/Icon.jsx +++ b/src/components/Icon/Icon.jsx @@ -62,6 +62,7 @@ const Icon = props => { return ( <IconUI aria-hidden + role="img" {...getValidProps(rest)} className={componentClassName} onClick={onClick} diff --git a/src/components/IconButton/IconButton.css.js b/src/components/IconButton/IconButton.css.js index 8cc81fc79..535daab39 100644 --- a/src/components/IconButton/IconButton.css.js +++ b/src/components/IconButton/IconButton.css.js @@ -1,66 +1,53 @@ import styled from 'styled-components' -import Button from '../Button/index' -import buttonConfig from '../Button/Button.config' -import forEach from '../../styles/utilities/forEach' -import { getColor } from '../../styles/utilities/color' +import { ButtonUI } from '../Button/Button.css' -export const config = { - size: buttonConfig.size, - transition: `background-color 120ms ease`, -} +export const IconContainerUI = styled.span` + display: inline-flex; + align-items: center; + justify-content: center; -export const IconButtonUI = styled(Button)` - transition: ${config.transition}; + .c-Avatar { + width: 100%; + height: 100%; + } +` - .c-Button__content { - display: block; +export const IconButtonUI = styled(ButtonUI)` + border-radius: 100px; + --focusRingRadius: 100px; + + &.is-size-xl, + &.is-size-lg { + min-width: var(--buttonHeight); + --buttonPadding: 3px; } - &.is-borderless, - &.is-borderless:hover { - border-color: transparent !important; + &.is-size-sm { + min-width: var(--buttonHeight); + --buttonPadding: 2px; } - ${makeButtonSizeStyles}; - ${makeButtonHoverStyles}; + &.has-icon-only { + min-width: 0; + width: var(--buttonHeight); + padding: var(--buttonPadding); + } - .c-Icon { - margin: auto; + ${IconContainerUI} { + height: calc(var(--buttonHeight) - calc(var(--buttonPadding) * 2) - 2px); + aspect-ratio: 1; } - .c-Icon.withCaret { - margin-left: -3px; + + &.is-style-outlined:not(.has-children):not(.is-seamless) { + box-shadow: inset 0 0 0 3px white; } ` -function makeButtonSizeStyles() { - return forEach( - config.size, - (size, props) => ` - &.is-${size} { - height: ${props.height}px; - min-width: ${props.height}px; - padding-left: 0.2em; - padding-right: 0.2em; - } - ` - ) -} +export const ChildrenUI = styled.span` + margin-left: 8px; + margin-right: 16px; -function makeButtonHoverStyles() { - return ` - &.is-kind-default, - &.is-kind-secondary, - &.is-kind-tertiary, - &.is-kind-link { - &:hover { - background: rgba(0, 0, 0, 0.03); - } - &:focus { - background: rgba(0, 0, 0, 0.05); - } - &:active { - background: rgba(0, 0, 0, 0.08); - } - } - ` -} + &.has-icon { + margin-left: 4px; + } +` diff --git a/src/components/IconButton/IconButton.jsx b/src/components/IconButton/IconButton.jsx index 8183ff234..42973c66b 100644 --- a/src/components/IconButton/IconButton.jsx +++ b/src/components/IconButton/IconButton.jsx @@ -1,111 +1,139 @@ -import React from 'react' +import React, { forwardRef } from 'react' import PropTypes from 'prop-types' -import Button from '../Button' -import Icon from '../Icon' import classNames from 'classnames' -import { noop } from '../../utilities/other' -import { IconButtonUI } from './IconButton.css' - -export const IconButton = React.forwardRef((props, forwardedRef) => { - const getClassName = () => { - const { className, kind, icon, isBorderless } = props - - return classNames( - IconButton.className, - icon && `is-icon-${icon}`, - kind && `is-kind-${kind}`, - isBorderless && 'is-borderless', - className - ) - } - const getIconSize = () => { - const { iconSize, size } = props +import { + useButtonClassnames, + useButtonAs, + SIZE_XL, + SIZE_LG, + SIZE_SM, +} from '../Button/Button.utils' +import Icon from '../Icon' +import Avatar from '../Avatar' +import { IconButtonUI, IconContainerUI, ChildrenUI } from './IconButton.css' +import getValidProps from '@helpscout/react-utils/dist/getValidProps' - switch (size) { - case 'xs': - return 16 +export const SIZES = [SIZE_XL, SIZE_LG, SIZE_SM] - case 'sm': - return 18 +const useIconButtonAvatar = props => { + if (!props) { + return null + } - default: - return iconSize - } + const { image, initials, ...rest } = props + if (!image && !initials) { + return null } + return <Avatar image={image} initials={initials} {...rest} /> +} + +const useIconButton = props => { const { children, + filled, icon, - iconSize, - innerRef, - isWithHiddenTitle, - withCaret, + seamless, + size, + submit, + title, ...rest } = props - const setRef = buttonRef => { - innerRef(buttonRef) - forwardedRef && forwardedRef(buttonRef) + const forcedProps = { + // IconButton is always rounded, makes it easier to overwrite properties without the classname + rounded: null, + size: SIZES.includes(size) ? size : 'xl', + outlined: !filled, + seamless: seamless && !filled, } + const additionalProps = useButtonAs(props) + + const buttonProps = { + ...rest, + ...forcedProps, + ...additionalProps, + } + + const componentClassName = useButtonClassnames('c-IconButton', buttonProps) + + const ariaLabel = rest['aria-label'] || title || undefined + const hasOnlyIcon = !children && icon + return { + 'data-testid': 'IconButton', + ...getValidProps(buttonProps), + className: classNames( + componentClassName, + children && 'has-children', + hasOnlyIcon && 'has-icon-only' + ), + 'aria-label': ariaLabel, + iconSize: size !== SIZE_SM ? 24 : 20, + icon, + children, + } +} + +export const IconButton = forwardRef((props, ref) => { + const { avatarProps = {}, ...rest } = props + + const { iconSize, children, icon, ...buttonProps } = useIconButton(rest) + + const avatarComponent = useIconButtonAvatar(avatarProps) + const shouldShowIcon = icon && !avatarComponent + return ( - <IconButtonUI {...rest} className={getClassName()} innerRef={setRef}> - <Icon - name={icon} - size={getIconSize()} - isWithHiddenTitle={isWithHiddenTitle} - withCaret={withCaret} - /> + <IconButtonUI {...buttonProps} ref={ref}> + <IconContainerUI> + {shouldShowIcon && ( + <Icon name={icon} size={iconSize} title={buttonProps['aria-label']} /> + )} + {avatarComponent && avatarComponent} + </IconContainerUI> + {children && ( + <ChildrenUI className={shouldShowIcon && 'has-icon'}> + {children} + </ChildrenUI> + )} </IconButtonUI> ) }) -IconButton.className = 'c-IconButton' - IconButton.defaultProps = { - ...Button.defaultProps, + disabled: false, + submit: false, + filled: false, + theme: 'grey', 'data-cy': 'IconButton', icon: 'search', - iconSize: 24, - innerRef: noop, - isBorderless: true, - isWithHiddenTitle: false, - kind: 'default', - size: 'md', - shape: 'circle', - withCaret: false, + seamless: false, } IconButton.propTypes = { + avatarProps: PropTypes.shape({ + image: PropTypes.string.isRequired, + fallbackImage: PropTypes.string, + }), + /** Change the html element used for the component. */ + as: PropTypes.string, /** Custom class names to be added to the component. */ className: PropTypes.string, + /** Disable the button so it can't be clicked. */ + disabled: PropTypes.bool, + /** Hide the border and background of an outlined button */ + seamless: PropTypes.bool, /** The name of the icon to render. */ icon: PropTypes.string, - /** Renders a border around the [Button](../Button). */ - isBorderless: PropTypes.bool, - /** Renders a caret for the icon */ - withCaret: PropTypes.bool, - /** Adjusts the size of the icon. */ - iconSize: PropTypes.oneOf([ - 8, - 10, - 12, - 13, - 14, - 15, - 16, - 18, - 20, - 24, - 32, - 48, - 52, - ]), - isWithHiddenTitle: PropTypes.bool, - innerRef: PropTypes.func, /** Data attr for Cypress tests. */ 'data-cy': PropTypes.string, + /** Sets the size of the button. */ + size: PropTypes.oneOf(['sm', 'lg', 'xl']), + /** Sets the `type` of the button to `"submit"`. */ + submit: PropTypes.bool, + /** Applies a theme based style to the button. */ + theme: PropTypes.oneOf(['blue', 'red', 'green', 'grey']), } export default IconButton diff --git a/src/components/IconButton/IconButton.stories.mdx b/src/components/IconButton/IconButton.stories.mdx index 26155b756..fd2be0111 100644 --- a/src/components/IconButton/IconButton.stories.mdx +++ b/src/components/IconButton/IconButton.stories.mdx @@ -1,6 +1,8 @@ import { Meta, Story, ArgsTable, Canvas } from '@storybook/addon-docs/blocks' import { boolean, number, text, select } from '@storybook/addon-knobs' -import IconButton from './' +import IconButton, { SIZES } from './IconButton' +import { THEMES } from '../Button/Button.utils' +import AvatarSpec from '../../utilities/specs/avatar.specs' <Meta title="Components/Buttons/IconButton" @@ -22,42 +24,14 @@ Actionable `Button` with an `Icon`. <Story name="default"> <IconButton icon={text('icon', 'search')} - iconSize={number('iconSize', 24)} - shape={select( - 'shape', - { - circle: 'circle', - default: 'default', - }, - 'circle' - )} - kind={select( - 'kind', - { - primary: 'primary', - primaryAlt: 'primaryAlt', - secondary: 'secondary', - secondaryAlt: 'secondaryAlt', - default: 'default', - link: 'link', - }, - 'secondary' - )} - size={select( - 'size', - { - xl: 'xl', - lgxl: 'lgxl', - lg: 'lg', - md: 'md', - sm: 'sm', - xs: 'xs', - }, - 'lg' - )} - isBorderless={boolean('isBorderless', true)} - withCaret={boolean('withCaret', false)} - /> + disabled={boolean('disabled', false)} + filled={boolean('filled', false)} + seamless={boolean('seamless', false)} + theme={select('theme', THEMES, THEMES[0])} + size={select('size', SIZES, SIZES[0])} + > + {text('text', '')} + </IconButton> </Story> </Canvas> @@ -72,3 +46,47 @@ Actionable `Button` with an `Icon`. ### Props <ArgsTable of={IconButton} /> + +### Stories + +#### with avatar + +<Canvas> + <Story name="with-avatar"> + <IconButton + icon={text('icon', 'search')} + disabled={boolean('disabled', false)} + filled={boolean('filled', false)} + seamless={boolean('seamless', false)} + theme={select('theme', THEMES, THEMES[0])} + size={select('size', SIZES, SIZES[0])} + avatarProps={{ + image: AvatarSpec.generate().image, + initials: 'PB', + }} + > + {text('text', '')} + </IconButton> + </Story> +</Canvas> + +#### with avatar & initials + +<Canvas> + <Story name="with-avatar-initials"> + <IconButton + icon={text('icon', 'search')} + disabled={boolean('disabled', false)} + filled={boolean('filled', false)} + seamless={boolean('seamless', false)} + theme={select('theme', THEMES, THEMES[0])} + size={select('size', SIZES, SIZES[0])} + avatarProps={{ + image: null, + initials: 'PB', + }} + > + {text('text', '')} + </IconButton> + </Story> +</Canvas> diff --git a/src/components/IconButton/IconButton.test.js b/src/components/IconButton/IconButton.test.js index 52e37d26a..407fb3cac 100644 --- a/src/components/IconButton/IconButton.test.js +++ b/src/components/IconButton/IconButton.test.js @@ -76,11 +76,11 @@ describe('Button', () => { }) test('Renders a button with various kinds', () => { - const { getByRole, rerender } = render(<IconButton kind="primary" />) + const { getByRole, rerender } = render(<IconButton theme="blue" />) expect(getByRole('button').classList.contains('is-primary')).toBeTruthy() - rerender(<IconButton kind="secondary" />) + rerender(<IconButton theme="grey" outlined />) expect(getByRole('button').classList.contains('is-secondary')).toBeTruthy() }) @@ -146,7 +146,7 @@ describe('Styles', () => { test('Can render with border styles', () => { const { container } = render( - <IconButton kind="secondary" isBorderless={false} /> + <IconButton theme="grey" outlined isBorderless={false} /> ) expect( @@ -160,18 +160,16 @@ describe('Styles', () => { const borderRadius = window.getComputedStyle( container.querySelector('.c-IconButton') ).borderRadius - const value = borderRadius && parseInt(borderRadius, 10) - expect(value).toBeGreaterThan(100) + expect(borderRadius).toBe('100%') }) - test('Shape can be set to not render a circle', () => { + test.only('Shape can be set to not render a circle', () => { const { container } = render(<IconButton shape="default" />) - const borderRadius = window.getComputedStyle( - container.querySelector('.c-IconButton') - ).borderRadius - const value = borderRadius && parseInt(borderRadius, 10) - - expect(value).toBeLessThan(100) + expect( + window + .getComputedStyle(container.querySelector('.c-IconButton')) + .getPropertyValue('--buttonRadius') + ).toBe('3px') }) }) diff --git a/src/components/Input/Input.stories.mdx b/src/components/Input/Input.stories.mdx index 2b9de7e4e..686e3b05e 100644 --- a/src/components/Input/Input.stories.mdx +++ b/src/components/Input/Input.stories.mdx @@ -122,12 +122,12 @@ The `Button` should be one step smaller compared to the `Input` (If the `Input` action={ <Flexy gap="xs"> <Flexy.Item> - <Button kind="primary" size="sm"> + <Button theme="blue" size="sm"> Apply </Button> </Flexy.Item> <Flexy.Item> - <Button kind="secondary" size="sm"> + <Button theme="grey" outlined size="sm"> Cancel </Button> </Flexy.Item> diff --git a/src/components/Link/Link.jsx b/src/components/Link/Link.jsx index daf7ef579..d49452000 100644 --- a/src/components/Link/Link.jsx +++ b/src/components/Link/Link.jsx @@ -1,86 +1,79 @@ -import React from 'react' +import React, { forwardRef } from 'react' import PropTypes from 'prop-types' import getValidProps from '@helpscout/react-utils/dist/getValidProps' -import RouteWrapper from '../RouteWrapper' import classNames from 'classnames' import { noop } from '../../utilities/other' import { wordHasSpaces } from '../../utilities/strings' import { LinkUI } from './Link.css' +import { Link as ReactRouterLink } from 'react-router-dom' -export class Link extends React.PureComponent { - getHref() { - const { href, voidOnClick } = this.props +const WrappedLink = forwardRef(function Link(props, ref) { + const { + autoWordWrap, + block, + children, + className, + external, + target, + nodeRef, + noUnderline, + wordWrap, + href, + voidOnClick, + to, + ...rest + } = props - if (voidOnClick) { - // eslint-disable-next-line no-script-url - return 'javascript:void(0);' - } + const forceWordWrap = wordWrap || (autoWordWrap && !wordHasSpaces(children)) + const componentClassName = classNames( + 'c-Link', + block && 'is-block', + forceWordWrap && 'is-word-wrap', + noUnderline && 'is-no-underline', + className + ) + const isTargetExternal = (target && target === '_blank') || external + const linkTarget = target || external ? '_blank' : undefined + const rel = isTargetExternal ? 'noopener noreferrer' : undefined - return href - } + // eslint-disable-next-line no-script-url + const linkHref = voidOnClick ? 'javascript:void(0);' : href - render() { - const { - autoWordWrap, - block, - children, - className, - external, - target, - nodeRef, - noUnderline, - wordWrap, - ...rest - } = this.props - const forceWordWrap = wordWrap || (autoWordWrap && !wordHasSpaces(children)) - const componentClassName = classNames( - 'c-Link', - block && 'is-block', - forceWordWrap && 'is-word-wrap', - noUnderline && 'is-no-underline', - className - ) - const isTargetExternal = (target && target === '_blank') || external - const linkTarget = target || external ? '_blank' : undefined - const rel = isTargetExternal ? 'noopener noreferrer' : undefined + return ( + <LinkUI + {...getValidProps(rest)} + className={componentClassName} + target={linkTarget} + rel={rel} + ref={ref || nodeRef} + href={linkHref} + to={to} + as={to ? ReactRouterLink : 'a'} + > + {children} + </LinkUI> + ) +}) - return ( - <LinkUI - {...getValidProps(rest)} - className={componentClassName} - target={linkTarget} - rel={rel} - ref={nodeRef} - href={this.getHref()} - > - {children} - </LinkUI> - ) - } -} - -Link.defaultProps = { +WrappedLink.defaultProps = { autoWordWrap: true, block: false, 'data-cy': 'Link', external: false, href: '#', - nodeRef: noop, onBlur: noop, onClick: noop, onFocus: noop, voidOnClick: false, } -Link.propTypes = { +WrappedLink.propTypes = { /** Custom class names to be added to the component. */ className: PropTypes.string, /** Opens link in a new tab. */ external: PropTypes.bool, /** Address for the link. Default is `#`. */ href: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - /** Callback function to retrieve the component's DOM node. */ - nodeRef: PropTypes.func, /** Callback function when the component is blurred. */ onBlur: PropTypes.func, /** Callback function when the component is clicked. */ @@ -101,4 +94,4 @@ Link.propTypes = { 'data-cy': PropTypes.string, } -export default RouteWrapper(Link) +export default WrappedLink diff --git a/src/components/Link/Link.test.js b/src/components/Link/Link.test.js index c3dd5f612..3ab797533 100644 --- a/src/components/Link/Link.test.js +++ b/src/components/Link/Link.test.js @@ -1,6 +1,6 @@ import React from 'react' import { mount, shallow } from 'enzyme' -import RouteLink, { Link } from './Link' +import Link from './Link' import { Router } from 'react-router' // Since we now wrap Link in a HOC, we have to use `.first.shallow()` to test. @@ -71,7 +71,7 @@ describe('External', () => { }) }) -describe('RouteWrapper', () => { +describe.skip('RouteWrapper', () => { let push let history let preventDefault @@ -88,9 +88,9 @@ describe('RouteWrapper', () => { const route = '/some/route/' const wrapper = mount( <Router history={history}> - <RouteLink href="/gator" to={route}> + <Link href="/gator" to={route}> Gator - </RouteLink> + </Link> </Router> ) wrapper.find('a').simulate('click', clickEvent) @@ -103,9 +103,9 @@ describe('RouteWrapper', () => { const route = '/some/route/' const wrapper = mount( <Router history={history}> - <RouteLink href="/gator" to={route}> + <Link href="/gator" to={route}> Gator - </RouteLink> + </Link> </Router> ) clickEvent.ctrlKey = true @@ -119,9 +119,9 @@ describe('RouteWrapper', () => { const route = '/some/route/' const wrapper = mount( <Router history={history}> - <RouteLink href="/gator" to={route}> + <Link href="/gator" to={route}> Gator - </RouteLink> + </Link> </Router> ) clickEvent.metaKey = true @@ -136,9 +136,9 @@ describe('RouteWrapper', () => { const to = 'some/route' const wrapper = mount( <Router history={history}> - <RouteLink fetch={fetch} to={to}> + <Link fetch={fetch} to={to}> Gator - </RouteLink> + </Link> </Router> ) wrapper.find('a').simulate('click', clickEvent) diff --git a/src/components/MessageCard/MessageCard.Button.jsx b/src/components/MessageCard/MessageCard.Button.jsx index 5ea176ad8..f43a34345 100644 --- a/src/components/MessageCard/MessageCard.Button.jsx +++ b/src/components/MessageCard/MessageCard.Button.jsx @@ -9,7 +9,7 @@ export class MessageCardButton extends React.PureComponent { const { children, ...rest } = this.props return ( - <ActionButtonUI {...rest}> + <ActionButtonUI theme="blue" {...rest}> <Truncate>{children}</Truncate> </ActionButtonUI> ) @@ -18,17 +18,13 @@ export class MessageCardButton extends React.PureComponent { MessageCardButton.defaultProps = { 'data-cy': 'beacon-message-cta', - kind: 'primary', onClick: noop, - isBlock: true, - size: 'xl', + size: 'xxl', } MessageCardButton.propTypes = { /** Data attr for Cypress tests. */ 'data-cy': PropTypes.string, - kind: PropTypes.string, - isBlock: PropTypes.bool, onClick: PropTypes.func, size: PropTypes.string, } diff --git a/src/components/MessageCard/MessageCard.css.js b/src/components/MessageCard/MessageCard.css.js index e5c9e2384..741faaeec 100644 --- a/src/components/MessageCard/MessageCard.css.js +++ b/src/components/MessageCard/MessageCard.css.js @@ -8,6 +8,7 @@ import Button from '../Button' import Heading from '../Heading' import Image from '../Image' import ArticleCard from '../ArticleCard' +import { messageVariableClassName } from './utils/MessageCard.utils' export const MAX_IMAGE_SIZE = 258 @@ -275,6 +276,25 @@ export const BodyUI = styled.div` s { text-decoration: line-through; } + + span.${messageVariableClassName} { + display: inline-flex; + align-items: center; + padding: 3px 8px; + margin-right: 4px; + height: 20px; + line-height: 17px; + + color: ${getColor('purple.800')}; + background-color: ${getColor('purple.200')}; + border-radius: 100px; + + // clearing any text style coming from b or i elements, as we want to have it always display the same + font-style: normal; + font-weight: normal; + text-decoration: none; + white-space: nowrap; + } ` export const ActionUI = styled('div')` @@ -286,8 +306,8 @@ export const ActionUI = styled('div')` export const ActionButtonUI = styled(Button)` ${setFontSize(14)}; font-family: ${FONT_FAMILY}; - height: 54px !important; line-height: normal !important; + width: 100%; ` export const ImageUI = styled(Image)` diff --git a/src/components/MessageCard/MessageCard.jsx b/src/components/MessageCard/MessageCard.jsx index dca6ccc62..899817853 100644 --- a/src/components/MessageCard/MessageCard.jsx +++ b/src/components/MessageCard/MessageCard.jsx @@ -35,6 +35,7 @@ export const MessageCard = React.memo( align, isMobile, isWithBoxShadow, + variables = [], ...rest }, ref @@ -96,7 +97,11 @@ export const MessageCard = React.memo( withMargin={!!(title || subtitle)} render={!!(body || image || children)} > - <MessageCardBody body={body} onClick={onBodyClick} /> + <MessageCardBody + body={body} + onClick={onBodyClick} + variables={variables} + /> <MessageCardImage image={image} onLoad={makeMessageVisible} /> {children} </MessageCardContent> @@ -165,6 +170,13 @@ MessageCard.propTypes = { onShow: PropTypes.func, /** Enable animations when showing the Message. */ withAnimation: PropTypes.bool, + /** List of variables that can be highlighted inside Message. */ + variables: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + display: PropTypes.string, + }) + ), } MessageCard.className = 'c-MessageCard' diff --git a/src/components/MessageCard/MessageCard.stories.mdx b/src/components/MessageCard/MessageCard.stories.mdx index 9073e7bf5..807b8421a 100644 --- a/src/components/MessageCard/MessageCard.stories.mdx +++ b/src/components/MessageCard/MessageCard.stories.mdx @@ -144,6 +144,26 @@ This component renders a Message Card Notification with (optional) Title, Subtit </Story> </Canvas> +# With variables + +<Canvas> + <Story name="With variables"> + <MessageCard + subtitle={text('Subtitle', 'The J&G Team is here')} + title={text('Title', 'Need help?')} + body={text( + 'Body', + '<p>Hi {%customer.firstName,fallback=there%}!</p> <p>This <i>sentence</i> has five <u>words</u>. </p> <p>See you at our <b>amazing show with {%coordinator,fallback=Coordinator%}</b>!</p> <p>It would happen at {%date%}</p> <p>And this is non-existing variable {%nonExisting,fallback=no%}</p>' + )} + variables={[ + { id: 'customer.firstName', display: 'First Name' }, + { id: 'coordinator', display: 'Person' }, + { id: 'date', display: 'Date' }, + ]} + /> + </Story> +</Canvas> + #### Reference - **Designer**: Buzz diff --git a/src/components/MessageCard/MessageCard.test.js b/src/components/MessageCard/MessageCard.test.js index 142e8e86c..04b7849fa 100644 --- a/src/components/MessageCard/MessageCard.test.js +++ b/src/components/MessageCard/MessageCard.test.js @@ -1,62 +1,49 @@ import React from 'react' -import { mount, render } from 'enzyme' +import { render, act, screen, fireEvent } from '@testing-library/react' import MessageCard from './MessageCard' -import { - TitleUI, - SubtitleUI, - BodyUI, - ActionUI, - ImageUI, -} from './MessageCard.css' import { MessageCardButton as Button } from './MessageCard.Button' -import { act } from 'react-dom/test-utils' +import userEvent from '@testing-library/user-event' describe('className', () => { test('Has default className', () => { - const wrapper = render(<MessageCard />) - const el = wrapper.find('.c-MessageCard') + const { container } = render(<MessageCard />) - expect(el.length).toBeTruthy() + expect(messageCard(container)).toBeInTheDocument() }) test('Can render custom className', () => { const customClassName = 'blue' - const wrapper = render(<MessageCard className={customClassName} />) - const el = wrapper.find('.c-MessageCard') + const { container } = render(<MessageCard className={customClassName} />) - expect(el.hasClass(customClassName)).toBeTruthy() + expect(messageCard(container)).toHaveClass(customClassName) }) }) describe('Mobile', () => { test('Should not have mobile styles by default', () => { - const wrapper = mount(<MessageCard />) - const el = wrapper.find('div.c-MessageCard') + const { container } = render(<MessageCard />) - expect(el.getDOMNode().classList.contains('is-mobile')).toBeFalsy() + expect(messageCard(container)).not.toHaveClass('is-mobile') }) test('Should have mobile styles if specified', () => { - const wrapper = mount(<MessageCard isMobile />) - const el = wrapper.find('div.c-MessageCard') + const { container } = render(<MessageCard isMobile />) - expect(el.getDOMNode().classList.contains('is-mobile')).toBeTruthy() + expect(messageCard(container)).toHaveClass('is-mobile') }) }) describe('Align', () => { test('Has default alignment of right', () => { - const wrapper = mount(<MessageCard />) - const el = wrapper.find('div.c-MessageCard') + const { container } = render(<MessageCard />) - expect(el.getDOMNode().classList.contains('is-align-right')).toBeTruthy() + expect(messageCard(container)).toHaveClass('is-align-right') }) test('Can change alignment styles, if specified', () => { - const wrapper = mount(<MessageCard align="left" />) - const el = wrapper.find('div.c-MessageCard') + const { container } = render(<MessageCard align="left" />) - expect(el.getDOMNode().classList.contains('is-align-left')).toBeTruthy() + expect(messageCard(container)).toHaveClass('is-align-left') }) }) @@ -65,201 +52,230 @@ describe('Visibility', () => { test('Should be visible by default if there is no image', () => { const onShowSpy = jest.fn() - const wrapper = mount(<MessageCard onShow={onShowSpy} />) + const { container } = render(<MessageCard onShow={onShowSpy} />) - expect(cardWrapperVisible(wrapper)).toEqual(false) + expect(cardWrapper(container)).not.toBeVisible() expect(onShowSpy).not.toHaveBeenCalled() act(() => { jest.runAllTimers() - wrapper.update() }) - expect(cardWrapperVisible(wrapper)).toEqual(true) + expect(cardWrapper(container)).toBeVisible() expect(onShowSpy).toHaveBeenCalled() }) test('Should not be visible by default if there is an image, but become visible when image loads', () => { const onShowSpy = jest.fn() - const wrapper = mount( + const { container } = render( <MessageCard image={{ url: 'https://path.to/image.png' }} onShow={onShowSpy} /> ) - expect(cardWrapperVisible(wrapper)).toEqual(false) + expect(cardWrapper(container)).not.toBeVisible() expect(onShowSpy).not.toHaveBeenCalled() - jest.runAllTimers() - wrapper.update() + act(() => { + jest.runAllTimers() + }) - expect(cardWrapperVisible(wrapper)).toEqual(false) + expect(cardWrapper(container)).not.toBeVisible() expect(onShowSpy).not.toHaveBeenCalled() - wrapper.find('img').simulate('load') + fireEvent.load(screen.getByRole('img')) act(() => { jest.runAllTimers() - wrapper.update() }) - expect(wrapper.find('img')).toHaveLength(1) - expect(cardWrapperVisible(wrapper)).toEqual(true) + expect(screen.getByRole('img')).toBeInTheDocument() + expect(cardWrapper(container)).toBeVisible() expect(onShowSpy).toHaveBeenCalled() }) test('Should become visible without image if image fails to load', () => { const onShowSpy = jest.fn() - const wrapper = mount( + const { container } = render( <MessageCard image={{ url: 'https://path.to/image.png' }} onShow={onShowSpy} /> ) - expect(cardWrapperVisible(wrapper)).toEqual(false) + expect(cardWrapper(container)).not.toBeVisible() expect(onShowSpy).not.toHaveBeenCalled() - jest.runAllTimers() - wrapper.update() + act(() => { + jest.runAllTimers() + }) - expect(cardWrapperVisible(wrapper)).toEqual(false) + expect(cardWrapper(container)).not.toBeVisible() expect(onShowSpy).not.toHaveBeenCalled() - wrapper.find('img').simulate('error') + fireEvent.error(screen.getByRole('img')) act(() => { jest.runAllTimers() - wrapper.update() }) - expect(wrapper.find('img')).toHaveLength(0) - expect(cardWrapperVisible(wrapper)).toEqual(true) + expect(screen.queryByRole('img')).not.toBeInTheDocument() + expect(cardWrapper(container)).toBeVisible() expect(onShowSpy).toHaveBeenCalled() }) - - function cardWrapperVisible(wrapper) { - return wrapper.find('.c-MessageCardWrapper').at(0).prop('visible') - } }) describe('Animation', () => { test('Should have no animation by default', () => { - const wrapper = mount(<MessageCard />) + const { container } = render(<MessageCard />) - expect(cardWrapperAnimation(wrapper)).toEqual(false) + expect(cardWrapper(container)).toHaveStyle({ transition: 'none' }) }) test('Should have animation if withAnimation is true', () => { - const wrapper = mount(<MessageCard withAnimation />) + const { container } = render(<MessageCard withAnimation />) - expect(cardWrapperAnimation(wrapper)).toEqual(true) + expect(cardWrapper(container)).toHaveStyle({ + transition: 'all 300ms ease-in-out', + }) }) - - function cardWrapperAnimation(wrapper) { - return wrapper.find('.c-MessageCardWrapper').at(0).prop('withAnimation') - } }) describe('Body', () => { test('Does not render body if is not passed down as a prop', () => { - const wrapper = mount(<MessageCard />) - const o = wrapper.find(BodyUI) + const { container } = render(<MessageCard />) - expect(o.length).toBe(0) + expect(messageBody(container)).not.toBeInTheDocument() }) test('Renders body if it is passed down as a prop', () => { - const wrapper = mount(<MessageCard body="Santa!" />) - const o = wrapper.find(BodyUI) + const { container } = render(<MessageCard body="Santa!" />) - expect(o.length).toBe(1) - expect(o.html()).toContain('Santa!') + expect(messageBody(container)).toHaveTextContent('Santa!') }) test('Renders html in body', () => { - const wrapper = mount(<MessageCard body="<span>Santa!</span>" />) - const o = wrapper.find(BodyUI) + const { container } = render(<MessageCard body="<span>Santa!</span>" />) - expect(o.render().find('span').length).toBe(1) + expect( + container.querySelector('[data-cy="beacon-message-body-content"] span') + ).toHaveTextContent('Santa!') }) test('Renders new line without html in body', () => { const body = 'this is a new line\nwith another line' - const wrapper = mount(<MessageCard body={body} />) - const o = wrapper.find(BodyUI) + const { container } = render(<MessageCard body={body} />) - expect(o.render().find('br').length).toBe(1) + expect( + container.querySelector('[data-cy="beacon-message-body-content"] br') + ).toBeInTheDocument() }) test('Accepts a custom onBodyClick callback', () => { const body = 'some text with a <a href="#">link</a> in it' const callback = jest.fn() - const wrapper = mount(<MessageCard body={body} onBodyClick={callback} />) + const { container } = render( + <MessageCard body={body} onBodyClick={callback} /> + ) - wrapper.simulate('click') + userEvent.click(messageCard(container)) expect(callback).not.toHaveBeenCalled() - wrapper.find(BodyUI).simulate('click') + userEvent.click(messageBody(container)) expect(callback).toHaveBeenCalled() }) }) +describe('Body variables', () => { + const variables = [ + { + id: 'customer.firstName', + display: 'First Name', + }, + ] + + it('should replace existing variables in body text', () => { + const body = `<p>Hi {%customer.firstName,fallback=there%}</p>` + + render(<MessageCard body={body} variables={variables} />) + + expect(screen.getByText('there')).toBeInTheDocument() + }) + + it('should replace existing variables in body text, when also having new line character', () => { + const body = `Hi\n{%customer.firstName,fallback=there%}` + + render(<MessageCard body={body} variables={variables} />) + + expect(screen.getByText('there')).toBeInTheDocument() + }) + + it('should NOT replace variable if none provided', () => { + const body = `<p>Hi {%customer.firstName,fallback=there%}</p>` + + render(<MessageCard body={body} />) + + expect( + screen.getByText('Hi {%customer.firstName,fallback=there%}') + ).toBeInTheDocument() + }) +}) + describe('Title', () => { test('Does not render title if is not passed down as a prop', () => { - const wrapper = mount(<MessageCard />) - const o = wrapper.find(TitleUI) + const { container } = render(<MessageCard />) - expect(o.length).toBe(0) + expect(messageTitle(container)).not.toBeInTheDocument() }) test('Renders title if it is passed down as a prop', () => { - const wrapper = mount(<MessageCard title="Santa!" />) - const o = wrapper.find(TitleUI) + const { container } = render(<MessageCard title="Santa!" />) - expect(o.length).toBe(1) - expect(o.html()).toContain('Santa!') + expect(messageTitle(container)).toHaveTextContent('Santa!') }) + + function messageTitle(container) { + return container.querySelector('[data-cy="beacon-message-title"]') + } }) describe('Subtitle', () => { test('Does not render subtitle if is not passed down as a prop', () => { - const wrapper = mount(<MessageCard />) - const o = wrapper.find(SubtitleUI) + const { container } = render(<MessageCard />) - expect(o.length).toBe(0) + expect(messageSubtitle(container)).not.toBeInTheDocument() }) test('Renders subtitle if it is passed down as a prop', () => { - const wrapper = mount(<MessageCard subtitle="Santa!" />) - const o = wrapper.find(SubtitleUI) + const { container } = render(<MessageCard subtitle="Santa!" />) - expect(o.length).toBe(1) - expect(o.html()).toContain('Santa!') + expect(messageSubtitle(container)).toHaveTextContent('Santa!') }) + + function messageSubtitle(container) { + return container.querySelector('[data-cy="beacon-message-subtitle"]') + } }) describe('image', () => { test('Does not render image if is not passed down as a prop', () => { - const wrapper = mount(<MessageCard />) - const image = wrapper.find('img') + render(<MessageCard />) - expect(image).toHaveLength(0) + expect(screen.queryByRole('img')).not.toBeInTheDocument() }) test('Renders image if it is passed down as a prop', () => { - const wrapper = mount( - <MessageCard image={{ url: 'https://path.to/image.png' }} /> - ) - const image = wrapper.find('img') + render(<MessageCard image={{ url: 'https://path.to/image.png' }} />) - expect(image).toHaveLength(1) - expect(image.prop('src')).toEqual('https://path.to/image.png') + expect(screen.getByRole('img')).toHaveAttribute( + 'src', + 'https://path.to/image.png' + ) }) test('Sets size of image when provided', () => { - const wrapper = mount( + render( <MessageCard image={{ url: 'https://path.to/image.png', @@ -268,14 +284,13 @@ describe('image', () => { }} /> ) - const image = wrapper.find(ImageUI) - expect(image.prop('width')).toEqual('100px') - expect(image.prop('height')).toEqual('200px') + expect(screen.getByRole('img')).toHaveStyle({ width: '100px' }) + expect(screen.getByRole('img')).toHaveStyle({ height: '200px' }) }) test('Scales size of image when larger than fits and width is bigger', () => { - const wrapper = mount( + render( <MessageCard image={{ url: 'https://path.to/image.png', @@ -284,14 +299,13 @@ describe('image', () => { }} /> ) - const image = wrapper.find(ImageUI) - expect(image.prop('height')).toEqual('96.75px') - expect(image.prop('width')).toEqual('258px') + expect(screen.getByRole('img')).toHaveStyle({ width: '258px' }) + expect(screen.getByRole('img')).toHaveStyle({ height: '96.75px' }) }) test('Scales size of image when larger than fits and height is bigger', () => { - const wrapper = mount( + render( <MessageCard image={{ url: 'https://path.to/image.png', @@ -300,114 +314,114 @@ describe('image', () => { }} /> ) - const image = wrapper.find(ImageUI) - expect(image.prop('height')).toEqual('258px') - expect(image.prop('width')).toEqual('96.75px') + expect(screen.getByRole('img')).toHaveStyle({ width: '96.75px' }) + expect(screen.getByRole('img')).toHaveStyle({ height: '258px' }) }) test('Sets default size of image when not provided', () => { - const wrapper = mount( - <MessageCard image={{ url: 'https://path.to/image.png' }} /> - ) - const image = wrapper.find(ImageUI) + render(<MessageCard image={{ url: 'https://path.to/image.png' }} />) - expect(image.prop('width')).toEqual('100%') - expect(image.prop('height')).toEqual('auto') + expect(screen.getByRole('img')).toHaveStyle({ width: '100%' }) + expect(screen.getByRole('img')).toHaveStyle({ height: 'auto' }) }) test('Sets provided alt text', () => { - const wrapper = mount( + render( <MessageCard image={{ url: 'https://path.to/image.png', altText: 'Alt text' }} /> ) - const image = wrapper.find('img') - expect(image.prop('alt')).toEqual('Alt text') + expect(screen.getByRole('img')).toHaveAttribute('alt', 'Alt text') }) test('Sets default alt text', () => { - const wrapper = mount( - <MessageCard image={{ url: 'https://path.to/image.png' }} /> - ) - const image = wrapper.find('img') + render(<MessageCard image={{ url: 'https://path.to/image.png' }} />) - expect(image.prop('alt')).toEqual('Message image') + expect(screen.getByRole('img')).toHaveAttribute('alt', 'Message image') }) }) describe('Action', () => { test('Does not render action if is not passed down as a prop', () => { - const wrapper = mount(<MessageCard />) - const o = wrapper.find(ActionUI) + const { container } = render(<MessageCard />) - expect(o.length).toBe(0) + expect( + container.querySelector('[data-cy="beacon-message-cta-wrapper"]') + ).not.toBeInTheDocument() }) test('Renders action if it is passed down as a prop', () => { const action = () => <div>Click here</div> - const wrapper = mount(<MessageCard action={action} />) - const o = wrapper.find(ActionUI) + const { container } = render(<MessageCard action={action} />) - expect(o.length).toBe(1) - expect(o.html()).toContain('Click here') + expect( + container.querySelector('[data-cy="beacon-message-cta-wrapper"]') + ).toHaveTextContent('Click here') }) test('Should remove the box shadow', () => { - const wrapper = mount(<MessageCard isWithBoxShadow={false} />) - const el = wrapper.find('div.c-MessageCard') + const { container } = render(<MessageCard isWithBoxShadow={false} />) - expect(el.getDOMNode().classList.contains('is-with-box-shadow')).toBeFalsy() + expect(messageCard(container)).not.toHaveClass('is-with-box-shadow') }) }) describe('UrlAttachmentImage', () => { test('Does not render if url not provided', () => { - const wrapper = mount(<MessageCard.UrlAttachmentImage />) - const image = wrapper.find('img') + render(<MessageCard.UrlAttachmentImage />) - expect(image.length).toBe(0) + expect(screen.queryByRole('img')).not.toBeInTheDocument() }) test('Renders image when url provided', () => { - const wrapper = mount( - <MessageCard.UrlAttachmentImage url="https://example.com" /> - ) - const image = wrapper.find('img') + render(<MessageCard.UrlAttachmentImage url="https://example.com" />) - expect(image.length).toBe(1) - expect(image.prop('src')).toEqual('https://example.com') + expect(screen.getByRole('img')).toHaveAttribute( + 'src', + 'https://example.com' + ) }) test('Allows to provide alt text', () => { - const wrapper = mount( + render( <MessageCard.UrlAttachmentImage url="https://example.com" altText="My alt text" /> ) - const image = wrapper.find('img') - expect(image.prop('alt')).toEqual('My alt text') + expect(screen.getByRole('img')).toHaveAttribute('alt', 'My alt text') }) }) -describe('Message Button Children', () => { +describe('Message Button', () => { test('Can render children', () => { const children = 'Hello world' - const wrapper = mount(<Button>{children}</Button>) + render(<Button>{children}</Button>) - expect(wrapper.html()).toContain(children) + expect(screen.getByRole('button', { name: children })).toBeInTheDocument() }) -}) -describe('Message Button onClick', () => { test('Can accept custom onClick callback', () => { const callback = jest.fn() - const wrapper = mount(<Button onClick={callback}>Click Me</Button>) - wrapper.simulate('click') + render(<Button onClick={callback}>Click Me</Button>) + + userEvent.click(screen.getByRole('button')) expect(callback).toHaveBeenCalled() }) }) + +function messageCard(container) { + return container.querySelector('.c-MessageCard') +} + +function cardWrapper(container) { + return container.querySelector('.c-MessageCardWrapper') +} + +function messageBody(container) { + return container.querySelector('[data-cy="beacon-message-body-content"]') +} diff --git a/src/components/MessageCard/components/MessageCard.Body.jsx b/src/components/MessageCard/components/MessageCard.Body.jsx index bb2acf842..065ada8a2 100644 --- a/src/components/MessageCard/components/MessageCard.Body.jsx +++ b/src/components/MessageCard/components/MessageCard.Body.jsx @@ -2,8 +2,9 @@ import { BodyUI } from '../MessageCard.css' import React from 'react' import PropTypes from 'prop-types' import { noop } from '../../../utilities/other' +import { replaceMessageVariables } from '../utils/MessageCard.utils' -export const MessageCardBody = ({ withMargin, body, onClick }) => { +export const MessageCardBody = ({ body, onClick, variables }) => { const getBodyToRender = () => { // if there is no html in the string, transform new line to paragraph if (body && !/<\/?[a-z][\s\S]*>/i.test(body)) { @@ -12,14 +13,10 @@ export const MessageCardBody = ({ withMargin, body, onClick }) => { return body } - const bodyToRender = getBodyToRender() + const bodyToRender = replaceMessageVariables(getBodyToRender(), variables) return bodyToRender ? ( - <BodyUI - onClick={onClick} - withMargin={withMargin} - data-cy="beacon-message-body-content" - > + <BodyUI onClick={onClick} data-cy="beacon-message-body-content"> <div dangerouslySetInnerHTML={{ __html: bodyToRender }} /> </BodyUI> ) : null @@ -28,10 +25,15 @@ export const MessageCardBody = ({ withMargin, body, onClick }) => { MessageCardBody.propTypes = { /** Body content */ body: PropTypes.string, - /** Indicate if should add margin above the body */ - withMargin: PropTypes.string, /** Callback when body clicked */ onClick: PropTypes.func, + /** List of variables that can be highlighted inside Body */ + variables: PropTypes.arrayOf( + PropTypes.shape({ + id: PropTypes.string.isRequired, + display: PropTypes.string, + }) + ), } MessageCardBody.defaultProps = { diff --git a/src/components/MessageCard/utils/MessageCard.utils.js b/src/components/MessageCard/utils/MessageCard.utils.js new file mode 100644 index 000000000..51bf6e001 --- /dev/null +++ b/src/components/MessageCard/utils/MessageCard.utils.js @@ -0,0 +1,35 @@ +export const messageVariableClassName = 'hsds-message-card-variable' + +/** + * This function replaces all occurrences of variables in provided string with human friendly text + * They would be replaced with fallback text, if present. If no fallback text, label from variable would be used + * Only variables provided in parameter would be replaced, others are kept untouched in the raw version + * + * Variable raw text is replaced with <span class="hsds-message-card-variable"> element. + * + * @param text {String} text to replace variables within (if any) + * @param variables {Array<{id: String, display: String}>} list of variables to replace + */ +export const replaceMessageVariables = (text = '', variables = []) => { + if (variables.length === 0) { + return text + } + + // Regex to match the following cases: + // 1) {%variableName,fallback=Fallback%} + // 2) {%variableName,fallback=%} + // 3) {%variableName%} + // There are 3 groups, from left: variable name, fallback presence (optional), fallback value (optional) + const regex = /{%([^,%]+)(,fallback=([^%]*)?)?%}/g + + const replacer = (match, variableId, fallbackConfig, fallback) => { + const variable = variables.find(variable => variable.id === variableId) + if (variable) { + const variableText = !fallback ? variable.display : fallback + return `<span class="${messageVariableClassName}">${variableText}</span>` + } + return match + } + + return text.replace(regex, replacer) +} diff --git a/src/components/MessageCard/utils/MessageCard.utils.test.js b/src/components/MessageCard/utils/MessageCard.utils.test.js new file mode 100644 index 000000000..31696ecc7 --- /dev/null +++ b/src/components/MessageCard/utils/MessageCard.utils.test.js @@ -0,0 +1,56 @@ +import { replaceMessageVariables } from './MessageCard.utils' + +describe('MessageCard.utils', () => { + const variables = [ + { + id: 'customer.firstName', + display: 'First Name', + }, + { + id: 'customer.lastName', + display: 'Last Name', + }, + { + id: 'customVariable', + display: 'Custom Variable', + }, + ] + + it('should replace variables in provided text with fallback value', () => { + const text = `<p>Hi {%customer.firstName,fallback=there%} {%customer.lastName,fallback=you%}</p>` + + const result = replaceMessageVariables(text, variables) + + expect(result).toEqual( + `<p>Hi <span class="hsds-message-card-variable">there</span> <span class="hsds-message-card-variable">you</span></p>` + ) + }) + + it('should replace variables in provided text with variable label when it has not fallback', () => { + const text = `<p>Hi {%customer.firstName,fallback=there%} {%customVariable%}</p>` + + const result = replaceMessageVariables(text, variables) + + expect(result).toEqual( + `<p>Hi <span class="hsds-message-card-variable">there</span> <span class="hsds-message-card-variable">Custom Variable</span></p>` + ) + }) + + it('should replace variables in provided text with variable label when empty fallback', () => { + const text = `<p>Hi {%customer.firstName,fallback=%}</p>` + + const result = replaceMessageVariables(text, variables) + + expect(result).toEqual( + `<p>Hi <span class="hsds-message-card-variable">First Name</span></p>` + ) + }) + + it('should NOT replace unknown variable', () => { + const text = `<p>Hi {%customer.unknown,fallback=Test%}</p>` + + const result = replaceMessageVariables(text, variables) + + expect(result).toEqual(text) + }) +}) diff --git a/src/components/Modal/Modal.ActionFooter.css.js b/src/components/Modal/Modal.ActionFooter.css.js index 1c99f7418..03cdbe541 100644 --- a/src/components/Modal/Modal.ActionFooter.css.js +++ b/src/components/Modal/Modal.ActionFooter.css.js @@ -29,6 +29,10 @@ export const ActionFooterUI = styled(Toolbar)` export const CancelButtonUI = styled(Button)` margin-right: auto; + + &.is-theme-grey { + --buttonFontWeight: normal; + } ` export const SecondaryButtonUI = styled(Button)` diff --git a/src/components/Modal/Modal.ActionFooter.jsx b/src/components/Modal/Modal.ActionFooter.jsx index 7e9781853..ac0e4fa6e 100644 --- a/src/components/Modal/Modal.ActionFooter.jsx +++ b/src/components/Modal/Modal.ActionFooter.jsx @@ -11,6 +11,12 @@ import { SecondaryAlertButtonUI, SecondaryButtonUI, } from './Modal.ActionFooter.css' +import { + THEME_GREEN, + THEME_BLUE, + THEME_RED, + THEME_GREY, +} from '../Button/Button.utils' class ModalActionFooter extends React.PureComponent { handleCancel = e => { @@ -32,14 +38,22 @@ class ModalActionFooter extends React.PureComponent { } renderPrimaryButton() { - const { primaryButtonText, state, primaryButtonDisabled } = this.props + const { + primaryButtonText, + primaryButtonProps, + state, + primaryButtonDisabled, + } = this.props + let theme = THEME_BLUE + if (state === 'success') theme = THEME_GREEN + if (state === 'danger') theme = THEME_RED + return ( <PrimaryButtonUI - data-cy="PrimaryButton" - state={state} - kind="primary" size="lg" - version={2} + data-cy="PrimaryButton" + {...primaryButtonProps} + theme={theme} onClick={this.handlePrimaryAction} disabled={primaryButtonDisabled} > @@ -49,7 +63,12 @@ class ModalActionFooter extends React.PureComponent { } renderSecondaryButton() { - const { kind, secondaryButtonText, secondaryButtonDisabled } = this.props + const { + kind, + secondaryButtonText, + secondaryButtonProps, + secondaryButtonDisabled, + } = this.props if (!secondaryButtonText) { return null @@ -60,10 +79,11 @@ class ModalActionFooter extends React.PureComponent { return ( <ButtonComponent - data-cy="SecondaryButton" - kind="secondary" size="lg" - version={2} + theme="grey" + outlined + data-cy="SecondaryButton" + {...secondaryButtonProps} onClick={this.handleSecondaryAction} disabled={secondaryButtonDisabled} > @@ -73,7 +93,7 @@ class ModalActionFooter extends React.PureComponent { } renderCancelButton() { - const { cancelText, showDefaultCancel } = this.props + const { cancelText, cancelProps, showDefaultCancel } = this.props if (!(showDefaultCancel && cancelText)) { return null @@ -81,10 +101,12 @@ class ModalActionFooter extends React.PureComponent { return ( <CancelButtonUI + linked + size="lg" + theme={THEME_GREY} className="is-cancel" data-cy="CancelButton" - kind="default" - version={2} + {...cancelProps} onClick={this.handleCancel} > {cancelText} @@ -123,14 +145,17 @@ class ModalActionFooter extends React.PureComponent { ModalActionFooter.defaultProps = { cancelText: 'Cancel', + cancelProps: {}, 'data-cy': 'ModalActionFooter', kind: MODAL_KIND.DEFAULT, onCancel: noop, onPrimaryClick: noop, onSecondaryClick: noop, primaryButtonText: 'Save', + primaryButtonProps: {}, primaryButtonDisabled: false, secondaryButtonText: null, + secondaryButtonProps: {}, secondaryButtonDisabled: false, showDefaultCancel: true, state: '', @@ -139,6 +164,8 @@ ModalActionFooter.defaultProps = { ModalActionFooter.propTypes = { /** Text on cancel button */ cancelText: PropTypes.string, + /** Extra props on cancel button */ + cancelProps: PropTypes.object, /** The kind of version 2 Modal style to apply. */ kind: PropTypes.oneOf(['alert', 'default', 'branded', 'sequence']), /** Callback on cancel button click */ @@ -149,12 +176,16 @@ ModalActionFooter.propTypes = { onSecondaryClick: PropTypes.func, /** Text on primary button */ primaryButtonText: PropTypes.string, + /** Extra props on primary button */ + primaryButtonProps: PropTypes.object, /** Whether the button is disabled */ primaryButtonDisabled: PropTypes.bool, /** Text on secondary button */ secondaryButtonText: PropTypes.string, /** Whether the button is disabled */ secondaryButtonDisabled: PropTypes.bool, + /** Extra props on secondary button */ + secondaryButtonProps: PropTypes.object, /** Show cancel button */ showDefaultCancel: PropTypes.bool, /** State to use when styling a version 2 Modal (currently only `danger` state is custom styled). */ diff --git a/src/components/Modal/Modal.stories.mdx b/src/components/Modal/Modal.stories.mdx index 068541a66..11a53f6ad 100644 --- a/src/components/Modal/Modal.stories.mdx +++ b/src/components/Modal/Modal.stories.mdx @@ -70,6 +70,7 @@ There are two versions of the Modal component, version 2 is the new standard (ve secondaryButtonText={text('secondaryButtonText', 'Secondary')} secondaryButtonDisabled={boolean('secondaryButtonDisabled', false)} primaryButtonDisabled={boolean('primaryButtonDisabled', false)} + cancelProps={{ 'data-cy': 'NewValue' }} state={select( 'State', { diff --git a/src/components/Modal/__tests__/Modal.ActionFooter.test.js b/src/components/Modal/__tests__/Modal.ActionFooter.test.js index f0eb8fd1f..a5c84412a 100644 --- a/src/components/Modal/__tests__/Modal.ActionFooter.test.js +++ b/src/components/Modal/__tests__/Modal.ActionFooter.test.js @@ -86,7 +86,7 @@ describe('Buttons', () => { const wrapper = mount( <ActionFooter primaryButtonText={buttonText} onPrimaryClick={clickSpy} /> ) - const o = wrapper.find('.c-Button.is-primary').first() + const o = wrapper.find('.c-Button.is-theme-blue').first() expect(o.length).toBe(1) expect(o.text()).toBe(buttonText) @@ -98,7 +98,7 @@ describe('Buttons', () => { test('Renders Primary Button', () => { const buttonText = 'Primary' const wrapper = mount(<ActionFooter primaryButtonText={buttonText} />) - const o = wrapper.find('.c-Button.is-primary').first() + const o = wrapper.find('.c-Button.is-theme-blue').first() expect(o.length).toBe(1) expect(o.text()).toBe(buttonText) @@ -113,7 +113,7 @@ describe('Buttons', () => { onSecondaryClick={clickSpy} /> ) - const o = wrapper.find('.c-Button.is-secondary').first() + const o = wrapper.find('.c-Button.is-theme-grey.is-style-outlined').first() expect(o.length).toBe(1) expect(o.text()).toBe(buttonText) @@ -125,7 +125,7 @@ describe('Buttons', () => { test('Renders Secondary Button', () => { const buttonText = 'Secondary' const wrapper = mount(<ActionFooter secondaryButtonText={buttonText} />) - const o = wrapper.find('.c-Button.is-secondary').first() + const o = wrapper.find('.c-Button.is-theme-grey.is-style-outlined').first() expect(o.length).toBe(1) expect(o.text()).toBe(buttonText) @@ -141,7 +141,7 @@ describe('Buttons', () => { onSecondaryClick={clickSpy} /> ) - const o = wrapper.find('.c-Button.is-secondary').first() + const o = wrapper.find('.c-Button.is-theme-grey.is-style-outlined').first() expect(o.length).toBe(1) expect(o.text()).toBe(buttonText) @@ -156,7 +156,7 @@ describe('Buttons', () => { const wrapper = mount( <ActionFooter cancelText={buttonText} onCancel={clickSpy} /> ) - const o = wrapper.find('.c-Button.is-default').first() + const o = wrapper.find('.c-Button.is-style-link').first() expect(o.length).toBe(1) expect(o.text()).toBe(buttonText) @@ -168,7 +168,7 @@ describe('Buttons', () => { test('Renders default Cancel Button', () => { const buttonText = 'Nevermind' const wrapper = mount(<ActionFooter cancelText={buttonText} />) - const o = wrapper.find('.c-Button.is-default').first() + const o = wrapper.find('.c-Button.is-style-link').first() expect(o.length).toBe(1) expect(o.text()).toBe(buttonText) @@ -177,7 +177,7 @@ describe('Buttons', () => { test('Hides default Cancel Button if specified', () => { const wrapper = mount(<ActionFooter showDefaultCancel={false} />) - const o = wrapper.find('.c-Button.is-default').first() + const o = wrapper.find('.c-Button.is-style-link').first() expect(o.length).toBeFalsy() }) diff --git a/src/components/Nav/Nav.Item.jsx b/src/components/Nav/Nav.Item.jsx index c321e7670..da9895a8c 100644 --- a/src/components/Nav/Nav.Item.jsx +++ b/src/components/Nav/Nav.Item.jsx @@ -4,7 +4,6 @@ import getValidProps from '@helpscout/react-utils/dist/getValidProps' import Flexy from '../Flexy' import Icon from '../Icon' import Tooltip from '../Tooltip' -import NavLink from '../NavLink' import classNames from 'classnames' import { noop } from '../../utilities/other' import { @@ -16,6 +15,8 @@ import { ErrorWrapperUI, } from './Nav.css' +import { NavLink } from 'react-router-dom' + export class NavItem extends React.Component { static className = 'c-NavItem' static contentClassName = 'c-NavItemContent' @@ -69,23 +70,15 @@ export class NavItem extends React.Component { ) } - renderContent = ({ isActive }) => { + renderContent = () => { const { children } = this.props - const componentClassName = classNames( - NavItem.contentClassName, - isActive && 'is-active' - ) + const componentClassName = classNames(NavItem.contentClassName) return ( <ContentUI className={componentClassName}> <Flexy gap="xs"> <Flexy.Block> - <TitleUI - size="13" - lineHeightReset - isActive={isActive} - className="c-NavItemTitle" - > + <TitleUI size="13" lineHeightReset className="c-NavItemTitle"> {children} </TitleUI> <GhostTitleUI @@ -99,7 +92,7 @@ export class NavItem extends React.Component { </Flexy.Block> {this.renderError()} </Flexy> - <IndicatorUI className="c-NavItemIndicator" isActive={isActive} /> + <IndicatorUI className="c-NavItemIndicator" /> </ContentUI> ) } @@ -115,9 +108,12 @@ export class NavItem extends React.Component { > <NavLink {...this.getLinkProps()} + activeClassName="is-active" className="c-NavItemLink" - render={this.renderContent} - /> + isActive={this.props.isActive || null} + > + {this.renderContent()} + </NavLink> </ItemUI> ) } diff --git a/src/components/Nav/Nav.css.js b/src/components/Nav/Nav.css.js index 4e6208a2c..eb5aed11e 100644 --- a/src/components/Nav/Nav.css.js +++ b/src/components/Nav/Nav.css.js @@ -22,6 +22,23 @@ export const ListUI = styled('ul')` padding: 0; ` +export const TitleUI = styled(Text)` + display: block; + min-height: 14px; +` +export const IndicatorUI = styled('div')` + background: ${getColor('blue.500')}; + border-radius: 9999px; + bottom: -1px; + height: 2px; + opacity: 0; + left: 0; + right: 0; + position: absolute; + transition: ${config.indicatorTransition}; + will-change: opacity; +` + export const ItemUI = styled('li')` padding: 0; transform: translateZ(0); @@ -43,6 +60,13 @@ export const ItemUI = styled('li')` &.is-active { color: ${getColor('charcoal.500')}; + + ${TitleUI} { + font-weight: 500; + } + ${IndicatorUI} { + opacity: 1; + } } } ` @@ -53,17 +77,6 @@ export const ContentUI = styled('div')` text-align: center; ` -export const TitleUI = styled(Text)` - display: block; - min-height: 14px; - - ${({ isActive }) => - isActive && - ` - font-weight: 500; - `}; -` - export const GhostTitleUI = styled(Text)` display: block; font-weight: 500; @@ -77,22 +90,3 @@ export const ErrorWrapperUI = styled(Flexy.Item)` top: -10px; width: 20px; ` - -export const IndicatorUI = styled('div')` - background: ${getColor('blue.500')}; - border-radius: 9999px; - bottom: -1px; - height: 2px; - opacity: 0; - left: 0; - right: 0; - position: absolute; - transition: ${config.indicatorTransition}; - will-change: opacity; - - ${({ isActive }) => - isActive && - ` - opacity: 1; - `}; -` diff --git a/src/components/Nav/Nav.test.js b/src/components/Nav/Nav.test.js index 1ad57e874..5cc9b8414 100644 --- a/src/components/Nav/Nav.test.js +++ b/src/components/Nav/Nav.test.js @@ -2,7 +2,9 @@ import React from 'react' import { mount, render } from 'enzyme' import { Nav } from './Nav' import Item from './Nav.Item' +import { Link } from 'react-router-dom' import { MemoryRouter as Router } from 'react-router-dom' +import { NavItem } from './Nav.Item' const wrap = fn => Component => fn(<Router>{Component}</Router>) const renderWithRouter = wrap(render) @@ -39,14 +41,16 @@ describe('Nav Sub-components', () => { describe('Nav.Item className', () => { test('Has default className', () => { - const wrapper = renderWithRouter(<Item />) + const wrapper = renderWithRouter(<Item to="/" />) expect(wrapper.hasClass('c-NavItem')).toBeTruthy() }) test('Can render custom className', () => { const customClassName = 'blue' - const wrapper = renderWithRouter(<Item className={customClassName} />) + const wrapper = renderWithRouter( + <Item to="/" className={customClassName} /> + ) expect(wrapper.hasClass(customClassName)).toBeTruthy() }) @@ -54,35 +58,20 @@ describe('Nav.Item className', () => { describe('Nav.Item HTML props', () => { test('Can render default HTML props', () => { - const wrapper = renderWithRouter(<Item data-cy="blue" />) + const wrapper = renderWithRouter(<Item to="/" data-cy="blue" />) expect(wrapper.attr('data-cy')).toBe('blue') }) }) describe('Nav.Item Route/Link', () => { - test('Renders a Link', () => { - const wrapper = mountWithRouter(<Item />) - const el = wrapper.find('Link') - - expect(el.length).toBeTruthy() - }) - - test('Passes appropriate props to Route', () => { - const wrapper = mountWithRouter( - <Item exact={true} to="/go" strict={true} /> - ) - const el = wrapper.find('Route') - - expect(el.prop('exact')).toBe(true) - expect(el.prop('path')).toBe('\\/go') - expect(el.prop('strict')).toBe(true) - }) - test('Renders active styles', () => { - const isActive = () => true - const wrapper = mountWithRouter(<Item isActive={isActive} />) - const el = wrapper.find(`div.${Item.contentClassName}`) + const isActive = () => { + return true + } + const wrapper = mountWithRouter(<Item to="/" isActive={isActive} />) + + const el = wrapper.find(Link).find('a.c-NavItemLink') expect(el.hasClass('is-active')).toBeTruthy() }) @@ -90,14 +79,14 @@ describe('Nav.Item Route/Link', () => { describe('Nav.Item Disabled', () => { test('Passes disabled prop to Link', () => { - const wrapper = mountWithRouter(<Item disabled={true} />) - const el = wrapper.find('Link') + const wrapper = mountWithRouter(<Item to="/" disabled={true} />) + const el = wrapper.find(Link).find('a.c-NavItemLink') expect(el.prop('disabled')).toBe(true) }) test('Renders disabled styles', () => { - const wrapper = mountWithRouter(<Item disabled={true} />) + const wrapper = mountWithRouter(<Item to="/" disabled={true} />) const el = wrapper.find(`li.${Item.className}`) expect(el.hasClass('is-disabled')).toBeTruthy() @@ -106,14 +95,14 @@ describe('Nav.Item Disabled', () => { describe('Nav.Item Error', () => { test('Can render an error UI (Icon)', () => { - const wrapper = mountWithRouter(<Item error="Error!" />) + const wrapper = mountWithRouter(<Item to="/" error="Error!" />) const el = wrapper.find('.c-NavItemErrorIcon') expect(el.length).toBeTruthy() }) test('Renders a Tooltip with error', () => { - const wrapper = mountWithRouter(<Item error="Error!" />) + const wrapper = mountWithRouter(<Item to="/" error="Error!" />) const el = wrapper.find('Tooltip') expect(el.length).toBeTruthy() @@ -122,7 +111,7 @@ describe('Nav.Item Error', () => { describe('Nav.Item data-bypass', () => { test('Pushes data-bypass attribute to the link element', () => { - const wrapper = mountWithRouter(<Item data-bypass={true} />) + const wrapper = mountWithRouter(<Item to="/" data-bypass={true} />) const link = wrapper.find('a') expect(link.prop('data-bypass')).toBeTruthy() diff --git a/src/components/NavLink/NavLink.jsx b/src/components/NavLink/NavLink.jsx deleted file mode 100644 index b3a141dd8..000000000 --- a/src/components/NavLink/NavLink.jsx +++ /dev/null @@ -1,70 +0,0 @@ -// Source -// https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/modules/NavLink.js - -import React from 'react' -import Link from '../Link' -import classNames from 'classnames' -import { Route } from 'react-router-dom' - -export const NavLink = ({ - 'aria-current': ariaCurrent, - activeClassName, - activeStyle, - children, - className, - exact, - isActive: getIsActive, - location, - render, - strict, - style, - to, - ...rest -}) => { - const path = typeof to === 'object' ? to.pathname : to - - // Regex taken from: https://github.com/pillarjs/path-to-regexp/blob/master/index.js#L202 - const escapedPath = path && path.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1') - - return ( - <Route - path={escapedPath} - exact={exact} - strict={strict} - location={location} - children={({ location, match }) => { - const isActive = !!(getIsActive ? getIsActive(match, location) : match) - - const componentClassName = classNames( - NavLink.className, - isActive ? activeClassName : '', - className - ) - - return ( - <Link - {...rest} - to={to} - className={componentClassName} - style={isActive ? { ...style, ...activeStyle } : style} - aria-current={(isActive && ariaCurrent) || null} - > - {render ? render({ isActive }) : children} - </Link> - ) - }} - /> - ) -} - -NavLink.className = 'c-NavLink' - -NavLink.defaultProps = { - 'aria-current': 'page', - activeClassName: 'active is-active', - activeStyle: {}, - 'data-cy': 'NavLink', - style: {}, -} - -export default NavLink diff --git a/src/components/NavLink/NavLink.test.js b/src/components/NavLink/NavLink.test.js deleted file mode 100644 index 6866128f2..000000000 --- a/src/components/NavLink/NavLink.test.js +++ /dev/null @@ -1,126 +0,0 @@ -import React from 'react' -import { mount } from 'enzyme' -import { MemoryRouter as Router } from 'react-router-dom' -import NavLink from './NavLink' - -const wrap = Component => { - return mount( - <Router> - <div>{Component}</div> - </Router> - ) -} - -describe('className', () => { - test('Has default className', () => { - const wrapper = wrap(<NavLink />) - const el = wrapper.find('a') - - expect(el.hasClass('c-NavLink')).toBeTruthy() - }) - - test('Can render custom className', () => { - const customClassName = 'blue' - const wrapper = wrap(<NavLink className={customClassName} />) - const el = wrapper.find('a') - - expect(el.hasClass(customClassName)).toBeTruthy() - }) - - test('Can render a custom active className', () => { - const customClassName = 'yup' - const isActive = () => true - const wrapper = wrap( - <NavLink isActive={isActive} activeClassName={customClassName} /> - ) - const el = wrapper.find('a') - - expect(el.hasClass(customClassName)).toBeTruthy() - }) -}) - -describe('HTML props', () => { - test('Can render default HTML props', () => { - const wrapper = wrap(<NavLink data-cy="blue" />) - const el = wrapper.find('a') - - expect(el.prop('data-cy')).toBe('blue') - }) - - test('Does not render aria-current when inactive', () => { - const wrapper = wrap(<NavLink aria-current="blue" isActive={() => false} />) - - expect(wrapper.find('a').prop('aria-current')).toBeFalsy() - }) - - test('Renders aria-current when active', () => { - const wrapper = wrap(<NavLink aria-current="blue" isActive={() => true} />) - - expect(wrapper.find('a').prop('aria-current')).toBe('blue') - }) -}) - -describe('Style', () => { - test('Can render custom styles', () => { - const wrapper = wrap(<NavLink style={{ background: 'red' }} />) - const el = wrapper.find('a') - - expect(el.prop('style')).toEqual({ background: 'red' }) - }) - - test('Can render custom styles + custom active styles', () => { - const isActive = () => true - const wrapper = wrap( - <NavLink - isActive={isActive} - style={{ background: 'red' }} - activeStyle={{ display: 'flex' }} - /> - ) - const el = wrapper.find('a') - - expect(el.prop('style')).toEqual({ background: 'red', display: 'flex' }) - }) -}) - -describe('Route', () => { - test('Passes appropriate props to Route', () => { - const props = { - strict: true, - location: {}, - exact: true, - } - const wrapper = wrap(<NavLink {...props} />) - const el = wrapper.find('Route') - - expect(el.prop('strict')).toBe(props.strict) - expect(el.prop('location')).toBe(props.location) - expect(el.prop('exact')).toBe(props.exact) - }) - - test('Parses a to object', () => { - const props = { - to: { pathname: '/blue' }, - } - const wrapper = wrap(<NavLink {...props} />) - const el = wrapper.find('Route') - - expect(el.prop('path')).toContain('/blue') - }) -}) - -describe('Link', () => { - test('Renders a Link', () => { - const wrapper = wrap(<NavLink />) - const el = wrapper.find('Link') - - expect(el.length).toBeTruthy() - }) - - test('Can pass an href to Link', () => { - const wrapper = wrap(<NavLink href="/blue" />) - const el = wrapper.find('Link') - - expect(el.prop('href')).toBe('/blue') - }) -}) diff --git a/src/components/NavLink/index.js b/src/components/NavLink/index.js deleted file mode 100644 index 97e19508d..000000000 --- a/src/components/NavLink/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import NavLink from './NavLink' - -export default NavLink diff --git a/src/components/Page/Page.stories.mdx b/src/components/Page/Page.stories.mdx index c5d0a858f..7b6fa9c62 100644 --- a/src/components/Page/Page.stories.mdx +++ b/src/components/Page/Page.stories.mdx @@ -226,9 +226,13 @@ This component is a presentational wrapper used to render form actions. { left: 'left', right: 'right' }, 'left' )} - primary={<Button kind="primary">Thing</Button>} - secondary={<Button kind="secondary">Thing</Button>} - serious={<Button kind="link">Thing</Button>} + primary={<Button theme="blue">Thing</Button>} + secondary={ + <Button theme="grey" outlined> + Thing + </Button> + } + serious={<Button theme="blue">Thing</Button>} /> </Page> </Story> diff --git a/src/components/Pagination/Pagination.css.js b/src/components/Pagination/Pagination.css.js index ca1c63236..d85bd4abb 100644 --- a/src/components/Pagination/Pagination.css.js +++ b/src/components/Pagination/Pagination.css.js @@ -1,8 +1,6 @@ import styled from 'styled-components' import { getColor } from '../../styles/utilities/color' import Button from '../Button' -import config from '../Button/Button.config' -import { focusRing } from '../../styles/mixins/focusRing.css' export const PaginationUI = styled.nav` padding: 0 10px; @@ -32,14 +30,21 @@ export const RangeUI = styled('span')` ` export const ButtonIconUI = styled(Button)` - ${focusRing} - &.is-default { - width: 26px; + &.is-theme-grey { + --focusRingOffset: -2px; + --buttonPadding: 0; + --buttonHeight: 26px; + --buttonMinWidth: 0; height: 26px; - } - &.is-default:focus, - &.is-default.is-focused { - color: ${config.default.colorActive}; + + &.is-style-outlined { + --buttonTextColorHover: ${getColor('charcoal.700')}; + --buttonBorderColorHover: transparent; + --buttonBorderColor: transparent; + } + &[disabled] { + border-color: transparent !important; + } } & + & { diff --git a/src/components/Pagination/Pagination.jsx b/src/components/Pagination/Pagination.jsx index 2eaf7032e..31f6c0112 100644 --- a/src/components/Pagination/Pagination.jsx +++ b/src/components/Pagination/Pagination.jsx @@ -114,6 +114,8 @@ const PaginationNavigation = props => { {isNotFirstPage && ( <> <ButtonIconUI + theme="grey" + outlined key="firstButton" onClick={handleFirstClick} className="c-Pagination__firstButton" @@ -124,6 +126,8 @@ const PaginationNavigation = props => { <Icon name="arrow-left-double-large" size="24" center /> </ButtonIconUI> <ButtonIconUI + theme="grey" + outlined key="prevButton" onClick={handlePrevClick} className="c-Pagination__prevButton" @@ -138,6 +142,8 @@ const PaginationNavigation = props => { {!isLastPage && ( <> <ButtonIconUI + theme="grey" + outlined key="nextButton" disabled={isLoading} onClick={handleNextClick} @@ -148,6 +154,8 @@ const PaginationNavigation = props => { <Icon name="arrow-right-single-large" size="24" center /> </ButtonIconUI> <ButtonIconUI + theme="grey" + outlined key="lastButton" disabled={isLoading} onClick={handleEndClick} diff --git a/src/components/ScrollableContainer/ScrollableContainer.stories.mdx b/src/components/ScrollableContainer/ScrollableContainer.stories.mdx index b06453279..cd7165f0f 100644 --- a/src/components/ScrollableContainer/ScrollableContainer.stories.mdx +++ b/src/components/ScrollableContainer/ScrollableContainer.stories.mdx @@ -84,7 +84,7 @@ This component is a ready to use container for when you need a layout when the m } footer={ <FooterUI> - <Button kind="primary">Action!</Button> + <Button theme="blue">Action!</Button> </FooterUI> } /> diff --git a/src/components/ScrollableContainer/ScrollableContainer.storiesHelpers.js b/src/components/ScrollableContainer/ScrollableContainer.storiesHelpers.js index fb8b927cb..70554970a 100644 --- a/src/components/ScrollableContainer/ScrollableContainer.storiesHelpers.js +++ b/src/components/ScrollableContainer/ScrollableContainer.storiesHelpers.js @@ -344,7 +344,7 @@ export const SimpleBarExample = function () { } footer={ <FooterUI> - <Button kind="primary">Action!</Button> + <Button theme="blue">Action!</Button> </FooterUI> } /> diff --git a/src/components/SidePanel/SidePanel.layouts.jsx b/src/components/SidePanel/SidePanel.layouts.jsx index 01982a368..256ed08f5 100644 --- a/src/components/SidePanel/SidePanel.layouts.jsx +++ b/src/components/SidePanel/SidePanel.layouts.jsx @@ -46,12 +46,12 @@ export function HeaderAndFooter({ <FooterUI className="SidePanel__Footer" ref={footerRef}> <Button + size="xxl" + theme="blue" className="SidePanel__MainAction" disabled={mainActionDisabled} - kind="primary" onClick={onMainActionClick} - size="xl" - innerRef={mainActionNode} + ref={mainActionNode} > {mainActionButtonContent} </Button> diff --git a/src/components/SidePanel/SidePanel.storiesHelpers.js b/src/components/SidePanel/SidePanel.storiesHelpers.js index ba22d7cad..0df42d0ad 100644 --- a/src/components/SidePanel/SidePanel.storiesHelpers.js +++ b/src/components/SidePanel/SidePanel.storiesHelpers.js @@ -118,7 +118,7 @@ export default function SidePanelApp() { <Radio label="Right" value="right" name="right" /> <Radio label="Left" value="left" name="left" /> </ChoiceGroup> - <Button kind="secondary" size="xs" onClick={() => handleToggle()}> + <Button theme="grey" outlined size="xs" onClick={() => handleToggle()}> Toggle Panel </Button> </FakeNavUI> diff --git a/src/components/SimpleModal/SimpleModal.css.js b/src/components/SimpleModal/SimpleModal.css.js index 28dd0af56..21599bcc3 100644 --- a/src/components/SimpleModal/SimpleModal.css.js +++ b/src/components/SimpleModal/SimpleModal.css.js @@ -2,32 +2,13 @@ import styled from 'styled-components' import { getColor } from '../../styles/utilities/color' import { rgba } from '../../utilities/color' import { defaultAnimation as overlayAnimation } from '../../hooks/useAnimatedRender' +import IconButton from '../IconButton' -export const CloseModalButtonUI = styled('button')` +export const CloseModalButtonUI = styled(IconButton)` position: absolute; - padding: 5px; - background: #fff; - border: 0; - border-radius: 50%; - color: ${getColor('charcoal.400')}; top: 10px; right: 10px; - height: 28px; - width: 28px; - background-color: ${getColor('grey.300')}; z-index: ${({ $zIndex }) => $zIndex}; - - &:hover { - color: ${getColor('charcoal.600')}; - background-color: ${getColor('grey.400')}; - cursor: pointer; - } - - &:active, - &:focus { - outline: 0; - box-shadow: 0 0 0 2px ${getColor('blue.400')}; - } ` export const SimpleModalOverlayUI = styled('div')` diff --git a/src/components/SimpleModal/SimpleModal.jsx b/src/components/SimpleModal/SimpleModal.jsx index a4f82c779..e8bf77dc2 100644 --- a/src/components/SimpleModal/SimpleModal.jsx +++ b/src/components/SimpleModal/SimpleModal.jsx @@ -10,7 +10,6 @@ import { SimpleModalOverlayUI, SimpleModalUI, } from './SimpleModal.css' -import Icon from '../Icon' function SimpleModal({ ariaLabelledBy = '', @@ -76,9 +75,10 @@ function SimpleModal({ className="SimpleModal__CloseButton" onClick={onClose} $zIndex={zIndexCloseButton} - > - <Icon size={18} name="cross" /> - </CloseModalButtonUI> + icon="cross-small" + size="lg" + seamless + /> ) } diff --git a/src/components/SimpleModal/SimpleModal.stories.mdx b/src/components/SimpleModal/SimpleModal.stories.mdx index ee4e58c26..968fa48d1 100644 --- a/src/components/SimpleModal/SimpleModal.stories.mdx +++ b/src/components/SimpleModal/SimpleModal.stories.mdx @@ -135,7 +135,7 @@ Don't forget to add styles to position it yourself. height: '100%', }} > - <Button kind="primary">Action!</Button> + <Button theme="blue">Action!</Button> </div> } > diff --git a/src/components/Table/Table.ColumnChooser.jsx b/src/components/Table/Table.ColumnChooser.jsx index c945ebac7..6bb0fafcb 100644 --- a/src/components/Table/Table.ColumnChooser.jsx +++ b/src/components/Table/Table.ColumnChooser.jsx @@ -38,9 +38,7 @@ function ColumnChooser({ <IconBtn a11yLabel="Choose columns to show or hide" iconName="column-check" - iconSize={24} - withCaret={false} - shape="circle" + size="lg" /> } withMultipleSelection diff --git a/src/components/Table/Table.css.js b/src/components/Table/Table.css.js index 36e5428af..ff24392c9 100644 --- a/src/components/Table/Table.css.js +++ b/src/components/Table/Table.css.js @@ -1,5 +1,6 @@ import styled from 'styled-components' import { getColor } from '../../styles/utilities/color' +import Button from '../Button' export const HeaderUI = styled('header')` display: flex; @@ -10,13 +11,8 @@ export const HeaderUI = styled('header')` } .DropListToggler { - color: ${getColor('charcoal.300')}; + align-self: center; margin-right: 4px; - margin-top: 4px; - - &.is-active { - color: ${getColor('charcoal.500')}; - } } ` @@ -255,6 +251,14 @@ export const SortableCellContentUI = styled('div')` } ` +export const ButtonExpanderUI = styled(Button)` + &.is-size-sm { + --buttonFontWeight: normal; + --buttonHeight: 26px; + margin: 8px 0 8px 4px; + } +` + function generateTableWidthStyles(tableWidth) { let style = 'width: 100%;' diff --git a/src/components/Table/Table.jsx b/src/components/Table/Table.jsx index 9a27798b2..e370314bc 100644 --- a/src/components/Table/Table.jsx +++ b/src/components/Table/Table.jsx @@ -4,9 +4,14 @@ import useDeepCompareEffect from 'use-deep-compare-effect' import { ThemeProvider } from 'styled-components' import classNames from 'classnames' import { noop } from '../../utilities/other' -import Button from '../Button' import Scrollable from '../Scrollable' -import { HeaderUI, TableWrapperUI, TableUI, LoadingUI } from './Table.css' +import { + HeaderUI, + TableWrapperUI, + TableUI, + LoadingUI, + ButtonExpanderUI, +} from './Table.css' import { defaultSkin, chooseSkin } from './Table.skins' import { columnShape, dataShape, isTableSortable } from './Table.utils' import { useTable } from './Table.hooks' @@ -172,31 +177,33 @@ export function Table({ {isLoading && <LoadingUI className={`${TABLE_CLASSNAME}__Loading`} />} {isTableCollapsable && isCollapsed ? ( - <Button + <ButtonExpanderUI + size="sm" + theme="blue" + linked className={`${TABLE_CLASSNAME}__Expander`} - kind="link" onClick={() => { expandTable(data) onExpand({ collapsed: false }) }} - style={{ marginLeft: '14px' }} > {expanderText ? expanderText.collapsed : 'View All'} - </Button> + </ButtonExpanderUI> ) : null} {isTableCollapsable && !isCollapsed ? ( - <Button + <ButtonExpanderUI + size="sm" + theme="blue" + linked className={`${TABLE_CLASSNAME}__Expander`} - kind="link" onClick={() => { collapseTable(data, maxRowsToDisplay) onExpand({ collapsed: true }) }} - style={{ marginLeft: '14px' }} > {expanderText ? expanderText.expanded : 'Collapse'} - </Button> + </ButtonExpanderUI> ) : null} </TableWrapperUI> </ThemeProvider> diff --git a/src/components/Table/Table.test.js b/src/components/Table/Table.test.js index 4f824dda6..d95539043 100644 --- a/src/components/Table/Table.test.js +++ b/src/components/Table/Table.test.js @@ -879,9 +879,10 @@ describe('Column Chooser', () => { const button = getByRole('button') - expect( - button.querySelectorAll('span.c-VisuallyHidden')[1] - ).toHaveTextContent('Choose columns to show or hide') + expect(button).toHaveAttribute( + 'aria-label', + 'Choose columns to show or hide' + ) // Starts with 3 columns expect(container.querySelectorAll('th').length).toBe(3) diff --git a/src/components/Tag/Tag.css.js b/src/components/Tag/Tag.css.js index e2a23add3..9f390abc5 100644 --- a/src/components/Tag/Tag.css.js +++ b/src/components/Tag/Tag.css.js @@ -25,6 +25,8 @@ export const config = { export const RemoveTagUI = styled.button` ${focusRing} + --focusRingRadius: 3px; + --focusRingShadow: ${focusShadowWithInset}; border-radius: 3px; width: 16px; @@ -43,11 +45,6 @@ export const RemoveTagUI = styled.button` background: transparent; padding: 0; - // focus border overwrites - &:before { - box-shadow: ${focusShadowWithInset}; - } - &:hover, &:focus { opacity: 1; @@ -91,6 +88,7 @@ export const RemoveIconUI = styled(Icon)` export const TagUI = styled('div')` ${focusRing} + --focusRingOffset: -3px; background-color: white; border-radius: 3px; @@ -106,24 +104,13 @@ export const TagUI = styled('div')` letter-spacing: -0.1px; line-height: 12px; font-size: 11.5px; - - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, - sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + font-family: var(--HSDSGlobalFontFamilySystem); text-decoration: none; &.is-all-caps { text-transform: uppercase; } - // focus border overwrites - &:before { - border-radius: 4px; - bottom: -3px; - left: -3px; - right: -3px; - top: -3px; - } - ${makeColorStyles()}; &.is-filled { diff --git a/src/components/TagList/TagList.css.js b/src/components/TagList/TagList.css.js index fe28f7d9b..53c9a5a70 100644 --- a/src/components/TagList/TagList.css.js +++ b/src/components/TagList/TagList.css.js @@ -21,8 +21,7 @@ export const ShowAllButtonUI = styled.button` box-shadow: none; font-size: 12px; line-height: 1; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, - sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + font-family: var(--HSDSGlobalFontFamilySystem); &:hover { cursor: pointer; diff --git a/src/components/Tooltip/Tooltip.storiesHelpers.js b/src/components/Tooltip/Tooltip.storiesHelpers.js index 1fffa3253..0736a4af1 100644 --- a/src/components/Tooltip/Tooltip.storiesHelpers.js +++ b/src/components/Tooltip/Tooltip.storiesHelpers.js @@ -91,7 +91,7 @@ export const CustomContent = () => { const tooltipContent = ( <Text> Hello there. <strong>This is really important!</strong> - <Button kind="primary" size="sm" state="gray"> + <Button theme="blue" size="sm"> Learn More </Button> </Text> diff --git a/src/components/VerificationCode/VerificationCode.css.js b/src/components/VerificationCode/VerificationCode.css.js index bbb4e06c3..6d7291f04 100644 --- a/src/components/VerificationCode/VerificationCode.css.js +++ b/src/components/VerificationCode/VerificationCode.css.js @@ -56,8 +56,7 @@ export const DigitMaskUI = styled('div')` width: 40px; height: 50px; padding: 0 0 7px 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, - sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + font-family: var(--HSDSGlobalFontFamilySystem); font-size: 35px; line-height: 41px; text-align: center; @@ -82,8 +81,7 @@ export const DigitInputUI = styled('input')` margin: 0; border: 0; border-bottom: 2px solid #d5dce1; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, - sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + font-family: var(--HSDSGlobalFontFamilySystem); font-size: 35px; line-height: 35px; text-align: center; diff --git a/src/components/index.js b/src/components/index.js index 86b3a2f94..7547758a5 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -73,7 +73,6 @@ export { default as Message } from './Message' export { default as MessageCard } from './MessageCard' export { default as Modal } from './Modal' export { default as Nav } from './Nav' -export { default as NavLink } from './NavLink' export { default as Notification } from './Notification' export { default as NotificationStack } from './NotificationStack' export { default as OptionTile } from './OptionTile' diff --git a/src/styles/configs/colorway.js b/src/styles/configs/colorway.js index 9a93a2dc5..736ff03f6 100644 --- a/src/styles/configs/colorway.js +++ b/src/styles/configs/colorway.js @@ -1,4 +1,4 @@ -// Generated with @helpscout/colorway (v0.9.7) +// Generated with @helpscout/colorway (v0.10.0) // // This file was automatically generated with @helpscout/colorway // Please don't modify this file. Your changes will be overwritten. @@ -144,6 +144,8 @@ const palette = { '700': '#FD9AAE', '800': '#FC5D7D', '900': '#F23459', + '950': '#E9074B', + '1000': '#C5003B', default: '#FFB3C3', }, whaletail: { diff --git a/src/styles/mixins/focusRing.css.js b/src/styles/mixins/focusRing.css.js index 2e23d2d9e..72777feb6 100644 --- a/src/styles/mixins/focusRing.css.js +++ b/src/styles/mixins/focusRing.css.js @@ -1,27 +1,34 @@ import { css } from 'styled-components' import { getColor } from '../../styles/utilities/color' -export const focusShadow = ` - 0 0 0 2px ${getColor('blue.500')}; -` +export const focusShadow = `0 0 0 2px var(--focusRingColor, ${getColor( + 'blue.500' +)});` + export const focusShadowWithInset = ` - 0 0 0 2px ${getColor('blue.500')}, inset 0 0 0 2px white; + 0 0 0 2px var(--focusRingColor, ${getColor( + 'blue.500' + )}), inset 0 0 0 2px white; ` export const focusRing = css` + --focusRingColor: ${getColor('blue.500')}; + --focusRingOffset: -2px; + --focusRingShadow: ${focusShadow}; + --focusRingRadius: inherit; outline: none; position: relative; &:before { content: ''; - border-radius: inherit; - bottom: -2px; - box-shadow: ${focusShadow}; - left: -2px; + border-radius: var(--focusRingRadius); + bottom: var(--focusRingOffset); + box-shadow: var(--focusRingShadow); + left: var(--focusRingOffset); pointer-events: none; position: absolute; - right: -2px; - top: -2px; + right: var(--focusRingOffset); + top: var(--focusRingOffset); opacity: 0; background: transparent; z-index: 3; diff --git a/src/utilities/pkg.js b/src/utilities/pkg.js index a02d2f802..a653d935b 100644 --- a/src/utilities/pkg.js +++ b/src/utilities/pkg.js @@ -1,3 +1,3 @@ export default { - version: '3.32.0', + version: '3.33.0', } diff --git a/src/utilities/specs/avatar.specs.js b/src/utilities/specs/avatar.specs.js index f0bb19215..7d2c0dd67 100644 --- a/src/utilities/specs/avatar.specs.js +++ b/src/utilities/specs/avatar.specs.js @@ -13,14 +13,14 @@ const avatarSpec = createSpec({ id: prop.id, name: prop.name, firstName: prop.firstName, - // image: prop.image, + image: `https://avatars.dicebear.com/api/pixel-art/${prop.id}.svg?mood[]=happy&background=%23E5E9EC`, } }) } return { id: props.id, name: props.name, - // image: props.image, + image: `https://avatars.dicebear.com/api/pixel-art/${props.id}.svg?mood[]=happy&background=%23E5E9EC`, } })