diff --git a/package-lock.json b/package-lock.json index 396805c8..bc3b3d84 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,7 +56,7 @@ "@storybook/react-webpack5": "^7.6.17", "@storybook/testing-library": "^0.2.0", "@storybook/theming": "^7.6.17", - "@types/zxcvbn": "^4.4.2", + "@types/zxcvbn": "^4.4.4", "babel-plugin-named-exports-order": "^0.0.2", "eslint-plugin-storybook": "^0.6.13", "husky": "^8.0.3", @@ -655,9 +655,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", - "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", + "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", "bin": { "parser": "bin/babel-parser.js" }, @@ -8537,9 +8537,9 @@ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "node_modules/@types/node": { - "version": "18.19.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.18.tgz", - "integrity": "sha512-80CP7B8y4PzZF0GWx15/gVWRrB5y/bIjNI84NK3cmQJu0WZwvmj2WMA5LcofQFVfLqqCSp545+U2LsrVzX36Zg==", + "version": "18.19.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.21.tgz", + "integrity": "sha512-2Q2NeB6BmiTFQi4DHBzncSoq/cJMLDdhPaAoJFnFCyD9a8VPZRf7a1GAwp1Edb7ROaZc5Jz/tnZyL6EsWMRaqw==", "dependencies": { "undici-types": "~5.26.4" } @@ -8736,9 +8736,9 @@ "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==" }, "node_modules/@types/zxcvbn": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/@types/zxcvbn/-/zxcvbn-4.4.2.tgz", - "integrity": "sha512-T7SEL8b/eN7AEhHQ8oFt7c6Y+l3p8OpH7KwJIe+5oBOPLMMioPeMsUTB3huNgEnXhiittV8Ohdw21Jg8E/f70Q==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@types/zxcvbn/-/zxcvbn-4.4.4.tgz", + "integrity": "sha512-Tuk4q7q0DnpzyJDI4aMeghGuFu2iS1QAdKpabn8JfbtfGmVDUgvZv1I7mEjP61Bvnp3ljKCC8BE6YYSTNxmvRQ==", "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { @@ -20160,9 +20160,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.7", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz", - "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==", + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", "dev": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" diff --git a/package.json b/package.json index 60163b68..96653649 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "@storybook/react-webpack5": "^7.6.17", "@storybook/testing-library": "^0.2.0", "@storybook/theming": "^7.6.17", - "@types/zxcvbn": "^4.4.2", + "@types/zxcvbn": "^4.4.4", "babel-plugin-named-exports-order": "^0.0.2", "eslint-plugin-storybook": "^0.6.13", "husky": "^8.0.3", diff --git a/src/components/Form/PasswordStrength.tsx b/src/components/Form/PasswordStrength.tsx new file mode 100644 index 00000000..705bcc74 --- /dev/null +++ b/src/components/Form/PasswordStrength.tsx @@ -0,0 +1,45 @@ +import { Box, Typography } from '@mui/material'; +import { useState, useEffect } from 'react'; +import zxcvbn from 'zxcvbn'; + +const STRENGTH_TITLES = ['Very weak', 'Weak', 'Good', 'Strong', 'Very strong']; +const STRENGTH_STYLES = [ + { width: 0 }, + { width: '25%', backgroundColor: 'orange' }, + { width: '50%', backgroundColor: 'yellow' }, + { width: '75%', backgroundColor: 'green' }, + { width: '100%', backgroundColor: 'darkgreen' }, +]; + +export interface PasswordStrengthProps { + password?: string; +} + +export const PasswordStrength = ({ password }: PasswordStrengthProps) => { + const [strengthScore, setStrengthScore] = useState(); + + useEffect(() => { + if (password?.length) { + const { score } = zxcvbn(password); + setStrengthScore(score); + } + }, [password]); + + if (!password || strengthScore === undefined) { + return null; + } + + return ( + + + Password strength: {STRENGTH_TITLES[strengthScore]} + + + + ); +}; diff --git a/src/components/Form/Widgets/PasswordWidget.tsx b/src/components/Form/Widgets/PasswordWidget.tsx index f199b366..13641837 100644 --- a/src/components/Form/Widgets/PasswordWidget.tsx +++ b/src/components/Form/Widgets/PasswordWidget.tsx @@ -1,65 +1,22 @@ import { - Box, FormControl, IconButton, InputAdornment, TextField, - Typography, } from '@mui/material'; -import * as React from 'react'; +import { Suspense, lazy, useState } from 'react'; import { Visibility, VisibilityOff } from '@mui/icons-material'; import { WidgetProps } from '@rjsf/utils'; - -const STRENGTH_TITLES = ['Very weak', 'Weak', 'Good', 'Strong', 'Very strong']; -const STRENGTH_STYLES = [ - { width: 0 }, - { width: '25%', backgroundColor: 'orange' }, - { width: '50%', backgroundColor: 'yellow' }, - { width: '75%', backgroundColor: 'green' }, - { width: '100%', backgroundColor: 'darkgreen' }, -]; - export interface PasswordStrengthProps { password?: string; } -const PasswordStrength = ({ password }: PasswordStrengthProps) => { - const [strengthScore, setStrengthScore] = React.useState< - number | undefined - >(); - - React.useEffect(() => { - // @ts-expect-error If you wish to show a stength meter, you need to load and set `zxcvbn` to a window variable by yourself. - const zxcvbn = window.zxcvbn; - - if (zxcvbn && password) { - try { - const { score } = zxcvbn(password); - setStrengthScore(score); - } catch { - // Ignore any errors, as we only want to show the strength meter if it is available. - } - } - }, [password]); - - if (!password || strengthScore === undefined) { - return null; - } - - return ( - - - Password strength: {STRENGTH_TITLES[strengthScore]} - - - - ); -}; +const PasswordStrength = lazy(async () => { + const importedModule = await import('../PasswordStrength'); + return { + default: importedModule.PasswordStrength, + }; +}); export const PasswordWidget = ({ id, @@ -74,7 +31,7 @@ export const PasswordWidget = ({ options, required, }: WidgetProps) => { - const [showPassword, setShowPassword] = React.useState(false); + const [showPassword, setShowPassword] = useState(false); const change = ({ target: { value } }: any) => { return onChange(value === '' ? options.emptyValue : value); @@ -121,7 +78,9 @@ export const PasswordWidget = ({ }} /> {options.showPasswordStrengthMeter && ( - + + + )} );