feat: updated rate/routing tooltips (#7412)

* feat: updated routing tooltips

* refactor: gas price formatting

* fit: boolean rendering
This commit is contained in:
cartcrom 2023-10-05 17:25:53 -04:00 committed by GitHub
parent 2d8dac5c15
commit 932c4482d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 557 additions and 543 deletions

@ -14,7 +14,7 @@ import { Z_INDEX } from 'theme/zIndex'
import { RoutingDiagramEntry } from 'utils/getRoutingDiagramEntries' import { RoutingDiagramEntry } from 'utils/getRoutingDiagramEntries'
import { ReactComponent as DotLine } from '../../assets/svg/dot_line.svg' import { ReactComponent as DotLine } from '../../assets/svg/dot_line.svg'
import { MouseoverTooltip } from '../Tooltip' import { MouseoverTooltip, TooltipSize } from '../Tooltip'
const Wrapper = styled(Box)` const Wrapper = styled(Box)`
align-items: center; align-items: center;
@ -142,6 +142,7 @@ function Pool({ currency0, currency1, feeAmount }: { currency0: Currency; curren
return ( return (
<MouseoverTooltip <MouseoverTooltip
text={<Trans>{tokenInfo0?.symbol + '/' + tokenInfo1?.symbol + ' ' + feeAmount / 10000}% pool</Trans>} text={<Trans>{tokenInfo0?.symbol + '/' + tokenInfo1?.symbol + ' ' + feeAmount / 10000}% pool</Trans>}
size={TooltipSize.ExtraSmall}
> >
<PoolBadge> <PoolBadge>
<Box margin="0 4px 0 12px"> <Box margin="0 4px 0 12px">

@ -20,7 +20,8 @@ import { getPriceImpactColor } from 'utils/prices'
import { GasBreakdownTooltip, UniswapXDescription } from './GasBreakdownTooltip' import { GasBreakdownTooltip, UniswapXDescription } from './GasBreakdownTooltip'
import { MaxSlippageTooltip } from './MaxSlippageTooltip' import { MaxSlippageTooltip } from './MaxSlippageTooltip'
import SwapRoute from './SwapRoute' import { RoutingTooltip, SwapRoute } from './SwapRoute'
import TradePrice from './TradePrice'
export enum SwapLineItemType { export enum SwapLineItemType {
EXCHANGE_RATE, EXCHANGE_RATE,
@ -76,15 +77,6 @@ function Loading({ width = 50 }: { width?: number }) {
return <LoadingRow data-testid="loading-row" height={15} width={width} /> return <LoadingRow data-testid="loading-row" height={15} width={width} />
} }
function ExchangeRateRow({ trade }: { trade: InterfaceTrade }) {
const { formatNumber } = useFormatter()
const rate = `1 ${trade.executionPrice.quoteCurrency.symbol} = ${formatNumber({
input: parseFloat(trade.executionPrice.toFixed(9)),
type: NumberType.TokenTx,
})} ${trade.executionPrice.baseCurrency.symbol}`
return <>{rate}</>
}
function ColoredPercentRow({ percent }: { percent: Percent }) { function ColoredPercentRow({ percent }: { percent: Percent }) {
const { formatSlippage } = useFormatter() const { formatSlippage } = useFormatter()
return <ColorWrapper textColor={getPriceImpactColor(percent)}>{formatSlippage(percent)}</ColorWrapper> return <ColorWrapper textColor={getPriceImpactColor(percent)}>{formatSlippage(percent)}</ColorWrapper>
@ -122,8 +114,10 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined {
switch (type) { switch (type) {
case SwapLineItemType.EXCHANGE_RATE: case SwapLineItemType.EXCHANGE_RATE:
return { return {
Label: () => <Trans>Exchange rate</Trans>, Label: () => <Trans>Rate</Trans>,
Value: () => <ExchangeRateRow trade={trade} />, Value: () => <TradePrice price={trade.executionPrice} />,
TooltipBody: !isPreview ? () => <RoutingTooltip trade={trade} /> : undefined,
tooltipSize: isUniswapX ? TooltipSize.Small : TooltipSize.Large,
} }
case SwapLineItemType.NETWORK_COST: case SwapLineItemType.NETWORK_COST:
if (!SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId)) return if (!SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId)) return
@ -185,12 +179,12 @@ function useLineItem(props: SwapLineItemProps): LineItemData | undefined {
loaderWidth: 70, loaderWidth: 70,
} }
case SwapLineItemType.ROUTING_INFO: case SwapLineItemType.ROUTING_INFO:
if (isPreview) return { Label: () => <Trans>Order routing</Trans>, Value: () => <Loading /> } if (isPreview || syncing) return { Label: () => <Trans>Order routing</Trans>, Value: () => <Loading /> }
return { return {
Label: () => <Trans>Order routing</Trans>, Label: () => <Trans>Order routing</Trans>,
TooltipBody: () => { TooltipBody: () => {
if (isUniswapX) return <UniswapXDescription /> if (isUniswapX) return <UniswapXDescription />
return <SwapRoute data-testid="swap-route-info" trade={trade} syncing={syncing} /> return <SwapRoute data-testid="swap-route-info" trade={trade} />
}, },
tooltipSize: isUniswapX ? TooltipSize.Small : TooltipSize.Large, tooltipSize: isUniswapX ? TooltipSize.Small : TooltipSize.Large,
Value: () => <RouterLabel trade={trade} />, Value: () => <RouterLabel trade={trade} />,

@ -1,64 +1,83 @@
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { useWeb3React } from '@web3-react/core'
import Column from 'components/Column' import Column from 'components/Column'
import { LoadingRows } from 'components/Loader/styled'
import RoutingDiagram from 'components/RoutingDiagram/RoutingDiagram' import RoutingDiagram from 'components/RoutingDiagram/RoutingDiagram'
import { RowBetween } from 'components/Row'
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains' import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
import useAutoRouterSupported from 'hooks/useAutoRouterSupported' import useAutoRouterSupported from 'hooks/useAutoRouterSupported'
import { ClassicTrade } from 'state/routing/types' import { ClassicTrade, SubmittableTrade } from 'state/routing/types'
import { isClassicTrade } from 'state/routing/utils'
import { Separator, ThemedText } from 'theme/components' import { Separator, ThemedText } from 'theme/components'
import { NumberType, useFormatter } from 'utils/formatNumbers'
import getRoutingDiagramEntries from 'utils/getRoutingDiagramEntries' import getRoutingDiagramEntries from 'utils/getRoutingDiagramEntries'
import RouterLabel from '../RouterLabel' import RouterLabel from '../RouterLabel'
import { UniswapXDescription } from './GasBreakdownTooltip'
export default function SwapRoute({ trade, syncing }: { trade: ClassicTrade; syncing: boolean }) { // TODO(WEB-2022)
const { chainId } = useWeb3React() // Can `trade.gasUseEstimateUSD` be defined when `chainId` is not in `SUPPORTED_GAS_ESTIMATE_CHAIN_IDS`?
const autoRouterSupported = useAutoRouterSupported() function useGasPrice({ gasUseEstimateUSD, inputAmount }: ClassicTrade) {
const { formatNumber } = useFormatter()
if (!gasUseEstimateUSD || !SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(inputAmount.currency.chainId)) return undefined
const routes = getRoutingDiagramEntries(trade) return gasUseEstimateUSD === 0 ? '<$0.01' : formatNumber({ input: gasUseEstimateUSD, type: NumberType.FiatGasPrice })
}
const gasPrice =
// TODO(WEB-2022)
// Can `trade.gasUseEstimateUSD` be defined when `chainId` is not in `SUPPORTED_GAS_ESTIMATE_CHAIN_IDS`?
trade.gasUseEstimateUSD && chainId && SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId)
? trade.gasUseEstimateUSD === 0
? '<$0.01'
: '$' + trade.gasUseEstimateUSD.toFixed(2)
: undefined
function RouteLabel({ trade }: { trade: SubmittableTrade }) {
return ( return (
<RowBetween>
<ThemedText.BodySmall color="neutral2">Order Routing</ThemedText.BodySmall>
<RouterLabel trade={trade} color="neutral1" />
</RowBetween>
)
}
function PriceImpactRow({ trade }: { trade: ClassicTrade }) {
const { formatPriceImpact } = useFormatter()
return (
<ThemedText.BodySmall color="neutral2">
<RowBetween>
<Trans>Price Impact</Trans>
<div>{formatPriceImpact(trade.priceImpact)}</div>
</RowBetween>
</ThemedText.BodySmall>
)
}
export function RoutingTooltip({ trade }: { trade: SubmittableTrade }) {
return isClassicTrade(trade) ? (
<Column gap="md"> <Column gap="md">
<RouterLabel trade={trade} color="neutral2" /> <PriceImpactRow trade={trade} />
<Separator /> <Separator />
{syncing ? ( <RouteLabel trade={trade} />
<LoadingRows> <SwapRoute trade={trade} />
<div style={{ width: '100%', height: '30px' }} /> </Column>
</LoadingRows>
) : ( ) : (
<RoutingDiagram <Column gap="md">
currencyIn={trade.inputAmount.currency} <RouteLabel trade={trade} />
currencyOut={trade.outputAmount.currency}
routes={routes}
/>
)}
{autoRouterSupported && (
<>
<Separator /> <Separator />
{syncing ? ( <UniswapXDescription />
<LoadingRows>
<div style={{ width: '100%', height: '15px' }} />
</LoadingRows>
) : (
<ThemedText.Caption color="neutral2">
{gasPrice ? <Trans>Best price route costs ~{gasPrice} in gas.</Trans> : null}{' '}
<Trans>
This route optimizes your total output by considering split routes, multiple hops, and the gas cost of
each step.
</Trans>
</ThemedText.Caption>
)}
</>
)}
</Column> </Column>
) )
} }
export function SwapRoute({ trade }: { trade: ClassicTrade }) {
const { inputAmount, outputAmount } = trade
const routes = getRoutingDiagramEntries(trade)
const gasPrice = useGasPrice(trade)
return useAutoRouterSupported() ? (
<Column gap="md">
<RoutingDiagram routes={routes} currencyIn={inputAmount.currency} currencyOut={outputAmount.currency} />
<ThemedText.Caption color="neutral2">
{Boolean(gasPrice) && <Trans>Best price route costs ~{gasPrice} in gas. </Trans>}
{Boolean(gasPrice) && ' '}
<Trans>
This route optimizes your total output by considering split routes, multiple hops, and the gas cost of each
step.
</Trans>
</ThemedText.Caption>
</Column>
) : (
<RoutingDiagram routes={routes} currencyIn={inputAmount.currency} currencyOut={outputAmount.currency} />
)
}

@ -146,28 +146,6 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
fill: #7D7D7D; fill: #7D7D7D;
} }
.c20 {
text-align: right;
overflow-wrap: break-word;
}
.c19 {
cursor: help;
color: #7D7D7D;
}
.c22 {
background: #22222212;
border-radius: 8px;
color: #7D7D7D;
height: 20px;
padding: 0 6px;
}
.c22::after {
content: 'Auto';
}
.c8 { .c8 {
background-color: transparent; background-color: transparent;
border: none; border: none;
@ -200,6 +178,28 @@ exports[`SwapDetailsDropdown.tsx renders a trade 1`] = `
user-select: text; user-select: text;
} }
.c20 {
text-align: right;
overflow-wrap: break-word;
}
.c19 {
cursor: help;
color: #7D7D7D;
}
.c22 {
background: #22222212;
border-radius: 8px;
color: #7D7D7D;
height: 20px;
padding: 0 6px;
}
.c22::after {
content: 'Auto';
}
.c5 { .c5 {
padding: 0; padding: 0;
-webkit-align-items: center; -webkit-align-items: center;

File diff suppressed because it is too large Load Diff

@ -91,22 +91,49 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
gap: 12px; gap: 12px;
} }
.c9 { .c7 {
display: inline-block; display: inline-block;
height: inherit; height: inherit;
} }
.c7 { .c9 {
background-color: transparent;
border: none;
cursor: pointer;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
padding: 0;
grid-template-columns: 1fr auto;
grid-gap: 0.25rem;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
text-align: left;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
.c8 {
text-align: right; text-align: right;
overflow-wrap: break-word; overflow-wrap: break-word;
} }
.c6 { .c6 {
cursor: auto;
color: #7D7D7D;
}
.c8 {
cursor: help; cursor: help;
color: #7D7D7D; color: #7D7D7D;
} }
@ -137,29 +164,45 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
class="c5 c6 css-142zc9n" class="c5 c6 css-142zc9n"
data-testid="swap-li-label" data-testid="swap-li-label"
> >
Exchange rate Rate
</div> </div>
<div <div
class="c5 c7 css-142zc9n" class="c7"
>
<div>
<div
class="c5 c8 css-142zc9n"
>
<button
class="c9"
title="1 DEF = 1.00 ABC "
>
<div
class="c5 css-142zc9n"
> >
1 DEF = 1.00 ABC 1 DEF = 1.00 ABC
</div> </div>
</button>
</div>
</div>
</div>
</div> </div>
<div <div
class="c2 c3 c4" class="c2 c3 c4"
> >
<div <div
class="c5 c8 css-142zc9n" class="c5 c6 css-142zc9n"
data-testid="swap-li-label" data-testid="swap-li-label"
> >
Price impact Price impact
</div> </div>
<div <div
class="c9" class="c7"
> >
<div> <div>
<div <div
class="c5 c7 css-142zc9n" class="c5 c8 css-142zc9n"
> >
<span <span
class="" class=""
@ -174,17 +217,17 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
class="c2 c3 c4" class="c2 c3 c4"
> >
<div <div
class="c5 c8 css-142zc9n" class="c5 c6 css-142zc9n"
data-testid="swap-li-label" data-testid="swap-li-label"
> >
Max. slippage Max. slippage
</div> </div>
<div <div
class="c9" class="c7"
> >
<div> <div>
<div <div
class="c5 c7 css-142zc9n" class="c5 c8 css-142zc9n"
> >
<div <div
class="c2 c10" class="c2 c10"
@ -202,17 +245,17 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
class="c2 c3 c4" class="c2 c3 c4"
> >
<div <div
class="c5 c8 css-142zc9n" class="c5 c6 css-142zc9n"
data-testid="swap-li-label" data-testid="swap-li-label"
> >
Receive at least Receive at least
</div> </div>
<div <div
class="c9" class="c7"
> >
<div> <div>
<div <div
class="c5 c7 css-142zc9n" class="c5 c8 css-142zc9n"
> >
0.00000000000000098 DEF 0.00000000000000098 DEF
</div> </div>
@ -223,17 +266,17 @@ exports[`SwapModalFooter.tsx matches base snapshot, test trade exact input 1`] =
class="c2 c3 c4" class="c2 c3 c4"
> >
<div <div
class="c5 c8 css-142zc9n" class="c5 c6 css-142zc9n"
data-testid="swap-li-label" data-testid="swap-li-label"
> >
Network cost Network cost
</div> </div>
<div <div
class="c9" class="c7"
> >
<div> <div>
<div <div
class="c5 c7 css-142zc9n" class="c5 c8 css-142zc9n"
> >
<div <div
class="c2 c13" class="c2 c13"
@ -447,7 +490,7 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
justify-content: flex-start; justify-content: flex-start;
} }
.c11 { .c12 {
width: 100%; width: 100%;
display: -webkit-box; display: -webkit-box;
display: -webkit-flex; display: -webkit-flex;
@ -476,7 +519,7 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
color: #222222; color: #222222;
} }
.c12 { .c13 {
color: #7D7D7D; color: #7D7D7D;
} }
@ -495,7 +538,7 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
gap: 12px; gap: 12px;
} }
.c10 { .c11 {
-webkit-animation: fAQEyV 1.5s infinite; -webkit-animation: fAQEyV 1.5s infinite;
animation: fAQEyV 1.5s infinite; animation: fAQEyV 1.5s infinite;
-webkit-animation-fill-mode: both; -webkit-animation-fill-mode: both;
@ -508,11 +551,43 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
width: 50px; width: 50px;
} }
.c9 { .c10 {
display: inline-block; display: inline-block;
height: inherit; height: inherit;
} }
.c8 {
background-color: transparent;
border: none;
cursor: pointer;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
padding: 0;
grid-template-columns: 1fr auto;
grid-gap: 0.25rem;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
text-align: left;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
.c7 { .c7 {
text-align: right; text-align: right;
overflow-wrap: break-word; overflow-wrap: break-word;
@ -523,12 +598,12 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
color: #7D7D7D; color: #7D7D7D;
} }
.c8 { .c9 {
cursor: help; cursor: help;
color: #7D7D7D; color: #7D7D7D;
} }
.c13 { .c14 {
background: #22222212; background: #22222212;
border-radius: 8px; border-radius: 8px;
color: #7D7D7D; color: #7D7D7D;
@ -536,7 +611,7 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
padding: 0 6px; padding: 0 6px;
} }
.c13::after { .c14::after {
content: 'Auto'; content: 'Auto';
} }
@ -558,32 +633,42 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
class="c5 c6 css-142zc9n" class="c5 c6 css-142zc9n"
data-testid="swap-li-label" data-testid="swap-li-label"
> >
Exchange rate Rate
</div> </div>
<div <div
class="c5 c7 css-142zc9n" class="c5 c7 css-142zc9n"
>
<button
class="c8"
title="1 DEF = 1.00 ABC "
>
<div
class="c5 css-142zc9n"
> >
1 DEF = 1.00 ABC 1 DEF = 1.00 ABC
</div> </div>
</button>
</div>
</div> </div>
<div <div
class="c2 c3 c4" class="c2 c3 c4"
> >
<div <div
class="c5 c8 css-142zc9n" class="c5 c9 css-142zc9n"
data-testid="swap-li-label" data-testid="swap-li-label"
> >
Price impact Price impact
</div> </div>
<div <div
class="c9" class="c10"
> >
<div> <div>
<div <div
class="c5 c7 css-142zc9n" class="c5 c7 css-142zc9n"
> >
<div <div
class="c10" class="c11"
data-testid="loading-row" data-testid="loading-row"
height="15" height="15"
width="50" width="50"
@ -596,23 +681,23 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
class="c2 c3 c4" class="c2 c3 c4"
> >
<div <div
class="c5 c8 css-142zc9n" class="c5 c9 css-142zc9n"
data-testid="swap-li-label" data-testid="swap-li-label"
> >
Max. slippage Max. slippage
</div> </div>
<div <div
class="c9" class="c10"
> >
<div> <div>
<div <div
class="c5 c7 css-142zc9n" class="c5 c7 css-142zc9n"
> >
<div <div
class="c2 c11" class="c2 c12"
> >
<div <div
class="c12 c13 css-1lgneq0" class="c13 c14 css-1lgneq0"
/> />
2% 2%
</div> </div>
@ -624,13 +709,13 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
class="c2 c3 c4" class="c2 c3 c4"
> >
<div <div
class="c5 c8 css-142zc9n" class="c5 c9 css-142zc9n"
data-testid="swap-li-label" data-testid="swap-li-label"
> >
Receive at least Receive at least
</div> </div>
<div <div
class="c9" class="c10"
> >
<div> <div>
<div <div
@ -645,20 +730,20 @@ exports[`SwapModalFooter.tsx renders a preview trade while disabling submission
class="c2 c3 c4" class="c2 c3 c4"
> >
<div <div
class="c5 c8 css-142zc9n" class="c5 c9 css-142zc9n"
data-testid="swap-li-label" data-testid="swap-li-label"
> >
Network cost Network cost
</div> </div>
<div <div
class="c9" class="c10"
> >
<div> <div>
<div <div
class="c5 c7 css-142zc9n" class="c5 c7 css-142zc9n"
> >
<div <div
class="c10" class="c11"
data-testid="loading-row" data-testid="loading-row"
height="15" height="15"
width="50" width="50"