Health indicator change preview (#410)
* fix: adjusted the AccountDetail width * feat: added health indicator updates * Update src/components/Account/AccountDetails.tsx Co-authored-by: Yusuf Seyrek <yusufseyrek@users.noreply.github.com> * fix: created a function for the back- and foregroundColors * fix: updated tailwind conf * fix: fixed the useHealthColorAndLabel function --------- Co-authored-by: Yusuf Seyrek <yusufseyrek@users.noreply.github.com> Co-authored-by: Bob van der Helm <34470358+bobthebuidlr@users.noreply.github.com>
This commit is contained in:
parent
23fe839229
commit
fbb4207f93
@ -48,6 +48,7 @@ function AccountDetails(props: Props) {
|
||||
const updatedAccount = useStore((s) => s.updatedAccount)
|
||||
const [isExpanded, setIsExpanded] = useToggle()
|
||||
const { health } = useHealthComputer(account)
|
||||
const { health: updatedHealth } = useHealthComputer(updatedAccount || account)
|
||||
const { data: prices } = usePrices()
|
||||
const accountBalanceValue = useMemo(
|
||||
() => calculateAccountBalanceValue(updatedAccount ? updatedAccount : account, prices),
|
||||
@ -78,8 +79,8 @@ function AccountDetails(props: Props) {
|
||||
<div
|
||||
data-testid='account-details'
|
||||
className={classNames(
|
||||
isExpanded ? 'right-6' : '-right-80',
|
||||
'w-100 flex items-start gap-4 absolute top-6',
|
||||
isExpanded ? 'right-4' : '-right-82.5',
|
||||
'w-100 flex items-start gap-6 absolute top-6',
|
||||
!reduceMotion && 'transition-all duration-300',
|
||||
)}
|
||||
>
|
||||
@ -94,7 +95,7 @@ function AccountDetails(props: Props) {
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
>
|
||||
<div className='flex flex-wrap justify-center w-full py-4'>
|
||||
<HealthGauge health={health} />
|
||||
<HealthGauge health={health} updatedHealth={updatedHealth} />
|
||||
<Text size='2xs' className='mb-0.5 mt-1 w-full text-center text-white/50'>
|
||||
Account Health
|
||||
</Text>
|
||||
@ -140,7 +141,7 @@ function AccountDetails(props: Props) {
|
||||
|
||||
{glowElement(!reduceMotion)}
|
||||
</div>
|
||||
<div className='flex w-80 backdrop-blur-sticky'>
|
||||
<div className='flex w-90 backdrop-blur-sticky'>
|
||||
<Card className='w-full bg-white/5' title={`Credit Account ${account.id}`}>
|
||||
<AccountComposition account={account} />
|
||||
<Text className='w-full px-4 py-2 text-white bg-white/10'>Balances</Text>
|
||||
|
@ -60,7 +60,7 @@ export default function AccountStats(props: Props) {
|
||||
<Text size='xs' className='w-auto mr-1 text-white/70'>
|
||||
Health
|
||||
</Text>
|
||||
<HealthBar health={health} classNames='w-[92px] h-0.5' hasLabel />
|
||||
<HealthBar health={health} className='w-[92px] h-0.5' hasLabel />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -45,25 +45,26 @@ export default function AccountSummary(props: Props) {
|
||||
[lendingAvailableAssets, accountLentAssets],
|
||||
)
|
||||
const { health } = useHealthComputer(props.account)
|
||||
const { health: updatedHealth } = useHealthComputer(updatedAccount)
|
||||
const leverage = useMemo(
|
||||
() => (props.account ? calculateAccountLeverage(props.account, prices) : BN_ZERO),
|
||||
[props.account, prices],
|
||||
)
|
||||
if (!props.account) return null
|
||||
return (
|
||||
<div className='h-[546px] min-w-[370px] basis-[370px] overflow-y-scroll scrollbar-hide'>
|
||||
<div className='h-[546px] min-w-92.5 basis-92.5 max-w-screen overflow-y-scroll scrollbar-hide'>
|
||||
<Card className='mb-4 h-min min-w-fit bg-white/10' contentClassName='flex'>
|
||||
<Item label='Net worth' classNames='flex-1'>
|
||||
<Item label='Net worth' classes='flex-1'>
|
||||
<DisplayCurrency
|
||||
coin={BNCoin.fromDenomAndBigNumber(ORACLE_DENOM, accountBalance)}
|
||||
className='text-sm'
|
||||
/>
|
||||
</Item>
|
||||
<Item label='Leverage' classNames='flex-1'>
|
||||
<Item label='Leverage' classes='flex-1'>
|
||||
<Text size='sm'>{formatLeverage(leverage.toNumber())}</Text>
|
||||
</Item>
|
||||
<Item label='Account health'>
|
||||
<HealthBar health={health} classNames='w-[184px] h-1' />
|
||||
<HealthBar health={health} updatedHealth={updatedHealth} className='w-[184px] h-1' />
|
||||
</Item>
|
||||
</Card>
|
||||
<Accordion
|
||||
@ -99,7 +100,7 @@ export default function AccountSummary(props: Props) {
|
||||
|
||||
interface ItemProps extends HTMLAttributes<HTMLDivElement> {
|
||||
label: string
|
||||
classNames?: string
|
||||
classes?: string
|
||||
}
|
||||
|
||||
function Item(props: ItemProps) {
|
||||
@ -107,7 +108,7 @@ function Item(props: ItemProps) {
|
||||
<div
|
||||
className={classNames(
|
||||
'flex flex-col justify-around px-3 py-1 border-r border-r-white/10',
|
||||
props.classNames,
|
||||
props.classes,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
|
@ -1,38 +1,54 @@
|
||||
import classNames from 'classnames'
|
||||
import { useMemo } from 'react'
|
||||
|
||||
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 { getHealthIndicatorColors } from 'utils/healthIndicator'
|
||||
|
||||
interface Props {
|
||||
health: number
|
||||
updatedHealth?: number
|
||||
hasLabel?: boolean
|
||||
classNames?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
function calculateHealth(health: number) {
|
||||
function calculateHealth(health: number): number {
|
||||
const firstBarEnd = 43
|
||||
const secondBarStart = 46
|
||||
const secondBarEnd = 93
|
||||
const thirdBarStart = 96
|
||||
const thirdBarEnd = 184
|
||||
|
||||
if (health <= 0) return 0
|
||||
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
|
||||
return ((thirdBarEnd - thirdBarStart) / 70) * (health - 30) + thirdBarStart
|
||||
}
|
||||
|
||||
export default function HealthBar(props: Props) {
|
||||
const { health } = props
|
||||
const { health, updatedHealth } = props
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
const [color, label] = useHealthColorAndLabel(health, 'fill-')
|
||||
const width = calculateHealth(health)
|
||||
const updatedWidth = calculateHealth(updatedHealth ?? 0)
|
||||
const isUpdated = updatedWidth > 0 && updatedWidth !== width
|
||||
const isIncrease = isUpdated && updatedWidth > width
|
||||
const [color, label] = useHealthColorAndLabel(health, 'fill')
|
||||
const [updatedColor, updatedLabel] = useHealthColorAndLabel(updatedHealth ?? 0, 'fill')
|
||||
const [backgroundColor, foreGroundColor] = useMemo(
|
||||
() => getHealthIndicatorColors(color, updatedColor, 'fill', isUpdated, isIncrease),
|
||||
[color, updatedColor, isUpdated, isIncrease],
|
||||
)
|
||||
|
||||
return (
|
||||
<Tooltip content={label} type='info' className='flex items-center w-full'>
|
||||
<div className={classNames('flex max-w-[184px] max-h-1', props.classNames)}>
|
||||
<Tooltip
|
||||
content={isUpdated ? updatedLabel : label}
|
||||
type='info'
|
||||
className='flex items-center w-full'
|
||||
>
|
||||
<div className={classNames('flex max-w-[184px] max-h-1', props.className)}>
|
||||
<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' />
|
||||
@ -41,11 +57,22 @@ export default function HealthBar(props: Props) {
|
||||
</mask>
|
||||
<rect className='fill-white/10' width='184' height='4' mask='url(#healthBarMask)' />
|
||||
<rect
|
||||
className={classNames(color, !reduceMotion && 'transition-all duration-500')}
|
||||
width={width}
|
||||
className={classNames(backgroundColor, !reduceMotion && 'transition-all duration-500')}
|
||||
width={isUpdated && isIncrease ? updatedWidth : width}
|
||||
height='4'
|
||||
mask='url(#healthBarMask)'
|
||||
/>
|
||||
{isUpdated && (
|
||||
<rect
|
||||
className={classNames(
|
||||
foreGroundColor,
|
||||
!reduceMotion && 'transition-all duration-500',
|
||||
)}
|
||||
width={isUpdated && !isIncrease ? updatedWidth : width}
|
||||
height='4'
|
||||
mask='url(#healthBarMask)'
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
@ -8,10 +8,12 @@ import { REDUCE_MOTION_KEY } from 'constants/localStore'
|
||||
import useHealthColorAndLabel from 'hooks/useHealthColorAndLabel'
|
||||
import useLocalStorage from 'hooks/useLocalStorage'
|
||||
import { computeHealthGaugePercentage } from 'utils/accounts'
|
||||
import { getHealthIndicatorColors } from 'utils/healthIndicator'
|
||||
|
||||
interface Props {
|
||||
diameter?: number
|
||||
health: number
|
||||
updatedHealth?: number
|
||||
}
|
||||
|
||||
const RADIUS = 350
|
||||
@ -20,13 +22,24 @@ const ROTATION = {
|
||||
transformOrigin: 'center',
|
||||
}
|
||||
|
||||
export const HealthGauge = ({ diameter = 40, health = 0 }: Props) => {
|
||||
const [color, label] = useHealthColorAndLabel(health, 'text-')
|
||||
export const HealthGauge = ({ diameter = 40, health = 0, updatedHealth = 0 }: Props) => {
|
||||
const [color, label] = useHealthColorAndLabel(health, 'text')
|
||||
const [updatedColor, updatedLabel] = useHealthColorAndLabel(updatedHealth ?? 0, 'text')
|
||||
const [reduceMotion] = useLocalStorage<boolean>(REDUCE_MOTION_KEY, DEFAULT_SETTINGS.reduceMotion)
|
||||
const percentage = useMemo(() => computeHealthGaugePercentage(health), [health])
|
||||
const updatedPercentage = useMemo(
|
||||
() => computeHealthGaugePercentage(updatedHealth),
|
||||
[updatedHealth],
|
||||
)
|
||||
const isUpdated = updatedHealth !== 0 && updatedPercentage !== percentage
|
||||
const isIncrease = isUpdated && updatedPercentage < percentage
|
||||
const [backgroundColor, foreGroundColor] = useMemo(
|
||||
() => getHealthIndicatorColors(color, updatedColor, 'text', isUpdated, isIncrease),
|
||||
[color, updatedColor, isUpdated, isIncrease],
|
||||
)
|
||||
|
||||
return (
|
||||
<Tooltip type='info' content={label}>
|
||||
<Tooltip type='info' content={isUpdated ? updatedLabel : label}>
|
||||
<div
|
||||
className={classNames(
|
||||
'relative grid place-items-center',
|
||||
@ -79,16 +92,36 @@ export const HealthGauge = ({ diameter = 40, health = 0 }: Props) => {
|
||||
strokeWidth={64}
|
||||
pathLength={100}
|
||||
strokeDasharray={100}
|
||||
strokeDashoffset={percentage}
|
||||
strokeDashoffset={isUpdated && isIncrease ? updatedPercentage : percentage}
|
||||
strokeLinecap='round'
|
||||
style={ROTATION}
|
||||
mask='url(#mask)'
|
||||
className={classNames(
|
||||
color,
|
||||
backgroundColor,
|
||||
'stroke-current',
|
||||
!reduceMotion && 'transition-color transition-[stroke-dashoffset] duration-500',
|
||||
)}
|
||||
/>
|
||||
{isUpdated && (
|
||||
<circle
|
||||
r={316}
|
||||
cx={RADIUS}
|
||||
cy={RADIUS}
|
||||
fill='transparent'
|
||||
strokeWidth={64}
|
||||
pathLength={100}
|
||||
strokeDasharray={100}
|
||||
strokeDashoffset={isUpdated && !isIncrease ? updatedPercentage : percentage}
|
||||
strokeLinecap='round'
|
||||
style={ROTATION}
|
||||
mask='url(#mask)'
|
||||
className={classNames(
|
||||
foreGroundColor,
|
||||
'stroke-current',
|
||||
!reduceMotion && 'transition-color transition-[stroke-dashoffset] duration-500',
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</svg>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
@ -1,5 +1,6 @@
|
||||
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']
|
||||
export default function useHealthColorAndLabel(health: number, colorPrefix: 'fill' | 'text') {
|
||||
if (health > 30) return [`${colorPrefix}-violet-500`, 'Healthy']
|
||||
if (health > 10) return [`${colorPrefix}-yellow-300`, 'Attention']
|
||||
|
||||
return [`${colorPrefix}-martian-red`, 'Liquidation risk']
|
||||
}
|
||||
|
15
src/utils/healthIndicator.ts
Normal file
15
src/utils/healthIndicator.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export function getHealthIndicatorColors(
|
||||
color: string,
|
||||
updatedColor: string,
|
||||
colorPrefix: 'fill' | 'text',
|
||||
isUpdated: boolean,
|
||||
isIncrease: boolean,
|
||||
): [backgroundColor: string, foregroundColor: string] {
|
||||
let backgroundColor = color
|
||||
if (isUpdated && isIncrease) backgroundColor = updatedColor
|
||||
if (isUpdated && !isIncrease) backgroundColor = `${colorPrefix}-grey`
|
||||
|
||||
const foreGroundColor = isIncrease ? `${colorPrefix}-grey` : updatedColor
|
||||
|
||||
return [backgroundColor, foreGroundColor]
|
||||
}
|
@ -29,9 +29,11 @@ module.exports = {
|
||||
'text-5xl',
|
||||
'text-yellow-300',
|
||||
'text-violet-500',
|
||||
'text-grey',
|
||||
'fill-yellow-300',
|
||||
'fill-violet-500',
|
||||
'fill-martian-red',
|
||||
'fill-grey',
|
||||
'w-2',
|
||||
],
|
||||
theme: {
|
||||
@ -82,7 +84,7 @@ module.exports = {
|
||||
error: '#F04438',
|
||||
'error-bg': '#FDA29B',
|
||||
green: '#039855',
|
||||
grey: '#3a3c49',
|
||||
grey: '#908e91',
|
||||
'grey-dark': '#1a1c25',
|
||||
'grey-highlight': '#4c4c4c',
|
||||
'grey-light': '#bfbfbf',
|
||||
@ -176,6 +178,7 @@ module.exports = {
|
||||
},
|
||||
minWidth: {
|
||||
15: '60px',
|
||||
92.5: '370px',
|
||||
},
|
||||
padding: {
|
||||
5.5: '22px',
|
||||
@ -191,6 +194,7 @@ module.exports = {
|
||||
},
|
||||
spacing: {
|
||||
35: '140px',
|
||||
82.5: '330px',
|
||||
},
|
||||
transitionDuration: {
|
||||
3000: '3000ms',
|
||||
@ -210,6 +214,7 @@ module.exports = {
|
||||
35: '140px',
|
||||
50: '200px',
|
||||
60: '240px',
|
||||
92.5: '370px',
|
||||
100: '400px',
|
||||
120: '480px',
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user