fix: initial transitions (#3719)
* fix: rm action fade * fix: disallow stale swaps * fix: fade in buttons * fix: fade in input text * fix: standardize border handling * fix: transition token button width * fix: cleanup transitions * fix: use transition for button * chore: cleanup
This commit is contained in:
parent
cbe421ee23
commit
f50bcbdb2d
@ -5,17 +5,7 @@ import { ReactNode, useMemo } from 'react'
|
||||
import Button from './Button'
|
||||
import Row from './Row'
|
||||
|
||||
const fadeIn = keyframes`
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
animation: ${fadeIn} 0.25s ease-in;
|
||||
border-radius: ${({ theme }) => theme.borderRadius * 0.75}em;
|
||||
flex-grow: 1;
|
||||
transition: background-color 0.25s ease-out, border-radius 0.25s ease-out, flex-grow 0.25s ease-out;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Icon } from 'lib/icons'
|
||||
import styled, { Color } from 'lib/theme'
|
||||
import styled, { Color, css } from 'lib/theme'
|
||||
import { ComponentProps, forwardRef } from 'react'
|
||||
|
||||
export const BaseButton = styled.button`
|
||||
@ -15,17 +15,26 @@ export const BaseButton = styled.button`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
:enabled {
|
||||
transition: filter 0.125s linear;
|
||||
}
|
||||
|
||||
:disabled {
|
||||
cursor: initial;
|
||||
filter: saturate(0) opacity(0.4);
|
||||
}
|
||||
`
|
||||
const transitionCss = css`
|
||||
transition: background-color 0.125s linear, border-color 0.125s linear, filter 0.125s linear;
|
||||
`
|
||||
|
||||
export default styled(BaseButton)<{ color?: Color }>`
|
||||
export default styled(BaseButton)<{ color?: Color; transition?: boolean }>`
|
||||
border: 1px solid transparent;
|
||||
color: ${({ color = 'interactive', theme }) => color === 'interactive' && theme.onInteractive};
|
||||
|
||||
:enabled {
|
||||
background-color: ${({ color = 'interactive', theme }) => theme[color]};
|
||||
${({ transition = true }) => transition && transitionCss};
|
||||
}
|
||||
|
||||
:enabled:hover {
|
||||
@ -33,9 +42,8 @@ export default styled(BaseButton)<{ color?: Color }>`
|
||||
}
|
||||
|
||||
:disabled {
|
||||
border: 1px solid ${({ theme }) => theme.outline};
|
||||
border-color: ${({ theme }) => theme.outline};
|
||||
color: ${({ theme }) => theme.secondary};
|
||||
cursor: initial;
|
||||
}
|
||||
`
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { loadingOpacity } from 'lib/css/loading'
|
||||
import styled, { css } from 'lib/theme'
|
||||
import { transparentize } from 'polished'
|
||||
import { ChangeEvent, forwardRef, HTMLProps, useCallback } from 'react'
|
||||
|
||||
const Input = styled.input`
|
||||
@ -34,6 +36,16 @@ const Input = styled.input`
|
||||
::placeholder {
|
||||
color: ${({ theme }) => theme.secondary};
|
||||
}
|
||||
|
||||
:enabled {
|
||||
transition: color 0.125s linear;
|
||||
}
|
||||
|
||||
:disabled {
|
||||
// Overrides WebKit's override of input:disabled color.
|
||||
-webkit-text-fill-color: ${({ theme }) => transparentize(1 - loadingOpacity, theme.primary)};
|
||||
color: ${({ theme }) => transparentize(1 - loadingOpacity, theme.primary)};
|
||||
}
|
||||
`
|
||||
|
||||
export default Input
|
||||
|
@ -80,7 +80,7 @@ export default function Input({ disabled, focused }: InputProps) {
|
||||
// extract eagerly in case of reversal
|
||||
usePrefetchCurrencyColor(inputCurrency)
|
||||
|
||||
const isRouteLoading = tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
|
||||
const isRouteLoading = disabled || tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
|
||||
const isDependentField = !useIsSwapFieldIndependent(Field.INPUT)
|
||||
const isLoading = isRouteLoading && isDependentField
|
||||
|
||||
|
@ -46,7 +46,7 @@ export default function Output({ disabled, focused, children }: PropsWithChildre
|
||||
const [swapOutputAmount, updateSwapOutputAmount] = useSwapAmount(Field.OUTPUT)
|
||||
const [swapOutputCurrency, updateSwapOutputCurrency] = useSwapCurrency(Field.OUTPUT)
|
||||
|
||||
const isRouteLoading = tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
|
||||
const isRouteLoading = disabled || tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
|
||||
const isDependentField = !useIsSwapFieldIndependent(Field.OUTPUT)
|
||||
const isLoading = isRouteLoading && isDependentField
|
||||
|
||||
|
@ -33,6 +33,7 @@ const Overlay = styled.div`
|
||||
|
||||
const StyledReverseButton = styled(Button)<{ turns: number }>`
|
||||
border-radius: ${({ theme }) => theme.borderRadius * 0.75}em;
|
||||
color: ${({ theme }) => theme.primary};
|
||||
height: 2.5em;
|
||||
position: relative;
|
||||
width: 2.5em;
|
||||
|
@ -14,6 +14,7 @@ import { TransactionType } from 'lib/state/transactions'
|
||||
import { useTheme } from 'lib/theme'
|
||||
import { isAnimating } from 'lib/utils/animations'
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { TradeState } from 'state/routing/types'
|
||||
import invariant from 'tiny-invariant'
|
||||
|
||||
import ActionButton, { ActionButtonProps } from '../../ActionButton'
|
||||
@ -152,13 +153,17 @@ export default memo(function SwapButton({ disabled }: SwapButtonProps) {
|
||||
if (disableSwap) {
|
||||
return { disabled: true }
|
||||
} else if (wrapType === WrapType.NONE) {
|
||||
return approvalAction ? { action: approvalAction } : { onClick: () => setOpen(true) }
|
||||
return approvalAction
|
||||
? { action: approvalAction }
|
||||
: trade.state === TradeState.VALID
|
||||
? { onClick: () => setOpen(true) }
|
||||
: { disabled: true }
|
||||
} else {
|
||||
return isPending
|
||||
? { action: { message: <Trans>Confirm in your wallet</Trans>, icon: Spinner } }
|
||||
: { onClick: onWrap }
|
||||
}
|
||||
}, [approvalAction, disableSwap, isPending, onWrap, wrapType])
|
||||
}, [approvalAction, disableSwap, isPending, onWrap, trade.state, wrapType])
|
||||
const Label = useCallback(() => {
|
||||
switch (wrapType) {
|
||||
case WrapType.UNWRAP:
|
||||
|
@ -1,29 +1,33 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { ChevronDown } from 'lib/icons'
|
||||
import styled, { ThemedText } from 'lib/theme'
|
||||
import { useMemo } from 'react'
|
||||
import styled, { css, ThemedText } from 'lib/theme'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import Button from '../Button'
|
||||
import Row from '../Row'
|
||||
import TokenImg from '../TokenImg'
|
||||
|
||||
const StyledTokenButton = styled(Button)<{ empty?: boolean }>`
|
||||
const transitionCss = css`
|
||||
transition: background-color 0.125s linear, border-color 0.125s linear, filter 0.125s linear, width 0.125s ease-out;
|
||||
`
|
||||
|
||||
const StyledTokenButton = styled(Button)`
|
||||
border-radius: ${({ theme }) => theme.borderRadius}em;
|
||||
padding: 0.25em;
|
||||
padding-left: ${({ empty }) => (empty ? 0.75 : 0.25)}em;
|
||||
|
||||
:disabled {
|
||||
// prevents border from expanding the button's box size
|
||||
padding: calc(0.25em - 1px);
|
||||
padding-left: calc(${({ empty }) => (empty ? 0.75 : 0.25)}em - 1px);
|
||||
:enabled {
|
||||
${({ transition }) => transition && transitionCss};
|
||||
}
|
||||
`
|
||||
|
||||
const TokenButtonRow = styled(Row)<{ collapsed: boolean }>`
|
||||
const TokenButtonRow = styled(Row)<{ empty: boolean; collapsed: boolean }>`
|
||||
float: right;
|
||||
height: 1.2em;
|
||||
// max-width must have an absolute value in order to transition.
|
||||
max-width: ${({ collapsed }) => (collapsed ? '1.2em' : '12em')};
|
||||
padding-left: ${({ empty }) => empty && 0.5}em;
|
||||
width: fit-content;
|
||||
overflow: hidden;
|
||||
transition: max-width 0.25s linear;
|
||||
|
||||
@ -42,10 +46,42 @@ interface TokenButtonProps {
|
||||
export default function TokenButton({ value, collapsed, disabled, onClick }: TokenButtonProps) {
|
||||
const buttonBackgroundColor = useMemo(() => (value ? 'interactive' : 'accent'), [value])
|
||||
const contentColor = useMemo(() => (value || disabled ? 'onInteractive' : 'onAccent'), [value, disabled])
|
||||
|
||||
// Transition the button only if transitioning from a disabled state.
|
||||
// This makes initialization cleaner without adding distracting UX to normal swap flows.
|
||||
const [shouldTransition, setShouldTransition] = useState(disabled)
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
setShouldTransition(true)
|
||||
}
|
||||
}, [disabled])
|
||||
|
||||
// width must have an absolute value in order to transition, so it is taken from the row ref.
|
||||
const [row, setRow] = useState<HTMLDivElement | null>(null)
|
||||
const style = useMemo(() => {
|
||||
if (!shouldTransition) return
|
||||
return { width: row ? row.clientWidth + /* padding= */ 8 + /* border= */ 2 : undefined }
|
||||
}, [row, shouldTransition])
|
||||
|
||||
return (
|
||||
<StyledTokenButton onClick={onClick} empty={!value} color={buttonBackgroundColor} disabled={disabled}>
|
||||
<StyledTokenButton
|
||||
onClick={onClick}
|
||||
color={buttonBackgroundColor}
|
||||
disabled={disabled}
|
||||
style={style}
|
||||
transition={shouldTransition}
|
||||
onTransitionEnd={() => setShouldTransition(false)}
|
||||
>
|
||||
<ThemedText.ButtonLarge color={contentColor}>
|
||||
<TokenButtonRow gap={0.4} collapsed={Boolean(value) && collapsed}>
|
||||
<TokenButtonRow
|
||||
gap={0.4}
|
||||
empty={!value}
|
||||
collapsed={collapsed}
|
||||
// ref is used to set an absolute width, so it must be reset for each value passed.
|
||||
// To force this, value?.symbol is passed as a key.
|
||||
ref={setRow}
|
||||
key={value?.symbol}
|
||||
>
|
||||
{value ? (
|
||||
<>
|
||||
<TokenImg token={value} size={1.2} />
|
||||
|
@ -31,7 +31,7 @@ const WidgetWrapper = styled.div<{ width?: number | string }>`
|
||||
font-size: 16px;
|
||||
font-smooth: always;
|
||||
font-variant: none;
|
||||
height: 356px;
|
||||
height: 360px;
|
||||
min-width: 300px;
|
||||
padding: 0.25em;
|
||||
position: relative;
|
||||
|
@ -9,6 +9,6 @@ export const loadingCss = css`
|
||||
|
||||
// need to use isLoading as `loading` is a reserved prop
|
||||
export const loadingTransitionCss = css<{ isLoading: boolean }>`
|
||||
${({ isLoading }) => isLoading && loadingCss};
|
||||
transition: opacity ${({ isLoading }) => (isLoading ? 0 : 0.2)}s ease-in-out;
|
||||
opacity: ${({ isLoading }) => isLoading && loadingOpacity};
|
||||
transition: color 0.125s linear, opacity ${({ isLoading }) => (isLoading ? 0 : 0.25)}s ease-in-out;
|
||||
`
|
||||
|
Loading…
Reference in New Issue
Block a user