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
renderContent: () => React.ReactNode
isOpen?: boolean
subTitle?: string | React.ReactNode
toggleOpen: (index: number) => void
}
export default function AccordionContent(props: Props) {
const { title, renderContent, isOpen, subTitle, toggleOpen } = props.item
const shouldShowSubTitle = subTitle && !isOpen
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
onClick={() => props.item.toggleOpen(props.index)}
onClick={() => toggleOpen(props.index)}
className={classNames(
'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',
'[&::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'>
{props.item.isOpen ? <ChevronRight /> : <ChevronDown />}
{isOpen ? <ChevronDown /> : <ChevronRight />}
</div>
</div>
{props.item.isOpen && (
<div className='bg-white/5 transition-[padding]'>{props.item.renderContent()}</div>
)}
{isOpen && <div className='bg-white/5 transition-[padding]'>{renderContent()}</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'
>
<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'>
Health
</Text>

View File

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

View File

@ -1,10 +1,10 @@
import BigNumber from 'bignumber.js'
import { useState } from 'react'
import { useMemo, useState } from 'react'
import Button from 'components/Button'
import DisplayCurrency from 'components/DisplayCurrency'
import Divider from 'components/Divider'
import { FormattedNumber } from 'components/FormattedNumber'
import { ArrowRight } from 'components/Icons'
import { ArrowRight, ExclamationMarkCircled } from 'components/Icons'
import Slider from 'components/Slider'
import Switch from 'components/Switch'
import Text from 'components/Text'
@ -12,89 +12,117 @@ import TokenInput from 'components/TokenInput'
import usePrice from 'hooks/usePrice'
import { getAmount } from 'utils/accounts'
import { BN } from 'utils/helpers'
import { Gauge } from 'components/Gauge'
import useStore from 'store'
interface Props {
primaryAmount: BigNumber
secondaryAmount: BigNumber
primaryAsset: Asset
secondaryAsset: Asset
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
}
export default function VaultDeposit(props: Props) {
const [isCustomAmount, setIsCustomAmount] = useState(false)
const [percentage, setPercentage] = useState(0)
const [deposits, setDeposits] = useState<Map<string, BigNumber>>(new Map())
const baseCurrency = useStore((s) => s.baseCurrency)
const availablePrimaryAmount = getAmount(props.primaryAsset.denom, props.account.deposits)
const availableSecondaryAmount = getAmount(props.secondaryAsset.denom, props.account.deposits)
const primaryPrice = usePrice(props.primaryAsset.denom)
const secondaryPrice = usePrice(props.secondaryAsset.denom)
const maxAssetValueNonCustom = BN(
Math.min(availablePrimaryAmount.toNumber(), availableSecondaryAmount.toNumber()),
const primaryValue = useMemo(
() => props.primaryAmount.times(primaryPrice),
[props.primaryAmount, primaryPrice],
)
const primaryMax = isCustomAmount
? availablePrimaryAmount
: maxAssetValueNonCustom.dividedBy(primaryPrice)
const secondaryMax = isCustomAmount
? availableSecondaryAmount
: maxAssetValueNonCustom.dividedBy(secondaryPrice)
const secondaryValue = useMemo(
() => props.secondaryAmount.times(secondaryPrice),
[props.secondaryAmount, secondaryPrice],
)
const totalValue = useMemo(
() => 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() {
const isCustomAmountNew = !isCustomAmount
if (!isCustomAmountNew) {
setDeposits((deposits) => {
deposits.clear()
return new Map(deposits)
})
const isCustomRatioNew = !props.isCustomRatio
if (!isCustomRatioNew) {
props.onChangePrimaryAmount(BN(0))
props.onChangeSecondaryAmount(BN(0))
setPercentage(0)
}
setIsCustomAmount(isCustomAmountNew)
props.onChangeIsCustomRatio(isCustomRatioNew)
}
function onChangePrimaryDeposit(amount: BigNumber) {
onChangeDeposit(props.primaryAsset.denom, amount)
if (!isCustomAmount) {
onChangeDeposit(
props.secondaryAsset.denom,
secondaryMax.multipliedBy(amount.dividedBy(primaryMax)),
)
if (amount.isGreaterThan(primaryMax)) {
amount = primaryMax
}
props.onChangePrimaryAmount(amount)
setPercentage(amount.dividedBy(primaryMax).times(100).decimalPlaces(0).toNumber())
if (!props.isCustomRatio) {
props.onChangeSecondaryAmount(secondaryMax.multipliedBy(amount.dividedBy(primaryMax)))
}
}
function onChangeSecondaryDeposit(amount: BigNumber) {
onChangeDeposit(props.secondaryAsset.denom, amount)
if (!isCustomAmount) {
onChangeDeposit(
props.primaryAsset.denom,
primaryMax.multipliedBy(amount.dividedBy(secondaryMax)),
)
if (amount.isGreaterThan(secondaryMax)) {
amount = secondaryMax
}
}
function onChangeDeposit(denom: string, amount: BigNumber) {
if (amount.isZero()) {
return setDeposits((deposits) => {
deposits.delete(denom)
return new Map(deposits)
})
props.onChangeSecondaryAmount(amount)
setPercentage(amount.dividedBy(secondaryMax).times(100).decimalPlaces(0).toNumber())
if (!props.isCustomRatio) {
props.onChangePrimaryAmount(primaryMax.multipliedBy(amount.dividedBy(secondaryMax)))
}
setDeposits((deposits) => {
deposits.set(denom, amount)
props.onChangeDeposits(deposits)
return new Map(deposits)
})
}
function onChangeSlider(value: number) {
setPercentage(value)
setDeposits((deposits) => {
deposits.set(props.primaryAsset.denom, primaryMax.multipliedBy(value / 100))
deposits.set(props.secondaryAsset.denom, secondaryMax.multipliedBy(value / 100))
return new Map(deposits)
})
props.onChangePrimaryAmount(primaryMax.multipliedBy(value / 100))
props.onChangeSecondaryAmount(secondaryMax.multipliedBy(value / 100))
}
function getWarningText(asset: Asset) {
@ -102,39 +130,89 @@ export default function VaultDeposit(props: Props) {
}
return (
<div className='flex h-full flex-col justify-between gap-6 p-4'>
<TokenInput
onChange={onChangePrimaryDeposit}
amount={deposits.get(props.primaryAsset.denom) ?? BN(0)}
max={primaryMax}
maxText='Balance'
asset={props.primaryAsset}
warning={primaryMax.isZero() ? getWarningText(props.primaryAsset) : undefined}
/>
{!isCustomAmount && <Slider value={percentage} onChange={onChangeSlider} />}
<TokenInput
onChange={onChangeSecondaryDeposit}
amount={deposits.get(props.secondaryAsset.denom) ?? BN(0)}
max={secondaryMax}
maxText='Balance'
asset={props.secondaryAsset}
warning={secondaryMax.isZero() ? getWarningText(props.secondaryAsset) : undefined}
/>
<Divider />
<div className='flex justify-between'>
<Text className='text-white/50'>Custom amount</Text>
<Switch checked={isCustomAmount} onChange={handleSwitch} name='customAmount' />
<div className='flex flex-col'>
<div className='flex gap-4 p-4'>
<div className='flex flex-col items-center justify-between gap-1 pb-[30px] pt-2'>
<Gauge
percentage={primaryValuePercentage}
tooltip={`${primaryValuePercentage}% of value is ${props.primaryAsset.symbol}`}
labelClassName='text-martian-red'
diameter={32}
strokeColor='#FF645F'
strokeWidth={3}
/>
<div className='h-full w-[1px] rounded-xl bg-white/10'></div>
<Gauge
percentage={secondaryValuePercentage}
tooltip={`${secondaryValuePercentage}% of value is ${props.secondaryAsset.symbol}`}
labelClassName='text-martian-red'
diameter={32}
strokeColor='#FF645F'
strokeWidth={3}
/>
</div>
<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 className='flex justify-between'>
<Text className='text-white/50'>{`${props.primaryAsset.symbol}-${props.secondaryAsset.symbol} Position Value`}</Text>
<FormattedNumber amount={0} options={{ prefix: '$' }} />
<div className='flex flex-col gap-6 p-4 pt-2'>
{disableInput ? (
<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>
<Button
onClick={() => props.toggleOpen(1)}
className='w-full'
text='Continue'
rightIcon={<ArrowRight />}
/>
</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 { useState } from 'react'
import { useCallback, useState } from 'react'
import Accordion from 'components/Accordion'
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 VaultDeposit from './VaultDeposit'
import VaultBorrowings from './VaultBorrowings'
import { BN } from 'utils/helpers'
interface Props {
vault: Vault
@ -17,7 +18,23 @@ interface Props {
export default function VaultModalContent(props: Props) {
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 (
<div className='flex flex-grow items-start gap-6 p-6'>
@ -26,14 +43,27 @@ export default function VaultModalContent(props: Props) {
{
renderContent: () => (
<VaultDeposit
onChangeDeposits={(deposits) => setDeposits(deposits)}
primaryAmount={primaryAmount}
secondaryAmount={secondaryAmount}
onChangePrimaryAmount={onChangePrimaryAmount}
onChangeSecondaryAmount={onChangeSecondaryAmount}
primaryAsset={props.primaryAsset}
secondaryAsset={props.secondaryAsset}
account={props.account}
toggleOpen={toggleOpen}
isCustomRatio={isCustomRatio}
onChangeIsCustomRatio={onChangeIsCustomRatio}
/>
),
title: 'Deposit',
subTitle: (
<VaultDepositSubTitle
primaryAmount={primaryAmount}
secondaryAmount={secondaryAmount}
primaryAsset={props.primaryAsset}
secondaryAsset={props.secondaryAsset}
/>
),
isOpen: isOpen[0],
toggleOpen: (index: number) => toggleOpen(index),
},

View File

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

View File

@ -83,7 +83,7 @@ export default function Slider(props: Props) {
className={classNames(
'relative min-h-3 w-full transition-opacity',
props.className,
props.disabled && 'pointer-events-none opacity-50',
props.disabled && 'pointer-events-none',
)}
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'
/>
<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} />
<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} />
<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} />
<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} />
<Mark onClick={props.onChange} value={100} sliderValue={props.value} />
<Mark
onClick={props.onChange}
value={100}
sliderValue={props.value}
disabled={props.disabled}
/>
</div>
<div onMouseEnter={handleShowTooltip} onMouseLeave={handleHideTooltip}>
<DraggableElement
@ -139,6 +164,7 @@ interface MarkProps {
value: number
sliderValue: number
onClick: (value: number) => void
disabled?: boolean
}
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 ${
props.sliderValue >= props.value ? 'bg-martian-red hover:border-white' : 'bg-grey-medium'
}`}
disabled={props.disabled}
></button>
)
}

View File

@ -45,11 +45,7 @@ export default function TokenInput(props: Props) {
return (
<div
data-testid='token-input-component'
className={classNames(
'flex w-full flex-col gap-2 transition-opacity',
props.className,
props.disabled && 'pointer-events-none opacity-50',
)}
className={classNames('flex w-full flex-col gap-2 transition-opacity', props.className)}
>
<div
data-testid='token-input-wrapper'
@ -109,9 +105,10 @@ export default function TokenInput(props: Props) {
<Button
dataTestId='token-input-max-button'
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'
onClick={onMaxBtnClick}
disabled={props.disabled}
>
MAX
</Button>