Skip to content

Commit

Permalink
feat(coinmarket): add crypto picker for buy/exchange
Browse files Browse the repository at this point in the history
  • Loading branch information
adderpositive authored and tomasklim committed Oct 1, 2024
1 parent 36c867d commit 01c60f3
Show file tree
Hide file tree
Showing 16 changed files with 1,752 additions and 167 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ export const VirtualizedList: StoryFn = () => {
onScrollEnd={() => {
setEnd(end + 1000);
}}
height={400}
listHeight={400}
listMinHeight={180}
renderItem={(_item: any, index: number) => <Item>content {index}</Item>}
/>
</Container>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isChanged } from '@suite-common/suite-utils';
import React, { useState, useEffect, useCallback, forwardRef, useRef } from 'react';
import styled from 'styled-components';

Expand All @@ -22,8 +23,16 @@ const BEFORE_AFTER_BUFFER_COUNT = 100;
const LOAD_MORE_BUFFER_COUNT = 100;
const ESTIMATED_ITEM_HEIGHT = 40;

const Container = styled.div<{ $height: number | string }>`
interface ContainerProps {
$height: number | string;
$minHeight: number | string;
}

const Container = styled.div<ContainerProps>`
height: ${({ $height }) => (typeof $height === 'number' ? `${$height}px` : $height)};
min-height: ${({ $minHeight }) =>
typeof $minHeight === 'number' ? `${$minHeight}px` : $minHeight};
width: 100%;
overflow-y: auto;
position: relative;
Expand All @@ -44,13 +53,21 @@ type VirtualizedListProps<T> = {
items: Array<T & { height: number }>;
onScroll?: (e: Event) => void;
onScrollEnd: () => void;
height: number | string;
listHeight: number | string;
listMinHeight: number | string;
renderItem: (item: T, index: number) => React.ReactNode;
};

export const VirtualizedList = forwardRef<HTMLDivElement, VirtualizedListProps<any>>(
<T,>(
{ items: initialItems, onScroll, onScrollEnd, height, renderItem }: VirtualizedListProps<T>,
{
items: initialItems,
onScroll,
onScrollEnd,
listHeight,
listMinHeight,
renderItem,
}: VirtualizedListProps<T>,
ref: React.Ref<HTMLDivElement>,
) => {
const newRef = useRef<HTMLDivElement>(null);
Expand All @@ -62,9 +79,19 @@ export const VirtualizedList = forwardRef<HTMLDivElement, VirtualizedListProps<a
const [totalHeight, setTotalHeight] = useState(0);
const debouncedOnScrollEnd = debounce(onScrollEnd, 1000);

const resetScroll = useCallback(() => {
if (!containerRef.current) return;

containerRef.current.scrollTop = 0;
}, [containerRef]);

useEffect(() => {
setItems(initialItems);
}, [initialItems]);
if (isChanged(items, initialItems)) {
setItems(initialItems);
resetScroll();
}
}, [initialItems, items, resetScroll]);

useEffect(() => {
const heights = items.map(item => calculateItemHeight(item));
setItemHeights(heights);
Expand Down Expand Up @@ -123,7 +150,7 @@ export const VirtualizedList = forwardRef<HTMLDivElement, VirtualizedListProps<a
}, [containerRef, handleScroll]);

return (
<Container ref={containerRef} $height={height}>
<Container ref={containerRef} $height={listHeight} $minHeight={listMinHeight}>
<Content style={{ height: `${totalHeight}px` }}>
{itemHeights.slice(startIndex, endIndex).map((height, index) => {
const itemIndex = startIndex + index;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { spacings, spacingsPx } from '@trezor/theme';
import { AssetLogo, Badge, Column, Icon, Row, Text } from '@trezor/components';
import { Badge, Column, Icon, Row, Text } from '@trezor/components';
import styled, { useTheme } from 'styled-components';

const ClickableContainer = styled.div`
Expand All @@ -11,43 +11,67 @@ const ClickableContainer = styled.div`
background: ${({ theme }) => theme.backgroundTertiaryPressedOnElevation0};
}
`;

const IconWrapper = styled.div`
cursor: pointer;
`;

const TextWrapper = styled.div`
overflow: hidden;
text-overflow: ellipsis;
`;

const BadgeWrapper = styled.div`
flex: none;
`;

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

export const AssetItem = ({
handleClick,
cryptoId,
name,
symbol,
badge,
logo,
isFavorite = false,
handleClick,
onFavoriteClick,
}: AssetItemProps) => {
const theme = useTheme();

const handleFavoriteClick = () => onFavoriteClick?.(isFavorite);
const handleFavoriteClick = onFavoriteClick ? () => onFavoriteClick(isFavorite) : undefined;

return (
<ClickableContainer onClick={() => handleClick(symbol)}>
<Row gap={spacings.sm} margin={{ right: spacings.md }}>
<AssetLogo size={24} coingeckoId="bitcoin" placeholder="btc" />
<ClickableContainer onClick={() => handleClick(cryptoId)}>
<Row gap={spacings.sm}>
{logo}
<Column flex="1" alignItems="stretch">
<Text typographyStyle="body">{name}</Text>
<Row gap={spacings.xs} alignItems="center">
<TextWrapper>
<Text typographyStyle="body" textWrap="nowrap">
{name}
</Text>
</TextWrapper>
{badge && (
<BadgeWrapper>
<Badge>{badge}</Badge>
</BadgeWrapper>
)}
</Row>
<Text typographyStyle="hint" variant="tertiary">
{symbol}
</Text>
</Column>
<div>
<Badge>{badge}</Badge>
</div>

{handleFavoriteClick && (
<IconWrapper>
{isFavorite ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Column, Paragraph, Text } from '@trezor/components';
import { FormattedMessage } from 'react-intl';
import { SelectAssetNetworkProps, SelectAssetSearchCategoryType } from './SelectAssetModal';
import { spacings } from '@trezor/theme';

interface AssetItemNotFoundProps {
searchCategory: SelectAssetSearchCategoryType;
networkCategories: SelectAssetNetworkProps[];
listHeight: string;
listMinHeight: number;
}

export const AssetItemNotFound = ({
searchCategory,
networkCategories,
listHeight,
listMinHeight,
}: AssetItemNotFoundProps) => {
// TODO: resolve messages sharing https://github.com/trezor/trezor-suite/issues/5325
const translations = searchCategory
? {
heading: {
id: 'TR_TOKEN_NOT_FOUND_ON_NETWORK',
defaultMessage: 'Token not found on the {networkName} network',
values: {
networkName: networkCategories.find(
category => category.coingeckoId === searchCategory.coingeckoId,
)?.name,
},
},
paragraph: {
id: 'TR_TOKEN_TRY_DIFFERENT_SEARCH_OR_SWITCH',
defaultMessage: 'Please try a different search or switch to another network.',
},
}
: {
heading: {
id: 'TR_TOKEN_NOT_FOUND',
defaultMessage: 'Token not found',
},
paragraph: {
id: 'TR_TOKEN_TRY_DIFFERENT_SEARCH',
defaultMessage: 'Please try a different search.',
},
};

return (
<Column
alignItems="center"
justifyContent="center"
height={listHeight}
minHeight={listMinHeight}
>
<Text typographyStyle="body">
<FormattedMessage {...translations.heading} />
</Text>
<Paragraph
align="center"
maxWidth={280}
margin={{
top: spacings.xxxs,
left: 'auto',
right: 'auto',
}}
>
<Text variant="tertiary" typographyStyle="hint">
<FormattedMessage {...translations.paragraph} />
</Text>
</Paragraph>
</Column>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { UIVariant } from '@trezor/components/src/config/types';
import {
borders,
Elevation,
mapElevationToBackground,
mapElevationToBorder,
spacingsPx,
typography,
} from '@trezor/theme';
import styled, { DefaultTheme } from 'styled-components';

export const tagVariants = ['primary', 'tertiary'] as const;
export type TagVariant = Extract<UIVariant, (typeof tagVariants)[number]>;

interface CheckableTagProps {
$variant: 'primary' | 'tertiary';
$elevation: Elevation;
}

const getCheckableTagStyles = (
$elevation: Elevation,
theme: DefaultTheme,
type: CheckableTagProps['$variant'] | 'hover',
) => {
switch (type) {
case 'primary':
return `
background: ${theme.backgroundPrimarySubtleOnElevation1};
color: ${theme.textPrimaryDefault};
border: 1px solid ${theme.borderSecondary};
`;
case 'hover':
return `
background: ${mapElevationToBackground({ $elevation, theme })};
color: ${theme.textDefault};
border: 1px solid ${mapElevationToBorder({ $elevation, theme })};
`;
default:
return `
background: ${mapElevationToBackground({ $elevation, theme })};
color: ${theme.textSubdued};
border: 1px solid ${mapElevationToBackground({ $elevation, theme })};
`;
}
};

export const CheckableTag = styled.button<CheckableTagProps>`
cursor: pointer;
border: 0;
${typography.hint};
padding: ${spacingsPx.xxs} ${spacingsPx.sm};
border-radius: ${borders.radii.full};
${({ $elevation, theme, $variant }) => getCheckableTagStyles($elevation, theme, $variant)}
&:hover {
${({ $elevation, theme, $variant }) =>
getCheckableTagStyles($elevation, theme, $variant === 'primary' ? 'primary' : 'hover')}
}
`;
Loading

0 comments on commit 01c60f3

Please sign in to comment.