feat: slippage warning ux (#3211)
* feat: setting input spacings * feat: popover icon props * fix: slippage input border * feat: slippage input warning ux * feat: slippage summary warning ux * fix: summary layout * fix: large icon compatibility * fix: input option style * fix: large icon compatibility * fix: popover dimensions * feat: tooltip hook * fix: better max slippage popovers * feat: error color input on invalid slippage * fix: use default tx ttl * fix: type userDeadline
This commit is contained in:
parent
c82b4fae64
commit
4b762ef5c9
@ -23,6 +23,8 @@ const Reference = styled.div`
|
||||
display: inline-block;
|
||||
`
|
||||
|
||||
const SQRT_8 = Math.sqrt(8)
|
||||
|
||||
const Arrow = styled.div`
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
@ -30,9 +32,7 @@ const Arrow = styled.div`
|
||||
|
||||
::before {
|
||||
background: ${({ theme }) => theme.dialog};
|
||||
border: 1px solid ${({ theme }) => theme.outline};
|
||||
content: '';
|
||||
|
||||
height: 8px;
|
||||
position: absolute;
|
||||
transform: rotate(45deg);
|
||||
@ -40,34 +40,30 @@ const Arrow = styled.div`
|
||||
}
|
||||
|
||||
&.arrow-top {
|
||||
bottom: -5px;
|
||||
bottom: -${SQRT_8}px;
|
||||
::before {
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
border-bottom-right-radius: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&.arrow-bottom {
|
||||
top: -5px;
|
||||
top: -${SQRT_8}px;
|
||||
::before {
|
||||
border-bottom: none;
|
||||
border-right: none;
|
||||
border-top-left-radius: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&.arrow-left {
|
||||
right: -5px;
|
||||
right: -${SQRT_8}px;
|
||||
::before {
|
||||
border-bottom: none;
|
||||
border-left: none;
|
||||
border-top-right-radius: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&.arrow-right {
|
||||
left: -5px;
|
||||
left: -${SQRT_8}px;
|
||||
::before {
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
border-bottom-left-radius: 1px;
|
||||
}
|
||||
}
|
||||
`
|
||||
@ -77,10 +73,11 @@ export interface PopoverProps {
|
||||
show: boolean
|
||||
children: React.ReactNode
|
||||
placement: Placement
|
||||
offset?: number
|
||||
contained?: true
|
||||
}
|
||||
|
||||
export default function Popover({ content, show, children, placement, contained }: PopoverProps) {
|
||||
export default function Popover({ content, show, children, placement, offset, contained }: PopoverProps) {
|
||||
const boundary = useContext(BoundaryContext)
|
||||
const reference = useRef<HTMLDivElement>(null)
|
||||
|
||||
@ -90,8 +87,8 @@ export default function Popover({ content, show, children, placement, contained
|
||||
|
||||
const options = useMemo((): Options => {
|
||||
const modifiers: Options['modifiers'] = [
|
||||
{ name: 'offset', options: { offset: [5, 5] } },
|
||||
{ name: 'arrow', options: { element: arrow, padding: 6 } },
|
||||
{ name: 'offset', options: { offset: [4, offset || 4] } },
|
||||
{ name: 'arrow', options: { element: arrow, padding: 4 } },
|
||||
]
|
||||
if (contained) {
|
||||
modifiers.push(
|
||||
@ -118,7 +115,7 @@ export default function Popover({ content, show, children, placement, contained
|
||||
strategy: 'absolute',
|
||||
modifiers,
|
||||
}
|
||||
}, [arrow, boundary, placement, contained])
|
||||
}, [offset, arrow, contained, placement, boundary])
|
||||
|
||||
const { styles, attributes } = usePopper(reference.current, popover, options)
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { t, Trans } from '@lingui/macro'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { useAtom } from 'jotai'
|
||||
import { Check, LargeIcon } from 'lib/icons'
|
||||
import { maxSlippageAtom } from 'lib/state/settings'
|
||||
import styled, { ThemedText } from 'lib/theme'
|
||||
import { PropsWithChildren, useCallback, useRef, useState } from 'react'
|
||||
import Popover from 'lib/components/Popover'
|
||||
import { TooltipHandlers, useTooltip } from 'lib/components/Tooltip'
|
||||
import { AlertTriangle, Check, Icon, LargeIcon, XOctagon } from 'lib/icons'
|
||||
import { MAX_VALID_SLIPPAGE, maxSlippageAtom, MIN_HIGH_SLIPPAGE } from 'lib/state/settings'
|
||||
import styled, { Color, ThemedText } from 'lib/theme'
|
||||
import { memo, PropsWithChildren, ReactNode, useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { BaseButton, TextButton } from '../../Button'
|
||||
import Column from '../../Column'
|
||||
@ -15,6 +17,10 @@ import { Label, optionCss } from './components'
|
||||
const tooltip = (
|
||||
<Trans>Your transaction will revert if the price changes unfavorably by more than this percentage.</Trans>
|
||||
)
|
||||
const highSlippage = <Trans>High slippage increases the risk of price movement</Trans>
|
||||
const invalidSlippage = <Trans>Please enter a valid slippage %</Trans>
|
||||
|
||||
const placeholder = '0.10'
|
||||
|
||||
const Button = styled(TextButton)<{ selected: boolean }>`
|
||||
${({ selected }) => optionCss(selected)}
|
||||
@ -23,27 +29,74 @@ const Button = styled(TextButton)<{ selected: boolean }>`
|
||||
const Custom = styled(BaseButton)<{ selected: boolean }>`
|
||||
${({ selected }) => optionCss(selected)}
|
||||
${inputCss}
|
||||
border-color: ${({ selected, theme }) => (selected ? theme.active : 'transparent')} !important;
|
||||
padding: calc(0.75em - 3px) 0.625em;
|
||||
`
|
||||
|
||||
interface OptionProps {
|
||||
interface OptionProps extends Partial<TooltipHandlers> {
|
||||
wrapper: typeof Button | typeof Custom
|
||||
selected: boolean
|
||||
onSelect: () => void
|
||||
icon?: ReactNode
|
||||
}
|
||||
|
||||
function Option({ wrapper: Wrapper, children, selected, onSelect }: PropsWithChildren<OptionProps>) {
|
||||
function Option({
|
||||
wrapper: Wrapper,
|
||||
children,
|
||||
selected,
|
||||
onSelect,
|
||||
icon,
|
||||
...tooltipHandlers
|
||||
}: PropsWithChildren<OptionProps>) {
|
||||
return (
|
||||
<Wrapper selected={selected} onClick={onSelect}>
|
||||
<Wrapper selected={selected} onClick={onSelect} {...tooltipHandlers}>
|
||||
<Row gap={0.5}>
|
||||
{children}
|
||||
<span style={{ width: '1.2em' }}>{selected && <LargeIcon icon={Check} />}</span>
|
||||
{icon ? icon : <LargeIcon icon={selected ? Check : undefined} size={1.25} />}
|
||||
</Row>
|
||||
</Wrapper>
|
||||
)
|
||||
}
|
||||
|
||||
enum WarningState {
|
||||
NONE,
|
||||
HIGH_SLIPPAGE,
|
||||
INVALID_SLIPPAGE,
|
||||
}
|
||||
|
||||
const Warning = memo(function Warning({ state, showTooltip }: { state: WarningState; showTooltip: boolean }) {
|
||||
let icon: Icon
|
||||
let color: Color
|
||||
let content: ReactNode
|
||||
let show = showTooltip
|
||||
switch (state) {
|
||||
case WarningState.INVALID_SLIPPAGE:
|
||||
icon = XOctagon
|
||||
color = 'error'
|
||||
content = invalidSlippage
|
||||
show = true
|
||||
break
|
||||
case WarningState.HIGH_SLIPPAGE:
|
||||
icon = AlertTriangle
|
||||
color = 'warning'
|
||||
content = highSlippage
|
||||
break
|
||||
case WarningState.NONE:
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<Popover
|
||||
key={state}
|
||||
content={<ThemedText.Caption>{content}</ThemedText.Caption>}
|
||||
show={show}
|
||||
placement="top"
|
||||
offset={16}
|
||||
contained
|
||||
>
|
||||
<LargeIcon icon={icon} color={color} size={1.25} />
|
||||
</Popover>
|
||||
)
|
||||
})
|
||||
|
||||
export default function MaxSlippageSelect() {
|
||||
const [maxSlippage, setMaxSlippage] = useAtom(maxSlippageAtom)
|
||||
|
||||
@ -51,22 +104,33 @@ export default function MaxSlippageSelect() {
|
||||
const input = useRef<HTMLInputElement>(null)
|
||||
const focus = useCallback(() => input.current?.focus(), [input])
|
||||
|
||||
const onInputChange = useCallback(
|
||||
(custom: string) => {
|
||||
setCustom(custom)
|
||||
const numerator = Math.floor(+custom * 100)
|
||||
if (numerator) {
|
||||
setMaxSlippage(new Percent(numerator, 10_000))
|
||||
} else {
|
||||
const [warning, setWarning] = useState(WarningState.NONE)
|
||||
const [showTooltip, setShowTooltip, tooltipProps] = useTooltip()
|
||||
useEffect(() => setShowTooltip(true), [warning, setShowTooltip]) // enables the tooltip if a warning is set
|
||||
|
||||
const processInput = useCallback(() => {
|
||||
const numerator = Math.floor(+custom * 100)
|
||||
if (numerator) {
|
||||
const percent = new Percent(numerator, 10_000)
|
||||
if (percent.greaterThan(MAX_VALID_SLIPPAGE)) {
|
||||
setWarning(WarningState.INVALID_SLIPPAGE)
|
||||
setMaxSlippage('auto')
|
||||
} else if (percent.greaterThan(MIN_HIGH_SLIPPAGE)) {
|
||||
setWarning(WarningState.HIGH_SLIPPAGE)
|
||||
setMaxSlippage(percent)
|
||||
} else {
|
||||
setWarning(WarningState.NONE)
|
||||
setMaxSlippage(percent)
|
||||
}
|
||||
},
|
||||
[setMaxSlippage]
|
||||
)
|
||||
} else {
|
||||
setMaxSlippage('auto')
|
||||
}
|
||||
}, [custom, setMaxSlippage])
|
||||
useEffect(processInput, [processInput])
|
||||
const onInputSelect = useCallback(() => {
|
||||
focus()
|
||||
onInputChange(custom)
|
||||
}, [custom, focus, onInputChange])
|
||||
processInput()
|
||||
}, [focus, processInput])
|
||||
|
||||
return (
|
||||
<Column gap={0.75}>
|
||||
@ -77,9 +141,22 @@ export default function MaxSlippageSelect() {
|
||||
<Trans>Auto</Trans>
|
||||
</ThemedText.ButtonMedium>
|
||||
</Option>
|
||||
<Option wrapper={Custom} onSelect={onInputSelect} selected={maxSlippage !== 'auto'}>
|
||||
<Row>
|
||||
<DecimalInput value={custom} onChange={onInputChange} placeholder={t`Custom`} ref={input} />%
|
||||
<Option
|
||||
wrapper={Custom}
|
||||
selected={maxSlippage !== 'auto'}
|
||||
onSelect={onInputSelect}
|
||||
icon={<Warning state={warning} showTooltip={showTooltip} />}
|
||||
{...tooltipProps}
|
||||
>
|
||||
<Row color={warning === WarningState.INVALID_SLIPPAGE ? 'error' : undefined}>
|
||||
<DecimalInput
|
||||
size={Math.max(custom.length, 3)}
|
||||
value={custom}
|
||||
onChange={setCustom}
|
||||
placeholder={placeholder}
|
||||
ref={input}
|
||||
/>
|
||||
%
|
||||
</Row>
|
||||
</Option>
|
||||
</Row>
|
||||
|
@ -11,6 +11,8 @@ import { Label } from './components'
|
||||
|
||||
const tooltip = <Trans>Your transaction will revert if it has been pending for longer than this period of time.</Trans>
|
||||
|
||||
const placeholder = TRANSACTION_TTL_DEFAULT.toString()
|
||||
|
||||
const Input = styled(Row)`
|
||||
${inputCss}
|
||||
`
|
||||
@ -22,11 +24,12 @@ export default function TransactionTtlInput() {
|
||||
<Column gap={0.75}>
|
||||
<Label name={<Trans>Transaction deadline</Trans>} tooltip={tooltip} />
|
||||
<ThemedText.Body1>
|
||||
<Input onClick={() => input.current?.focus()}>
|
||||
<Input justify="start" onClick={() => input.current?.focus()}>
|
||||
<IntegerInput
|
||||
placeholder={TRANSACTION_TTL_DEFAULT.toString()}
|
||||
placeholder={placeholder}
|
||||
value={transactionTtl?.toString() ?? ''}
|
||||
onChange={(value) => setTransactionTtl(value ? parseFloat(value) : 0)}
|
||||
size={Math.max(transactionTtl?.toString().length || 0, placeholder.length)}
|
||||
ref={input}
|
||||
/>
|
||||
<Trans>minutes</Trans>
|
||||
|
@ -17,6 +17,10 @@ export const optionCss = (selected: boolean) => css`
|
||||
:enabled:hover {
|
||||
border-color: ${({ theme }) => theme.onHover(selected ? theme.active : theme.outline)};
|
||||
}
|
||||
|
||||
:enabled:focus-within {
|
||||
border-color: ${({ theme }) => theme.active};
|
||||
}
|
||||
`
|
||||
|
||||
export function value(Value: AnyStyledComponent) {
|
||||
|
@ -2,8 +2,8 @@ import { t } from '@lingui/macro'
|
||||
import { Trade } from '@uniswap/router-sdk'
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { useAtom } from 'jotai'
|
||||
import { integratorFeeAtom } from 'lib/state/settings'
|
||||
import { ThemedText } from 'lib/theme'
|
||||
import { integratorFeeAtom, MIN_HIGH_SLIPPAGE } from 'lib/state/settings'
|
||||
import { Color, ThemedText } from 'lib/theme'
|
||||
import { useMemo } from 'react'
|
||||
import { currencyId } from 'utils/currencyId'
|
||||
import { computeRealizedLPFeePercent } from 'utils/prices'
|
||||
@ -13,11 +13,12 @@ import Row from '../../Row'
|
||||
interface DetailProps {
|
||||
label: string
|
||||
value: string
|
||||
color?: Color
|
||||
}
|
||||
|
||||
function Detail({ label, value }: DetailProps) {
|
||||
function Detail({ label, value, color }: DetailProps) {
|
||||
return (
|
||||
<ThemedText.Caption>
|
||||
<ThemedText.Caption color={color}>
|
||||
<Row gap={2}>
|
||||
<span>{label}</span>
|
||||
<span style={{ whiteSpace: 'nowrap' }}>{value}</span>
|
||||
@ -44,7 +45,7 @@ export default function Details({ trade, allowedSlippage }: DetailsProps) {
|
||||
return trade.priceImpact.subtract(realizedLpFeePercent)
|
||||
}, [trade])
|
||||
|
||||
const details = useMemo((): [string, string][] => {
|
||||
const details = useMemo(() => {
|
||||
// @TODO(ianlapham): Check that provider fee is even a valid list item
|
||||
return [
|
||||
// [t`Liquidity provider fee`, `${swap.lpFee} ${inputSymbol}`],
|
||||
@ -56,17 +57,21 @@ export default function Details({ trade, allowedSlippage }: DetailsProps) {
|
||||
trade.tradeType === TradeType.EXACT_OUTPUT
|
||||
? [t`Minimum received`, `${trade.minimumAmountOut(allowedSlippage).toSignificant(6)} ${outputCurrency.symbol}`]
|
||||
: [],
|
||||
[t`Slippage tolerance`, `${allowedSlippage.toFixed(2)}%`],
|
||||
[
|
||||
t`Slippage tolerance`,
|
||||
`${allowedSlippage.toFixed(2)}%`,
|
||||
allowedSlippage.greaterThan(MIN_HIGH_SLIPPAGE) && 'warning',
|
||||
],
|
||||
].filter(isDetail)
|
||||
|
||||
function isDetail(detail: unknown[]): detail is [string, string] {
|
||||
function isDetail(detail: unknown[]): detail is [string, string, Color | undefined] {
|
||||
return Boolean(detail[1])
|
||||
}
|
||||
}, [allowedSlippage, inputCurrency, integrator, integratorFee, outputCurrency.symbol, priceImpact, trade])
|
||||
return (
|
||||
<>
|
||||
{details.map(([label, detail]) => (
|
||||
<Detail key={label} label={label} value={detail} />
|
||||
{details.map(([label, detail, color]) => (
|
||||
<Detail key={label} label={label} value={detail} color={color} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
|
@ -4,7 +4,8 @@ import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { IconButton } from 'lib/components/Button'
|
||||
import useScrollbar from 'lib/hooks/useScrollbar'
|
||||
import { Expando, Info } from 'lib/icons'
|
||||
import { AlertTriangle, Expando, Info } from 'lib/icons'
|
||||
import { MIN_HIGH_SLIPPAGE } from 'lib/state/settings'
|
||||
import { Field, independentFieldAtom } from 'lib/state/swap'
|
||||
import styled, { ThemedText } from 'lib/theme'
|
||||
import { useMemo, useState } from 'react'
|
||||
@ -44,6 +45,7 @@ const Body = styled(Column)<{ open: boolean }>`
|
||||
|
||||
${Column} {
|
||||
height: 100%;
|
||||
grid-template-rows: repeat(auto-fill, 1em);
|
||||
padding: ${({ open }) => (open ? '0.5em 0' : 0)};
|
||||
transition: padding 0.25s;
|
||||
|
||||
@ -61,6 +63,7 @@ const Body = styled(Column)<{ open: boolean }>`
|
||||
|
||||
${Estimate} {
|
||||
max-height: ${({ open }) => (open ? 0 : 56 / 12)}em; // 2 * line-height + padding
|
||||
min-height: 0;
|
||||
overflow-y: hidden;
|
||||
padding: ${({ open }) => (open ? 0 : '1em 0')};
|
||||
transition: ${({ open }) =>
|
||||
@ -87,6 +90,8 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
|
||||
|
||||
const independentField = useAtomValue(independentFieldAtom)
|
||||
|
||||
const slippageWarning = useMemo(() => allowedSlippage.greaterThan(MIN_HIGH_SLIPPAGE), [allowedSlippage])
|
||||
|
||||
const [confirmedTrade, setConfirmedTrade] = useState(trade)
|
||||
const doesTradeDiffer = useMemo(
|
||||
() => Boolean(trade && confirmedTrade && tradeMeaningfullyDiffers(trade, confirmedTrade)),
|
||||
@ -115,7 +120,7 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
|
||||
<Rule />
|
||||
<Row>
|
||||
<Row gap={0.5}>
|
||||
<Info color="secondary" />
|
||||
{slippageWarning ? <AlertTriangle color="warning" /> : <Info color="secondary" />}
|
||||
<ThemedText.Subhead2 color="secondary">
|
||||
<Trans>Swap details</Trans>
|
||||
</ThemedText.Subhead2>
|
||||
|
@ -1,40 +1,52 @@
|
||||
import { Placement } from '@popperjs/core'
|
||||
import { HelpCircle, Icon } from 'lib/icons'
|
||||
import styled from 'lib/theme'
|
||||
import { ReactNode, useState } from 'react'
|
||||
import { ComponentProps, ReactNode, useCallback, useState } from 'react'
|
||||
|
||||
import { IconButton } from './Button'
|
||||
import Popover from './Popover'
|
||||
|
||||
export interface TooltipHandlers {
|
||||
onMouseEnter: () => void
|
||||
onMouseLeave: () => void
|
||||
onFocus: () => void
|
||||
onBlur: () => void
|
||||
}
|
||||
|
||||
export function useTooltip(): [boolean, (show: boolean) => void, TooltipHandlers] {
|
||||
const [show, setShow] = useState(false)
|
||||
const enable = useCallback(() => setShow(true), [])
|
||||
const disable = useCallback(() => setShow(false), [])
|
||||
return [show, setShow, { onMouseEnter: enable, onMouseLeave: disable, onFocus: enable, onBlur: disable }]
|
||||
}
|
||||
|
||||
const IconTooltip = styled(IconButton)`
|
||||
:hover {
|
||||
cursor: help;
|
||||
}
|
||||
`
|
||||
|
||||
interface TooltipInterface {
|
||||
interface TooltipProps {
|
||||
icon?: Icon
|
||||
iconProps?: ComponentProps<Icon>
|
||||
children: ReactNode
|
||||
placement: Placement
|
||||
placement?: Placement
|
||||
offset?: number
|
||||
contained?: true
|
||||
}
|
||||
|
||||
export default function Tooltip({
|
||||
icon: Icon = HelpCircle,
|
||||
iconProps,
|
||||
children,
|
||||
placement = 'auto',
|
||||
offset,
|
||||
contained,
|
||||
}: TooltipInterface) {
|
||||
const [show, setShow] = useState(false)
|
||||
}: TooltipProps) {
|
||||
const [showTooltip, , tooltipProps] = useTooltip()
|
||||
return (
|
||||
<Popover content={children} show={show} placement={placement} contained={contained}>
|
||||
<IconTooltip
|
||||
onMouseEnter={() => setShow(true)}
|
||||
onMouseLeave={() => setShow(false)}
|
||||
onFocus={() => setShow(true)}
|
||||
onBlur={() => setShow(false)}
|
||||
icon={Icon}
|
||||
/>
|
||||
<Popover content={children} show={showTooltip} placement={placement} offset={offset} contained={contained}>
|
||||
<IconTooltip icon={Icon} iconProps={iconProps} {...tooltipProps} />
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { L2_CHAIN_IDS } from 'constants/chains'
|
||||
import { L2_DEADLINE_FROM_NOW } from 'constants/misc'
|
||||
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { transactionTtlAtom } from 'lib/state/settings'
|
||||
import { TRANSACTION_TTL_DEFAULT, transactionTtlAtom } from 'lib/state/settings'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import useActiveWeb3React from './useActiveWeb3React'
|
||||
@ -11,7 +11,7 @@ import useActiveWeb3React from './useActiveWeb3React'
|
||||
// combines the block timestamp with the user setting to give the deadline that should be used for any submitted transaction
|
||||
export default function useTransactionDeadline(): BigNumber | undefined {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const userDeadline = useAtomValue(transactionTtlAtom)
|
||||
const userDeadline: number = useAtomValue(transactionTtlAtom) || TRANSACTION_TTL_DEFAULT
|
||||
const blockTimestamp = useCurrentBlockTimestamp()
|
||||
return useMemo(() => {
|
||||
if (blockTimestamp && chainId && L2_CHAIN_IDS.includes(chainId)) return blockTimestamp.add(L2_DEADLINE_FROM_NOW)
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
Slash as SlashIcon,
|
||||
Trash2 as Trash2Icon,
|
||||
X as XIcon,
|
||||
XOctagon as XOctagonIcon,
|
||||
} from 'react-feather'
|
||||
|
||||
import { ReactComponent as CheckIcon } from '../assets/svg/check.svg'
|
||||
@ -36,8 +37,6 @@ function icon(Icon: FeatherIcon | SVGIcon) {
|
||||
`
|
||||
}
|
||||
|
||||
export type Icon = ReturnType<typeof icon>
|
||||
|
||||
export const largeIconCss = css<{ iconSize: number }>`
|
||||
display: flex;
|
||||
|
||||
@ -50,11 +49,14 @@ export const largeIconCss = css<{ iconSize: number }>`
|
||||
|
||||
const LargeWrapper = styled.div<{ iconSize: number }>`
|
||||
height: 1em;
|
||||
width: ${({ iconSize }) => iconSize}em;
|
||||
${largeIconCss}
|
||||
`
|
||||
|
||||
export type Icon = ReturnType<typeof icon> | typeof LargeIcon
|
||||
|
||||
interface LargeIconProps {
|
||||
icon: Icon
|
||||
icon?: Icon
|
||||
color?: Color
|
||||
size?: number
|
||||
className?: string
|
||||
@ -63,7 +65,7 @@ interface LargeIconProps {
|
||||
export function LargeIcon({ icon: Icon, color, size = 1.2, className }: LargeIconProps) {
|
||||
return (
|
||||
<LargeWrapper color={color} iconSize={size} className={className}>
|
||||
<Icon color={color} />
|
||||
{Icon && <Icon color={color} />}
|
||||
</LargeWrapper>
|
||||
)
|
||||
}
|
||||
@ -83,6 +85,7 @@ export const Settings = icon(SettingsIcon)
|
||||
export const Slash = icon(SlashIcon)
|
||||
export const Trash2 = icon(Trash2Icon)
|
||||
export const X = icon(XIcon)
|
||||
export const XOctagon = icon(XOctagonIcon)
|
||||
|
||||
export const Check = styled(icon(CheckIcon))`
|
||||
circle {
|
||||
|
@ -3,6 +3,9 @@ import { atomWithReset } from 'jotai/utils'
|
||||
|
||||
import { pickAtom, setTogglable } from './atoms'
|
||||
|
||||
export const MAX_VALID_SLIPPAGE = new Percent(1, 2)
|
||||
export const MIN_HIGH_SLIPPAGE = new Percent(1, 100)
|
||||
|
||||
// transaction deadline in minutes, needs to be adjusted to seconds before use
|
||||
// @TODO(ianlapham): update this to be stored as seconds
|
||||
export const TRANSACTION_TTL_DEFAULT = 30
|
||||
@ -17,7 +20,7 @@ interface Settings {
|
||||
|
||||
const initialSettings: Settings = {
|
||||
maxSlippage: 'auto',
|
||||
transactionTtl: TRANSACTION_TTL_DEFAULT,
|
||||
transactionTtl: undefined,
|
||||
integratorFee: undefined,
|
||||
mockTogglable: true,
|
||||
clientSideRouter: false,
|
||||
|
Loading…
Reference in New Issue
Block a user