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:
Linkie Link 2023-09-01 11:04:40 +02:00 committed by GitHub
parent 23fe839229
commit fbb4207f93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 113 additions and 30 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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}
>

View File

@ -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>

View File

@ -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>

View File

@ -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']
}

View 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]
}

View File

@ -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',
},