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: (