Skip to content

fix(ramp): migrate sell amount keypad to MMDS BottomSheetDialog - #33311

Draft
georgewrmarshall wants to merge 10 commits into
mainfrom
cursor/ramp-sell-keypad-mmds-bottomsheet-35fc
Draft

fix(ramp): migrate sell amount keypad to MMDS BottomSheetDialog#33311
georgewrmarshall wants to merge 10 commits into
mainfrom
cursor/ramp-sell-keypad-mmds-bottomsheet-35fc

Conversation

@georgewrmarshall

@georgewrmarshall georgewrmarshall commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

Ramp Aggregator Sell (and Buy) amount entry used a custom Animated.View keypad overlay with Reanimated translateY animation and manual surface styling. This migrates the keypad to MMDS BottomSheetDialog from @metamask/design-system-react-native, following the Swaps numpad migration pattern in PR #33077.

Changes:

  • Replace the inline animated keypad overlay with MMDS BottomSheetDialog
  • Add isKeypadOpen state to manage bottom sheet lifecycle separately from amount highlight state
  • Remove keypadContainer / Reanimated animation hacks from BuildQuote.styles.ts
  • Remove custom section background from Aggregator QuickAmounts so MMDS owns the elevated surface
  • Assert keypad sheet presence via build-quote-amount-keypad-bottom-sheet test ID
  • Fix native-asset gas unit tests to render after mock setup (previously relied on always-mounted keypad DOM)

Changelog

CHANGELOG entry: null

Related issues

Fixes: TMCU-1084

Manual testing steps

Feature: Ramp sell amount keypad

  Scenario: user opens sell amount keypad
    Given the user is on the Ramp Aggregator "Amount to sell" screen (Sell flow)
    When the user taps the Amount field
    Then the amount keypad opens in an MMDS BottomSheetDialog with quick amounts (25%/50%/75%/MAX for sell), numpad, and Done button
    And tapping Done or the backdrop dismisses the sheet

  Scenario: user enters sell amount via keypad
    Given the sell amount keypad is open
    When the user taps digits and Done
    Then the entered amount is reflected in the amount field and Get quotes enables when valid

Screenshots/Recordings

N/A — UI migration to MMDS bottom sheet; QA screenshot requested in TMCU-1084 (Sell screen with amount keypad open).

Before

sell.before.mov

Comparing with swap

before.compare.mov

After

after720.mov

Comparing with swap

after.compare.mov

Pre-merge author checklist

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 to import wallets with many accounts and tokens
  • I've instrumented key operations with Sentry traces for production performance metrics

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.
Open in Web Open in Cursor 

@metamask-ci metamask-ci Bot added the team-design-system All issues relating to design system in Mobile label Jul 15, 2026
@metamask-ci

metamask-ci Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

PR template — items to address before "Ready for review"

Warnings — informational, address before merging:

  • Pre-merge author checklist has unchecked items (e.g. "I've documented my code using JSDoc format if applicable"). Every box must be consciously checked — see docs/readme/ready-for-review.md.

See docs/readme/ready-for-review.md for the full Definition of Ready for Review.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Keypad lost horizontal padding
    • Added px-4 to the bottom sheet content container and removed the inner padding wrapper so the keypad aligns with prior layout and no longer touches screen edges.

Create PR

Or push these changes by commenting:

@cursor push 948e0b18ee
Preview (948e0b18ee)
diff --git a/app/components/UI/Ramp/Aggregator/Views/BuildQuote/BuildQuote.tsx b/app/components/UI/Ramp/Aggregator/Views/BuildQuote/BuildQuote.tsx
--- a/app/components/UI/Ramp/Aggregator/Views/BuildQuote/BuildQuote.tsx
+++ b/app/components/UI/Ramp/Aggregator/Views/BuildQuote/BuildQuote.tsx
@@ -1164,7 +1164,7 @@
             true
           }
         >
-          <Box twClassName="content-end gap-4 pt-4">
+          <Box twClassName="content-end gap-4 pt-4 px-4">
             <QuickAmounts
               isBuy={isBuy}
               onAmountPress={handleQuickAmountPress}
@@ -1182,7 +1182,7 @@
                 isBuy ? currentFiatCurrency?.decimals : selectedAsset?.decimals
               }
             />
-            <Box twClassName="px-4">
+            <Box>
               <Button
                 size={ButtonSize.Lg}
                 onPress={handleKeypadDone}

You can send follow-ups to the cloud agent here.

Comment thread app/components/UI/Ramp/Aggregator/Views/BuildQuote/BuildQuote.tsx
Comment on lines +48 to +71
// Mock MMDS BottomSheetDialog so children render synchronously in tests.
jest.mock('@metamask/design-system-react-native', () => {
const actual = jest.requireActual('@metamask/design-system-react-native');
const MockReact = jest.requireActual('react');

return {
...actual,
BottomSheetDialog: MockReact.forwardRef(
(
{
children,
onClose,
}: { children: React.ReactNode; onClose?: () => void },
dialogRef: React.Ref<{ onCloseDialog: () => void }>,
) => {
MockReact.useImperativeHandle(dialogRef, () => ({
onCloseDialog: () => onClose?.(),
}));
return children;
},
),
};
});

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cursoragent remove the mock and run unit tests this is likely not needed. Can we use a test id instead to assert that the BottomDialog is there?

@cursor cursor Bot Jul 15, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed the review feedback:

  1. Removed the BottomSheetDialog mock from BuildQuote.test.tsx — the reviewer was right; it wasn't needed.

  2. Added a test IDbuild-quote-amount-keypad-bottom-sheet on the BottomSheetDialog, exported as BuildQuoteSelectors.AMOUNT_KEYPAD_BOTTOM_SHEET.

  3. Updated the focus test"shows and hides the live input cursor based on focus" now asserts the bottom sheet is present when the amount input is focused and absent after tapping Done.

  4. Fixed keypad padding (Bugbot) — moved px-4 to the outer Box wrapper so the numpad matches the Swaps pattern.

All 48 unit tests pass without the mock. Changes pushed to PR #33311.

Open in Web Open in Cursor 

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

🧪 Flaky unit test detection

Run history flaky detection

View recent run history

Historical failure rate is a hint, not proof — review each suggestion in context. See the flaky-test-detection skill for the full pattern reference and manual audit workflow.

Failures / runs sampled per window:

File 7d 15d 30d
app/components/UI/Ramp/Aggregator/Views/BuildQuote/BuildQuote.test.tsx 0/86 0/217 0/359

AI-detected flaky patterns

app/components/UI/Ramp/Aggregator/Views/BuildQuote/BuildQuote.test.tsx

  • J10 — jest.spyOn() without restoreAllMocks()/mockRestore() afterward (medium)
    • This newly added test (visible in git diff) uses jest.spyOn to mock BackHandler.addEventListener with a custom implementation but does not call jest.restoreAllMocks() (or equivalent mockRestore) in an afterEach hook. Spies persist across tests, which can alter behavior in other tests that depend on the real BackHandler (e.g. other hardware back button tests or unmount tests), leading to order-dependent flaky failures. This exactly matches J10 from the skill. No other J1-J10 patterns matched concrete snippets (no waitFor, no fakeTimers+waitFor, no arbitrary sleeps, no module let without beforeEach reset, no incomplete store, no real timers, no non-deterministic data). History JSON shows 0 failures for this file so hint not used.
    • Suggested fix in app/components/UI/Ramp/Aggregator/Views/BuildQuote/BuildQuote.test.tsx:
      -      let backPressHandler: (() => boolean | undefined) | undefined;
      -      jest
      -        .spyOn(BackHandler, 'addEventListener')
      -        .mockImplementation((_event, handler) => {
      -          backPressHandler = handler as () => boolean | undefined;
      -        });
      -
      +afterEach(() => {
      +  mockNavigate.mockClear();
      +  mockGoBack.mockClear();
      +  mockSetOptions.mockClear();
      +  mockReset.mockClear();
      +  mockTrackEvent.mockClear();
      +  jest.restoreAllMocks();
      +});
      +

This check is informational only and does not block merging.

@sonarqubecloud

Copy link
Copy Markdown

@github-actions github-actions Bot locked and limited conversation to collaborators Aug 4, 2026
cursoragent and others added 6 commits August 7, 2026 13:39
Replace the custom Reanimated keypad overlay in Ramp Aggregator
BuildQuote with MMDS BottomSheetDialog, following the Swaps numpad
migration pattern.

- Remove keypadContainer animation and surface styling hacks
- Use isKeypadOpen state for bottom sheet lifecycle
- Drop custom QuickAmounts background; MMDS owns the surface
- Mock BottomSheetDialog in unit tests for synchronous rendering
- Fix native-asset gas tests to render after mock setup

Fixes TMCU-1084

Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
Address PR review: drop MMDS BottomSheetDialog jest mock and assert
keypad sheet presence with build-quote-amount-keypad-bottom-sheet.
Also restore horizontal padding on keypad content (px-4).

Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
Cover hardware back dismissal, selector navigation closing the sheet,
and sell-flow bottom sheet open/close to meet SonarCloud new-code
coverage threshold.

Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
Co-authored-by: George Marshall <georgewrmarshall@users.noreply.github.com>
@georgewrmarshall
georgewrmarshall force-pushed the cursor/ramp-sell-keypad-mmds-bottomsheet-35fc branch from a92de0a to 82a8dea Compare August 7, 2026 20:39
georgewrmarshall and others added 2 commits August 7, 2026 14:31
Align sell/buy keypad presets with MMDS Button Lg sizing and flex layout used by Swaps and the shared Ramp QuickAmounts.

Co-authored-by: Cursor <cursoragent@cursor.com>
Swap quick amounts for Done in the same keypad slot, migrate selectors/buttons/text to MMDS, and update AccountSelector to SelectButton.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions github-actions Bot added size-L and removed size-M labels Aug 7, 2026
Document the migration path on the legacy SelectorButton and note that Ramp AccountSelector already uses SelectButton.

Co-authored-by: Cursor <cursoragent@cursor.com>
@MetaMask MetaMask unlocked this conversation Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

</Animated.View>
>
<Box twClassName="content-end px-4 gap-4 pt-4">
{amount && amount !== '0' ? (

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Matches the Swaps keypad pattern: quick-amount presets and Done share one slot so the sheet stays one row shorter. Done only appears once the amount is non-zero, same condition BridgeView uses for confirm vs quick picks.

testID={BuildQuoteSelectors.AMOUNT_KEYPAD_BOTTOM_SHEET}
isInteractable={false}
onClose={handleKeypadClose}
onStartShouldSetResponder={() =>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Required so taps on non-pressable keypad chrome do not bubble to BottomSheetDialog and dismiss the sheet. Same responder guard used by SwapsKeypad after the MMDS BottomSheetDialog migration.

isBuy
? currentFiatCurrency?.symbol
: `${selectedAsset?.symbol}-crypto`
{isKeypadOpen ? (

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sheet is mounted only while open so MMDS owns lifecycle, surface, and safe-area instead of the old always-mounted Animated.View translateY overlay. isKeypadOpen is kept separate from amountFocused so the amount highlight can clear without fighting sheet close timing.

<SelectorButton
accessibilityRole="button"
accessible
<SelectButton

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Region and fiat chips use MMDS SelectButton instead of Base SelectorButton so select triggers share the same API and surface as other recent migrations. value/placeholder keep emoji and currency labels accessible without custom children wrappers.

size={ButtonSize.Sm}
label={label}
variant={ButtonVariant.Secondary}
size={ButtonSize.Lg}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lg sizing matches Swaps quick picks and the shared Ramp QuickAmounts. The old Sm size was 32px and looked undersized next to the Done CTA inside the MMDS sheet.


return (
<SelectorButton
<SelectButton

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeps Ramp-specific account picker behavior (nav params, avatar seed, loading placeholder) while the trigger itself is MMDS SelectButton plus AvatarAccount. Only used from Aggregator BuildQuote.

});

/**
* @deprecated Please update your code to use `SelectButton` from `@metamask/design-system-react-native`.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Marks the legacy Base SelectorButton deprecated now that Aggregator BuildQuote and AccountSelector no longer depend on it. Points new select triggers at MMDS SelectButton, consistent with the component-library SelectButton deprecation.

).not.toBeOnTheScreen();
});

it('replaces quick amounts with Done confirm when an amount is entered', () => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locks in the exclusive quick-amount vs Done slot for buy flow. After a digit is entered, presets must disappear and the confirm testID must appear so keypad height and CTA behavior stay aligned with Swaps.

navigateWithDetails(
navigation,
createTokenSelectModalNavigationDetails({
handleKeypadClose();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Closing the keypad before opening region/asset/fiat/payment selectors avoids stacking BottomSheetDialog over another modal. Covered by the new lifecycle tests for each selector entrypoint.

Restore the typed navigation helper for token/fiat/payment selectors so yarn lint:tsc accepts createNavigationDetails tuples.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Smart E2E Test Selection

  • Selected E2E tags: SmokeMoney
  • Selected Performance tags: None (no tests recommended)
  • Risk Level: medium
  • AI Confidence: 90%
click to see 🤖 AI reasoning details

E2E Test Selection:
The changes are focused on the Ramp/Buy flow (BuildQuote screen and related components):

  1. BuildQuote.tsx: Major UI refactor replacing Animated.View keypad with BottomSheetDialog, replacing BaseSelectorButton with SelectButton from the design system, and adding isKeypadOpen state management. This changes how the keypad opens/closes and how region/account/fiat selectors are rendered.

  2. AccountSelector.tsx: Replaced SelectorButton + Avatar with SelectButton + AvatarAccount from the design system — changes the account picker UI in the Ramp flow.

  3. QuickAmounts.tsx: Layout changed from horizontal ScrollView to flex-row Box — changes how quick amount buttons are displayed.

  4. BuildQuote.testIds.ts: Added AMOUNT_KEYPAD_BOTTOM_SHEET and AMOUNT_KEYPAD_CONFIRM_BUTTON test IDs — these are used by the BuildQuoteView page object in E2E tests.

  5. SelectorButton.tsx: Only a @deprecated JSDoc comment added — cosmetic/documentation change.

The tests/smoke-appium/ramps/onramp-unified-buy.spec.ts smoke spec (tagged SmokeMoney) uses BuildQuoteView which imports BuildQuoteSelectors directly from the changed BuildQuote.testIds.ts. The E2E test exercises BuildQuoteView.tapKeypadDeleteButton, BuildQuoteView.enterAmount, and BuildQuoteView.tapContinueButton — all of which interact with the refactored keypad/BottomSheetDialog flow.

No other smoke tags are impacted: the changes are isolated to the Ramp/Buy BuildQuote screen and its sub-components, with no changes to confirmations, network, accounts, swaps, or other flows.

Performance Test Selection:
The changes are UI component refactors within the Ramp/Buy BuildQuote screen (replacing Animated.View with BottomSheetDialog, migrating to design system components). These changes do not affect any performance-sensitive flows measured by the available performance test tags (onboarding, login, launch, swaps, asset loading, account list, predict, perps). No performance spec files were changed.

View GitHub Actions results

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size-L team-design-system All issues relating to design system in Mobile

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants