Mp 2544 Vault deposit (#240)

* Show guages on deposit

* Finish VaultDeposit

* resolve PR comments
This commit is contained in:
Bob van der Helm 2023-06-01 15:55:42 +02:00 committed by GitHub
parent ac7e82b0a4
commit 9d09ebdf77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 341 additions and 126 deletions

View File

@ -12,29 +12,39 @@ export interface Item {
title: string title: string
renderContent: () => React.ReactNode renderContent: () => React.ReactNode
isOpen?: boolean isOpen?: boolean
subTitle?: string | React.ReactNode
toggleOpen: (index: number) => void toggleOpen: (index: number) => void
} }
export default function AccordionContent(props: Props) { export default function AccordionContent(props: Props) {
const { title, renderContent, isOpen, subTitle, toggleOpen } = props.item
const shouldShowSubTitle = subTitle && !isOpen
return ( return (
<div key={props.item.title} className='group border-b-white/10 [&:not(:last-child)]:border-b'> <div key={title} className='group border-b-white/10 [&:not(:last-child)]:border-b'>
<div <div
onClick={() => props.item.toggleOpen(props.index)} onClick={() => toggleOpen(props.index)}
className={classNames( className={classNames(
'mb-0 flex cursor-pointer items-center justify-between border-t border-white/10 bg-white/10 p-4 text-white', 'mb-0 flex cursor-pointer items-center justify-between border-t border-white/10 bg-white/10 p-4 text-white',
'group-[&:first-child]:border-t-0 group-[[open]]:border-b', 'group-[&:first-child]:border-t-0 group-[[open]]:border-b',
'[&::marker]:hidden [&::marker]:content-[""]', '[&::marker]:hidden [&::marker]:content-[""]',
props.item.isOpen && 'border-b [&:first-child]:border-t-0', isOpen && 'border-b [&:first-child]:border-t-0',
)} )}
> >
<Text>{props.item.title}</Text> <div>
<Text>{title}</Text>
{shouldShowSubTitle && (
<Text size='xs' className='mt-1 text-white/60'>
{subTitle}
</Text>
)}
</div>
<div className='block pr-1 group-[[open]]:hidden'> <div className='block pr-1 group-[[open]]:hidden'>
{props.item.isOpen ? <ChevronRight /> : <ChevronDown />} {isOpen ? <ChevronDown /> : <ChevronRight />}
</div> </div>
</div> </div>
{props.item.isOpen && ( {isOpen && <div className='bg-white/5 transition-[padding]'>{renderContent()}</div>}
<div className='bg-white/5 transition-[padding]'>{props.item.renderContent()}</div>
)}
</div> </div>
) )
} }

View File

@ -15,7 +15,7 @@ export default function AccountDetails() {
className='fixed right-4 top-[89px] w-16 rounded-base border border-white/20 bg-white/5 backdrop-blur-sticky' className='fixed right-4 top-[89px] w-16 rounded-base border border-white/20 bg-white/5 backdrop-blur-sticky'
> >
<div className='flex w-full flex-wrap justify-center py-4'> <div className='flex w-full flex-wrap justify-center py-4'>
<Gauge tooltip='Health Factor' value={0.2} icon={<Heart />} /> <Gauge tooltip='Health Factor' percentage={20} icon={<Heart />} />
<Text size='2xs' className='mb-0.5 mt-1 w-full text-center text-white/50'> <Text size='2xs' className='mb-0.5 mt-1 w-full text-center text-white/50'>
Health Health
</Text> </Text>

View File

@ -3,33 +3,42 @@ import { ReactElement, ReactNode } from 'react'
import { Tooltip } from 'components/Tooltip' import { Tooltip } from 'components/Tooltip'
import useStore from 'store' import useStore from 'store'
import { FormattedNumber } from 'components/FormattedNumber'
interface Props { interface Props {
tooltip: string | ReactNode tooltip: string | ReactNode
strokeColor?: string
strokeWidth?: number strokeWidth?: number
background?: string background?: string
diameter?: number diameter?: number
value: number percentage: number
label?: string labelClassName?: string
icon?: ReactElement icon?: ReactElement
} }
export const Gauge = ({ export const Gauge = ({
background = '#FFFFFF22', background = '#FFFFFF22',
strokeColor,
strokeWidth = 4,
diameter = 40, diameter = 40,
value = 0, percentage = 0,
tooltip, tooltip,
icon, icon,
labelClassName,
}: Props) => { }: Props) => {
const enableAnimations = useStore((s) => s.enableAnimations) const enableAnimations = useStore((s) => s.enableAnimations)
const radius = 16 const radius = 16
const percentage = value * 100
const percentageValue = percentage > 100 ? 100 : percentage < 0 ? 0 : percentage const percentageValue = percentage > 100 ? 100 : percentage < 0 ? 0 : percentage
const circlePercent = 100 - percentageValue const circlePercent = 100 - percentageValue
return ( return (
<Tooltip type='info' content={tooltip}> <Tooltip type='info' content={tooltip}>
<div className={classNames('relative', `w-${diameter / 4} h-${diameter / 4}`)}> <div
className={classNames(
'relative grid place-items-center',
`w-${diameter / 4} h-${diameter / 4}`,
)}
>
<svg <svg
viewBox='2 -2 28 36' viewBox='2 -2 28 36'
width={diameter} width={diameter}
@ -37,15 +46,17 @@ export const Gauge = ({
style={{ transform: 'rotate(-90deg)' }} style={{ transform: 'rotate(-90deg)' }}
className='absolute left-0 top-0' className='absolute left-0 top-0'
> >
<linearGradient id='gradient'> {!strokeColor && (
<stop stopColor='rgba(255, 160, 187)' offset='0%'></stop> <linearGradient id='gradient'>
<stop stopColor='rgba(186, 8, 189)' offset='50%'></stop> <stop stopColor='rgba(255, 160, 187)' offset='0%'></stop>
<stop stopColor='rgba(255, 160, 187)' offset='100%'></stop> <stop stopColor='rgba(186, 8, 189)' offset='50%'></stop>
</linearGradient> <stop stopColor='rgba(255, 160, 187)' offset='100%'></stop>
</linearGradient>
)}
<circle <circle
fill='none' fill='none'
stroke={background} stroke={background}
strokeWidth={4} strokeWidth={strokeWidth}
strokeDashoffset='0' strokeDashoffset='0'
r={radius} r={radius}
cx={radius} cx={radius}
@ -57,8 +68,8 @@ export const Gauge = ({
cx={radius} cx={radius}
cy={radius} cy={radius}
fill='transparent' fill='transparent'
stroke='url(#gradient)' stroke={strokeColor ? strokeColor : `url(#gradient)`}
strokeWidth={5} strokeWidth={strokeWidth}
strokeDashoffset={circlePercent} strokeDashoffset={circlePercent}
strokeDasharray='100' strokeDasharray='100'
pathLength='100' pathLength='100'
@ -74,6 +85,12 @@ export const Gauge = ({
{icon} {icon}
</div> </div>
)} )}
<FormattedNumber
className={classNames(labelClassName, 'text-2xs')}
amount={Math.round(percentage)}
options={{ maxDecimals: 0, minDecimals: 0 }}
animate
/>
</div> </div>
</Tooltip> </Tooltip>
) )

View File

@ -1,10 +1,10 @@
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { useState } from 'react' import { useMemo, useState } from 'react'
import Button from 'components/Button' import Button from 'components/Button'
import DisplayCurrency from 'components/DisplayCurrency'
import Divider from 'components/Divider' import Divider from 'components/Divider'
import { FormattedNumber } from 'components/FormattedNumber' import { ArrowRight, ExclamationMarkCircled } from 'components/Icons'
import { ArrowRight } from 'components/Icons'
import Slider from 'components/Slider' import Slider from 'components/Slider'
import Switch from 'components/Switch' import Switch from 'components/Switch'
import Text from 'components/Text' import Text from 'components/Text'
@ -12,89 +12,117 @@ import TokenInput from 'components/TokenInput'
import usePrice from 'hooks/usePrice' import usePrice from 'hooks/usePrice'
import { getAmount } from 'utils/accounts' import { getAmount } from 'utils/accounts'
import { BN } from 'utils/helpers' import { BN } from 'utils/helpers'
import { Gauge } from 'components/Gauge'
import useStore from 'store'
interface Props { interface Props {
primaryAmount: BigNumber
secondaryAmount: BigNumber
primaryAsset: Asset primaryAsset: Asset
secondaryAsset: Asset secondaryAsset: Asset
account: Account account: Account
onChangeDeposits: (deposits: Map<string, BigNumber>) => void isCustomRatio: boolean
onChangeIsCustomRatio: (isCustomRatio: boolean) => void
onChangePrimaryAmount: (amount: BigNumber) => void
onChangeSecondaryAmount: (amount: BigNumber) => void
toggleOpen: (index: number) => void toggleOpen: (index: number) => void
} }
export default function VaultDeposit(props: Props) { export default function VaultDeposit(props: Props) {
const [isCustomAmount, setIsCustomAmount] = useState(false) const baseCurrency = useStore((s) => s.baseCurrency)
const [percentage, setPercentage] = useState(0)
const [deposits, setDeposits] = useState<Map<string, BigNumber>>(new Map())
const availablePrimaryAmount = getAmount(props.primaryAsset.denom, props.account.deposits) const availablePrimaryAmount = getAmount(props.primaryAsset.denom, props.account.deposits)
const availableSecondaryAmount = getAmount(props.secondaryAsset.denom, props.account.deposits) const availableSecondaryAmount = getAmount(props.secondaryAsset.denom, props.account.deposits)
const primaryPrice = usePrice(props.primaryAsset.denom) const primaryPrice = usePrice(props.primaryAsset.denom)
const secondaryPrice = usePrice(props.secondaryAsset.denom) const secondaryPrice = usePrice(props.secondaryAsset.denom)
const maxAssetValueNonCustom = BN( const primaryValue = useMemo(
Math.min(availablePrimaryAmount.toNumber(), availableSecondaryAmount.toNumber()), () => props.primaryAmount.times(primaryPrice),
[props.primaryAmount, primaryPrice],
) )
const primaryMax = isCustomAmount const secondaryValue = useMemo(
? availablePrimaryAmount () => props.secondaryAmount.times(secondaryPrice),
: maxAssetValueNonCustom.dividedBy(primaryPrice) [props.secondaryAmount, secondaryPrice],
const secondaryMax = isCustomAmount )
? availableSecondaryAmount const totalValue = useMemo(
: maxAssetValueNonCustom.dividedBy(secondaryPrice) () => primaryValue.plus(secondaryValue),
[primaryValue, secondaryValue],
)
const primaryValuePercentage = useMemo(
() => primaryValue.div(totalValue).times(100).decimalPlaces(2).toNumber() || 50,
[primaryValue, totalValue],
)
const secondaryValuePercentage = useMemo(
() => new BigNumber(100).minus(primaryValuePercentage).decimalPlaces(2).toNumber() || 50,
[primaryValuePercentage],
)
const maxAssetValueNonCustom = useMemo(
() =>
BN(
Math.min(
availablePrimaryAmount.times(primaryPrice).toNumber(),
availableSecondaryAmount.times(secondaryPrice).toNumber(),
),
),
[availablePrimaryAmount, primaryPrice, availableSecondaryAmount, secondaryPrice],
)
const primaryMax = useMemo(
() =>
props.isCustomRatio ? availablePrimaryAmount : maxAssetValueNonCustom.dividedBy(primaryPrice),
[props.isCustomRatio, availablePrimaryAmount, primaryPrice, maxAssetValueNonCustom],
)
const secondaryMax = useMemo(
() =>
props.isCustomRatio
? availableSecondaryAmount
: maxAssetValueNonCustom.dividedBy(secondaryPrice),
[props.isCustomRatio, availableSecondaryAmount, secondaryPrice, maxAssetValueNonCustom],
)
const [percentage, setPercentage] = useState(
primaryValue.dividedBy(maxAssetValueNonCustom).times(100).decimalPlaces(0).toNumber(),
)
const disableInput =
(availablePrimaryAmount.isZero() || availableSecondaryAmount.isZero()) && !props.isCustomRatio
function handleSwitch() { function handleSwitch() {
const isCustomAmountNew = !isCustomAmount const isCustomRatioNew = !props.isCustomRatio
if (!isCustomAmountNew) { if (!isCustomRatioNew) {
setDeposits((deposits) => { props.onChangePrimaryAmount(BN(0))
deposits.clear() props.onChangeSecondaryAmount(BN(0))
return new Map(deposits)
})
setPercentage(0) setPercentage(0)
} }
setIsCustomAmount(isCustomAmountNew) props.onChangeIsCustomRatio(isCustomRatioNew)
} }
function onChangePrimaryDeposit(amount: BigNumber) { function onChangePrimaryDeposit(amount: BigNumber) {
onChangeDeposit(props.primaryAsset.denom, amount) if (amount.isGreaterThan(primaryMax)) {
if (!isCustomAmount) { amount = primaryMax
onChangeDeposit( }
props.secondaryAsset.denom, props.onChangePrimaryAmount(amount)
secondaryMax.multipliedBy(amount.dividedBy(primaryMax)), setPercentage(amount.dividedBy(primaryMax).times(100).decimalPlaces(0).toNumber())
) if (!props.isCustomRatio) {
props.onChangeSecondaryAmount(secondaryMax.multipliedBy(amount.dividedBy(primaryMax)))
} }
} }
function onChangeSecondaryDeposit(amount: BigNumber) { function onChangeSecondaryDeposit(amount: BigNumber) {
onChangeDeposit(props.secondaryAsset.denom, amount) if (amount.isGreaterThan(secondaryMax)) {
if (!isCustomAmount) { amount = secondaryMax
onChangeDeposit(
props.primaryAsset.denom,
primaryMax.multipliedBy(amount.dividedBy(secondaryMax)),
)
} }
} props.onChangeSecondaryAmount(amount)
setPercentage(amount.dividedBy(secondaryMax).times(100).decimalPlaces(0).toNumber())
function onChangeDeposit(denom: string, amount: BigNumber) { if (!props.isCustomRatio) {
if (amount.isZero()) { props.onChangePrimaryAmount(primaryMax.multipliedBy(amount.dividedBy(secondaryMax)))
return setDeposits((deposits) => {
deposits.delete(denom)
return new Map(deposits)
})
} }
setDeposits((deposits) => {
deposits.set(denom, amount)
props.onChangeDeposits(deposits)
return new Map(deposits)
})
} }
function onChangeSlider(value: number) { function onChangeSlider(value: number) {
setPercentage(value) setPercentage(value)
setDeposits((deposits) => { props.onChangePrimaryAmount(primaryMax.multipliedBy(value / 100))
deposits.set(props.primaryAsset.denom, primaryMax.multipliedBy(value / 100)) props.onChangeSecondaryAmount(secondaryMax.multipliedBy(value / 100))
deposits.set(props.secondaryAsset.denom, secondaryMax.multipliedBy(value / 100))
return new Map(deposits)
})
} }
function getWarningText(asset: Asset) { function getWarningText(asset: Asset) {
@ -102,39 +130,89 @@ export default function VaultDeposit(props: Props) {
} }
return ( return (
<div className='flex h-full flex-col justify-between gap-6 p-4'> <div className='flex flex-col'>
<TokenInput <div className='flex gap-4 p-4'>
onChange={onChangePrimaryDeposit} <div className='flex flex-col items-center justify-between gap-1 pb-[30px] pt-2'>
amount={deposits.get(props.primaryAsset.denom) ?? BN(0)} <Gauge
max={primaryMax} percentage={primaryValuePercentage}
maxText='Balance' tooltip={`${primaryValuePercentage}% of value is ${props.primaryAsset.symbol}`}
asset={props.primaryAsset} labelClassName='text-martian-red'
warning={primaryMax.isZero() ? getWarningText(props.primaryAsset) : undefined} diameter={32}
/> strokeColor='#FF645F'
{!isCustomAmount && <Slider value={percentage} onChange={onChangeSlider} />} strokeWidth={3}
<TokenInput />
onChange={onChangeSecondaryDeposit} <div className='h-full w-[1px] rounded-xl bg-white/10'></div>
amount={deposits.get(props.secondaryAsset.denom) ?? BN(0)} <Gauge
max={secondaryMax} percentage={secondaryValuePercentage}
maxText='Balance' tooltip={`${secondaryValuePercentage}% of value is ${props.secondaryAsset.symbol}`}
asset={props.secondaryAsset} labelClassName='text-martian-red'
warning={secondaryMax.isZero() ? getWarningText(props.secondaryAsset) : undefined} diameter={32}
/> strokeColor='#FF645F'
<Divider /> strokeWidth={3}
<div className='flex justify-between'> />
<Text className='text-white/50'>Custom amount</Text> </div>
<Switch checked={isCustomAmount} onChange={handleSwitch} name='customAmount' /> <div className='flex h-full flex-grow flex-col justify-between gap-6'>
<TokenInput
onChange={onChangePrimaryDeposit}
amount={props.primaryAmount}
max={availablePrimaryAmount}
maxText='Balance'
asset={props.primaryAsset}
warning={
availablePrimaryAmount.isZero() ? getWarningText(props.primaryAsset) : undefined
}
disabled={disableInput}
/>
{!props.isCustomRatio && (
<Slider value={percentage} onChange={onChangeSlider} disabled={disableInput} />
)}
<TokenInput
onChange={onChangeSecondaryDeposit}
amount={props.secondaryAmount}
max={availableSecondaryAmount}
maxText='Balance'
asset={props.secondaryAsset}
warning={
availableSecondaryAmount.isZero() ? getWarningText(props.secondaryAsset) : undefined
}
disabled={disableInput}
/>
</div>
</div> </div>
<div className='flex justify-between'> <div className='flex flex-col gap-6 p-4 pt-2'>
<Text className='text-white/50'>{`${props.primaryAsset.symbol}-${props.secondaryAsset.symbol} Position Value`}</Text> {disableInput ? (
<FormattedNumber amount={0} options={{ prefix: '$' }} /> <div>
<Divider />
<div className='flex items-center gap-4 py-4'>
<div className='w-5'>
<ExclamationMarkCircled className='w-5 gap-3 text-white' />
</div>
<Text size='xs'>
You currently have little to none of one asset. Toggle custom ratio to supply your
assets asymmetrically.
</Text>
</div>
<Divider />
</div>
) : (
<Divider />
)}
<div className='flex justify-between'>
<Text className='text-white/50'>Custom ratio</Text>
<Switch checked={props.isCustomRatio} onChange={handleSwitch} name='customRatio' />
</div>
<div className='flex justify-between'>
<Text className='text-white/50'>{`${props.primaryAsset.symbol}-${props.secondaryAsset.symbol} Deposit Value`}</Text>
<DisplayCurrency coin={{ denom: baseCurrency.denom, amount: totalValue.toString() }} />
</div>
<Button
onClick={() => props.toggleOpen(1)}
className='w-full'
text='Continue'
rightIcon={<ArrowRight />}
/>
</div> </div>
<Button
onClick={() => props.toggleOpen(1)}
className='w-full'
text='Continue'
rightIcon={<ArrowRight />}
/>
</div> </div>
) )
} }

View File

@ -0,0 +1,49 @@
import BigNumber from 'bignumber.js'
import DisplayCurrency from 'components/DisplayCurrency'
import usePrice from 'hooks/usePrice'
import useStore from 'store'
import { formatAmountWithSymbol } from 'utils/formatters'
interface Props {
primaryAmount: BigNumber
secondaryAmount: BigNumber
primaryAsset: Asset
secondaryAsset: Asset
}
export default function VaultDepositSubTitle(props: Props) {
const baseCurrency = useStore((s) => s.baseCurrency)
const primaryPrice = usePrice(props.primaryAsset.denom)
const secondaryPrice = usePrice(props.secondaryAsset.denom)
const primaryText = formatAmountWithSymbol({
denom: props.primaryAsset.denom,
amount: props.primaryAmount.toString(),
})
const secondaryText = formatAmountWithSymbol({
denom: props.secondaryAsset.denom,
amount: props.secondaryAmount.toString(),
})
const positionValue = props.primaryAmount
.times(primaryPrice)
.plus(props.secondaryAmount.times(secondaryPrice))
.toNumber()
const showPrimaryText = !props.primaryAmount.isZero()
const showSecondaryText = !props.secondaryAmount.isZero()
return (
<>
{showPrimaryText && primaryText}
{showPrimaryText && showSecondaryText && ' + '}
{showSecondaryText && secondaryText}
{(showPrimaryText || showSecondaryText) && (
<>
{` = `}
<DisplayCurrency coin={{ denom: baseCurrency.denom, amount: positionValue.toString() }} />
</>
)}
</>
)
}

View File

@ -1,12 +1,13 @@
import BigNumber from 'bignumber.js' import BigNumber from 'bignumber.js'
import { useState } from 'react' import { useCallback, useState } from 'react'
import Accordion from 'components/Accordion' import Accordion from 'components/Accordion'
import AccountSummary from 'components/Account/AccountSummary' import AccountSummary from 'components/Account/AccountSummary'
import VaultBorrowings from 'components/Modals/vault/VaultBorrowings'
import VaultDeposit from 'components/Modals/vault/VaultDeposit'
import VaultDepositSubTitle from 'components/Modals/vault/VaultDepositSubTitle'
import useIsOpenArray from 'hooks/useIsOpenArray' import useIsOpenArray from 'hooks/useIsOpenArray'
import { BN } from 'utils/helpers'
import VaultDeposit from './VaultDeposit'
import VaultBorrowings from './VaultBorrowings'
interface Props { interface Props {
vault: Vault vault: Vault
@ -17,7 +18,23 @@ interface Props {
export default function VaultModalContent(props: Props) { export default function VaultModalContent(props: Props) {
const [isOpen, toggleOpen] = useIsOpenArray(2, false) const [isOpen, toggleOpen] = useIsOpenArray(2, false)
const [deposits, setDeposits] = useState<Map<string, BigNumber>>(new Map()) const [primaryAmount, setPrimaryAmount] = useState<BigNumber>(BN(0))
const [secondaryAmount, setSecondaryAmount] = useState<BigNumber>(BN(0))
const [isCustomRatio, setIsCustomRatio] = useState(false)
const onChangePrimaryAmount = useCallback(
(amount: BigNumber) => setPrimaryAmount(amount),
[setPrimaryAmount],
)
const onChangeSecondaryAmount = useCallback(
(amount: BigNumber) => setSecondaryAmount(amount),
[setSecondaryAmount],
)
const onChangeIsCustomRatio = useCallback(
(isCustomRatio: boolean) => setIsCustomRatio(isCustomRatio),
[setIsCustomRatio],
)
return ( return (
<div className='flex flex-grow items-start gap-6 p-6'> <div className='flex flex-grow items-start gap-6 p-6'>
@ -26,14 +43,27 @@ export default function VaultModalContent(props: Props) {
{ {
renderContent: () => ( renderContent: () => (
<VaultDeposit <VaultDeposit
onChangeDeposits={(deposits) => setDeposits(deposits)} primaryAmount={primaryAmount}
secondaryAmount={secondaryAmount}
onChangePrimaryAmount={onChangePrimaryAmount}
onChangeSecondaryAmount={onChangeSecondaryAmount}
primaryAsset={props.primaryAsset} primaryAsset={props.primaryAsset}
secondaryAsset={props.secondaryAsset} secondaryAsset={props.secondaryAsset}
account={props.account} account={props.account}
toggleOpen={toggleOpen} toggleOpen={toggleOpen}
isCustomRatio={isCustomRatio}
onChangeIsCustomRatio={onChangeIsCustomRatio}
/> />
), ),
title: 'Deposit', title: 'Deposit',
subTitle: (
<VaultDepositSubTitle
primaryAmount={primaryAmount}
secondaryAmount={secondaryAmount}
primaryAsset={props.primaryAsset}
secondaryAsset={props.secondaryAsset}
/>
),
isOpen: isOpen[0], isOpen: isOpen[0],
toggleOpen: (index: number) => toggleOpen(index), toggleOpen: (index: number) => toggleOpen(index),
}, },

View File

@ -25,16 +25,18 @@ interface Props {
export default function NumberInput(props: Props) { export default function NumberInput(props: Props) {
const inputRef = React.useRef<HTMLInputElement>(null) const inputRef = React.useRef<HTMLInputElement>(null)
const cursorRef = React.useRef(0) const cursorRef = React.useRef(0)
// const max = props.max ? demagnify(props.max, props.asset) : undefined
const [formattedAmount, setFormattedAmount] = useState( const [formattedAmount, setFormattedAmount] = useState(
props.amount.shiftedBy(-1 * props.asset.decimals).toString(), props.amount.shiftedBy(-1 * props.asset.decimals).toString(),
) )
useEffect(() => { useEffect(() => {
if (props.amount.isZero()) return setFormattedAmount('')
setFormattedAmount( setFormattedAmount(
formatValue(props.amount.toNumber(), { formatValue(props.amount.toNumber(), {
decimals: props.asset.decimals, decimals: props.asset.decimals,
minDecimals: 0,
maxDecimals: props.asset.decimals, maxDecimals: props.asset.decimals,
thousandSeparator: false, thousandSeparator: false,
}), }),
@ -95,6 +97,11 @@ export default function NumberInput(props: Props) {
const isTooLong = props.maxLength !== undefined && numberCount > props.maxLength const isTooLong = props.maxLength !== undefined && numberCount > props.maxLength
const exceedsMaxDecimals = props.maxDecimals !== undefined && decimals > props.maxDecimals const exceedsMaxDecimals = props.maxDecimals !== undefined && decimals > props.maxDecimals
if (formattedAmount === '') {
updateValues('0', BN(0))
return
}
if (isNegative && !props.allowNegative) return if (isNegative && !props.allowNegative) return
if (isSeparator && formattedAmount.length === 1) { if (isSeparator && formattedAmount.length === 1) {
@ -140,7 +147,7 @@ export default function NumberInput(props: Props) {
<input <input
ref={inputRef} ref={inputRef}
type='text' type='text'
value={formattedAmount === '0' ? '' : formattedAmount} value={formattedAmount === '0' ? '0' : formattedAmount}
onFocus={onInputFocus} onFocus={onInputFocus}
onChange={(e) => onInputChange(e.target.value)} onChange={(e) => onInputChange(e.target.value)}
onBlur={props.onBlur} onBlur={props.onBlur}

View File

@ -83,7 +83,7 @@ export default function Slider(props: Props) {
className={classNames( className={classNames(
'relative min-h-3 w-full transition-opacity', 'relative min-h-3 w-full transition-opacity',
props.className, props.className,
props.disabled && 'pointer-events-none opacity-50', props.disabled && 'pointer-events-none',
)} )}
onMouseEnter={handleSliderRect} onMouseEnter={handleSliderRect}
> >
@ -95,15 +95,40 @@ export default function Slider(props: Props) {
className='absolute z-2 w-full cursor-pointer appearance-none bg-transparent [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:appearance-none' className='absolute z-2 w-full cursor-pointer appearance-none bg-transparent [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:appearance-none'
/> />
<div className='absolute flex w-full items-center gap-1'> <div className='absolute flex w-full items-center gap-1'>
<Mark onClick={props.onChange} value={0} sliderValue={props.value} /> <Mark
onClick={props.onChange}
value={0}
sliderValue={props.value}
disabled={props.disabled}
/>
<Track maxValue={23} sliderValue={props.value} /> <Track maxValue={23} sliderValue={props.value} />
<Mark onClick={props.onChange} value={25} sliderValue={props.value} /> <Mark
onClick={props.onChange}
value={25}
sliderValue={props.value}
disabled={props.disabled}
/>
<Track maxValue={48} sliderValue={props.value} /> <Track maxValue={48} sliderValue={props.value} />
<Mark onClick={props.onChange} value={50} sliderValue={props.value} /> <Mark
onClick={props.onChange}
value={50}
sliderValue={props.value}
disabled={props.disabled}
/>
<Track maxValue={73} sliderValue={props.value} /> <Track maxValue={73} sliderValue={props.value} />
<Mark onClick={props.onChange} value={75} sliderValue={props.value} /> <Mark
onClick={props.onChange}
value={75}
sliderValue={props.value}
disabled={props.disabled}
/>
<Track maxValue={98} sliderValue={props.value} /> <Track maxValue={98} sliderValue={props.value} />
<Mark onClick={props.onChange} value={100} sliderValue={props.value} /> <Mark
onClick={props.onChange}
value={100}
sliderValue={props.value}
disabled={props.disabled}
/>
</div> </div>
<div onMouseEnter={handleShowTooltip} onMouseLeave={handleHideTooltip}> <div onMouseEnter={handleShowTooltip} onMouseLeave={handleHideTooltip}>
<DraggableElement <DraggableElement
@ -139,6 +164,7 @@ interface MarkProps {
value: number value: number
sliderValue: number sliderValue: number
onClick: (value: number) => void onClick: (value: number) => void
disabled?: boolean
} }
function Mark(props: MarkProps) { function Mark(props: MarkProps) {
@ -148,6 +174,7 @@ function Mark(props: MarkProps) {
className={`z-20 h-3 w-3 rotate-45 rounded-xs border-[1px] border-white/20 hover:border-[2px] hover:border-white ${ className={`z-20 h-3 w-3 rotate-45 rounded-xs border-[1px] border-white/20 hover:border-[2px] hover:border-white ${
props.sliderValue >= props.value ? 'bg-martian-red hover:border-white' : 'bg-grey-medium' props.sliderValue >= props.value ? 'bg-martian-red hover:border-white' : 'bg-grey-medium'
}`} }`}
disabled={props.disabled}
></button> ></button>
) )
} }

View File

@ -45,11 +45,7 @@ export default function TokenInput(props: Props) {
return ( return (
<div <div
data-testid='token-input-component' data-testid='token-input-component'
className={classNames( className={classNames('flex w-full flex-col gap-2 transition-opacity', props.className)}
'flex w-full flex-col gap-2 transition-opacity',
props.className,
props.disabled && 'pointer-events-none opacity-50',
)}
> >
<div <div
data-testid='token-input-wrapper' data-testid='token-input-wrapper'
@ -109,9 +105,10 @@ export default function TokenInput(props: Props) {
<Button <Button
dataTestId='token-input-max-button' dataTestId='token-input-max-button'
color='tertiary' color='tertiary'
className='h-4 bg-white/20 px-1.5 py-0.5 text-2xs' className='!h-4 !min-h-0 bg-white/20 !px-2 !py-0.5 text-2xs'
variant='transparent' variant='transparent'
onClick={onMaxBtnClick} onClick={onMaxBtnClick}
disabled={props.disabled}
> >
MAX MAX
</Button> </Button>