From a7cceee30fbf847642142540b3b25bd9485a37dc Mon Sep 17 00:00:00 2001 From: Sepehr Sanaei <46657145+sepehr2github@users.noreply.github.com> Date: Wed, 27 Mar 2024 12:51:40 +0330 Subject: [PATCH] feat: APP-2795 - Implement AssetDataListItemStructure module component (#128) --- CHANGELOG.md | 4 +- .../assetDataListItemStructure.stories.tsx | 58 +++++++++ .../assetDataListItemStructure.test.tsx | 72 +++++++++++ .../assetDataListItemStructure.tsx | 115 ++++++++++++++++++ .../assetDataListItemStructure/index.ts | 1 + .../asset/assetDataListItem/index.ts | 7 ++ src/modules/components/asset/index.ts | 1 + .../daoDataListItemStructure/index.ts | 8 +- .../components/dao/daoDataListItem/index.ts | 8 +- src/modules/components/index.ts | 1 + .../member/memberDataListItem/index.ts | 8 +- .../memberDataListItemStructure/index.ts | 8 +- 12 files changed, 273 insertions(+), 18 deletions(-) create mode 100644 src/modules/components/asset/assetDataListItem/assetDataListItemStructure/assetDataListItemStructure.stories.tsx create mode 100644 src/modules/components/asset/assetDataListItem/assetDataListItemStructure/assetDataListItemStructure.test.tsx create mode 100644 src/modules/components/asset/assetDataListItem/assetDataListItemStructure/assetDataListItemStructure.tsx create mode 100644 src/modules/components/asset/assetDataListItem/assetDataListItemStructure/index.ts create mode 100644 src/modules/components/asset/assetDataListItem/index.ts create mode 100644 src/modules/components/asset/index.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 728376ba0..58267bdc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added -- Implement `DaoDataListItem.Structure`, `ProposalDataListItem.Structure`, `MemberDataListItem.Structure` and - `AddressInput` module components +- Implement `DaoDataListItem.Structure`, `ProposalDataListItem.Structure`, `MemberDataListItem.Structure`, + `AssetDataListItem.Structure` and `AddressInput` module components - Implement `StatePingAnimation` core component - Implement `addressUtils` and `ensUtils` module utilities - Implement `useDebouncedValue` core hook and `clipboardUtils` core utility diff --git a/src/modules/components/asset/assetDataListItem/assetDataListItemStructure/assetDataListItemStructure.stories.tsx b/src/modules/components/asset/assetDataListItem/assetDataListItemStructure/assetDataListItemStructure.stories.tsx new file mode 100644 index 000000000..75d37c1ca --- /dev/null +++ b/src/modules/components/asset/assetDataListItem/assetDataListItemStructure/assetDataListItemStructure.stories.tsx @@ -0,0 +1,58 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { DataList } from '../../../../../core'; +import { AssetDataListItemStructure } from './assetDataListItemStructure'; + +const meta: Meta = { + title: 'Modules/Components/Asset/AssetDataListItem/AssetDataListItem.Structure', + component: AssetDataListItemStructure, + tags: ['autodocs'], + parameters: { + design: { + type: 'figma', + url: 'https://www.figma.com/file/P0GeJKqILL7UXvaqu5Jj7V/v1.1.0?type=design&node-id=8079-18352&mode=design&t=MR1awSDoExtPDiEd-4', + }, + }, +}; + +type Story = StoryObj; + +/** + * Default usage example of the AssetDataListItem component. + */ +export const Default: Story = { + args: { + logoSrc: 'https://assets.coingecko.com/coins/images/279/standard/ethereum.png?1696501628', + name: 'Ethereum', + amount: 420.69, + symbol: 'ETH', + fiatPrice: 3654.76, + priceChange: 15, + }, + render: (props) => ( + + + + + + ), +}; + +/** + * Usage of the AssetDataListItem without changedAmount and changedPercentage. + */ +export const Fallback: Story = { + args: { + name: 'Ethereum', + amount: 420.69, + symbol: 'ETH', + }, + render: (props) => ( + + + + + + ), +}; + +export default meta; diff --git a/src/modules/components/asset/assetDataListItem/assetDataListItemStructure/assetDataListItemStructure.test.tsx b/src/modules/components/asset/assetDataListItem/assetDataListItemStructure/assetDataListItemStructure.test.tsx new file mode 100644 index 000000000..cf7f0dff9 --- /dev/null +++ b/src/modules/components/asset/assetDataListItem/assetDataListItemStructure/assetDataListItemStructure.test.tsx @@ -0,0 +1,72 @@ +import { render, screen } from '@testing-library/react'; +import { DataList } from '../../../../../core'; +import { AssetDataListItemStructure, type IAssetDataListItemStructureProps } from './assetDataListItemStructure'; + +describe(' component', () => { + const createTestComponent = (props: Partial = {}) => { + const completeProps: IAssetDataListItemStructureProps = { + name: 'Ethereum', + symbol: 'ETH', + amount: 420.69, + ...props, + }; + + return ( + + + + + + ); + }; + + it('renders tokenName and symbol', () => { + const props = { + name: 'Ethereum', + symbol: 'ETH', + amount: 420.69, + }; + + render(createTestComponent(props)); + expect(screen.getByText(props.name)).toBeInTheDocument(); + expect(screen.getByText(props.symbol)).toBeInTheDocument(); + }); + + it('renders amount, fiat price', async () => { + const props = { + name: 'Ethereum', + symbol: 'ETH', + amount: 420.69, + fiatPrice: 3654.76, + }; + + render(createTestComponent(props)); + const USDAmount = await screen.findByText(/1.54/); + expect(USDAmount).toHaveTextContent('$1.54M'); + expect(screen.getByText(props.amount)).toBeInTheDocument(); + }); + + it('handles not passing fiat price', () => { + const props = { + name: 'Ethereum', + symbol: 'ETH', + amount: 0, + priceChange: 0, + }; + + render(createTestComponent(props)); + expect(screen.getByText('Unknown')).toBeInTheDocument(); // Assuming Tag component renders '0%' for zero priceChange + }); + + it('handle not passing priceChange', async () => { + const props = { + name: 'Ethereum', + amount: 420.69, + symbol: 'ETH', + fiatPrice: 3654.76, + }; + + render(createTestComponent(props)); + expect(screen.getByText('0%')).toBeInTheDocument(); + }); +}); diff --git a/src/modules/components/asset/assetDataListItem/assetDataListItemStructure/assetDataListItemStructure.tsx b/src/modules/components/asset/assetDataListItem/assetDataListItemStructure/assetDataListItemStructure.tsx new file mode 100644 index 000000000..216ed69db --- /dev/null +++ b/src/modules/components/asset/assetDataListItem/assetDataListItemStructure/assetDataListItemStructure.tsx @@ -0,0 +1,115 @@ +import classNames from 'classnames'; +import type React from 'react'; +import { useMemo } from 'react'; +import { Avatar, DataList, NumberFormat, Tag, formatterUtils, type IDataListItemProps } from '../../../../../core'; + +export interface IAssetDataListItemStructureProps extends IDataListItemProps { + /** + * The name of the asset. + */ + name: string; + /** + * The symbol of the asset. + */ + symbol: string; + /** + * The amount of the asset. + */ + amount: number | string; + /** + * The logo source of the asset + */ + logoSrc?: string; + /** + * The fiat price of the asset. + */ + fiatPrice?: number | string; + /** + * the price change in percentage of the asset (E.g. in last 24h). + * @default 0 + */ + priceChange?: number; +} + +export const AssetDataListItemStructure: React.FC = (props) => { + const { logoSrc, name, amount, symbol, fiatPrice, priceChange = 0, ...otherProps } = props; + + const usdAmountChanged = useMemo(() => { + if (!fiatPrice || !priceChange) { + return 0; + } + const usdAmount = (amount ? Number(amount) : 0) * (fiatPrice ? Number(fiatPrice) : 0); + const oldUsdAmount = (100 / (priceChange + 100)) * usdAmount; + return usdAmount - oldUsdAmount; + }, [amount, fiatPrice, priceChange]); + + const sign = (value: number) => (value > 0 ? '+' : value < 0 ? '-' : ''); + + const changedAmountClasses = classNames( + 'text-sm font-normal leading-tight md:text-base', + { 'text-success-800': usdAmountChanged > 0 }, + { 'text-neutral-500': usdAmountChanged === 0 }, + { 'text-critical-800': usdAmountChanged < 0 }, + ); + + const formattedAmount = formatterUtils.formatNumber(amount, { + format: NumberFormat.TOKEN_AMOUNT_SHORT, + fallback: '', + }); + + const formattedPrice = formatterUtils.formatNumber( + (amount ? Number(amount) : 0) * (fiatPrice ? Number(fiatPrice) : 0), + { + format: NumberFormat.FIAT_TOTAL_SHORT, + fallback: '-', + }, + ); + + const formattedPriceChanged = formatterUtils.formatNumber(Math.abs(usdAmountChanged), { + format: NumberFormat.FIAT_TOTAL_SHORT, + }); + + const formattedPriceChangedPercentage = formatterUtils.formatNumber(Math.abs(priceChange / 100), { + format: NumberFormat.PERCENTAGE_SHORT, + }); + + return ( + +
+
+ +
+
+
+ {name} +

+ {`${formattedAmount}`} + {symbol} +

+
+
+ {fiatPrice ? ( + <> + + {formattedPrice} + +
+ + {sign(usdAmountChanged)} + {formattedPriceChanged} + + 0 ? 'success' : priceChange < 0 ? 'critical' : 'neutral'} + /> +
+ + ) : ( + Unknown + )} +
+
+
+
+ ); +}; diff --git a/src/modules/components/asset/assetDataListItem/assetDataListItemStructure/index.ts b/src/modules/components/asset/assetDataListItem/assetDataListItemStructure/index.ts new file mode 100644 index 000000000..489e9785e --- /dev/null +++ b/src/modules/components/asset/assetDataListItem/assetDataListItemStructure/index.ts @@ -0,0 +1 @@ +export { AssetDataListItemStructure, type IAssetDataListItemStructureProps } from './assetDataListItemStructure'; diff --git a/src/modules/components/asset/assetDataListItem/index.ts b/src/modules/components/asset/assetDataListItem/index.ts new file mode 100644 index 000000000..85f31b135 --- /dev/null +++ b/src/modules/components/asset/assetDataListItem/index.ts @@ -0,0 +1,7 @@ +import { AssetDataListItemStructure } from './assetDataListItemStructure'; + +export const AssetDataListItem = { + Structure: AssetDataListItemStructure, +}; + +export type { IAssetDataListItemStructureProps } from './assetDataListItemStructure'; diff --git a/src/modules/components/asset/index.ts b/src/modules/components/asset/index.ts new file mode 100644 index 000000000..e64cc0259 --- /dev/null +++ b/src/modules/components/asset/index.ts @@ -0,0 +1 @@ +export * from './assetDataListItem'; diff --git a/src/modules/components/dao/daoDataListItem/daoDataListItemStructure/index.ts b/src/modules/components/dao/daoDataListItem/daoDataListItemStructure/index.ts index 9de8fc4b7..b2858938d 100644 --- a/src/modules/components/dao/daoDataListItem/daoDataListItemStructure/index.ts +++ b/src/modules/components/dao/daoDataListItem/daoDataListItemStructure/index.ts @@ -1,7 +1 @@ -import { DaoDataListItemStructure } from './daoDataListItemStructure'; - -export const DaoDataListItem = { - Structure: DaoDataListItemStructure, -}; - -export type { IDaoDataListItemStructureProps } from './daoDataListItemStructure'; +export { DaoDataListItemStructure, type IDaoDataListItemStructureProps } from './daoDataListItemStructure'; diff --git a/src/modules/components/dao/daoDataListItem/index.ts b/src/modules/components/dao/daoDataListItem/index.ts index b6fd5ed01..9de8fc4b7 100644 --- a/src/modules/components/dao/daoDataListItem/index.ts +++ b/src/modules/components/dao/daoDataListItem/index.ts @@ -1 +1,7 @@ -export * from './daoDataListItemStructure'; +import { DaoDataListItemStructure } from './daoDataListItemStructure'; + +export const DaoDataListItem = { + Structure: DaoDataListItemStructure, +}; + +export type { IDaoDataListItemStructureProps } from './daoDataListItemStructure'; diff --git a/src/modules/components/index.ts b/src/modules/components/index.ts index 14a21e0b3..00f45ebbd 100644 --- a/src/modules/components/index.ts +++ b/src/modules/components/index.ts @@ -1,4 +1,5 @@ export * from './address'; +export * from './asset'; export * from './dao'; export * from './member'; export * from './odsModulesProvider'; diff --git a/src/modules/components/member/memberDataListItem/index.ts b/src/modules/components/member/memberDataListItem/index.ts index 3ebb05790..8bdc5bc5c 100644 --- a/src/modules/components/member/memberDataListItem/index.ts +++ b/src/modules/components/member/memberDataListItem/index.ts @@ -1 +1,7 @@ -export * from './memberDataListItemStructure'; +import { MemberDataListItemStructure } from './memberDataListItemStructure'; + +export const MemberDataListItem = { + Structure: MemberDataListItemStructure, +}; + +export type { IMemberDataListItemProps } from './memberDataListItemStructure'; diff --git a/src/modules/components/member/memberDataListItem/memberDataListItemStructure/index.ts b/src/modules/components/member/memberDataListItem/memberDataListItemStructure/index.ts index 8bdc5bc5c..22ef3ce48 100644 --- a/src/modules/components/member/memberDataListItem/memberDataListItemStructure/index.ts +++ b/src/modules/components/member/memberDataListItem/memberDataListItemStructure/index.ts @@ -1,7 +1 @@ -import { MemberDataListItemStructure } from './memberDataListItemStructure'; - -export const MemberDataListItem = { - Structure: MemberDataListItemStructure, -}; - -export type { IMemberDataListItemProps } from './memberDataListItemStructure'; +export { MemberDataListItemStructure, type IMemberDataListItemProps } from './memberDataListItemStructure';