Skip to content

Commit

Permalink
refactor(product-components): SelectAssetModal independent f coinmarket
Browse files Browse the repository at this point in the history
  • Loading branch information
enjojoy authored and tomasklim committed Nov 5, 2024
1 parent b57033c commit 12964d6
Show file tree
Hide file tree
Showing 10 changed files with 523 additions and 316 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { spacings, spacingsPx } from '@trezor/theme';
import { Badge, Column, Icon, Row, Text } from '@trezor/components';
import styled, { useTheme } from 'styled-components';
import { NetworkSymbol } from '@suite-common/wallet-config';
import { SelectAssetOptionCurrencyProps } from './SelectAssetModal';

const ClickableContainer = styled.div`
cursor: pointer;
Expand All @@ -26,23 +28,27 @@ const BadgeWrapper = styled.div`
`;

type AssetItemProps = {
cryptoId: string;
name: string;
symbol: string;
name?: string;
symbol: NetworkSymbol;
badge: string | undefined;
networkSymbol: NetworkSymbol;
coingeckoId: string;
logo: JSX.Element;
isFavorite?: boolean;
handleClick: (selectedAsset: string) => void;
contractAddress: string | null;
handleClick: (selectedAsset: SelectAssetOptionCurrencyProps) => void;
onFavoriteClick?: (isFavorite: boolean) => void;
};

export const AssetItem = ({
cryptoId,
name,
symbol,
badge,
networkSymbol,
coingeckoId,
logo,
isFavorite = false,
contractAddress,
handleClick,
onFavoriteClick,
}: AssetItemProps) => {
Expand All @@ -51,7 +57,17 @@ export const AssetItem = ({
const handleFavoriteClick = onFavoriteClick ? () => onFavoriteClick(isFavorite) : undefined;

return (
<ClickableContainer onClick={() => handleClick(cryptoId)}>
<ClickableContainer
onClick={() =>
handleClick({
symbol,
contractAddress: contractAddress ?? null,
type: 'currency',
networkSymbol,
coingeckoId,
})
}
>
<Row gap={spacings.sm}>
{logo}
<Column flex="1" alignItems="stretch">
Expand All @@ -68,7 +84,7 @@ export const AssetItem = ({
)}
</Row>
<Text typographyStyle="hint" variant="tertiary">
{symbol}
{symbol.toUpperCase()}
</Text>
</Column>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Column, Paragraph, Text } from '@trezor/components';
import { FormattedMessage } from 'react-intl';
import { SelectAssetNetworkProps, SelectAssetSearchCategoryType } from './SelectAssetModal';
import { NetworkFilterCategory, SelectAssetSearchCategory } from './SelectAssetModal';
import { spacings } from '@trezor/theme';

interface AssetItemNotFoundProps {
searchCategory: SelectAssetSearchCategoryType;
networkCategories: SelectAssetNetworkProps[];
searchCategory: SelectAssetSearchCategory;
networkCategories: NetworkFilterCategory[];
listHeight: string;
listMinHeight: number;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { AssetLogo, Row, Tooltip, useElevation } from '@trezor/components';
import { Elevation, mapElevationToBorder, spacings, spacingsPx } from '@trezor/theme';
import { SelectAssetNetworkProps, SelectAssetSearchCategoryType } from './SelectAssetModal';
import { NetworkFilterCategory, SelectAssetSearchCategory } from './SelectAssetModal';
import styled from 'styled-components';
import { FormattedMessage } from 'react-intl';
import { CheckableTag } from './CheckableTag';
Expand All @@ -18,10 +18,10 @@ const NetworkTabsWrapper = styled.div<NetworkTabsWrapperProps>`
`;

interface NetworkTabsProps {
networkCategories: SelectAssetNetworkProps[];
networkCategories: NetworkFilterCategory[];
networkCount: number;
searchCategory: SelectAssetSearchCategoryType;
setSearchCategory: (value: SelectAssetSearchCategoryType) => void;
searchCategory: SelectAssetSearchCategory;
setSearchCategory: (value: SelectAssetSearchCategory) => void;
}

export const NetworkTabs = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { intermediaryTheme, NewModal } from '@trezor/components';
import { action } from '@storybook/addon-actions';
import { IntlProvider } from 'react-intl';
import { selectAssetModalOptions, selectAssetModalNetworks } from './mockData';
import { NetworkFilterCategory } from './SelectAssetModal';

const meta: Meta = {
title: 'SelectAssetModal',
Expand All @@ -29,7 +30,10 @@ export const SelectAssetModal: StoryObj<SelectAssetModalProps> = {
// onFavoriteClick: undefined,
onClose: action('onClose'),
options: selectAssetModalOptions,
networkCategories: selectAssetModalNetworks,
filterCategories: {
categoriesType: 'networks',
categories: selectAssetModalNetworks as NetworkFilterCategory[],
},
},
argTypes: {},
};
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,22 @@ import { AssetItem } from './AssetItem';
import { NetworkTabs } from './NetworkTabs';
import { useIntl } from 'react-intl';
import { AssetItemNotFound } from './AssetItemNotFound';
import { getNetworkByCoingeckoId, Network } from '@suite-common/wallet-config';
import { getNetworkByCoingeckoId, Network, NetworkSymbol } from '@suite-common/wallet-config';
import { SendTokenTabs } from './SendTokenTabs';

export interface SelectAssetOptionCurrencyProps {
type: 'currency';
value: string; // CryptoId (networkId + contractAddress)
label: string; // token shortcut
cryptoName: string | undefined; // full name
coingeckoId: string; // CryptoId (networkId)
contractAddress?: string; // CryptoId (contractAddress)
symbol: string;
balance?: string;
badge?: string;
networkSymbol: NetworkSymbol | (string & {});
cryptoName?: string | undefined;
coingeckoId: string;
contractAddress: string | null;
networkName?: string;
hidden?: boolean;
unverified?: boolean;
tokenExplorerUrl?: string;
}
export interface SelectAssetOptionGroupProps {
type: 'group';
Expand All @@ -32,49 +38,70 @@ export interface SelectAssetOptionGroupProps {
}
export type SelectAssetOptionProps = SelectAssetOptionCurrencyProps | SelectAssetOptionGroupProps;

export interface SelectAssetNetworkProps {
export type NetworkFilterCategory = {
name: Network['name'];
symbol: Network['symbol'];
coingeckoId: Network['coingeckoId'];
coingeckoNativeId?: Network['coingeckoNativeId'];
}
};

export type SelectAssetSearchCategoryType = {
export type NetworkFilterCategories = {
categoriesType: 'networks';
categories: NetworkFilterCategory[];
};

export type SelectAssetSearchCategory = {
coingeckoId: string;
coingeckoNativeId?: string;
} | null;

export type TokenFilterCategory = {
type: 'visibleWithBalance' | 'hiddenWithBalance';
label: string;
};

export type TokenFilterCategories = {
categoriesType: 'tokens';
categories: TokenFilterCategory[];
};

export interface SelectAssetModalProps {
options: SelectAssetOptionProps[];
networkCategories: SelectAssetNetworkProps[];
onSelectAssetModal: (selectedAsset: string) => void;
filterCategories: NetworkFilterCategories | TokenFilterCategories;
searchPlaceholderText?: string;
setPickedSendTokenCategory?: (category: TokenFilterCategory['type']) => void;
pickedSendTokenCategory?: TokenFilterCategory['type'];
onSelectAssetModal: (selectedAsset: SelectAssetOptionCurrencyProps) => void;
onFavoriteClick?: (isFavorite: boolean) => void;
onClose: () => void;
disableSearchByNetwork?: boolean;
selectAssetModalHeight?: SelectAssetModalHeight;
}

interface AssetProps
extends Pick<SelectAssetOptionCurrencyProps, 'coingeckoId' | 'contractAddress'> {
cryptoId: string;
symbol: string;
name: string;
networkSymbol: NetworkSymbol | ({} & string);
name: string | undefined;
height: number;
isFavorite: boolean;
badge?: string;
unverified?: boolean;
}

const HEADER_HEIGHT = 267;
export type SelectAssetModalHeight = 'short' | 'tall';

const ITEM_HEIGHT = 60;
const LIST_HEIGHT = `calc(80vh - ${HEADER_HEIGHT}px)`;
const LIST_MIN_HEIGHT = ITEM_HEIGHT * 3;

const getData = (options: SelectAssetOptionProps[]): AssetProps[] =>
options
.filter(item => item.type === 'currency')
.map(item => ({
cryptoId: item.value,
symbol: item.label,
name: item.cryptoName ?? item.label,
badge: item.networkName,
symbol: item.symbol,
networkSymbol: item.networkSymbol,
name: item.cryptoName ?? item.symbol,
badge: item.badge ?? item.networkName,
coingeckoId: item.coingeckoId,
contractAddress: item.contractAddress,
isFavorite: false,
Expand Down Expand Up @@ -106,33 +133,49 @@ const getNetworkCount = (options: SelectAssetOptionProps[]) => {

export const SelectAssetModal = ({
options,
networkCategories,
filterCategories,
searchPlaceholderText,
setPickedSendTokenCategory,
pickedSendTokenCategory = 'visibleWithBalance',
onSelectAssetModal,
onFavoriteClick,
disableSearchByNetwork = false,
onClose,
selectAssetModalHeight = 'tall',
}: SelectAssetModalProps) => {
const intl = useIntl();
const [search, setSearch] = useState('');
const [searchCategory, setSearchCategory] = useState<SelectAssetSearchCategoryType>(null); // coingeckoNativeId as fallback for ex. polygon
const [searchCategory, setSearchCategory] = useState<SelectAssetSearchCategory>(null); // coingeckoNativeId as fallback for ex. polygon
const [end, setEnd] = useState(options.length);
const data = useMemo(() => getData(options), [options]);
const { scrollElementRef, onScroll, ShadowTop, ShadowBottom, ShadowContainer } =
useScrollShadow();
const networkCount = getNetworkCount(options);

const searchPlaceholder = searchPlaceholderText
? searchPlaceholderText
: intl.formatMessage({
id: 'TR_SELECT_NAME_OR_ADDRESS',
defaultMessage: 'Search by name, symbol, network or contract address',
});

const filteredData = data.filter(item => {
const categoryFilter = searchCategory
? item.coingeckoId === searchCategory.coingeckoId ||
item.coingeckoId === searchCategory.coingeckoNativeId
: true;
const networkContractAddress = `${item.networkSymbol}${item.contractAddress ? `--${item.contractAddress}` : ''}`;
const contractAddress = item.contractAddress || undefined;
const searchFor = (property: string | undefined) =>
property?.toLocaleLowerCase().includes(search.toLocaleLowerCase());

return (
(searchFor(item.name) ||
searchFor(item.cryptoId) ||
searchFor(item.badge) ||
searchFor(item.symbol)) &&
(!disableSearchByNetwork && searchFor(item.symbol)) ||
(disableSearchByNetwork
? searchFor(contractAddress)
: searchFor(networkContractAddress))) &&
categoryFilter
);
});
Expand All @@ -141,6 +184,9 @@ export const SelectAssetModal = ({
$elevation: 0,
});

const HEADER_HEIGHT = 267;
const LIST_HEIGHT = `calc(${selectAssetModalHeight === 'short' ? '50' : '80'}vh - ${HEADER_HEIGHT}px)`;

return (
<NewModal
heading={intl.formatMessage({
Expand All @@ -152,10 +198,7 @@ export const SelectAssetModal = ({
>
<Column gap={spacings.md} alignItems="stretch">
<Input
placeholder={intl.formatMessage({
id: 'TR_SELECT_NAME_OR_ADDRESS',
defaultMessage: 'Search by name, symbol, network or contract address',
})}
placeholder={searchPlaceholder}
value={search}
onChange={event => setSearch(event.target.value)}
autoFocus
Expand All @@ -167,19 +210,30 @@ export const SelectAssetModal = ({
innerAddon={<Icon name="search" variant="tertiary" size="medium" />}
innerAddonAlign="left"
/>
<NetworkTabs
networkCategories={networkCategories}
networkCount={networkCount}
searchCategory={searchCategory}
setSearchCategory={setSearchCategory}
/>
{filterCategories?.categoriesType === 'networks' && (
<NetworkTabs
networkCategories={filterCategories.categories}
networkCount={networkCount}
searchCategory={searchCategory}
setSearchCategory={setSearchCategory}
/>
)}
{filterCategories?.categoriesType === 'tokens' &&
setPickedSendTokenCategory &&
pickedSendTokenCategory && (
<SendTokenTabs
sendTokenCategories={filterCategories.categories}
pickedCategory={pickedSendTokenCategory}
setPickedCategory={setPickedSendTokenCategory}
/>
)}

{filteredData.length === 0 ? (
<AssetItemNotFound
listHeight={LIST_HEIGHT}
listMinHeight={LIST_MIN_HEIGHT}
searchCategory={searchCategory}
networkCategories={networkCategories}
networkCategories={filterCategories.categories as NetworkFilterCategory[]}
/>
) : (
<ShadowContainer>
Expand All @@ -189,27 +243,29 @@ export const SelectAssetModal = ({
ref={scrollElementRef}
onScroll={onScroll}
renderItem={({
cryptoId,
name,
symbol,
coingeckoId,
networkSymbol,
isFavorite,
badge,
coingeckoId,
contractAddress,
}: AssetProps) => (
<AssetItem
key={`${symbol}-${name}`}
cryptoId={cryptoId}
name={name}
symbol={symbol}
symbol={symbol as NetworkSymbol}
coingeckoId={coingeckoId}
contractAddress={contractAddress || null}
networkSymbol={networkSymbol as NetworkSymbol}
isFavorite={isFavorite}
badge={badge}
logo={
<AssetLogo
size={24}
coingeckoId={coingeckoId}
contractAddress={contractAddress}
placeholder={symbol.toLowerCase()}
contractAddress={contractAddress?.toLowerCase()}
placeholder={symbol.toUpperCase()}
/>
}
handleClick={onSelectAssetModal}
Expand Down
Loading

0 comments on commit 12964d6

Please sign in to comment.