Commit a33187c3 authored by Ian Lapham's avatar Ian Lapham Committed by GitHub

feat: routing tooltip for widget (#3259)

* start file updates for routing tooltip

* start tooltip UI

* fix styling

* remove use of px and add header

* UI updates;

* update styles

* update file structure

* update routing components and type

* small code fixes

* fix broken sizing bug

* nit fixes
parent 248bc07c
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Protocol } from '@uniswap/router-sdk' import { Currency } from '@uniswap/sdk-core'
import { Currency, Percent } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk' import { FeeAmount } from '@uniswap/v3-sdk'
import Badge from 'components/Badge' import Badge from 'components/Badge'
import CurrencyLogo from 'components/CurrencyLogo' import CurrencyLogo from 'components/CurrencyLogo'
import DoubleCurrencyLogo from 'components/DoubleLogo' import DoubleCurrencyLogo from 'components/DoubleLogo'
import Row, { AutoRow } from 'components/Row' import Row, { AutoRow } from 'components/Row'
import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList' import { useTokenInfoFromActiveList } from 'hooks/useTokenInfoFromActiveList'
import { RoutingDiagramEntry } from 'lib/components/Swap/RoutingDiagram/utils'
import { Box } from 'rebass' import { Box } from 'rebass'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import { ThemedText, Z_INDEX } from 'theme' import { ThemedText, Z_INDEX } from 'theme'
...@@ -14,12 +14,6 @@ import { ThemedText, Z_INDEX } from 'theme' ...@@ -14,12 +14,6 @@ import { ThemedText, Z_INDEX } from 'theme'
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 } from '../Tooltip'
export interface RoutingDiagramEntry {
percent: Percent
path: [Currency, Currency, FeeAmount][]
protocol: Protocol
}
const Wrapper = styled(Box)` const Wrapper = styled(Box)`
align-items: center; align-items: center;
width: 100%; width: 100%;
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Trade } from '@uniswap/router-sdk' import { Currency, TradeType } from '@uniswap/sdk-core'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import AnimatedDropdown from 'components/AnimatedDropdown' import AnimatedDropdown from 'components/AnimatedDropdown'
import { AutoColumn } from 'components/Column' import { AutoColumn } from 'components/Column'
import { LoadingRows } from 'components/Loader/styled' import { LoadingRows } from 'components/Loader/styled'
import RoutingDiagram, { RoutingDiagramEntry } from 'components/RoutingDiagram/RoutingDiagram' import RoutingDiagram from 'components/RoutingDiagram/RoutingDiagram'
import { AutoRow, RowBetween } from 'components/Row' import { AutoRow, RowBetween } from 'components/Row'
import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains' import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'constants/chains'
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useAutoRouterSupported from 'hooks/useAutoRouterSupported' import useAutoRouterSupported from 'hooks/useAutoRouterSupported'
import { getTokenPath } from 'lib/components/Swap/RoutingDiagram/utils'
import { memo, useState } from 'react' import { memo, useState } from 'react'
import { Plus } from 'react-feather' import { Plus } from 'react-feather'
import { InterfaceTrade } from 'state/routing/types' import { InterfaceTrade } from 'state/routing/types'
...@@ -39,8 +38,6 @@ const OpenCloseIcon = styled(Plus)<{ open?: boolean }>` ...@@ -39,8 +38,6 @@ const OpenCloseIcon = styled(Plus)<{ open?: boolean }>`
} }
` `
const V2_DEFAULT_FEE_TIER = 3000
interface SwapRouteProps extends React.HTMLAttributes<HTMLDivElement> { interface SwapRouteProps extends React.HTMLAttributes<HTMLDivElement> {
trade: InterfaceTrade<Currency, Currency, TradeType> trade: InterfaceTrade<Currency, Currency, TradeType>
syncing: boolean syncing: boolean
...@@ -109,35 +106,3 @@ export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...r ...@@ -109,35 +106,3 @@ export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...r
</Wrapper> </Wrapper>
) )
}) })
function getTokenPath(trade: Trade<Currency, Currency, TradeType>): RoutingDiagramEntry[] {
return trade.swaps.map(({ route: { path: tokenPath, pools, protocol }, inputAmount, outputAmount }) => {
const portion =
trade.tradeType === TradeType.EXACT_INPUT
? inputAmount.divide(trade.inputAmount)
: outputAmount.divide(trade.outputAmount)
const percent = new Percent(portion.numerator, portion.denominator)
const path: RoutingDiagramEntry['path'] = []
for (let i = 0; i < pools.length; i++) {
const nextPool = pools[i]
const tokenIn = tokenPath[i]
const tokenOut = tokenPath[i + 1]
const entry: RoutingDiagramEntry['path'][0] = [
tokenIn,
tokenOut,
nextPool instanceof Pair ? V2_DEFAULT_FEE_TIER : nextPool.fee,
]
path.push(entry)
}
return {
percent,
path,
protocol,
}
})
}
<svg width="23" height="20" viewBox="0 0 23 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="gradient" x1="0" y1="0" x2="1" y2="0" gradientTransform="rotate(95)">
<stop id="stop1" offset="0" stop-color="#2274E2"/>
<stop id="stop1" offset="0.5" stop-color="#2274E2"/>
<stop id="stop2" offset="1" stop-color="#3FB672" />
</linearGradient>
</defs>
<path d="M16 16C10 16 9 10 5 10M16 16C16 17.6569 17.3431 19 19 19C20.6569 19 22 17.6569 22 16C22 14.3431 20.6569 13 19 13C17.3431 13 16 14.3431 16 16ZM5 10C9 10 10 4 16 4M5 10H1.5M16 4C16 5.65685 17.3431 7 19 7C20.6569 7 22 5.65685 22 4C22 2.34315 20.6569 1 19 1C17.3431 1 16 2.34315 16 4Z" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke="url(#gradient)" />
</svg>
\ No newline at end of file
import styled, { Color } from 'lib/theme'
import Row from './Row'
const Badge = styled(Row)<{ borderRadius?: number; padding?: string; color?: Color }>`
background-color: ${({ theme, color = 'outline' }) => theme[color]};
border-radius: ${({ borderRadius }) => `${borderRadius ?? 0.5}em`};
padding: ${({ padding }) => padding ?? '0.25em 0.375em'};
`
export default Badge
import { Plural, Trans } from '@lingui/macro'
import { Currency, TradeType } from '@uniswap/sdk-core'
import { FeeAmount } from '@uniswap/v3-sdk'
import { ReactComponent as DotLineImage } from 'assets/svg/dot_line.svg'
import Badge from 'lib/components/Badge'
import Column from 'lib/components/Column'
import Row from 'lib/components/Row'
import Rule from 'lib/components/Rule'
import TokenImg from 'lib/components/TokenImg'
import { AutoRouter } from 'lib/icons'
import styled, { Layer, ThemedText } from 'lib/theme'
import { useMemo } from 'react'
import { InterfaceTrade } from 'state/routing/types'
import { getTokenPath, RoutingDiagramEntry } from './utils'
const Wrapper = styled(Column)`
padding: 0.25em;
`
const RouteRow = styled(Row)`
grid-template-columns: 1em 1.15em 1fr 1em;
min-width: 430px;
`
const RouteDetailsContainer = styled(Row)`
padding: 0.1em 0.5em;
position: relative;
`
const DotsContainer = styled.div`
align-items: center;
display: flex;
opacity: 0.5;
position: absolute;
width: calc(100% - 1em);
z-index: ${Layer.UNDERLAYER};
`
const DotsContainerShort = styled(DotsContainer)`
overflow: hidden;
position: relative;
width: 71px;
`
const Dots = styled(DotLineImage)`
path {
stroke: ${({ theme }) => theme.secondary};
}
`
const DetailsRow = styled(Row)`
display: grid;
grid-template-columns: 4.8125em 1fr;
width: 100%;
`
const StyledAutoRouterLabel = styled(ThemedText.ButtonSmall)`
@supports (-webkit-background-clip: text) and (-webkit-text-fill-color: transparent) {
background-image: linear-gradient(90deg, #2172e5 0%, #54e521 163.16%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
`
function Pool({
originCurrency,
targetCurrency,
feeAmount,
}: {
originCurrency: Currency
targetCurrency: Currency
feeAmount: FeeAmount
}) {
return (
<Badge padding="0 4px" color="dialog">
<Badge gap={0.375}>
<Row>
<TokenImg token={originCurrency} />
<TokenImg token={targetCurrency} />
</Row>
<ThemedText.Subhead2>{feeAmount / 10_000}%</ThemedText.Subhead2>
</Badge>
</Badge>
)
}
export default function RoutingDiagram({ trade }: { trade: InterfaceTrade<Currency, Currency, TradeType> }) {
const routes: RoutingDiagramEntry[] = useMemo(() => getTokenPath(trade), [trade])
return (
<Wrapper gap={0.75}>
<Row justify="space-between">
<Row gap={0.25}>
<AutoRouter />
<StyledAutoRouterLabel color="primary" lineHeight={'16px'}>
<Trans>Auto Router</Trans>
</StyledAutoRouterLabel>
</Row>
<ThemedText.ButtonSmall>
<Plural value={routes.length} _1="Best route via 1 hop" other="Best route via # hops" />
</ThemedText.ButtonSmall>
</Row>
<Rule />
{routes.map((route, index) => (
<RouteRow key={index} align="center">
<TokenImg token={trade.inputAmount.currency} />
<DotsContainerShort>
<Dots />
</DotsContainerShort>
<RouteDetailsContainer justify="flex-start" flex>
<DotsContainer>
<Dots />
</DotsContainer>
<DetailsRow>
<Badge padding="0 4px" color="dialog">
<Badge gap={0.375}>
<ThemedText.ButtonSmall color="secondary">{route.percent.toSignificant(2)}%</ThemedText.ButtonSmall>
<Badge padding="0.125em" borderRadius={0.25} color="module">
<ThemedText.Badge color="secondary" fontSize={'0.5rem'}>
{route.protocol.toUpperCase()}
</ThemedText.Badge>
</Badge>
</Badge>
</Badge>
<Row justify="space-evenly" flex style={{ width: '100%' }}>
{route.path.map(([originCurrency, targetCurrency, feeAmount], index) => (
<Pool
key={index}
originCurrency={originCurrency}
targetCurrency={targetCurrency}
feeAmount={feeAmount}
/>
))}
</Row>
</DetailsRow>
</RouteDetailsContainer>
<TokenImg token={trade.outputAmount.currency} />
</RouteRow>
))}
</Wrapper>
)
}
import { Protocol } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Pair } from '@uniswap/v2-sdk'
import { FeeAmount } from '@uniswap/v3-sdk'
import { InterfaceTrade } from 'state/routing/types'
export interface RoutingDiagramEntry {
percent: Percent
path: [Currency, Currency, FeeAmount][]
protocol: Protocol
}
const V2_DEFAULT_FEE_TIER = 3000
/**
* Loops through all routes on a trade and returns an array of diagram entries.
*/
export function getTokenPath(trade: InterfaceTrade<Currency, Currency, TradeType>): RoutingDiagramEntry[] {
return trade.swaps.map(({ route: { path: tokenPath, pools, protocol }, inputAmount, outputAmount }) => {
const portion =
trade.tradeType === TradeType.EXACT_INPUT
? inputAmount.divide(trade.inputAmount)
: outputAmount.divide(trade.outputAmount)
const percent = new Percent(portion.numerator, portion.denominator)
const path: RoutingDiagramEntry['path'] = []
for (let i = 0; i < pools.length; i++) {
const nextPool = pools[i]
const tokenIn = tokenPath[i]
const tokenOut = tokenPath[i + 1]
const entry: RoutingDiagramEntry['path'][0] = [
tokenIn,
tokenOut,
nextPool instanceof Pair ? V2_DEFAULT_FEE_TIER : nextPool.fee,
]
path.push(entry)
}
return {
percent,
path,
protocol,
}
})
}
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Currency, TradeType } from '@uniswap/sdk-core' import { Currency, TradeType } from '@uniswap/sdk-core'
import useUSDCPrice from 'hooks/useUSDCPrice' import useUSDCPrice from 'hooks/useUSDCPrice'
import Tooltip from 'lib/components/Tooltip'
import { AlertTriangle, Icon, Info, Spinner } from 'lib/icons' import { AlertTriangle, Icon, Info, Spinner } from 'lib/icons'
import { ThemedText } from 'lib/theme' import { ThemedText } from 'lib/theme'
import { ReactNode, useMemo, useState } from 'react' import { ReactNode, useMemo, useState } from 'react'
...@@ -8,7 +9,7 @@ import { InterfaceTrade } from 'state/routing/types' ...@@ -8,7 +9,7 @@ import { InterfaceTrade } from 'state/routing/types'
import { TextButton } from '../../Button' import { TextButton } from '../../Button'
import Row from '../../Row' import Row from '../../Row'
import RoutingTooltip from './RoutingTooltip' import RoutingDiagram from '../RoutingDiagram'
interface CaptionProps { interface CaptionProps {
icon?: Icon icon?: Icon
...@@ -72,7 +73,9 @@ export function Trade({ trade }: { trade: InterfaceTrade<Currency, Currency, Tra ...@@ -72,7 +73,9 @@ export function Trade({ trade }: { trade: InterfaceTrade<Currency, Currency, Tra
return ( return (
<> <>
<RoutingTooltip /> <Tooltip placement="bottom" icon={Info}>
<RoutingDiagram trade={trade} />
</Tooltip>
<TextButton color="primary" onClick={() => setFlip(!flip)}> <TextButton color="primary" onClick={() => setFlip(!flip)}>
{ratio} {ratio}
</TextButton> </TextButton>
......
import { Info } from 'lib/icons'
export default function RoutingTooltip() {
return <Info color="secondary" />
/* TODO(zzmp): Implement post-beta launch.
return (
<Tooltip icon={Info} placement="bottom">
<ThemeProvider>
<ThemedText.Subhead2>TODO: Routing Tooltip</ThemedText.Subhead2>
</ThemeProvider>
</Tooltip>
)
*/
}
import { ReactComponent as RouterIcon } from 'lib/assets/svg/auto_router.svg'
import { ReactComponent as CheckIcon } from 'lib/assets/svg/check.svg' import { ReactComponent as CheckIcon } from 'lib/assets/svg/check.svg'
import { ReactComponent as ExpandoIcon } from 'lib/assets/svg/expando.svg' import { ReactComponent as ExpandoIcon } from 'lib/assets/svg/expando.svg'
import { ReactComponent as LogoIcon } from 'lib/assets/svg/logo.svg' import { ReactComponent as LogoIcon } from 'lib/assets/svg/logo.svg'
...@@ -82,6 +83,7 @@ export const Clock = icon(ClockIcon) ...@@ -82,6 +83,7 @@ export const Clock = icon(ClockIcon)
export const HelpCircle = icon(HelpCircleIcon) export const HelpCircle = icon(HelpCircleIcon)
export const Info = icon(InfoIcon) export const Info = icon(InfoIcon)
export const Link = icon(LinkIcon) export const Link = icon(LinkIcon)
export const AutoRouter = icon(RouterIcon)
export const Settings = icon(SettingsIcon) export const Settings = icon(SettingsIcon)
export const Slash = icon(SlashIcon) export const Slash = icon(SlashIcon)
export const Trash2 = icon(Trash2Icon) export const Trash2 = icon(Trash2Icon)
......
export enum Layer { export enum Layer {
UNDERLAYER = -1,
OVERLAY = 100, OVERLAY = 100,
DIALOG = 1000, DIALOG = 1000,
TOOLTIP = 2000, TOOLTIP = 2000,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment