From 44f5c3d3318998e42f1cc64be1c67c0636d310c1 Mon Sep 17 00:00:00 2001
From: Matthew Grainger <46547583+Matt561@users.noreply.github.com>
Date: Mon, 27 Jan 2025 15:05:54 -0500
Subject: [PATCH] feat: STAKE-929 build token list item component (#13154)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## **Description**
Added generic `` for use in Staking flows.
## **Related issues**
Fixes: [STAKE-929: build token list item
component](https://consensyssoftware.atlassian.net/browse/STAKE-929)
## **Manual testing steps**
N/A
## **Screenshots/Recordings**
### **Before**
N/A
### **After**
#### Default
#### Styled Primary and Secondary Text
#### Primary Text only
## **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
- [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [ ] 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.
---
.../EarnTokenListItem.styles.tsx | 26 +++
.../EarnTokenListItem.test.tsx | 88 ++++++++
.../EarnTokenListItem.types.ts | 13 ++
.../EarnTokenListItem.test.tsx.snap | 200 ++++++++++++++++++
.../components/EarnTokenListItem/index.tsx | 99 +++++++++
5 files changed, 426 insertions(+)
create mode 100644 app/components/UI/Stake/components/EarnTokenListItem/EarnTokenListItem.styles.tsx
create mode 100644 app/components/UI/Stake/components/EarnTokenListItem/EarnTokenListItem.test.tsx
create mode 100644 app/components/UI/Stake/components/EarnTokenListItem/EarnTokenListItem.types.ts
create mode 100644 app/components/UI/Stake/components/EarnTokenListItem/__snapshots__/EarnTokenListItem.test.tsx.snap
create mode 100644 app/components/UI/Stake/components/EarnTokenListItem/index.tsx
diff --git a/app/components/UI/Stake/components/EarnTokenListItem/EarnTokenListItem.styles.tsx b/app/components/UI/Stake/components/EarnTokenListItem/EarnTokenListItem.styles.tsx
new file mode 100644
index 00000000000..ff83f31dd51
--- /dev/null
+++ b/app/components/UI/Stake/components/EarnTokenListItem/EarnTokenListItem.styles.tsx
@@ -0,0 +1,26 @@
+import { StyleSheet } from 'react-native';
+
+const styleSheet = () =>
+ StyleSheet.create({
+ container: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+ left: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 16,
+ },
+ right: {
+ alignItems: 'flex-end',
+ justifyContent: 'center',
+ },
+ networkAvatar: {
+ height: 32,
+ width: 32,
+ flexShrink: 0,
+ },
+ });
+
+export default styleSheet;
diff --git a/app/components/UI/Stake/components/EarnTokenListItem/EarnTokenListItem.test.tsx b/app/components/UI/Stake/components/EarnTokenListItem/EarnTokenListItem.test.tsx
new file mode 100644
index 00000000000..40e28970ca4
--- /dev/null
+++ b/app/components/UI/Stake/components/EarnTokenListItem/EarnTokenListItem.test.tsx
@@ -0,0 +1,88 @@
+import React from 'react';
+import { EarnTokenListItemProps } from './EarnTokenListItem.types';
+import EarnTokenListItem from '.';
+import { strings } from '../../../../../../locales/i18n';
+import renderWithProvider from '../../../../../util/test/renderWithProvider';
+import { useSelector } from 'react-redux';
+import { selectIsIpfsGatewayEnabled } from '../../../../../selectors/preferencesController';
+import {
+ TextColor,
+ TextVariant,
+} from '../../../../../component-library/components/Texts/Text';
+
+jest.mock('react-redux', () => ({
+ ...jest.requireActual('react-redux'),
+ useSelector: jest.fn(),
+}));
+
+describe('EarnTokenListItem', () => {
+ beforeEach(() => {
+ (useSelector as jest.Mock).mockImplementation((selector) => {
+ if (selector === selectIsIpfsGatewayEnabled) return true;
+ });
+ });
+
+ afterEach(() => {
+ (useSelector as jest.Mock).mockClear();
+ });
+
+ const baseProps: EarnTokenListItemProps = {
+ token: {
+ chainId: '0x1',
+ image:
+ 'https://static.cx.metamask.io/api/v1/tokenIcons/1/0x6b175474e89094c44da98b954eedeac495271d0f.png',
+ name: 'Dai Stablecoin',
+ symbol: 'DAI',
+ address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
+ aggregators: [],
+ decimals: 18,
+ balance: '',
+ balanceFiat: '',
+ logo: undefined,
+ isETH: false,
+ },
+ primaryText: {
+ value: `3.0% ${strings('stake.apr')}`,
+ variant: TextVariant.BodyMDBold,
+ color: TextColor.Success,
+ },
+ onPress: jest.fn(),
+ };
+
+ const secondaryText = {
+ value: '10,100.00 USDC',
+ variant: TextVariant.BodySMBold,
+ color: TextColor.Alternative,
+ };
+
+ it('render matches snapshot', () => {
+ const props: EarnTokenListItemProps = {
+ ...baseProps,
+ secondaryText,
+ };
+
+ const { toJSON } = renderWithProvider();
+
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it('renders primary text and secondary text', () => {
+ const props: EarnTokenListItemProps = {
+ ...baseProps,
+ secondaryText,
+ };
+
+ const { getByText } = renderWithProvider();
+
+ expect(getByText('Dai Stablecoin')).toBeDefined();
+ expect(getByText('10,100.00 USDC')).toBeDefined();
+ });
+
+ it('renders only primary text', () => {
+ const { getByText } = renderWithProvider(
+ ,
+ );
+
+ expect(getByText('Dai Stablecoin')).toBeDefined();
+ });
+});
diff --git a/app/components/UI/Stake/components/EarnTokenListItem/EarnTokenListItem.types.ts b/app/components/UI/Stake/components/EarnTokenListItem/EarnTokenListItem.types.ts
new file mode 100644
index 00000000000..7f1152fda81
--- /dev/null
+++ b/app/components/UI/Stake/components/EarnTokenListItem/EarnTokenListItem.types.ts
@@ -0,0 +1,13 @@
+import { TextProps } from '../../../../../component-library/components/Texts/Text/Text.types';
+import { TokenI } from '../../../Tokens/types';
+
+interface Text extends Omit {
+ value: string;
+}
+
+export interface EarnTokenListItemProps {
+ token: TokenI;
+ primaryText: Text;
+ secondaryText?: Text;
+ onPress: (token: TokenI) => void;
+}
diff --git a/app/components/UI/Stake/components/EarnTokenListItem/__snapshots__/EarnTokenListItem.test.tsx.snap b/app/components/UI/Stake/components/EarnTokenListItem/__snapshots__/EarnTokenListItem.test.tsx.snap
new file mode 100644
index 00000000000..c23dc7fe98c
--- /dev/null
+++ b/app/components/UI/Stake/components/EarnTokenListItem/__snapshots__/EarnTokenListItem.test.tsx.snap
@@ -0,0 +1,200 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`EarnTokenListItem render matches snapshot 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dai Stablecoin
+
+
+
+
+ 3.0% [missing "en.stake.apr" translation]
+
+
+ 10,100.00 USDC
+
+
+
+`;
diff --git a/app/components/UI/Stake/components/EarnTokenListItem/index.tsx b/app/components/UI/Stake/components/EarnTokenListItem/index.tsx
new file mode 100644
index 00000000000..3f2567ba214
--- /dev/null
+++ b/app/components/UI/Stake/components/EarnTokenListItem/index.tsx
@@ -0,0 +1,99 @@
+import React from 'react';
+import { TouchableOpacity, View } from 'react-native';
+import { useSelector } from 'react-redux';
+import Badge, {
+ BadgeVariant,
+} from '../../../../../component-library/components/Badges/Badge';
+import BadgeWrapper from '../../../../../component-library/components/Badges/BadgeWrapper';
+import Text, {
+ TextColor,
+ TextVariant,
+} from '../../../../../component-library/components/Texts/Text';
+import { selectNetworkName } from '../../../../../selectors/networkInfos';
+import { useStyles } from '../../../../hooks/useStyles';
+import { EarnTokenListItemProps } from './EarnTokenListItem.types';
+import { getNetworkImageSource } from '../../../../../util/networks';
+import styleSheet from './EarnTokenListItem.styles';
+import { AvatarSize } from '../../../../../component-library/components/Avatars/Avatar';
+import AvatarToken from '../../../../../component-library/components/Avatars/Avatar/variants/AvatarToken';
+import NetworkAssetLogo from '../../../NetworkAssetLogo';
+import { TokenI } from '../../../Tokens/types';
+
+interface EarnNetworkAvatarProps {
+ token: TokenI;
+}
+
+const EarnNetworkAvatar = ({ token }: EarnNetworkAvatarProps) => {
+ const { styles } = useStyles(styleSheet, {});
+
+ if (token.isNative) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+};
+
+const EarnTokenListItem = ({
+ token,
+ primaryText,
+ secondaryText,
+ onPress,
+}: EarnTokenListItemProps) => {
+ const { styles } = useStyles(styleSheet, {});
+
+ const networkName = useSelector(selectNetworkName);
+
+ return (
+ onPress(token)}>
+
+
+ }
+ >
+
+
+ {token.name}
+
+
+
+ {primaryText.value}
+
+ {secondaryText?.value && (
+
+ {secondaryText.value}
+
+ )}
+
+
+ );
+};
+
+export default EarnTokenListItem;