Commit b4aac94c authored by Zach Pomerantz's avatar Zach Pomerantz Committed by GitHub

fix: settings ux (#3282)

* fix: max slippage warning logic

* fix: option border specificity

* fix: dialog resizing through animation

* fix: initial warning states

* fix: hide Modal class
parent f47fcc9c
......@@ -73,13 +73,12 @@ export const Modal = styled.div<{ color: Color }>`
border-radius: ${({ theme }) => theme.borderRadius * 0.75}em;
display: flex;
flex-direction: column;
height: calc(100% - 0.5em);
height: 100%;
left: 0;
margin: 0.25em;
overflow: hidden;
position: absolute;
top: 0;
width: calc(100% - 0.5em);
width: 100%;
z-index: ${Layer.DIALOG};
`
......
......@@ -3,6 +3,7 @@ import { Percent } from '@uniswap/sdk-core'
import { useAtom } from 'jotai'
import Popover from 'lib/components/Popover'
import { TooltipHandlers, useTooltip } from 'lib/components/Tooltip'
import { toPercent } from 'lib/hooks/useAllowedSlippage'
import { AlertTriangle, Check, Icon, LargeIcon, XOctagon } from 'lib/icons'
import { autoSlippageAtom, MAX_VALID_SLIPPAGE, maxSlippageAtom, MIN_HIGH_SLIPPAGE } from 'lib/state/settings'
import styled, { Color, ThemedText } from 'lib/theme'
......@@ -58,9 +59,17 @@ function Option({
}
enum WarningState {
NONE,
INVALID_SLIPPAGE = 1,
HIGH_SLIPPAGE,
INVALID_SLIPPAGE,
}
function toWarningState(percent: Percent | undefined): WarningState | undefined {
if (percent?.greaterThan(MAX_VALID_SLIPPAGE)) {
return WarningState.INVALID_SLIPPAGE
} else if (percent?.greaterThan(MIN_HIGH_SLIPPAGE)) {
return WarningState.HIGH_SLIPPAGE
}
return
}
const Warning = memo(function Warning({ state, showTooltip }: { state: WarningState; showTooltip: boolean }) {
......@@ -80,8 +89,6 @@ const Warning = memo(function Warning({ state, showTooltip }: { state: WarningSt
color = 'warning'
content = highSlippage
break
case WarningState.NONE:
return null
}
return (
<Popover
......@@ -104,41 +111,24 @@ export default function MaxSlippageSelect() {
const [autoSlippage, setAutoSlippage] = useAtom(autoSlippageAtom)
const [maxSlippage, setMaxSlippage] = useAtom(maxSlippageAtom)
const maxSlippageInput = useMemo(() => maxSlippage?.toString() || '', [maxSlippage])
const [warning, setWarning] = useState(WarningState.NONE)
const [showTooltip, setShowTooltip, tooltipProps] = useTooltip()
const [warning, setWarning] = useState<WarningState | undefined>(toWarningState(toPercent(maxSlippage)))
const [showTooltip, setShowTooltip, tooltipProps] = useTooltip(/*showOnMount=*/ true)
useEffect(() => setShowTooltip(true), [warning, setShowTooltip]) // enables the tooltip when a warning is set
const processInput = useCallback(
(input: number | undefined) => {
const numerator = input && Math.floor(input * 100)
if (numerator) {
const percent = new Percent(numerator, 10_000)
if (percent.greaterThan(MAX_VALID_SLIPPAGE)) {
setWarning(WarningState.INVALID_SLIPPAGE)
setAutoSlippage(true)
setMaxSlippage(input)
} else if (percent.greaterThan(MIN_HIGH_SLIPPAGE)) {
setWarning(WarningState.HIGH_SLIPPAGE)
setAutoSlippage(false)
setMaxSlippage(input)
} else {
setWarning(WarningState.NONE)
setAutoSlippage(false)
setMaxSlippage(input)
}
} else {
setAutoSlippage(true)
setMaxSlippage(undefined)
}
const processValue = useCallback(
(value: number | undefined) => {
const percent = toPercent(value)
const warning = toWarningState(percent)
setWarning(warning)
setMaxSlippage(value)
setAutoSlippage(!percent || warning === WarningState.INVALID_SLIPPAGE)
},
[setAutoSlippage, setMaxSlippage]
)
const onInputSelect = useCallback(() => {
focus()
processInput(maxSlippage)
}, [focus, maxSlippage, processInput])
useEffect(() => processInput(maxSlippage), [maxSlippage, processInput]) // processes any warnings on mount
useEffect(() => setShowTooltip(true), [warning, setShowTooltip]) // enables the tooltip if a warning is set
processValue(maxSlippage)
}, [focus, maxSlippage, processValue])
return (
<Column gap={0.75}>
......@@ -153,14 +143,14 @@ export default function MaxSlippageSelect() {
wrapper={Custom}
selected={!autoSlippage}
onSelect={onInputSelect}
icon={<Warning state={warning} showTooltip={showTooltip} />}
icon={warning && <Warning state={warning} showTooltip={showTooltip} />}
{...tooltipProps}
>
<Row color={warning === WarningState.INVALID_SLIPPAGE ? 'error' : undefined}>
<DecimalInput
size={Math.max(maxSlippageInput.length, 3)}
value={maxSlippageInput}
onChange={(input) => processInput(+input)}
onChange={(input) => processValue(+input)}
placeholder={placeholder}
ref={input}
/>
......
......@@ -14,6 +14,10 @@ export const optionCss = (selected: boolean) => css`
grid-gap: 0.25em;
padding: calc(0.75em - 1px) 0.625em;
:enabled {
border: 1px solid ${({ theme }) => (selected ? theme.active : theme.outline)};
}
:enabled:hover {
border-color: ${({ theme }) => theme.onHover(selected ? theme.active : theme.outline)};
}
......
......@@ -13,8 +13,8 @@ export interface TooltipHandlers {
onBlur: () => void
}
export function useTooltip(): [boolean, (show: boolean) => void, TooltipHandlers] {
const [show, setShow] = useState(false)
export function useTooltip(showOnMount = false): [boolean, (show: boolean) => void, TooltipHandlers] {
const [show, setShow] = useState(showOnMount)
const enable = useCallback(() => setShow(true), [])
const disable = useCallback(() => setShow(false), [])
return [show, setShow, { onMouseEnter: enable, onMouseLeave: disable, onFocus: enable, onBlur: disable }]
......
......@@ -15,19 +15,6 @@ import ErrorBoundary, { ErrorHandler } from './Error/ErrorBoundary'
import WidgetPropValidator from './Error/WidgetsPropsValidator'
import Web3Provider from './Web3Provider'
const slideDown = keyframes`
to {
height: 0;
top: calc(100% - 0.25em);
}
`
const slideUp = keyframes`
from {
height: 0;
top: calc(100% - 0.25em);
}
`
const WidgetWrapper = styled.div<{ width?: number | string }>`
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
......@@ -56,6 +43,31 @@ const WidgetWrapper = styled.div<{ width?: number | string }>`
font-family: ${({ theme }) => theme.fontFamilyVariable};
}
}
`
const slideDown = keyframes`
to {
transform: translateY(calc(100% - 0.25em));
}
`
const slideUp = keyframes`
from {
transform: translateY(calc(100% - 0.25em));
}
`
const DialogWrapper = styled.div`
height: calc(100% - 0.5em);
left: 0;
margin: 0.25em;
overflow: hidden;
position: absolute;
top: 0;
width: calc(100% - 0.5em);
@supports (overflow: clip) {
overflow: clip;
}
${Modal} {
animation: ${slideUp} 0.25s ease-in-out;
......@@ -95,18 +107,19 @@ export default function Widget(props: PropsWithChildren<WidgetProps>) {
provider,
jsonRpcEndpoint,
width = 360,
dialog,
dialog: userDialog,
className,
onError,
} = props
const [wrapper, setWrapper] = useState<HTMLDivElement | null>(null)
const [dialog, setDialog] = useState<HTMLDivElement | null>(null)
return (
<StrictMode>
<I18nProvider locale={locale}>
<ThemeProvider theme={theme}>
<WidgetWrapper width={width} className={className} ref={setWrapper}>
<DialogProvider value={dialog || wrapper}>
<WidgetWrapper width={width} className={className}>
<DialogWrapper ref={setDialog} />
<DialogProvider value={userDialog || dialog}>
<ErrorBoundary onError={onError}>
<WidgetPropValidator {...props}>
<ReduxProvider store={multicallStore}>
......
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