Mp 3250 update health bars (#403)
* [Story] Add HealthBar * Update tooltip * Fixes after rebase * Fixes after rebase * Fixes after rebase * Finish health bar + gauge * Finish health bar + gauge * Finish health bar + gauge * feat: added an svg mask to the HealthBar * fix: added transitions * tidy: value sanity * fix: fixed the AccountStats * tidy: design update * fix: div adjustments * update healthguagepercentage function * make tooltiparrow responsive --------- Co-authored-by: Linkie Link <linkielink.dev@gmail.com>
This commit is contained in:
parent
f64d2a78ff
commit
e15de26efd
@ -26,7 +26,11 @@ jest.mock('hooks/broadcast/useDepositVault', () => jest.fn(() => ({ actions: []
|
||||
|
||||
jest.mock('components/DisplayCurrency')
|
||||
|
||||
jest.mock('hooks/useHealthComputer', () => jest.fn(() => ({ computeMaxBorrowAmount: () => {} })))
|
||||
jest.mock('hooks/useHealthComputer', () =>
|
||||
jest.fn(() => ({
|
||||
computeMaxBorrowAmount: () => {},
|
||||
})),
|
||||
)
|
||||
|
||||
const mockedDisplayCurrency = jest
|
||||
.mocked(DisplayCurrency)
|
||||
@ -49,13 +53,6 @@ describe('<VaultBorrowings />', () => {
|
||||
const defaultProps: VaultBorrowingsProps = {
|
||||
primaryAsset: ASSETS[0],
|
||||
secondaryAsset: ASSETS[1],
|
||||
updatedAccount: {
|
||||
id: 'test',
|
||||
deposits: [],
|
||||
debts: [],
|
||||
vaults: [],
|
||||
lends: [],
|
||||
},
|
||||
vault: mockedVault,
|
||||
borrowings: [],
|
||||
deposits: [],
|
||||
|
@ -6,8 +6,8 @@ import AccountComposition from 'components/Account/AccountComposition'
|
||||
import Card from 'components/Card'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { Gauge } from 'components/Gauge'
|
||||
import { ChevronLeft, ChevronRight, Heart } from 'components/Icons'
|
||||
import { HealthGauge } from 'components/HealthGauge'
|
||||
import { ChevronLeft, ChevronRight } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import { ORACLE_DENOM } from 'constants/oracle'
|
||||
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
||||
@ -83,7 +83,7 @@ function AccountDetails(props: Props) {
|
||||
'flex flex-wrap w-16 group/details relative',
|
||||
'border rounded-base border-white/20',
|
||||
'bg-white/5 backdrop-blur-sticky transition-colors duration-300',
|
||||
'hover:bg-white/20 hover:cursor-pointer ',
|
||||
'hover:bg-white/10 hover:cursor-pointer ',
|
||||
)}
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
>
|
||||
@ -97,18 +97,10 @@ function AccountDetails(props: Props) {
|
||||
{isExpanded ? <ChevronRight className='w-2' /> : <ChevronLeft className='w-2' />}
|
||||
</div>
|
||||
<div className='flex flex-wrap justify-center w-full py-4'>
|
||||
<Gauge tooltip='Health Factor' percentage={health} icon={<Heart />} />
|
||||
<HealthGauge health={health} />
|
||||
<Text size='2xs' className='mb-0.5 mt-1 w-full text-center text-white/50'>
|
||||
Health
|
||||
Account Health
|
||||
</Text>
|
||||
<div className='flex'>
|
||||
<FormattedNumber
|
||||
className={'w-full text-center text-xs'}
|
||||
amount={health}
|
||||
options={{ maxDecimals: 0, minDecimals: 0, suffix: '%' }}
|
||||
animate
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='w-full py-4 border-t border-white/20'>
|
||||
<Text size='2xs' className='mb-0.5 w-full text-center text-white/50'>
|
||||
|
@ -1,62 +0,0 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { Heart } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
|
||||
interface Props {
|
||||
health: number
|
||||
hasLabel?: boolean
|
||||
classNames?: string
|
||||
}
|
||||
|
||||
export default function AccountHealth(props: Props) {
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
const healthBarWidth = (props.health / 100) * 53
|
||||
|
||||
return (
|
||||
<div className={classNames('flex items-center gap-1.5', props.classNames)}>
|
||||
<div className='flex flex-auto w-3 h-4'>
|
||||
<Heart />
|
||||
</div>
|
||||
{props.hasLabel && (
|
||||
<Text size='xs' className='pr-0.5 text-white/70'>
|
||||
Health
|
||||
</Text>
|
||||
)}
|
||||
<div className='flex flex-shrink'>
|
||||
<Tooltip content={`Account Health: ${props.health}%`} type='info'>
|
||||
<svg width='100%' viewBox='0 0 53 4' fill='none' xmlns='http://www.w3.org/2000/svg'>
|
||||
<rect width='53' height='4' rx='2' fill='white' fillOpacity='0.1' />
|
||||
<rect
|
||||
width={healthBarWidth}
|
||||
height='4'
|
||||
rx='2'
|
||||
fill='url(#bar)'
|
||||
style={{
|
||||
transition: reduceMotion ? 'none' : 'width 1s ease',
|
||||
}}
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id='bar'
|
||||
x1='0%'
|
||||
y1='0%'
|
||||
x2='100%'
|
||||
y2='0%'
|
||||
gradientUnits='userSpaceOnUse'
|
||||
>
|
||||
<stop stopColor='#FF645F' />
|
||||
<stop offset='0.5' stopColor='#FFB1AE' />
|
||||
<stop offset='1' stopColor='#FFD5D3' />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
@ -1,9 +1,10 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import AccountHealth from 'components/Account/AccountHealth'
|
||||
import HealthBar from 'components/Account/HealthBar'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import { ArrowChartLineUp } from 'components/Icons'
|
||||
import { ArrowChartLineUp, Heart } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
import { ORACLE_DENOM } from 'constants/oracle'
|
||||
import useBorrowMarketAssetsTableData from 'hooks/useBorrowMarketAssetsTableData'
|
||||
import useHealthComputer from 'hooks/useHealthComputer'
|
||||
@ -40,21 +41,27 @@ export default function AccountStats(props: Props) {
|
||||
[props.account, borrowAssetsData, lendingAssetsData, prices],
|
||||
)
|
||||
return (
|
||||
<div className='flex-wrap w-full'>
|
||||
<div className='flex flex-wrap w-full'>
|
||||
<DisplayCurrency
|
||||
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, positionBalance)}
|
||||
className='w-full text-xl'
|
||||
/>
|
||||
<div className='flex items-center justify-between w-full mt-1'>
|
||||
<span className='flex flex-wrap'>
|
||||
<div className='flex items-center'>
|
||||
<ArrowChartLineUp className='w-4 mr-1' />
|
||||
<FormattedNumber
|
||||
className='text-xs text-white/70'
|
||||
amount={apr.toNumber()}
|
||||
options={{ prefix: 'APR: ', suffix: '%', minDecimals: 2, maxDecimals: 2 }}
|
||||
/>
|
||||
</span>
|
||||
<AccountHealth health={health} classNames='w-[140px]' hasLabel />
|
||||
</div>
|
||||
<div className='flex items-center gap-1'>
|
||||
<Heart className='w-3' />
|
||||
<Text size='xs' className='w-auto mr-1 text-white/70'>
|
||||
Health
|
||||
</Text>
|
||||
<HealthBar health={health} classNames='w-[92px] h-0.5' hasLabel />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { useMemo } from 'react'
|
||||
import { HTMLAttributes, useMemo } from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import Accordion from 'components/Accordion'
|
||||
import AccountBalancesTable from 'components/Account/AccountBalancesTable'
|
||||
import AccountComposition from 'components/Account/AccountComposition'
|
||||
import AccountHealth from 'components/Account/AccountHealth'
|
||||
import HealthBar from 'components/Account/HealthBar'
|
||||
import Card from 'components/Card'
|
||||
import DisplayCurrency from 'components/DisplayCurrency'
|
||||
import { FormattedNumber } from 'components/FormattedNumber'
|
||||
import Text from 'components/Text'
|
||||
import { BN_ZERO } from 'constants/math'
|
||||
import { ORACLE_DENOM } from 'constants/oracle'
|
||||
@ -17,6 +17,7 @@ import useLendingMarketAssetsTableData from 'hooks/useLendingMarketAssetsTableDa
|
||||
import usePrices from 'hooks/usePrices'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { calculateAccountBalanceValue, calculateAccountLeverage } from 'utils/accounts'
|
||||
import { formatLeverage } from 'utils/formatters'
|
||||
|
||||
interface Props {
|
||||
account: Account
|
||||
@ -49,24 +50,19 @@ export default function AccountSummary(props: Props) {
|
||||
if (!props.account) return null
|
||||
|
||||
return (
|
||||
<div className='h-[546px] min-w-[345px] basis-[345px] overflow-y-scroll scrollbar-hide'>
|
||||
<div className='h-[546px] min-w-[370px] basis-[370px] overflow-y-scroll scrollbar-hide'>
|
||||
<Card className='mb-4 h-min min-w-fit bg-white/10' contentClassName='flex'>
|
||||
<Item title='Networth'>
|
||||
<Item label='Net worth' classNames='flex-1'>
|
||||
<DisplayCurrency
|
||||
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, accountBalance)}
|
||||
className='text-xs'
|
||||
className='text-sm'
|
||||
/>
|
||||
</Item>
|
||||
<Item title='Leverage'>
|
||||
<FormattedNumber
|
||||
className='text-xs'
|
||||
amount={leverage.toNumber()}
|
||||
options={{ minDecimals: 2, maxDecimals: 2, suffix: 'x' }}
|
||||
animate
|
||||
/>
|
||||
<Item label='Leverage' classNames='flex-1'>
|
||||
<Text size='sm'>{formatLeverage(leverage.toNumber())}</Text>
|
||||
</Item>
|
||||
<Item title='Account Health'>
|
||||
<AccountHealth health={health} />
|
||||
<Item label='Account health'>
|
||||
<HealthBar health={health} classNames='w-[184px] h-1' />
|
||||
</Item>
|
||||
</Card>
|
||||
<Accordion
|
||||
@ -100,16 +96,24 @@ export default function AccountSummary(props: Props) {
|
||||
)
|
||||
}
|
||||
|
||||
function Item(props: React.HTMLAttributes<HTMLDivElement>) {
|
||||
interface ItemProps extends HTMLAttributes<HTMLDivElement> {
|
||||
label: string
|
||||
classNames?: string
|
||||
}
|
||||
|
||||
function Item(props: ItemProps) {
|
||||
return (
|
||||
<div
|
||||
className='flex flex-wrap items-center gap-1 px-4 py-2 border-r border-r-white/10'
|
||||
className={classNames(
|
||||
'flex flex-col justify-around px-3 py-1 border-r border-r-white/10',
|
||||
props.classNames,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<Text size='xs' className='w-full text-white/50'>
|
||||
{props.title}
|
||||
<Text size='2xs' className='text-white/50 whitespace-nowrap'>
|
||||
{props.label}
|
||||
</Text>
|
||||
{props.children}
|
||||
<div className='flex h-4.5 w-full'>{props.children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
53
src/components/Account/HealthBar.tsx
Normal file
53
src/components/Account/HealthBar.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import useHealthColorAndLabel from 'hooks/useHealthColorAndLabel'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
|
||||
interface Props {
|
||||
health: number
|
||||
hasLabel?: boolean
|
||||
classNames?: string
|
||||
}
|
||||
|
||||
function calculateHealth(health: number) {
|
||||
const firstBarEnd = 43
|
||||
const secondBarStart = 46
|
||||
const secondBarEnd = 93
|
||||
const thirdBarStart = 96
|
||||
const thirdBarEnd = 184
|
||||
|
||||
if (health <= 10) return (firstBarEnd / 10) * health
|
||||
if (health <= 30) return ((secondBarEnd - secondBarStart) / 20) * (health - 10) + secondBarStart
|
||||
if (health <= 100) return ((thirdBarEnd - thirdBarStart) / 70) * (health - 30) + thirdBarStart
|
||||
}
|
||||
|
||||
export default function HealthBar(props: Props) {
|
||||
const { health } = props
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
const [color, label] = useHealthColorAndLabel(health, 'fill-')
|
||||
const width = calculateHealth(health)
|
||||
|
||||
return (
|
||||
<Tooltip content={label} type='info' className='flex items-center w-full'>
|
||||
<div className={classNames('flex max-w-[184px] max-h-1', props.classNames)}>
|
||||
<svg version='1.1' xmlns='http://www.w3.org/2000/svg' x='0px' y='0px' viewBox='0 0 184 4'>
|
||||
<mask id='healthBarMask'>
|
||||
<path fill='#FFFFFF' d='M0,2c0-1.1,0.9-2,2-2h41.6v4H2C0.9,4,0,3.1,0,2z' />
|
||||
<rect x='46' fill='#FFFFFF' width='47' height='4' />
|
||||
<path fill='#FFFFFF' d='M95.5,0H182c1.1,0,2,0.9,2,2s-0.9,2-2,2H95.5V0z' />
|
||||
</mask>
|
||||
<rect className='fill-white/10' width='184' height='4' mask='url(#healthBarMask)' />
|
||||
<rect
|
||||
className={classNames(color, !reduceMotion && 'transition-all duration-500')}
|
||||
width={width}
|
||||
height='4'
|
||||
mask='url(#healthBarMask)'
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
96
src/components/HealthGauge.tsx
Normal file
96
src/components/HealthGauge.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
import classNames from 'classnames'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import { Heart } from 'components/Icons'
|
||||
import { Tooltip } from 'components/Tooltip'
|
||||
import { DEFAULT_SETTINGS } from 'constants/defaultSettings'
|
||||
import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import useHealthColorAndLabel from 'hooks/useHealthColorAndLabel'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import { computeHealthGaugePercentage } from 'utils/accounts'
|
||||
|
||||
interface Props {
|
||||
diameter?: number
|
||||
health: number
|
||||
}
|
||||
|
||||
const RADIUS = 350
|
||||
const ROTATION = {
|
||||
rotate: '-126deg',
|
||||
transformOrigin: 'center',
|
||||
}
|
||||
|
||||
export const HealthGauge = ({ diameter = 40, health = 0 }: Props) => {
|
||||
const [color, label] = useHealthColorAndLabel(health, 'text-')
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
const percentage = useMemo(() => computeHealthGaugePercentage(health), [health])
|
||||
|
||||
return (
|
||||
<Tooltip type='info' content={label}>
|
||||
<div
|
||||
className={classNames(
|
||||
'relative grid place-items-center',
|
||||
`w-${diameter / 4} h-${diameter / 4}`,
|
||||
)}
|
||||
>
|
||||
<Heart className='text-white/50' width={20} />
|
||||
<svg
|
||||
version='1.1'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
x='0px'
|
||||
y='0px'
|
||||
viewBox='0 0 700 700'
|
||||
className='absolute'
|
||||
>
|
||||
<mask id='mask'>
|
||||
<g transform='matrix(-0.5,0.866025,-0.866025,-0.5,827.404,221.632)'>
|
||||
<path
|
||||
fill='white'
|
||||
d='M585.2,594.5C577.4,594.5 569.5,591.7 563.3,586.1C549.9,574 548.8,553.3 560.9,539.8C601.3,494.9 626,439.5 632.3,379.4C638.6,319.3 626,259.9 595.8,207.7C586.8,192 592.1,172 607.8,162.9C623.5,153.8 643.5,159.2 652.6,174.9C689.8,239.2 705.3,312.3 697.5,386.3C689.7,460.2 659.3,528.5 609.6,583.7C603,590.9 594.1,594.5 585.2,594.5Z'
|
||||
/>
|
||||
</g>
|
||||
<g transform='matrix(-0.5,0.866025,-0.866025,-0.5,827.404,221.632)'>
|
||||
<path
|
||||
fill='white'
|
||||
d='M561.7,147C553.9,147 546,144.2 539.8,138.6C494.9,98.2 439.5,73.5 379.4,67.2C319.4,60.9 259.9,73.5 207.6,103.7C191.9,112.8 171.9,107.4 162.8,91.7C153.7,76 159.1,56 174.8,46.9C239.2,9.7 312.3,-5.8 386.2,2C460.1,9.8 528.4,40.2 583.6,89.9C597,102 598.1,122.7 586,136.2C579.6,143.3 570.7,147 561.7,147Z'
|
||||
/>
|
||||
</g>
|
||||
<g transform='matrix(-0.5,0.866025,-0.866025,-0.5,827.404,221.632)'>
|
||||
<path
|
||||
fill='white'
|
||||
d='M349.6,699.3C328.4,699.3 307.1,697.4 285.9,693.5C204.1,678.3 130.2,634.4 77.9,569.8C25.6,505.2 -2.1,423.8 0.1,340.6C2.3,257.4 34.1,177.6 89.8,115.8C101.9,102.4 122.7,101.3 136.1,113.4C149.5,125.5 150.6,146.2 138.5,159.7C93.3,209.9 67.4,274.8 65.6,342.4C63.8,410 86.3,476.1 128.8,528.6C171.3,581.1 231.4,616.8 297.8,629.2C364.2,641.5 433.1,629.7 491.6,595.9C507.3,586.8 527.3,592.2 536.4,607.9C545.4,623.6 540.1,643.6 524.4,652.7C471,683.4 410.6,699.3 349.6,699.3Z'
|
||||
/>
|
||||
</g>
|
||||
</mask>
|
||||
<circle
|
||||
r={RADIUS}
|
||||
cx={RADIUS}
|
||||
cy={RADIUS}
|
||||
fill='white'
|
||||
fillOpacity={0.2}
|
||||
mask='url(#mask)'
|
||||
style={ROTATION}
|
||||
/>
|
||||
<circle
|
||||
r={316}
|
||||
cx={RADIUS}
|
||||
cy={RADIUS}
|
||||
fill='transparent'
|
||||
strokeWidth={64}
|
||||
pathLength={100}
|
||||
strokeDasharray={100}
|
||||
strokeDashoffset={percentage}
|
||||
strokeLinecap='round'
|
||||
style={ROTATION}
|
||||
mask='url(#mask)'
|
||||
className={classNames(
|
||||
color,
|
||||
'stroke-current',
|
||||
!reduceMotion && 'transition-color transition-[stroke-dashoffset] duration-500',
|
||||
)}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
5
src/components/Icons/TooltipArrow.svg
Normal file
5
src/components/Icons/TooltipArrow.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg viewBox="0 0 8 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4.86824 5.48057C4.48435 6.15239 3.51565 6.15239 3.13176 5.48057L0 0L8 0L4.86824 5.48057Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 219 B |
@ -46,6 +46,7 @@ export { default as StarFilled } from 'components/Icons/StarFilled.svg'
|
||||
export { default as StarOutlined } from 'components/Icons/StarOutlined.svg'
|
||||
export { default as Subtract } from 'components/Icons/Subtract.svg'
|
||||
export { default as SwapIcon } from 'components/Icons/SwapIcon.svg'
|
||||
export { default as TooltipArrow } from 'components/Icons/TooltipArrow.svg'
|
||||
export { default as TrashBin } from 'components/Icons/TrashBin.svg'
|
||||
export { default as VerticalThreeLine } from 'components/Icons/VerticalThreeLine.svg'
|
||||
export { default as Wallet } from 'components/Icons/Wallet.svg'
|
||||
|
@ -14,7 +14,7 @@ import { BN_ZERO } from 'constants/math'
|
||||
import usePrice from 'hooks/usePrice'
|
||||
import useStore from 'store'
|
||||
import { BNCoin } from 'types/classes/BNCoin'
|
||||
import { accumulateAmounts, getAmount } from 'utils/accounts'
|
||||
import { accumulateAmounts } from 'utils/accounts'
|
||||
import { findCoinByDenom } from 'utils/assets'
|
||||
import { BN } from 'utils/helpers'
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import classNames from 'classnames'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
import { ExclamationMarkCircled } from 'components/Icons'
|
||||
import { TooltipArrow } from 'components/Icons'
|
||||
import Text from 'components/Text'
|
||||
|
||||
import { TooltipType } from '.'
|
||||
@ -13,19 +13,35 @@ interface Props {
|
||||
|
||||
export default function TooltipContent(props: Props) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'flex max-w-[320px] flex-1 gap-2 rounded-sm p-3 text-xs shadow-tooltip backdrop-blur-lg',
|
||||
'before:content-[" "] before:absolute before:inset-0 before:-z-1 before:rounded-base before:p-[1px] before:border-glas',
|
||||
props.type === 'info' && 'bg-white/20',
|
||||
props.type === 'warning' && 'bg-warning',
|
||||
props.type === 'error' && 'bg-error',
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<ExclamationMarkCircled className='w-5 gap-3 text-white' />
|
||||
<div>
|
||||
<div
|
||||
className={classNames(
|
||||
'flex max-w-[320px] flex-1 gap-2 rounded-sm py-0.5 px-2 text-sm shadow-tooltip backdrop-blur-lg',
|
||||
props.type === 'info' && 'bg-white/20',
|
||||
props.type === 'warning' && 'bg-warning',
|
||||
props.type === 'error' && 'bg-error',
|
||||
)}
|
||||
>
|
||||
{typeof props.content === 'string' ? (
|
||||
<Text size='sm' className='font-semibold'>
|
||||
{props.content}
|
||||
</Text>
|
||||
) : (
|
||||
props.content
|
||||
)}
|
||||
</div>
|
||||
{typeof props.content === 'string' ? <Text size='sm'>{props.content}</Text> : props.content}
|
||||
{
|
||||
<div data-popper-arrow=''>
|
||||
<TooltipArrow
|
||||
width={8}
|
||||
className={classNames(
|
||||
props.type === 'info' && 'text-white/20',
|
||||
props.type === 'warning' && 'text-warning',
|
||||
props.type === 'error' && 'text-error',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
5
src/hooks/useHealthColorAndLabel.ts
Normal file
5
src/hooks/useHealthColorAndLabel.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export default function useHealthColorAndLabel(health: number, colorPrefix: string) {
|
||||
if (health > 30) return [`${colorPrefix}violet-500`, 'Healthy']
|
||||
if (health > 10) return [`${colorPrefix}yellow-300`, 'Attention']
|
||||
return [`${colorPrefix}martian-red`, 'Liquidation risk']
|
||||
}
|
@ -70,7 +70,7 @@ export const calculateAccountApr = (
|
||||
|
||||
let totalLendsInterestValue = BN_ZERO
|
||||
let totalVaultsInterestValue = BN_ZERO
|
||||
let totalDeptsInterestValue = BN_ZERO
|
||||
let totalDebtInterestValue = BN_ZERO
|
||||
|
||||
lends?.forEach((lend) => {
|
||||
const asset = getAssetByDenom(lend.denom)
|
||||
@ -104,15 +104,13 @@ export const calculateAccountApr = (
|
||||
borrowAssetsData.find((borrowAsset) => borrowAsset.asset.denom === debt.denom)?.borrowRate ??
|
||||
0
|
||||
const positionInterest = amount.multipliedBy(price).multipliedBy(apr)
|
||||
totalDeptsInterestValue = totalDeptsInterestValue.plus(positionInterest)
|
||||
totalDebtInterestValue = totalDebtInterestValue.plus(positionInterest)
|
||||
})
|
||||
|
||||
const totalInterstValue = totalLendsInterestValue
|
||||
.plus(totalVaultsInterestValue)
|
||||
.minus(totalDeptsInterestValue)
|
||||
const totalApr = totalInterstValue.dividedBy(totalValue).times(100)
|
||||
|
||||
return totalApr
|
||||
.minus(totalDebtInterestValue)
|
||||
return totalInterstValue.dividedBy(totalValue).times(100)
|
||||
}
|
||||
|
||||
export const calculateAccountBorrowRate = (
|
||||
@ -206,3 +204,27 @@ export function cloneAccount(account: Account): Account {
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
export function computeHealthGaugePercentage(health: number) {
|
||||
const ATTENTION_CUTOFF = 10
|
||||
const HEALTHY_CUTOFF = 30
|
||||
const HEALTHY_BAR_SIZE = 55
|
||||
const UNHEALTHY_BAR_SIZE = 21
|
||||
const GAP_SIZE = 3
|
||||
|
||||
if (health > HEALTHY_CUTOFF) {
|
||||
const basePercentage = 100 - HEALTHY_BAR_SIZE
|
||||
const additionalPercentage =
|
||||
((health - HEALTHY_CUTOFF) / (100 - HEALTHY_CUTOFF)) * HEALTHY_BAR_SIZE
|
||||
return 100 - (basePercentage + additionalPercentage + GAP_SIZE)
|
||||
}
|
||||
|
||||
if (health > ATTENTION_CUTOFF) {
|
||||
const basePercentage = UNHEALTHY_BAR_SIZE
|
||||
const additionalPercentage =
|
||||
((health - ATTENTION_CUTOFF) / (HEALTHY_CUTOFF - ATTENTION_CUTOFF)) * UNHEALTHY_BAR_SIZE
|
||||
return 100 - (basePercentage + additionalPercentage + GAP_SIZE)
|
||||
}
|
||||
|
||||
return 100 - (health / ATTENTION_CUTOFF) * UNHEALTHY_BAR_SIZE
|
||||
}
|
||||
|
@ -27,6 +27,11 @@ module.exports = {
|
||||
'text-4xl',
|
||||
'text-5xl-caps',
|
||||
'text-5xl',
|
||||
'text-yellow-300',
|
||||
'text-violet-500',
|
||||
'fill-yellow-300',
|
||||
'fill-violet-500',
|
||||
'fill-martian-red',
|
||||
'w-2',
|
||||
],
|
||||
theme: {
|
||||
@ -60,7 +65,7 @@ module.exports = {
|
||||
overlay: '0 2px 2px rgba(0, 0, 0, 0.14), 0 1px 5px rgba(0, 0, 0, 0.2)',
|
||||
button: '0px 1px 1px rgba(0, 0, 0, 0.14), 0px 1px 3px rgba(0, 0, 0, 0.2)',
|
||||
tooltip:
|
||||
'0 3px 4px rgba(0, 0, 0, 0.14), 0 3px 3px rgba(0, 0, 0, 0.12), 0 1px 8px rgba(0, 0, 0, 0.2)',
|
||||
'0 3px 4px rgba(0, 0, 0, 0.04), 0 3px 3px rgba(0, 0, 0, 0.04), 0 1px 8px rgba(0, 0, 0, 0.04)',
|
||||
},
|
||||
brightness: {
|
||||
30: '.3',
|
||||
@ -222,160 +227,161 @@ module.exports = {
|
||||
h2: { fontSize: '9px', lineHeight: '56px' },
|
||||
h3: { fontSize: '30px', lineHeight: '40px' },
|
||||
h4: { fontSize: '24px', lineHeight: '36px', fontWeight: theme('fontWeight.normal') },
|
||||
}),
|
||||
addUtilities({
|
||||
'.blur-orb-primary': {
|
||||
filter: 'blur(clamp(50px, 8vw, 100px))',
|
||||
},
|
||||
'.blur-orb-secondary': {
|
||||
filter: 'blur(clamp(60px, 20vw, 140px))',
|
||||
},
|
||||
'.blur-orb-tertiary': {
|
||||
filter: 'blur(clamp(60px, 10vw, 110px))',
|
||||
},
|
||||
'.border-glas': {
|
||||
background: 'linear-gradient(180deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0))',
|
||||
mask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
|
||||
'-webkit-mask-composite': 'xor',
|
||||
maskComposite: 'exclude',
|
||||
},
|
||||
'.glow-line': {
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: 'transparent',
|
||||
stroke: '#FFF',
|
||||
strokeWidth: '0.5',
|
||||
strokeDasharray: '20px 30px',
|
||||
},
|
||||
'.glow-hover': {
|
||||
strokeDashoffset: '-80px',
|
||||
transition: 'stroke-dashoffset 1000ms ease-in',
|
||||
},
|
||||
'.gradient-atom': {
|
||||
background: 'linear-gradient(to bottom, #2e3148, #6f7390)',
|
||||
},
|
||||
'.gradient-axlusdc': {
|
||||
background: 'linear-gradient(to bottom, #1f5c9e, #478edc)',
|
||||
},
|
||||
'.gradient-card': {
|
||||
background:
|
||||
'linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%), linear-gradient(0deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05))',
|
||||
},
|
||||
'.gradient-card-content': {
|
||||
backgroundImage: 'linear-gradient(to right, transparent, rgba(255, 255, 255, 0.05))',
|
||||
},
|
||||
'.gradient-hatched': {
|
||||
backgroundImage:
|
||||
'linear-gradient(135deg,transparent 33.33%,#826d6b 33.33%,#826d6b 50%,transparent 50%,transparent 83.33%,#826d6b 83.33%,#826d6b 100%)',
|
||||
backgroundSize: '5px 5px',
|
||||
},
|
||||
'.gradient-header': {
|
||||
background:
|
||||
'linear-gradient(90deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0) 50%)',
|
||||
},
|
||||
'.gradient-limit': {
|
||||
background:
|
||||
'linear-gradient(to right,#15bfa9 20.9%,#5e4bb1 49.68%,#382685 82.55%,#c83333 100%)',
|
||||
},
|
||||
'.gradient-mars': {
|
||||
background: 'linear-gradient(to bottom, #a03b45, #c83333)',
|
||||
},
|
||||
'.gradient-osmo': {
|
||||
background: 'linear-gradient(to bottom, #3a02e2, #e700ca)',
|
||||
},
|
||||
'.gradient-popover': {
|
||||
background:
|
||||
'linear-gradient(180deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0) 100%), linear-gradient(0deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05))',
|
||||
},
|
||||
'.gradient-primary-to-secondary': {
|
||||
background: 'linear-gradient(180deg, #7F78E8 0%, #926AC8 100%)',
|
||||
},
|
||||
'.gradient-secondary-to-primary': {
|
||||
background: 'linear-gradient(180deg, #926AC8 100%, #7F78E8 0%)',
|
||||
},
|
||||
'.gradient-tooltip': {
|
||||
background:
|
||||
'linear-gradient(77.47deg, rgba(20, 24, 57, 0.9) 11.58%, rgba(34, 16, 57, 0.9) 93.89%)',
|
||||
},
|
||||
'.gradient-active-tab': {
|
||||
background:
|
||||
'linear-gradient(270deg, rgba(186, 8, 189, 0.764896) 0%, rgba(255, 160, 187, 0.88641) 23.77%, rgba(48, 29, 24, 0.26) 99.2%)',
|
||||
},
|
||||
'.number': {
|
||||
whiteSpace: 'nowrap',
|
||||
fontFeatureSettings: '"tnum" on',
|
||||
},
|
||||
'.text-3xs': { fontSize: '9px', lineHeight: '12px' },
|
||||
'.text-3xs-caps': {
|
||||
fontSize: '9px',
|
||||
lineHeight: '12px',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: theme('fontWeight.semibold'),
|
||||
letterSpacing: theme('letterSpacing.wide'),
|
||||
},
|
||||
'.text-2xs': { fontSize: '10px', lineHeight: '16px' },
|
||||
'.text-2xs-caps': {
|
||||
fontSize: '10px',
|
||||
lineHeight: '16px',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: theme('fontWeight.semibold'),
|
||||
letterSpacing: theme('letterSpacing.wide'),
|
||||
},
|
||||
'.text-xs-caps': {
|
||||
fontSize: '12px',
|
||||
lineHeight: '16px',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: theme('fontWeight.semibold'),
|
||||
letterSpacing: theme('letterSpacing.wider'),
|
||||
},
|
||||
'.text-sm-caps': {
|
||||
fontSize: '14px',
|
||||
lineHeight: '20px',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: theme('fontWeight.semibold'),
|
||||
letterSpacing: theme('letterSpacing.wider'),
|
||||
},
|
||||
'.text-base-caps': {
|
||||
fontSize: '16px',
|
||||
lineHeight: '20px',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: theme('fontWeight.semibold'),
|
||||
letterSpacing: theme('letterSpacing.wider'),
|
||||
},
|
||||
'.text-lg-caps': {
|
||||
fontSize: '17px',
|
||||
lineHeight: '24px',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: theme('fontWeight.semibold'),
|
||||
letterSpacing: theme('letterSpacing.wider'),
|
||||
},
|
||||
'.text-xl-caps': {
|
||||
fontSize: '19px',
|
||||
lineHeight: '28px',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: theme('fontWeight.light'),
|
||||
letterSpacing: theme('letterSpacing.widest'),
|
||||
},
|
||||
'.text-2xl-caps': {
|
||||
fontSize: '21px',
|
||||
lineHeight: '32px',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: theme('letterSpacing.wider'),
|
||||
},
|
||||
'.text-3xl-caps': {
|
||||
fontSize: '24px',
|
||||
lineHeight: '36px',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: theme('letterSpacing.wider'),
|
||||
},
|
||||
'.text-4xl-caps': { fontSize: '30px', lineHeight: '40px', textTransform: 'uppercase' },
|
||||
'.text-5xl-caps': {
|
||||
fontSize: '39px',
|
||||
lineHeight: '56px',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '9px',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
addUtilities({
|
||||
'.blur-orb-primary': {
|
||||
filter: 'blur(clamp(50px, 8vw, 100px))',
|
||||
},
|
||||
'.blur-orb-secondary': {
|
||||
filter: 'blur(clamp(60px, 20vw, 140px))',
|
||||
},
|
||||
'.blur-orb-tertiary': {
|
||||
filter: 'blur(clamp(60px, 10vw, 110px))',
|
||||
},
|
||||
'.border-glas': {
|
||||
background: 'linear-gradient(180deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0))',
|
||||
mask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
|
||||
'-webkit-mask-composite': 'xor',
|
||||
maskComposite: 'exclude',
|
||||
},
|
||||
'.glow-line': {
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: 'transparent',
|
||||
stroke: '#FFF',
|
||||
strokeWidth: '0.5',
|
||||
strokeDasharray: '20px 30px',
|
||||
},
|
||||
'.glow-hover': {
|
||||
strokeDashoffset: '-80px',
|
||||
transition: 'stroke-dashoffset 1000ms ease-in',
|
||||
},
|
||||
'.gradient-atom': {
|
||||
background: 'linear-gradient(to bottom, #2e3148, #6f7390)',
|
||||
},
|
||||
'.gradient-axlusdc': {
|
||||
background: 'linear-gradient(to bottom, #1f5c9e, #478edc)',
|
||||
},
|
||||
'.gradient-card': {
|
||||
background:
|
||||
'linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 100%), linear-gradient(0deg, rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05))',
|
||||
},
|
||||
'.gradient-card-content': {
|
||||
backgroundImage: 'linear-gradient(to right, transparent, rgba(255, 255, 255, 0.05))',
|
||||
},
|
||||
'.gradient-hatched': {
|
||||
backgroundImage:
|
||||
'linear-gradient(135deg,transparent 33.33%,#826d6b 33.33%,#826d6b 50%,transparent 50%,transparent 83.33%,#826d6b 83.33%,#826d6b 100%)',
|
||||
backgroundSize: '5px 5px',
|
||||
},
|
||||
'.gradient-header': {
|
||||
background:
|
||||
'linear-gradient(90deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0) 50%)',
|
||||
},
|
||||
'.gradient-limit': {
|
||||
background:
|
||||
'linear-gradient(to right,#15bfa9 20.9%,#5e4bb1 49.68%,#382685 82.55%,#c83333 100%)',
|
||||
},
|
||||
'.gradient-mars': {
|
||||
background: 'linear-gradient(to bottom, #a03b45, #c83333)',
|
||||
},
|
||||
'.gradient-osmo': {
|
||||
background: 'linear-gradient(to bottom, #3a02e2, #e700ca)',
|
||||
},
|
||||
'.gradient-popover': {
|
||||
background:
|
||||
'linear-gradient(180deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0) 100%), linear-gradient(0deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.05))',
|
||||
},
|
||||
'.gradient-primary-to-secondary': {
|
||||
background: 'linear-gradient(180deg, #7F78E8 0%, #926AC8 100%)',
|
||||
},
|
||||
'.gradient-secondary-to-primary': {
|
||||
background: 'linear-gradient(180deg, #926AC8 100%, #7F78E8 0%)',
|
||||
},
|
||||
'.gradient-tooltip': {
|
||||
background:
|
||||
'linear-gradient(77.47deg, rgba(20, 24, 57, 0.9) 11.58%, rgba(34, 16, 57, 0.9) 93.89%)',
|
||||
},
|
||||
'.gradient-active-tab': {
|
||||
background:
|
||||
'linear-gradient(270deg, rgba(186, 8, 189, 0.764896) 0%, rgba(255, 160, 187, 0.88641) 23.77%, rgba(48, 29, 24, 0.26) 99.2%)',
|
||||
},
|
||||
'.number': {
|
||||
whiteSpace: 'nowrap',
|
||||
fontFeatureSettings: '"tnum" on',
|
||||
},
|
||||
'.text-3xs': { fontSize: '9px', lineHeight: '12px' },
|
||||
'.text-3xs-caps': {
|
||||
fontSize: '9px',
|
||||
lineHeight: '12px',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: theme('fontWeight.semibold'),
|
||||
letterSpacing: theme('letterSpacing.wide'),
|
||||
},
|
||||
'.text-2xs': { fontSize: '10px', lineHeight: '16px' },
|
||||
'.text-2xs-caps': {
|
||||
fontSize: '10px',
|
||||
lineHeight: '16px',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: theme('fontWeight.semibold'),
|
||||
letterSpacing: theme('letterSpacing.wide'),
|
||||
},
|
||||
'.text-xs-caps': {
|
||||
fontSize: '12px',
|
||||
lineHeight: '16px',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: theme('fontWeight.semibold'),
|
||||
letterSpacing: theme('letterSpacing.wider'),
|
||||
},
|
||||
'.text-sm-caps': {
|
||||
fontSize: '14px',
|
||||
lineHeight: '20px',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: theme('fontWeight.semibold'),
|
||||
letterSpacing: theme('letterSpacing.wider'),
|
||||
},
|
||||
'.text-base-caps': {
|
||||
fontSize: '16px',
|
||||
lineHeight: '20px',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: theme('fontWeight.semibold'),
|
||||
letterSpacing: theme('letterSpacing.wider'),
|
||||
},
|
||||
'.text-lg-caps': {
|
||||
fontSize: '17px',
|
||||
lineHeight: '24px',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: theme('fontWeight.semibold'),
|
||||
letterSpacing: theme('letterSpacing.wider'),
|
||||
},
|
||||
'.text-xl-caps': {
|
||||
fontSize: '19px',
|
||||
lineHeight: '28px',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: theme('fontWeight.light'),
|
||||
letterSpacing: theme('letterSpacing.widest'),
|
||||
},
|
||||
'.text-2xl-caps': {
|
||||
fontSize: '21px',
|
||||
lineHeight: '32px',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: theme('letterSpacing.wider'),
|
||||
},
|
||||
'.text-3xl-caps': {
|
||||
fontSize: '24px',
|
||||
lineHeight: '36px',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: theme('letterSpacing.wider'),
|
||||
},
|
||||
'.text-4xl-caps': { fontSize: '30px', lineHeight: '40px', textTransform: 'uppercase' },
|
||||
'.text-5xl-caps': {
|
||||
fontSize: '39px',
|
||||
lineHeight: '56px',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '9px',
|
||||
},
|
||||
})
|
||||
}),
|
||||
],
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user