fix(trading): market info refactor (#3985)

Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
m.ray 2023-06-06 05:00:29 +03:00 committed by GitHub
parent d6e2432955
commit 011e4e4cec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 381 additions and 521 deletions

View File

@ -1,5 +1,6 @@
import { t } from '@vegaprotocol/i18n';
import type { MarketInfoWithData } from '@vegaprotocol/markets';
import { PriceMonitoringBoundsInfoPanel } from '@vegaprotocol/markets';
import {
LiquidityInfoPanel,
LiquidityMonitoringParametersInfoPanel,
@ -39,133 +40,69 @@ export const MarketDetails = ({ market }: { market: MarketInfoWithData }) => {
return [];
};
const oraclePanels = isEqual(
const showTwoOracles = isEqual(
getSigners(settlementData),
getSigners(terminationData)
)
? [
{
title: t('Settlement Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="settlementData"
/>
),
},
{
title: t('Termination Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="termination"
/>
),
},
]
: [
{
title: t('Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="settlementData"
/>
),
},
];
);
const panels = [
{
title: t('Key details'),
content: <KeyDetailsInfoPanel noBorder={false} market={market} />,
},
{
title: t('Instrument'),
content: <InstrumentInfoPanel noBorder={false} market={market} />,
},
{
title: t('Settlement asset'),
content: <SettlementAssetInfoPanel market={market} noBorder={false} />,
},
{
title: t('Metadata'),
content: <MetadataInfoPanel noBorder={false} market={market} />,
},
{
title: t('Risk model'),
content: <RiskModelInfoPanel noBorder={false} market={market} />,
},
{
title: t('Risk parameters'),
content: <RiskParametersInfoPanel noBorder={false} market={market} />,
},
{
title: t('Risk factors'),
content: <RiskFactorsInfoPanel noBorder={false} market={market} />,
},
...(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(trigger, i) => ({
title: t(`Price monitoring trigger ${i + 1}`),
content: <MarketInfoTable noBorder={false} data={trigger} />,
})
),
...(market.data?.priceMonitoringBounds || []).map((trigger, i) => ({
title: t(`Price monitoring bound ${i + 1}`),
content: (
<>
<MarketInfoTable
noBorder={false}
data={{
maxValidPrice: trigger.maxValidPrice,
minValidPrice: trigger.minValidPrice,
}}
decimalPlaces={market.decimalPlaces}
/>
<MarketInfoTable
noBorder={false}
data={{ referencePrice: trigger.referencePrice }}
decimalPlaces={
market.tradableInstrument.instrument.product.settlementAsset
.decimals
}
/>
</>
),
})),
{
title: t('Liquidity monitoring parameters'),
content: (
<LiquidityMonitoringParametersInfoPanel
noBorder={false}
market={market}
/>
),
},
{
title: t('Liquidity'),
content: <LiquidityInfoPanel market={market} noBorder={false} />,
},
{
title: t('Liquidity price range'),
content: (
<LiquidityPriceRangeInfoPanel market={market} noBorder={false} />
),
},
...oraclePanels,
];
const headerClassName = 'font-alpha calt text-xl mt-4 border-b-2 pb-2';
return (
<>
{panels.map((p) => (
<div key={p.title} className="mb-3">
<h2 className="font-alpha calt text-xl">{p.title}</h2>
{p.content}
</div>
<div>
<h2 className={headerClassName}>{t('Key details')}</h2>
<KeyDetailsInfoPanel market={market} />
<h2 className={headerClassName}>{t('Instrument')}</h2>
<InstrumentInfoPanel market={market} />
<h2 className={headerClassName}>{t('Settlement asset')}</h2>
<SettlementAssetInfoPanel market={market} />
<h2 className={headerClassName}>{t('Metadata')}</h2>
<MetadataInfoPanel market={market} />
<h2 className={headerClassName}>{t('Risk model')}</h2>
<RiskModelInfoPanel market={market} />
<h2 className={headerClassName}>{t('Risk parameters')}</h2>
<RiskParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Risk factors')}</h2>
<RiskFactorsInfoPanel market={market} />
{(market.data?.priceMonitoringBounds || []).map((trigger, i) => (
<>
<h2 className={headerClassName}>
{t('Price monitoring bounds %s', [(i + 1).toString()])}
</h2>
<PriceMonitoringBoundsInfoPanel
market={market}
triggerIndex={i + 1}
/>
</>
))}
</>
{(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(trigger, i) => (
<>
<h2 className={headerClassName}>
{t('Price monitoring settings %s', [(i + 1).toString()])}
</h2>
<MarketInfoTable data={trigger} key={i} />
</>
)
)}
<h2 className={headerClassName}>{t('Liquidity monitoring')}</h2>
<LiquidityMonitoringParametersInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity')}</h2>
<LiquidityInfoPanel market={market} />
<h2 className={headerClassName}>{t('Liquidity price range')}</h2>
<LiquidityPriceRangeInfoPanel market={market} />
{showTwoOracles ? (
<>
<h2 className={headerClassName}>{t('Settlement oracle')}</h2>
<OracleInfoPanel market={market} type="settlementData" />
<h2 className={headerClassName}>{t('Termination oracle')}</h2>
<OracleInfoPanel market={market} type="termination" />
</>
) : (
<>
<h2 className={headerClassName}>{t('Oracle')}</h2>
<OracleInfoPanel market={market} type="settlementData" />
</>
)}
</div>
);
};

View File

@ -55,12 +55,9 @@ describe('market info is displayed', { tags: '@smoke' }, () => {
validateMarketDataRow(3, 'Quote Unit', 'BTC');
});
// TODO: fix this test
// New volume check logic, added by https://github.com/vegaprotocol/frontend-monorepo/pull/3870 has caused the
// 24hr volume assertion to fail as it now reads 'Unknown'
it.skip('market volume displayed', () => {
it('market volume displayed', () => {
cy.getByTestId(marketTitle).contains('Market volume').click();
validateMarketDataRow(0, '24 Hour Volume', '1');
validateMarketDataRow(0, '24 Hour Volume', 'Unknown');
validateMarketDataRow(1, 'Open Interest', '-');
validateMarketDataRow(2, 'Best Bid Volume', '1');
validateMarketDataRow(3, 'Best Offer Volume', '3');

View File

@ -34,7 +34,7 @@ const Row = ({
assetSymbol = '',
noBorder = true,
}: RowProps) => {
const className = 'text-black dark:text-white text-sm !px-0';
const className = 'text-sm';
const getFormattedValue = (value: ReactNode) => {
if (typeof value !== 'string' && typeof value !== 'number') return value;

View File

@ -10,6 +10,7 @@ import {
Link as UILink,
Splash,
TinyScroll,
AccordionItem,
} from '@vegaprotocol/ui-toolkit';
import { generatePath, Link } from 'react-router-dom';
@ -85,26 +86,6 @@ export const MarketInfoAccordion = ({
market.accountsConnection?.edges
);
const marketDataPanels = [
{
title: t('Current fees'),
content: <CurrentFeesInfoPanel market={market} />,
},
{
title: t('Market price'),
content: <MarketPriceInfoPanel market={market} />,
},
{
title: t('Market volume'),
content: <MarketVolumeInfoPanel market={market} />,
},
...marketAccounts
.filter((a) => a.type === Schema.AccountType.ACCOUNT_TYPE_INSURANCE)
.map((a) => ({
title: t(`Insurance pool`),
content: <InsurancePoolInfoPanel market={market} account={a} />,
})),
];
const settlementData = market.tradableInstrument.instrument.product
.dataSourceSpecForSettlementData.data as DataSourceDefinition;
const terminationData = market.tradableInstrument.instrument.product
@ -123,165 +104,187 @@ export const MarketInfoAccordion = ({
}
return [];
};
const oraclePanels = isEqual(
getSigners(settlementData),
getSigners(terminationData)
)
? [
{
title: t('Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="settlementData"
/>
),
},
]
: [
{
title: t('Settlement Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="settlementData"
/>
),
},
{
title: t('Termination Oracle'),
content: (
<OracleInfoPanel
noBorder={false}
market={market}
type="termination"
/>
),
},
];
const marketSpecPanels = [
{
title: t('Key details'),
content: <KeyDetailsInfoPanel market={market} />,
},
{
title: t('Instrument'),
content: <InstrumentInfoPanel market={market} />,
},
...oraclePanels,
{
title: t('Settlement asset'),
content: <SettlementAssetInfoPanel market={market} />,
},
{
title: t('Metadata'),
content: <MetadataInfoPanel market={market} />,
},
{
title: t('Risk model'),
content: <RiskModelInfoPanel market={market} />,
},
{
title: t('Risk parameters'),
content: <RiskParametersInfoPanel market={market} />,
},
{
title: t('Risk factors'),
content: <RiskFactorsInfoPanel market={market} />,
},
...(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(_, triggerIndex) => ({
title: t(`Price monitoring bounds ${triggerIndex + 1}`),
content: (
<PriceMonitoringBoundsInfoPanel
market={market}
triggerIndex={triggerIndex}
/>
),
})
),
{
title: t('Liquidity monitoring parameters'),
content: <LiquidityMonitoringParametersInfoPanel market={market} />,
},
{
title: t('Liquidity'),
content: (
<LiquidityInfoPanel market={market}>
<Link
to={`/liquidity/${market.id}`}
onClick={(ev) => onSelect?.(market.id, ev.metaKey || ev.ctrlKey)}
data-testid="view-liquidity-link"
>
<UILink>{t('View liquidity provision table')}</UILink>
</Link>
</LiquidityInfoPanel>
),
},
{
title: t('Liquidity price range'),
content: <LiquidityPriceRangeInfoPanel market={market} />,
},
];
const marketGovPanels = [
{
title: t('Proposal'),
content: (
<div className="">
{VEGA_TOKEN_URL && (
<ExternalLink
className="mb-2 w-full"
href={generatePath(TokenStaticLinks.PROPOSAL_PAGE, {
tokenUrl: VEGA_TOKEN_URL,
proposalId: market.proposal?.id || '',
})}
title={
market.proposal?.rationale.title ||
market.proposal?.rationale.description ||
''
}
>
{t('View governance proposal')}
</ExternalLink>
)}
{VEGA_TOKEN_URL && (
<ExternalLink
className="w-full"
href={generatePath(TokenStaticLinks.UPDATE_PROPOSAL_PAGE, {
tokenUrl: VEGA_TOKEN_URL,
})}
title={
market.proposal?.rationale.title ||
market.proposal?.rationale.description ||
''
}
>
{t('Propose a change to market')}
</ExternalLink>
)}
</div>
),
},
];
return (
<div className="p-4">
<div className="mb-8">
<h3 className={headerClassName}>{t('Market data')}</h3>
<Accordion panels={marketDataPanels} />
<Accordion>
<AccordionItem
itemId="current-fees"
title={t('Current fees')}
content={<CurrentFeesInfoPanel market={market} />}
/>
<AccordionItem
itemId="market-price"
title={t('Market price')}
content={<MarketPriceInfoPanel market={market} />}
/>
<AccordionItem
itemId="market-volume"
title={t('Market volume')}
content={<MarketVolumeInfoPanel market={market} />}
/>
{marketAccounts
.filter((a) => a.type === Schema.AccountType.ACCOUNT_TYPE_INSURANCE)
.map((a) => (
<AccordionItem
itemId={`${a.type}:${a.asset.id}`}
title={t('Insurance pool')}
content={<InsurancePoolInfoPanel market={market} account={a} />}
/>
))}
</Accordion>
</div>
<div className="mb-8">
<MarketProposalNotification marketId={market.id} />
<h3 className={headerClassName}>{t('Market specification')}</h3>
<Accordion panels={marketSpecPanels} />
<Accordion>
<AccordionItem
itemId="key-details"
title={t('Key details')}
content={<KeyDetailsInfoPanel market={market} />}
/>
<AccordionItem
itemId="instrument"
title={t('Instrument')}
content={<InstrumentInfoPanel market={market} />}
/>
{isEqual(getSigners(settlementData), getSigners(terminationData)) ? (
<AccordionItem
itemId="oracles"
title={t('Oracle')}
content={
<OracleInfoPanel market={market} type="settlementData" />
}
/>
) : (
<>
<AccordionItem
itemId="settlement-oracle"
title={t('Settlement Oracle')}
content={
<OracleInfoPanel market={market} type="settlementData" />
}
/>
<AccordionItem
itemId="termination-oracle"
title={t('Termination Oracle')}
content={<OracleInfoPanel market={market} type="termination" />}
/>
</>
)}
<AccordionItem
itemId="settlement-asset"
title={t('Settlement asset')}
content={<SettlementAssetInfoPanel market={market} />}
/>
<AccordionItem
itemId="metadata"
title={t('Metadata')}
content={<MetadataInfoPanel market={market} />}
/>
<AccordionItem
itemId="risk-model"
title={t('Risk model')}
content={<RiskModelInfoPanel market={market} />}
/>
<AccordionItem
itemId="risk-parameters"
title={t('Risk parameters')}
content={<RiskParametersInfoPanel market={market} />}
/>
<AccordionItem
itemId="risk-factors"
title={t('Risk factors')}
content={<RiskFactorsInfoPanel market={market} />}
/>
{(market.priceMonitoringSettings?.parameters?.triggers || []).map(
(_, triggerIndex) => (
<AccordionItem
itemId={`trigger-${triggerIndex}`}
title={t(`Price monitoring bounds ${triggerIndex + 1}`)}
content={
<PriceMonitoringBoundsInfoPanel
market={market}
triggerIndex={triggerIndex}
/>
}
/>
)
)}
<AccordionItem
itemId="liqudity-monitoring-parameters"
title={t('Liquidity monitoring parameters')}
content={<LiquidityMonitoringParametersInfoPanel market={market} />}
/>
<AccordionItem
itemId="liquidity"
title={t('Liquidity')}
content={
<LiquidityInfoPanel market={market}>
<div className="mt-2">
<Link
to={`/liquidity/${market.id}`}
onClick={(ev) =>
onSelect?.(market.id, ev.metaKey || ev.ctrlKey)
}
data-testid="view-liquidity-link"
>
<UILink>{t('View liquidity provision table')}</UILink>
</Link>
</div>
</LiquidityInfoPanel>
}
/>
<AccordionItem
itemId="liquidity-price-range"
title={t('Liquidity price range')}
content={<LiquidityPriceRangeInfoPanel market={market} />}
/>
</Accordion>
</div>
{VEGA_TOKEN_URL && marketGovPanels && market.proposal?.id && (
{VEGA_TOKEN_URL && market.proposal?.id && (
<div>
<h3 className={headerClassName}>{t('Market governance')}</h3>
<Accordion panels={marketGovPanels} />
<Accordion>
<AccordionItem
itemId="proposal"
title={t('Proposal')}
content={
<>
<ExternalLink
className="mb-2 w-full"
href={generatePath(TokenStaticLinks.PROPOSAL_PAGE, {
tokenUrl: VEGA_TOKEN_URL,
proposalId: market.proposal?.id || '',
})}
title={
market.proposal?.rationale.title ||
market.proposal?.rationale.description ||
''
}
>
{t('View governance proposal')}
</ExternalLink>
<ExternalLink
className="w-full"
href={generatePath(TokenStaticLinks.UPDATE_PROPOSAL_PAGE, {
tokenUrl: VEGA_TOKEN_URL,
})}
title={
market.proposal?.rationale.title ||
market.proposal?.rationale.description ||
''
}
>
{t('Propose a change to market')}
</ExternalLink>
</>
}
/>
</Accordion>
</div>
)}
</div>

View File

@ -1,4 +1,4 @@
import type { ComponentProps } from 'react';
import type { ReactNode } from 'react';
import { useState } from 'react';
import { useMemo } from 'react';
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
@ -30,19 +30,12 @@ import { useOracleProofs } from '../../hooks';
import { OracleDialog } from '../oracle-dialog/oracle-dialog';
import { useDataProvider } from '@vegaprotocol/data-provider';
type PanelProps = Pick<
ComponentProps<typeof MarketInfoTable>,
'children' | 'noBorder'
>;
type MarketInfoProps = {
market: MarketInfo;
children?: ReactNode;
};
export const CurrentFeesInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => (
export const CurrentFeesInfoPanel = ({ market }: MarketInfoProps) => (
<>
<MarketInfoTable
data={{
@ -52,7 +45,6 @@ export const CurrentFeesInfoPanel = ({
totalFees: totalFeesPercentage(market.fees.factors),
}}
asPercentage={true}
{...props}
/>
<p className="text-xs">
{t(
@ -62,10 +54,7 @@ export const CurrentFeesInfoPanel = ({
</>
);
export const MarketPriceInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => {
export const MarketPriceInfoPanel = ({ market }: MarketInfoProps) => {
const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
const quoteUnit =
@ -84,9 +73,8 @@ export const MarketPriceInfoPanel = ({
quoteUnit: market.tradableInstrument.instrument.product.quoteName,
}}
decimalPlaces={market.decimalPlaces}
{...props}
/>
<p className="text-xs mt-4">
<p className="text-xs mt-2">
{t(
'There is 1 unit of the settlement asset (%s) to every 1 quote unit (%s).',
[assetSymbol, quoteUnit]
@ -96,10 +84,7 @@ export const MarketPriceInfoPanel = ({
);
};
export const MarketVolumeInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => {
export const MarketVolumeInfoPanel = ({ market }: MarketInfoProps) => {
const { data } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: market.id },
@ -124,7 +109,6 @@ export const MarketVolumeInfoPanel = ({
bestStaticOfferVolume: dash(data?.bestStaticOfferVolume),
}}
decimalPlaces={market.positionDecimalPlaces}
{...props}
/>
);
};
@ -132,13 +116,11 @@ export const MarketVolumeInfoPanel = ({
export const InsurancePoolInfoPanel = ({
market,
account,
...props
}: {
account: NonNullable<
Get<MarketInfoWithData, 'accountsConnection.edges[0].node'>
>;
} & MarketInfoProps &
PanelProps) => {
} & MarketInfoProps) => {
const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
return (
@ -150,14 +132,11 @@ export const InsurancePoolInfoPanel = ({
decimalPlaces={
market.tradableInstrument.instrument.product.settlementAsset.decimals
}
{...props}
/>
);
};
export const KeyDetailsInfoPanel = ({
market,
}: MarketInfoProps & PanelProps) => {
export const KeyDetailsInfoPanel = ({ market }: MarketInfoProps) => {
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
return (
@ -175,10 +154,7 @@ export const KeyDetailsInfoPanel = ({
);
};
export const InstrumentInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => (
export const InstrumentInfoPanel = ({ market }: MarketInfoProps) => (
<MarketInfoTable
data={{
marketName: market.tradableInstrument.instrument.name,
@ -186,14 +162,10 @@ export const InstrumentInfoPanel = ({
productType: market.tradableInstrument.instrument.product.__typename,
quoteName: market.tradableInstrument.instrument.product.quoteName,
}}
{...props}
/>
);
export const SettlementAssetInfoPanel = ({
market,
noBorder = true,
}: MarketInfoProps & PanelProps) => {
export const SettlementAssetInfoPanel = ({ market }: MarketInfoProps) => {
const assetSymbol =
market?.tradableInstrument.instrument.product?.settlementAsset.symbol || '';
const quoteUnit =
@ -208,7 +180,7 @@ export const SettlementAssetInfoPanel = ({
<AssetDetailsTable
asset={asset}
inline={true}
noBorder={noBorder}
noBorder={true}
dtClassName="text-black dark:text-white text-ui !px-0 !font-normal"
ddClassName="text-black dark:text-white text-ui !px-0 !font-normal max-w-full"
/>
@ -224,10 +196,7 @@ export const SettlementAssetInfoPanel = ({
);
};
export const MetadataInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => (
export const MetadataInfoPanel = ({ market }: MarketInfoProps) => (
<MarketInfoTable
data={{
expiryDate: getMarketExpiryDateFormatted(
@ -240,68 +209,44 @@ export const MetadataInfoPanel = ({
})
.reduce((acc, curr) => ({ ...acc, ...curr }), {}),
}}
{...props}
/>
);
export const RiskModelInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => {
export const RiskModelInfoPanel = ({ market }: MarketInfoProps) => {
if (market.tradableInstrument.riskModel.__typename !== 'LogNormalRiskModel') {
return null;
}
const { tau, riskAversionParameter } = market.tradableInstrument.riskModel;
return (
<MarketInfoTable
data={{ tau, riskAversionParameter }}
unformatted
{...props}
/>
);
return <MarketInfoTable data={{ tau, riskAversionParameter }} unformatted />;
};
export const RiskParametersInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => {
export const RiskParametersInfoPanel = ({ market }: MarketInfoProps) => {
if (market.tradableInstrument.riskModel.__typename === 'LogNormalRiskModel') {
const { r, sigma, mu } = market.tradableInstrument.riskModel.params;
return <MarketInfoTable data={{ r, sigma, mu }} unformatted {...props} />;
return <MarketInfoTable data={{ r, sigma, mu }} unformatted />;
}
if (market.tradableInstrument.riskModel.__typename === 'SimpleRiskModel') {
const { factorLong, factorShort } =
market.tradableInstrument.riskModel.params;
return (
<MarketInfoTable
data={{ factorLong, factorShort }}
unformatted
{...props}
/>
);
return <MarketInfoTable data={{ factorLong, factorShort }} unformatted />;
}
return null;
};
export const RiskFactorsInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => {
export const RiskFactorsInfoPanel = ({ market }: MarketInfoProps) => {
if (!market.riskFactors) {
return null;
}
const { short, long } = market.riskFactors;
return <MarketInfoTable data={{ short, long }} unformatted {...props} />;
return <MarketInfoTable data={{ short, long }} unformatted />;
};
export const PriceMonitoringBoundsInfoPanel = ({
market,
triggerIndex,
...props
}: {
}: MarketInfoProps & {
triggerIndex: number;
} & MarketInfoProps &
PanelProps) => {
}) => {
const { data } = useDataProvider({
dataProvider: marketDataProvider,
variables: { marketId: market.id },
@ -318,8 +263,8 @@ export const PriceMonitoringBoundsInfoPanel = ({
return null;
}
return (
<div className="text-xs">
<div className="grid grid-cols-2 text-xs mb-4">
<>
<div className="grid grid-cols-2 text-sm mb-2">
<p className="col-span-1">
{t('%s probability price bounds', [
formatNumberPercentage(
@ -331,32 +276,28 @@ export const PriceMonitoringBoundsInfoPanel = ({
{t('Within %s seconds', [formatNumber(trigger.horizonSecs)])}
</p>
</div>
<div className="pl-2 pb-0 text-xs border-l-2">
{bounds && (
<MarketInfoTable
data={{
highestPrice: bounds.maxValidPrice,
lowestPrice: bounds.minValidPrice,
}}
decimalPlaces={market.decimalPlaces}
assetSymbol={quoteUnit}
{...props}
/>
)}
</div>
<p className="mt-4">
{bounds && (
<MarketInfoTable
data={{
highestPrice: bounds.maxValidPrice,
lowestPrice: bounds.minValidPrice,
}}
decimalPlaces={market.decimalPlaces}
assetSymbol={quoteUnit}
/>
)}
<p className="text-xs mt-2">
{t('Results in %s seconds auction if breached', [
trigger.auctionExtensionSecs.toString(),
])}
</p>
</div>
</>
);
};
export const LiquidityMonitoringParametersInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => (
}: MarketInfoProps) => (
<MarketInfoTable
data={{
triggeringRatio: market.liquidityMonitoringParameters.triggeringRatio,
@ -366,14 +307,10 @@ export const LiquidityMonitoringParametersInfoPanel = ({
market.liquidityMonitoringParameters.targetStakeParameters
.scalingFactor,
}}
{...props}
/>
);
export const LiquidityInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => {
export const LiquidityInfoPanel = ({ market, children }: MarketInfoProps) => {
const assetDecimals =
market.tradableInstrument.instrument.product.settlementAsset.decimals;
const assetSymbol =
@ -383,23 +320,22 @@ export const LiquidityInfoPanel = ({
variables: { marketId: market.id },
});
return (
<MarketInfoTable
data={{
targetStake: data?.targetStake,
suppliedStake: data?.suppliedStake,
marketValueProxy: data?.marketValueProxy,
}}
decimalPlaces={assetDecimals}
assetSymbol={assetSymbol}
{...props}
/>
<>
<MarketInfoTable
data={{
targetStake: data?.targetStake,
suppliedStake: data?.suppliedStake,
marketValueProxy: data?.marketValueProxy,
}}
decimalPlaces={assetDecimals}
assetSymbol={assetSymbol}
/>
{children}
</>
);
};
export const LiquidityPriceRangeInfoPanel = ({
market,
...props
}: MarketInfoProps & PanelProps) => {
export const LiquidityPriceRangeInfoPanel = ({ market }: MarketInfoProps) => {
const quoteUnit =
market?.tradableInstrument.instrument.product?.quoteName || '';
const liquidityPriceRange = formatNumberPercentage(
@ -411,39 +347,37 @@ export const LiquidityPriceRangeInfoPanel = ({
});
return (
<>
<p className="text-xs mb-4">
<p className="text-sm mb-2">
{`For liquidity orders to count towards a commitment, they must be
within the liquidity monitoring bounds.`}
</p>
<p className="text-xs mb-4">
<p className="text-sm mb-2">
{`The liquidity price range is a ${liquidityPriceRange} difference from the mid
price.`}
</p>
<div className="pl-2 pb-0 text-xs border-l-2">
<MarketInfoTable
data={{
liquidityPriceRange: `${liquidityPriceRange} of mid price`,
lowestPrice:
data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.minus(market.lpPriceRange)
.times(data.midPrice)
.toString(),
market.decimalPlaces
)} ${quoteUnit}`,
highestPrice:
data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.plus(market.lpPriceRange)
.times(data.midPrice)
.toString(),
market.decimalPlaces
)} ${quoteUnit}`,
}}
/>
</div>
<MarketInfoTable
data={{
liquidityPriceRange: `${liquidityPriceRange} of mid price`,
lowestPrice:
data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.minus(market.lpPriceRange)
.times(data.midPrice)
.toString(),
market.decimalPlaces
)} ${quoteUnit}`,
highestPrice:
data?.midPrice &&
`${addDecimalsFormatNumber(
new BigNumber(1)
.plus(market.lpPriceRange)
.times(data.midPrice)
.toString(),
market.decimalPlaces
)} ${quoteUnit}`,
}}
/>
</>
);
};
@ -451,8 +385,7 @@ export const LiquidityPriceRangeInfoPanel = ({
export const OracleInfoPanel = ({
market,
type,
}: MarketInfoProps &
PanelProps & { type: 'settlementData' | 'termination' }) => {
}: MarketInfoProps & { type: 'settlementData' | 'termination' }) => {
const product = market.tradableInstrument.instrument.product;
const { VEGA_EXPLORER_URL, ORACLE_PROOFS_URL } = useEnvironment();
const { data } = useOracleProofs(ORACLE_PROOFS_URL);
@ -469,7 +402,7 @@ export const OracleInfoPanel = ({
) as DataSourceDefinition;
return (
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<DataSourceProof
data-testid="oracle-proof-links"
data={dataSourceSpec}
@ -593,7 +526,7 @@ const OracleLink = ({
}
return (
<div>
<div className="mt-2">
{signerProviders.map((provider) => (
<OracleProfile
key={dataSourceSpecId}

View File

@ -112,31 +112,27 @@ export const OracleBasicProfile = ({
<Icon size={3} name={icon as IconName} />
</span>
</span>
<p className="dark:text-vega-light-300 text-vega-dark-300">{message}</p>
<p
data-testid="signed-proofs"
className="dark:text-vega-light-300 text-vega-dark-300"
>
{oracleMarkets &&
t('Involved in %s %s', [
<p className="text-sm dark:text-vega-light-300 text-vega-dark-300 mb-2">
{message}
</p>
{oracleMarkets && (
<p
data-testid="signed-proofs"
className="text-sm dark:text-vega-light-300 text-vega-dark-300 mb-2"
>
{t('Involved in %s %s', [
oracleMarkets.length.toString(),
oracleMarkets.length !== 1 ? t('markets') : t('market'),
])}
</p>
</p>
)}
{links.length > 0 && (
<div className="flex flex-row gap-1">
<div className="flex flex-row gap-3">
{links.map((link) => (
<ExternalLink
key={link.url}
href={link.url}
data-testid={link.url}
className="flex align-items-bottom underline text-sm"
>
<span className="pt-1 pr-1">
<ExternalLink key={link.url} href={link.url} data-testid={link.url}>
<span className="flex gap-1 items-center">
<VegaIcon name={getLinkIcon(link.type)} />
</span>
<span className="underline capitalize">
{link.type}
<span className="capitalize underline">{link.type}</span>
<VegaIcon name={VegaIconNames.OPEN_EXTERNAL} size={13} />
</span>
</ExternalLink>

View File

@ -1,15 +1,17 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { Accordion } from './accordion';
import { Accordion, AccordionItem } from './accordion';
describe('Accordion', () => {
it('should render successfully', () => {
render(
<Accordion
panels={[
{ title: 'Lorem ipsum title', content: 'Lorem ipsum content' },
]}
/>
<Accordion>
<AccordionItem
itemId="1"
title="Lorem ipsum title"
content="Lorem ipsum content"
/>
</Accordion>
);
expect(screen.queryByTestId('accordion-title')).toHaveTextContent(
'Lorem ipsum title'
@ -18,11 +20,13 @@ describe('Accordion', () => {
it('should toggle and open expansion panel', () => {
render(
<Accordion
panels={[
{ title: 'Lorem ipsum title', content: 'Lorem ipsum content' },
]}
/>
<Accordion>
<AccordionItem
itemId="1"
title="Lorem ipsum title"
content="Lorem ipsum content"
/>
</Accordion>
);
fireEvent.click(screen.getByTestId('accordion-toggle'));
expect(screen.queryByTestId('accordion-title')).toHaveTextContent(

View File

@ -1,5 +1,5 @@
import type { Story, Meta } from '@storybook/react';
import { Accordion } from './accordion';
import { Accordion, AccordionItem } from './accordion';
export default {
component: Accordion,
@ -10,18 +10,21 @@ const Template: Story = (args) => <Accordion panels={args.panels} />;
export const Default = Template.bind({});
Default.args = {
panels: [
{
title: 'Title of expansion panel',
content: 'Lorem ipsum',
},
{
title: 'Title of expansion panel',
content: 'Lorem ipsum',
},
{
title: 'Title of expansion panel',
content: 'Lorem ipsum',
},
children: [
<AccordionItem
itemId="1"
title={'Title of expansion panel'}
content={'Lorem ipsum'}
/>,
<AccordionItem
itemId="2"
title={'Title of expansion panel'}
content={'Lorem ipsum'}
/>,
<AccordionItem
itemId="3"
title={'Title of expansion panel'}
content={'Lorem ipsum'}
/>,
],
};

View File

@ -1,7 +1,6 @@
import React, { useState } from 'react';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
import classNames from 'classnames';
import { Icon } from '../icon';
import { VegaIcon, VegaIconNames } from '../icon';
export interface AccordionItemProps {
title: React.ReactNode;
@ -10,7 +9,6 @@ export interface AccordionItemProps {
export interface AccordionPanelProps extends AccordionItemProps {
itemId: string;
active: boolean;
}
export interface AccordionProps {
@ -19,23 +17,8 @@ export interface AccordionProps {
}
export const Accordion = ({ panels, children }: AccordionProps) => {
const [values, setValues] = useState<string[]>([]);
return (
<AccordionPrimitive.Root
type="multiple"
value={values}
onValueChange={setValues}
>
{panels?.map(({ title, content }, i) => (
<AccordionItem
key={`item-${i + 1}`}
itemId={`item-${i + 1}`}
title={title}
content={content}
active={values.includes(`item-${i + 1}`)}
/>
))}
<AccordionPrimitive.Root type="multiple">
{children}
</AccordionPrimitive.Root>
);
@ -45,11 +28,11 @@ export const AccordionItem = ({
title,
content,
itemId,
active,
}: AccordionPanelProps) => {
const triggerClassNames = classNames(
'w-full py-2',
'flex items-center justify-between border-b border-neutral-500 text-sm'
'flex items-center justify-between border-b border-vega-light-200 dark:border-vega-dark-200 text-sm',
'group'
);
return (
<AccordionPrimitive.Item value={itemId}>
@ -59,26 +42,28 @@ export const AccordionItem = ({
className={triggerClassNames}
>
<span data-testid="accordion-title">{title}</span>
<AccordionChevron active={active} aria-hidden />
<AccordionChevron aria-hidden />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
<AccordionPrimitive.Content data-testid="accordion-content-ref">
<div className="py-4 text-sm" data-testid="accordion-content">
{content}
</div>
<AccordionPrimitive.Content
className="py-3 text-sm"
data-testid="accordion-content"
>
{content}
</AccordionPrimitive.Content>
</AccordionPrimitive.Item>
);
};
export const AccordionChevron = ({ active }: { active: boolean }) => {
export const AccordionChevron = () => {
return (
<Icon
name="chevron-down"
className={classNames('transition ease-in-out duration-300', {
'transform rotate-180': active,
})}
aria-hidden
/>
<span
className={classNames(
'transform transition ease-in-out duration-300',
'group-data-[state=open]:rotate-180'
)}
>
<VegaIcon name={VegaIconNames.CHEVRON_DOWN} aria-hidden />
</span>
);
};

View File

@ -43,8 +43,8 @@ Link.displayName = 'Link';
export const ExternalLink = ({ children, className, ...props }: LinkProps) => (
<Link
className={classNames(
className,
'inline-flex gap-1 items-baseline underline-offset-4'
'inline-flex items-center gap-1 underline-offset-4',
className
)}
target="_blank"
data-testid="external-link"

View File

@ -42,7 +42,9 @@ export const Tooltip = ({
alignOffset={8}
className="max-w-sm border border-neutral-600 bg-neutral-100 dark:bg-neutral-800 px-4 py-2 z-20 rounded text-sm text-black dark:text-white break-word"
>
<div className="relative z-0">{description}</div>
<div className="relative z-0" data-testid="tooltip-content">
{description}
</div>
</Content>
</Portal>
)}