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:
Zach Pomerantz 2022-02-01 15:03:55 -08:00 committed by GitHub
parent c82b4fae64
commit 4b762ef5c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 185 additions and 76 deletions

@ -23,6 +23,8 @@ const Reference = styled.div`
display: inline-block; display: inline-block;
` `
const SQRT_8 = Math.sqrt(8)
const Arrow = styled.div` const Arrow = styled.div`
height: 8px; height: 8px;
width: 8px; width: 8px;
@ -30,9 +32,7 @@ const Arrow = styled.div`
::before { ::before {
background: ${({ theme }) => theme.dialog}; background: ${({ theme }) => theme.dialog};
border: 1px solid ${({ theme }) => theme.outline};
content: ''; content: '';
height: 8px; height: 8px;
position: absolute; position: absolute;
transform: rotate(45deg); transform: rotate(45deg);
@ -40,34 +40,30 @@ const Arrow = styled.div`
} }
&.arrow-top { &.arrow-top {
bottom: -5px; bottom: -${SQRT_8}px;
::before { ::before {
border-left: none; border-bottom-right-radius: 1px;
border-top: none;
} }
} }
&.arrow-bottom { &.arrow-bottom {
top: -5px; top: -${SQRT_8}px;
::before { ::before {
border-bottom: none; border-top-left-radius: 1px;
border-right: none;
} }
} }
&.arrow-left { &.arrow-left {
right: -5px; right: -${SQRT_8}px;
::before { ::before {
border-bottom: none; border-top-right-radius: 1px;
border-left: none;
} }
} }
&.arrow-right { &.arrow-right {
left: -5px; left: -${SQRT_8}px;
::before { ::before {
border-right: none; border-bottom-left-radius: 1px;
border-top: none;
} }
} }
` `
@ -77,10 +73,11 @@ export interface PopoverProps {
show: boolean show: boolean
children: React.ReactNode children: React.ReactNode
placement: Placement placement: Placement
offset?: number
contained?: true 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 boundary = useContext(BoundaryContext)
const reference = useRef<HTMLDivElement>(null) const reference = useRef<HTMLDivElement>(null)
@ -90,8 +87,8 @@ export default function Popover({ content, show, children, placement, contained
const options = useMemo((): Options => { const options = useMemo((): Options => {
const modifiers: Options['modifiers'] = [ const modifiers: Options['modifiers'] = [
{ name: 'offset', options: { offset: [5, 5] } }, { name: 'offset', options: { offset: [4, offset || 4] } },
{ name: 'arrow', options: { element: arrow, padding: 6 } }, { name: 'arrow', options: { element: arrow, padding: 4 } },
] ]
if (contained) { if (contained) {
modifiers.push( modifiers.push(
@ -118,7 +115,7 @@ export default function Popover({ content, show, children, placement, contained
strategy: 'absolute', strategy: 'absolute',
modifiers, modifiers,
} }
}, [arrow, boundary, placement, contained]) }, [offset, arrow, contained, placement, boundary])
const { styles, attributes } = usePopper(reference.current, popover, options) 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 { Percent } from '@uniswap/sdk-core'
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
import { Check, LargeIcon } from 'lib/icons' import Popover from 'lib/components/Popover'
import { maxSlippageAtom } from 'lib/state/settings' import { TooltipHandlers, useTooltip } from 'lib/components/Tooltip'
import styled, { ThemedText } from 'lib/theme' import { AlertTriangle, Check, Icon, LargeIcon, XOctagon } from 'lib/icons'
import { PropsWithChildren, useCallback, useRef, useState } from 'react' 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 { BaseButton, TextButton } from '../../Button'
import Column from '../../Column' import Column from '../../Column'
@ -15,6 +17,10 @@ import { Label, optionCss } from './components'
const tooltip = ( const tooltip = (
<Trans>Your transaction will revert if the price changes unfavorably by more than this percentage.</Trans> <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 }>` const Button = styled(TextButton)<{ selected: boolean }>`
${({ selected }) => optionCss(selected)} ${({ selected }) => optionCss(selected)}
@ -23,27 +29,74 @@ const Button = styled(TextButton)<{ selected: boolean }>`
const Custom = styled(BaseButton)<{ selected: boolean }>` const Custom = styled(BaseButton)<{ selected: boolean }>`
${({ selected }) => optionCss(selected)} ${({ selected }) => optionCss(selected)}
${inputCss} ${inputCss}
border-color: ${({ selected, theme }) => (selected ? theme.active : 'transparent')} !important;
padding: calc(0.75em - 3px) 0.625em; padding: calc(0.75em - 3px) 0.625em;
` `
interface OptionProps { interface OptionProps extends Partial<TooltipHandlers> {
wrapper: typeof Button | typeof Custom wrapper: typeof Button | typeof Custom
selected: boolean selected: boolean
onSelect: () => void 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 ( return (
<Wrapper selected={selected} onClick={onSelect}> <Wrapper selected={selected} onClick={onSelect} {...tooltipHandlers}>
<Row gap={0.5}> <Row gap={0.5}>
{children} {children}
<span style={{ width: '1.2em' }}>{selected && <LargeIcon icon={Check} />}</span> {icon ? icon : <LargeIcon icon={selected ? Check : undefined} size={1.25} />}
</Row> </Row>
</Wrapper> </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() { export default function MaxSlippageSelect() {
const [maxSlippage, setMaxSlippage] = useAtom(maxSlippageAtom) const [maxSlippage, setMaxSlippage] = useAtom(maxSlippageAtom)
@ -51,22 +104,33 @@ export default function MaxSlippageSelect() {
const input = useRef<HTMLInputElement>(null) const input = useRef<HTMLInputElement>(null)
const focus = useCallback(() => input.current?.focus(), [input]) const focus = useCallback(() => input.current?.focus(), [input])
const onInputChange = useCallback( const [warning, setWarning] = useState(WarningState.NONE)
(custom: string) => { const [showTooltip, setShowTooltip, tooltipProps] = useTooltip()
setCustom(custom) useEffect(() => setShowTooltip(true), [warning, setShowTooltip]) // enables the tooltip if a warning is set
const numerator = Math.floor(+custom * 100)
if (numerator) { const processInput = useCallback(() => {
setMaxSlippage(new Percent(numerator, 10_000)) const numerator = Math.floor(+custom * 100)
} else { if (numerator) {
const percent = new Percent(numerator, 10_000)
if (percent.greaterThan(MAX_VALID_SLIPPAGE)) {
setWarning(WarningState.INVALID_SLIPPAGE)
setMaxSlippage('auto') setMaxSlippage('auto')
} else if (percent.greaterThan(MIN_HIGH_SLIPPAGE)) {
setWarning(WarningState.HIGH_SLIPPAGE)
setMaxSlippage(percent)
} else {
setWarning(WarningState.NONE)
setMaxSlippage(percent)
} }
}, } else {
[setMaxSlippage] setMaxSlippage('auto')
) }
}, [custom, setMaxSlippage])
useEffect(processInput, [processInput])
const onInputSelect = useCallback(() => { const onInputSelect = useCallback(() => {
focus() focus()
onInputChange(custom) processInput()
}, [custom, focus, onInputChange]) }, [focus, processInput])
return ( return (
<Column gap={0.75}> <Column gap={0.75}>
@ -77,9 +141,22 @@ export default function MaxSlippageSelect() {
<Trans>Auto</Trans> <Trans>Auto</Trans>
</ThemedText.ButtonMedium> </ThemedText.ButtonMedium>
</Option> </Option>
<Option wrapper={Custom} onSelect={onInputSelect} selected={maxSlippage !== 'auto'}> <Option
<Row> wrapper={Custom}
<DecimalInput value={custom} onChange={onInputChange} placeholder={t`Custom`} ref={input} />% 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> </Row>
</Option> </Option>
</Row> </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 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)` const Input = styled(Row)`
${inputCss} ${inputCss}
` `
@ -22,11 +24,12 @@ export default function TransactionTtlInput() {
<Column gap={0.75}> <Column gap={0.75}>
<Label name={<Trans>Transaction deadline</Trans>} tooltip={tooltip} /> <Label name={<Trans>Transaction deadline</Trans>} tooltip={tooltip} />
<ThemedText.Body1> <ThemedText.Body1>
<Input onClick={() => input.current?.focus()}> <Input justify="start" onClick={() => input.current?.focus()}>
<IntegerInput <IntegerInput
placeholder={TRANSACTION_TTL_DEFAULT.toString()} placeholder={placeholder}
value={transactionTtl?.toString() ?? ''} value={transactionTtl?.toString() ?? ''}
onChange={(value) => setTransactionTtl(value ? parseFloat(value) : 0)} onChange={(value) => setTransactionTtl(value ? parseFloat(value) : 0)}
size={Math.max(transactionTtl?.toString().length || 0, placeholder.length)}
ref={input} ref={input}
/> />
<Trans>minutes</Trans> <Trans>minutes</Trans>

@ -17,6 +17,10 @@ export const optionCss = (selected: boolean) => css`
:enabled:hover { :enabled:hover {
border-color: ${({ theme }) => theme.onHover(selected ? theme.active : theme.outline)}; border-color: ${({ theme }) => theme.onHover(selected ? theme.active : theme.outline)};
} }
:enabled:focus-within {
border-color: ${({ theme }) => theme.active};
}
` `
export function value(Value: AnyStyledComponent) { export function value(Value: AnyStyledComponent) {

@ -2,8 +2,8 @@ import { t } from '@lingui/macro'
import { Trade } from '@uniswap/router-sdk' import { Trade } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core' import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
import { integratorFeeAtom } from 'lib/state/settings' import { integratorFeeAtom, MIN_HIGH_SLIPPAGE } from 'lib/state/settings'
import { ThemedText } from 'lib/theme' import { Color, ThemedText } from 'lib/theme'
import { useMemo } from 'react' import { useMemo } from 'react'
import { currencyId } from 'utils/currencyId' import { currencyId } from 'utils/currencyId'
import { computeRealizedLPFeePercent } from 'utils/prices' import { computeRealizedLPFeePercent } from 'utils/prices'
@ -13,11 +13,12 @@ import Row from '../../Row'
interface DetailProps { interface DetailProps {
label: string label: string
value: string value: string
color?: Color
} }
function Detail({ label, value }: DetailProps) { function Detail({ label, value, color }: DetailProps) {
return ( return (
<ThemedText.Caption> <ThemedText.Caption color={color}>
<Row gap={2}> <Row gap={2}>
<span>{label}</span> <span>{label}</span>
<span style={{ whiteSpace: 'nowrap' }}>{value}</span> <span style={{ whiteSpace: 'nowrap' }}>{value}</span>
@ -44,7 +45,7 @@ export default function Details({ trade, allowedSlippage }: DetailsProps) {
return trade.priceImpact.subtract(realizedLpFeePercent) return trade.priceImpact.subtract(realizedLpFeePercent)
}, [trade]) }, [trade])
const details = useMemo((): [string, string][] => { const details = useMemo(() => {
// @TODO(ianlapham): Check that provider fee is even a valid list item // @TODO(ianlapham): Check that provider fee is even a valid list item
return [ return [
// [t`Liquidity provider fee`, `${swap.lpFee} ${inputSymbol}`], // [t`Liquidity provider fee`, `${swap.lpFee} ${inputSymbol}`],
@ -56,17 +57,21 @@ export default function Details({ trade, allowedSlippage }: DetailsProps) {
trade.tradeType === TradeType.EXACT_OUTPUT trade.tradeType === TradeType.EXACT_OUTPUT
? [t`Minimum received`, `${trade.minimumAmountOut(allowedSlippage).toSignificant(6)} ${outputCurrency.symbol}`] ? [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) ].filter(isDetail)
function isDetail(detail: unknown[]): detail is [string, string] { function isDetail(detail: unknown[]): detail is [string, string, Color | undefined] {
return Boolean(detail[1]) return Boolean(detail[1])
} }
}, [allowedSlippage, inputCurrency, integrator, integratorFee, outputCurrency.symbol, priceImpact, trade]) }, [allowedSlippage, inputCurrency, integrator, integratorFee, outputCurrency.symbol, priceImpact, trade])
return ( return (
<> <>
{details.map(([label, detail]) => ( {details.map(([label, detail, color]) => (
<Detail key={label} label={label} value={detail} /> <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 { useAtomValue } from 'jotai/utils'
import { IconButton } from 'lib/components/Button' import { IconButton } from 'lib/components/Button'
import useScrollbar from 'lib/hooks/useScrollbar' 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 { Field, independentFieldAtom } from 'lib/state/swap'
import styled, { ThemedText } from 'lib/theme' import styled, { ThemedText } from 'lib/theme'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
@ -44,6 +45,7 @@ const Body = styled(Column)<{ open: boolean }>`
${Column} { ${Column} {
height: 100%; height: 100%;
grid-template-rows: repeat(auto-fill, 1em);
padding: ${({ open }) => (open ? '0.5em 0' : 0)}; padding: ${({ open }) => (open ? '0.5em 0' : 0)};
transition: padding 0.25s; transition: padding 0.25s;
@ -61,6 +63,7 @@ const Body = styled(Column)<{ open: boolean }>`
${Estimate} { ${Estimate} {
max-height: ${({ open }) => (open ? 0 : 56 / 12)}em; // 2 * line-height + padding max-height: ${({ open }) => (open ? 0 : 56 / 12)}em; // 2 * line-height + padding
min-height: 0;
overflow-y: hidden; overflow-y: hidden;
padding: ${({ open }) => (open ? 0 : '1em 0')}; padding: ${({ open }) => (open ? 0 : '1em 0')};
transition: ${({ open }) => transition: ${({ open }) =>
@ -87,6 +90,8 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
const independentField = useAtomValue(independentFieldAtom) const independentField = useAtomValue(independentFieldAtom)
const slippageWarning = useMemo(() => allowedSlippage.greaterThan(MIN_HIGH_SLIPPAGE), [allowedSlippage])
const [confirmedTrade, setConfirmedTrade] = useState(trade) const [confirmedTrade, setConfirmedTrade] = useState(trade)
const doesTradeDiffer = useMemo( const doesTradeDiffer = useMemo(
() => Boolean(trade && confirmedTrade && tradeMeaningfullyDiffers(trade, confirmedTrade)), () => Boolean(trade && confirmedTrade && tradeMeaningfullyDiffers(trade, confirmedTrade)),
@ -115,7 +120,7 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
<Rule /> <Rule />
<Row> <Row>
<Row gap={0.5}> <Row gap={0.5}>
<Info color="secondary" /> {slippageWarning ? <AlertTriangle color="warning" /> : <Info color="secondary" />}
<ThemedText.Subhead2 color="secondary"> <ThemedText.Subhead2 color="secondary">
<Trans>Swap details</Trans> <Trans>Swap details</Trans>
</ThemedText.Subhead2> </ThemedText.Subhead2>

@ -1,40 +1,52 @@
import { Placement } from '@popperjs/core' import { Placement } from '@popperjs/core'
import { HelpCircle, Icon } from 'lib/icons' import { HelpCircle, Icon } from 'lib/icons'
import styled from 'lib/theme' import styled from 'lib/theme'
import { ReactNode, useState } from 'react' import { ComponentProps, ReactNode, useCallback, useState } from 'react'
import { IconButton } from './Button' import { IconButton } from './Button'
import Popover from './Popover' 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)` const IconTooltip = styled(IconButton)`
:hover { :hover {
cursor: help; cursor: help;
} }
` `
interface TooltipInterface { interface TooltipProps {
icon?: Icon icon?: Icon
iconProps?: ComponentProps<Icon>
children: ReactNode children: ReactNode
placement: Placement placement?: Placement
offset?: number
contained?: true contained?: true
} }
export default function Tooltip({ export default function Tooltip({
icon: Icon = HelpCircle, icon: Icon = HelpCircle,
iconProps,
children, children,
placement = 'auto', placement = 'auto',
offset,
contained, contained,
}: TooltipInterface) { }: TooltipProps) {
const [show, setShow] = useState(false) const [showTooltip, , tooltipProps] = useTooltip()
return ( return (
<Popover content={children} show={show} placement={placement} contained={contained}> <Popover content={children} show={showTooltip} placement={placement} offset={offset} contained={contained}>
<IconTooltip <IconTooltip icon={Icon} iconProps={iconProps} {...tooltipProps} />
onMouseEnter={() => setShow(true)}
onMouseLeave={() => setShow(false)}
onFocus={() => setShow(true)}
onBlur={() => setShow(false)}
icon={Icon}
/>
</Popover> </Popover>
) )
} }

@ -3,7 +3,7 @@ import { L2_CHAIN_IDS } from 'constants/chains'
import { L2_DEADLINE_FROM_NOW } from 'constants/misc' import { L2_DEADLINE_FROM_NOW } from 'constants/misc'
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp' import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
import { useAtomValue } from 'jotai/utils' 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 { useMemo } from 'react'
import useActiveWeb3React from './useActiveWeb3React' 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 // 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 { export default function useTransactionDeadline(): BigNumber | undefined {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const userDeadline = useAtomValue(transactionTtlAtom) const userDeadline: number = useAtomValue(transactionTtlAtom) || TRANSACTION_TTL_DEFAULT
const blockTimestamp = useCurrentBlockTimestamp() const blockTimestamp = useCurrentBlockTimestamp()
return useMemo(() => { return useMemo(() => {
if (blockTimestamp && chainId && L2_CHAIN_IDS.includes(chainId)) return blockTimestamp.add(L2_DEADLINE_FROM_NOW) if (blockTimestamp && chainId && L2_CHAIN_IDS.includes(chainId)) return blockTimestamp.add(L2_DEADLINE_FROM_NOW)

@ -18,6 +18,7 @@ import {
Slash as SlashIcon, Slash as SlashIcon,
Trash2 as Trash2Icon, Trash2 as Trash2Icon,
X as XIcon, X as XIcon,
XOctagon as XOctagonIcon,
} from 'react-feather' } from 'react-feather'
import { ReactComponent as CheckIcon } from '../assets/svg/check.svg' 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 }>` export const largeIconCss = css<{ iconSize: number }>`
display: flex; display: flex;
@ -50,11 +49,14 @@ export const largeIconCss = css<{ iconSize: number }>`
const LargeWrapper = styled.div<{ iconSize: number }>` const LargeWrapper = styled.div<{ iconSize: number }>`
height: 1em; height: 1em;
width: ${({ iconSize }) => iconSize}em;
${largeIconCss} ${largeIconCss}
` `
export type Icon = ReturnType<typeof icon> | typeof LargeIcon
interface LargeIconProps { interface LargeIconProps {
icon: Icon icon?: Icon
color?: Color color?: Color
size?: number size?: number
className?: string className?: string
@ -63,7 +65,7 @@ interface LargeIconProps {
export function LargeIcon({ icon: Icon, color, size = 1.2, className }: LargeIconProps) { export function LargeIcon({ icon: Icon, color, size = 1.2, className }: LargeIconProps) {
return ( return (
<LargeWrapper color={color} iconSize={size} className={className}> <LargeWrapper color={color} iconSize={size} className={className}>
<Icon color={color} /> {Icon && <Icon color={color} />}
</LargeWrapper> </LargeWrapper>
) )
} }
@ -83,6 +85,7 @@ 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 const X = icon(XIcon) export const X = icon(XIcon)
export const XOctagon = icon(XOctagonIcon)
export const Check = styled(icon(CheckIcon))` export const Check = styled(icon(CheckIcon))`
circle { circle {

@ -3,6 +3,9 @@ import { atomWithReset } from 'jotai/utils'
import { pickAtom, setTogglable } from './atoms' 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 // transaction deadline in minutes, needs to be adjusted to seconds before use
// @TODO(ianlapham): update this to be stored as seconds // @TODO(ianlapham): update this to be stored as seconds
export const TRANSACTION_TTL_DEFAULT = 30 export const TRANSACTION_TTL_DEFAULT = 30
@ -17,7 +20,7 @@ interface Settings {
const initialSettings: Settings = { const initialSettings: Settings = {
maxSlippage: 'auto', maxSlippage: 'auto',
transactionTtl: TRANSACTION_TTL_DEFAULT, transactionTtl: undefined,
integratorFee: undefined, integratorFee: undefined,
mockTogglable: true, mockTogglable: true,
clientSideRouter: false, clientSideRouter: false,