Skip to content

Commit

Permalink
feat(suite-native): Introduce TradingBottomSheetSectionList
Browse files Browse the repository at this point in the history
  • Loading branch information
jbazant committed Feb 10, 2025
1 parent 6ef1768 commit b6a7ae1
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 151 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,40 +14,10 @@ export type AssetListItemProps = {
onPress: () => void;
onFavouritePress: () => void;
isFavourite?: boolean;
isFirst?: boolean;
isLast?: boolean;
};

type AssetItemStyleProps = {
isFirst: boolean;
isLast: boolean;
};

export const ASSET_ITEM_HEIGHT = 68;

const itemStyle = prepareNativeStyle<AssetItemStyleProps>(
({ colors, spacings, borders }, { isFirst, isLast }) => ({
backgroundColor: colors.backgroundSurfaceElevation1,
paddingHorizontal: spacings.sp12,
extend: [
{
condition: isFirst,
style: {
borderTopLeftRadius: borders.radii.r20,
borderTopRightRadius: borders.radii.r20,
},
},
{
condition: isLast,
style: {
borderBottomLeftRadius: borders.radii.r20,
borderBottomRightRadius: borders.radii.r20,
},
},
],
}),
);

const vStackStyle = prepareNativeStyle(({ spacings }) => ({
height: ASSET_ITEM_HEIGHT,
justifyContent: 'center',
Expand All @@ -63,21 +33,14 @@ export const TradeableAssetListItem = ({
onPress,
onFavouritePress,
isFavourite = false,
isFirst = false,
isLast = false,
}: AssetListItemProps) => {
const { applyStyle } = useNativeStyles();
const { DisplaySymbolFormatter, FiatAmountFormatter, NetworkNameFormatter } = useFormatters();

const assetName = name ?? <NetworkNameFormatter value={symbol} />;

return (
<Pressable
onPress={onPress}
accessibilityRole="radio"
accessibilityLabel={name ?? symbol}
style={applyStyle(itemStyle, { isFirst, isLast })}
>
<Pressable onPress={onPress} accessibilityRole="radio" accessibilityLabel={name ?? symbol}>
<HStack alignItems="center" spacing="sp12">
<Box justifyContent="center">
<RoundedIcon symbol={symbol} contractAddress={contractAddress} />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { ReactNode, useMemo } from 'react';
import { useMemo } from 'react';

import { UnreachableCaseError } from '@suite-common/suite-utils';
import { TokenAddress } from '@suite-common/wallet-types';
import { BottomSheetFlashList, Box, Text } from '@suite-native/atoms';
import { Translation } from '@suite-native/intl';

import {
ItemRenderConfig,
SectionListData,
TradingBottomSheetSectionList,
} from '../TradingBottomSheetSectionList';
import { TradeAssetsListEmptyComponent } from './TradeAssetsListEmptyComponent';
import { ASSET_ITEM_HEIGHT, TradeableAssetListItem } from './TradeableAssetListItem';
import { TradeableAssetsSheetHeader } from './TradeableAssetsSheetHeader';
Expand All @@ -16,13 +19,9 @@ export type TradeableAssetsSheetProps = {
onAssetSelect: (symbol: TradeableAsset) => void;
};

type ListInnerItemShape =
// [type, text, key]
| ['sectionHeader', ReactNode, string]
// [type, data, isFavourite]
| ['asset', TradeableAsset, { isFavourite?: boolean; isFirst?: boolean; isLast?: boolean }];

const SECTION_HEADER_HEIGHT = 48 as const;
type ListItemExtraData = {
isFavourite: boolean;
};

const mockFavourites: TradeableAsset[] = [
{ symbol: 'btc' },
Expand Down Expand Up @@ -50,104 +49,32 @@ const mockAssets: TradeableAsset[] = [
const getMockFiatRate = () => Math.random() * 1000;
const getMockPriceChange = () => Math.random() * 3 - 1;

const getEstimatedListHeight = (itemsCount: number) =>
itemsCount * ASSET_ITEM_HEIGHT + 2 * SECTION_HEADER_HEIGHT;

const transformToInnerFlatListData = (
favourites: TradeableAsset[],
assetsData: TradeableAsset[],
): ListInnerItemShape[] => [
[
'sectionHeader',
<Translation key="favourites" id="moduleTrading.tradeableAssetsSheet.favouritesTitle" />,
'section_favourites',
],
...favourites.map(
(asset, index) =>
[
'asset',
asset,
{
isFavourite: true,
isFirst: index === 0,
isLast: index === favourites.length - 1,
},
] as ListInnerItemShape,
),
[
'sectionHeader',
<Translation key="all" id="moduleTrading.tradeableAssetsSheet.allTitle" />,
'section_all',
],
...assetsData.map(
(asset, index) =>
[
'asset',
asset,
{
isFavourite: false,
isFirst: index === 0,
isLast: index === assetsData.length - 1,
},
] as ListInnerItemShape,
),
];

const keyExtractor = (item: ListInnerItemShape) => {
switch (item[0]) {
case 'sectionHeader':
return item[2];

case 'asset': {
const [_, { symbol, contractAddress }, { isFavourite }] = item;

return `asset_${symbol}_${contractAddress ?? ''}_${isFavourite ? 'favourite' : ''}`;
}

default:
throw new UnreachableCaseError(item[0]);
}
};

const renderItem = (data: ListInnerItemShape, onAssetSelect: (asset: TradeableAsset) => void) => {
switch (data[0]) {
case 'sectionHeader': {
const text = data[1];

return (
<Box paddingVertical="sp12">
<Text variant="hint" color="textSubdued">
{text}
</Text>
</Box>
);
}

case 'asset': {
const [_, asset, { isFavourite, isFirst, isLast }] = data;
const toggleFavourite = () => {
// TODO: Implement
// eslint-disable-next-line no-console
console.log('Not implemented!');
};

return (
<TradeableAssetListItem
asset={asset}
onPress={() => onAssetSelect(asset)}
onFavouritePress={toggleFavourite}
priceChange={getMockPriceChange()}
fiatRate={getMockFiatRate()}
isFavourite={isFavourite}
isFirst={isFirst}
isLast={isLast}
/>
);
}
const keyExtractor = (
{ symbol, contractAddress }: TradeableAsset,
{ isFavourite }: ListItemExtraData,
) => `asset_${symbol}_${contractAddress ?? ''}}_${isFavourite ? 'favourite' : 'all'}`;

const renderItem = (
asset: TradeableAsset,
{ sectionData }: ItemRenderConfig<ListItemExtraData>,
onAssetSelect: (asset: TradeableAsset) => void,
) => {
const toggleFavourite = () => {
// TODO: Implement
// eslint-disable-next-line no-console
console.log('Not implemented!');
};

default:
throw new UnreachableCaseError(data[0]);
}
return (
<TradeableAssetListItem
asset={asset}
onPress={() => onAssetSelect(asset)}
onFavouritePress={toggleFavourite}
priceChange={getMockPriceChange()}
fiatRate={getMockFiatRate()}
isFavourite={sectionData.isFavourite}
/>
);
};

export const TradeableAssetsSheet = ({
Expand All @@ -162,24 +89,37 @@ export const TradeableAssetsSheet = ({

const favourites = mockFavourites;
const assetsData = mockAssets;
const estimatedListHeight = getEstimatedListHeight(favourites.length + assetsData.length);

const data: ListInnerItemShape[] = useMemo(
() => transformToInnerFlatListData(favourites, assetsData),
const data = useMemo(
() =>
[
{
key: 'section_favourites',
label: <Translation id="moduleTrading.tradeableAssetsSheet.favouritesTitle" />,
data: favourites,
sectionData: { isFavourite: true },
},
{
key: 'section_all',
label: <Translation id="moduleTrading.tradeableAssetsSheet.allTitle" />,
data: assetsData,
sectionData: { isFavourite: false },
},
] as SectionListData<TradeableAsset, ListItemExtraData>,
[favourites, assetsData],
);

return (
<BottomSheetFlashList<ListInnerItemShape>
<TradingBottomSheetSectionList<TradeableAsset, ListItemExtraData>
isVisible={isVisible}
onClose={onClose}
ListEmptyComponent={<TradeAssetsListEmptyComponent />}
handleComponent={() => <TradeableAssetsSheetHeader onClose={onClose} />}
data={data}
keyExtractor={keyExtractor}
estimatedListHeight={estimatedListHeight}
estimatedItemSize={ASSET_ITEM_HEIGHT}
renderItem={({ item }) => renderItem(item, onAssetSelectCallback)}
renderItem={(item, config) => renderItem(item, config, onAssetSelectCallback)}
noSingletonSectionHeader
/>
);
};
Loading

0 comments on commit b6a7ae1

Please sign in to comment.