Skip to content

Commit a86c788

Browse files
chore(runway): cherry-pick feat: implement Url Bar Button Updates (#25459)
- feat: implement Url Bar Button Updates (#25418) ## **Description** This PR updates the WebBrowser URL bar button interactions to match the new design specifications and fix the "3 X buttons" issue. **Jira Ticket:** https://consensyssoftware.atlassian.net/browse/MCWP-310 ### Problem Previously, the URL bar could display up to 3 "X" buttons simultaneously: 1. Top-left back button (always an X) 2. Clear input button (CircleX inside input) 3. Cancel button (could be X when `showCloseButton` was true) This created a confusing user experience where multiple identical-looking buttons performed different actions. ### Solution - **Back Button**: Changed icon from `X` to `<` (ArrowLeft) and hidden when URL input is focused - **Cancel Button**: Now always displays "Cancel" text (never an X icon), styled to match the Explore search bar - **Clear Button**: Unchanged - CircleX inside input to clear text (already correct) - Removed the `showCloseButton` prop that was causing the Cancel button to show an X icon in certain flows ### Button Behavior Summary | State | Before | After | |-------|--------|-------| | **Not focused** | X button (left) + Tabs + Account | `<` button (left) + Tabs + Account | | **Focused** | X button (left) + Clear (X) + Cancel (X or text) | Clear (X) + Cancel (text) | ## **Changelog** CHANGELOG entry: Updated browser URL bar buttons - back button now shows chevron icon and hides when typing, cancel button always shows text instead of X icon ## **Related issues** Fixes: <!-- Add issue number if applicable --> ## **Manual testing steps** ```gherkin Feature: Browser URL Bar Button Interactions Scenario: User sees back button when browsing Given the user has opened a website in the browser And the URL bar is not focused When user views the browser top bar Then the back button should display a "<" chevron icon on the left And the tabs button and account button should be visible on the right Scenario: User focuses on URL bar to search Given the user has opened a website in the browser And the URL bar is not focused When user taps on the URL bar Then the back button should be hidden And the "Cancel" text button should appear on the right And the clear (X) button should appear inside the input field Scenario: User clears search input Given the user has focused on the URL bar And has typed some text When user taps the clear (X) button inside the input Then the text should be cleared And the URL bar should remain focused Scenario: User cancels search Given the user has focused on the URL bar When user taps the "Cancel" button Then the URL bar should lose focus And the back button "<" should reappear And the current page URL should be restored Scenario: User navigates back to Explore Given the user has opened a website in the browser And the URL bar is not focused When user taps the back button "<" Then user should be navigated back to the Explore/Trending page ``` ## **Screenshots/Recordings** ### **Before** <img width="450" height="316" alt="Screenshot 2026-01-29 at 16 22 49" src="https://github.com/user-attachments/assets/3dba6e1b-9f3f-4dae-8f9c-17512524bf87" /> - Back button showed "X" icon - Cancel button could show "X" icon (when opened from Trending) - 3 X buttons could be visible simultaneously when URL bar was focused ### **After** - Back button shows "<" chevron icon - Back button hides when URL bar is focused - Cancel button always shows "Cancel" text - Only the clear button (inside input) shows an X when focused https://github.com/user-attachments/assets/68be1901-f419-4d1e-891f-6d976709a610 <!-- Add actual screenshots/recordings here --> ## **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** - [x] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [x] 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. --- ## Files Changed | File | Change | |------|--------| | `BrowserTab.tsx` | Changed back button icon to ArrowLeft, hide when URL bar focused | | `BrowserUrlBar.tsx` | Removed `showCloseButton` prop, simplified `renderRightButton` | | `BrowserUrlBar.types.ts` | Removed `showCloseButton` prop type | | `BrowserUrlBar.styles.ts` | Updated `cancelButtonText` to use default text color with medium weight | | `BrowserUrlBar.test.tsx` | Updated tests for removed `showCloseButton` functionality | | `BrowserTab/index.test.tsx` | Added test for back button visibility | | `RemoteImage/index.test.tsx` | Fixed flaky test by properly cleaning up Dimensions mock | <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk UI behavior/styling changes in the in-app browser header plus test updates; main risk is minor UX regressions around focus state and navigation affordances. > > **Overview** > **Browser URL bar button behavior is simplified to match new designs.** `BrowserUrlBar` no longer supports `showCloseButton`; when focused it always shows a text **Cancel** button (with updated styling) instead of sometimes rendering a close icon. > > **Browser header navigation icon is updated.** `BrowserTab` replaces the left-side close icon with an `ArrowLeft` button and hides it while the URL bar is focused. > > Tests and snapshots are updated accordingly, and `RemoteImage` tests now properly restore the `Dimensions.get` spy to reduce test flakiness. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit be165e0. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> [6471fcd](6471fcd) Co-authored-by: Aslau Mario-Daniel <marioaslau@gmail.com>
1 parent 1b55cbd commit a86c788

9 files changed

Lines changed: 45 additions & 103 deletions

File tree

app/components/Base/RemoteImage/index.test.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,15 +336,21 @@ describe('RemoteImage', () => {
336336
});
337337

338338
describe('Image Dimensions', () => {
339+
let dimensionsSpy: jest.SpyInstance;
340+
339341
beforeEach(() => {
340-
jest.spyOn(Dimensions, 'get').mockReturnValue({
342+
dimensionsSpy = jest.spyOn(Dimensions, 'get').mockReturnValue({
341343
width: 400,
342344
height: 800,
343345
scale: 1,
344346
fontScale: 1,
345347
});
346348
});
347349

350+
afterEach(() => {
351+
dimensionsSpy.mockRestore();
352+
});
353+
348354
it('calculates dimensions for horizontal image', async () => {
349355
const { UNSAFE_getByType } = render(
350356
<RemoteImage

app/components/UI/BrowserUrlBar/BrowserUrlBar.styles.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,10 @@ const styleSheet = ({
6262
},
6363
cancelButtonText: {
6464
fontSize: 14,
65-
color: colors.primary.default,
66-
...fontStyles.normal,
65+
color: colors.text.default,
66+
fontWeight: '500',
6767
},
6868
rightButton: { height: 50, justifyContent: 'center' },
69-
closeButton: {
70-
marginRight: 16,
71-
},
7269
tabsButton: {
7370
height: 50,
7471
justifyContent: 'center',

app/components/UI/BrowserUrlBar/BrowserUrlBar.test.tsx

Lines changed: 8 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -520,10 +520,10 @@ describe('BrowserUrlBar', () => {
520520
});
521521
});
522522

523-
describe('when URL bar is focused and showCloseButton is false', () => {
523+
describe('when URL bar is focused', () => {
524524
it('renders Cancel button with text', () => {
525525
const { getByTestId, getByText } = renderWithProvider(
526-
<BrowserUrlBar {...defaultProps} showCloseButton={false} />,
526+
<BrowserUrlBar {...defaultProps} />,
527527
{ state: mockInitialState },
528528
);
529529

@@ -541,7 +541,7 @@ describe('BrowserUrlBar', () => {
541541
const props = { ...defaultProps, onCancel: onCancelMock };
542542

543543
const { getByTestId } = renderWithProvider(
544-
<BrowserUrlBar {...props} showCloseButton={false} />,
544+
<BrowserUrlBar {...props} />,
545545
{ state: mockInitialState },
546546
);
547547

@@ -554,64 +554,10 @@ describe('BrowserUrlBar', () => {
554554
});
555555
});
556556

557-
describe('when URL bar is focused and showCloseButton is true', () => {
558-
it('renders Close icon ButtonIcon', () => {
559-
const { getByTestId, queryByText } = renderWithProvider(
560-
<BrowserUrlBar {...defaultProps} showCloseButton />,
561-
{ state: mockInitialState },
562-
);
563-
564-
const closeButton = getByTestId(
565-
BrowserURLBarSelectorsIDs.CANCEL_BUTTON_ON_BROWSER_ID,
566-
);
567-
const cancelText = queryByText('Cancel');
568-
569-
expect(closeButton).toBeDefined();
570-
expect(cancelText).toBeNull();
571-
});
572-
573-
it('calls onCancel when Close button is pressed', () => {
574-
const onCancelMock = jest.fn();
575-
const props = { ...defaultProps, onCancel: onCancelMock };
576-
577-
const { getByTestId } = renderWithProvider(
578-
<BrowserUrlBar {...props} showCloseButton />,
579-
{ state: mockInitialState },
580-
);
581-
582-
const closeButton = getByTestId(
583-
BrowserURLBarSelectorsIDs.CANCEL_BUTTON_ON_BROWSER_ID,
584-
);
585-
fireEvent.press(closeButton);
586-
587-
expect(onCancelMock).toHaveBeenCalled();
588-
});
589-
590-
it('sets URL bar focused state to false when Close button is pressed', () => {
591-
const setIsUrlBarFocusedMock = jest.fn();
592-
const props = {
593-
...defaultProps,
594-
setIsUrlBarFocused: setIsUrlBarFocusedMock,
595-
};
596-
597-
const { getByTestId } = renderWithProvider(
598-
<BrowserUrlBar {...props} showCloseButton />,
599-
{ state: mockInitialState },
600-
);
601-
602-
const closeButton = getByTestId(
603-
BrowserURLBarSelectorsIDs.CANCEL_BUTTON_ON_BROWSER_ID,
604-
);
605-
fireEvent.press(closeButton);
606-
607-
expect(setIsUrlBarFocusedMock).toHaveBeenCalledWith(false);
608-
});
609-
});
610-
611557
describe('button rendering logic', () => {
612-
it('does not render Cancel or Close button when URL bar is not focused', () => {
558+
it('does not render Cancel button when URL bar is not focused', () => {
613559
const { queryByText } = renderWithProvider(
614-
<BrowserUrlBar {...propsWithoutUrlBarFocused} showCloseButton />,
560+
<BrowserUrlBar {...propsWithoutUrlBarFocused} />,
615561
{ state: mockInitialState },
616562
);
617563

@@ -620,19 +566,14 @@ describe('BrowserUrlBar', () => {
620566
expect(cancelText).toBeNull();
621567
});
622568

623-
it('renders correct button based on showCloseButton prop value change', () => {
624-
const { getByText, rerender, queryByText } = renderWithProvider(
625-
<BrowserUrlBar {...defaultProps} showCloseButton={false} />,
569+
it('always renders Cancel text button when URL bar is focused', () => {
570+
const { getByText } = renderWithProvider(
571+
<BrowserUrlBar {...defaultProps} />,
626572
{ state: mockInitialState },
627573
);
628574

629575
const cancelText = getByText('Cancel');
630576
expect(cancelText).toBeDefined();
631-
632-
rerender(<BrowserUrlBar {...defaultProps} showCloseButton />);
633-
634-
const cancelTextAfterRerender = queryByText('Cancel');
635-
expect(cancelTextAfterRerender).toBeNull();
636577
});
637578
});
638579
});

app/components/UI/BrowserUrlBar/BrowserUrlBar.tsx

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ const BrowserUrlBar = forwardRef<BrowserUrlBarRef, BrowserUrlBarProps>(
5757
activeUrl,
5858
setIsUrlBarFocused,
5959
isUrlBarFocused,
60-
showCloseButton,
6160
showTabs,
6261
},
6362
ref,
@@ -143,19 +142,7 @@ const BrowserUrlBar = forwardRef<BrowserUrlBarRef, BrowserUrlBarProps>(
143142
);
144143
}
145144

146-
if (showCloseButton) {
147-
return (
148-
<ButtonIcon
149-
iconName={IconName.Close}
150-
onPress={onCancelInput}
151-
iconColor={colors.icon.default}
152-
size={ButtonIconSizes.Lg}
153-
style={styles.closeButton}
154-
testID={BrowserURLBarSelectorsIDs.CANCEL_BUTTON_ON_BROWSER_ID}
155-
/>
156-
);
157-
}
158-
145+
// Always show "Cancel" text when focused
159146
return (
160147
<TouchableOpacity
161148
hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
@@ -170,12 +157,9 @@ const BrowserUrlBar = forwardRef<BrowserUrlBarRef, BrowserUrlBarProps>(
170157
);
171158
}, [
172159
isUrlBarFocused,
173-
showCloseButton,
174160
selectedAddress,
175161
handleAccountRightButtonPress,
176162
onCancelInput,
177-
colors.icon.default,
178-
styles.closeButton,
179163
styles.cancelButton,
180164
styles.cancelButtonText,
181165
]);

app/components/UI/BrowserUrlBar/BrowserUrlBar.types.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,5 @@ export type BrowserUrlBarProps = {
3333
activeUrl: string;
3434
setIsUrlBarFocused: (focused: boolean) => void;
3535
isUrlBarFocused: boolean;
36-
showCloseButton?: boolean;
3736
showTabs?: () => void;
3837
};

app/components/UI/BrowserUrlBar/__snapshots__/BrowserUrlBar.test.tsx.snap

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,10 @@ exports[`BrowserUrlBar render matches snapshot when focused 1`] = `
171171
accessibilityRole="text"
172172
style={
173173
{
174-
"color": "#4459ff",
175-
"fontFamily": "Geist-Regular",
174+
"color": "#121314",
175+
"fontFamily": "Geist-Medium",
176176
"fontSize": 14,
177+
"fontWeight": "500",
177178
"letterSpacing": 0,
178179
"lineHeight": 24,
179180
}

app/components/Views/BrowserTab/BrowserTab.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,12 +1395,14 @@ export const BrowserTab: React.FC<BrowserTabProps> = React.memo(
13951395
alignItems={BoxAlignItems.Center}
13961396
twClassName="gap-2"
13971397
>
1398-
<ButtonIcon
1399-
iconName={IconName.Close}
1400-
size={ButtonIconSize.Lg}
1401-
onPress={handleClosePress}
1402-
testID="browser-tab-close-button"
1403-
/>
1398+
{!isUrlBarFocused && (
1399+
<ButtonIcon
1400+
iconName={IconName.ArrowLeft}
1401+
size={ButtonIconSize.Lg}
1402+
onPress={handleClosePress}
1403+
testID="browser-tab-close-button"
1404+
/>
1405+
)}
14041406
<Box twClassName="flex-1">
14051407
<BrowserUrlBar
14061408
ref={urlBarRef}
@@ -1414,9 +1416,6 @@ export const BrowserTab: React.FC<BrowserTabProps> = React.memo(
14141416
activeUrl={resolvedUrlRef.current}
14151417
setIsUrlBarFocused={setIsUrlBarFocused}
14161418
isUrlBarFocused={isUrlBarFocused}
1417-
showCloseButton={
1418-
fromTrending && isAssetsTrendingTokensEnabled
1419-
}
14201419
showTabs={showTabsView}
14211420
/>
14221421
</Box>

app/components/Views/BrowserTab/__snapshots__/index.test.tsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ exports[`BrowserTab render Browser 1`] = `
9898
>
9999
<SvgMock
100100
fill="currentColor"
101-
name="Close"
101+
name="ArrowLeft"
102102
style={
103103
[
104104
{

app/components/Views/BrowserTab/index.test.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,21 @@ describe('BrowserTab', () => {
103103
expect(screen.toJSON()).toMatchSnapshot();
104104
});
105105

106+
describe('Back Button', () => {
107+
it('renders back button when URL bar is not focused', async () => {
108+
renderWithProvider(<BrowserTab {...mockProps} />, {
109+
state: mockInitialState,
110+
});
111+
112+
await waitFor(() =>
113+
expect(screen.getByTestId('browser-webview')).toBeVisible(),
114+
);
115+
116+
const backButton = screen.getByTestId('browser-tab-close-button');
117+
expect(backButton).toBeTruthy();
118+
});
119+
});
120+
106121
describe('WebView originWhitelist', () => {
107122
it('sets originWhitelist to wildcard for all URLs', async () => {
108123
renderWithProvider(<BrowserTab {...mockProps} />, {

0 commit comments

Comments
 (0)