Skip to content

Commit

Permalink
feat: APP-2795 - Implement AssetDataListItemStructure module component (
Browse files Browse the repository at this point in the history
  • Loading branch information
sepehr2github authored Mar 27, 2024
1 parent bd0f4ce commit a7cceee
Show file tree
Hide file tree
Showing 12 changed files with 273 additions and 18 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { Meta, StoryObj } from '@storybook/react';
import { DataList } from '../../../../../core';
import { AssetDataListItemStructure } from './assetDataListItemStructure';

const meta: Meta<typeof AssetDataListItemStructure> = {
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<typeof AssetDataListItemStructure>;

/**
* 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) => (
<DataList.Root entityLabel="Assets">
<DataList.Container>
<AssetDataListItemStructure {...props} />
</DataList.Container>
</DataList.Root>
),
};

/**
* Usage of the AssetDataListItem without changedAmount and changedPercentage.
*/
export const Fallback: Story = {
args: {
name: 'Ethereum',
amount: 420.69,
symbol: 'ETH',
},
render: (props) => (
<DataList.Root entityLabel="Assets">
<DataList.Container>
<AssetDataListItemStructure {...props} />
</DataList.Container>
</DataList.Root>
),
};

export default meta;
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { render, screen } from '@testing-library/react';
import { DataList } from '../../../../../core';
import { AssetDataListItemStructure, type IAssetDataListItemStructureProps } from './assetDataListItemStructure';

describe('<AssetDataListItem.Structure /> component', () => {
const createTestComponent = (props: Partial<IAssetDataListItemStructureProps> = {}) => {
const completeProps: IAssetDataListItemStructureProps = {
name: 'Ethereum',
symbol: 'ETH',
amount: 420.69,
...props,
};

return (
<DataList.Root entityLabel="Assets">
<DataList.Container>
<AssetDataListItemStructure {...completeProps} />
</DataList.Container>
</DataList.Root>
);
};

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();
});
});
Original file line number Diff line number Diff line change
@@ -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<IAssetDataListItemStructureProps> = (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 (
<DataList.Item {...otherProps}>
<div className="flex gap-x-3 py-0 md:py-1.5">
<div className="flex items-center">
<Avatar src={logoSrc} responsiveSize={{ md: 'md', sm: 'sm' }} className="block" />
</div>
<div className=" flex w-full justify-between">
<div className="flex flex-col gap-y-0.5">
<span className="truncate text-sm leading-tight text-neutral-800 md:text-base">{name}</span>
<p className="text-sm leading-tight text-neutral-500 md:text-base">
<span>{`${formattedAmount}`} </span>
<span className="truncate">{symbol}</span>
</p>
</div>
<div className="flex flex-col items-end justify-center gap-y-0.5">
{fiatPrice ? (
<>
<span className="text-sm leading-tight text-neutral-800 md:text-base">
{formattedPrice}
</span>
<div className="flex items-center gap-x-1">
<span className={changedAmountClasses}>
{sign(usdAmountChanged)}
{formattedPriceChanged}
</span>
<Tag
label={`${sign(priceChange / 100)}${formattedPriceChangedPercentage}`}
variant={priceChange > 0 ? 'success' : priceChange < 0 ? 'critical' : 'neutral'}
/>
</div>
</>
) : (
<span className="text-sm leading-tight text-neutral-800 md:text-base">Unknown</span>
)}
</div>
</div>
</div>
</DataList.Item>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AssetDataListItemStructure, type IAssetDataListItemStructureProps } from './assetDataListItemStructure';
7 changes: 7 additions & 0 deletions src/modules/components/asset/assetDataListItem/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { AssetDataListItemStructure } from './assetDataListItemStructure';

export const AssetDataListItem = {
Structure: AssetDataListItemStructure,
};

export type { IAssetDataListItemStructureProps } from './assetDataListItemStructure';
1 change: 1 addition & 0 deletions src/modules/components/asset/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './assetDataListItem';
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
import { DaoDataListItemStructure } from './daoDataListItemStructure';

export const DaoDataListItem = {
Structure: DaoDataListItemStructure,
};

export type { IDaoDataListItemStructureProps } from './daoDataListItemStructure';
export { DaoDataListItemStructure, type IDaoDataListItemStructureProps } from './daoDataListItemStructure';
8 changes: 7 additions & 1 deletion src/modules/components/dao/daoDataListItem/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
export * from './daoDataListItemStructure';
import { DaoDataListItemStructure } from './daoDataListItemStructure';

export const DaoDataListItem = {
Structure: DaoDataListItemStructure,
};

export type { IDaoDataListItemStructureProps } from './daoDataListItemStructure';
1 change: 1 addition & 0 deletions src/modules/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './address';
export * from './asset';
export * from './dao';
export * from './member';
export * from './odsModulesProvider';
Expand Down
8 changes: 7 additions & 1 deletion src/modules/components/member/memberDataListItem/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
export * from './memberDataListItemStructure';
import { MemberDataListItemStructure } from './memberDataListItemStructure';

export const MemberDataListItem = {
Structure: MemberDataListItemStructure,
};

export type { IMemberDataListItemProps } from './memberDataListItemStructure';
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
import { MemberDataListItemStructure } from './memberDataListItemStructure';

export const MemberDataListItem = {
Structure: MemberDataListItemStructure,
};

export type { IMemberDataListItemProps } from './memberDataListItemStructure';
export { MemberDataListItemStructure, type IMemberDataListItemProps } from './memberDataListItemStructure';

0 comments on commit a7cceee

Please sign in to comment.