feat: #155 trading full market details (#549)

* feat: [#155] move grid tab to ui-toolkit and add info component in the tabbed ticket

* feat: [#155] add accordion component + storybook

* feat: [#155] styling updates, unit tests and export ui-toolkit

* feat: [#155] generate deal ticket query and info-market.tsx updates

* feat: [#155] move grid tab to ui-toolkit and add info component in the tabbed ticket

* feat: [#155] add accordion component + storybook

* feat: [#155] styling updates, unit tests and export ui-toolkit

* feat: [#155] generate deal ticket query and info-market.tsx updates

* fix: [#155] styling updates to mimic figma

* fix: [#155] fix trading-e2e generate deal ticket query

* feat: [#155] remove open field, add omits configurable

* fix: [#155] row component in info market

* feat: market info/accordion styling tweaks

* fix: [#155] add risk models and fix readme

* fix: [#155] update  generate deal ticket query to fix test

* fix: [#155] fix cypress test data

Co-authored-by: PeteWilliams <me@petewilliams.info>
This commit is contained in:
m.ray 2022-06-17 15:56:42 +01:00 committed by GitHub
parent 0414b26fed
commit 675716089a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 748 additions and 198 deletions

View File

@ -47,11 +47,11 @@ For shared Cypress logic, commands and steps.
### [Web3](./libs/web3)
A ulitity library for connecting to the Ethereum network and interacting with Vega Web3 contracts.
A utility library for connecting to the Ethereum network and interacting with Vega Web3 contracts.
### [React Helpers](./libs/react-helpers)
Generic react helpers that can be used across multilpe applications, along with other utilties.
Generic react helpers that can be used across multilpe applications, along with other utilities.
# 💻 Develop
@ -107,7 +107,7 @@ Visit the [Nx Documentation](https://nx.dev/getting-started/intro) to learn more
## Docker
The [Dockerfile](./Dockerfile) for running the frontends is pretty basic, merely building the application with the APP arg that is passed in and serving the application from [nginx](./nginx/nginx.conf). The only complexity that exists is that there is a script which allows the passing of run time environement variabels to the containers. See configuration below for how to do this.
The [Dockerfile](./Dockerfile) for running the frontends is pretty basic, merely building the application with the APP arg that is passed in and serving the application from [nginx](./nginx/nginx.conf). The only complexity that exists is that there is a script which allows the passing of run time environment variables to the containers. See configuration below for how to do this.
You can build any of the containers locally with the following command:
@ -123,7 +123,7 @@ docker run -p 3000:80 [TAG]
## Config
As envrionment variabels are build time and not run time in frontend applications. We have built a system which allows for passing run time environment variables, this generates a JSON file that will override the default environement vairbales that the container was built with (which is always testnet, using the default .env files).
As environment variables are build time and not run time in frontend applications. We have built a system which allows for passing run time environment variables, this generates a JSON file that will override the default environment variables that the container was built with (which is always testnet, using the default .env files).
In order to override specific environment variables you can pass these to the container like this:
@ -137,7 +137,7 @@ Which will now point the app to use a devnet data node. To see a list of all pos
Coming soon! You will be able to run the containers within Vega Capsule.
You can run against a local intance of Vega Cpasule today by using the .env.capsule present in the apps.
You can run against a local instance of Vega Capsule today by using the .env.capsule present in the apps.
# 📑 License

View File

@ -24,10 +24,10 @@ describe('NetworkParametersTable', () => {
);
const rows = screen.getAllByTestId('key-value-table-row');
expect(rows[0].children[0]).toHaveTextContent(
'market.fee.factors.infrastructureFee'
'Market Fee Factors Infrastructure Fee'
);
expect(rows[1].children[0]).toHaveTextContent(
'market.liquidityProvision.minLpStakeQuantumMultiple'
'Market Liquidity Provision Min Lp Stake Quantum Multiple'
);
expect(rows[0].children[1]).toHaveTextContent('0.0005');
expect(rows[1].children[1]).toHaveTextContent('1');
@ -54,10 +54,10 @@ describe('NetworkParametersTable', () => {
);
const rows = screen.getAllByTestId('key-value-table-row');
expect(rows[0].children[0]).toHaveTextContent(
'market.fee.factors.infrastructureFee'
'Market Fee Factors Infrastructure Fee'
);
expect(rows[1].children[0]).toHaveTextContent(
'market.liquidityProvision.minLpStakeQuantumMultiple'
'Market Liquidity Provision Min Lp Stake Quantum Multiple'
);
expect(rows[0].children[1]).toHaveTextContent('0.0005');
expect(rows[1].children[1]).toHaveTextContent('1');

View File

@ -16,6 +16,7 @@ import type {
NetworkParametersQuery_networkParameters,
} from './__generated__/NetworkParametersQuery';
import orderBy from 'lodash/orderBy';
import startCase from 'lodash/startCase';
const BIG_NUMBER_PARAMS = [
'spam.protection.delegation.min.tokens',
@ -42,7 +43,7 @@ export const renderRow = ({
const isSyntaxRow = isJsonObject(value);
return (
<KeyValueTableRow key={key} inline={!isSyntaxRow}>
{key}
{startCase(key)}
{isSyntaxRow ? (
<SyntaxHighlighter data={JSON.parse(value)} />
) : isNaN(Number(value)) ? (

View File

@ -1,22 +1,70 @@
import type { DealTicketQuery } from '@vegaprotocol/deal-ticket';
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import type { DealTicketQuery } from '@vegaprotocol/deal-ticket';
export const generateDealTicketQuery = (
override?: PartialDeep<DealTicketQuery>
): DealTicketQuery => {
const defaultResult: DealTicketQuery = {
market: {
__typename: 'Market',
id: 'market-0',
name: 'ETHBTC Quarterly (30 Jun 2022)',
decimalPlaces: 2,
positionDecimalPlaces: 0,
state: MarketState.Active,
tradingMode: MarketTradingMode.Continuous,
fees: {
__typename: 'Fees',
factors: {
__typename: 'FeeFactors',
makerFee: '0.0002',
infrastructureFee: '0.0005',
liquidityFee: '0.01',
},
},
priceMonitoringSettings: {
__typename: 'PriceMonitoringSettings',
parameters: {
__typename: 'PriceMonitoringParameters',
triggers: [
{
__typename: 'PriceMonitoringTrigger',
horizonSecs: 43200,
probability: 0.9999999,
auctionExtensionSecs: 600,
},
],
},
updateFrequencySecs: 1,
},
riskFactors: {
__typename: 'RiskFactor',
market:
'54b78c1b877e106842ae156332ccec740ad98d6bad43143ac6a029501dd7c6e0',
short: '0.008571790367285281',
long: '0.008508132993273576',
},
data: {
__typename: 'MarketData',
market: {
__typename: 'Market',
id: '54b78c1b877e106842ae156332ccec740ad98d6bad43143ac6a029501dd7c6e0',
},
markPrice: '5749',
indicativeVolume: '0',
bestBidVolume: '5',
bestOfferVolume: '1',
bestStaticBidVolume: '5',
bestStaticOfferVolume: '1',
},
tradableInstrument: {
__typename: 'TradableInstrument',
instrument: {
__typename: 'Instrument',
product: {
__typename: 'Future',
quoteName: 'BTC',
settlementAsset: {
__typename: 'Asset',
@ -24,11 +72,19 @@ export const generateDealTicketQuery = (
symbol: 'tBTC',
name: 'tBTC TEST',
},
__typename: 'Future',
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
riskModel: {
__typename: 'LogNormalRiskModel',
tau: 0.0001140771161,
riskAversionParameter: 0.01,
params: {
__typename: 'LogNormalModelParams',
r: 0.016,
sigma: 0.3,
mu: 0,
},
},
},
depth: {
__typename: 'MarketDepth',
@ -37,7 +93,6 @@ export const generateDealTicketQuery = (
price: '100',
},
},
__typename: 'Market',
},
};

View File

@ -12,9 +12,13 @@ import { t } from '@vegaprotocol/react-helpers';
import { AccountsContainer } from '@vegaprotocol/accounts';
import { DepthChartContainer } from '@vegaprotocol/market-depth';
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
import { GridTab, GridTabs } from '../../components/grid-tabs';
import { SelectMarketDialog } from '@vegaprotocol/market-list';
import { ArrowDown, PriceCellChange } from '@vegaprotocol/ui-toolkit';
import {
ArrowDown,
GridTab,
GridTabs,
PriceCellChange,
} from '@vegaprotocol/ui-toolkit';
import type { CandleClose } from '@vegaprotocol/types';
const TradingViews = {

View File

@ -3,10 +3,9 @@ import { t } from '@vegaprotocol/react-helpers';
import { PositionsContainer } from '@vegaprotocol/positions';
import { OrderListContainer } from '@vegaprotocol/order-list';
import { AccountsContainer } from '@vegaprotocol/accounts';
import { AnchorButton } from '@vegaprotocol/ui-toolkit';
import { AnchorButton, GridTab, GridTabs } from '@vegaprotocol/ui-toolkit';
import { WithdrawalsContainer } from './withdrawals/withdrawals-container';
import { GridTab, GridTabs } from '../../components/grid-tabs';
const Portfolio = () => {
const tabClassName = 'p-[16px] pl-[316px]';

View File

@ -1,132 +0,0 @@
/* tslint:disable */
/* eslint-disable */
// @generated
// This file was automatically generated and should not be edited.
import { MarketState, MarketTradingMode } from "@vegaprotocol/types";
// ====================================================
// GraphQL query operation: DealTicketQuery
// ====================================================
export interface DealTicketQuery_market_tradableInstrument_instrument_product_settlementAsset {
__typename: "Asset";
/**
* The id of the asset
*/
id: string;
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: string;
/**
* The full name of the asset (e.g: Great British Pound)
*/
name: string;
}
export interface DealTicketQuery_market_tradableInstrument_instrument_product {
__typename: "Future";
/**
* String representing the quote (e.g. BTCUSD -> USD is quote)
*/
quoteName: string;
/**
* The name of the asset (string)
*/
settlementAsset: DealTicketQuery_market_tradableInstrument_instrument_product_settlementAsset;
}
export interface DealTicketQuery_market_tradableInstrument_instrument {
__typename: "Instrument";
/**
* A reference to or instance of a fully specified product, including all required product parameters for that product (Product union)
*/
product: DealTicketQuery_market_tradableInstrument_instrument_product;
}
export interface DealTicketQuery_market_tradableInstrument {
__typename: "TradableInstrument";
/**
* An instance of or reference to a fully specified instrument.
*/
instrument: DealTicketQuery_market_tradableInstrument_instrument;
}
export interface DealTicketQuery_market_depth_lastTrade {
__typename: "Trade";
/**
* The price of the trade (probably initially the passive order price, other determination algorithms are possible though) (uint64)
*/
price: string;
}
export interface DealTicketQuery_market_depth {
__typename: "MarketDepth";
/**
* Last trade for the given market (if available)
*/
lastTrade: DealTicketQuery_market_depth_lastTrade | null;
}
export interface DealTicketQuery_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
/**
* Market full name
*/
name: string;
/**
* decimalPlaces indicates the number of decimal places that an integer must be shifted by in order to get a correct
* number denominated in the currency of the Market. (uint64)
*
* Examples:
* Currency Balance decimalPlaces Real Balance
* GBP 100 0 GBP 100
* GBP 100 2 GBP 1.00
* GBP 100 4 GBP 0.01
* GBP 1 4 GBP 0.0001 ( 0.01p )
*
* GBX (pence) 100 0 GBP 1.00 (100p )
* GBX (pence) 100 2 GBP 0.01 ( 1p )
* GBX (pence) 100 4 GBP 0.0001 ( 0.01p )
* GBX (pence) 1 4 GBP 0.000001 ( 0.0001p)
*/
decimalPlaces: number;
/**
* positionDecimalPlaces indicated the number of decimal places that an integer must be shifted in order to get a correct size (uint64).
* i.e. 0 means there are no fractional orders for the market, and order sizes are always whole sizes.
* 2 means sizes given as 10^2 * desired size, e.g. a desired size of 1.23 is represented as 123 in this market.
*/
positionDecimalPlaces: number;
/**
* Current state of the market
*/
state: MarketState;
/**
* Current mode of execution of the market
*/
tradingMode: MarketTradingMode;
/**
* An instance of or reference to a tradable instrument.
*/
tradableInstrument: DealTicketQuery_market_tradableInstrument;
/**
* Current depth on the order book for this market
*/
depth: DealTicketQuery_market_depth;
}
export interface DealTicketQuery {
/**
* An instrument that is trading on the VEGA network
*/
market: DealTicketQuery_market | null;
}
export interface DealTicketQueryVariables {
marketId: string;
}

View File

@ -9,12 +9,150 @@ import { MarketState, MarketTradingMode } from "@vegaprotocol/types";
// GraphQL query operation: DealTicketQuery
// ====================================================
export interface DealTicketQuery_market_fees_factors {
__typename: "FeeFactors";
/**
* The factor applied to calculate MakerFees, a non-negative float
*/
makerFee: string;
/**
* The factor applied to calculate InfrastructureFees, a non-negative float
*/
infrastructureFee: string;
/**
* The factor applied to calculate LiquidityFees, a non-negative float
*/
liquidityFee: string;
}
export interface DealTicketQuery_market_fees {
__typename: "Fees";
/**
* The factors used to calculate the different fees
*/
factors: DealTicketQuery_market_fees_factors;
}
export interface DealTicketQuery_market_priceMonitoringSettings_parameters_triggers {
__typename: "PriceMonitoringTrigger";
/**
* Price monitoring projection horizon τ in seconds (> 0).
*/
horizonSecs: number;
/**
* Price monitoring probability level p. (>0 and < 1)
*/
probability: number;
/**
* Price monitoring auction extension duration in seconds should the price
* breach it's theoretical level over the specified horizon at the specified
* probability level (> 0)
*/
auctionExtensionSecs: number;
}
export interface DealTicketQuery_market_priceMonitoringSettings_parameters {
__typename: "PriceMonitoringParameters";
/**
* The list of triggers for this price monitoring
*/
triggers: DealTicketQuery_market_priceMonitoringSettings_parameters_triggers[] | null;
}
export interface DealTicketQuery_market_priceMonitoringSettings {
__typename: "PriceMonitoringSettings";
/**
* Specified a set of PriceMonitoringParameters to be use for price monitoring purposes
*/
parameters: DealTicketQuery_market_priceMonitoringSettings_parameters | null;
/**
* How often (in seconds) the price monitoring bounds should be updated
*/
updateFrequencySecs: number;
}
export interface DealTicketQuery_market_riskFactors {
__typename: "RiskFactor";
/**
* market the risk factor was emitted for
*/
market: string;
/**
* short factor
*/
short: string;
/**
* long factor
*/
long: string;
}
export interface DealTicketQuery_market_data_market {
__typename: "Market";
/**
* Market ID
*/
id: string;
}
export interface DealTicketQuery_market_data {
__typename: "MarketData";
/**
* market id of the associated mark price
*/
market: DealTicketQuery_market_data_market;
/**
* the mark price (actually an unsigned int)
*/
markPrice: string;
/**
* indicative volume if the auction ended now, 0 if not in auction mode
*/
indicativeVolume: string;
/**
* the aggregated volume being bid at the best bid price.
*/
bestBidVolume: string;
/**
* the aggregated volume being offered at the best offer price.
*/
bestOfferVolume: string;
/**
* the aggregated volume being offered at the best static bid price, excluding pegged orders
*/
bestStaticBidVolume: string;
/**
* the aggregated volume being offered at the best static offer price, excluding pegged orders.
*/
bestStaticOfferVolume: string;
}
export interface DealTicketQuery_market_tradableInstrument_instrument_product_settlementAsset {
__typename: "Asset";
/**
* The id of the asset
*/
id: string;
/**
* The symbol of the asset (e.g: GBP)
*/
symbol: string;
/**
* The full name of the asset (e.g: Great British Pound)
*/
name: string;
}
export interface DealTicketQuery_market_tradableInstrument_instrument_product {
__typename: "Future";
/**
* String representing the quote (e.g. BTCUSD -> USD is quote)
*/
quoteName: string;
/**
* The name of the asset (string)
*/
settlementAsset: DealTicketQuery_market_tradableInstrument_instrument_product_settlementAsset;
}
export interface DealTicketQuery_market_tradableInstrument_instrument {
@ -25,12 +163,70 @@ export interface DealTicketQuery_market_tradableInstrument_instrument {
product: DealTicketQuery_market_tradableInstrument_instrument_product;
}
export interface DealTicketQuery_market_tradableInstrument_riskModel_LogNormalRiskModel_params {
__typename: "LogNormalModelParams";
/**
* r parameter
*/
r: number;
/**
* sigma parameter
*/
sigma: number;
/**
* mu parameter
*/
mu: number;
}
export interface DealTicketQuery_market_tradableInstrument_riskModel_LogNormalRiskModel {
__typename: "LogNormalRiskModel";
/**
* Tau parameter of the risk model
*/
tau: number;
/**
* Lambda parameter of the risk model
*/
riskAversionParameter: number;
/**
* Params for the log normal risk model
*/
params: DealTicketQuery_market_tradableInstrument_riskModel_LogNormalRiskModel_params;
}
export interface DealTicketQuery_market_tradableInstrument_riskModel_SimpleRiskModel_params {
__typename: "SimpleRiskModelParams";
/**
* Risk factor for long
*/
factorLong: number;
/**
* Risk factor for short
*/
factorShort: number;
}
export interface DealTicketQuery_market_tradableInstrument_riskModel_SimpleRiskModel {
__typename: "SimpleRiskModel";
/**
* Params for the simple risk model
*/
params: DealTicketQuery_market_tradableInstrument_riskModel_SimpleRiskModel_params;
}
export type DealTicketQuery_market_tradableInstrument_riskModel = DealTicketQuery_market_tradableInstrument_riskModel_LogNormalRiskModel | DealTicketQuery_market_tradableInstrument_riskModel_SimpleRiskModel;
export interface DealTicketQuery_market_tradableInstrument {
__typename: "TradableInstrument";
/**
* An instance of or reference to a fully specified instrument.
*/
instrument: DealTicketQuery_market_tradableInstrument_instrument;
/**
* A reference to a risk model that is valid for the instrument
*/
riskModel: DealTicketQuery_market_tradableInstrument_riskModel;
}
export interface DealTicketQuery_market_depth_lastTrade {
@ -90,6 +286,22 @@ export interface DealTicketQuery_market {
* Current mode of execution of the market
*/
tradingMode: MarketTradingMode;
/**
* Fees related data
*/
fees: DealTicketQuery_market_fees;
/**
* Price monitoring settings for the market
*/
priceMonitoringSettings: DealTicketQuery_market_priceMonitoringSettings;
/**
* risk factors for the market
*/
riskFactors: DealTicketQuery_market_riskFactors | null;
/**
* marketData for the given market
*/
data: DealTicketQuery_market_data | null;
/**
* An instance of or reference to a tradable instrument.
*/

View File

@ -0,0 +1,2 @@
export * from './DealTicketQuery';
export * from './OrderEvent';

View File

@ -1,11 +1,14 @@
import { gql, useQuery } from '@apollo/client';
import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit';
import {
AsyncRenderer,
GridTab,
GridTabs,
Splash,
} from '@vegaprotocol/ui-toolkit';
import { DealTicketManager } from './deal-ticket-manager';
import type {
DealTicketQuery,
DealTicketQuery_market,
} from '../__generated__/DealTicketQuery';
import { t } from '@vegaprotocol/react-helpers';
import { Info } from './info-market';
import type { DealTicketQuery_market, DealTicketQuery } from './__generated__';
const DEAL_TICKET_QUERY = gql`
query DealTicketQuery($marketId: ID!) {
@ -16,6 +19,40 @@ const DEAL_TICKET_QUERY = gql`
positionDecimalPlaces
state
tradingMode
fees {
factors {
makerFee
infrastructureFee
liquidityFee
}
}
priceMonitoringSettings {
parameters {
triggers {
horizonSecs
probability
auctionExtensionSecs
}
}
updateFrequencySecs
}
riskFactors {
market
short
long
}
data {
market {
id
}
markPrice
indicativeVolume
bestBidVolume
bestOfferVolume
bestStaticBidVolume
bestStaticOfferVolume
indicativeVolume
}
tradableInstrument {
instrument {
product {
@ -29,6 +66,23 @@ const DEAL_TICKET_QUERY = gql`
}
}
}
riskModel {
... on LogNormalRiskModel {
tau
riskAversionParameter
params {
r
sigma
mu
}
}
... on SimpleRiskModel {
params {
factorLong
factorShort
}
}
}
}
depth {
lastTrade {
@ -57,18 +111,45 @@ export const DealTicketContainer = ({
});
return (
<AsyncRenderer<DealTicketQuery> data={data} loading={loading} error={error}>
{data && data.market ? (
children ? (
children(data)
) : (
<DealTicketManager market={data.market} />
)
) : (
<Splash>
<p>{t('Could not load market')}</p>
</Splash>
)}
</AsyncRenderer>
<GridTabs>
<GridTab id="ticket" name={t('Ticket')}>
<AsyncRenderer<DealTicketQuery>
data={data}
loading={loading}
error={error}
>
{data && data.market ? (
children ? (
children(data)
) : (
<DealTicketManager market={data.market} />
)
) : (
<Splash>
<p>{t('Could not load market')}</p>
</Splash>
)}
</AsyncRenderer>
</GridTab>
<GridTab id="info" name={t('Info')}>
<AsyncRenderer<DealTicketQuery>
data={data}
loading={loading}
error={error}
>
{data && data.market ? (
children ? (
children(data)
) : (
<Info market={data.market} />
)
) : (
<Splash>
<p>{t('Could not load market')}</p>
</Splash>
)}
</AsyncRenderer>
</GridTab>
</GridTabs>
);
};

View File

@ -6,7 +6,7 @@ import { VegaTxStatus } from '@vegaprotocol/wallet';
import { DealTicket } from './deal-ticket';
import { OrderDialog } from './order-dialog';
import { useOrderSubmit } from '../hooks/use-order-submit';
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
export interface DealTicketManagerProps {
market: DealTicketQuery_market;

View File

@ -7,11 +7,11 @@ import { TypeSelector } from './type-selector';
import { SideSelector } from './side-selector';
import { DealTicketAmount } from './deal-ticket-amount';
import { TimeInForceSelector } from './time-in-force-selector';
import { ExpirySelector } from './expiry-selector';
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
import { useOrderValidation } from '../hooks/use-order-validation';
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
import type { Order } from '../utils/get-default-order';
import { getDefaultOrder } from '../utils/get-default-order';
import { useOrderValidation } from '../hooks/use-order-validation';
import { ExpirySelector } from './expiry-selector';
export type TransactionStatus = 'default' | 'pending';

View File

@ -0,0 +1,13 @@
export * from './__generated__';
export * from './deal-ticket-amount';
export * from './deal-ticket-container';
export * from './deal-ticket-limit-amount';
export * from './deal-ticket-manager';
export * from './deal-ticket-market-amount';
export * from './deal-ticket';
export * from './expiry-selector';
export * from './info-market';
export * from './order-dialog';
export * from './side-selector';
export * from './time-in-force-selector';
export * from './type-selector';

View File

@ -0,0 +1,193 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
addDecimalsFormatNumber,
formatNumber,
formatNumberPercentage,
t,
} from '@vegaprotocol/react-helpers';
import {
KeyValueTable,
KeyValueTableRow,
AccordionPanel,
} from '@vegaprotocol/ui-toolkit';
import startCase from 'lodash/startCase';
import pick from 'lodash/pick';
import omit from 'lodash/omit';
import type { DealTicketQuery_market } from './__generated__/DealTicketQuery';
import BigNumber from 'bignumber.js';
export interface InfoProps {
market: DealTicketQuery_market;
}
export const Info = ({ market }: InfoProps) => {
const headerClassName =
'text-h5 font-bold uppercase text-black dark:text-white';
return (
<div className="p-16 flex flex-col gap-32">
<div className="flex flex-col gap-12">
<p className={headerClassName}>{t('Market data')}</p>
<AccordionPanel
key="fees"
title={t('Current fees')}
content={
<>
<MarketInfoTable data={market.fees.factors} asPercentage={true} />
<p className="text-ui-small">
{t(
'All fees are paid by price takers and are a % of the trade notional value. Fees are not paid during auction uncrossing.'
)}
</p>
</>
}
/>
<AccordionPanel
key="market-data"
title={t('Market data')}
content={
<MarketInfoTable
data={market.data}
decimalPlaces={market.decimalPlaces}
/>
}
/>
</div>
<div className="flex flex-col gap-12">
<p className={headerClassName}>{t('Market specification')}</p>
<AccordionPanel
title={t('Key details')}
key="details"
content={
<MarketInfoTable
data={pick(
market,
'name',
'decimalPlaces',
'positionDecimalPlaces',
'tradingMode',
'state'
)}
/>
}
/>
<AccordionPanel
title={t('Instrument')}
key="instrument"
content={
<MarketInfoTable
data={{
product: market.tradableInstrument.instrument.product,
...market.tradableInstrument.instrument.product.settlementAsset,
}}
/>
}
/>
<AccordionPanel
title={t('Risk factors')}
key="risk-factors"
content={
<MarketInfoTable
data={market.riskFactors}
unformatted={true}
omits={['market', '__typename']}
/>
}
/>
<AccordionPanel
title={t('Risk model')}
key="risk-model"
content={
<MarketInfoTable
data={market.tradableInstrument.riskModel}
unformatted={true}
omits={[]}
/>
}
/>
{(market.priceMonitoringSettings?.parameters?.triggers ?? []).map(
(trigger, i) => (
<AccordionPanel
key={`trigger-${i}`}
title={t(`Price monitoring trigger ${i + 1}`)}
content={<MarketInfoTable data={trigger} />}
/>
)
)}
</div>
</div>
);
};
interface RowProps {
field: string;
value: any;
decimalPlaces?: number;
asPercentage?: boolean;
unformatted?: boolean;
}
const Row = ({
field,
value,
decimalPlaces,
asPercentage,
unformatted,
}: RowProps) => {
const isNumber = typeof value === 'number' || !isNaN(Number(value));
const isPrimitive = typeof value === 'string' || isNumber;
const className = 'text-black dark:text-white text-ui !px-0 !font-normal';
if (isPrimitive) {
return (
<KeyValueTableRow
key={field}
inline={isPrimitive}
muted={true}
noBorder={true}
dtClassName={className}
ddClassName={className}
>
{startCase(t(field))}
{isNumber && !unformatted
? decimalPlaces
? addDecimalsFormatNumber(value, decimalPlaces)
: asPercentage
? formatNumberPercentage(new BigNumber(value))
: formatNumber(Number(value))
: value}
</KeyValueTableRow>
);
}
return null;
};
export interface MarketInfoTableProps {
data: any;
decimalPlaces?: number;
asPercentage?: boolean;
unformatted?: boolean;
omits?: string[];
}
export const MarketInfoTable = ({
data,
decimalPlaces,
asPercentage,
unformatted,
omits = ['id', '__typename'],
}: MarketInfoTableProps) => {
return (
<KeyValueTable muted={true}>
{Object.entries(omit(data, ...omits) || []).map(([key, value]) => (
<Row
key={key}
field={key}
value={value}
decimalPlaces={decimalPlaces}
asPercentage={asPercentage}
unformatted={unformatted || key.toLowerCase().includes('volume')}
/>
))}
</KeyValueTable>
);
};

View File

@ -1,6 +1,6 @@
import { Icon, Loader } from '@vegaprotocol/ui-toolkit';
import type { ReactNode } from 'react';
import type { OrderEvent_busEvents_event_Order } from '../__generated__/OrderEvent';
import type { OrderEvent_busEvents_event_Order } from './__generated__/OrderEvent';
import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
import type { VegaTxState } from '@vegaprotocol/wallet';
import { VegaTxStatus } from '@vegaprotocol/wallet';

View File

@ -8,8 +8,8 @@ import type {
OrderEvent,
OrderEventVariables,
OrderEvent_busEvents_event_Order,
} from '../__generated__/OrderEvent';
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
} from '../components/__generated__/OrderEvent';
import type { DealTicketQuery_market } from '../components/__generated__/DealTicketQuery';
const ORDER_EVENT_SUB = gql`
subscription OrderEvent($partyId: ID!) {

View File

@ -8,8 +8,8 @@ import {
} from '@vegaprotocol/wallet';
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
import type { Order } from '../utils/get-default-order';
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
import { ERROR_SIZE_DECIMAL } from '../utils/validate-size';
import type { DealTicketQuery_market } from '../components/__generated__/DealTicketQuery';
export type ValidationProps = {
step: number;

View File

@ -1,16 +1,4 @@
export * from './components/expiry-selector';
export * from './components/type-selector';
export * from './components/time-in-force-selector';
export * from './components/side-selector';
export * from './components/deal-ticket';
export * from './components/deal-ticket-amount';
export * from './components/deal-ticket-limit-amount';
export * from './components/deal-ticket-market-amount';
export * from './components/deal-ticket-manager';
export * from './components/order-dialog';
export * from './components/deal-ticket-container';
export * from './__generated__/DealTicketQuery';
export * from './__generated__/OrderEvent';
export * from './components';
export * from './utils/get-default-order';
export * from './hooks/use-order-submit';
export * from './hooks/use-order-validation';

View File

@ -1,6 +1,6 @@
import { OrderTimeInForce, OrderType, OrderSide } from '@vegaprotocol/wallet';
import type { DealTicketQuery_market } from '../__generated__/DealTicketQuery';
import { toDecimal } from '@vegaprotocol/react-helpers';
import type { DealTicketQuery_market } from '../components/__generated__/DealTicketQuery';
export type Order =
| {

View File

@ -0,0 +1,33 @@
import { fireEvent, render, screen } from '@testing-library/react';
import { AccordionPanel } from './accordion';
describe('Accordion', () => {
it('should render successfully', () => {
render(
<AccordionPanel
title={'Lorem ipsum title'}
content={'Lorem ipsum content'}
/>
);
expect(screen.queryByTestId('accordion-title')).toHaveTextContent(
'Lorem ipsum title'
);
});
it('should toggle and open expansion panel', () => {
render(
<AccordionPanel
title={'Lorem ipsum title'}
content={'Lorem ipsum content'}
/>
);
fireEvent.click(screen.getByTestId('accordion-toggle'));
expect(screen.queryByTestId('accordion-title')).toHaveTextContent(
'Lorem ipsum title'
);
expect(screen.getByTestId('accordion-content')).toHaveTextContent(
'Lorem ipsum content'
);
});
});

View File

@ -0,0 +1,17 @@
import type { Story, Meta } from '@storybook/react';
import { AccordionPanel } from './accordion';
export default {
component: AccordionPanel,
title: 'Accordion',
} as Meta;
const Template: Story = (args) => (
<AccordionPanel title={args.title} content={args.content} />
);
export const Default = Template.bind({});
Default.args = {
title: 'Title of expansion panel',
content: 'Lorem ipsum',
};

View File

@ -0,0 +1,73 @@
import React, { useRef, useState } from 'react';
export interface AccordionProps {
title: React.ReactNode;
content: React.ReactNode;
}
export const AccordionPanel = ({ title, content }: AccordionProps) => {
const [active, setActive] = useState(false);
const [height, setHeight] = useState('0px');
const [rotate, setRotate] = useState(
'transform duration-300 ease rotate-180'
);
const contentSpace = useRef(null);
const toggleAccordion = () => {
setActive((prevState) => !prevState);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
setHeight(active ? '0px' : `${contentSpace.current.scrollHeight}px`);
setRotate(
active
? 'transform duration-300 ease rotate-180'
: 'transform duration-300 ease'
);
};
return (
<div className="flex flex-col">
<button
data-testid="accordion-toggle"
className="py-2 box-border appearance-none cursor-pointer focus:outline-none flex items-center justify-between border-b border-muted"
onClick={toggleAccordion}
>
<p
className="inline-block text-footnote font-bold text-h6 text-black dark:text-white pt-5 "
data-testid="accordion-title"
>
{title}
</p>
<svg
width="20"
height="20"
aria-label="chevron icon"
data-testid="accordion-chevron-icon"
className={`${rotate} inline-block fill-black dark:fill-white`}
viewBox="0 0 20 20"
fill="fillCurrent"
xmlns="http://www.w3.org/2000/svg"
>
<rect x="3" y="12" width="2" height="2" />
<rect x="5" y="10" width="2" height="2" />
<rect x="7" y="8" width="2" height="2" />
<rect x="9" y="6" width="2" height="2" />
<rect x="11" y="8" width="2" height="2" />
<rect x="13" y="10" width="2" height="2" />
<rect x="15" y="12" width="2" height="2" />
</svg>
</button>
<div
ref={contentSpace}
style={{ maxHeight: `${height}` }}
data-testid="accordion-content-ref"
className="overflow-auto transition-max-height duration-300 ease-in-out"
>
<div className="pb-5" data-testid="accordion-content">
{content}
</div>
</div>
</div>
);
};

View File

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

View File

@ -1,3 +1,4 @@
export * from './accordion';
export * from './ag-grid';
export * from './arrows';
export * from './async-renderer';
@ -7,13 +8,14 @@ export * from './card';
export * from './copy-with-tooltip';
export * from './dialog';
export * from './dropdown-menu';
export * from './link';
export * from './form-group';
export * from './grid-tabs';
export * from './icon';
export * from './indicator';
export * from './input';
export * from './input-error';
export * from './key-value-table';
export * from './link';
export * from './loader';
export * from './lozenge';
export * from './price-change';

View File

@ -63,6 +63,9 @@ export interface KeyValueTableRowProps
numerical?: boolean; // makes all values monospace
muted?: boolean;
inline?: boolean;
noBorder?: boolean;
dtClassName?: string;
ddClassName?: string;
}
export const KeyValueTableRow = ({
@ -71,9 +74,13 @@ export const KeyValueTableRow = ({
muted,
numerical,
inline = true,
noBorder = false,
dtClassName,
ddClassName,
}: KeyValueTableRowProps) => {
const dlClassName = classNames(
'flex gap-1 flex-wrap justify-between border-b first:border-t border-black dark:border-white',
'flex gap-1 flex-wrap justify-between ',
{ 'border-b first:border-t border-black dark:border-white': !noBorder },
{ 'flex-col items-start': !inline },
{ 'flex-row items-center': inline },
{
@ -82,18 +89,19 @@ export const KeyValueTableRow = ({
},
className
);
const dtClassName = `break-words font-medium uppercase align-top p-4 capitalize`;
const ddClassName = classNames(
const dtClassNames = `break-words font-medium uppercase align-top p-4 capitalize ${dtClassName}`;
const ddClassNames = classNames(
'align-top p-4 text-black/60 dark:text-white/60 break-words',
{
'font-mono': numerical,
}
},
ddClassName
);
return (
<dl className={dlClassName} data-testid="key-value-table-row">
<dt className={dtClassName}>{children[0]}</dt>
<dd className={ddClassName}>{children[1]}</dd>
<dt className={dtClassNames}>{children[0]}</dt>
<dd className={ddClassNames}>{children[1]}</dd>
</dl>
);
};