chore(runway): cherry-pick feat(predict): Bottom Sheet Keyboard Fix cp-7.78.0#30488
Merged
Merged
Conversation
…p-7.78.0 (#30483) ## **Description** The Predict Buy bottom-sheet introduced in [#28779](#28779) opened with the custom keypad already rendered behind the rest of the sheet content. The root cause was a single `isInputFocused` boolean inside `usePredictBuyInputState` that was hard-coded to `true` on first render and overloaded across **five** unrelated behaviours: 1. Mounting `PredictKeypad`. 2. Highlighting the amount-display "active" state. 3. Hiding `PredictBuyBottomContent` while the keypad is up. 4. Disabling `PredictFeeSummary`. 5. Deferring the mm_pay relay-config side effects (`updatePendingAmount` / `setPayToken`) inside `PredictPayWithAnyTokenInfo`. Because the only "off switch" was `setIsInputFocused(false)` (driven from a `Done` button that doesn't exist in sheet mode) the previous PR worked around the issue with three `isSheetMode ? false : isInputFocused` ternaries — Bugbot flagged this on #28779. The workarounds also produced a confusing situation where bottom content and keypad rendered simultaneously on first sheet open. This PR separates the conflated meanings, parameterises the initial state, and removes the workarounds. No behaviour change for the legacy full-screen flow. ### What changed **`usePredictBuyInputState` ([hooks/usePredictBuyInputState.ts](app/components/UI/Predict/views/PredictBuyWithAnyToken/hooks/usePredictBuyInputState.ts))** - Accepts an options object: `{ initialKeypadOpen = true }`. - Renamed state + setter: `isInputFocused` → `isKeypadOpen`, `setIsInputFocused` → `setIsKeypadOpen`. - Default value preserves legacy behaviour; sheet mode opts out. **`PredictBuyWithAnyToken` ([PredictBuyWithAnyToken.tsx](app/components/UI/Predict/views/PredictBuyWithAnyToken/PredictBuyWithAnyToken.tsx))** - Calls the hook as `usePredictBuyInputState({ initialKeypadOpen: !isSheetMode })` so the sheet opens with the keypad collapsed. - Removed the three `isSheetMode ? false : isInputFocused` patches. - Renders `PredictBuyBottomContent` at the call site via `{(isSheetMode || !isKeypadOpen) && <PredictBuyBottomContent ... />}` instead of relying on the component to bail internally. Keeps the legacy "hide while typing" behaviour while letting the sheet show the bottom content (and Confirm) at all times. - Passes the new self-documenting `shouldDeferRelaySetup={!isSheetMode && isKeypadOpen}` prop to `PredictPayWithAnyTokenInfo`. Same effective value as before, but the prop name now describes its actual purpose. **`PredictPayWithAnyTokenInfo` ([components/PredictPayWithAnyTokenInfo](app/components/UI/Predict/views/PredictBuyWithAnyToken/components/PredictPayWithAnyTokenInfo/PredictPayWithAnyTokenInfo.tsx))** - Renamed prop: `isInputFocused` → `shouldDeferRelaySetup`. This is the genuine separation: the prop is **not** about UI keypad state, it's about pausing `updatePendingAmount` / `setPayToken` calls. Legacy mode still defers until the user taps Done; sheet mode never defers (relay must update on every keystroke since there's no Done and the user can tap Confirm with the keypad still open — preventing underfunded deposits). - Added a JSDoc comment explaining the contract. **`PredictBuyBottomContent` ([components/PredictBuyBottomContent](app/components/UI/Predict/views/PredictBuyWithAnyToken/components/PredictBuyBottomContent/PredictBuyBottomContent.tsx))** - Removed the `isInputFocused` prop and the `if (isInputFocused) return null` early return. Component is now purely structural; visibility is the parent's responsibility. **`PredictKeypad` and `PredictBuyAmountSection`** - Mechanical prop renames: `isInputFocused` → `isKeypadOpen`, `setIsInputFocused` → `setIsKeypadOpen`. **Legacy `PredictBuyPreview` ([views/PredictBuyPreview](app/components/UI/Predict/views/PredictBuyPreview/PredictBuyPreview.tsx))** - Local `useState(true)` renamed to `isKeypadOpen` for consistency with the rest of the tree. No behaviour change (still initialises `true`, still gates `renderBottomContent`). **Tests** - All prop/state references renamed. - New coverage on `usePredictBuyInputState` for `initialKeypadOpen: false` and `initialKeypadOpen: true`. - New assertions in `PredictBuyWithAnyToken.test.tsx` that the hook is called with `initialKeypadOpen: false` in sheet mode and `initialKeypadOpen: true` in non-sheet mode. - `PredictBuyBottomContent.test.tsx`: removed the now-irrelevant `isInputFocused is true / false` describe blocks since visibility is caller-controlled. - `PredictPayWithAnyTokenInfo.test.tsx`: updated 43 prop references and renamed two test descriptions to reflect the new "relay deferral" intent. - `PredictBuyPreview.test.tsx`: updated `renderBottomContent` describe block descriptions. ### Net behavioural effect (sheet mode) - Sheet opens → keypad hidden, amount display shows `$0` (inactive), quick amounts + pay-with row + fee summary + Confirm button visible (Confirm disabled until amount > 0). - Tap amount display → keypad opens at the bottom of the sheet, stacked **below** the Confirm button (does not overlap). - Tap a quick amount → value set, keypad closes. - Tap Confirm with keypad still open → places order (relay setup is up-to-date because `shouldDeferRelaySetup` is `false` in sheet mode). - Auto-blur on banner display still works. Legacy full-screen flow: unchanged. ## **Changelog** CHANGELOG entry: null ## **Related issues** Refs: PRED-707 (follow-up to [#28779](#28779)) ## **Manual testing steps** ```gherkin Feature: Predict buy bottom-sheet keypad initial state Scenario: Sheet opens with keypad collapsed (flag ON) Given the predictBottomSheet feature flag is enabled And the user is on a prediction market details page When user taps a "Yes" or "No" outcome button Then a bottom sheet opens with the buy preview content And the custom numeric keypad is NOT visible And the amount display shows "$0" And the quick amount buttons ($20 / $50 / $100 / $250) are visible And the Pay with row, fee summary and Confirm button are visible And the Confirm button is disabled Scenario: Tapping the amount opens the keypad Given the buy bottom sheet is open with no amount entered And the keypad is hidden When user taps the amount display Then the custom keypad becomes visible at the bottom of the sheet And the keypad does NOT cover the Confirm button And the amount display shows the active highlight Scenario: Tapping a quick amount sets value and closes the keypad Given the keypad is open And the user has typed "$73" When user taps the "$50" quick amount button Then the amount becomes "$50" And the keypad closes And haptic feedback fires (Light impact) And the Confirm button is enabled Scenario: Confirming with keypad still open Given the keypad is open And the user has typed "$25" When user taps the Confirm button Then the order is placed successfully And the relay was configured for $25 (no underfunded deposit) And the sheet closes Scenario: Banner display auto-blurs the keypad in sheet mode Given the buy bottom sheet is open And the keypad is open When an order_failed or price_changed banner appears Then the keypad auto-closes And the banner + Retry CTA are visible without needing to tap Done Scenario: Legacy full-screen flow is unchanged (flag OFF) Given the predictBottomSheet feature flag is disabled And the user navigates to the BuyPreview screen When the screen mounts Then the keypad is open by default (matching previous behaviour) And tapping Done closes the keypad and reveals the bottom content And the relay is configured once on Done (deferred during typing) ``` ## **Screenshots/Recordings** ### **Before** Sheet opens with the keypad rendered behind the bottom content; bottom content and keypad are both visible simultaneously on first paint. (See screenshot in PR comments — keypad sits below `Confirm` even though the user has not yet tapped the amount display.) ### **After** Sheet opens with the keypad hidden; bottom content (quick amounts, pay with row, fee summary, Confirm) is the only thing visible. Tapping the amount display reveals the keypad; tapping a quick amount or Confirm collapses it. https://github.com/user-attachments/assets/af89b78a-5103-48eb-b2c9-346ea64cd463 <!-- TODO: attach .mov / screenshots once recorded against the branch --> ## **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. #### 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 > N/A — pure refactor of UI state semantics, no new code paths. ## **Pre-merge reviewer checklist** - [x] 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** > Changes how the Predict buy keypad state is tracked and used to gate bottom content and mm_pay relay setup; mistakes could surface as incorrect UI visibility or misconfigured deposit/payment amounts during checkout. Scope is mostly contained to Predict buy views and covered by updated tests. > > **Overview** > Fixes the Predict buy bottom-sheet keypad initial state by splitting the previously overloaded `isInputFocused` flag into a dedicated `isKeypadOpen` state, including a new `initialKeypadOpen` option in `usePredictBuyInputState` so sheet mode can start closed. > > Updates `PredictBuyWithAnyToken`/`PredictKeypad`/`PredictBuyAmountSection` to use `isKeypadOpen` for keypad mounting and active styling, moves bottom-content visibility control to the parent (removing `PredictBuyBottomContent`’s internal early-return), and introduces `shouldDeferRelaySetup` (replacing `isInputFocused`) to explicitly gate `PredictPayWithAnyTokenInfo`’s relay-configuration side effects. > > Refactors legacy `PredictBuyPreview` to the same `isKeypadOpen` naming and updates/adds tests to assert the new initialization, visibility behavior, and relay deferral propagation. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 98d3d4d. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
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. |
Contributor
🔍 Smart E2E Test Selection⏭️ Smart E2E selection skipped - PR targets a release branch (release/*) All E2E tests pre-selected. |
|
sleepytanya
approved these changes
May 21, 2026
Contributor
|
LGTM |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Description
The Predict Buy bottom-sheet introduced in
#28779 opened
with the custom keypad already rendered behind the rest of the sheet
content. The root cause was a single
isInputFocusedboolean insideusePredictBuyInputStatethat was hard-coded totrueon first renderand overloaded across five unrelated behaviours:
PredictKeypad.PredictBuyBottomContentwhile the keypad is up.PredictFeeSummary.updatePendingAmount/
setPayToken) insidePredictPayWithAnyTokenInfo.Because the only "off switch" was
setIsInputFocused(false)(drivenfrom a
Donebutton that doesn't exist in sheet mode) the previous PRworked around the issue with three
isSheetMode ? false : isInputFocusedternaries — Bugbot flagged this on #28779. Theworkarounds also produced a confusing situation where bottom content and
keypad rendered simultaneously on first sheet open.
This PR separates the conflated meanings, parameterises the initial
state, and removes the workarounds. No behaviour change for the legacy
full-screen flow.
What changed
usePredictBuyInputState(hooks/usePredictBuyInputState.ts)
{ initialKeypadOpen = true }.isInputFocused→isKeypadOpen,setIsInputFocused→setIsKeypadOpen.PredictBuyWithAnyToken(PredictBuyWithAnyToken.tsx)
usePredictBuyInputState({ initialKeypadOpen: !isSheetMode })so the sheet opens with the keypad collapsed.isSheetMode ? false : isInputFocusedpatches.PredictBuyBottomContentat the call site via{(isSheetMode || !isKeypadOpen) && <PredictBuyBottomContent ... />}instead ofrelying on the component to bail internally. Keeps the legacy "hide
while typing" behaviour while letting the sheet show the bottom content
(and Confirm) at all times.
shouldDeferRelaySetup={!isSheetMode && isKeypadOpen}prop toPredictPayWithAnyTokenInfo. Same effectivevalue as before, but the prop name now describes its actual purpose.
PredictPayWithAnyTokenInfo(components/PredictPayWithAnyTokenInfo)
isInputFocused→shouldDeferRelaySetup. This is thegenuine separation: the prop is not about UI keypad state, it's
about pausing
updatePendingAmount/setPayTokencalls. Legacy modestill defers until the user taps Done; sheet mode never defers (relay
must update on every keystroke since there's no Done and the user can
tap Confirm with the keypad still open — preventing underfunded
deposits).
PredictBuyBottomContent(components/PredictBuyBottomContent)
isInputFocusedprop and theif (isInputFocused) return nullearly return. Component is now purely structural; visibility isthe parent's responsibility.
PredictKeypadandPredictBuyAmountSectionisInputFocused→isKeypadOpen,setIsInputFocused→setIsKeypadOpen.Legacy
PredictBuyPreview(views/PredictBuyPreview)
useState(true)renamed toisKeypadOpenfor consistency withthe rest of the tree. No behaviour change (still initialises
true,still gates
renderBottomContent).Tests
usePredictBuyInputStateforinitialKeypadOpen: falseandinitialKeypadOpen: true.PredictBuyWithAnyToken.test.tsxthat the hook iscalled with
initialKeypadOpen: falsein sheet mode andinitialKeypadOpen: truein non-sheet mode.PredictBuyBottomContent.test.tsx: removed the now-irrelevantisInputFocused is true / falsedescribe blocks since visibility iscaller-controlled.
PredictPayWithAnyTokenInfo.test.tsx: updated 43 prop references andrenamed two test descriptions to reflect the new "relay deferral"
intent.
PredictBuyPreview.test.tsx: updatedrenderBottomContentdescribeblock descriptions.
Net behavioural effect (sheet mode)
$0(inactive),quick amounts + pay-with row + fee summary + Confirm button visible
(Confirm disabled until amount > 0).
below the Confirm button (does not overlap).
up-to-date because
shouldDeferRelaySetupisfalsein sheet mode).Legacy full-screen flow: unchanged.
Changelog
CHANGELOG entry: null
Related issues
Refs: PRED-707 (follow-up to
#28779)
Manual testing steps
Screenshots/Recordings
Before
Sheet opens with the keypad rendered behind the bottom content; bottom
content and keypad are both visible simultaneously on first paint. (See
screenshot in PR comments — keypad sits below
Confirmeven though theuser has not yet tapped the amount display.)
After
Sheet opens with the keypad hidden; bottom content (quick amounts, pay
with row, fee summary, Confirm) is the only thing visible. Tapping the
amount display reveals the keypad; tapping a quick amount or Confirm
collapses it.
bottomSheetKeyboardFix.mov
Pre-merge author checklist
Docs and MetaMask Mobile
Coding
Standards.
if applicable
guidelines).
Not required for external contributors.
Performance checks (if applicable)
SRPs
to import wallets with many accounts and tokens
performance metrics
trace()for usage andaddTokenfor an example
Pre-merge reviewer checklist
app, test code being changed).
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
Note
Medium Risk
Changes how the Predict buy keypad state is tracked and used to gate
bottom content and mm_pay relay setup; mistakes could surface as
incorrect UI visibility or misconfigured deposit/payment amounts during
checkout. Scope is mostly contained to Predict buy views and covered by
updated tests.
Overview
Fixes the Predict buy bottom-sheet keypad initial state by splitting
the previously overloaded
isInputFocusedflag into a dedicatedisKeypadOpenstate, including a newinitialKeypadOpenoption inusePredictBuyInputStateso sheet mode can start closed.Updates
PredictBuyWithAnyToken/PredictKeypad/PredictBuyAmountSectiontouse
isKeypadOpenfor keypad mounting and active styling, movesbottom-content visibility control to the parent (removing
PredictBuyBottomContent’s internal early-return), and introducesshouldDeferRelaySetup(replacingisInputFocused) to explicitly gatePredictPayWithAnyTokenInfo’s relay-configuration side effects.Refactors legacy
PredictBuyPreviewto the sameisKeypadOpennamingand updates/adds tests to assert the new initialization, visibility
behavior, and relay deferral propagation.
Reviewed by Cursor Bugbot for commit
98d3d4d. Bugbot is set up for automated
code reviews on this repo. Configure
here.