feat(trading): margin required progressive disclosure (#4755)
This commit is contained in:
parent
7f5e8ebb15
commit
250a654544
@ -1,27 +1,28 @@
|
|||||||
|
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||||
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
|
||||||
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
|
||||||
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
|
||||||
NX_ORACLE_PROOFS_URL=https://raw.githubusercontent.com/vegaprotocol/well-known/main/__generated__/oracle-proofs.json
|
NX_SENTRY_DSN=https://2ffce43721964aafa78277c50654ece4@o286262.ingest.sentry.io/6300613
|
||||||
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet1/vegawallet-stagnet1.toml
|
NX_VEGA_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/fairground/vegawallet-fairground.toml
|
||||||
NX_VEGA_ENV=STAGNET1
|
NX_VEGA_ENV=TESTNET
|
||||||
NX_VEGA_EXPLORER_URL=https://explorer.stagnet1.vega.rocks
|
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||||
NX_VEGA_NETWORKS={\"MAINNET\":\"https://console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
|
NX_VEGA_NETWORKS={\"MAINNET\":\"https://console.vega.xyz\",\"TESTNET\":\"https://console.fairground.wtf\",\"STAGNET1\":\"https://trading.stagnet1.vega.rocks\"}
|
||||||
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
|
NX_VEGA_TOKEN_URL=https://governance.fairground.wtf
|
||||||
NX_VEGA_WALLET_URL=http://localhost:1789
|
NX_VEGA_WALLET_URL=http://localhost:1789
|
||||||
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
|
||||||
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega/releases
|
||||||
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/main/announcements.json
|
NX_ANNOUNCEMENTS_CONFIG_URL=https://raw.githubusercontent.com/vegaprotocol/announcements/fairground/announcements.json
|
||||||
NX_WALLETCONNECT_PROJECT_ID=fe8091dc35738863e509fc4947525c72
|
NX_VEGA_INCIDENT_URL=https://blog.vega.xyz/tagged/vega-incident-reports
|
||||||
|
NX_VEGA_CONSOLE_URL=https://console.fairground.wtf
|
||||||
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
NX_CHROME_EXTENSION_URL=https://chrome.google.com/webstore/detail/vega-wallet-fairground/nmmjkiafpmphlikhefgjbblebfgclikn
|
||||||
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
NX_MOZILLA_EXTENSION_URL=https://addons.mozilla.org/firefox/addon/vega-wallet-fairground
|
||||||
|
|
||||||
|
|
||||||
# Cosmic elevator flags
|
# Cosmic elevator flags
|
||||||
NX_SUCCESSOR_MARKETS=true
|
NX_SUCCESSOR_MARKETS=true
|
||||||
NX_STOP_ORDERS=true
|
NX_STOP_ORDERS=true
|
||||||
# NX_ICEBERG_ORDERS
|
NX_ICEBERG_ORDERS=true
|
||||||
# NX_PRODUCT_PERPETUALS
|
# NX_PRODUCT_PERPETUALS
|
||||||
NX_METAMASK_SNAPS=true
|
NX_METAMASK_SNAPS=false
|
||||||
|
|
||||||
NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.rocks
|
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
|
||||||
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
|
NX_TENDERMINT_WEBSOCKET_URL=wss://be.testnet.vega.xyz/websocket
|
||||||
|
@ -11,8 +11,7 @@ import { AccountBreakdownDialog } from '@vegaprotocol/accounts';
|
|||||||
import { formatRange, formatValue } from '@vegaprotocol/utils';
|
import { formatRange, formatValue } from '@vegaprotocol/utils';
|
||||||
import { marketMarginDataProvider } from '@vegaprotocol/accounts';
|
import { marketMarginDataProvider } from '@vegaprotocol/accounts';
|
||||||
import { useDataProvider } from '@vegaprotocol/data-provider';
|
import { useDataProvider } from '@vegaprotocol/data-provider';
|
||||||
|
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||||
import * as Accordion from '@radix-ui/react-accordion';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
MARGIN_DIFF_TOOLTIP_TEXT,
|
MARGIN_DIFF_TOOLTIP_TEXT,
|
||||||
@ -24,7 +23,13 @@ import {
|
|||||||
} from '../../constants';
|
} from '../../constants';
|
||||||
import { useEstimateFees } from '../../hooks';
|
import { useEstimateFees } from '../../hooks';
|
||||||
import { KeyValue } from './key-value';
|
import { KeyValue } from './key-value';
|
||||||
import { TOOLTIP_TRIGGER_CLASS_NAME } from '@vegaprotocol/ui-toolkit';
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionChevron,
|
||||||
|
AccordionPanel,
|
||||||
|
Tooltip,
|
||||||
|
} from '@vegaprotocol/ui-toolkit';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
const emptyValue = '-';
|
const emptyValue = '-';
|
||||||
|
|
||||||
@ -246,31 +251,56 @@ export const DealTicketMarginDetails = ({
|
|||||||
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
const quoteName = market.tradableInstrument.instrument.product.quoteName;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="flex flex-col gap-2 w-full">
|
||||||
<Accordion.Root type="single" collapsible>
|
<Accordion>
|
||||||
<Accordion.Item value="margin">
|
<AccordionPanel
|
||||||
<KeyValue
|
itemId="margin"
|
||||||
id="margin-required"
|
trigger={
|
||||||
label={
|
<AccordionPrimitive.Trigger
|
||||||
<Accordion.Trigger className={TOOLTIP_TRIGGER_CLASS_NAME}>
|
data-testid="accordion-toggle"
|
||||||
{t('Margin required')}
|
className={classNames(
|
||||||
</Accordion.Trigger>
|
'w-full pt-2',
|
||||||
}
|
'flex items-center gap-2 text-xs',
|
||||||
value={formatRange(
|
'group'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-testid={`deal-ticket-fee-margin-required`}
|
||||||
|
key={'value-dropdown'}
|
||||||
|
className="flex items-center gap-2 justify-between w-full"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Tooltip description={MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol)}>
|
||||||
|
<span className="text-muted">{t('Margin required')}</span>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<AccordionChevron size={10} />
|
||||||
|
</div>
|
||||||
|
<Tooltip
|
||||||
|
description={
|
||||||
|
formatRange(
|
||||||
marginRequiredBestCase,
|
marginRequiredBestCase,
|
||||||
marginRequiredWorstCase,
|
marginRequiredWorstCase,
|
||||||
assetDecimals
|
assetDecimals
|
||||||
)}
|
) ?? '-'
|
||||||
formattedValue={formatRange(
|
}
|
||||||
|
noUnderline
|
||||||
|
>
|
||||||
|
<div className="font-mono text-right">
|
||||||
|
{formatRange(
|
||||||
marginRequiredBestCase,
|
marginRequiredBestCase,
|
||||||
marginRequiredWorstCase,
|
marginRequiredWorstCase,
|
||||||
assetDecimals,
|
assetDecimals,
|
||||||
quantum
|
quantum
|
||||||
)}
|
)}{' '}
|
||||||
labelDescription={MARGIN_DIFF_TOOLTIP_TEXT(assetSymbol)}
|
{assetSymbol || ''}
|
||||||
symbol={assetSymbol}
|
</div>
|
||||||
/>
|
</Tooltip>
|
||||||
<Accordion.Content>
|
</div>
|
||||||
|
</AccordionPrimitive.Trigger>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-2 w-full">
|
||||||
<KeyValue
|
<KeyValue
|
||||||
label={t('Total margin available')}
|
label={t('Total margin available')}
|
||||||
indent
|
indent
|
||||||
@ -310,12 +340,12 @@ export const DealTicketMarginDetails = ({
|
|||||||
quantum
|
quantum
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Accordion.Content>
|
</div>
|
||||||
</Accordion.Item>
|
</AccordionPanel>
|
||||||
</Accordion.Root>
|
</Accordion>
|
||||||
{projectedMargin}
|
{projectedMargin}
|
||||||
<KeyValue
|
<KeyValue
|
||||||
label={t('Liquidation price estimate')}
|
label={t('Liquidation')}
|
||||||
value={liquidationPriceEstimate}
|
value={liquidationPriceEstimate}
|
||||||
formattedValue={liquidationPriceEstimate}
|
formattedValue={liquidationPriceEstimate}
|
||||||
symbol={quoteName}
|
symbol={quoteName}
|
||||||
@ -329,6 +359,6 @@ export const DealTicketMarginDetails = ({
|
|||||||
onClose={onAccountBreakdownDialogClose}
|
onClose={onAccountBreakdownDialogClose}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -538,7 +538,7 @@ const NotionalAndFees = ({
|
|||||||
market.positionDecimalPlaces
|
market.positionDecimalPlaces
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div className="mb-4">
|
<div className="mb-4 flex flex-col gap-2 w-full">
|
||||||
<KeyValue
|
<KeyValue
|
||||||
label={t('Notional')}
|
label={t('Notional')}
|
||||||
value={formatValue(notionalSize, market.decimalPlaces)}
|
value={formatValue(notionalSize, market.decimalPlaces)}
|
||||||
|
@ -464,7 +464,7 @@ export const DealTicket = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="mb-4">
|
<div className="mb-4 flex flex-col gap-2 w-full">
|
||||||
<KeyValue
|
<KeyValue
|
||||||
label={t('Notional')}
|
label={t('Notional')}
|
||||||
value={formatValue(notionalSize, market.decimalPlaces)}
|
value={formatValue(notionalSize, market.decimalPlaces)}
|
||||||
|
@ -25,11 +25,11 @@ export const KeyValue = ({
|
|||||||
}: KeyValuePros) => {
|
}: KeyValuePros) => {
|
||||||
const displayValue = `${formattedValue ?? '-'} ${symbol || ''}`;
|
const displayValue = `${formattedValue ?? '-'} ${symbol || ''}`;
|
||||||
const valueElement = onClick ? (
|
const valueElement = onClick ? (
|
||||||
<button onClick={onClick} className="text-muted">
|
<button onClick={onClick} className="font-mono">
|
||||||
{displayValue}
|
{displayValue}
|
||||||
</button>
|
</button>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-muted">{displayValue}</div>
|
<div className="font-mono">{displayValue}</div>
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -40,14 +40,14 @@ export const KeyValue = ({
|
|||||||
}`}
|
}`}
|
||||||
key={typeof label === 'string' ? label : 'value-dropdown'}
|
key={typeof label === 'string' ? label : 'value-dropdown'}
|
||||||
className={classnames(
|
className={classnames(
|
||||||
'text-xs mt-2 flex justify-between items-center gap-4 flex-wrap',
|
'text-xs flex justify-between items-center gap-4 flex-wrap',
|
||||||
{ 'ml-2': indent }
|
{ 'ml-2': indent }
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Tooltip description={labelDescription}>
|
<Tooltip description={labelDescription}>
|
||||||
<div>{label}</div>
|
<div className="text-muted">{label}</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip description={`${value ?? '-'} ${symbol || ''}`}>
|
<Tooltip description={`${value ?? '-'} ${symbol || ''}`} noUnderline>
|
||||||
{valueElement}
|
{valueElement}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,7 +8,7 @@ export const EST_MARGIN_TOOLTIP_TEXT = (settlementAsset: string) =>
|
|||||||
[settlementAsset]
|
[settlementAsset]
|
||||||
);
|
);
|
||||||
export const EST_TOTAL_MARGIN_TOOLTIP_TEXT = t(
|
export const EST_TOTAL_MARGIN_TOOLTIP_TEXT = t(
|
||||||
'Estimated total margin that will cover open position, active orders and this order.'
|
'Estimated total margin that will cover open positions, active orders and this order.'
|
||||||
);
|
);
|
||||||
export const MARGIN_ACCOUNT_TOOLTIP_TEXT = t('Margin account balance.');
|
export const MARGIN_ACCOUNT_TOOLTIP_TEXT = t('Margin account balance.');
|
||||||
export const MARGIN_DIFF_TOOLTIP_TEXT = (settlementAsset: string) =>
|
export const MARGIN_DIFF_TOOLTIP_TEXT = (settlementAsset: string) =>
|
||||||
@ -60,7 +60,7 @@ export const EST_FEES_TOOLTIP_TEXT = t(
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT = t(
|
export const LIQUIDATION_PRICE_ESTIMATE_TOOLTIP_TEXT = t(
|
||||||
'This is a approximation to the liquidation price for that particular contract position, assuming nothing else changes, which may affect your margin and collateral balances.'
|
'This is an approximation (or a range) for the liquidation price for that particular contract position, assuming nothing else changes, which may affect your margin and collateral balances.'
|
||||||
);
|
);
|
||||||
|
|
||||||
export const EST_SLIPPAGE = t(
|
export const EST_SLIPPAGE = t(
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import type { VegaIconProps } from '../icon';
|
||||||
import { VegaIcon, VegaIconNames } from '../icon';
|
import { VegaIcon, VegaIconNames } from '../icon';
|
||||||
|
|
||||||
export interface AccordionItemProps {
|
export interface AccordionItemProps {
|
||||||
@ -24,6 +25,28 @@ export const Accordion = ({ panels, children }: AccordionProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const AccordionPanel = ({
|
||||||
|
trigger,
|
||||||
|
children,
|
||||||
|
itemId,
|
||||||
|
}: {
|
||||||
|
trigger: React.ReactNode;
|
||||||
|
children: React.ReactNode;
|
||||||
|
itemId: string;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<AccordionPrimitive.Item value={itemId}>
|
||||||
|
<AccordionPrimitive.Header>{trigger}</AccordionPrimitive.Header>
|
||||||
|
<AccordionPrimitive.Content
|
||||||
|
className="py-3 text-sm"
|
||||||
|
data-testid="accordion-content"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AccordionPrimitive.Content>
|
||||||
|
</AccordionPrimitive.Item>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const AccordionItem = ({
|
export const AccordionItem = ({
|
||||||
title,
|
title,
|
||||||
content,
|
content,
|
||||||
@ -31,7 +54,7 @@ export const AccordionItem = ({
|
|||||||
}: AccordionPanelProps) => {
|
}: AccordionPanelProps) => {
|
||||||
const triggerClassNames = classNames(
|
const triggerClassNames = classNames(
|
||||||
'w-full py-2',
|
'w-full py-2',
|
||||||
'flex items-center justify-between border-b border-vega-light-200 dark:border-vega-dark-200 text-sm',
|
'flex items-center justify-between gap-2 border-b border-vega-light-200 dark:border-vega-dark-200 text-sm',
|
||||||
'group'
|
'group'
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
@ -41,7 +64,9 @@ export const AccordionItem = ({
|
|||||||
data-testid="accordion-toggle"
|
data-testid="accordion-toggle"
|
||||||
className={triggerClassNames}
|
className={triggerClassNames}
|
||||||
>
|
>
|
||||||
<span data-testid="accordion-title">{title}</span>
|
<span data-testid="accordion-title" className="flex-1 text-left">
|
||||||
|
{title}
|
||||||
|
</span>
|
||||||
<AccordionChevron aria-hidden />
|
<AccordionChevron aria-hidden />
|
||||||
</AccordionPrimitive.Trigger>
|
</AccordionPrimitive.Trigger>
|
||||||
</AccordionPrimitive.Header>
|
</AccordionPrimitive.Header>
|
||||||
@ -55,15 +80,17 @@ export const AccordionItem = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AccordionChevron = () => {
|
export const AccordionChevron = ({
|
||||||
|
size = 16,
|
||||||
|
}: Pick<VegaIconProps, 'size'>) => {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'transform transition ease-in-out duration-300',
|
'flex transform transition ease-in-out duration-300',
|
||||||
'group-data-[state=open]:rotate-180'
|
'group-data-[state=open]:rotate-180'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} aria-hidden />
|
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} aria-hidden size={size} />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
Portal,
|
Portal,
|
||||||
} from '@radix-ui/react-tooltip';
|
} from '@radix-ui/react-tooltip';
|
||||||
import type { ITooltipParams } from 'ag-grid-community';
|
import type { ITooltipParams } from 'ag-grid-community';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
const tooltipContentClasses =
|
const tooltipContentClasses =
|
||||||
'max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-2 py-1 z-20 rounded text-xs text-black dark:text-white break-word';
|
'max-w-sm bg-vega-light-100 dark:bg-vega-dark-100 border border-vega-light-200 dark:border-vega-dark-200 px-2 py-1 z-20 rounded text-xs text-black dark:text-white break-word';
|
||||||
@ -18,10 +19,14 @@ export interface TooltipProps {
|
|||||||
align?: 'start' | 'center' | 'end';
|
align?: 'start' | 'center' | 'end';
|
||||||
side?: 'top' | 'right' | 'bottom' | 'left';
|
side?: 'top' | 'right' | 'bottom' | 'left';
|
||||||
sideOffset?: number;
|
sideOffset?: number;
|
||||||
|
noUnderline?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TOOLTIP_TRIGGER_CLASS_NAME =
|
export const TOOLTIP_TRIGGER_CLASS_NAME = (noUnderline?: boolean) =>
|
||||||
'underline underline-offset-2 decoration-neutral-400 dark:decoration-neutral-400 decoration-dashed';
|
classNames(
|
||||||
|
{ 'underline underline-offset-2': !noUnderline },
|
||||||
|
'decoration-neutral-400 dark:decoration-neutral-400 decoration-dashed'
|
||||||
|
);
|
||||||
|
|
||||||
// Conditionally rendered tooltip if description content is provided.
|
// Conditionally rendered tooltip if description content is provided.
|
||||||
export const Tooltip = ({
|
export const Tooltip = ({
|
||||||
@ -31,11 +36,12 @@ export const Tooltip = ({
|
|||||||
sideOffset,
|
sideOffset,
|
||||||
align = 'start',
|
align = 'start',
|
||||||
side = 'bottom',
|
side = 'bottom',
|
||||||
|
noUnderline,
|
||||||
}: TooltipProps) =>
|
}: TooltipProps) =>
|
||||||
description ? (
|
description ? (
|
||||||
<Provider delayDuration={200} skipDelayDuration={100}>
|
<Provider delayDuration={200} skipDelayDuration={100}>
|
||||||
<Root open={open}>
|
<Root open={open}>
|
||||||
<Trigger asChild className={TOOLTIP_TRIGGER_CLASS_NAME}>
|
<Trigger asChild className={TOOLTIP_TRIGGER_CLASS_NAME(noUnderline)}>
|
||||||
{children}
|
{children}
|
||||||
</Trigger>
|
</Trigger>
|
||||||
{description && (
|
{description && (
|
||||||
|
Loading…
Reference in New Issue
Block a user