Skip to content

Commit f3ba475

Browse files
refactor: migrate AccountDetails leaf components to design system (#29765)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until this PR meets the canonical Definition of Ready For Review in `docs/readme/ready-for-review.md`. In short: the template must be materially complete (not just section titles present), all status checks must be currently passing, and the only expected follow-up commits must be reviewer-driven. --> ## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> Part **1 of 2** for [MUL-1689](https://consensyssoftware.atlassian.net/browse/MUL-1689). Migrates Account Details **leaf** UI from deprecated `app/component-library` to `@metamask/design-system-react-native`: - `AccountInfo.tsx` — `AvatarAccount`, `Text`, semantic colors and typography. - `ExportCredentials.tsx` — `Text`, `Icon`, `TextVariant` / `TextColor` / `FontWeight` from the design system. - `SmartAccount.tsx` — `Text`, `Icon`. - `SmartAccountNetworkList.tsx` — `Skeleton` from the design system (alongside existing `Box`). - `SmartAccount.test.tsx` — `Icon` import from the design system for `UNSAFE_getByType`. No changes to `BaseAccountDetails` or `SmartAccountModal` in this PR; those land in part 2. ## **Changelog** <!-- If this PR is not End-User-Facing and should not show up in the CHANGELOG, you can choose to either: 1. Write `CHANGELOG entry: null` 2. Label with `no-changelog` If this PR is End-User-Facing, please write a short User-Facing description in the past tense like: `CHANGELOG entry: Added a new tab for users to see their NFTs` `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker` (This helps the Release Engineer do their job more quickly and accurately) --> CHANGELOG entry: null ## **Related issues** Refs: https://consensyssoftware.atlassian.net/browse/MUL-1689 ## **Manual testing steps** ```gherkin Feature: Multichain Account Details (leaf components, part 1) Scenario: Account info and export rows render after migration Given the user has opened Multichain Account Details for an HD account When the screen loads Then the account avatar, name, and address copy area render without error And export credential rows (when applicable) still show labels and chevrons Scenario: Smart account entry row Given the user views Account Details for an EVM account When the Smart Account row is visible Then the row label matches copy and the row remains tappable ``` Note: There is a bug where the SCA toggles are not being displayed, this is not caused by this PR ## **Screenshots/Recordings** <img width="250" alt="Simulator Screenshot - iPhone 16 - 2026-05-13 at 16 25 34" src="https://github.com/user-attachments/assets/796f094b-263b-4643-9c64-b33efad67d87" /> <img width="250" alt="Simulator Screenshot - iPhone 16 - 2026-05-13 at 16 25 37" src="https://github.com/user-attachments/assets/8d7e371b-4b1c-46ae-b382-687cc27dc318" /> ## **Pre-merge author checklist** <!-- Every checklist item must be consciously assessed before marking this PR as "Ready for review". A checked box means you deliberately considered that responsibility, not that you literally performed every action listed. Unchecked boxes are ambiguous: they are not an implicit "N/A" and they are not a silent "skip". See `docs/readme/ready-for-review.md` for the full checklist semantics. --> - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I've applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. #### Performance checks (if applicable) - [x] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [x] I've tested with a power user scenario - Use these [power-user SRPs](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/edit-v2/401401446401?draftShareId=9d77e1e1-4bdc-4be1-9ebb-ccd916988d93) to import wallets with many accounts and tokens - [x] I've instrumented key operations with Sentry traces for production performance metrics - See [`trace()`](/app/util/trace.ts) for usage and [`addToken`](/app/components/Views/AddAsset/components/AddCustomToken/AddCustomToken.tsx#L274) for an example For performance guidelines and tooling, see the [Performance Guide](https://consensyssoftware.atlassian.net/wiki/spaces/TL1/pages/400085549067/Performance+Guide+for+Engineers). ## **Pre-merge reviewer checklist** <!-- Reviewer checklist items follow the same semantics as the author checklist: an unchecked box is ambiguous, a checked box means the reviewer consciously assessed that responsibility. See `docs/readme/ready-for-review.md`. --> - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. [MUL-1689]: https://consensyssoftware.atlassian.net/browse/MUL-1689?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Primarily a UI refactor swapping deprecated component-library primitives for design-system equivalents, with minor test adjustments; functional behavior should remain the same. > > **Overview** > Migrates Multichain Account Details *leaf* UI components (`AccountInfo`, `ExportCredentials`, `SmartAccount`) from deprecated `app/component-library` primitives to `@metamask/design-system-react-native` components, updating typography, icon usage, and semantic colors. > > Updates related tests to match the new design-system components (e.g., `Icon` type lookup) and stabilizes `MultichainAccountSelectorList` search tests by mocking `Maskicon`, extracting a shared `performSearch` helper, and increasing the debounce wait timeout for CI reliability. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit dddbc74. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent a75f9df commit f3ba475

6 files changed

Lines changed: 74 additions & 60 deletions

File tree

app/component-library/components-temp/MultichainAccounts/MultichainAccountSelectorList/MultichainAccountSelectorList.test.tsx

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ jest.mock('react-native-gesture-handler', () => {
1515
ScrollView: RN.ScrollView,
1616
};
1717
});
18+
19+
jest.mock('@metamask/design-system-react-native', () => {
20+
const actual = jest.requireActual('@metamask/design-system-react-native');
21+
const ReactActual = jest.requireActual('react');
22+
const { View } = jest.requireActual('react-native');
23+
24+
return {
25+
...actual,
26+
Maskicon: ({ testID }: { testID?: string }) =>
27+
ReactActual.createElement(View, { testID }),
28+
};
29+
});
1830
import {
1931
AccountGroupObject,
2032
AccountWalletObject,
@@ -85,6 +97,7 @@ jest.mock('../../../../util/analytics/analytics', () => ({
8597

8698
describe('MultichainAccountSelectorList', () => {
8799
const mockOnSelectAccount = jest.fn();
100+
const SEARCH_WAIT_TIMEOUT_MS = 2000;
88101

89102
beforeEach(() => {
90103
jest.clearAllMocks();
@@ -106,7 +119,7 @@ describe('MultichainAccountSelectorList', () => {
106119
fireEvent.changeText(searchInput, searchTerm);
107120
});
108121

109-
// Wait for debounce (1s) to complete and filtering to occur. Use a
122+
// Wait for debounce to complete and filtering to occur. Use a
110123
// generous timeout so CI has time for debounce + re-render + list update.
111124
await waitFor(
112125
() => {
@@ -117,7 +130,7 @@ describe('MultichainAccountSelectorList', () => {
117130
expect(queryByText(text)).not.toBeOnTheScreen();
118131
});
119132
},
120-
{ timeout: 1000 },
133+
{ timeout: SEARCH_WAIT_TIMEOUT_MS },
121134
);
122135
};
123136

@@ -633,33 +646,20 @@ describe('MultichainAccountSelectorList', () => {
633646
[account1],
634647
);
635648

636-
// Search with different cases
637-
const searchInput = getByTestId(
638-
MULTICHAIN_ACCOUNT_SELECTOR_SEARCH_INPUT_TESTID,
639-
);
640-
641-
// Test uppercase search
642-
await act(async () => {
643-
fireEvent.changeText(searchInput, 'MY ACCOUNT');
644-
});
645-
await waitFor(
646-
() => {
647-
expect(queryByText('My Account')).toBeTruthy();
648-
expect(queryByText('Test Account')).toBeFalsy();
649-
},
650-
{ timeout: 1000 }, // Increased timeout for debounced search
649+
await performSearch(
650+
getByTestId,
651+
queryByText,
652+
'MY ACCOUNT',
653+
['My Account'],
654+
['Test Account'],
651655
);
652656

653-
// Test mixed case search
654-
await act(async () => {
655-
fireEvent.changeText(searchInput, 'tEsT aCcOuNt');
656-
});
657-
await waitFor(
658-
() => {
659-
expect(queryByText('My Account')).toBeFalsy();
660-
expect(queryByText('Test Account')).toBeTruthy();
661-
},
662-
{ timeout: 1000 }, // Increased timeout for debounced search
657+
await performSearch(
658+
getByTestId,
659+
queryByText,
660+
'tEsT aCcOuNt',
661+
['Test Account'],
662+
['My Account'],
663663
);
664664
});
665665

app/components/Views/MultichainAccounts/AccountDetails/components/AccountInfo/AccountInfo.tsx

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@ import {
55
FlexDirection,
66
} from '../../../../../UI/Box/box.types';
77
import { Box } from '../../../../../UI/Box/Box';
8-
import Avatar, {
9-
AvatarVariant,
10-
} from '../../../../../../component-library/components/Avatars/Avatar';
8+
import {
9+
AvatarAccount,
10+
AvatarAccountSize,
11+
FontWeight,
12+
IconColor,
13+
Text,
14+
TextColor,
15+
TextVariant,
16+
} from '@metamask/design-system-react-native';
1117
import { InternalAccount } from '@metamask/keyring-internal-api';
1218
import { useStyles } from '../../../../../hooks/useStyles';
1319
import styleSheet from './AccountInfo.styles';
14-
import Text, {
15-
TextVariant,
16-
} from '../../../../../../component-library/components/Texts/Text';
1720
import AddressCopy from '../../../../../UI/AddressCopy';
18-
import { IconColor } from '@metamask/design-system-react-native';
1921
import { formatAddress } from '../../../../../../util/address';
2022
import { getFormattedAddressFromInternalAccount } from '../../../../../../core/Multichain/utils';
2123

@@ -24,19 +26,22 @@ interface AccountInfoProps {
2426
}
2527

2628
export const AccountInfo = ({ account }: AccountInfoProps) => {
27-
const { styles, theme } = useStyles(styleSheet, {});
28-
const { colors } = theme;
29+
const { styles } = useStyles(styleSheet, {});
2930

3031
const formattedAddress = getFormattedAddressFromInternalAccount(account);
3132

3233
return (
3334
<Box flexDirection={FlexDirection.Column} alignItems={AlignItems.center}>
34-
<Avatar
35+
<AvatarAccount
3536
style={styles.avatar}
36-
variant={AvatarVariant.Account}
37-
accountAddress={formattedAddress}
37+
address={formattedAddress}
38+
size={AvatarAccountSize.Md}
3839
/>
39-
<Text variant={TextVariant.BodyLGMedium} style={styles.address}>
40+
<Text
41+
variant={TextVariant.BodyLg}
42+
fontWeight={FontWeight.Medium}
43+
style={styles.address}
44+
>
4045
{account.metadata.name}
4146
</Text>
4247
<Box
@@ -45,7 +50,11 @@ export const AccountInfo = ({ account }: AccountInfoProps) => {
4550
alignItems={AlignItems.center}
4651
justifyContent={JustifyContent.center}
4752
>
48-
<Text variant={TextVariant.BodyLGMedium} color={colors.primary.default}>
53+
<Text
54+
variant={TextVariant.BodyLg}
55+
fontWeight={FontWeight.Medium}
56+
color={TextColor.PrimaryDefault}
57+
>
4958
{formatAddress(formattedAddress, 'short')}
5059
</Text>
5160
<AddressCopy iconColor={IconColor.PrimaryDefault} />

app/components/Views/MultichainAccounts/AccountDetails/components/ExportCredentials/ExportCredentials.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,17 @@ import {
88
areAddressesEqual,
99
} from '../../../../../../util/address';
1010
import styleSheet from './ExportCredentials.styles';
11-
import Text, {
11+
import {
12+
FontWeight,
13+
Icon,
14+
IconName,
15+
IconSize,
16+
Text,
1217
TextColor,
1318
TextVariant,
14-
} from '../../../../../../component-library/components/Texts/Text';
19+
} from '@metamask/design-system-react-native';
1520
import { strings } from '../../../../../../../locales/i18n';
1621
import { TouchableOpacity } from 'react-native';
17-
import Icon, {
18-
IconName,
19-
IconSize,
20-
} from '../../../../../../component-library/components/Icons/Icon';
2122
import { useHdKeyringsWithSnapAccounts } from '../../../../../hooks/useHdKeyringsWithSnapAccounts';
2223
import { useNavigation } from '@react-navigation/native';
2324
import Routes from '../../../../../../constants/navigation/Routes';
@@ -109,7 +110,8 @@ export const ExportCredentials = ({ account }: ExportCredentialsProps) => {
109110
alignItems={AlignItems.center}
110111
>
111112
<Text
112-
variant={TextVariant.BodyMDMedium}
113+
variant={TextVariant.BodyMd}
114+
fontWeight={FontWeight.Medium}
113115
data-testid={ExportCredentialsIds.SRP_NAME}
114116
>
115117
{srpName}
@@ -122,8 +124,9 @@ export const ExportCredentials = ({ account }: ExportCredentialsProps) => {
122124
>
123125
{showSeedphraseBackReminder && (
124126
<Text
125-
variant={TextVariant.BodyMDMedium}
126-
color={TextColor.Error}
127+
variant={TextVariant.BodyMd}
128+
fontWeight={FontWeight.Medium}
129+
color={TextColor.ErrorDefault}
127130
>
128131
{strings('multichain_accounts.export_credentials.backup')}
129132
</Text>
@@ -145,7 +148,8 @@ export const ExportCredentials = ({ account }: ExportCredentialsProps) => {
145148
alignItems={AlignItems.center}
146149
>
147150
<Text
148-
variant={TextVariant.BodyMDMedium}
151+
variant={TextVariant.BodyMd}
152+
fontWeight={FontWeight.Medium}
149153
data-testid={ExportCredentialsIds.PRIVATE_KEY_LINK}
150154
>
151155
{strings('multichain_accounts.account_details.private_key')}

app/components/Views/MultichainAccounts/AccountDetails/components/SmartAccount/SmartAccount.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { KeyringTypes } from '@metamask/keyring-controller';
1010
import renderWithProvider from '../../../../../../util/test/renderWithProvider';
1111
import { InternalAccount } from '@metamask/keyring-internal-api';
1212
import { strings } from '../../../../../../../locales/i18n';
13-
import Icon from '../../../../../../component-library/components/Icons/Icon';
13+
import { Icon } from '@metamask/design-system-react-native';
1414

1515
const mockNavigate = jest.fn();
1616
jest.mock('@react-navigation/native', () => ({

app/components/Views/MultichainAccounts/AccountDetails/components/SmartAccount/SmartAccount.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import React, { useCallback } from 'react';
22

33
import { SwitchAccountModalSelectorIDs } from '../../../../../../components/Views/confirmations/components/modals/switch-account-type-modal/SwitchAccountModal.testIds';
4-
import Text, {
4+
import {
5+
FontWeight,
6+
Icon,
7+
IconName,
8+
IconSize,
9+
Text,
510
TextVariant,
6-
} from '../../../../../../component-library/components/Texts/Text';
11+
} from '@metamask/design-system-react-native';
712
import { Box } from '../../../../../UI/Box/Box';
813
import { strings } from '../../../../../../../locales/i18n';
914
import { InternalAccount } from '@metamask/keyring-internal-api';
@@ -17,10 +22,6 @@ import styleSheet from './SmartAccount.styles';
1722
import { useStyles } from '../../../../../hooks/useStyles';
1823
import { useNavigation } from '@react-navigation/native';
1924
import { TouchableOpacity } from 'react-native';
20-
import Icon, {
21-
IconName,
22-
IconSize,
23-
} from '../../../../../../component-library/components/Icons/Icon';
2425

2526
interface SmartAccountDetailsProps {
2627
account: InternalAccount;
@@ -49,7 +50,7 @@ export const SmartAccountDetails = ({ account }: SmartAccountDetailsProps) => {
4950
justifyContent={JustifyContent.spaceBetween}
5051
alignItems={AlignItems.center}
5152
>
52-
<Text variant={TextVariant.BodyMDMedium}>
53+
<Text variant={TextVariant.BodyMd} fontWeight={FontWeight.Medium}>
5354
{strings('multichain_accounts.account_details.smart_account')}
5455
</Text>
5556
<Box

app/components/Views/MultichainAccounts/AccountDetails/components/SmartAccountNetworkList/SmartAccountNetworkList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ import React, { useMemo, useEffect } from 'react';
22
import { BackHandler } from 'react-native';
33
import { useNavigation } from '@react-navigation/native';
44
import { Box } from '@metamask/design-system-react-native';
5+
import { Skeleton } from '../../../../../../component-library/components-temp/Skeleton';
56
import { useEIP7702Networks } from '../../../../confirmations/hooks/7702/useEIP7702Networks';
67
import AccountNetworkRow from '../../../../confirmations/components/modals/switch-account-type-modal/account-network-row';
78
import { Hex } from '@metamask/utils';
89
import { useStyles } from '../../../../../hooks/useStyles';
910
import styleSheet from './SmartAccountNetworkList.styles';
10-
import { Skeleton } from '../../../../../../component-library/components-temp/Skeleton';
1111
import { SMART_ACCOUNT_NETWORK_LIST_TEST_IDS } from './SmartAccountNetworkList.testIds';
1212

1313
interface SmartAccountNetworkListProps {

0 commit comments

Comments
 (0)