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 image #### Styled Primary and Secondary Text image #### Primary Text only image ## **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;