From d507b505cb340d3b00d1a394088f4c72ee524156 Mon Sep 17 00:00:00 2001 From: Pete Watters <2938440+pete-watters@users.noreply.github.com> Date: Wed, 3 Apr 2024 11:52:30 +0100 Subject: [PATCH 1/3] fix: improve dynamic sizing of virtuoso lists, ref #5149, #5242 --- package.json | 2 +- pnpm-lock.yaml | 77 +++++++------------ .../common/hooks/use-get-virtuoso-height.ts | 36 +++++++++ src/app/common/hooks/use-media-query.ts | 23 ++++++ .../components/switch-account-list-item.tsx | 25 +++--- .../switch-account-dialog.tsx | 21 ++--- .../choose-account/components/accounts.tsx | 19 ++--- .../recipient-accounts-dialog.tsx | 19 +++-- .../components/containers/dialog/dialog.tsx | 26 ++++--- src/app/ui/shared/virtuoso.ts | 8 -- 10 files changed, 146 insertions(+), 110 deletions(-) create mode 100644 src/app/common/hooks/use-get-virtuoso-height.ts create mode 100644 src/app/common/hooks/use-media-query.ts delete mode 100644 src/app/ui/shared/virtuoso.ts diff --git a/package.json b/package.json index 22f2ced1604..c615c6056be 100644 --- a/package.json +++ b/package.json @@ -230,7 +230,7 @@ "react-qr-code": "2.0.12", "react-redux": "9.1.0", "react-router-dom": "6.22.3", - "react-virtuoso": "4.7.1", + "react-virtuoso": "4.7.7", "redux-persist": "6.0.0", "rxjs": "7.8.1", "style-loader": "3.3.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b370756222..340ea9dc6ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -315,8 +315,8 @@ dependencies: specifier: 6.22.3 version: 6.22.3(react-dom@18.2.0)(react@18.2.0) react-virtuoso: - specifier: 4.7.1 - version: 4.7.1(react-dom@18.2.0)(react@18.2.0) + specifier: 4.7.7 + version: 4.7.7(react-dom@18.2.0)(react@18.2.0) redux-persist: specifier: 6.0.0 version: 6.0.0(react@18.2.0)(redux@5.0.1) @@ -2410,7 +2410,7 @@ packages: resolution: {integrity: sha512-We0knVd0x8M998jDOvNgesN+wqzvGuYA6Q8oNRAhGhTcIjd9AsPGEpPo9glqZb/wot9nioiz3bnFQH50M/FtXQ==} engines: {node: '>=16.0.0', yarn: '>=1.22.18'} dependencies: - chromatic: 11.1.0 + chromatic: 11.0.0 filesize: 10.1.0 jsonfile: 6.1.0 react-confetti: 6.1.0(react@18.2.0) @@ -2528,9 +2528,9 @@ packages: peerDependencies: postcss: ^8.4 dependencies: - '@csstools/selector-specificity': 3.0.2(postcss-selector-parser@6.0.15) + '@csstools/selector-specificity': 3.0.3(postcss-selector-parser@6.0.16) postcss: 8.4.35 - postcss-selector-parser: 6.0.15 + postcss-selector-parser: 6.0.16 dev: true /@csstools/postcss-cascade-layers@4.0.4(postcss@8.4.38): @@ -2875,15 +2875,6 @@ packages: postcss-selector-parser: 6.0.16 dev: false - /@csstools/selector-specificity@3.0.2(postcss-selector-parser@6.0.15): - resolution: {integrity: sha512-RpHaZ1h9LE7aALeQXmXrJkRG84ZxIsctEN2biEUmFyKpzFM3zZ35eUMcIzZFsw/2olQE6v69+esEqU2f1MKycg==} - engines: {node: ^14 || ^16 || >=18} - peerDependencies: - postcss-selector-parser: ^6.0.13 - dependencies: - postcss-selector-parser: 6.0.15 - dev: true - /@csstools/selector-specificity@3.0.3(postcss-selector-parser@6.0.16): resolution: {integrity: sha512-KEPNw4+WW5AVEIyzC80rTbWEUatTW2lXpN8+8ILC8PiPeWPjwUzrPZDIOZ2wwqDmeqOYTdSGyL3+vE5GC3FB3Q==} engines: {node: ^14 || ^16 || >=18} @@ -2891,7 +2882,6 @@ packages: postcss-selector-parser: ^6.0.13 dependencies: postcss-selector-parser: 6.0.16 - dev: false /@csstools/utilities@1.0.0(postcss@8.4.38): resolution: {integrity: sha512-tAgvZQe/t2mlvpNosA4+CkMiZ2azISW5WPAcdSalZlEjQvUfghHxfQcrCiK/7/CrfAWVxyM88kGFYO82heIGDg==} @@ -3844,7 +3834,7 @@ packages: resolution: {integrity: sha512-o6eptw6Wf5df424N5Mmv8yqv/ySb/5+4M76gQUgDp/MrKUhMHnz7JAmREL94XZzVZI8T4RDu+XPMdZWzCjz0Ow==} dependencies: '@csstools/postcss-cascade-layers': 4.0.2(postcss@8.4.35) - '@pandacss/is-valid-prop': 0.32.0 + '@pandacss/is-valid-prop': 0.32.1 '@pandacss/logger': 0.32.0 '@pandacss/shared': 0.32.0 '@pandacss/token-dictionary': 0.32.0 @@ -3900,7 +3890,7 @@ packages: resolution: {integrity: sha512-DxMnoXhsmk3xwJ0GTI8WioN9Bf+U93I0bAluSpV7HLeLBqTRpbFfsLhM0Fzsfk/h5urrnFFQMS+4cX/KUP8iZA==} dependencies: '@pandacss/core': 0.32.0 - '@pandacss/is-valid-prop': 0.32.0 + '@pandacss/is-valid-prop': 0.32.1 '@pandacss/logger': 0.32.0 '@pandacss/shared': 0.32.0 '@pandacss/token-dictionary': 0.32.0 @@ -3912,8 +3902,8 @@ packages: ts-pattern: 5.0.8 dev: true - /@pandacss/is-valid-prop@0.32.0: - resolution: {integrity: sha512-IFI8S++hjP6Q2bIBamJV8BS1X+y5RPoTGu5VIkUPitF4zuesR5orfPIYFyixo4CweLCZce2rFng3yim/Cam9jA==} + /@pandacss/is-valid-prop@0.32.1: + resolution: {integrity: sha512-u+6TcjbP9lOLdlbYi5bjcDqUdJt8v2R7JBmcJhGtHYEz349emxPraOmG64FDJck5w30HV4TmF5yzbQBxgIFDJg==} dev: true /@pandacss/logger@0.32.0: @@ -8925,7 +8915,7 @@ packages: resolution: {integrity: sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==} dependencies: fast-glob: 3.3.2 - minimatch: 9.0.3 + minimatch: 9.0.4 mkdirp: 3.0.1 path-browserify: 1.0.1 dev: true @@ -10171,7 +10161,7 @@ packages: /@vue/compiler-core@3.4.19: resolution: {integrity: sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==} dependencies: - '@babel/parser': 7.24.0 + '@babel/parser': 7.24.4 '@vue/shared': 3.4.19 entities: 4.5.0 estree-walker: 2.0.2 @@ -10188,13 +10178,13 @@ packages: /@vue/compiler-sfc@3.4.19: resolution: {integrity: sha512-LQ3U4SN0DlvV0xhr1lUsgLCYlwQfUfetyPxkKYu7dkfvx7g3ojrGAkw0AERLOKYXuAGnqFsEuytkdcComei3Yg==} dependencies: - '@babel/parser': 7.24.0 + '@babel/parser': 7.24.4 '@vue/compiler-core': 3.4.19 '@vue/compiler-dom': 3.4.19 '@vue/compiler-ssr': 3.4.19 '@vue/shared': 3.4.19 estree-walker: 2.0.2 - magic-string: 0.30.7 + magic-string: 0.30.9 postcss: 8.4.38 source-map-js: 1.2.0 dev: true @@ -11094,7 +11084,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.23.0 - caniuse-lite: 1.0.30001588 + caniuse-lite: 1.0.30001605 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -11747,7 +11737,7 @@ packages: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} dependencies: browserslist: 4.23.0 - caniuse-lite: 1.0.30001588 + caniuse-lite: 1.0.30001605 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 dev: true @@ -11757,7 +11747,6 @@ packages: /caniuse-lite@1.0.30001605: resolution: {integrity: sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==} - dev: false /case-sensitive-paths-webpack-plugin@2.4.0: resolution: {integrity: sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==} @@ -11925,12 +11914,12 @@ packages: optional: true dev: true - /chromatic@11.1.0: - resolution: {integrity: sha512-0fGXzWqz7PTXmjYf+aSYB4jOUgDN/b8zkl6i8Syt+qGdhqqeW8Q1tHnHToC+eSuMxDLNVAVq28cMeYnRxL3MOA==} + /chromatic@11.0.0: + resolution: {integrity: sha512-utzRVqdMrpzYwZNf7dHWU0z0/rx6SH/FUVNozQxYHDtQfYUdEj+Ro4OSch5+Wsk2FoUmznJyLkaC2J839z1N7A==} hasBin: true peerDependencies: - '@chromatic-com/cypress': ^0.*.* || ^1.0.0 - '@chromatic-com/playwright': ^0.*.* || ^1.0.0 + '@chromatic-com/cypress': ^0.5.2 || ^1.0.0 + '@chromatic-com/playwright': ^0.5.2 || ^1.0.0 peerDependenciesMeta: '@chromatic-com/cypress': optional: true @@ -15199,7 +15188,7 @@ packages: function-bind: 1.1.2 has-proto: 1.0.3 has-symbols: 1.0.3 - hasown: 2.0.2 + hasown: 2.0.1 /get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} @@ -15640,6 +15629,7 @@ packages: engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 + dev: true /hast-util-heading-rank@3.0.0: resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} @@ -16126,7 +16116,7 @@ packages: engines: {node: '>= 0.4'} dependencies: get-intrinsic: 1.2.4 - hasown: 2.0.2 + hasown: 2.0.1 side-channel: 1.0.5 dev: true @@ -18625,15 +18615,6 @@ packages: hasBin: true dev: true - /mlly@1.4.2: - resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} - dependencies: - acorn: 8.11.3 - pathe: 1.1.1 - pkg-types: 1.0.3 - ufo: 1.3.2 - dev: true - /mlly@1.6.1: resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==} dependencies: @@ -19699,8 +19680,8 @@ packages: resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} dependencies: jsonc-parser: 3.2.0 - mlly: 1.4.2 - pathe: 1.1.1 + mlly: 1.6.1 + pathe: 1.1.2 dev: true /pkg-up@3.1.0: @@ -19982,7 +19963,7 @@ packages: caniuse-api: 3.0.0 cssnano-utils: 4.0.1(postcss@8.4.35) postcss: 8.4.35 - postcss-selector-parser: 6.0.15 + postcss-selector-parser: 6.0.16 dev: true /postcss-minify-selectors@6.0.2(postcss@8.4.35): @@ -19992,7 +19973,7 @@ packages: postcss: ^8.4.31 dependencies: postcss: 8.4.35 - postcss-selector-parser: 6.0.15 + postcss-selector-parser: 6.0.16 dev: true /postcss-modules-extract-imports@3.1.0(postcss@8.4.38): @@ -20039,7 +20020,7 @@ packages: postcss: ^8.2.14 dependencies: postcss: 8.4.35 - postcss-selector-parser: 6.0.15 + postcss-selector-parser: 6.0.16 dev: true /postcss-nesting@12.1.1(postcss@8.4.38): @@ -20990,8 +20971,8 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: true - /react-virtuoso@4.7.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-V1JIZLEwgX7R+YNkbY8dq6NcnIGKGWXe4mnMJJPsA2L4qeFKst0LY3mDk6sBCJyKRbMzYFxTZWyTT4QsA1JvVQ==} + /react-virtuoso@4.7.7(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-n9NdMNaAtxHYH6e3H6zr1Kb08sp+1XPVnfE4cEMgrvmPBLugd9eeJtQo/1uA+SHhGaPX3uqZOuQsfKbX1r8P/A==} engines: {node: '>=10'} peerDependencies: react: '>=16 || >=17 || >= 18' diff --git a/src/app/common/hooks/use-get-virtuoso-height.ts b/src/app/common/hooks/use-get-virtuoso-height.ts new file mode 100644 index 00000000000..3fd70904b03 --- /dev/null +++ b/src/app/common/hooks/use-get-virtuoso-height.ts @@ -0,0 +1,36 @@ +import { token } from 'leather-styles/tokens'; + +import { pxStringToNumber } from '@shared/utils/px-string-to-number'; + +import { useViewportMinWidth } from '@app/common/hooks/use-media-query'; +import { useHasLedgerKeys } from '@app/store/ledger/ledger.selectors'; +import { getHeightOffset } from '@app/ui/components/containers/dialog/dialog'; + +// virtuosoHeight = calc(InteractiveItem height - negative margin) +// calc(72px - 24px) = 58 +const virtuosoHeight = 58; + +type VirtuosoVariants = 'footer' | 'no-footer' | 'popup'; + +export function useGetVirtuosoHeight(accountNum: number, variant: VirtuosoVariants) { + const isAtleastBreakpointMd = useViewportMinWidth('md'); + + const isLedger = useHasLedgerKeys(); + const offset = getHeightOffset(true, !isLedger); + + // TODO rename dialogHeight as actionPopupHeight in monorepo + const actionPopupHeight = token('sizes.dialogHeight'); + // handles resizing of a full size browser - non popup + const isNarrowLargeWindow = + window.innerHeight > pxStringToNumber(actionPopupHeight) && !isAtleastBreakpointMd; + + if (accountNum > 10) { + if (variant === 'popup') return '50vh'; + if (isNarrowLargeWindow) { + return variant === 'footer' ? '85vh' : '95vh'; + } + return variant === 'footer' ? '70vh' : '85vh'; + } + const visibleAccounts = virtuosoHeight * accountNum; + return visibleAccounts + offset; +} diff --git a/src/app/common/hooks/use-media-query.ts b/src/app/common/hooks/use-media-query.ts new file mode 100644 index 00000000000..1d9249726cc --- /dev/null +++ b/src/app/common/hooks/use-media-query.ts @@ -0,0 +1,23 @@ +import { useEffect, useState } from 'react'; + +import { BreakpointToken, token } from 'leather-styles/tokens'; + +function useMediaQuery(query: string) { + const [matches, setMatches] = useState(false); + + useEffect(() => { + const media = window.matchMedia(query); + if (media.matches !== matches) { + setMatches(media.matches); + } + const listener = () => setMatches(media.matches); + window.addEventListener('resize', listener); + return () => window.removeEventListener('resize', listener); + }, [matches, query]); + + return matches; +} + +export function useViewportMinWidth(viewport: BreakpointToken) { + return useMediaQuery(`(min-width: ${token(`breakpoints.${viewport}`)})`); +} diff --git a/src/app/features/dialogs/switch-account-dialog/components/switch-account-list-item.tsx b/src/app/features/dialogs/switch-account-dialog/components/switch-account-list-item.tsx index 323882e01b3..75a80569141 100644 --- a/src/app/features/dialogs/switch-account-dialog/components/switch-account-list-item.tsx +++ b/src/app/features/dialogs/switch-account-dialog/components/switch-account-list-item.tsx @@ -1,4 +1,4 @@ -import { memo } from 'react'; +import { memo, useCallback } from 'react'; import { useAccountDisplayName } from '@app/common/hooks/account/use-account-names'; import { useSwitchAccount } from '@app/common/hooks/account/use-switch-account'; @@ -8,49 +8,44 @@ import { AcccountAddresses } from '@app/components/account/account-addresses'; import { AccountListItemLayout } from '@app/components/account/account-list-item.layout'; import { AccountNameLayout } from '@app/components/account/account-name'; import { useNativeSegwitSigner } from '@app/store/accounts/blockchain/bitcoin/native-segwit-account.hooks'; -import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; +import { type StacksAccount } from '@app/store/accounts/blockchain/stacks/stacks-account.models'; import { AccountAvatarItem } from '@app/ui/components/account/account-avatar/account-avatar-item'; interface SwitchAccountListItemProps { handleClose(): void; currentAccountIndex: number; index: number; + stxAccount: StacksAccount; } export const SwitchAccountListItem = memo( - ({ handleClose, currentAccountIndex, index }: SwitchAccountListItemProps) => { - const stacksAccounts = useStacksAccounts(); - const stxAddress = stacksAccounts[index]?.address || ''; + ({ handleClose, currentAccountIndex, index, stxAccount }: SwitchAccountListItemProps) => { const bitcoinSigner = useNativeSegwitSigner(index); const btcAddress = bitcoinSigner?.(0).address || ''; + const { address: stxAddress, stxPublicKey } = stxAccount; + const { isLoading, setIsLoading, setIsIdle } = useLoading( 'SWITCH_ACCOUNTS' + stxAddress || btcAddress ); const { handleSwitchAccount } = useSwitchAccount(handleClose); const { name, isLoading: isLoadingBnsName } = useAccountDisplayName({ - address: stxAddress, + address: stxAccount?.address, index, }); - const handleClick = async () => { + const handleClick = useCallback(async () => { setIsLoading(); setTimeout(async () => { await handleSwitchAccount(index); setIsIdle(); }, 80); - }; + }, [index, handleSwitchAccount, setIsIdle, setIsLoading]); return ( } accountName={{name}} - avatar={ - - } + avatar={} balanceLabel={} index={index} isLoading={isLoading} diff --git a/src/app/features/dialogs/switch-account-dialog/switch-account-dialog.tsx b/src/app/features/dialogs/switch-account-dialog/switch-account-dialog.tsx index 2ac6b1e0478..f88d3d5b376 100644 --- a/src/app/features/dialogs/switch-account-dialog/switch-account-dialog.tsx +++ b/src/app/features/dialogs/switch-account-dialog/switch-account-dialog.tsx @@ -4,16 +4,16 @@ import { Virtuoso } from 'react-virtuoso'; import { Box } from 'leather-styles/jsx'; import { useCreateAccount } from '@app/common/hooks/account/use-create-account'; +import { useGetVirtuosoHeight } from '@app/common/hooks/use-get-virtuoso-height'; import { useWalletType } from '@app/common/use-wallet-type'; import { useCurrentAccountIndex } from '@app/store/accounts/account'; import { useFilteredBitcoinAccounts } from '@app/store/accounts/blockchain/bitcoin/bitcoin.ledger'; import { useStacksAccounts } from '@app/store/accounts/blockchain/stacks/stacks-account.hooks'; import { useHasLedgerKeys } from '@app/store/ledger/ledger.selectors'; import { Button } from '@app/ui/components/button/button'; -import { Dialog, DialogProps, getHeightOffset } from '@app/ui/components/containers/dialog/dialog'; +import { Dialog, DialogProps } from '@app/ui/components/containers/dialog/dialog'; import { Footer } from '@app/ui/components/containers/footers/footer'; import { Header } from '@app/ui/components/containers/headers/header'; -import { virtuosoHeight, virtuosoStyles } from '@app/ui/shared/virtuoso'; import { AccountListUnavailable } from './components/account-list-unavailable'; import { SwitchAccountListItem } from './components/switch-account-list-item'; @@ -22,6 +22,7 @@ export const SwitchAccountDialog = memo(({ isShowing, onClose }: DialogProps) => const currentAccountIndex = useCurrentAccountIndex(); const createAccount = useCreateAccount(); const { whenWallet } = useWalletType(); + const isLedger = useHasLedgerKeys(); const stacksAccounts = useStacksAccounts(); @@ -34,22 +35,25 @@ export const SwitchAccountDialog = memo(({ isShowing, onClose }: DialogProps) => onClose(); }; + const accountNum = stacksAddressesNum || btcAddressesNum; + + const maxHeight = useGetVirtuosoHeight(accountNum, isLedger ? 'no-footer' : 'footer'); + if (isShowing && stacksAddressesNum === 0 && btcAddressesNum === 0) { return ; } + // #4370 SMELL without this early return the wallet crashes on new install with // : Wallet is neither of type `ledger` nor `software` // FIXME remove this when adding Create Account to Ledger in #2502 #4983 if (!isShowing) return null; - const accountNum = stacksAddressesNum || btcAddressesNum; - const maxAccountsShown = accountNum > 10 ? 10 : accountNum; - return ( } isShowing={isShowing} onClose={onClose} + contentMaxHeight={maxHeight} footer={whenWallet({ software: (