Skip to content

Commit 1aaf417

Browse files
authored
feat: migrate Button (perps scope) (#27870)
<!-- Please submit this PR as a draft initially. Do not mark it as "Ready for review" until the template has been completely filled out, and PR status checks have passed at least once. --> ## **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? --> Use `Button` from DSRN package. ## **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/DSYS-445 ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** https://github.com/user-attachments/assets/3a3f1703-91d7-4e57-9a90-19db116d4dd1 ### **After** https://github.com/user-attachments/assets/cb777793-b3a0-47cd-a4fb-e85a75dd9dfa ## **Pre-merge author checklist** - [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. ## **Pre-merge reviewer checklist** - [ ] 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** > Touches many Perps screens and components by swapping the underlying `Button` implementation and prop API, which could subtly change disabled/loading behavior and accessibility across critical trading flows. > > **Overview** > **Perps UI is migrated from the legacy component-library `Button` to `@metamask/design-system-react-native` across multiple views/components** (e.g., adjust margin, place/close order, market details/order book, transactions, tutorials, error states). This updates the button API usage (e.g., `variant` enums, `isFullWidth`, `isDisabled`, `isLoading`, and children-based labels) and aligns icon usage where needed. > > **Loading/disabled handling is normalized to the DSRN button behavior**, including removing bespoke spinners in `PerpsStopLossPromptBanner` and updating tests to assert on disabled/busy state and the button’s internal spinner instead of legacy `loading`/`disabled` props. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2a16909. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 4a0a472 commit 1aaf417

28 files changed

Lines changed: 466 additions & 354 deletions

app/components/UI/Perps/Views/PerpsAdjustMarginView/PerpsAdjustMarginView.test.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ jest.mock('../../components/PerpsOrderHeader', () => {
113113
};
114114
});
115115
jest.mock('../../components/PerpsAmountDisplay', () => 'PerpsAmountDisplay');
116+
jest.mock(
117+
'../../components/PerpsBottomSheetTooltip',
118+
() => 'PerpsBottomSheetTooltip',
119+
);
116120
jest.mock('../../components/PerpsSlider', () => {
117121
const ReactModule = jest.requireActual('react');
118122
const { View } = jest.requireActual('react-native');
@@ -128,6 +132,40 @@ jest.mock('../../components/PerpsSlider', () => {
128132
};
129133
});
130134

135+
jest.mock('@metamask/design-system-react-native', () => {
136+
const { TouchableOpacity, Text } = jest.requireActual('react-native');
137+
return {
138+
__esModule: true,
139+
Button: ({
140+
label,
141+
onPress,
142+
isDisabled,
143+
isLoading,
144+
children,
145+
...props
146+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
147+
}: any) => (
148+
<TouchableOpacity
149+
onPress={onPress}
150+
disabled={isDisabled}
151+
accessibilityRole="button"
152+
accessibilityLabel={label}
153+
{...props}
154+
>
155+
{!isLoading && <Text>{label ?? children}</Text>}
156+
</TouchableOpacity>
157+
),
158+
ButtonVariant: {
159+
Primary: 'Primary',
160+
Secondary: 'Secondary',
161+
},
162+
ButtonSize: {
163+
Lg: 'Lg',
164+
Sm: 'Sm',
165+
},
166+
};
167+
});
168+
131169
jest.mock('../../../../../component-library/components/Icons/Icon', () => {
132170
const ReactModule = jest.requireActual('react');
133171
const { View } = jest.requireActual('react-native');

app/components/UI/Perps/Views/PerpsAdjustMarginView/PerpsAdjustMarginView.tsx

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import Text, {
77
TextVariant,
88
TextColor,
99
} from '../../../../../component-library/components/Texts/Text';
10-
import Button, {
11-
ButtonVariants,
12-
ButtonWidthTypes,
10+
import {
11+
Button,
12+
ButtonVariant,
1313
ButtonSize,
14-
} from '../../../../../component-library/components/Buttons/Button';
14+
} from '@metamask/design-system-react-native';
1515
import { strings } from '../../../../../../locales/i18n';
1616
import { type Position, PERPS_CONSTANTS } from '@metamask/perps-controller';
1717
import styleSheet from './PerpsAdjustMarginView.styles';
@@ -447,51 +447,56 @@ const PerpsAdjustMarginView: React.FC = () => {
447447
<View style={styles.footer}>
448448
<Button
449449
testID={PerpsAdjustMarginViewSelectorsIDs.CONFIRM_BUTTON}
450-
variant={ButtonVariants.Primary}
450+
variant={ButtonVariant.Primary}
451451
size={ButtonSize.Lg}
452-
width={ButtonWidthTypes.Full}
453-
label={buttonLabel}
452+
isFullWidth
454453
onPress={handleConfirm}
455454
isDisabled={
456455
marginAmount <= 0 ||
457456
isAdjusting ||
458457
(!isAddMode && marginAmount > flooredMaxAmount)
459458
}
460-
loading={isAdjusting}
461-
/>
459+
isLoading={isAdjusting}
460+
>
461+
{buttonLabel}
462+
</Button>
462463
</View>
463464
) : (
464465
<View style={styles.keypadFooter}>
465466
<View style={styles.percentageButtonsContainer}>
466467
<Button
467-
variant={ButtonVariants.Secondary}
468+
variant={ButtonVariant.Secondary}
468469
size={ButtonSize.Md}
469-
label="25%"
470470
onPress={() => handlePercentagePress(0.25)}
471471
style={styles.percentageButton}
472-
/>
472+
>
473+
25%
474+
</Button>
473475
<Button
474-
variant={ButtonVariants.Secondary}
476+
variant={ButtonVariant.Secondary}
475477
size={ButtonSize.Md}
476-
label="50%"
477478
onPress={() => handlePercentagePress(0.5)}
478479
style={styles.percentageButton}
479-
/>
480+
>
481+
50%
482+
</Button>
480483
<Button
481-
variant={ButtonVariants.Secondary}
484+
variant={ButtonVariant.Secondary}
482485
size={ButtonSize.Md}
483-
label={strings('perps.deposit.max_button')}
484486
onPress={handleMaxPress}
485487
style={styles.percentageButton}
486-
/>
488+
>
489+
{strings('perps.deposit.max_button')}
490+
</Button>
487491
<Button
488492
testID={PerpsAdjustMarginViewSelectorsIDs.DONE_BUTTON}
489-
variant={ButtonVariants.Secondary}
493+
variant={ButtonVariant.Secondary}
490494
size={ButtonSize.Md}
491-
label={strings('perps.deposit.done_button')}
492495
onPress={handleDonePress}
493496
style={styles.percentageButton}
494-
/>
497+
>
498+
{strings('perps.deposit.done_button')}
499+
</Button>
495500
</View>
496501

497502
<Keypad

app/components/UI/Perps/Views/PerpsClosePositionView/PerpsClosePositionView.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,8 @@ describe('PerpsClosePositionView', () => {
346346
const confirmButton = getByTestId(
347347
PerpsClosePositionViewSelectorsIDs.CLOSE_POSITION_CONFIRM_BUTTON,
348348
);
349-
expect(confirmButton.props.loading).toBe(true);
349+
expect(confirmButton).toBeDisabled();
350+
expect(confirmButton.props.accessibilityState.busy).toBe(true);
350351
});
351352
});
352353

app/components/UI/Perps/Views/PerpsClosePositionView/PerpsClosePositionView.tsx

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ import { ScrollView, TouchableOpacity, View } from 'react-native';
1414
import { SafeAreaView } from 'react-native-safe-area-context';
1515
import { PerpsClosePositionViewSelectorsIDs } from '../../Perps.testIds';
1616
import { strings } from '../../../../../../locales/i18n';
17-
import Button, {
17+
import {
18+
Button,
19+
ButtonVariant,
1820
ButtonSize,
19-
ButtonVariants,
20-
ButtonWidthTypes,
21-
} from '../../../../../component-library/components/Buttons/Button';
21+
} from '@metamask/design-system-react-native';
2222
import Icon, {
2323
IconColor,
2424
IconName,
@@ -687,33 +687,37 @@ const PerpsClosePositionView: React.FC = () => {
687687
{Summary}
688688
<View style={styles.percentageButtonsContainer}>
689689
<Button
690-
variant={ButtonVariants.Secondary}
690+
variant={ButtonVariant.Secondary}
691691
size={ButtonSize.Md}
692-
label="25%"
693692
onPress={() => handlePercentagePress(0.25)}
694693
style={styles.percentageButton}
695-
/>
694+
>
695+
25%
696+
</Button>
696697
<Button
697-
variant={ButtonVariants.Secondary}
698+
variant={ButtonVariant.Secondary}
698699
size={ButtonSize.Md}
699-
label="50%"
700700
onPress={() => handlePercentagePress(0.5)}
701701
style={styles.percentageButton}
702-
/>
702+
>
703+
50%
704+
</Button>
703705
<Button
704-
variant={ButtonVariants.Secondary}
706+
variant={ButtonVariant.Secondary}
705707
size={ButtonSize.Md}
706-
label={strings('perps.deposit.max_button')}
707708
onPress={handleMaxPress}
708709
style={styles.percentageButton}
709-
/>
710+
>
711+
{strings('perps.deposit.max_button')}
712+
</Button>
710713
<Button
711-
variant={ButtonVariants.Secondary}
714+
variant={ButtonVariant.Secondary}
712715
size={ButtonSize.Md}
713-
label={strings('perps.deposit.done_button')}
714716
onPress={handleDonePress}
715717
style={styles.percentageButton}
716-
/>
718+
>
719+
{strings('perps.deposit.done_button')}
720+
</Button>
717721
</View>
718722

719723
<Keypad
@@ -732,14 +736,9 @@ const PerpsClosePositionView: React.FC = () => {
732736
{!isInputFocused && Summary}
733737
{!isInputFocused && (
734738
<Button
735-
label={
736-
isClosing
737-
? strings('perps.close_position.closing')
738-
: strings('perps.close_position.button')
739-
}
740-
variant={ButtonVariants.Primary}
739+
variant={ButtonVariant.Primary}
741740
size={ButtonSize.Lg}
742-
width={ButtonWidthTypes.Full}
741+
isFullWidth
743742
onPress={handleConfirm}
744743
isDisabled={
745744
isClosing ||
@@ -748,11 +747,15 @@ const PerpsClosePositionView: React.FC = () => {
748747
(orderType === 'market' && closePercentage === 0) ||
749748
!validationResult.isValid
750749
}
751-
loading={isClosing}
750+
isLoading={isClosing}
752751
testID={
753752
PerpsClosePositionViewSelectorsIDs.CLOSE_POSITION_CONFIRM_BUTTON
754753
}
755-
/>
754+
>
755+
{isClosing
756+
? strings('perps.close_position.closing')
757+
: strings('perps.close_position.button')}
758+
</Button>
756759
)}
757760
</View>
758761

app/components/UI/Perps/Views/PerpsHeroCardView/PerpsHeroCardView.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ import Text, {
1616
TextColor,
1717
TextVariant,
1818
} from '../../../../../component-library/components/Texts/Text';
19-
import Button, {
19+
import {
20+
Button,
21+
ButtonVariant,
2022
ButtonSize,
21-
ButtonVariants,
22-
ButtonWidthTypes,
23-
} from '../../../../../component-library/components/Buttons/Button';
23+
IconName as DSIconName,
24+
} from '@metamask/design-system-react-native';
2425
import {
2526
IconName,
2627
IconColor,
@@ -519,16 +520,17 @@ const PerpsHeroCardView: React.FC = () => {
519520
{/* Footer Button */}
520521
<View style={styles.footerButtonContainer}>
521522
<Button
522-
variant={ButtonVariants.Primary}
523+
variant={ButtonVariant.Primary}
523524
size={ButtonSize.Lg}
524-
width={ButtonWidthTypes.Full}
525-
label={strings('perps.pnl_hero_card.share_button')}
526-
startIconName={isSharing ? undefined : IconName.Share}
525+
isFullWidth
526+
startIconName={isSharing ? undefined : DSIconName.Share}
527527
onPress={handleShare}
528-
loading={isSharing}
528+
isLoading={isSharing}
529529
isDisabled={isSharing}
530530
testID={PerpsHeroCardViewSelectorsIDs.SHARE_BUTTON}
531-
/>
531+
>
532+
{strings('perps.pnl_hero_card.share_button')}
533+
</Button>
532534
</View>
533535
</SafeAreaView>
534536
);

app/components/UI/Perps/Views/PerpsMarketAndRiskFlow.view.test.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -270,12 +270,7 @@ describe('Market Browsing & Risk Awareness Flow', () => {
270270
expect(
271271
await screen.findByTestId(PerpsStopLossPromptSelectorsIDs.CONTAINER),
272272
).toBeOnTheScreen();
273-
expect(
274-
screen.getByTestId(PerpsStopLossPromptSelectorsIDs.LOADING),
275-
).toBeOnTheScreen();
276-
expect(
277-
screen.queryByText(strings('perps.stop_loss_prompt.set_button')),
278-
).not.toBeOnTheScreen();
273+
expect(screen.getByTestId('spinner-container')).toBeOnTheScreen();
279274

280275
// Stop-loss success state — button shows check icon
281276
cleanup();

0 commit comments

Comments
 (0)