Skip to content

Commit 7675a57

Browse files
refactor: migrate backup/sync UI to design-system (#29444)
<!-- 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? --> Migrate "Backup & sync" UI components to `@metamask/design-system-react-native`, specifically `Text`, `Icon`, and `BottomSheet`. ## **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** Fixes: https://consensyssoftware.atlassian.net/browse/MUL-1684 ## **Manual testing steps** - Onboarding - Go through the onboarding flow - Before finishing, disable "Backup & Sync" - Check that there aren't any UI regressions - Settings - Disable "Backup & Sync" and enable it back - Check that there aren't any UI regressions ## **Screenshots/Recordings** ### Onboarding <img width="300" alt="Screenshot 2026-05-04 at 2 08 04 PM" src="https://github.com/user-attachments/assets/e6d5dd7c-7d96-4cd9-867d-c677cf314fe5" /> <img width="300" alt="Screenshot 2026-05-04 at 2 08 13 PM" src="https://github.com/user-attachments/assets/0a56a630-f1db-4d08-96ab-a0459026aebc" /> ### Settings <img width="300" alt="Screenshot 2026-05-04 at 2 21 33 PM" src="https://github.com/user-attachments/assets/a9faa395-8309-4ece-a73a-96cf3d737257" /> <img width="300" alt="Screenshot 2026-05-04 at 2 21 40 PM" src="https://github.com/user-attachments/assets/f8e33356-9402-464d-9262-f3809db383f4" /> ## **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. --> - [ ] 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). - [ ] I've completed the PR template to the best of my ability - [ ] I've included tests if applicable - [ ] I've documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] 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) - [ ] I've tested on Android - Ideally on a mid-range device; emulator is acceptable - [ ] 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 - [ ] 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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Primarily a UI refactor, but it changes shared component imports/enums and adds a global Jest mock for the design-system `BottomSheet`, which could affect rendering/tests across the suite. > > **Overview** > Migrates the Backup & Sync settings/onboarding UI to `@metamask/design-system-react-native` primitives (`Text`, `Icon`, `BottomSheet`), updating variant/color enum values (e.g., `HeadingSm`, `TextAlternative`, `SuccessDefault`). > > Updates the shared notification modal (`Notification/Modal`) to use design-system `Text`/`Icon` and adjusts typography variants accordingly. > > Improves test stability by adding a global Jest mock for the design-system `BottomSheet` in `testSetup.js` (synchronous open/close callbacks) and stubbing `QuickBuyBottomSheet` in `TraderPositionView` tests to avoid unintended mounts/selector dependencies. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ad2f26b. 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 85015f7 commit 7675a57

6 files changed

Lines changed: 106 additions & 29 deletions

File tree

app/components/UI/Identity/BackupAndSyncFeaturesToggles/BackupAndSyncFeaturesToggles.tsx

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import React, { useCallback, useMemo } from 'react';
22
import { View, Switch, InteractionManager } from 'react-native';
33

4-
import Text, {
4+
import {
5+
Text,
56
TextColor,
67
TextVariant,
7-
} from '../../../../component-library/components/Texts/Text';
8+
Icon,
9+
IconName,
10+
} from '@metamask/design-system-react-native';
811
import { useTheme } from '../../../../util/theme';
912
import styles from './BackupAndSyncFeaturesToggles.styles';
1013
import { useBackupAndSync } from '../../../../util/identity/hooks/useBackupAndSync';
@@ -16,9 +19,6 @@ import {
1619
selectIsBackupAndSyncUpdateLoading,
1720
} from '../../../../selectors/identity';
1821
import { BACKUPANDSYNC_FEATURES } from '@metamask/profile-sync-controller/user-storage';
19-
import Icon, {
20-
IconName,
21-
} from '../../../../component-library/components/Icons/Icon';
2222
import { strings } from '../../../../../locales/i18n';
2323
import { MetaMetricsEvents } from '../../../../core/Analytics';
2424
import { useAnalytics } from '../../../hooks/useAnalytics/useAnalytics';
@@ -126,10 +126,10 @@ const BackupAndSyncFeaturesToggles = () => {
126126
return (
127127
<View style={styles.setting}>
128128
<View style={styles.heading}>
129-
<Text variant={TextVariant.HeadingSM}>
129+
<Text variant={TextVariant.HeadingSm}>
130130
{strings('backupAndSync.manageWhatYouSync.title')}
131131
</Text>
132-
<Text variant={TextVariant.BodySM} color={TextColor.Alternative}>
132+
<Text variant={TextVariant.BodySm} color={TextColor.TextAlternative}>
133133
{strings('backupAndSync.manageWhatYouSync.description')}
134134
</Text>
135135
</View>

app/components/UI/Identity/BackupAndSyncToggle/BackupAndSyncToggle.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import React, { useCallback, useEffect } from 'react';
33
import { View, Switch, Linking, InteractionManager } from 'react-native';
44
// import { useNavigation } from '@react-navigation/native';
55

6-
import Text, {
6+
import {
7+
Text,
78
TextVariant,
89
TextColor,
9-
} from '../../../../component-library/components/Texts/Text';
10+
} from '@metamask/design-system-react-native';
1011
import { useTheme } from '../../../../util/theme';
1112
// import { strings } from '../../../../../locales/i18n';
1213
import styles from './BackupAndSyncToggle.styles';
@@ -150,7 +151,7 @@ const BackupAndSyncToggle = ({
150151
return (
151152
<View style={styles.setting}>
152153
<View style={styles.heading}>
153-
<Text variant={TextVariant.HeadingSM}>
154+
<Text variant={TextVariant.HeadingSm}>
154155
{strings('backupAndSync.title')}
155156
</Text>
156157
<Switch
@@ -165,9 +166,9 @@ const BackupAndSyncToggle = ({
165166
testID={BACKUP_AND_SYNC_TOGGLE_TEST_IDS.TOGGLE}
166167
/>
167168
</View>
168-
<Text variant={TextVariant.BodyMD} color={TextColor.Alternative}>
169+
<Text variant={TextVariant.BodyMd} color={TextColor.TextAlternative}>
169170
{strings('backupAndSync.enable.description')}
170-
<Text color={TextColor.Info} onPress={handleLink}>
171+
<Text color={TextColor.InfoDefault} onPress={handleLink}>
171172
{strings('backupAndSync.privacyLink')}
172173
</Text>
173174
</Text>

app/components/UI/Identity/ConfirmTurnOnBackupAndSyncModal/ConfirmTurnOnBackupAndSyncModal.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import React, { useRef } from 'react';
22

3-
import BottomSheet, {
4-
BottomSheetRef,
5-
} from '../../../../component-library/components/BottomSheets/BottomSheet';
6-
import { strings } from '../../../../../locales/i18n';
7-
83
import {
4+
BottomSheet,
5+
type BottomSheetRef,
96
IconColor,
107
IconName,
118
IconSize,
12-
} from '../../../../component-library/components/Icons/Icon';
9+
} from '@metamask/design-system-react-native';
10+
import { strings } from '../../../../../locales/i18n';
1311
import ModalContent from '../../Notification/Modal';
1412
import { toggleBasicFunctionality } from '../../../../actions/settings';
1513
import { useParams } from '../../../../util/navigation/navUtils';
@@ -45,7 +43,7 @@ const ConfirmTurnOnBackupAndSyncModal = () => {
4543
const turnContent = {
4644
icon: {
4745
name: IconName.Check,
48-
color: IconColor.Success,
46+
color: IconColor.SuccessDefault,
4947
},
5048
bottomSheetTitle: strings('backupAndSync.enable.title'),
5149
bottomSheetMessage: strings('backupAndSync.enable.confirmation'),

app/components/UI/Notification/Modal/index.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
import React from 'react';
22
import { View } from 'react-native';
33
import Checkbox from '../../../../component-library/components/Checkbox/Checkbox';
4-
import Icon, {
5-
IconColor,
6-
IconName,
7-
IconSize,
8-
} from '../../../../component-library/components/Icons/Icon';
9-
import Text, {
10-
TextVariant,
11-
} from '../../../../component-library/components/Texts/Text';
124
import {
135
Button,
146
ButtonVariant,
157
ButtonSize,
8+
Icon,
9+
IconColor,
10+
IconName,
11+
IconSize,
12+
Text,
13+
TextVariant,
1614
} from '@metamask/design-system-react-native';
1715
import createStyles from './styles';
1816
import { useTheme } from '../../../../util/theme';
@@ -60,10 +58,10 @@ const ModalContent = ({
6058
size={iconSize}
6159
style={styles.icon}
6260
/>
63-
<Text variant={TextVariant.HeadingMD} style={styles.title}>
61+
<Text variant={TextVariant.HeadingMd} style={styles.title}>
6462
{title}
6563
</Text>
66-
<Text variant={TextVariant.BodyMD} style={styles.description}>
64+
<Text variant={TextVariant.BodyMd} style={styles.description}>
6765
{message}
6866
</Text>
6967
<View style={styles.bottom}>

app/components/Views/SocialLeaderboard/TraderPositionView/TraderPositionView.test.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,17 @@ jest.mock('../../../../core/ClipboardManager', () => ({
9292
setString: jest.fn().mockResolvedValue(undefined),
9393
}));
9494

95+
// Pressing buy mounts QuickBuyBottomSheet. Jest's global mock for design-system
96+
// `BottomSheet` (see app/util/test/testSetup.js) invokes `onOpenBottomSheet`'s
97+
// callback synchronously, so `QuickBuyBottomSheetContent` mounts in the same turn
98+
// and runs `useQuickBuyBottomSheet` (bridge selectors, device version compare,
99+
// NetworkController, …). This file intentionally uses a minimal Redux store, so
100+
// we stub the sheet here.
101+
jest.mock('./components/QuickBuyBottomSheet', () => ({
102+
__esModule: true,
103+
default: () => null,
104+
}));
105+
95106
jest.mock('../../../../util/haptics', () => {
96107
const actual = jest.requireActual<typeof import('../../../../util/haptics')>(
97108
'../../../../util/haptics',

app/util/test/testSetup.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -850,6 +850,75 @@ jest.mock('../../component-library/components/BottomSheets/BottomSheet', () => {
850850
};
851851
});
852852

853+
// Mock @metamask/design-system-react-native BottomSheet to render children immediately
854+
// and run open/close callbacks synchronously (bypasses reanimated animations).
855+
// Matches the component-library BottomSheet mock above; components that migrated
856+
// to the design-system sheet otherwise trigger act() warnings from Animated updates.
857+
jest.mock('@metamask/design-system-react-native', () => {
858+
const React = require('react');
859+
const PropTypes = require('prop-types');
860+
const { View } = require('react-native');
861+
const actual = jest.requireActual('@metamask/design-system-react-native');
862+
863+
const BottomSheet = React.forwardRef(
864+
(
865+
{
866+
children,
867+
onClose,
868+
onOpen,
869+
goBack,
870+
style,
871+
twClassName: _twClassName,
872+
testID,
873+
accessibilityLabel,
874+
},
875+
ref,
876+
) => {
877+
React.useImperativeHandle(ref, () => ({
878+
onOpenBottomSheet: (callback) => {
879+
onOpen?.();
880+
callback?.();
881+
},
882+
onCloseBottomSheet: (callback) => {
883+
const hasCallback = Boolean(callback);
884+
onClose?.(hasCallback);
885+
goBack?.();
886+
callback?.();
887+
},
888+
}));
889+
return React.createElement(
890+
View,
891+
{
892+
testID: testID || 'design-system-bottom-sheet-mock',
893+
style,
894+
accessibilityLabel,
895+
},
896+
children,
897+
);
898+
},
899+
);
900+
BottomSheet.displayName = 'BottomSheet';
901+
BottomSheet.propTypes = {
902+
children: PropTypes.node,
903+
onClose: PropTypes.func,
904+
onOpen: PropTypes.func,
905+
goBack: PropTypes.func,
906+
style: PropTypes.oneOfType([
907+
PropTypes.object,
908+
PropTypes.array,
909+
PropTypes.number,
910+
]),
911+
twClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
912+
testID: PropTypes.string,
913+
accessibilityLabel: PropTypes.string,
914+
};
915+
916+
return {
917+
...actual,
918+
BottomSheet,
919+
};
920+
});
921+
853922
// Mock react-native-modal to render children immediately (bypasses animation)
854923
jest.mock('react-native-modal', () => {
855924
const React = require('react');

0 commit comments

Comments
 (0)