refactor(ui-toolkit): move healthbar to ui-toolkit (#3121)

This commit is contained in:
Ciaran McGhie 2023-03-22 11:33:46 +00:00 committed by GitHub
parent 4cbd7a4928
commit 74a3aa8566
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 227 additions and 223 deletions

View File

@ -18,6 +18,7 @@ import type * as Schema from '@vegaprotocol/types';
import {
AsyncRenderer,
Icon,
HealthBar,
TooltipCellComponent,
} from '@vegaprotocol/ui-toolkit';
import type { GetRowIdParams, RowClickedEvent } from 'ag-grid-community';
@ -27,9 +28,9 @@ import { AgGridColumn } from 'ag-grid-react';
import { useCallback, useState } from 'react';
import { Grid } from '../../grid';
import { HealthBar } from '../../health-bar';
import { HealthDialog } from '../../health-dialog';
import { Status } from '../../status';
import { intentForStatus } from '../../../lib/utils';
import { formatDistanceToNow } from 'date-fns';
export const MarketList = () => {
@ -273,13 +274,13 @@ export const MarketList = () => {
data: Market;
}) => (
<HealthBar
status={value}
target={data.target}
decimals={
data.tradableInstrument.instrument.product.settlementAsset
.decimals
}
levels={data.feeLevels}
intent={intentForStatus(value)}
/>
)}
sortable={false}

View File

@ -1,13 +1,13 @@
import { useState } from 'react';
import { t } from '@vegaprotocol/i18n';
import { Icon } from '@vegaprotocol/ui-toolkit';
import { Icon, HealthBar } from '@vegaprotocol/ui-toolkit';
import { formatWithAsset } from '@vegaprotocol/liquidity';
import type * as Schema from '@vegaprotocol/types';
import { HealthBar } from '../../health-bar';
import { HealthDialog } from '../../health-dialog';
import { Last24hVolume } from '../last-24h-volume';
import { Status } from '../../status';
import { intentForStatus } from '../../../lib/utils';
interface Levels {
fee: string;
@ -92,10 +92,10 @@ export const Market = ({
<td className="px-4">
{tradingMode && settlementAsset?.decimals && feeLevels && (
<HealthBar
status={tradingMode}
target={targetStake}
decimals={settlementAsset.decimals}
levels={feeLevels}
intent={intentForStatus(tradingMode)}
/>
)}
</td>

View File

@ -1,213 +0,0 @@
import classNames from 'classnames';
import type * as Schema from '@vegaprotocol/types';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { BigNumber } from 'bignumber.js';
import type { ReactNode } from 'react';
import { getColorForStatus } from '../../lib/utils';
import { Indicator } from '../indicator';
const Remainder = () => (
<div className="bg-greys-light-200 h-[inherit] relative flex-1"></div>
);
const COPY_CLASS =
'text-sm font-medium whitespace-nowrap text-white font-alpha calt';
const Tooltip = ({
children,
isExpanded,
}: {
children: ReactNode;
isExpanded: boolean;
}) => {
return (
<div
className={classNames(
'absolute top-0 left-1/2 -translate-x-2/4 -translate-y-[80%] p-2 z-10 bg-greys-light-400 group-hover:flex rounded',
{
flex: isExpanded,
hidden: !isExpanded,
}
)}
>
{children}
</div>
);
};
const Target = ({
targetPercent,
isLarge,
children,
}: {
targetPercent: number;
isLarge: boolean;
children: ReactNode;
}) => {
return (
<div
className={classNames(
'absolute top-1/2 left-1/2 -translate-x-2/4 -translate-y-1/2 px-1.5 group'
)}
style={{ left: `${targetPercent}%` }}
>
<div
className={classNames(
'health-target w-0.5 bg-black group-hover:scale-x-150 group-hover:scale-y-108',
{
'h-6': !isLarge,
'h-12': isLarge,
}
)}
></div>
{children}
</div>
);
};
const Level = ({
children,
commitmentAmount,
total,
backgroundColor,
opacity,
}: {
children: ReactNode;
commitmentAmount: number;
total: number;
backgroundColor: string;
opacity: number;
}) => {
const width = new BigNumber(commitmentAmount)
.div(total)
.multipliedBy(100)
.toNumber();
return (
<div
className={classNames(`relative h-[inherit] w-full group min-w-[1px]`)}
style={{
width: `${width}%`,
}}
>
<div
className="relative w-full h-[inherit] group-hover:scale-y-150"
style={{
opacity,
backgroundColor,
}}
></div>
{children}
</div>
);
};
const Full = () => (
<div className="bg-transparent w-full h-[inherit] absolute bottom-0 left-0"></div>
);
interface Levels {
fee: string;
commitmentAmount: number;
}
export const HealthBar = ({
status,
target = '0',
decimals,
levels,
size = 'small',
isExpanded = false,
}: {
status: Schema.MarketTradingMode;
target: string;
decimals: number;
levels: Levels[];
isExpanded?: boolean;
size?: 'small' | 'large';
}) => {
const targetNumber = parseInt(target, 10);
const committedNumber = levels
.reduce((total, current) => {
return total.plus(current.commitmentAmount);
}, new BigNumber(0))
.toNumber();
const total =
targetNumber * 2 >= committedNumber ? targetNumber * 2 : committedNumber;
const targetPercent = (targetNumber / total) * 100;
const isLarge = size === 'large';
const backgroundColor = getColorForStatus(status);
return (
<div className="w-full">
<div
className={classNames('health-wrapper relative', {
'py-2': !isLarge,
'py-5': isLarge,
})}
>
<div
className={classNames('health-inner relative w-full flex', {
'h-4': !isLarge,
'h-8': isLarge,
})}
>
<Full />
<div className="health-bars h-[inherit] flex w-full gap-0.5">
{levels.map((p, index) => {
const { commitmentAmount, fee } = p;
const prevLevel = levels[index - 1]?.commitmentAmount;
const opacity = 1 - 0.2 * index;
return (
<Level
commitmentAmount={commitmentAmount}
total={total}
backgroundColor={backgroundColor}
opacity={opacity}
>
<Tooltip isExpanded={isExpanded}>
<div className="mt-1.5 inline-flex">
<Indicator status={status} opacity={opacity} />
</div>
<div className="flex flex-col">
<span className={COPY_CLASS}>
{fee}% {t('Fee')}
</span>
<span className={classNames(COPY_CLASS, 'opacity-60')}>
{prevLevel
? addDecimalsFormatNumber(prevLevel, decimals)
: '0'}{' '}
- {addDecimalsFormatNumber(commitmentAmount, decimals)}
</span>
</div>
</Tooltip>
</Level>
);
})}
{(total !== committedNumber || levels.length === 0) && (
<Remainder />
)}
</div>
</div>
<Target targetPercent={targetPercent} isLarge={isLarge}>
<Tooltip isExpanded={isExpanded}>
<div className="mt-1.5 inline-flex">
<Indicator />
</div>
<span className={COPY_CLASS}>
{t('Target stake')} {addDecimalsFormatNumber(target, decimals)}
</span>
</Tooltip>
</Target>
</div>
</div>
);
};

View File

@ -1 +0,0 @@
export * from './health-bar';

View File

@ -1,9 +1,8 @@
import { t } from '@vegaprotocol/i18n';
import { Dialog } from '@vegaprotocol/ui-toolkit';
import { Dialog, HealthBar } from '@vegaprotocol/ui-toolkit';
import * as Schema from '@vegaprotocol/types';
import classNames from 'classnames';
import { HealthBar } from '../health-bar';
import { intentForStatus } from '../../lib/utils';
interface HealthDialogProps {
isOpen: boolean;
@ -96,9 +95,9 @@ export const HealthDialog = ({ onChange, isOpen }: HealthDialogProps) => {
<HealthBar
size="large"
levels={r.data.levels}
status={r.data.status}
target={r.data.target}
decimals={r.data.decimals}
intent={intentForStatus(r.data.status)}
/>
</td>
</tr>

View File

@ -1,4 +1,5 @@
import * as Schema from '@vegaprotocol/types';
import { Intent } from '@vegaprotocol/ui-toolkit';
const marketTradingModeStyle = {
[Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS]: '#00D46E',
@ -10,3 +11,15 @@ const marketTradingModeStyle = {
export const getColorForStatus = (status: Schema.MarketTradingMode) =>
marketTradingModeStyle[status];
const marketTradingModeIntent = {
[Schema.MarketTradingMode.TRADING_MODE_CONTINUOUS]: Intent.Success,
[Schema.MarketTradingMode.TRADING_MODE_MONITORING_AUCTION]: Intent.Danger,
[Schema.MarketTradingMode.TRADING_MODE_OPENING_AUCTION]: Intent.Primary,
[Schema.MarketTradingMode.TRADING_MODE_BATCH_AUCTION]: Intent.Danger,
[Schema.MarketTradingMode.TRADING_MODE_NO_TRADING]: Intent.Danger,
};
export const intentForStatus = (status: Schema.MarketTradingMode) => {
return marketTradingModeIntent[status];
};

View File

@ -0,0 +1,203 @@
import classNames from 'classnames';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { BigNumber } from 'bignumber.js';
import { getIntentBackground, Intent } from '../../utils/intent';
import { Indicator } from '../indicator';
import { Tooltip } from '../tooltip';
const Remainder = () => (
<div className="bg-greys-light-200 h-[inherit] relative flex-1" />
);
const Target = ({
target,
decimals,
isLarge,
}: {
isLarge: boolean;
target: string;
decimals: number;
}) => {
return (
<Tooltip
description={
<>
<div className="mt-1.5 inline-flex">
<Indicator variant={Intent.None} />
</div>
<span>
{t('Target stake')} {addDecimalsFormatNumber(target, decimals)}
</span>
</>
}
>
<div
className={classNames(
'absolute top-1/2 left-1/2 -translate-x-2/4 -translate-y-1/2 px-1.5 group'
)}
style={{ left: '50%' }}
>
<div
className={classNames(
'health-target w-0.5 bg-black group-hover:scale-x-150 group-hover:scale-y-108',
{
'h-6': !isLarge,
'h-12': isLarge,
}
)}
/>
</div>
</Tooltip>
);
};
const Level = ({
commitmentAmount,
rangeLimit,
opacity,
fee,
prevLevel,
decimals,
intent,
}: {
commitmentAmount: number;
rangeLimit: number;
opacity: number;
fee: string;
prevLevel: number;
decimals: number;
intent: Intent;
}) => {
const width = new BigNumber(commitmentAmount)
.div(rangeLimit)
.multipliedBy(100)
.toNumber();
const tooltipContent = (
<>
<div className="mt-1.5 inline-flex">
<Indicator variant={intent} />
</div>
<span>
{fee}% {t('Fee')}
</span>
<div className="flex flex-col">
<span>
{prevLevel ? addDecimalsFormatNumber(prevLevel, decimals) : '0'} -{' '}
{addDecimalsFormatNumber(commitmentAmount, decimals)}
</span>
</div>
</>
);
return (
<Tooltip description={tooltipContent}>
<div
className={classNames(`relative h-[inherit] w-full group min-w-[1px]`)}
style={{
width: `${width}%`,
}}
>
<div
className={classNames(
'relative w-full h-[inherit] group-hover:scale-y-150',
getIntentBackground(intent)
)}
style={{ opacity }}
/>
</div>
</Tooltip>
);
};
const Full = () => (
<div className="bg-transparent w-full h-[inherit] absolute bottom-0 left-0" />
);
interface Levels {
fee: string;
commitmentAmount: number;
}
export const HealthBar = ({
target = '0',
decimals,
levels,
size = 'small',
intent,
}: {
target: string;
decimals: number;
levels: Levels[];
size?: 'small' | 'large';
intent: Intent;
}) => {
const targetNumber = parseInt(target, 10);
const rangeLimit = targetNumber * 2;
let lastVisibleLevel = 0;
const committedNumber = levels
.reduce((total, current, index) => {
const newTotal = total.plus(current.commitmentAmount);
if (total.isLessThan(rangeLimit) && newTotal.isGreaterThan(rangeLimit)) {
lastVisibleLevel = index;
}
return newTotal;
}, new BigNumber(0))
.toNumber();
const isLarge = size === 'large';
const showRemainder = committedNumber < rangeLimit || levels.length === 0;
const showOverflow = !showRemainder && lastVisibleLevel < levels.length - 1;
return (
<div className="w-full">
<div
className={classNames('health-wrapper relative', {
'py-2': !isLarge,
'py-5': isLarge,
})}
>
<div
className={classNames('health-inner relative w-full flex', {
'h-4': !isLarge,
'h-8': isLarge,
})}
>
<Full />
<div className="health-bars h-[inherit] flex w-full gap-0.5">
{levels.map((p, index) => {
const { commitmentAmount, fee } = p;
const prevLevel = levels[index - 1]?.commitmentAmount;
const opacity = 1 - 0.2 * index;
return index <= lastVisibleLevel ? (
<Level
commitmentAmount={commitmentAmount}
rangeLimit={rangeLimit}
opacity={opacity}
fee={fee}
prevLevel={prevLevel}
decimals={0}
intent={intent}
/>
) : null;
})}
{showRemainder && <Remainder />}
{showOverflow && (
<Tooltip
description={t(
'Providers greater than 2x target stake not shown'
)}
>
<div className="h-[inherit] relative flex-1 leading-4">...</div>
</Tooltip>
)}
</div>
</div>
<Target isLarge={isLarge} target={target} decimals={decimals} />
</div>
</div>
);
};

View File

@ -0,0 +1 @@
export * from './healthbar';

View File

@ -46,3 +46,4 @@ export * from './traffic-light';
export * from './vega-icons';
export * from './vega-logo';
export * from './viewing-as-user';
export * from './healthbar';