Skip to content

Commit d3dcfe1

Browse files
committed
add missing ui components for spot
1 parent 22b43ea commit d3dcfe1

File tree

13 files changed

+1063
-15
lines changed

13 files changed

+1063
-15
lines changed

src/components/Output.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import styled, { css } from 'styled-components';
88
import { SupportedLocales } from '@/constants/localization';
99
import {
1010
LEVERAGE_DECIMALS,
11+
NumberSign,
1112
PERCENT_DECIMALS,
1213
SMALL_PERCENT_DECIMALS,
1314
SMALL_USD_DECIMALS,
@@ -326,6 +327,7 @@ type StyleProps = {
326327
className?: string;
327328
withBaseFont?: boolean;
328329
withSignColor?: boolean;
330+
withPolarityColor?: boolean;
329331
};
330332

331333
export type OutputProps = ElementProps & StyleProps;
@@ -349,6 +351,7 @@ export const Output = ({
349351
withParentheses,
350352
showSign = ShowSign.Negative,
351353
withSignColor = false,
354+
withPolarityColor,
352355

353356
dateOptions,
354357
relativeTimeOptions = {
@@ -499,6 +502,15 @@ export const Output = ({
499502
className={className}
500503
withParentheses={withParentheses}
501504
withBaseFont={withBaseFont}
505+
$polarity={
506+
withPolarityColor
507+
? isNegative
508+
? NumberSign.Negative
509+
: isPositive
510+
? NumberSign.Positive
511+
: undefined
512+
: undefined
513+
}
502514
>
503515
{slotLeft}
504516
{sign && (
@@ -553,10 +565,22 @@ const $Text = styled.output<{ withParentheses?: boolean }>`
553565
--output-afterString: ')';
554566
`}
555567
`;
556-
const $Number = styled($Text)<{ withBaseFont?: boolean }>`
568+
const $Number = styled($Text)<{ withBaseFont?: boolean; $polarity?: NumberSign }>`
557569
${({ withBaseFont }) =>
558570
!withBaseFont &&
559571
css`
560572
font-feature-settings: var(--fontFeature-monoNumbers);
561573
`}
574+
575+
${({ $polarity }) =>
576+
$polarity === NumberSign.Positive &&
577+
css`
578+
color: var(--color-positive) !important;
579+
`}
580+
581+
${({ $polarity }) =>
582+
$polarity === NumberSign.Negative &&
583+
css`
584+
color: var(--color-negative) !important;
585+
`}
562586
`;

src/pages/spot/Spot.tsx

Lines changed: 209 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { useMemo, useState } from 'react';
2+
13
import { useParams } from 'react-router-dom';
24
import styled, { css } from 'styled-components';
35

@@ -6,33 +8,224 @@ import { TradeLayouts } from '@/constants/layout';
68
import breakpoints from '@/styles/breakpoints';
79
import { layoutMixins } from '@/styles/layoutMixins';
810

11+
import { IconName } from '@/components/Icon';
12+
import { Output, OutputType } from '@/components/Output';
913
import { SpotTvChart } from '@/views/charts/TradingView/SpotTvChart';
1014

1115
import { useAppSelector } from '@/state/appTypes';
1216
import { getSelectedTradeLayout } from '@/state/layoutSelectors';
1317

18+
import { SpotHeader } from './SpotHeader';
19+
import { type SpotHoldingRow } from './SpotHoldingsTable';
20+
import { SpotHorizontalPanel } from './SpotHorizontalPanel';
21+
import { SpotTokenInfo } from './SpotTokenInfo';
1422
import { SpotTradeForm } from './SpotTradeForm';
23+
import { SpotMarketToken } from './types';
24+
25+
function generateDummyHoldings(count: number): SpotHoldingRow[] {
26+
const rows: SpotHoldingRow[] = [];
27+
for (let i = 0; i < count; i += 1) {
28+
const tokenSymbol = `${i} ASSET`;
29+
const tokenName = `${[...tokenSymbol].reverse().join('')}`;
30+
const holdingsAmount = Math.round(1000 + Math.random() * 2_000_000);
31+
const avgPrice = 0.0001 + Math.random() * 5;
32+
const holdingsUsd = Math.round(holdingsAmount * avgPrice);
33+
const boughtAmount = holdingsAmount;
34+
const boughtUsd = holdingsUsd;
35+
const soldAmount = Math.round(Math.random() * 10_000);
36+
const soldUsd = Math.round(soldAmount * avgPrice);
37+
const pnlUsd = Math.round((Math.random() - 0.5) * 10_000);
38+
39+
rows.push({
40+
tokenAddress: tokenSymbol,
41+
tokenSymbol,
42+
tokenName,
43+
holdingsAmount,
44+
holdingsUsd,
45+
boughtAmount,
46+
boughtUsd,
47+
soldAmount,
48+
soldUsd,
49+
pnlUsd,
50+
});
51+
}
52+
return rows;
53+
}
54+
55+
const DUMMY_TOKENS: SpotMarketToken[] = [
56+
{
57+
tokenAddress: 'So11111111111111111111111111111111111111112',
58+
name: 'Solana',
59+
symbol: 'SOL',
60+
logoUrl: 'https://cryptologos.cc/logos/solana-sol-logo.png',
61+
volume24hUsd: 224_400_000,
62+
priceUsd: 151.23,
63+
marketCapUsd: 68_000_000_000,
64+
change24hPercent: 2.35,
65+
markPriceUsd: 151.2,
66+
fdvUsd: 70_000_000_000,
67+
liquidityUsd: 1_000_000_000,
68+
circulatingSupply: 450_000_000,
69+
totalSupply: 560_000_000,
70+
percentChange24h: 2.35,
71+
buys24hUsd: 120_000_000,
72+
sells24hUsd: -104_400_000,
73+
},
74+
{
75+
tokenAddress: 'FARTxLVqm9ezNvQ8V4E8w9FVBYRHGpJzp2cXm7pump',
76+
name: 'Fartcoin',
77+
symbol: 'FARTCOIN',
78+
logoUrl: 'https://cryptologos.cc/logos/fartcoin-fart-logo.png',
79+
volume24hUsd: 124_300_000,
80+
priceUsd: 1.53,
81+
marketCapUsd: 1_530_000_000,
82+
change24hPercent: 12.35,
83+
markPriceUsd: 1.54,
84+
fdvUsd: 1_600_000_000,
85+
liquidityUsd: 30_000_000,
86+
circulatingSupply: 1_000_000_000,
87+
totalSupply: 1_600_000_000,
88+
percentChange24h: 12.35,
89+
buys24hUsd: 70_000_000,
90+
sells24hUsd: -54_300_000,
91+
},
92+
{
93+
tokenAddress: 'WIFgzYxgkMtFGzGYAzm72rnWC9eFsEhSUvBdtpump',
94+
name: 'dogwifhat',
95+
symbol: 'WIF',
96+
logoUrl: 'https://cryptologos.cc/logos/dogwifhat-wif-logo.png',
97+
volume24hUsd: 111_200_000,
98+
priceUsd: 0.652,
99+
marketCapUsd: 290_000_000,
100+
change24hPercent: -3.35,
101+
markPriceUsd: 0.651,
102+
fdvUsd: 300_000_000,
103+
liquidityUsd: 20_000_000,
104+
circulatingSupply: 445_000_000,
105+
totalSupply: 460_000_000,
106+
percentChange24h: -3.35,
107+
buys24hUsd: 50_000_000,
108+
sells24hUsd: -61_200_000,
109+
},
110+
{
111+
tokenAddress: 'BONKxYxgkMtFGzGYAzm72rnWC9eFsEhSUvBdtbonk',
112+
name: 'Bonk',
113+
symbol: 'BONK',
114+
logoUrl: 'https://cryptologos.cc/logos/bonk-bonk-logo.png',
115+
volume24hUsd: 80_000_000,
116+
priceUsd: 0.000025,
117+
marketCapUsd: 1_500_000_000,
118+
change24hPercent: 8.5,
119+
markPriceUsd: 0.0000251,
120+
fdvUsd: 2_000_000_000,
121+
liquidityUsd: 10_000_000,
122+
circulatingSupply: 60_000_000_000_000,
123+
totalSupply: 100_000_000_000_000,
124+
percentChange24h: 8.5,
125+
buys24hUsd: 45_000_000,
126+
sells24hUsd: -35_000_000,
127+
},
128+
];
15129

16130
const SpotPage = () => {
17131
const { symbol } = useParams<{ symbol: string }>();
18132
const tradeLayout = useAppSelector(getSelectedTradeLayout);
19133

134+
const [isHorizontalOpen, setIsHorizontalOpen] = useState(true);
135+
136+
const dummyHoldings: SpotHoldingRow[] = useMemo(() => generateDummyHoldings(50), []);
137+
138+
const handleTokenSelect = () => {
139+
// Navigate
140+
};
141+
142+
const handlePositionSelect = () => {
143+
// Navigate
144+
};
145+
146+
const handleTokenSearchChange = () => {
147+
// Query search API
148+
};
149+
150+
const handlePositionSell = () => {
151+
// Sell dialog or navigate
152+
};
153+
20154
return (
21-
<$SpotLayout tradeLayout={tradeLayout}>
155+
<$SpotLayout tradeLayout={tradeLayout} isHorizontalOpen={isHorizontalOpen}>
22156
<header tw="[grid-area:Top]">
23-
<div tw="p-1">Spot Market Selector (Coming Soon)</div>
157+
<SpotHeader
158+
currentToken={DUMMY_TOKENS[1]!}
159+
searchResults={DUMMY_TOKENS}
160+
onTokenSelect={handleTokenSelect}
161+
onSearchTextChange={handleTokenSearchChange}
162+
/>
24163
</header>
25164

26-
<$GridSection gridArea="Side" tw="p-1">
165+
<$GridSection gridArea="Side">
27166
<SpotTradeForm />
167+
<SpotTokenInfo
168+
links={[
169+
{ icon: IconName.Earth, url: '' },
170+
{ icon: IconName.File, url: '' },
171+
{ icon: IconName.CoinMarketCap, url: '' },
172+
{ icon: IconName.SocialX, url: '' },
173+
]}
174+
contractAddress="WIFgzYxgkMtFGzGYAzm72rnWC9eFsEhSUvBdtpump"
175+
createdAt={Date.now() - 21 * 24 * 60 * 60 * 1000}
176+
items={[
177+
{
178+
key: 'holders',
179+
iconName: IconName.Positions,
180+
label: 'Holders',
181+
value: <Output type={OutputType.CompactNumber} value={123123} />,
182+
},
183+
{
184+
key: 'top10',
185+
iconName: IconName.Position,
186+
label: 'Top 10',
187+
value: <Output type={OutputType.Percent} value={0.0424} />,
188+
},
189+
{
190+
key: 'devHolding',
191+
iconName: IconName.Gear,
192+
label: 'Dev Holding',
193+
value: <Output type={OutputType.Percent} value={0.0123} />,
194+
},
195+
{
196+
key: 'snipers',
197+
iconName: IconName.Viewfinder,
198+
label: 'Snipers',
199+
value: <Output type={OutputType.Percent} value={0.0124} />,
200+
},
201+
{
202+
key: 'bundlers',
203+
iconName: IconName.Shield,
204+
label: 'Bundlers',
205+
value: <Output type={OutputType.Percent} value={0.0424} />,
206+
},
207+
{
208+
key: 'insiders',
209+
iconName: IconName.Warning,
210+
label: 'Insiders',
211+
value: <Output type={OutputType.Percent} value={0.01} />,
212+
},
213+
]}
214+
/>
28215
</$GridSection>
29216

30217
<$GridSection gridArea="Inner">
31218
<SpotTvChart symbol={symbol!} />
32219
</$GridSection>
33220

34221
<$GridSection gridArea="Horizontal">
35-
<div tw="p-1">Spot Horizontal Panel (Coming Soon)</div>
222+
<SpotHorizontalPanel
223+
data={dummyHoldings}
224+
isOpen={isHorizontalOpen}
225+
setIsOpen={setIsHorizontalOpen}
226+
onRowAction={handlePositionSelect}
227+
onSellAction={handlePositionSell}
228+
/>
36229
</$GridSection>
37230
</$SpotLayout>
38231
);
@@ -42,6 +235,7 @@ export default SpotPage;
42235

43236
const $SpotLayout = styled.article<{
44237
tradeLayout: TradeLayouts;
238+
isHorizontalOpen: boolean;
45239
}>`
46240
/* prettier-ignore */
47241
--layout-default:
@@ -57,10 +251,8 @@ const $SpotLayout = styled.article<{
57251
'Horizontal Side' 300px
58252
/ 1fr 400px;
59253
60-
// Props/defaults
61254
--layout: var(--layout-default);
62255
63-
// Variants
64256
@media ${breakpoints.desktopMedium} {
65257
--layout: var(--layout-default-desktopMedium);
66258
}
@@ -76,7 +268,16 @@ const $SpotLayout = styled.article<{
76268
`,
77269
})[tradeLayout]}
78270
79-
// Rules
271+
${({ isHorizontalOpen }) =>
272+
!isHorizontalOpen &&
273+
css`
274+
--layout-default: 'Top Top' auto 'Inner Side' minmax(0, 1fr) 'Horizontal Side'
275+
var(--tabs-height) / 1fr 400px;
276+
277+
--layout-default-desktopMedium: 'Top Side' auto 'Inner Side' minmax(0, 1fr) 'Horizontal Side'
278+
var(--tabs-height) / 1fr 400px;
279+
`}
280+
80281
width: 0;
81282
min-width: 100%;
82283
height: 0;
@@ -103,4 +304,5 @@ const $SpotLayout = styled.article<{
103304

104305
const $GridSection = styled.section<{ gridArea: string }>`
105306
grid-area: ${({ gridArea }) => gridArea};
307+
${layoutMixins.withOuterAndInnerBorders}
106308
`;

src/pages/spot/SpotHeader.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import styled from 'styled-components';
2+
3+
import { layoutMixins } from '@/styles/layoutMixins';
4+
5+
import { VerticalSeparator } from '@/components/Separator';
6+
7+
import { SpotMarketStatsRow } from './SpotMarketStatsRow';
8+
import { SpotMarketsDropdown } from './SpotMarketsDropdown';
9+
import { SpotMarketToken } from './types';
10+
11+
type SpotHeaderProps = {
12+
currentToken: SpotMarketToken;
13+
searchResults: SpotMarketToken[];
14+
onTokenSelect: (token: SpotMarketToken) => void;
15+
onSearchTextChange?: (value: string) => void;
16+
className?: string;
17+
};
18+
19+
export const SpotHeader = ({
20+
currentToken,
21+
searchResults,
22+
onTokenSelect,
23+
onSearchTextChange,
24+
className,
25+
}: SpotHeaderProps) => {
26+
return (
27+
<$Container className={className}>
28+
<SpotMarketsDropdown
29+
current={currentToken}
30+
searchResults={searchResults}
31+
onSelect={onTokenSelect}
32+
onSearchTextChange={onSearchTextChange}
33+
/>
34+
<VerticalSeparator fullHeight />
35+
<SpotMarketStatsRow stats={currentToken} />
36+
</$Container>
37+
);
38+
};
39+
40+
const $Container = styled.div`
41+
${layoutMixins.container}
42+
${layoutMixins.scrollAreaFadeEnd}
43+
44+
display: grid;
45+
grid-template: var(--market-info-row-height) / auto;
46+
grid-auto-flow: column;
47+
justify-content: start;
48+
align-items: stretch;
49+
`;

0 commit comments

Comments
 (0)