Skip to content

Commit

Permalink
chore: refactor header for tokens (#11855)
Browse files Browse the repository at this point in the history
<!--
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**
This PR updates the Header for the Token details view, based on this
[design update
](https://www.figma.com/design/bkJ9Ot0HGXiakMqZtQfs8G/Design-Quality?node-id=2362-458)by
@amandaye0h . Specifically
- Left and right button icons are now using the `ButtonIcon` component
with the large size
- Overflow icon is updated to vertical version
- Alignment of header is now top aligned
<!--
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?
-->

## **Related issues**

Fixes: #11854

## **Manual testing steps**

1. Go to Wallet
2. Click on a token
3.

## **Screenshots/Recordings**

<!-- If applicable, add screenshots and/or recordings to visualize the
before and after of your change. -->

### **Before**
![Token 2 -
Dark](https://github.com/user-attachments/assets/2696b7a8-e726-4048-828c-cd912e63f4c2)
![Token 2 -
Light](https://github.com/user-attachments/assets/1d953931-1d32-4daf-bf65-e68148ae4454)

<!-- [screenshots/recordings] -->

### **After**

https://github.com/user-attachments/assets/d1e0c824-f403-4f34-95c0-5d27c0dd6b3b


<!-- [screenshots/recordings] -->

## **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
- [ ] 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.
  • Loading branch information
brianacnguyen authored Feb 13, 2025
1 parent 42263b2 commit bff1503
Show file tree
Hide file tree
Showing 29 changed files with 251 additions and 135 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
exports[`BottomSheetHeader should render snapshot correctly 1`] = `
<View
style={
{
"backgroundColor": "#ffffff",
"flexDirection": "row",
"padding": 16,
}
[
{
"backgroundColor": "#ffffff",
"flexDirection": "row",
"padding": 16,
},
false,
]
}
testID="header"
>
Expand Down
34 changes: 34 additions & 0 deletions app/component-library/components/HeaderBase/HeaderBase.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Third party dependencies.
import React from 'react';
import { render } from '@testing-library/react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

// External dependencies.
import Text, { TextVariant } from '../Texts/Text';
Expand All @@ -15,7 +16,16 @@ import {
HEADERBASE_TITLE_TEST_ID,
} from './HeaderBase.constants';

jest.mock('react-native-safe-area-context', () => ({
useSafeAreaInsets: jest.fn(),
}));
describe('HeaderBase', () => {
const mockInsets = { top: 20, bottom: 0, left: 0, right: 0 };

beforeEach(() => {
(useSafeAreaInsets as jest.Mock).mockReturnValue(mockInsets);
});

it('should render snapshot correctly', () => {
const wrapper = render(<HeaderBase>Sample HeaderBase Title</HeaderBase>);
expect(wrapper).toMatchSnapshot();
Expand Down Expand Up @@ -55,4 +65,28 @@ describe('HeaderBase', () => {

expect(getByRole('text').props.style.fontFamily).toBe(fontFamily);
});

it('applies marginTop when includesTopInset is true', () => {
const { getByTestId } = render(
<HeaderBase includesTopInset>Header Content</HeaderBase>,
);

const headerBase = getByTestId(HEADERBASE_TEST_ID);
// Verify the marginTop is applied
expect(headerBase.props.style).toEqual(
expect.arrayContaining([{ marginTop: mockInsets.top }]),
);
});

it('does not apply marginTop when includesTopInset is false', () => {
const { getByTestId } = render(
<HeaderBase includesTopInset={false}>Header Content</HeaderBase>,
);

const headerBase = getByTestId(HEADERBASE_TEST_ID);
// Verify the marginTop is not applied
expect(headerBase.props.style).toEqual(
expect.not.arrayContaining([{ marginTop: mockInsets.top }]),
);
});
});
9 changes: 8 additions & 1 deletion app/component-library/components/HeaderBase/HeaderBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Third party dependencies.
import React from 'react';
import { View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

// External dependencies.
import { useComponentSize, useStyles } from '../../hooks';
Expand All @@ -22,19 +23,25 @@ const HeaderBase: React.FC<HeaderBaseProps> = ({
children,
startAccessory,
endAccessory,
includesTopInset = false,
}) => {
const { size: startAccessorySize, onLayout: startAccessoryOnLayout } =
useComponentSize();
const { size: endAccessorySize, onLayout: endAccessoryOnLayout } =
useComponentSize();
const insets = useSafeAreaInsets();

const { styles } = useStyles(styleSheet, {
style,
startAccessorySize,
endAccessorySize,
});

return (
<View style={styles.base} testID={HEADERBASE_TEST_ID}>
<View
style={[styles.base, includesTopInset && { marginTop: insets.top }]}
testID={HEADERBASE_TEST_ID}
>
<View style={styles.accessoryWrapper}>
<View onLayout={startAccessoryOnLayout}>{startAccessory}</View>
</View>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ export interface HeaderBaseProps extends ViewProps {
* Optional prop to include content to be displayed after the title.
*/
endAccessory?: React.ReactNode;
/**
* Optional prop to include the top inset to make sure the header is visible
* below device's knob
* @default: false
*/
includesTopInset?: boolean;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
exports[`HeaderBase should render snapshot correctly 1`] = `
<View
style={
{
"backgroundColor": "#ffffff",
"flexDirection": "row",
}
[
{
"backgroundColor": "#ffffff",
"flexDirection": "row",
},
false,
]
}
testID="header"
>
Expand Down
4 changes: 1 addition & 3 deletions app/components/Base/RemoteImage/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import { act, render } from '@testing-library/react-native';

jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useSelector: jest
.fn()
.mockImplementation(() => 'https://dweb.link/ipfs/'),
useSelector: jest.fn().mockImplementation(() => 'https://dweb.link/ipfs/'),
}));

jest.mock('../../../components/hooks/useIpfsGateway', () => jest.fn());
Expand Down
89 changes: 37 additions & 52 deletions app/components/UI/Navbar/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import Icon, {
IconColor,
} from '../../../component-library/components/Icons/Icon';
import { AddContactViewSelectorsIDs } from '../../../../e2e/selectors/Settings/Contacts/AddContactView.selectors';
import HeaderBase from '../../../component-library/components/HeaderBase';
import AddressCopy from '../AddressCopy';
import PickerAccount from '../../../component-library/components/Pickers/PickerAccount';
import { createAccountSelectorNavDetails } from '../../../components/Views/AccountSelector';
Expand Down Expand Up @@ -143,6 +144,12 @@ const styles = StyleSheet.create({
top: 2,
right: 10,
},
headerLeftButton: {
marginHorizontal: 16,
},
headerRightButton: {
marginHorizontal: 16,
},
addressCopyWrapper: {
marginHorizontal: 4,
},
Expand Down Expand Up @@ -1316,62 +1323,40 @@ export function getNetworkNavbarOptions(
contentOffset = 0,
networkName = '',
) {
const innerStyles = StyleSheet.create({
headerStyle: {
backgroundColor: themeColors.background.default,
shadowColor: importedColors.transparent,
elevation: 0,
},
headerShadow: {
elevation: 2,
shadowColor: themeColors.background.primary,
shadowOpacity: contentOffset < 20 ? contentOffset / 100 : 0.2,
shadowOffset: { height: 4, width: 0 },
shadowRadius: 8,
},
headerIcon: {
color: themeColors.primary.default,
},
});
return {
headerTitle: () => (
<NavbarTitle
disableNetwork={disableNetwork}
title={title}
translate={translate}
networkName={networkName}
/>
),
headerLeft: () => (
// eslint-disable-next-line react/jsx-no-bind
<TouchableOpacity
onPress={() => navigation.pop()}
style={styles.backButton}
testID={CommonSelectorsIDs.BACK_ARROW_BUTTON}
header: () => (
<HeaderBase
includesTopInset
startAccessory={
<ButtonIcon
style={styles.headerLeftButton}
onPress={() => navigation.pop()}
testID={CommonSelectorsIDs.BACK_ARROW_BUTTON}
size={ButtonIconSizes.Lg}
iconName={IconName.ArrowLeft}
iconColor={IconColor.Default}
/>
}
endAccessory={
onRightPress && (
<ButtonIcon
style={styles.headerRightButton}
onPress={onRightPress}
size={ButtonIconSizes.Lg}
iconName={IconName.MoreVertical}
iconColor={IconColor.Default}
/>
)
}
>
<IonicIcon
name={'ios-close'}
size={38}
style={innerStyles.headerIcon}
<NavbarTitle
disableNetwork={disableNetwork}
title={title}
translate={translate}
networkName={networkName}
/>
</TouchableOpacity>
</HeaderBase>
),
headerRight: onRightPress
? () => (
<TouchableOpacity style={styles.backButton} onPress={onRightPress}>
<MaterialCommunityIcon
name={'dots-horizontal'}
size={28}
style={innerStyles.headerIcon}
/>
</TouchableOpacity>
// eslint-disable-next-line no-mixed-spaces-and-tabs
)
: () => <View />,
headerStyle: [
innerStyles.headerStyle,
contentOffset && innerStyles.headerShadow,
],
};
}

Expand Down
41 changes: 41 additions & 0 deletions app/components/UI/Navbar/index.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* eslint-disable react/prop-types */
import React from 'react';
import { fireEvent } from '@testing-library/react-native';
import { createStackNavigator } from '@react-navigation/stack';
import renderWithProvider from '../../../util/test/renderWithProvider';
import { getNetworkNavbarOptions } from '.';

describe('getNetworkNavbarOptions', () => {
const Stack = createStackNavigator();

const mockNavigation = {
pop: jest.fn(),
};

const TestNavigator = ({ options }) => (
<Stack.Navigator>
<Stack.Screen name="TestScreen" component={() => options.header()} />
</Stack.Navigator>
);

beforeEach(() => {
jest.clearAllMocks();
});

it('renders correctly with default options', () => {
const options = getNetworkNavbarOptions(
'Test Title',
false,
mockNavigation,
);

const { getByText, getByRole } = renderWithProvider(
<TestNavigator options={options} />,
{
state: {},
},
);

expect(getByText('Test Title')).toBeTruthy();
});
});
13 changes: 8 additions & 5 deletions app/components/UI/NetworkModal/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,14 @@ exports[`NetworkDetails renders correctly 1`] = `
>
<View
style={
{
"backgroundColor": "#ffffff",
"flexDirection": "row",
"padding": 16,
}
[
{
"backgroundColor": "#ffffff",
"flexDirection": "row",
"padding": 16,
},
false,
]
}
testID="header"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ exports[`NetworkVerificationInfo renders correctly 1`] = `
>
<View
style={
{
"backgroundColor": "#ffffff",
"flexDirection": "row",
"padding": 16,
}
[
{
"backgroundColor": "#ffffff",
"flexDirection": "row",
"padding": 16,
},
false,
]
}
testID="header"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -953,11 +953,14 @@ exports[`NetworkSwitcher View renders and dismisses network modal when pressing
>
<View
style={
{
"backgroundColor": "#ffffff",
"flexDirection": "row",
"padding": 16,
}
[
{
"backgroundColor": "#ffffff",
"flexDirection": "row",
"padding": 16,
},
false,
]
}
testID="header"
>
Expand Down
Loading

0 comments on commit bff1503

Please sign in to comment.