diff --git a/.storybook/storybook.requires.js b/.storybook/storybook.requires.js
index b2fdc79d50a4..906409277994 100644
--- a/.storybook/storybook.requires.js
+++ b/.storybook/storybook.requires.js
@@ -116,6 +116,8 @@ const getStories = () => {
'./app/component-library/components-temp/TagColored/TagColored.stories.tsx': require('../app/component-library/components-temp/TagColored/TagColored.stories.tsx'),
'./app/components/UI/Name/Name.stories.tsx': require('../app/components/UI/Name/Name.stories.tsx'),
"./app/components/UI/SimulationDetails/SimulationDetails.stories.tsx": require("../app/components/UI/SimulationDetails/SimulationDetails.stories.tsx"),
+ './app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.stories': require('../app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.stories.js'),
+ './app/component-library/components-temp/Price/PercentageChange/PercentageChange.stories': require('../app/component-library/components-temp/Price/PercentageChange/PercentageChange.stories.js'),
};
};
diff --git a/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.stories.js b/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.stories.js
new file mode 100644
index 000000000000..d9706d5e2e64
--- /dev/null
+++ b/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.stories.js
@@ -0,0 +1,63 @@
+import React from 'react';
+import { Provider } from 'react-redux';
+import AggregatedPercentage from './AggregatedPercentage';
+import { createStore } from 'redux';
+import initialBackgroundState from '../../../../util/test/initial-background-state.json';
+
+const mockInitialState = {
+ wizard: {
+ step: 1,
+ },
+ engine: {
+ backgroundState: initialBackgroundState,
+ },
+};
+
+const rootReducer = (state = mockInitialState) => state;
+const store = createStore(rootReducer);
+
+export default {
+ title: 'Component Library / AggregatedPercentage',
+ component: AggregatedPercentage,
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+};
+
+const Template = (args) => ;
+
+export const Default = Template.bind({});
+Default.args = {
+ ethFiat: 1000,
+ tokenFiat: 500,
+ tokenFiat1dAgo: 950,
+ ethFiat1dAgo: 450,
+};
+
+export const NegativePercentageChange = Template.bind({});
+NegativePercentageChange.args = {
+ ethFiat: 900,
+ tokenFiat: 400,
+ tokenFiat1dAgo: 950,
+ ethFiat1dAgo: 1000,
+};
+
+export const PositivePercentageChange = Template.bind({});
+PositivePercentageChange.args = {
+ ethFiat: 1100,
+ tokenFiat: 600,
+ tokenFiat1dAgo: 500,
+ ethFiat1dAgo: 1000,
+};
+
+export const MixedPercentageChange = Template.bind({});
+MixedPercentageChange.args = {
+ ethFiat: 1050,
+ tokenFiat: 450,
+ tokenFiat1dAgo: 500,
+ ethFiat1dAgo: 1000,
+};
diff --git a/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.test.tsx b/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.test.tsx
new file mode 100644
index 000000000000..818ecc2f591f
--- /dev/null
+++ b/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.test.tsx
@@ -0,0 +1,70 @@
+import React from 'react';
+import { render } from '@testing-library/react-native';
+import AggregatedPercentage from './AggregatedPercentage';
+import { mockTheme } from '../../../../util/theme';
+import { useSelector } from 'react-redux';
+import { selectCurrentCurrency } from '../../../../selectors/currencyRateController';
+
+jest.mock('react-redux', () => ({
+ ...jest.requireActual('react-redux'),
+ useSelector: jest.fn(),
+}));
+describe('AggregatedPercentage', () => {
+ beforeEach(() => {
+ (useSelector as jest.Mock).mockImplementation((selector) => {
+ if (selector === selectCurrentCurrency) return 'USD';
+ });
+ });
+ afterEach(() => {
+ (useSelector as jest.Mock).mockClear();
+ });
+ it('should render correctly', () => {
+ const { toJSON } = render(
+ ,
+ );
+ expect(toJSON()).toMatchSnapshot();
+ });
+
+ it('renders positive percentage change correctly', () => {
+ const { getByText } = render(
+ ,
+ );
+
+ expect(getByText('(+25.00%)')).toBeTruthy();
+ expect(getByText('+100 USD')).toBeTruthy();
+
+ expect(getByText('(+25.00%)').props.style).toMatchObject({
+ color: mockTheme.colors.success.default,
+ textTransform: 'uppercase',
+ });
+ });
+
+ it('renders negative percentage change correctly', () => {
+ const { getByText } = render(
+ ,
+ );
+
+ expect(getByText('(-30.00%)')).toBeTruthy();
+ expect(getByText('-150 USD')).toBeTruthy();
+
+ expect(getByText('(-30.00%)').props.style).toMatchObject({
+ color: mockTheme.colors.error.default,
+ textTransform: 'uppercase',
+ });
+ });
+});
diff --git a/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.tsx b/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.tsx
new file mode 100644
index 000000000000..bb037f3824fb
--- /dev/null
+++ b/app/component-library/components-temp/Price/AggregatedPercentage/AggregatedPercentage.tsx
@@ -0,0 +1,93 @@
+import React from 'react';
+import { fontStyles } from '../../../../styles/common';
+import Text from '../../../../component-library/components/Texts/Text';
+import { View, StyleSheet } from 'react-native';
+import { useTheme } from '../../../../util/theme';
+import { Colors } from '../../../../util/theme/models';
+import { renderFiat } from '../../../../util/number';
+import { useSelector } from 'react-redux';
+import { selectCurrentCurrency } from '../../../../selectors/currencyRateController';
+
+const createStyles = (colors: Colors) =>
+ StyleSheet.create({
+ wrapper: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ balanceZeroStyle: {
+ color: colors.text.default,
+ ...fontStyles.normal,
+ textTransform: 'uppercase',
+ },
+ balancePositiveStyle: {
+ color: colors.success.default,
+ ...fontStyles.normal,
+ textTransform: 'uppercase',
+ },
+ balanceNegativeStyle: {
+ color: colors.error.default,
+ ...fontStyles.normal,
+ textTransform: 'uppercase',
+ },
+ });
+
+const isValidAmount = (amount: number | null | undefined): boolean =>
+ amount !== null && amount !== undefined && !Number.isNaN(amount);
+
+const AggregatedPercentage = ({
+ ethFiat,
+ tokenFiat,
+ tokenFiat1dAgo,
+ ethFiat1dAgo,
+}: {
+ ethFiat: number;
+ tokenFiat: number;
+ tokenFiat1dAgo: number;
+ ethFiat1dAgo: number;
+}) => {
+ const { colors } = useTheme();
+ const styles = createStyles(colors);
+ const currentCurrency = useSelector(selectCurrentCurrency);
+ const DECIMALS_TO_SHOW = 2;
+
+ const totalBalance = ethFiat + tokenFiat;
+ const totalBalance1dAgo = ethFiat1dAgo + tokenFiat1dAgo;
+
+ const amountChange = totalBalance - totalBalance1dAgo;
+
+ const percentageChange =
+ ((totalBalance - totalBalance1dAgo) / totalBalance1dAgo) * 100 || 0;
+
+ let percentageStyle = styles.balanceZeroStyle;
+
+ if (percentageChange === 0) {
+ percentageStyle = styles.balanceZeroStyle;
+ } else if (percentageChange > 0) {
+ percentageStyle = styles.balancePositiveStyle;
+ } else {
+ percentageStyle = styles.balanceNegativeStyle;
+ }
+
+ const formattedPercentage = isValidAmount(percentageChange)
+ ? `(${(percentageChange as number) >= 0 ? '+' : ''}${(
+ percentageChange as number
+ ).toFixed(2)}%)`
+ : '';
+
+ const formattedValuePrice = isValidAmount(amountChange)
+ ? `${(amountChange as number) >= 0 ? '+' : ''}${renderFiat(
+ amountChange,
+ currentCurrency,
+ DECIMALS_TO_SHOW,
+ )} `
+ : '';
+
+ return (
+
+ {formattedValuePrice}
+ {formattedPercentage}
+
+ );
+};
+
+export default AggregatedPercentage;
diff --git a/app/component-library/components-temp/Price/AggregatedPercentage/__snapshots__/AggregatedPercentage.test.tsx.snap b/app/component-library/components-temp/Price/AggregatedPercentage/__snapshots__/AggregatedPercentage.test.tsx.snap
new file mode 100644
index 000000000000..0466373e38f6
--- /dev/null
+++ b/app/component-library/components-temp/Price/AggregatedPercentage/__snapshots__/AggregatedPercentage.test.tsx.snap
@@ -0,0 +1,45 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`AggregatedPercentage should render correctly 1`] = `
+
+
+ +20 USD
+
+
+ (+11.11%)
+
+
+`;
diff --git a/app/component-library/components-temp/Price/AggregatedPercentage/index.ts b/app/component-library/components-temp/Price/AggregatedPercentage/index.ts
new file mode 100644
index 000000000000..3e7965d02fa3
--- /dev/null
+++ b/app/component-library/components-temp/Price/AggregatedPercentage/index.ts
@@ -0,0 +1 @@
+export { default } from './AggregatedPercentage';
diff --git a/app/component-library/components-temp/Price/PercentageChange/PercentageChange.stories.js b/app/component-library/components-temp/Price/PercentageChange/PercentageChange.stories.js
new file mode 100644
index 000000000000..59612dcf9ba2
--- /dev/null
+++ b/app/component-library/components-temp/Price/PercentageChange/PercentageChange.stories.js
@@ -0,0 +1,56 @@
+import React from 'react';
+import { Provider } from 'react-redux';
+import PercentageChange from './PercentageChange';
+import { createStore } from 'redux';
+import initialBackgroundState from '../../../../util/test/initial-background-state.json';
+
+const mockInitialState = {
+ wizard: {
+ step: 1,
+ },
+ engine: {
+ backgroundState: initialBackgroundState,
+ },
+};
+
+const rootReducer = (state = mockInitialState) => state;
+const store = createStore(rootReducer);
+
+export default {
+ title: 'Component Library / PercentageChange',
+ component: PercentageChange,
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+};
+
+const Template = (args) => ;
+
+export const Default = Template.bind({});
+Default.args = {
+ value: 0,
+};
+
+export const PositiveChange = Template.bind({});
+PositiveChange.args = {
+ value: 5.5,
+};
+
+export const NegativeChange = Template.bind({});
+NegativeChange.args = {
+ value: -3.75,
+};
+
+export const NoChange = Template.bind({});
+NoChange.args = {
+ value: 0,
+};
+
+export const InvalidValue = Template.bind({});
+InvalidValue.args = {
+ value: null,
+};
diff --git a/app/component-library/components-temp/Price/PercentageChange/PercentageChange.test.tsx b/app/component-library/components-temp/Price/PercentageChange/PercentageChange.test.tsx
new file mode 100644
index 000000000000..043aaf7e19db
--- /dev/null
+++ b/app/component-library/components-temp/Price/PercentageChange/PercentageChange.test.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import { render } from '@testing-library/react-native';
+import PercentageChange from './PercentageChange';
+import { mockTheme } from '../../../../util/theme';
+
+describe('PercentageChange', () => {
+ it('should render correctly', () => {
+ const { toJSON } = render();
+ expect(toJSON()).toMatchSnapshot();
+ });
+ it('displays a positive value correctly', () => {
+ const { getByText } = render();
+ const positiveText = getByText('+5.50%');
+ expect(positiveText).toBeTruthy();
+ expect(positiveText.props.style).toMatchObject({
+ color: mockTheme.colors.success.default,
+ textTransform: 'uppercase',
+ });
+ });
+
+ it('displays a negative value correctly', () => {
+ const { getByText } = render();
+ const negativeText = getByText('-3.25%');
+ expect(negativeText).toBeTruthy();
+ expect(negativeText.props.style).toMatchObject({
+ color: mockTheme.colors.error.default,
+ textTransform: 'uppercase',
+ });
+ });
+
+ it('handles null value correctly', () => {
+ const { queryByText } = render();
+ expect(queryByText(/\+/)).toBeNull();
+ expect(queryByText(/-/)).toBeNull();
+ });
+
+ it('handles undefined value correctly', () => {
+ const { queryByText } = render();
+ expect(queryByText(/\+/)).toBeNull();
+ expect(queryByText(/-/)).toBeNull();
+ });
+});
diff --git a/app/component-library/components-temp/Price/PercentageChange/PercentageChange.tsx b/app/component-library/components-temp/Price/PercentageChange/PercentageChange.tsx
new file mode 100644
index 000000000000..1f051a4391d4
--- /dev/null
+++ b/app/component-library/components-temp/Price/PercentageChange/PercentageChange.tsx
@@ -0,0 +1,44 @@
+import React from 'react';
+import { fontStyles } from '../../../../styles/common';
+import Text from '../../../../component-library/components/Texts/Text';
+import { View, StyleSheet } from 'react-native';
+import { useTheme } from '../../../../util/theme';
+import { Colors } from '../../../../util/theme/models';
+
+const createStyles = (colors: Colors) =>
+ StyleSheet.create({
+ balancePositiveStyle: {
+ color: colors.success.default,
+ ...fontStyles.normal,
+ textTransform: 'uppercase',
+ },
+ balanceNegativeStyle: {
+ color: colors.error.default,
+ ...fontStyles.normal,
+ textTransform: 'uppercase',
+ },
+ });
+
+const PercentageChange = ({ value }: { value: number | null | undefined }) => {
+ const { colors } = useTheme();
+ const styles = createStyles(colors);
+ const percentageStyle =
+ value && value >= 0
+ ? styles.balancePositiveStyle
+ : styles.balanceNegativeStyle;
+
+ const isValidAmount = (amount: number | null | undefined): boolean =>
+ amount !== null && amount !== undefined && !Number.isNaN(amount);
+
+ const formattedValue = isValidAmount(value)
+ ? `${(value as number) >= 0 ? '+' : ''}${(value as number).toFixed(2)}%`
+ : '';
+
+ return (
+
+ {formattedValue}
+
+ );
+};
+
+export default PercentageChange;
diff --git a/app/component-library/components-temp/Price/PercentageChange/__snapshots__/PercentageChange.test.tsx.snap b/app/component-library/components-temp/Price/PercentageChange/__snapshots__/PercentageChange.test.tsx.snap
new file mode 100644
index 000000000000..6433cce265cf
--- /dev/null
+++ b/app/component-library/components-temp/Price/PercentageChange/__snapshots__/PercentageChange.test.tsx.snap
@@ -0,0 +1,22 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`PercentageChange should render correctly 1`] = `
+
+
+ +5.50%
+
+
+`;
diff --git a/app/component-library/components-temp/Price/PercentageChange/index.ts b/app/component-library/components-temp/Price/PercentageChange/index.ts
new file mode 100644
index 000000000000..6a3f076bb2ae
--- /dev/null
+++ b/app/component-library/components-temp/Price/PercentageChange/index.ts
@@ -0,0 +1 @@
+export { default } from './PercentageChange';
diff --git a/app/components/UI/AssetElement/__snapshots__/index.test.tsx.snap b/app/components/UI/AssetElement/__snapshots__/index.test.tsx.snap
index 5704e5960e90..99fc71bcbfce 100644
--- a/app/components/UI/AssetElement/__snapshots__/index.test.tsx.snap
+++ b/app/components/UI/AssetElement/__snapshots__/index.test.tsx.snap
@@ -14,5 +14,15 @@ exports[`AssetElement should render correctly 1`] = `
}
}
testID="asset-DAI"
-/>
+>
+
+
`;
diff --git a/app/components/UI/AssetElement/index.tsx b/app/components/UI/AssetElement/index.tsx
index e95c3a3a1f66..fdf638bb1256 100644
--- a/app/components/UI/AssetElement/index.tsx
+++ b/app/components/UI/AssetElement/index.tsx
@@ -1,6 +1,6 @@
/* eslint-disable react/prop-types */
import React from 'react';
-import { TouchableOpacity, StyleSheet, Platform } from 'react-native';
+import { TouchableOpacity, StyleSheet, Platform, View } from 'react-native';
import Text, {
TextVariant,
} from '../../../component-library/components/Texts/Text';
@@ -12,15 +12,20 @@ import {
TOKEN_BALANCE_LOADING,
TOKEN_RATE_UNDEFINED,
} from '../Tokens/constants';
+import { Colors } from '../../../util/theme/models';
+import { fontStyles } from '../../../styles/common';
+import { useTheme } from '../../../util/theme';
+
interface AssetElementProps {
children?: React.ReactNode;
asset: TokenI;
onPress?: (asset: TokenI) => void;
onLongPress?: ((asset: TokenI) => void) | null;
balance?: string;
+ mainBalance?: string | null;
}
-const createStyles = () =>
+const createStyles = (colors: Colors) =>
StyleSheet.create({
itemWrapper: {
flex: 1,
@@ -32,6 +37,7 @@ const createStyles = () =>
arrow: {
flex: 1,
alignSelf: 'flex-end',
+ alignItems: 'flex-end',
},
arrowIcon: {
marginTop: 16,
@@ -39,6 +45,12 @@ const createStyles = () =>
skeleton: {
width: 50,
},
+ balanceFiat: {
+ color: colors.text.alternative,
+ paddingHorizontal: 0,
+ ...fontStyles.normal,
+ textTransform: 'uppercase',
+ },
});
/**
@@ -48,10 +60,12 @@ const AssetElement: React.FC = ({
children,
balance,
asset,
+ mainBalance = null,
onPress,
onLongPress,
}) => {
- const styles = createStyles();
+ const { colors } = useTheme();
+ const styles = createStyles(colors);
const handleOnPress = () => {
onPress?.(asset);
@@ -70,21 +84,32 @@ const AssetElement: React.FC = ({
>
{children}
- {balance && (
-
- {balance === TOKEN_BALANCE_LOADING ? (
-
- ) : (
- balance
- )}
-
- )}
+
+ {balance && (
+
+ {balance === TOKEN_BALANCE_LOADING ? (
+
+ ) : (
+ balance
+ )}
+
+ )}
+ {mainBalance ? (
+
+ {mainBalance === TOKEN_BALANCE_LOADING ? (
+
+ ) : (
+ mainBalance
+ )}
+
+ ) : null}
+
);
};
diff --git a/app/components/UI/Tokens/__snapshots__/index.test.tsx.snap b/app/components/UI/Tokens/__snapshots__/index.test.tsx.snap
index 4aaccc2fc586..eda582ee8983 100644
--- a/app/components/UI/Tokens/__snapshots__/index.test.tsx.snap
+++ b/app/components/UI/Tokens/__snapshots__/index.test.tsx.snap
@@ -782,22 +782,22 @@ exports[`Tokens should hide zero balance tokens when setting is on 1`] = `
/>
-
+
- < $0.01
-
+ />
+
-
- ETH
-
+
+ ETH
+
+
+ < $0.01
+
+
-
+
- < $0.01
-
+ />
+
-
- < 0.00001 BAT
-
+
+ < 0.00001 BAT
+
+
+ < $0.01
+
+
-
+
- < $0.01
-
+ />
+
-
- ETH
-
+
+ ETH
+
+
+ < $0.01
+
+
-
+
- < $0.01
-
+ />
+
-
- < 0.00001 BAT
-
+
+ < 0.00001 BAT
+
+
+ < $0.01
+
+
-
+
- < $0.01
-
+ />
+
-
- ETH
-
+
+ ETH
+
+
+ < $0.01
+
+
-
+
- < $0.01
-
+ />
+
-
- < 0.00001 BAT
-
+
+ < 0.00001 BAT
+
+
+ < $0.01
+
+
-
+
- < $0.01
-
+ />
+
-
- 0 LINK
-
+
+ 0 LINK
+
+
+ < $0.01
+
+
= ({ tokens }) => {
const { colors } = useTheme();
@@ -135,6 +137,7 @@ const Tokens: React.FC = ({ tokens }) => {
);
const { data: tokenBalances } = useTokenBalancesController();
const tokenExchangeRates = useSelector(selectContractExchangeRates);
+
const hideZeroBalanceTokens = useSelector(
// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -367,6 +370,10 @@ const Tokens: React.FC = ({ tokens }) => {
const { balanceFiat, balanceValueFormatted } = handleBalance(asset);
+ const pricePercentChange1d = itemAddress
+ ? tokenExchangeRates?.[itemAddress as `0x${string}`]?.pricePercentChange1d
+ : tokenExchangeRates?.[zeroAddress()]?.pricePercentChange1d;
+
// render balances according to primary currency
let mainBalance, secondaryBalance;
mainBalance = TOKEN_BALANCE_LOADING;
@@ -434,6 +441,7 @@ const Tokens: React.FC = ({ tokens }) => {
onLongPress={asset.isETH ? null : showRemoveMenu}
asset={asset}
balance={secondaryBalance}
+ mainBalance={mainBalance}
>
= ({ tokens }) => {
{/** Add button link to Portfolio Stake if token is mainnet ETH */}
{asset.isETH && isMainnet && renderStakeButton(asset)}
-
-
- {mainBalance === TOKEN_BALANCE_LOADING ? (
-
- ) : (
- mainBalance
- )}
-
+
{renderScamWarningIcon(asset)}
@@ -622,12 +623,21 @@ const Tokens: React.FC = ({ tokens }) => {
return (
-
- {fiatBalance}
-
+
+
+ {fiatBalance}
+
+
+
+