diff --git a/e2e/src/utils/search.utils.ts b/e2e/src/utils/search.utils.ts index bc0070db2e..8eac9f27d0 100644 --- a/e2e/src/utils/search.utils.ts +++ b/e2e/src/utils/search.utils.ts @@ -35,7 +35,7 @@ export const findChildElement = async ( }; export const findElementBySelectors = async (selectors: string, timeout = MEDIUM_TIMEOUT, errorTitle?: string) => { - const element = await BrowserContext.page.waitForSelector(selectors, { visible: true, timeout }).catch(error => { + const element = await BrowserContext.page.waitForSelector(selectors, { timeout }).catch(error => { if (errorTitle && error instanceof Error) { error.message = `${errorTitle}\n` + error.message; } diff --git a/package.json b/package.json index 6b2f2d9cc6..b8d5395543 100644 --- a/package.json +++ b/package.json @@ -166,7 +166,7 @@ "react-qr-svg": "2.2.2", "react-redux": "^8.0.2", "react-text-mask": "^5.5.0", - "react-transition-group": "4.4.2", + "react-transition-group": "4.4.5", "react-virtualized": "^9.22.3", "redux-observable": "^2.0.0", "redux-persist": "^6.0.0", diff --git a/src/app/atoms/DropdownWrapper.tsx b/src/app/atoms/DropdownWrapper.tsx index 7ad3868fb8..d4bd34d34b 100644 --- a/src/app/atoms/DropdownWrapper.tsx +++ b/src/app/atoms/DropdownWrapper.tsx @@ -1,9 +1,10 @@ -import React, { FC, HTMLAttributes } from 'react'; +import React, { FC, HTMLAttributes, useCallback, useRef, useState } from 'react'; import classNames from 'clsx'; import CSSTransition from 'react-transition-group/CSSTransition'; type DropdownWrapperProps = HTMLAttributes & { + isNetworks?: boolean; opened: boolean; design?: Design; hiddenOverflow?: boolean; @@ -15,6 +16,8 @@ const DESIGN_CLASS_NAMES = { dark: 'bg-gray-910 border-gray-850' }; +const ANIMATION_DURATION = 100; + type Design = keyof typeof DESIGN_CLASS_NAMES; const DropdownWrapper: FC = ({ @@ -24,34 +27,56 @@ const DropdownWrapper: FC = ({ scaleAnimation = true, className, style = {}, + isNetworks, ...rest -}) => ( - -
- -); +}) => { + // Recommended: https://reactcommunity.org/react-transition-group/transition#Transition-prop-nodeRef + const nodeRef = useRef(null); + + const [key, setKey] = useState(0); + + const onExiting = useCallback(() => { + // Transition component does not propperly update, when Suspense is involved. + // E.g. happens when new node RPC is selected & chainId is being fetched (see: `useCustomChainId` hook). + // Status `exited` & `unmounted` never arrive in such case! + // See: https://github.com/reactjs/react-transition-group/issues/817#issuecomment-1122997210 + // We will re-create it every time ourselves via different key. + setTimeout(() => setKey(key => (key % 2) + 1), 2 * ANIMATION_DURATION); + }, []); + + return ( + +
+ + ); +}; export default DropdownWrapper; diff --git a/src/app/layouts/PageLayout/Header/NetworkSelect/index.tsx b/src/app/layouts/PageLayout/Header/NetworkSelect/index.tsx index 9724dcab1d..69fc5657c5 100644 --- a/src/app/layouts/PageLayout/Header/NetworkSelect/index.tsx +++ b/src/app/layouts/PageLayout/Header/NetworkSelect/index.tsx @@ -71,7 +71,7 @@ const NetworkSelect: FC = () => { placement="bottom-end" strategy="fixed" popup={({ opened, setOpened }) => ( - +

void; numberOfWords: number; setNumberOfWords: (n: number) => void; + testIDs: { + a: string; + b: string; + }; } const defaultNumberOfWords = 12; -export const SeedPhraseInput: FC = ({ - isFirstAccount, - submitted, - seedError, - labelWarning, - onChange, - setSeedError, - reset, - numberOfWords, - setNumberOfWords, - testID -}) => { - const inputsRef = useRef>([]); - - const { popup } = useAppEnv(); - - const [pasteFailed, setPasteFailed] = useState(false); - const [draftSeed, setDraftSeed] = useState(new Array(defaultNumberOfWords).fill('')); - const [wordSpellingErrorsCount, setWordSpellingErrorsCount] = useState(0); - - const { getRevealRef, onReveal, resetRevealRef } = useRevealRef(); - - const onSeedChange = useCallback( - (newDraftSeed: string[]) => { - setDraftSeed(newDraftSeed); - - const joinedDraftSeed = newDraftSeed.join(' '); - let newSeedError = ''; - - if (!newDraftSeed.some(Boolean)) { - onChange(joinedDraftSeed); - return; - } +export const SeedPhraseInput = memo( + ({ + isFirstAccount, + submitted, + seedError, + labelWarning, + onChange, + setSeedError, + reset, + numberOfWords, + setNumberOfWords, + testID, + testIDs + }) => { + const inputsRef = useRef>([]); + + const { popup } = useAppEnv(); + + const [pasteFailed, setPasteFailed] = useState(false); + const [draftSeed, setDraftSeed] = useState(new Array(defaultNumberOfWords).fill('')); + const [wordSpellingErrorsCount, setWordSpellingErrorsCount] = useState(0); + + const { getRevealRef, onReveal, resetRevealRef } = useRevealRef(); + + const onSeedChange = useCallback( + (newDraftSeed: string[]) => { + setDraftSeed(newDraftSeed); + + const joinedDraftSeed = newDraftSeed.join(' '); + let newSeedError = ''; + + if (!newDraftSeed.some(Boolean)) { + onChange(joinedDraftSeed); + return; + } - if (newDraftSeed.some(word => word === '')) { - newSeedError = t('mnemonicWordsAmountConstraint', [numberOfWords]) as string; - } + if (newDraftSeed.some(word => word === '')) { + newSeedError = t('mnemonicWordsAmountConstraint', [numberOfWords]) as string; + } - if (!validateMnemonic(formatMnemonic(joinedDraftSeed))) { - newSeedError = t('justValidPreGeneratedMnemonic'); - } + if (!validateMnemonic(formatMnemonic(joinedDraftSeed))) { + newSeedError = t('justValidPreGeneratedMnemonic'); + } - setSeedError(newSeedError); - onChange(newSeedError ? '' : joinedDraftSeed); - }, - [setDraftSeed, setSeedError, onChange, numberOfWords] - ); + setSeedError(newSeedError); + onChange(newSeedError ? '' : joinedDraftSeed); + }, + [setDraftSeed, setSeedError, onChange, numberOfWords] + ); - const onSeedWordChange = useCallback( - (index: number, value: string) => { - if (pasteFailed) { - setPasteFailed(false); - } - const newSeed = draftSeed.slice(); - newSeed[index] = value.trim(); - onSeedChange(newSeed); - }, - [draftSeed, onSeedChange, pasteFailed] - ); - - const onSeedPaste = useCallback( - (rawSeed: string) => { - const parsedSeed = formatMnemonic(rawSeed); - let newDraftSeed = parsedSeed.split(' '); - - if (newDraftSeed.length > 24) { - setPasteFailed(true); - return; - } else if (pasteFailed) { - setPasteFailed(false); - } + const onSeedWordChange = useCallback( + (index: number, value: string) => { + if (pasteFailed) { + setPasteFailed(false); + } + const newSeed = draftSeed.slice(); + newSeed[index] = value.trim(); + onSeedChange(newSeed); + }, + [draftSeed, onSeedChange, pasteFailed] + ); + + const onSeedPaste = useCallback( + (rawSeed: string) => { + const parsedSeed = formatMnemonic(rawSeed); + let newDraftSeed = parsedSeed.split(' '); + + if (newDraftSeed.length > 24) { + setPasteFailed(true); + return; + } else if (pasteFailed) { + setPasteFailed(false); + } - let newNumberOfWords = numberOfWords; - if (newDraftSeed.length !== numberOfWords) { - if (newDraftSeed.length < 12) { - newNumberOfWords = 12; - } else if (newDraftSeed.length % 3 === 0) { - newNumberOfWords = newDraftSeed.length; - } else { - newNumberOfWords = newDraftSeed.length + (3 - (newDraftSeed.length % 3)); + let newNumberOfWords = numberOfWords; + if (newDraftSeed.length !== numberOfWords) { + if (newDraftSeed.length < 12) { + newNumberOfWords = 12; + } else if (newDraftSeed.length % 3 === 0) { + newNumberOfWords = newDraftSeed.length; + } else { + newNumberOfWords = newDraftSeed.length + (3 - (newDraftSeed.length % 3)); + } + setNumberOfWords(newNumberOfWords); } - setNumberOfWords(newNumberOfWords); - } - if (newDraftSeed.length < newNumberOfWords) { - newDraftSeed = newDraftSeed.concat(new Array(newNumberOfWords - newDraftSeed.length).fill('')); - } + if (newDraftSeed.length < newNumberOfWords) { + newDraftSeed = newDraftSeed.concat(new Array(newNumberOfWords - newDraftSeed.length).fill('')); + } - resetRevealRef(); - onSeedChange(newDraftSeed); - clearClipboard(); - }, - [numberOfWords, onSeedChange, pasteFailed, setPasteFailed, resetRevealRef, setNumberOfWords] - ); + resetRevealRef(); + onSeedChange(newDraftSeed); + clearClipboard(); + }, + [numberOfWords, onSeedChange, pasteFailed, setPasteFailed, resetRevealRef, setNumberOfWords] + ); - const onSeedWordPaste = useCallback>( - event => { - const newSeed = event.clipboardData.getData('text'); + const onSeedWordPaste = useCallback>( + event => { + const newSeed = event.clipboardData.getData('text'); - if (newSeed.trim().match(/\s/u)) { - event.preventDefault(); - onSeedPaste(newSeed); + if (newSeed.trim().match(/\s/u)) { + event.preventDefault(); + onSeedPaste(newSeed); + } + }, + [onSeedPaste] + ); + + const numberOfWordsOptions = useMemo(() => { + const result = []; + for (let i = 12; i <= 24; i += 3) { + result.push(`${i}`); } - }, - [onSeedPaste] - ); - - const numberOfWordsOptions = useMemo(() => { - const result = []; - for (let i = 12; i <= 24; i += 3) { - result.push(`${i}`); - } - return result; - }, []); - - return ( -
-
-

- -

- -
- { - const newNumberOfWords = parseInt(newSelectedOption, 10); - if (Number.isNaN(newNumberOfWords)) { - throw new Error('Unable to parse option as integer'); - } - - const newDraftSeed = new Array(newNumberOfWords).fill(''); - setNumberOfWords(newNumberOfWords); - onSeedChange(newDraftSeed); - reset(); - setSeedError(''); - setWordSpellingErrorsCount(0); - }} - /> + return result; + }, []); + + return ( +
+
+

+ +

+ +
+ { + const newNumberOfWords = parseInt(newSelectedOption, 10); + if (Number.isNaN(newNumberOfWords)) { + throw new Error('Unable to parse option as integer'); + } + + const newDraftSeed = new Array(newNumberOfWords).fill(''); + setNumberOfWords(newNumberOfWords); + onSeedChange(newDraftSeed); + reset(); + setSeedError(''); + setWordSpellingErrorsCount(0); + }} + /> +
-
- {labelWarning && ( -
{labelWarning}
- )} + {labelWarning && ( +
{labelWarning}
+ )} -
-

{t('seedPhraseTip')}

-
+
+

{t('seedPhraseTip')}

+
-
- {[...Array(numberOfWords).keys()].map(index => { - const key = `import-seed-word-${index}`; - - const handleChange = (event: React.ChangeEvent) => { - event.preventDefault(); - onSeedWordChange(index, event.target.value); - }; - - return ( - onReveal(index)} - value={draftSeed[index]} - testID={testID} - onPaste={onSeedWordPaste} - setWordSpellingErrorsCount={setWordSpellingErrorsCount} - onSeedWordChange={onSeedWordChange} - /> - ); - })} -
+
+ {[...Array(numberOfWords).keys()].map(index => { + const key = `import-seed-word-${index}`; + + const handleChange = (event: React.ChangeEvent) => { + event.preventDefault(); + onSeedWordChange(index, event.target.value); + }; + + return ( + onReveal(index)} + value={draftSeed[index]} + testID={testID} + onPaste={onSeedWordPaste} + setWordSpellingErrorsCount={setWordSpellingErrorsCount} + onSeedWordChange={onSeedWordChange} + /> + ); + })} +
-
- {submitted && seedError &&
{seedError}
} +
+ {submitted && seedError &&
{seedError}
} - {wordSpellingErrorsCount > 0 && ( -
- -
- )} + {wordSpellingErrorsCount > 0 && ( +
+ +
+ )} - {pasteFailed && ( -
- -
- )} + {pasteFailed && ( +
+ +
+ )} +
-
- ); -}; + ); + } +); export const isSeedPhraseFilled = (seedPhrase: string) => seedPhrase && !seedPhrase.split(' ').includes(''); diff --git a/src/lib/ui/Popper.tsx b/src/lib/ui/Popper.tsx index 6a198a8444..0f85586b11 100644 --- a/src/lib/ui/Popper.tsx +++ b/src/lib/ui/Popper.tsx @@ -126,7 +126,7 @@ const Popper = memo(({ popup, children, fallbackPlacementsEnabled = {triggerNode} -
+
{popupNode}
diff --git a/yarn.lock b/yarn.lock index 91c22cef79..b09ffa0c7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10597,10 +10597,10 @@ react-textarea-autosize@^8.3.2: use-composed-ref "^1.0.0" use-latest "^1.0.0" -react-transition-group@4.4.2: - version "4.4.2" - resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470" - integrity sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg== +react-transition-group@4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== dependencies: "@babel/runtime" "^7.5.5" dom-helpers "^5.0.1"