feat: market list mega dropdown (rich popover) (#889)
* feat: use MarketList query only * fix: remove Market.ts from index * feat: 30 refactor dialog, market list, change query * feat: #30 add indicativeVolume, total fees, tooltip, large dialog, tooltip accepts html description * fix: #30 total fees display in tooltip * fix: #30 toggle title on dialog open * fix: #30 fix order, price, high, low utils * fix: #30 fix test for market utils * feat: #30 add popover with markets to select * feat: #30 storybook popover * feat: #30 remove border on trigger and add some other classes * fix: #30 fix format check with format:write * feat: #30 add tooltip on taker fee * feat: #30 add tooltip on taker fee * fix: #30 format on select market list * fix: #30 remove unknown cast in test mock data * fix: #30 show markets where you have open positions * fix: #30 double check if open positions * fix: #30 dialog has only small/large sizes * feat: #30 add border on trigger and change padding and no wrap * fix: #30 if fees or factors are not found * fix: #30 remove markets.cy tests as markets page is now gone * fix #30 remove view full market list test * fix: #30 add rotating arrow on market title * fix: #30 add ease-in-out on popover * fix: #30 add ease-in-out on popover * fix: #30 align select a market table * fix: #30 select a market title * fix: #30 select a market title * fix: #30 fix any validateDOMnesting issues * fix: #30 show loading market data * fix: #30 add list of header columns * fix: #30 add list of header columns * fix: #30 small refactoring after review * fix: #30 update bold undreline class names * fix: #30 add large-mobile size * feat: #30 refactor select markets tables to render array of columns * fix: #30 remove size from select market dialog * fix: #30 add extra file for columns * fix: #30 update formtting * fix: #30 make sure popup closes on same market navigation * fix: rename market-utils, add calcCandle methods, store market id on select * fix: useMemo ondata and marketPositionData + orderbook stories fix * feat: #30 add open volume positions * fix: add market summary back * fix: update formatting * fix: use currentcolor on arrow * fix: create all markets page * fix: add overflow-y auto * fix: enlarge select market to get started dialog * fix: revert markets container * fix: use query to fix flickering on position markets * fix: edit unordered list in tooltips * fix: fix tooltip table * fix: fix home.cy.ts * chore: skip /markets tests
This commit is contained in:
parent
1be1a78a69
commit
0523b56e39
@ -92,16 +92,6 @@ describe('home', () => {
|
||||
cy.getByTestId(selectMarketOverlay).should('not.exist');
|
||||
cy.url().should('include', 'market-1');
|
||||
});
|
||||
|
||||
it('view full market list goes to markets page', () => {
|
||||
cy.getByTestId(selectMarketOverlay)
|
||||
.should('exist')
|
||||
.contains('Or view full market list')
|
||||
.click();
|
||||
cy.getByTestId(selectMarketOverlay).should('not.exist');
|
||||
cy.url().should('include', '/markets');
|
||||
cy.get('main[data-testid="markets"]').should('exist');
|
||||
});
|
||||
});
|
||||
|
||||
describe('no default found', () => {
|
||||
|
@ -11,7 +11,7 @@ describe('markets table', () => {
|
||||
cy.visit('/markets');
|
||||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
it.skip('renders correctly', () => {
|
||||
const marketRowHeaderClassname = 'div > span.ag-header-cell-text';
|
||||
const marketRowNameColumn = 'tradableInstrument.instrument.code';
|
||||
const marketRowSymbolColumn =
|
||||
@ -56,7 +56,7 @@ describe('markets table', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('can select an active market', () => {
|
||||
it.skip('can select an active market', () => {
|
||||
cy.wait('@Markets');
|
||||
cy.get('.ag-root-wrapper').should('be.visible');
|
||||
|
||||
|
@ -3,8 +3,11 @@ import { Vega } from '../icons/vega';
|
||||
import Link from 'next/link';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import classNames from 'classnames';
|
||||
import { useGlobalStore } from '../../stores/global';
|
||||
|
||||
export const Navbar = () => {
|
||||
const { marketId } = useGlobalStore();
|
||||
const tradingPath = marketId ? `/markets/${marketId}` : '/';
|
||||
return (
|
||||
<nav className="flex items-center">
|
||||
<Link href="/" passHref={true}>
|
||||
@ -14,7 +17,11 @@ export const Navbar = () => {
|
||||
</a>
|
||||
</Link>
|
||||
{[
|
||||
{ name: t('Trading'), path: '/markets' },
|
||||
{
|
||||
name: t('Trading'),
|
||||
path: tradingPath,
|
||||
exact: false,
|
||||
},
|
||||
{ name: t('Portfolio'), path: '/portfolio' },
|
||||
].map((route) => (
|
||||
<NavLink key={route.path} {...route} />
|
||||
|
@ -20,11 +20,7 @@ const MARKETS_QUERY = gql`
|
||||
`;
|
||||
|
||||
const getMarketList = ({ markets = [] }: MarketsLanding) => {
|
||||
return orderBy(
|
||||
markets,
|
||||
['marketTimestamps.open', 'id'],
|
||||
['asc', 'asc', 'asc']
|
||||
);
|
||||
return orderBy(markets, ['marketTimestamps.open', 'id'], ['asc', 'asc']);
|
||||
};
|
||||
|
||||
export function Index() {
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { SelectMarketDialog } from '@vegaprotocol/market-list';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Interval } from '@vegaprotocol/types';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { useRouter } from 'next/router';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { PageQueryContainer } from '../../components/page-query-container';
|
||||
import { TradeGrid, TradePanels } from './trade-grid';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { LandingDialog } from '@vegaprotocol/market-list';
|
||||
import type { Market, MarketVariables } from './__generated__/Market';
|
||||
import { Interval } from '@vegaprotocol/types';
|
||||
import { TradeGrid, TradePanels } from './trade-grid';
|
||||
|
||||
import type { Market, MarketVariables } from './__generated__/Market';
|
||||
// Top level page query
|
||||
const MARKET_QUERY = gql`
|
||||
query Market($marketId: ID!, $interval: Interval!, $since: String!) {
|
||||
@ -118,9 +118,17 @@ const MarketPage = ({ id }: { id?: string }) => {
|
||||
) : (
|
||||
<TradePanels market={market} />
|
||||
)}
|
||||
<LandingDialog
|
||||
open={store.landingDialog}
|
||||
setOpen={(isOpen) => store.setLandingDialog(isOpen)}
|
||||
<SelectMarketDialog
|
||||
dialogOpen={store.landingDialog}
|
||||
setDialogOpen={(isOpen: boolean) =>
|
||||
store.setLandingDialog(isOpen)
|
||||
}
|
||||
onSelect={(marketId: string) => {
|
||||
if (marketId && store.marketId !== marketId) {
|
||||
store.setMarketId(marketId);
|
||||
}
|
||||
}}
|
||||
title={t('Select a market to get started')}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -1,37 +1,38 @@
|
||||
import classNames from 'classnames';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useState } from 'react';
|
||||
import 'allotment/dist/style.css';
|
||||
import {
|
||||
DealTicketContainer,
|
||||
MarketInfoContainer,
|
||||
} from '@vegaprotocol/deal-ticket';
|
||||
import { OrderListContainer } from '@vegaprotocol/orders';
|
||||
import { TradesContainer } from '@vegaprotocol/trades';
|
||||
import { PositionsContainer } from '@vegaprotocol/positions';
|
||||
import { OrderbookContainer } from '@vegaprotocol/market-depth';
|
||||
import type { Market_market } from './__generated__/Market';
|
||||
import { SelectMarketPopover } from '@vegaprotocol/market-list';
|
||||
import { OrderListContainer } from '@vegaprotocol/orders';
|
||||
import { PositionsContainer } from '@vegaprotocol/positions';
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatLabel,
|
||||
t,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { TradesContainer } from '@vegaprotocol/trades';
|
||||
import { AuctionTrigger, MarketTradingMode } from '@vegaprotocol/types';
|
||||
import { Allotment, LayoutPriority } from 'allotment';
|
||||
import classNames from 'classnames';
|
||||
import { useState } from 'react';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import type { Market_market } from './__generated__/Market';
|
||||
import type { CandleClose } from '@vegaprotocol/types';
|
||||
import { useGlobalStore } from '../../stores';
|
||||
import { AccountsContainer } from '@vegaprotocol/accounts';
|
||||
import { DepthChartContainer } from '@vegaprotocol/market-depth';
|
||||
import { CandlesChartContainer } from '@vegaprotocol/candles-chart';
|
||||
import { SelectMarketDialog } from '@vegaprotocol/market-list';
|
||||
import {
|
||||
ArrowDown,
|
||||
Tab,
|
||||
Tabs,
|
||||
PriceCellChange,
|
||||
Tooltip,
|
||||
ResizablePanel,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import type { CandleClose } from '@vegaprotocol/types';
|
||||
import { AuctionTrigger } from '@vegaprotocol/types';
|
||||
import { MarketTradingMode } from '@vegaprotocol/types';
|
||||
import { Allotment, LayoutPriority } from 'allotment';
|
||||
import { TradingModeTooltip } from '../../components/trading-mode-tooltip';
|
||||
|
||||
const TradingViews = {
|
||||
@ -57,34 +58,33 @@ export const TradeMarketHeader = ({
|
||||
market,
|
||||
className,
|
||||
}: TradeMarketHeaderProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const candlesClose: string[] = (market?.candles || [])
|
||||
.map((candle) => candle?.close)
|
||||
.filter((c): c is CandleClose => c !== null);
|
||||
const headerItemClassName = 'whitespace-nowrap flex flex-col';
|
||||
const headerItemClassName = 'whitespace-nowrap flex flex-col ';
|
||||
const itemClassName =
|
||||
'font-sans font-normal mb-0 text-black-60 dark:text-white-80 text-ui-small';
|
||||
const itemValueClassName =
|
||||
'font-sans tracking-tighter text-black dark:text-white text-ui';
|
||||
const headerClassName = classNames(
|
||||
'w-full p-8 mb-4 bg-white dark:bg-black',
|
||||
'w-full bg-white dark:bg-black',
|
||||
className
|
||||
);
|
||||
|
||||
const store = useGlobalStore();
|
||||
const onSelect = (marketId: string) => {
|
||||
if (marketId && store.marketId !== marketId) {
|
||||
store.setMarketId(marketId);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<header className={headerClassName}>
|
||||
<SelectMarketDialog dialogOpen={open} setDialogOpen={setOpen} />
|
||||
<div className="flex flex-col md:flex-row gap-20 md:gap-64 ml-auto mr-8">
|
||||
<button
|
||||
onClick={() => setOpen(!open)}
|
||||
className="shrink-0 text-vega-pink dark:text-vega-yellow font-medium text-h5 flex items-center gap-8 px-4 py-0 h-37 hover:bg-black/10 dark:hover:bg-white/20"
|
||||
>
|
||||
<span className="break-words text-left">{market.name}</span>
|
||||
<ArrowDown color="yellow" borderX={8} borderTop={12} />
|
||||
</button>
|
||||
|
||||
<SelectMarketPopover marketName={market.name} onSelect={onSelect} />
|
||||
<div
|
||||
data-testid="market-summary"
|
||||
className="flex flex-auto items-start gap-64 overflow-x-auto whitespace-nowrap"
|
||||
className="flex flex-auto items-start gap-64 overflow-x-auto whitespace-nowrap py-8 pr-8"
|
||||
>
|
||||
<div className={headerItemClassName}>
|
||||
<span className={itemClassName}>{t('Change (24h)')}</span>
|
||||
|
@ -10,6 +10,8 @@ interface GlobalStore {
|
||||
setVegaNetworkSwitcherDialog: (isOpen: boolean) => void;
|
||||
landingDialog: boolean;
|
||||
setLandingDialog: (isOpen: boolean) => void;
|
||||
marketId: string | null;
|
||||
setMarketId: (marketId: string) => void;
|
||||
}
|
||||
|
||||
export const useGlobalStore = create((set: SetState<GlobalStore>) => ({
|
||||
@ -29,4 +31,8 @@ export const useGlobalStore = create((set: SetState<GlobalStore>) => ({
|
||||
setLandingDialog: (isOpen: boolean) => {
|
||||
set({ landingDialog: isOpen });
|
||||
},
|
||||
marketId: null,
|
||||
setMarketId: (id: string) => {
|
||||
set({ marketId: id });
|
||||
},
|
||||
}));
|
||||
|
@ -1,6 +1 @@
|
||||
export * from './lib/accounts-table';
|
||||
export * from './lib/accounts-container';
|
||||
export * from './lib/accounts-data-provider';
|
||||
export * from './lib/__generated__/AccountFields';
|
||||
export * from './lib/__generated__/Accounts';
|
||||
export * from './lib/__generated__/AccountSubscribe';
|
||||
export * from './lib';
|
||||
|
3
libs/accounts/src/lib/__generated__/index.ts
generated
Normal file
3
libs/accounts/src/lib/__generated__/index.ts
generated
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './AccountFields';
|
||||
export * from './AccountSubscribe';
|
||||
export * from './Accounts';
|
5
libs/accounts/src/lib/index.ts
Normal file
5
libs/accounts/src/lib/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './__generated__';
|
||||
export * from './accounts-container';
|
||||
export * from './accounts-data-provider';
|
||||
export * from './accounts-manager';
|
||||
export * from './accounts-table';
|
@ -1,5 +1 @@
|
||||
export * from './lib/candles-chart';
|
||||
export * from './lib/__generated__/Candles';
|
||||
export * from './lib/__generated__/CandleFields';
|
||||
export * from './lib/__generated__/CandlesSub';
|
||||
export * from './lib/__generated__/Chart';
|
||||
export * from './lib';
|
||||
|
4
libs/candles-chart/src/lib/__generated__/index.ts
generated
Normal file
4
libs/candles-chart/src/lib/__generated__/index.ts
generated
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './CandleFields';
|
||||
export * from './Candles';
|
||||
export * from './CandlesSub';
|
||||
export * from './Chart';
|
3
libs/candles-chart/src/lib/index.ts
Normal file
3
libs/candles-chart/src/lib/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './__generated__';
|
||||
export * from './candles-chart';
|
||||
export * from './data-source';
|
@ -19,6 +19,7 @@ import omit from 'lodash/omit';
|
||||
import type { MarketInfoQuery, MarketInfoQuery_market } from './__generated__';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import { totalFees } from '@vegaprotocol/market-list';
|
||||
|
||||
const MARKET_INFO_QUERY = gql`
|
||||
query MarketInfoQuery($marketId: ID!) {
|
||||
@ -176,7 +177,13 @@ export const Info = ({ market }: InfoProps) => {
|
||||
title: t('Current fees'),
|
||||
content: (
|
||||
<>
|
||||
<MarketInfoTable data={market.fees.factors} asPercentage={true} />
|
||||
<MarketInfoTable
|
||||
data={{
|
||||
...market.fees.factors,
|
||||
totalFees: totalFees(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.'
|
||||
|
@ -206,7 +206,7 @@ export const MarketSelector = ({ market, setMarket, ItemRenderer }: Props) => {
|
||||
<hr className="mb-5" />
|
||||
<div
|
||||
className={classNames(
|
||||
'md:absolute z-20 flex flex-col top-[30px] z-10 md:drop-shadow-md md:border-1 md:border-black md:dark:border-white bg-white dark:bg-black text-black dark:text-white min-w-full md:max-h-[200px] overflow-y-auto',
|
||||
'md:absolute z-20 flex flex-col top-[30px] md:drop-shadow-md md:border-1 md:border-black md:dark:border-white bg-white dark:bg-black text-black dark:text-white min-w-full md:max-h-[200px] overflow-y-auto',
|
||||
showPane ? 'block' : 'hidden'
|
||||
)}
|
||||
data-testid="market-pane"
|
||||
@ -273,11 +273,11 @@ export const MarketSelector = ({ market, setMarket, ItemRenderer }: Props) => {
|
||||
<>
|
||||
{!dialogContent && selectorContent}
|
||||
<Dialog
|
||||
titleClassNames="uppercase font-alpha"
|
||||
contentClassNames="left-[0px] top-[99px] h-[calc(100%-99px)] border-0 translate-x-[0] translate-y-[0] border-none overflow-y-auto"
|
||||
title={t('Select Market')}
|
||||
titleClassNames="font-alpha"
|
||||
title={t('Select market')}
|
||||
open={Boolean(dialogContent)}
|
||||
onChange={handleDialogOnchange}
|
||||
size="large"
|
||||
>
|
||||
{dialogContent}
|
||||
</Dialog>
|
||||
|
@ -54,13 +54,11 @@ export const TimeInForceSelector = ({
|
||||
className="w-full"
|
||||
data-testid="order-tif"
|
||||
>
|
||||
{options.map(([key, value]) => {
|
||||
return (
|
||||
<option key={key} value={value}>
|
||||
{`${timeInForceLabel(value)} (${key})`}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
{options.map(([key, value]) => (
|
||||
<option key={key} value={value}>
|
||||
{`${timeInForceLabel(value)} (${key})`}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</FormGroup>
|
||||
);
|
||||
|
@ -1,3 +1 @@
|
||||
export * from './lib/depth-chart';
|
||||
export * from './lib/orderbook-container';
|
||||
export * from './lib/__generated__/MarketDepth';
|
||||
export * from './lib';
|
||||
|
2
libs/market-depth/src/lib/__generated__/index.ts
generated
Normal file
2
libs/market-depth/src/lib/__generated__/index.ts
generated
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './MarketDepth';
|
||||
export * from './MarketDepthSubscription';
|
9
libs/market-depth/src/lib/index.ts
Normal file
9
libs/market-depth/src/lib/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export * from './__generated__';
|
||||
export * from './depth-chart';
|
||||
export * from './market-depth-data-provider';
|
||||
export * from './orderbook-container';
|
||||
export * from './orderbook-data';
|
||||
export * from './orderbook-manager';
|
||||
export * from './orderbook-row';
|
||||
export * from './orderbook.stories';
|
||||
export * from './orderbook';
|
@ -8,7 +8,7 @@ type Props = Omit<MockDataGeneratorParams, 'resolution'> & {
|
||||
decimalPlaces: number;
|
||||
};
|
||||
|
||||
const OrderbokMockDataProvider = ({ decimalPlaces, ...props }: Props) => {
|
||||
const OrderbookMockDataProvider = ({ decimalPlaces, ...props }: Props) => {
|
||||
const [resolution, setResolution] = useState(1);
|
||||
return (
|
||||
<div className="absolute inset-0 dark:bg-black dark:text-white-60 bg-white text-black-60">
|
||||
@ -17,6 +17,7 @@ const OrderbokMockDataProvider = ({ decimalPlaces, ...props }: Props) => {
|
||||
style={{ width: '400px' }}
|
||||
>
|
||||
<Orderbook
|
||||
positionDecimalPlaces={0}
|
||||
onResolutionChange={setResolution}
|
||||
decimalPlaces={decimalPlaces}
|
||||
{...generateMockData({ ...props, resolution })}
|
||||
@ -27,11 +28,13 @@ const OrderbokMockDataProvider = ({ decimalPlaces, ...props }: Props) => {
|
||||
};
|
||||
|
||||
export default {
|
||||
component: OrderbokMockDataProvider,
|
||||
component: OrderbookMockDataProvider,
|
||||
title: 'Orderbook',
|
||||
} as Meta;
|
||||
|
||||
const Template: Story<Props> = (args) => <OrderbokMockDataProvider {...args} />;
|
||||
const Template: Story<Props> = (args) => (
|
||||
<OrderbookMockDataProvider {...args} />
|
||||
);
|
||||
|
||||
export const Continuous = Template.bind({});
|
||||
Continuous.args = {
|
||||
|
@ -1,2 +1 @@
|
||||
export * from './lib/components';
|
||||
export * from './lib/utils';
|
||||
export * from './lib';
|
||||
|
@ -47,4 +47,8 @@ export interface MarketDataFields {
|
||||
* what triggered an auction (if an auction was started)
|
||||
*/
|
||||
trigger: AuctionTrigger;
|
||||
/**
|
||||
* indicative volume if the auction ended now, 0 if not in auction mode
|
||||
*/
|
||||
indicativeVolume: string;
|
||||
}
|
@ -47,6 +47,10 @@ export interface MarketDataSub_marketData {
|
||||
* what triggered an auction (if an auction was started)
|
||||
*/
|
||||
trigger: AuctionTrigger;
|
||||
/**
|
||||
* indicative volume if the auction ended now, 0 if not in auction mode
|
||||
*/
|
||||
indicativeVolume: string;
|
||||
}
|
||||
|
||||
export interface MarketDataSub {
|
@ -3,18 +3,50 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { Interval, MarketState, MarketTradingMode } from "@vegaprotocol/types";
|
||||
import { Interval, MarketState, MarketTradingMode, AuctionTrigger } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: MarketList
|
||||
// ====================================================
|
||||
|
||||
export interface MarketList_markets_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 MarketList_markets_fees {
|
||||
__typename: "Fees";
|
||||
/**
|
||||
* The factors used to calculate the different fees
|
||||
*/
|
||||
factors: MarketList_markets_fees_factors;
|
||||
}
|
||||
|
||||
export interface MarketList_markets_data_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Current state of the market
|
||||
*/
|
||||
state: MarketState;
|
||||
/**
|
||||
* Current mode of execution of the market
|
||||
*/
|
||||
tradingMode: MarketTradingMode;
|
||||
}
|
||||
|
||||
export interface MarketList_markets_data {
|
||||
@ -23,10 +55,26 @@ export interface MarketList_markets_data {
|
||||
* market id of the associated mark price
|
||||
*/
|
||||
market: MarketList_markets_data_market;
|
||||
/**
|
||||
* the highest price level on an order book for buy orders.
|
||||
*/
|
||||
bestBidPrice: string;
|
||||
/**
|
||||
* the lowest price level on an order book for offer orders.
|
||||
*/
|
||||
bestOfferPrice: string;
|
||||
/**
|
||||
* the mark price (actually an unsigned int)
|
||||
*/
|
||||
markPrice: string;
|
||||
/**
|
||||
* what triggered an auction (if an auction was started)
|
||||
*/
|
||||
trigger: AuctionTrigger;
|
||||
/**
|
||||
* indicative volume if the auction ended now, 0 if not in auction mode
|
||||
*/
|
||||
indicativeVolume: string;
|
||||
}
|
||||
|
||||
export interface MarketList_markets_tradableInstrument_instrument_metadata {
|
||||
@ -37,6 +85,22 @@ export interface MarketList_markets_tradableInstrument_instrument_metadata {
|
||||
tags: string[] | null;
|
||||
}
|
||||
|
||||
export interface MarketList_markets_tradableInstrument_instrument_product_settlementAsset {
|
||||
__typename: "Asset";
|
||||
/**
|
||||
* The symbol of the asset (e.g: GBP)
|
||||
*/
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
export interface MarketList_markets_tradableInstrument_instrument_product {
|
||||
__typename: "Future";
|
||||
/**
|
||||
* The name of the asset (string)
|
||||
*/
|
||||
settlementAsset: MarketList_markets_tradableInstrument_instrument_product_settlementAsset;
|
||||
}
|
||||
|
||||
export interface MarketList_markets_tradableInstrument_instrument {
|
||||
__typename: "Instrument";
|
||||
/**
|
||||
@ -51,6 +115,10 @@ export interface MarketList_markets_tradableInstrument_instrument {
|
||||
* Metadata for this instrument
|
||||
*/
|
||||
metadata: MarketList_markets_tradableInstrument_instrument_metadata;
|
||||
/**
|
||||
* A reference to or instance of a fully specified product, including all required product parameters for that product (Product union)
|
||||
*/
|
||||
product: MarketList_markets_tradableInstrument_instrument_product;
|
||||
}
|
||||
|
||||
export interface MarketList_markets_tradableInstrument {
|
||||
@ -83,6 +151,14 @@ export interface MarketList_markets_candles {
|
||||
* Close price (uint64)
|
||||
*/
|
||||
close: string;
|
||||
/**
|
||||
* High price (uint64)
|
||||
*/
|
||||
high: string;
|
||||
/**
|
||||
* Low price (uint64)
|
||||
*/
|
||||
low: string;
|
||||
}
|
||||
|
||||
export interface MarketList_markets {
|
||||
@ -91,6 +167,10 @@ export interface MarketList_markets {
|
||||
* 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)
|
||||
@ -108,6 +188,12 @@ export interface MarketList_markets {
|
||||
* 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
|
||||
*/
|
||||
@ -116,6 +202,10 @@ export interface MarketList_markets {
|
||||
* Current mode of execution of the market
|
||||
*/
|
||||
tradingMode: MarketTradingMode;
|
||||
/**
|
||||
* Fees related data
|
||||
*/
|
||||
fees: MarketList_markets_fees;
|
||||
/**
|
||||
* marketData for the given market
|
||||
*/
|
@ -1,4 +1,3 @@
|
||||
export * from './MarketDataFields';
|
||||
export * from './MarketDataSub';
|
||||
export * from './MarketList';
|
||||
export * from './Markets';
|
@ -1,2 +1,4 @@
|
||||
export * from './landing';
|
||||
export * from './markets-container';
|
||||
export * from './select-market-columns';
|
||||
export * from './select-market-table';
|
||||
export * from './select-market';
|
||||
|
@ -1,3 +0,0 @@
|
||||
export * from './landing-dialog';
|
||||
export * from './select-market-dialog';
|
||||
export * from './select-market-list';
|
@ -1,41 +0,0 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Interval } from '@vegaprotocol/types';
|
||||
import { AsyncRenderer, Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { MARKET_LIST_QUERY } from '../markets-container/markets-data-provider';
|
||||
import type { MarketList } from '../markets-container/__generated__/MarketList';
|
||||
|
||||
import { SelectMarketList } from './select-market-list';
|
||||
|
||||
interface LandingDialogProps {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const LandingDialog = ({ open, setOpen }: LandingDialogProps) => {
|
||||
const setClose = () => setOpen(false);
|
||||
|
||||
const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600;
|
||||
const yTimestamp = new Date(yesterday * 1000).toISOString();
|
||||
|
||||
const { data, loading, error } = useQuery<MarketList>(MARKET_LIST_QUERY, {
|
||||
variables: { interval: Interval.I1H, since: yTimestamp },
|
||||
});
|
||||
|
||||
return (
|
||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||
<Dialog
|
||||
title={t('Select a market to get started')}
|
||||
intent={Intent.Primary}
|
||||
open={open}
|
||||
onChange={setClose}
|
||||
titleClassNames={
|
||||
'font-bold font-sans text-3xl tracking-tight mb-0 pl-8'
|
||||
}
|
||||
contentClassNames={'md:w-[520px] lg:w-[520px] w-full'}
|
||||
>
|
||||
<SelectMarketList data={data} onSelect={setClose} />
|
||||
</Dialog>
|
||||
</AsyncRenderer>
|
||||
);
|
||||
};
|
@ -1,33 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { SelectMarketDialog } from './select-market-dialog';
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
|
||||
jest.mock(
|
||||
'next/link',
|
||||
() =>
|
||||
({ children }: { children: ReactNode }) =>
|
||||
children
|
||||
);
|
||||
|
||||
jest.mock('next/router', () => ({
|
||||
useRouter() {
|
||||
return {
|
||||
route: '/',
|
||||
pathname: '',
|
||||
query: '',
|
||||
asPath: '',
|
||||
};
|
||||
},
|
||||
}));
|
||||
|
||||
describe('SelectMarketDialog', () => {
|
||||
it('should render select a market dialog', () => {
|
||||
render(
|
||||
<MockedProvider>
|
||||
<SelectMarketDialog dialogOpen={true} setDialogOpen={() => jest.fn()} />
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(screen.getByText('Select a market')).toBeTruthy();
|
||||
});
|
||||
});
|
@ -1,28 +0,0 @@
|
||||
import { Dialog, Intent } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { MarketsContainer } from '../markets-container';
|
||||
|
||||
export interface SelectMarketListProps {
|
||||
dialogOpen: boolean;
|
||||
setDialogOpen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const SelectMarketDialog = ({
|
||||
dialogOpen,
|
||||
setDialogOpen,
|
||||
}: SelectMarketListProps) => {
|
||||
return (
|
||||
<Dialog
|
||||
title={t('Select a market')}
|
||||
intent={Intent.Primary}
|
||||
open={dialogOpen}
|
||||
onChange={() => setDialogOpen(false)}
|
||||
titleClassNames="font-bold font-sans text-3xl tracking-tight mb-0 pl-8"
|
||||
contentClassNames="w-full lg:w-[1020px]"
|
||||
>
|
||||
<div className="h-[200px] w-full">
|
||||
<MarketsContainer />
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
@ -1,118 +0,0 @@
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
PriceCell,
|
||||
t,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import type { CandleClose } from '@vegaprotocol/types';
|
||||
import { PriceCellChange, Sparkline } from '@vegaprotocol/ui-toolkit';
|
||||
import Link from 'next/link';
|
||||
import { mapDataToMarketList } from '../../utils';
|
||||
import type { MarketList } from '../markets-container/__generated__/MarketList';
|
||||
|
||||
export interface SelectMarketListDataProps {
|
||||
data: MarketList | undefined;
|
||||
onSelect: (id: string) => void;
|
||||
}
|
||||
|
||||
export const SelectMarketList = ({
|
||||
data,
|
||||
onSelect,
|
||||
}: SelectMarketListDataProps) => {
|
||||
const handleKeyPress = (
|
||||
event: React.KeyboardEvent<HTMLAnchorElement>,
|
||||
id: string
|
||||
) => {
|
||||
if (event.key === 'Enter') {
|
||||
return onSelect(id);
|
||||
}
|
||||
};
|
||||
const thClassNames = (direction: 'left' | 'right') =>
|
||||
`px-8 text-${direction} font-sans font-normal text-ui-small leading-9 mb-0 text-dark/80 dark:text-white/80`;
|
||||
const tdClassNames =
|
||||
'px-8 font-sans leading-9 capitalize text-ui-small text-right';
|
||||
|
||||
const boldUnderlineClassNames =
|
||||
'px-8 underline font-sans text-base leading-9 font-bold tracking-tight decoration-solid text-ui light:hover:text-black/80 dark:hover:text-white/80';
|
||||
return (
|
||||
<div
|
||||
className="max-h-[40rem] overflow-x-auto"
|
||||
data-testid="select-market-list"
|
||||
>
|
||||
<table className="relative h-full min-w-full whitespace-nowrap">
|
||||
<thead className="sticky top-0 z-10 dark:bg-black bg-white">
|
||||
<tr>
|
||||
<th className={thClassNames('left')}>Market</th>
|
||||
<th className={thClassNames('right')}>Last price</th>
|
||||
<th className={thClassNames('right')}>Change (24h)</th>
|
||||
<th className={thClassNames('right')}></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{data &&
|
||||
mapDataToMarketList(data)
|
||||
.slice(0, 12)
|
||||
?.map(({ id, marketName, lastPrice, candles, decimalPlaces }) => {
|
||||
const candlesClose: string[] = candles
|
||||
.map((candle) => candle?.close)
|
||||
.filter((c): c is CandleClose => c !== null);
|
||||
return (
|
||||
<tr
|
||||
key={id}
|
||||
className={`hover:bg-black/20 dark:hover:bg-white/20 cursor-pointer relative`}
|
||||
>
|
||||
<td className={`${boldUnderlineClassNames} relative`}>
|
||||
<Link href={`/markets/${id}`} passHref={true}>
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid,jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
onKeyPress={(event) => handleKeyPress(event, id)}
|
||||
onClick={() => onSelect(id)}
|
||||
data-testid={`market-link-${id}`}
|
||||
className={`focus:decoration-vega-yellow`}
|
||||
>
|
||||
{marketName}
|
||||
</a>
|
||||
</Link>
|
||||
</td>
|
||||
<td className={tdClassNames}>
|
||||
{lastPrice && (
|
||||
<PriceCell
|
||||
value={BigInt(lastPrice)}
|
||||
valueFormatted={addDecimalsFormatNumber(
|
||||
lastPrice.toString(),
|
||||
decimalPlaces,
|
||||
2
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td className={`${tdClassNames} `}>
|
||||
<PriceCellChange
|
||||
candles={candlesClose}
|
||||
decimalPlaces={decimalPlaces}
|
||||
/>
|
||||
</td>
|
||||
<td className="px-8">
|
||||
{candles && (
|
||||
<Sparkline
|
||||
width={100}
|
||||
height={20}
|
||||
muted={false}
|
||||
data={candlesClose.map((c) => Number(c))}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a
|
||||
className={`${boldUnderlineClassNames} text-ui-small focus:decoration-vega-yellow`}
|
||||
href="/markets"
|
||||
>
|
||||
{t('Or view full market list')}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,130 +0,0 @@
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { MarketState, MarketTradingMode, AuctionTrigger } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: Markets
|
||||
// ====================================================
|
||||
|
||||
export interface Markets_markets_data_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* Current state of the market
|
||||
*/
|
||||
state: MarketState;
|
||||
/**
|
||||
* Current mode of execution of the market
|
||||
*/
|
||||
tradingMode: MarketTradingMode;
|
||||
}
|
||||
|
||||
export interface Markets_markets_data {
|
||||
__typename: "MarketData";
|
||||
/**
|
||||
* market id of the associated mark price
|
||||
*/
|
||||
market: Markets_markets_data_market;
|
||||
/**
|
||||
* the highest price level on an order book for buy orders.
|
||||
*/
|
||||
bestBidPrice: string;
|
||||
/**
|
||||
* the lowest price level on an order book for offer orders.
|
||||
*/
|
||||
bestOfferPrice: string;
|
||||
/**
|
||||
* the mark price (actually an unsigned int)
|
||||
*/
|
||||
markPrice: string;
|
||||
/**
|
||||
* what triggered an auction (if an auction was started)
|
||||
*/
|
||||
trigger: AuctionTrigger;
|
||||
}
|
||||
|
||||
export interface Markets_markets_tradableInstrument_instrument_product_settlementAsset {
|
||||
__typename: "Asset";
|
||||
/**
|
||||
* The symbol of the asset (e.g: GBP)
|
||||
*/
|
||||
symbol: string;
|
||||
}
|
||||
|
||||
export interface Markets_markets_tradableInstrument_instrument_product {
|
||||
__typename: "Future";
|
||||
/**
|
||||
* The name of the asset (string)
|
||||
*/
|
||||
settlementAsset: Markets_markets_tradableInstrument_instrument_product_settlementAsset;
|
||||
}
|
||||
|
||||
export interface Markets_markets_tradableInstrument_instrument {
|
||||
__typename: "Instrument";
|
||||
/**
|
||||
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string)
|
||||
*/
|
||||
code: string;
|
||||
/**
|
||||
* A reference to or instance of a fully specified product, including all required product parameters for that product (Product union)
|
||||
*/
|
||||
product: Markets_markets_tradableInstrument_instrument_product;
|
||||
}
|
||||
|
||||
export interface Markets_markets_tradableInstrument {
|
||||
__typename: "TradableInstrument";
|
||||
/**
|
||||
* An instance of or reference to a fully specified instrument.
|
||||
*/
|
||||
instrument: Markets_markets_tradableInstrument_instrument;
|
||||
}
|
||||
|
||||
export interface Markets_markets {
|
||||
__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;
|
||||
/**
|
||||
* marketData for the given market
|
||||
*/
|
||||
data: Markets_markets_data | null;
|
||||
/**
|
||||
* An instance of or reference to a tradable instrument.
|
||||
*/
|
||||
tradableInstrument: Markets_markets_tradableInstrument;
|
||||
}
|
||||
|
||||
export interface Markets {
|
||||
/**
|
||||
* One or more instruments that are trading on the VEGA network
|
||||
*/
|
||||
markets: Markets_markets[] | null;
|
||||
}
|
@ -1,5 +1 @@
|
||||
export * from './market-list-table';
|
||||
export * from './markets-container';
|
||||
export * from './markets-data-provider';
|
||||
export * from './summary-cell';
|
||||
export * from './__generated__';
|
||||
|
@ -1,20 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { MockedProvider } from '@apollo/react-testing';
|
||||
import MarketListTable from './market-list-table';
|
||||
|
||||
describe('MarketListTable', () => {
|
||||
it('should render successfully', async () => {
|
||||
await act(async () => {
|
||||
const { baseElement } = render(
|
||||
<MockedProvider>
|
||||
<MarketListTable
|
||||
data={[]}
|
||||
onRowClicked={jest.fn((marketId: string) => null)}
|
||||
/>
|
||||
</MockedProvider>
|
||||
);
|
||||
expect(baseElement).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
@ -15,9 +15,9 @@ import type {
|
||||
} from 'ag-grid-react';
|
||||
import { MarketTradingMode, AuctionTrigger } from '@vegaprotocol/types';
|
||||
import type {
|
||||
Markets_markets,
|
||||
Markets_markets_data,
|
||||
} from './__generated__/Markets';
|
||||
MarketList_markets,
|
||||
MarketList_markets_data,
|
||||
} from '../../__generated__';
|
||||
|
||||
type Props = AgGridReactProps | AgReactUiProps;
|
||||
|
||||
@ -25,7 +25,7 @@ type MarketListTableValueFormatterParams = Omit<
|
||||
ValueFormatterParams,
|
||||
'data' | 'value'
|
||||
> & {
|
||||
data: Markets_markets;
|
||||
data: MarketList_markets;
|
||||
};
|
||||
|
||||
export const MarketListTable = forwardRef<AgGridReact, Props>((props, ref) => {
|
||||
@ -58,7 +58,7 @@ export const MarketListTable = forwardRef<AgGridReact, Props>((props, ref) => {
|
||||
valueFormatter={({
|
||||
value,
|
||||
}: MarketListTableValueFormatterParams & {
|
||||
value?: Markets_markets_data;
|
||||
value?: MarketList_markets_data;
|
||||
}) => {
|
||||
if (!value) return value;
|
||||
const { market, trigger } = value;
|
||||
@ -79,7 +79,7 @@ export const MarketListTable = forwardRef<AgGridReact, Props>((props, ref) => {
|
||||
value,
|
||||
data,
|
||||
}: MarketListTableValueFormatterParams & {
|
||||
value?: Markets_markets_data['bestBidPrice'];
|
||||
value?: MarketList_markets_data['bestBidPrice'];
|
||||
}) =>
|
||||
value === undefined
|
||||
? value
|
||||
@ -94,7 +94,7 @@ export const MarketListTable = forwardRef<AgGridReact, Props>((props, ref) => {
|
||||
value,
|
||||
data,
|
||||
}: MarketListTableValueFormatterParams & {
|
||||
value?: Markets_markets_data['bestOfferPrice'];
|
||||
value?: MarketList_markets_data['bestOfferPrice'];
|
||||
}) =>
|
||||
value === undefined
|
||||
? value
|
||||
@ -111,7 +111,7 @@ export const MarketListTable = forwardRef<AgGridReact, Props>((props, ref) => {
|
||||
value,
|
||||
data,
|
||||
}: MarketListTableValueFormatterParams & {
|
||||
value?: Markets_markets_data['markPrice'];
|
||||
value?: MarketList_markets_data['markPrice'];
|
||||
}) =>
|
||||
value === undefined
|
||||
? value
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useRef, useCallback } from 'react';
|
||||
import { useRef, useCallback, useMemo } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import { MarketListTable } from './market-list-table';
|
||||
@ -6,17 +6,25 @@ import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import type { AgGridReact } from 'ag-grid-react';
|
||||
import type { IGetRowsParams } from 'ag-grid-community';
|
||||
import type {
|
||||
Markets_markets,
|
||||
Markets_markets_data,
|
||||
} from './__generated__/Markets';
|
||||
import { marketsDataProvider as dataProvider } from './markets-data-provider';
|
||||
import { MarketState } from '@vegaprotocol/types';
|
||||
MarketList_markets,
|
||||
MarketList_markets_data,
|
||||
} from '../../__generated__/MarketList';
|
||||
import { marketsDataProvider as dataProvider } from '../../markets-data-provider';
|
||||
import { Interval, MarketState } from '@vegaprotocol/types';
|
||||
|
||||
export const MarketsContainer = () => {
|
||||
const { push } = useRouter();
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const dataRef = useRef<Markets_markets[] | null>(null);
|
||||
const update = useCallback(({ data }: { data: Markets_markets[] }) => {
|
||||
const dataRef = useRef<MarketList_markets[] | null>(null);
|
||||
|
||||
const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600;
|
||||
const yTimestamp = new Date(yesterday * 1000).toISOString();
|
||||
const variables = useMemo(
|
||||
() => ({ interval: Interval.I1H, since: yTimestamp }),
|
||||
[yTimestamp]
|
||||
);
|
||||
|
||||
const update = useCallback(({ data }: { data: MarketList_markets[] }) => {
|
||||
if (!gridRef.current?.api) {
|
||||
return false;
|
||||
}
|
||||
@ -25,9 +33,9 @@ export const MarketsContainer = () => {
|
||||
return true;
|
||||
}, []);
|
||||
const { data, error, loading } = useDataProvider<
|
||||
Markets_markets[],
|
||||
Markets_markets_data
|
||||
>({ dataProvider, update });
|
||||
MarketList_markets[],
|
||||
MarketList_markets_data
|
||||
>({ dataProvider, update, variables });
|
||||
dataRef.current = data;
|
||||
const getRows = async ({
|
||||
successCallback,
|
||||
@ -48,7 +56,7 @@ export const MarketsContainer = () => {
|
||||
rowModelType="infinite"
|
||||
datasource={{ getRows }}
|
||||
ref={gridRef}
|
||||
onRowClicked={({ data }: { data: Markets_markets }) =>
|
||||
onRowClicked={({ data }: { data: MarketList_markets }) =>
|
||||
push(`/markets/${data.id}`)
|
||||
}
|
||||
/>
|
||||
|
543
libs/market-list/src/lib/components/select-market-columns.tsx
Normal file
543
libs/market-list/src/lib/components/select-market-columns.tsx
Normal file
@ -0,0 +1,543 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
addDecimalsFormatNumber,
|
||||
formatLabel,
|
||||
formatNumberPercentage,
|
||||
PriceCell,
|
||||
t,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { AuctionTrigger, MarketTradingMode } from '@vegaprotocol/types';
|
||||
import {
|
||||
KeyValueTable,
|
||||
KeyValueTableRow,
|
||||
PriceCellChange,
|
||||
Sparkline,
|
||||
Tooltip,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import Link from 'next/link';
|
||||
|
||||
import { totalFees } from '../utils';
|
||||
|
||||
import type { CandleClose } from '@vegaprotocol/types';
|
||||
import type { MarketList_markets_fees_factors } from '../__generated__/MarketList';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const thClassNames = (direction: 'left' | 'right') =>
|
||||
`px-8 text-${direction} font-sans text-ui-small leading-9 mb-0 text-dark dark:text-white first:w-[10%]`;
|
||||
export const tdClassNames =
|
||||
'px-8 font-sans leading-9 capitalize text-ui-small text-right text-dark dark:text-white';
|
||||
export const boldUnderlineClassNames = classNames(
|
||||
'px-8 underline font-sans',
|
||||
'leading-9 font-bold tracking-tight decoration-solid',
|
||||
'text-ui light:hover:text-black/80 dark:hover:text-white/80',
|
||||
'first:w-[10%]'
|
||||
);
|
||||
|
||||
export interface Column {
|
||||
value: string | React.ReactNode;
|
||||
className: string;
|
||||
onlyOnDetailed: boolean;
|
||||
}
|
||||
|
||||
export const columnHeadersPositionMarkets: Column[] = [
|
||||
{
|
||||
value: t('Market'),
|
||||
className: thClassNames('left'),
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: t('Last price'),
|
||||
className: thClassNames('right'),
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: t('Settlement asset'),
|
||||
className: thClassNames('left'),
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: t('Change (24h)'),
|
||||
className: thClassNames('right'),
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{ value: t(''), className: thClassNames('right'), onlyOnDetailed: false },
|
||||
{
|
||||
value: t('24h High'),
|
||||
className: thClassNames('right'),
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: t('24h Low'),
|
||||
className: thClassNames('right'),
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: t('Trading mode'),
|
||||
className: thClassNames('left'),
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: (
|
||||
<Tooltip
|
||||
description={
|
||||
<span className="text-ui-small">
|
||||
{t(
|
||||
'Fees are paid by market takers on aggressive orders only. The fee displayed is made up of:'
|
||||
)}
|
||||
<ul className="list-disc ml-20">
|
||||
<li className="py-5">{t('An infrastructure fee')}</li>
|
||||
<li className="py-5">{t('A liquidity provision fee')}</li>
|
||||
<li className="py-5">{t('A maker fee')}</li>
|
||||
</ul>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<span className="border-b-2 border-dotted">{t('Taker fee')}</span>
|
||||
</Tooltip>
|
||||
),
|
||||
className: thClassNames('right'),
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: t('Volume'),
|
||||
className: thClassNames('right'),
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: t('Position'),
|
||||
className: thClassNames('left'),
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const columnHeaders: Column[] = [
|
||||
{
|
||||
value: t('Market'),
|
||||
className: thClassNames('left'),
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: t('Last price'),
|
||||
className: thClassNames('right'),
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: t('Settlement asset'),
|
||||
className: thClassNames('left'),
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: t('Change (24h)'),
|
||||
className: thClassNames('right'),
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{ value: t(''), className: thClassNames('right'), onlyOnDetailed: false },
|
||||
{
|
||||
value: t('24h High'),
|
||||
className: thClassNames('right'),
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: t('24h Low'),
|
||||
className: thClassNames('right'),
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: t('Trading mode'),
|
||||
className: thClassNames('left'),
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: (
|
||||
<Tooltip
|
||||
description={
|
||||
<span className="text-ui-small">
|
||||
{t(
|
||||
'Fees are paid by market takers on aggressive orders only. The fee displayed is made up of:'
|
||||
)}
|
||||
<ul className="list-disc ml-20">
|
||||
<li className="py-5">{t('An infrastructure fee')}</li>
|
||||
<li className="py-5">{t('A liquidity provision fee')}</li>
|
||||
<li className="py-5">{t('A maker fee')}</li>
|
||||
</ul>
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<span className="border-b-2 border-dotted">{t('Taker fee')}</span>
|
||||
</Tooltip>
|
||||
),
|
||||
className: thClassNames('right'),
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: t('Volume'),
|
||||
className: thClassNames('right'),
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: t('Full name'),
|
||||
className: thClassNames('left'),
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const columns = (market: any, onSelect: (id: string) => void) => {
|
||||
const candlesClose = market.candles
|
||||
.map((candle: { close: string }) => candle?.close)
|
||||
.filter((c: string): c is CandleClose => c !== null);
|
||||
const handleKeyPress = (
|
||||
event: React.KeyboardEvent<HTMLAnchorElement>,
|
||||
id: string
|
||||
) => {
|
||||
if (event.key === 'Enter' && onSelect) {
|
||||
return onSelect(id);
|
||||
}
|
||||
};
|
||||
const selectMarketColumns: Column[] = [
|
||||
{
|
||||
value: (
|
||||
<Link href={`/markets/${market.id}`} passHref={true}>
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid,jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
onKeyPress={(event) => handleKeyPress(event, market.id)}
|
||||
onClick={() => {
|
||||
onSelect(market.id);
|
||||
}}
|
||||
data-testid={`market-link-${market.id}`}
|
||||
className={`focus:decoration-vega-pink dark:focus:decoration-vega-yellow text-black dark:text-white`}
|
||||
>
|
||||
{market.tradableInstrument.instrument.code}
|
||||
</a>
|
||||
</Link>
|
||||
),
|
||||
className: `${boldUnderlineClassNames} relative`,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: market.lastPrice ? (
|
||||
<PriceCell
|
||||
value={new BigNumber(market.lastPrice).toNumber()}
|
||||
valueFormatted={addDecimalsFormatNumber(
|
||||
market.lastPrice.toString(),
|
||||
market.decimalPlaces,
|
||||
2
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
),
|
||||
className: tdClassNames,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: market.settlementAsset,
|
||||
className: thClassNames('left'),
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: (
|
||||
<PriceCellChange
|
||||
candles={candlesClose}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
/>
|
||||
),
|
||||
className: tdClassNames,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: market.candles && (
|
||||
<Sparkline
|
||||
width={100}
|
||||
height={20}
|
||||
muted={false}
|
||||
data={candlesClose.map((c: string) => Number(c))}
|
||||
/>
|
||||
),
|
||||
className: 'px-8',
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: market.candleHigh ? (
|
||||
<PriceCell
|
||||
value={new BigNumber(market.candleHigh).toNumber()}
|
||||
valueFormatted={addDecimalsFormatNumber(
|
||||
market.candleHigh.toString(),
|
||||
market.decimalPlaces,
|
||||
2
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
),
|
||||
className: tdClassNames,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: market.candleLow ? (
|
||||
<PriceCell
|
||||
value={new BigNumber(market.candleLow).toNumber()}
|
||||
valueFormatted={addDecimalsFormatNumber(
|
||||
market.candleLow.toString(),
|
||||
market.decimalPlaces,
|
||||
2
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
),
|
||||
className: tdClassNames,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value:
|
||||
market.tradingMode === MarketTradingMode.MonitoringAuction &&
|
||||
market.data?.trigger &&
|
||||
market.data.trigger !== AuctionTrigger.Unspecified
|
||||
? `${formatLabel(
|
||||
market.tradingMode
|
||||
)} - ${market.data?.trigger.toLowerCase()}`
|
||||
: formatLabel(market.tradingMode),
|
||||
className: thClassNames('left'),
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: (
|
||||
<Tooltip
|
||||
description={<FeesBreakdown feeFactors={market.fees?.factors} />}
|
||||
>
|
||||
<span className="border-b-2 border-dotted">
|
||||
{market.totalFees ?? '-'}
|
||||
</span>
|
||||
</Tooltip>
|
||||
),
|
||||
className: tdClassNames,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value:
|
||||
market.data.indicativeVolume && market.data.indicativeVolume !== '0'
|
||||
? addDecimalsFormatNumber(
|
||||
market.data.indicativeVolume,
|
||||
market.positionDecimalPlaces
|
||||
)
|
||||
: '-',
|
||||
className: tdClassNames,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: market.name,
|
||||
className: thClassNames('left'),
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
];
|
||||
return selectMarketColumns;
|
||||
};
|
||||
|
||||
export const columnsPositionMarkets = (
|
||||
market: any,
|
||||
onSelect: (id: string) => void
|
||||
) => {
|
||||
const candlesClose = market.candles
|
||||
.map((candle: { close: string }) => candle?.close)
|
||||
.filter((c: string): c is CandleClose => c !== null);
|
||||
const handleKeyPress = (
|
||||
event: React.KeyboardEvent<HTMLAnchorElement>,
|
||||
id: string
|
||||
) => {
|
||||
if (event.key === 'Enter' && onSelect) {
|
||||
return onSelect(id);
|
||||
}
|
||||
};
|
||||
const selectMarketColumns: Column[] = [
|
||||
{
|
||||
value: (
|
||||
<Link href={`/markets/${market.id}`} passHref={true}>
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid,jsx-a11y/no-static-element-interactions */}
|
||||
<a
|
||||
onKeyPress={(event) => handleKeyPress(event, market.id)}
|
||||
onClick={() => {
|
||||
onSelect(market.id);
|
||||
}}
|
||||
data-testid={`market-link-${market.id}`}
|
||||
className={`focus:decoration-vega-pink dark:focus:decoration-vega-yellow text-black dark:text-white`}
|
||||
>
|
||||
{market.tradableInstrument.instrument.code}
|
||||
</a>
|
||||
</Link>
|
||||
),
|
||||
className: `${boldUnderlineClassNames} relative`,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: market.lastPrice ? (
|
||||
<PriceCell
|
||||
value={new BigNumber(market.lastPrice).toNumber()}
|
||||
valueFormatted={addDecimalsFormatNumber(
|
||||
market.lastPrice.toString(),
|
||||
market.decimalPlaces,
|
||||
2
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
),
|
||||
className: tdClassNames,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: market.settlementAsset,
|
||||
className: thClassNames('left'),
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: (
|
||||
<PriceCellChange
|
||||
candles={candlesClose}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
/>
|
||||
),
|
||||
className: tdClassNames,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: market.candles && (
|
||||
<Sparkline
|
||||
width={100}
|
||||
height={20}
|
||||
muted={false}
|
||||
data={candlesClose.map((c: string) => Number(c))}
|
||||
/>
|
||||
),
|
||||
className: 'px-8',
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: market.candleHigh ? (
|
||||
<PriceCell
|
||||
value={new BigNumber(market.candleHigh).toNumber()}
|
||||
valueFormatted={addDecimalsFormatNumber(
|
||||
market.candleHigh.toString(),
|
||||
market.decimalPlaces,
|
||||
2
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
),
|
||||
className: tdClassNames,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: market.candleLow ? (
|
||||
<PriceCell
|
||||
value={new BigNumber(market.candleLow).toNumber()}
|
||||
valueFormatted={addDecimalsFormatNumber(
|
||||
market.candleLow.toString(),
|
||||
market.decimalPlaces,
|
||||
2
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
),
|
||||
className: tdClassNames,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value:
|
||||
market.tradingMode === MarketTradingMode.MonitoringAuction &&
|
||||
market.data?.trigger &&
|
||||
market.data.trigger !== AuctionTrigger.Unspecified
|
||||
? `${formatLabel(
|
||||
market.tradingMode
|
||||
)} - ${market.data?.trigger.toLowerCase()}`
|
||||
: formatLabel(market.tradingMode),
|
||||
className: thClassNames('left'),
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: (
|
||||
<Tooltip
|
||||
description={<FeesBreakdown feeFactors={market.fees?.factors} />}
|
||||
>
|
||||
<span className="border-b-2 border-dotted">
|
||||
{market.totalFees ?? '-'}
|
||||
</span>
|
||||
</Tooltip>
|
||||
),
|
||||
className: tdClassNames,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value:
|
||||
market.data && market.data.indicativeVolume !== '0'
|
||||
? addDecimalsFormatNumber(
|
||||
market.data.indicativeVolume,
|
||||
market.positionDecimalPlaces
|
||||
)
|
||||
: '-',
|
||||
className: tdClassNames,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: (
|
||||
<p
|
||||
className={
|
||||
market.openVolume.includes('+')
|
||||
? 'text-dark-green dark:text-vega-green'
|
||||
: market.openVolume.includes('-')
|
||||
? 'text-red dark:text-vega-red'
|
||||
: 'text-black dark:text-white'
|
||||
}
|
||||
>
|
||||
{market.openVolume}
|
||||
</p>
|
||||
),
|
||||
className: thClassNames('left'),
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
];
|
||||
return selectMarketColumns;
|
||||
};
|
||||
|
||||
export const FeesBreakdown = ({
|
||||
feeFactors,
|
||||
}: {
|
||||
feeFactors?: MarketList_markets_fees_factors;
|
||||
}) => {
|
||||
if (!feeFactors) return null;
|
||||
return (
|
||||
<KeyValueTable muted={true}>
|
||||
<KeyValueTableRow>
|
||||
<span className={thClassNames('left')}>{t('Infrastructure Fee')}</span>
|
||||
<span className={tdClassNames}>
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(feeFactors.infrastructureFee).times(100)
|
||||
)}
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span className={thClassNames('left')}>{t('Liquidity Fee')}</span>
|
||||
<span className={tdClassNames}>
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(feeFactors.liquidityFee).times(100)
|
||||
)}
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span className={thClassNames('left')}>{t('Maker Fee')}</span>
|
||||
<span className={tdClassNames}>
|
||||
{formatNumberPercentage(
|
||||
new BigNumber(feeFactors.makerFee).times(100)
|
||||
)}
|
||||
</span>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<span className={thClassNames('left')}>{t('Total Fees')}</span>
|
||||
<span className={tdClassNames}>{totalFees(feeFactors)}</span>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
);
|
||||
};
|
43
libs/market-list/src/lib/components/select-market-table.tsx
Normal file
43
libs/market-list/src/lib/components/select-market-table.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import type { Column } from './select-market-columns';
|
||||
import { columnHeaders } from './select-market-columns';
|
||||
|
||||
export const SelectMarketTableHeader = ({
|
||||
detailed = false,
|
||||
headers = columnHeaders,
|
||||
}) => {
|
||||
return (
|
||||
<tr>
|
||||
{headers.map(
|
||||
({ value, className, onlyOnDetailed }, i) =>
|
||||
(!onlyOnDetailed || detailed === onlyOnDetailed) && (
|
||||
<th key={i} className={className}>
|
||||
{value}
|
||||
</th>
|
||||
)
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectMarketTableRow = ({
|
||||
detailed = false,
|
||||
columns,
|
||||
}: {
|
||||
detailed?: boolean;
|
||||
columns: Column[];
|
||||
}) => {
|
||||
return (
|
||||
<tr
|
||||
className={`hover:bg-black/20 dark:hover:bg-white/20 cursor-pointer relative`}
|
||||
>
|
||||
{columns.map(
|
||||
({ value, className, onlyOnDetailed }, i) =>
|
||||
(!onlyOnDetailed || detailed === onlyOnDetailed) && (
|
||||
<td key={i} className={className}>
|
||||
{value}
|
||||
</td>
|
||||
)
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
};
|
@ -1,7 +1,12 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import type { MarketList } from '../markets-container/__generated__/MarketList';
|
||||
import { SelectMarketList } from './select-market-list';
|
||||
import type { MarketList_markets } from '../__generated__/MarketList';
|
||||
|
||||
import {
|
||||
SelectAllMarketsTableBody,
|
||||
SelectMarketLandingTable,
|
||||
} from './select-market';
|
||||
|
||||
jest.mock(
|
||||
'next/link',
|
||||
@ -10,28 +15,25 @@ jest.mock(
|
||||
children
|
||||
);
|
||||
|
||||
describe('SelectMarketList', () => {
|
||||
it('should render', () => {
|
||||
describe('SelectMarket', () => {
|
||||
it('should render the SelectAllMarketsTableBody', () => {
|
||||
const onSelect = jest.fn();
|
||||
const expectedMarket = mockData.data.markets[0];
|
||||
const { container } = render(
|
||||
<SelectMarketList
|
||||
data={mockData.data as MarketList}
|
||||
onSelect={jest.fn()}
|
||||
/>
|
||||
<SelectAllMarketsTableBody data={mockData.data} onSelect={onSelect} />
|
||||
);
|
||||
expect(screen.getByText('AAPL.MF21')).toBeTruthy();
|
||||
expect(screen.getByText('-3.14%')).toBeTruthy();
|
||||
expect(container).toHaveTextContent(/141\.75/);
|
||||
expect(screen.getByText('Or view full market list')).toBeTruthy();
|
||||
fireEvent.click(screen.getByTestId(`market-link-${expectedMarket.id}`));
|
||||
expect(onSelect).toHaveBeenCalledWith(expectedMarket.id);
|
||||
});
|
||||
|
||||
it('should call onSelect callback', () => {
|
||||
it('should call onSelect callback on SelectMarketLandingTable', () => {
|
||||
const onSelect = jest.fn();
|
||||
const expectedMarket = mockData.data.markets[0];
|
||||
render(
|
||||
<SelectMarketList
|
||||
data={mockData.data as MarketList}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
<SelectMarketLandingTable data={mockData.data} onSelect={onSelect} />
|
||||
);
|
||||
fireEvent.click(screen.getByTestId(`market-link-${expectedMarket.id}`));
|
||||
expect(onSelect).toHaveBeenCalledWith(expectedMarket.id);
|
||||
@ -45,6 +47,11 @@ const mockData = {
|
||||
__typename: 'Market',
|
||||
id: '062ddcb97beae5b7cc4fa20621fe0c83b2a6f7e76cf5b129c6bd3dc14e8111ef',
|
||||
decimalPlaces: 2,
|
||||
name: '',
|
||||
positionDecimalPlaces: 4,
|
||||
state: 'Active',
|
||||
tradingMode: 'Continuous',
|
||||
data: {},
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
@ -58,6 +65,15 @@ const mockData = {
|
||||
open: '2022-05-18T13:08:27.693537312Z',
|
||||
close: null,
|
||||
},
|
||||
fees: {
|
||||
__typename: 'Fees',
|
||||
factors: {
|
||||
__typename: 'FeeFactors',
|
||||
infrastructureFee: '0.01',
|
||||
makerFee: '0.01',
|
||||
liquidityFee: '0.01',
|
||||
},
|
||||
},
|
||||
candles: [
|
||||
{
|
||||
__typename: 'Candle',
|
||||
@ -90,7 +106,7 @@ const mockData = {
|
||||
close: '774',
|
||||
},
|
||||
],
|
||||
},
|
||||
} as MarketList_markets,
|
||||
{
|
||||
__typename: 'Market',
|
||||
id: '3e6671566ccf5c33702e955fe8b018683fcdb812bfe3ed283fc250bb4f798ff3',
|
||||
@ -103,6 +119,15 @@ const mockData = {
|
||||
code: 'AAPL.MF21',
|
||||
},
|
||||
},
|
||||
fees: {
|
||||
__typename: 'Fees',
|
||||
factors: {
|
||||
__typename: 'FeeFactors',
|
||||
infrastructureFee: '0.01',
|
||||
makerFee: '0.01',
|
||||
liquidityFee: '0.01',
|
||||
},
|
||||
},
|
||||
marketTimestamps: {
|
||||
__typename: 'MarketTimestamps',
|
||||
open: '2022-05-18T13:00:39.328347732Z',
|
||||
@ -140,7 +165,12 @@ const mockData = {
|
||||
close: '14174855',
|
||||
},
|
||||
],
|
||||
},
|
||||
name: '',
|
||||
positionDecimalPlaces: 4,
|
||||
state: 'Active',
|
||||
tradingMode: 'Continuous',
|
||||
data: {},
|
||||
} as MarketList_markets,
|
||||
],
|
||||
},
|
||||
};
|
249
libs/market-list/src/lib/components/select-market.tsx
Normal file
249
libs/market-list/src/lib/components/select-market.tsx
Normal file
@ -0,0 +1,249 @@
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { t, volumePrefix } from '@vegaprotocol/react-helpers';
|
||||
import { Interval } from '@vegaprotocol/types';
|
||||
import {
|
||||
Dialog,
|
||||
Intent,
|
||||
Popover,
|
||||
RotatingArrow,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import classNames from 'classnames';
|
||||
import isNil from 'lodash/isNil';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { MARKET_LIST_QUERY } from '../markets-data-provider';
|
||||
import type { Column } from './select-market-columns';
|
||||
import {
|
||||
columnHeadersPositionMarkets,
|
||||
columnsPositionMarkets,
|
||||
} from './select-market-columns';
|
||||
import { columnHeaders } from './select-market-columns';
|
||||
import { columns } from './select-market-columns';
|
||||
import type { MarketList } from '../__generated__';
|
||||
import { useVegaWallet } from '@vegaprotocol/wallet';
|
||||
import type { Positions } from '@vegaprotocol/positions';
|
||||
import { POSITION_QUERY } from '@vegaprotocol/positions';
|
||||
import { mapDataToMarketList } from '../utils/market-utils';
|
||||
import {
|
||||
SelectMarketTableHeader,
|
||||
SelectMarketTableRow,
|
||||
} from './select-market-table';
|
||||
|
||||
export const SelectMarketLandingTable = ({
|
||||
data,
|
||||
onSelect,
|
||||
}: {
|
||||
data: MarketList | undefined;
|
||||
onSelect: (id: string) => void;
|
||||
}) => {
|
||||
const marketList = data && mapDataToMarketList(data);
|
||||
return (
|
||||
<div
|
||||
className="max-h-[40rem] overflow-x-auto"
|
||||
data-testid="select-market-list"
|
||||
>
|
||||
<table className="relative h-full min-w-full whitespace-nowrap">
|
||||
<thead className="sticky top-0 z-10 dark:bg-black bg-white">
|
||||
<SelectMarketTableHeader />
|
||||
</thead>
|
||||
<tbody>
|
||||
{marketList?.map((market, i) => (
|
||||
<SelectMarketTableRow
|
||||
key={i}
|
||||
detailed={false}
|
||||
columns={columns(market, onSelect)}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectAllMarketsTableBody = ({
|
||||
data,
|
||||
title = t('All markets'),
|
||||
onSelect,
|
||||
headers = columnHeaders,
|
||||
tableColumns = (market) => columns(market, onSelect),
|
||||
}: {
|
||||
data?: MarketList;
|
||||
title?: string;
|
||||
onSelect: (id: string) => void;
|
||||
headers?: Column[];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
tableColumns?: (market: any) => Column[];
|
||||
}) => {
|
||||
const marketList = useMemo(() => data && mapDataToMarketList(data), [data]);
|
||||
|
||||
return marketList ? (
|
||||
<>
|
||||
<thead className="sticky top-0 z-10 dark:bg-black bg-white">
|
||||
<tr
|
||||
className={`text-h5 font-bold text-black-95 dark:text-white-95 mb-6`}
|
||||
data-testid="dialog-title"
|
||||
>
|
||||
<th>{title}</th>
|
||||
</tr>
|
||||
<SelectMarketTableHeader detailed={true} headers={headers} />
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{data &&
|
||||
marketList?.map((market, i) => (
|
||||
<SelectMarketTableRow
|
||||
key={i}
|
||||
detailed={true}
|
||||
columns={tableColumns(market)}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</>
|
||||
) : (
|
||||
<thead>
|
||||
<tr>
|
||||
<td className="text-black dark:text-white text-h5">
|
||||
{t('Loading market data...')}
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectMarketPopover = ({
|
||||
marketName,
|
||||
onSelect,
|
||||
}: {
|
||||
marketName: string;
|
||||
onSelect: (id: string) => void;
|
||||
}) => {
|
||||
const headerTriggerButtonClassName =
|
||||
'flex items-center gap-8 shrink-0 p-8 font-medium text-h5 hover:bg-black/10 dark:hover:bg-white/20';
|
||||
|
||||
const { keypair } = useVegaWallet();
|
||||
const [open, setOpen] = useState(false);
|
||||
const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600;
|
||||
const yTimestamp = new Date(yesterday * 1000).toISOString();
|
||||
|
||||
const variables = useMemo(() => ({ partyId: keypair?.pub }), [keypair?.pub]);
|
||||
const { data } = useQuery<MarketList>(MARKET_LIST_QUERY, {
|
||||
variables: { interval: Interval.I1H, since: yTimestamp },
|
||||
});
|
||||
const { data: marketDataPositions } = useQuery<Positions>(POSITION_QUERY, {
|
||||
variables,
|
||||
});
|
||||
|
||||
const positionMarkets = useMemo(
|
||||
() => ({
|
||||
markets:
|
||||
data?.markets
|
||||
?.filter((market) =>
|
||||
marketDataPositions?.party?.positions?.find(
|
||||
(position) => position.market.id === market.id
|
||||
)
|
||||
)
|
||||
.map((market) => {
|
||||
const position = marketDataPositions?.party?.positions?.find(
|
||||
(position) => position.market.id === market.id
|
||||
);
|
||||
return {
|
||||
...market,
|
||||
openVolume:
|
||||
position?.openVolume && volumePrefix(position.openVolume),
|
||||
};
|
||||
}) || null,
|
||||
}),
|
||||
[data, marketDataPositions]
|
||||
);
|
||||
|
||||
const onSelectMarket = (marketId: string) => {
|
||||
onSelect(marketId);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
open={open}
|
||||
onChange={setOpen}
|
||||
trigger={
|
||||
<div
|
||||
className={classNames(
|
||||
'dark:text-vega-yellow text-vega-pink',
|
||||
headerTriggerButtonClassName
|
||||
)}
|
||||
>
|
||||
<span className="break-words text-left ml-5 ">{marketName}</span>
|
||||
<RotatingArrow borderX={8} borderBottom={12} up={open} />
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="max-h-[40rem] overflow-x-auto m-20"
|
||||
data-testid="select-market-list"
|
||||
>
|
||||
<span
|
||||
className="text-h4 font-bold text-black-95 dark:text-white-95 mt-0 mb-6"
|
||||
data-testid="dialog-title"
|
||||
>
|
||||
{t('Select a market')}
|
||||
</span>
|
||||
<table className="relative h-full w-full whitespace-nowrap overflow-y-auto">
|
||||
{keypair &&
|
||||
positionMarkets?.markets &&
|
||||
positionMarkets.markets.length > 0 && (
|
||||
<SelectAllMarketsTableBody
|
||||
title={t('My markets')}
|
||||
data={positionMarkets}
|
||||
onSelect={onSelectMarket}
|
||||
headers={columnHeadersPositionMarkets}
|
||||
tableColumns={(market) =>
|
||||
columnsPositionMarkets(market, onSelectMarket)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<SelectAllMarketsTableBody
|
||||
title={t('All markets')}
|
||||
data={data}
|
||||
onSelect={onSelectMarket}
|
||||
/>
|
||||
</table>
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectMarketDialog = ({
|
||||
dialogOpen,
|
||||
setDialogOpen,
|
||||
onSelect,
|
||||
title = t('Select a market'),
|
||||
}: {
|
||||
dialogOpen: boolean;
|
||||
setDialogOpen: (open: boolean) => void;
|
||||
title?: string;
|
||||
detailed?: boolean;
|
||||
onSelect: (id: string) => void;
|
||||
}) => {
|
||||
const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600;
|
||||
const yTimestamp = new Date(yesterday * 1000).toISOString();
|
||||
|
||||
const onSelectMarket = (id: string) => {
|
||||
onSelect(id);
|
||||
setDialogOpen(false);
|
||||
};
|
||||
|
||||
const { data } = useQuery<MarketList>(MARKET_LIST_QUERY, {
|
||||
variables: { interval: Interval.I1H, since: yTimestamp },
|
||||
});
|
||||
return (
|
||||
<Dialog
|
||||
title={title}
|
||||
intent={Intent.Primary}
|
||||
open={!isNil(data) && dialogOpen}
|
||||
onChange={() => setDialogOpen(false)}
|
||||
titleClassNames="font-bold font-sans text-3xl tracking-tight mb-0 pl-8"
|
||||
size="small"
|
||||
>
|
||||
<SelectMarketLandingTable data={data} onSelect={onSelectMarket} />
|
||||
</Dialog>
|
||||
);
|
||||
};
|
4
libs/market-list/src/lib/index.ts
Normal file
4
libs/market-list/src/lib/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export * from './__generated__';
|
||||
export * from './components';
|
||||
export * from './utils';
|
||||
export * from './markets-data-provider';
|
@ -1,12 +1,12 @@
|
||||
import produce from 'immer';
|
||||
import { gql } from '@apollo/client';
|
||||
import { makeDataProvider } from '@vegaprotocol/react-helpers';
|
||||
import type {
|
||||
Markets,
|
||||
Markets_markets,
|
||||
MarketDataSub,
|
||||
MarketDataSub_marketData,
|
||||
} from './';
|
||||
import { makeDataProvider } from '@vegaprotocol/react-helpers';
|
||||
MarketList,
|
||||
MarketList_markets,
|
||||
} from './__generated__';
|
||||
|
||||
const MARKET_DATA_FRAGMENT = gql`
|
||||
fragment MarketDataFields on MarketData {
|
||||
@ -19,32 +19,7 @@ const MARKET_DATA_FRAGMENT = gql`
|
||||
bestOfferPrice
|
||||
markPrice
|
||||
trigger
|
||||
}
|
||||
`;
|
||||
|
||||
const MARKETS_QUERY = gql`
|
||||
${MARKET_DATA_FRAGMENT}
|
||||
query Markets {
|
||||
markets {
|
||||
id
|
||||
name
|
||||
decimalPlaces
|
||||
data {
|
||||
...MarketDataFields
|
||||
}
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
code
|
||||
product {
|
||||
... on Future {
|
||||
settlementAsset {
|
||||
symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
indicativeVolume
|
||||
}
|
||||
`;
|
||||
|
||||
@ -52,14 +27,29 @@ export const MARKET_LIST_QUERY = gql`
|
||||
query MarketList($interval: Interval!, $since: String!) {
|
||||
markets {
|
||||
id
|
||||
name
|
||||
decimalPlaces
|
||||
positionDecimalPlaces
|
||||
state
|
||||
tradingMode
|
||||
fees {
|
||||
factors {
|
||||
makerFee
|
||||
infrastructureFee
|
||||
liquidityFee
|
||||
}
|
||||
}
|
||||
data {
|
||||
market {
|
||||
id
|
||||
state
|
||||
tradingMode
|
||||
}
|
||||
bestBidPrice
|
||||
bestOfferPrice
|
||||
markPrice
|
||||
trigger
|
||||
indicativeVolume
|
||||
}
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
@ -68,6 +58,13 @@ export const MARKET_LIST_QUERY = gql`
|
||||
metadata {
|
||||
tags
|
||||
}
|
||||
product {
|
||||
... on Future {
|
||||
settlementAsset {
|
||||
symbol
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
marketTimestamps {
|
||||
@ -77,6 +74,8 @@ export const MARKET_LIST_QUERY = gql`
|
||||
candles(interval: $interval, since: $since) {
|
||||
open
|
||||
close
|
||||
high
|
||||
low
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -91,7 +90,10 @@ const MARKET_DATA_SUB = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
const update = (data: Markets_markets[], delta: MarketDataSub_marketData) => {
|
||||
const update = (
|
||||
data: MarketList_markets[],
|
||||
delta: MarketDataSub_marketData
|
||||
) => {
|
||||
return produce(data, (draft) => {
|
||||
const index = draft.findIndex((m) => m.id === delta.market.id);
|
||||
if (index !== -1) {
|
||||
@ -101,14 +103,14 @@ const update = (data: Markets_markets[], delta: MarketDataSub_marketData) => {
|
||||
});
|
||||
};
|
||||
|
||||
const getData = (responseData: Markets): Markets_markets[] | null =>
|
||||
const getData = (responseData: MarketList): MarketList_markets[] | null =>
|
||||
responseData.markets;
|
||||
const getDelta = (subscriptionData: MarketDataSub): MarketDataSub_marketData =>
|
||||
subscriptionData.marketData;
|
||||
|
||||
export const marketsDataProvider = makeDataProvider<
|
||||
Markets,
|
||||
Markets_markets[],
|
||||
MarketList,
|
||||
MarketList_markets[],
|
||||
MarketDataSub,
|
||||
MarketDataSub_marketData
|
||||
>(MARKETS_QUERY, MARKET_DATA_SUB, update, getData, getDelta);
|
||||
>(MARKET_LIST_QUERY, MARKET_DATA_SUB, update, getData, getDelta);
|
@ -1 +1 @@
|
||||
export * from './market-list.utils';
|
||||
export * from './market-utils';
|
||||
|
@ -1,151 +0,0 @@
|
||||
import type { MarketList } from '../components/markets-container/__generated__/MarketList';
|
||||
import { mapDataToMarketList } from './market-list.utils';
|
||||
|
||||
describe('mapDataToMarketList', () => {
|
||||
it('should map queried data to market list format', () => {
|
||||
const result = mapDataToMarketList(mockData.data as unknown as MarketList);
|
||||
expect(result).toEqual(mockList);
|
||||
});
|
||||
});
|
||||
|
||||
const mockList = [
|
||||
{
|
||||
candles: [
|
||||
{ __typename: 'Candle', close: '14633864', open: '14707175' },
|
||||
{ __typename: 'Candle', close: '14550193', open: '14658400' },
|
||||
{ __typename: 'Candle', close: '14373526', open: '14550193' },
|
||||
{ __typename: 'Candle', close: '14339846', open: '14307141' },
|
||||
{ __typename: 'Candle', close: '14179971', open: '14357485' },
|
||||
{ __typename: 'Candle', close: '14174855', open: '14179972' },
|
||||
],
|
||||
close: null,
|
||||
decimalPlaces: 5,
|
||||
id: '3e6671566ccf5c33702e955fe8b018683fcdb812bfe3ed283fc250bb4f798ff3',
|
||||
lastPrice: '14174855',
|
||||
marketName: 'AAPL.MF21',
|
||||
open: 1652878839328,
|
||||
},
|
||||
{
|
||||
candles: [
|
||||
{ __typename: 'Candle', close: '798', open: '822' },
|
||||
{ __typename: 'Candle', close: '792', open: '793' },
|
||||
{ __typename: 'Candle', close: '776', open: '794' },
|
||||
{ __typename: 'Candle', close: '786', open: '785' },
|
||||
{ __typename: 'Candle', close: '770', open: '803' },
|
||||
{ __typename: 'Candle', close: '774', open: '785' },
|
||||
],
|
||||
close: null,
|
||||
decimalPlaces: 2,
|
||||
id: '062ddcb97beae5b7cc4fa20621fe0c83b2a6f7e76cf5b129c6bd3dc14e8111ef',
|
||||
lastPrice: '774',
|
||||
marketName: 'APEUSD',
|
||||
open: 1652879307693,
|
||||
},
|
||||
];
|
||||
|
||||
const mockData = {
|
||||
data: {
|
||||
markets: [
|
||||
{
|
||||
__typename: 'Market',
|
||||
id: '062ddcb97beae5b7cc4fa20621fe0c83b2a6f7e76cf5b129c6bd3dc14e8111ef',
|
||||
decimalPlaces: 2,
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
name: 'APEUSD (May 2022)',
|
||||
code: 'APEUSD',
|
||||
},
|
||||
},
|
||||
marketTimestamps: {
|
||||
__typename: 'MarketTimestamps',
|
||||
open: '2022-05-18T13:08:27.693537312Z',
|
||||
close: null,
|
||||
},
|
||||
candles: [
|
||||
{
|
||||
__typename: 'Candle',
|
||||
open: '822',
|
||||
close: '798',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
open: '793',
|
||||
close: '792',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
open: '794',
|
||||
close: '776',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
open: '785',
|
||||
close: '786',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
open: '803',
|
||||
close: '770',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
open: '785',
|
||||
close: '774',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
__typename: 'Market',
|
||||
id: '3e6671566ccf5c33702e955fe8b018683fcdb812bfe3ed283fc250bb4f798ff3',
|
||||
decimalPlaces: 5,
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
name: 'Apple Monthly (30 Jun 2022)',
|
||||
code: 'AAPL.MF21',
|
||||
},
|
||||
},
|
||||
marketTimestamps: {
|
||||
__typename: 'MarketTimestamps',
|
||||
open: '2022-05-18T13:00:39.328347732Z',
|
||||
close: null,
|
||||
},
|
||||
candles: [
|
||||
{
|
||||
__typename: 'Candle',
|
||||
open: '14707175',
|
||||
close: '14633864',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
open: '14658400',
|
||||
close: '14550193',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
open: '14550193',
|
||||
close: '14373526',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
open: '14307141',
|
||||
close: '14339846',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
open: '14357485',
|
||||
close: '14179971',
|
||||
},
|
||||
{
|
||||
__typename: 'Candle',
|
||||
open: '14179972',
|
||||
close: '14174855',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
@ -1,39 +0,0 @@
|
||||
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import type {
|
||||
MarketList,
|
||||
MarketList_markets,
|
||||
} from '../components/markets-container/__generated__/MarketList';
|
||||
|
||||
export const lastPrice = ({ candles }: MarketList_markets) =>
|
||||
candles && candles.length > 0
|
||||
? candles && candles[candles?.length - 1]?.close
|
||||
: undefined;
|
||||
|
||||
export const mapDataToMarketList = ({ markets }: MarketList) =>
|
||||
orderBy(
|
||||
markets
|
||||
?.filter(
|
||||
(m) =>
|
||||
m.state !== MarketState.Rejected &&
|
||||
m.tradingMode !== MarketTradingMode.NoTrading
|
||||
)
|
||||
.map((m) => {
|
||||
return {
|
||||
id: m.id,
|
||||
decimalPlaces: m.decimalPlaces,
|
||||
marketName: m.tradableInstrument.instrument?.code,
|
||||
lastPrice: lastPrice(m) ?? m.data?.markPrice,
|
||||
candles: (m.candles || []).filter((c) => c),
|
||||
open: m.marketTimestamps.open
|
||||
? new Date(m.marketTimestamps.open).getTime()
|
||||
: null,
|
||||
close: m.marketTimestamps.close
|
||||
? new Date(m.marketTimestamps.close).getTime()
|
||||
: null,
|
||||
state: m.state,
|
||||
};
|
||||
}) || [],
|
||||
['state', 'open', 'id'],
|
||||
['asc', 'asc', 'asc']
|
||||
);
|
222
libs/market-list/src/lib/utils/market-utils.spec.tsx
Normal file
222
libs/market-list/src/lib/utils/market-utils.spec.tsx
Normal file
@ -0,0 +1,222 @@
|
||||
import type { MarketList } from '../__generated__/MarketList';
|
||||
import { mapDataToMarketList } from './market-utils';
|
||||
|
||||
describe('mapDataToMarketList', () => {
|
||||
it('should map queried data to market list format', () => {
|
||||
const result = mapDataToMarketList(mockData.data as unknown as MarketList);
|
||||
expect(result).toEqual(mockList);
|
||||
});
|
||||
});
|
||||
|
||||
const mockList = [
|
||||
{
|
||||
__typename: 'Market',
|
||||
id: '3e6671566ccf5c33702e955fe8b018683fcdb812bfe3ed283fc250bb4f798ff3',
|
||||
decimalPlaces: 5,
|
||||
candles: [
|
||||
{
|
||||
open: '16141155',
|
||||
close: '16293551',
|
||||
high: '16320190',
|
||||
low: '16023805',
|
||||
__typename: 'Candle',
|
||||
},
|
||||
{
|
||||
open: '16293548',
|
||||
close: '16322118',
|
||||
high: '16365861',
|
||||
low: '16192970',
|
||||
__typename: 'Candle',
|
||||
},
|
||||
],
|
||||
fees: {
|
||||
factors: {
|
||||
makerFee: 0.0002,
|
||||
infrastructureFee: 0.0005,
|
||||
liquidityFee: 0.001,
|
||||
},
|
||||
},
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
name: 'Apple Monthly (30 Jun 2022)',
|
||||
code: 'AAPL.MF21',
|
||||
product: {
|
||||
settlementAsset: { symbol: 'AAPL.MF21', __typename: 'Asset' },
|
||||
__typename: 'Future',
|
||||
},
|
||||
},
|
||||
},
|
||||
marketTimestamps: {
|
||||
__typename: 'MarketTimestamps',
|
||||
open: '2022-05-18T13:00:39.328347732Z',
|
||||
close: null,
|
||||
},
|
||||
marketName: 'AAPL.MF21',
|
||||
settlementAsset: 'AAPL.MF21',
|
||||
lastPrice: '16322118',
|
||||
candleHigh: '16365861',
|
||||
candleLow: '16023805',
|
||||
open: 1652878839328,
|
||||
close: null,
|
||||
totalFees: '0.14%',
|
||||
},
|
||||
{
|
||||
__typename: 'Market',
|
||||
id: '062ddcb97beae5b7cc4fa20621fe0c83b2a6f7e76cf5b129c6bd3dc14e8111ef',
|
||||
decimalPlaces: 2,
|
||||
fees: {
|
||||
factors: {
|
||||
makerFee: 0.0002,
|
||||
infrastructureFee: 0.0005,
|
||||
liquidityFee: 0.001,
|
||||
},
|
||||
},
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
name: 'APEUSD (May 2022)',
|
||||
code: 'APEUSD',
|
||||
product: {
|
||||
settlementAsset: { symbol: 'APEUSD', __typename: 'Asset' },
|
||||
__typename: 'Future',
|
||||
},
|
||||
},
|
||||
},
|
||||
marketTimestamps: {
|
||||
__typename: 'MarketTimestamps',
|
||||
open: '2022-05-18T13:08:27.693537312Z',
|
||||
close: null,
|
||||
},
|
||||
candles: [
|
||||
{
|
||||
open: '16141155',
|
||||
close: '16293551',
|
||||
high: '16320190',
|
||||
low: '16023805',
|
||||
__typename: 'Candle',
|
||||
},
|
||||
{
|
||||
open: '16293548',
|
||||
close: '16322118',
|
||||
high: '16365861',
|
||||
low: '16192970',
|
||||
__typename: 'Candle',
|
||||
},
|
||||
],
|
||||
marketName: 'APEUSD',
|
||||
settlementAsset: 'APEUSD',
|
||||
lastPrice: '16322118',
|
||||
candleHigh: '16365861',
|
||||
candleLow: '16023805',
|
||||
open: 1652879307693,
|
||||
close: null,
|
||||
totalFees: '0.14%',
|
||||
},
|
||||
];
|
||||
|
||||
const mockData = {
|
||||
data: {
|
||||
markets: [
|
||||
{
|
||||
__typename: 'Market',
|
||||
id: '062ddcb97beae5b7cc4fa20621fe0c83b2a6f7e76cf5b129c6bd3dc14e8111ef',
|
||||
decimalPlaces: 2,
|
||||
fees: {
|
||||
factors: {
|
||||
makerFee: 0.0002,
|
||||
infrastructureFee: 0.0005,
|
||||
liquidityFee: 0.001,
|
||||
},
|
||||
},
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
name: 'APEUSD (May 2022)',
|
||||
code: 'APEUSD',
|
||||
product: {
|
||||
settlementAsset: {
|
||||
symbol: 'APEUSD',
|
||||
__typename: 'Asset',
|
||||
},
|
||||
__typename: 'Future',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
marketTimestamps: {
|
||||
__typename: 'MarketTimestamps',
|
||||
open: '2022-05-18T13:08:27.693537312Z',
|
||||
close: null,
|
||||
},
|
||||
candles: [
|
||||
{
|
||||
open: '16141155',
|
||||
close: '16293551',
|
||||
high: '16320190',
|
||||
low: '16023805',
|
||||
__typename: 'Candle',
|
||||
},
|
||||
{
|
||||
open: '16293548',
|
||||
close: '16322118',
|
||||
high: '16365861',
|
||||
low: '16192970',
|
||||
__typename: 'Candle',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
__typename: 'Market',
|
||||
id: '3e6671566ccf5c33702e955fe8b018683fcdb812bfe3ed283fc250bb4f798ff3',
|
||||
decimalPlaces: 5,
|
||||
candles: [
|
||||
{
|
||||
open: '16141155',
|
||||
close: '16293551',
|
||||
high: '16320190',
|
||||
low: '16023805',
|
||||
__typename: 'Candle',
|
||||
},
|
||||
{
|
||||
open: '16293548',
|
||||
close: '16322118',
|
||||
high: '16365861',
|
||||
low: '16192970',
|
||||
__typename: 'Candle',
|
||||
},
|
||||
],
|
||||
fees: {
|
||||
factors: {
|
||||
makerFee: 0.0002,
|
||||
infrastructureFee: 0.0005,
|
||||
liquidityFee: 0.001,
|
||||
},
|
||||
},
|
||||
tradableInstrument: {
|
||||
__typename: 'TradableInstrument',
|
||||
instrument: {
|
||||
__typename: 'Instrument',
|
||||
name: 'Apple Monthly (30 Jun 2022)',
|
||||
code: 'AAPL.MF21',
|
||||
product: {
|
||||
settlementAsset: {
|
||||
symbol: 'AAPL.MF21',
|
||||
__typename: 'Asset',
|
||||
},
|
||||
__typename: 'Future',
|
||||
},
|
||||
},
|
||||
},
|
||||
marketTimestamps: {
|
||||
__typename: 'MarketTimestamps',
|
||||
open: '2022-05-18T13:00:39.328347732Z',
|
||||
close: null,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
85
libs/market-list/src/lib/utils/market-utils.ts
Normal file
85
libs/market-list/src/lib/utils/market-utils.ts
Normal file
@ -0,0 +1,85 @@
|
||||
import { formatNumberPercentage } from '@vegaprotocol/react-helpers';
|
||||
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import type {
|
||||
MarketList,
|
||||
MarketList_markets,
|
||||
MarketList_markets_fees_factors,
|
||||
} from '../__generated__/MarketList';
|
||||
|
||||
export const lastPrice = ({ candles }: MarketList_markets) =>
|
||||
candles && candles.length > 0
|
||||
? candles && candles[candles?.length - 1]?.close
|
||||
: undefined;
|
||||
|
||||
export const totalFees = (fees: MarketList_markets_fees_factors) => {
|
||||
if (!fees) {
|
||||
return undefined;
|
||||
}
|
||||
return formatNumberPercentage(
|
||||
new BigNumber(fees.makerFee)
|
||||
.plus(fees.liquidityFee)
|
||||
.plus(fees.makerFee)
|
||||
.times(100)
|
||||
);
|
||||
};
|
||||
|
||||
export const mapDataToMarketList = ({ markets }: MarketList) =>
|
||||
orderBy(
|
||||
markets
|
||||
?.filter(
|
||||
(m) =>
|
||||
m.state !== MarketState.Rejected &&
|
||||
m.tradingMode !== MarketTradingMode.NoTrading
|
||||
)
|
||||
.map((m) => {
|
||||
return {
|
||||
...m,
|
||||
marketName: m.tradableInstrument.instrument?.code,
|
||||
settlementAsset:
|
||||
m.tradableInstrument.instrument.product?.settlementAsset?.symbol,
|
||||
lastPrice: lastPrice(m) ?? m.data?.markPrice,
|
||||
candles: (m.candles || []).filter((c) => c),
|
||||
candleHigh: calcCandleHigh(m),
|
||||
candleLow: calcCandleLow(m),
|
||||
open: m.marketTimestamps.open
|
||||
? new Date(m.marketTimestamps.open).getTime()
|
||||
: null,
|
||||
close: m.marketTimestamps.close
|
||||
? new Date(m.marketTimestamps.close).getTime()
|
||||
: null,
|
||||
totalFees: totalFees(m.fees?.factors),
|
||||
};
|
||||
}) || [],
|
||||
['open', 'id'],
|
||||
['asc', 'asc']
|
||||
);
|
||||
|
||||
export const calcCandleLow = (m: MarketList_markets): string | undefined => {
|
||||
return m.candles
|
||||
?.reduce((acc: BigNumber, c) => {
|
||||
if (c?.low) {
|
||||
if (acc.isLessThan(new BigNumber(c.low))) {
|
||||
return acc;
|
||||
}
|
||||
return new BigNumber(c.low);
|
||||
}
|
||||
return acc;
|
||||
}, new BigNumber(m.candles?.[0]?.high ?? 0))
|
||||
.toString();
|
||||
};
|
||||
|
||||
export const calcCandleHigh = (m: MarketList_markets): string | undefined => {
|
||||
return m.candles
|
||||
?.reduce((acc: BigNumber, c) => {
|
||||
if (c?.high) {
|
||||
if (acc.isGreaterThan(new BigNumber(c.high))) {
|
||||
return acc;
|
||||
}
|
||||
return new BigNumber(c.high);
|
||||
}
|
||||
return acc;
|
||||
}, new BigNumber(0))
|
||||
.toString();
|
||||
};
|
@ -24,7 +24,6 @@ const generateJsx = (
|
||||
<OrderListTable
|
||||
rowData={orders}
|
||||
cancel={jest.fn()}
|
||||
setEditOrderDialogOpen={jest.fn()}
|
||||
setEditOrder={jest.fn()}
|
||||
/>
|
||||
</VegaWalletContext.Provider>
|
||||
|
@ -122,14 +122,14 @@ export interface Positions_party_positions_market {
|
||||
/**
|
||||
* 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 )
|
||||
|
@ -54,7 +54,7 @@ const POSITIONS_FRAGMENT = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
const POSITION_QUERY = gql`
|
||||
export const POSITION_QUERY = gql`
|
||||
${POSITIONS_FRAGMENT}
|
||||
query Positions($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
|
@ -1,15 +1,35 @@
|
||||
import classNames from 'classnames';
|
||||
|
||||
export interface ArrowStyleProps {
|
||||
color?: string;
|
||||
borderX?: number;
|
||||
borderTop?: number;
|
||||
borderBottom?: number;
|
||||
up?: boolean;
|
||||
}
|
||||
|
||||
export const ArrowUp = ({
|
||||
color = 'green',
|
||||
export const RotatingArrow = ({
|
||||
borderX = 4,
|
||||
borderBottom = 4,
|
||||
}: ArrowStyleProps) => (
|
||||
up = true,
|
||||
}: ArrowStyleProps) => {
|
||||
const arrowClassName = `w-0 h-0 border-b-currentColor-dark dark:border-b-currentColor`;
|
||||
return (
|
||||
<span
|
||||
data-testid="arrow-up"
|
||||
className={classNames(
|
||||
{ 'rotate-180 ease-in duration-200': !up, 'ease-in duration-200': up },
|
||||
arrowClassName
|
||||
)}
|
||||
style={{
|
||||
borderLeft: `${borderX}px solid transparent`,
|
||||
borderRight: `${borderX}px solid transparent`,
|
||||
borderBottom: `${borderBottom}px solid`,
|
||||
}}
|
||||
></span>
|
||||
);
|
||||
};
|
||||
|
||||
export const ArrowUp = ({ borderX = 4, borderBottom = 4 }: ArrowStyleProps) => (
|
||||
<span
|
||||
data-testid="arrow-up"
|
||||
style={{
|
||||
@ -17,14 +37,11 @@ export const ArrowUp = ({
|
||||
borderRight: `${borderX}px solid transparent`,
|
||||
borderBottom: `${borderBottom}px solid`,
|
||||
}}
|
||||
className={`w-0 h-0 border-b-${color}-dark dark:border-b-${color}`}
|
||||
className={`w-0 h-0 border-b-currentColor-dark dark:border-b-currentColor`}
|
||||
></span>
|
||||
);
|
||||
export const ArrowDown = ({
|
||||
color = 'red',
|
||||
borderX = 4,
|
||||
borderTop = 4,
|
||||
}: ArrowStyleProps) => (
|
||||
|
||||
export const ArrowDown = ({ borderX = 4, borderTop = 4 }: ArrowStyleProps) => (
|
||||
<span
|
||||
data-testid="arrow-down"
|
||||
style={{
|
||||
@ -32,7 +49,7 @@ export const ArrowDown = ({
|
||||
borderRight: `${borderX}px solid transparent`,
|
||||
borderTop: `${borderTop}px solid`,
|
||||
}}
|
||||
className={`w-0 h-0 border-t-${color}-dark dark:border-t-${color}`}
|
||||
className={`w-0 h-0 border-t-currentColor-dark dark:border-t-currentColor`}
|
||||
></span>
|
||||
);
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
import * as DialogPrimitives from '@radix-ui/react-dialog';
|
||||
import classNames from 'classnames';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { Intent } from '../../utils/intent';
|
||||
import { getIntentShadow, getIntentBorder } from '../../utils/intent';
|
||||
|
||||
import { getIntentBorder, getIntentShadow } from '../../utils/intent';
|
||||
import { Icon } from '../icon';
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import type { Intent } from '../../utils/intent';
|
||||
interface DialogProps {
|
||||
children: ReactNode;
|
||||
open: boolean;
|
||||
@ -13,7 +14,7 @@ interface DialogProps {
|
||||
icon?: ReactNode;
|
||||
intent?: Intent;
|
||||
titleClassNames?: string;
|
||||
contentClassNames?: string;
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
}
|
||||
|
||||
export function Dialog({
|
||||
@ -24,16 +25,21 @@ export function Dialog({
|
||||
icon,
|
||||
intent,
|
||||
titleClassNames,
|
||||
contentClassNames,
|
||||
size = 'medium',
|
||||
}: DialogProps) {
|
||||
const contentClasses = classNames(
|
||||
// Positions the modal in the center of screen
|
||||
'z-20 fixed w-full md:w-[720px] lg:w-[940px] px-28 py-24 top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]',
|
||||
'z-20 fixed px-28 py-24 top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]',
|
||||
// Need to apply background and text colors again as content is rendered in a portal
|
||||
'dark:bg-black dark:text-white-95 bg-white text-black-95',
|
||||
getIntentShadow(intent),
|
||||
getIntentBorder(intent),
|
||||
contentClassNames
|
||||
{
|
||||
'lg:w-[620px] w-full': size === 'small',
|
||||
'w-full w-full md:w-[720px] lg:w-[940px]': size === 'medium',
|
||||
'left-[0px] top-[99px] h-[calc(100%-99px)] border-0 translate-x-[0] translate-y-[0] border-none overflow-y-auto':
|
||||
size === 'large',
|
||||
}
|
||||
);
|
||||
return (
|
||||
<DialogPrimitives.Root open={open} onOpenChange={(x) => onChange?.(x)}>
|
||||
|
@ -5,8 +5,8 @@ export * from './async-renderer';
|
||||
export * from './button';
|
||||
export * from './callout';
|
||||
export * from './card';
|
||||
export * from './copy-with-tooltip';
|
||||
export * from './checkbox';
|
||||
export * from './copy-with-tooltip';
|
||||
export * from './dialog';
|
||||
export * from './dropdown-menu';
|
||||
export * from './form-group';
|
||||
@ -18,6 +18,7 @@ export * from './key-value-table';
|
||||
export * from './link';
|
||||
export * from './loader';
|
||||
export * from './lozenge';
|
||||
export * from './popover';
|
||||
export * from './price-change';
|
||||
export * from './radio-group';
|
||||
export * from './resizable-panel';
|
||||
|
1
libs/ui-toolkit/src/components/popover/index.ts
Normal file
1
libs/ui-toolkit/src/components/popover/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './popover';
|
61
libs/ui-toolkit/src/components/popover/popover.stories.tsx
Normal file
61
libs/ui-toolkit/src/components/popover/popover.stories.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { Intent } from '../../utils/intent';
|
||||
import { Button } from '../button';
|
||||
import { Popover } from './popover';
|
||||
|
||||
import type { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
export default {
|
||||
title: 'Popover',
|
||||
component: Popover,
|
||||
} as ComponentMeta<typeof Popover>;
|
||||
|
||||
const Template: ComponentStory<typeof Popover> = (args) => {
|
||||
const [open, setOpen] = useState(args.open);
|
||||
return (
|
||||
<div>
|
||||
<Popover
|
||||
intent={args.intent}
|
||||
open={open}
|
||||
onChange={setOpen}
|
||||
trigger={<Button variant="accent">Trigger</Button>}
|
||||
>
|
||||
{args.children}
|
||||
</Popover>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
open: false,
|
||||
children: <p>Some content</p>,
|
||||
};
|
||||
|
||||
export const Primary = Template.bind({});
|
||||
Primary.args = {
|
||||
open: false,
|
||||
intent: Intent.Primary,
|
||||
children: <p>Some content</p>,
|
||||
};
|
||||
|
||||
export const Danger = Template.bind({});
|
||||
Danger.args = {
|
||||
open: false,
|
||||
children: <p>Some content</p>,
|
||||
intent: Intent.Danger,
|
||||
};
|
||||
|
||||
export const Warning = Template.bind({});
|
||||
Warning.args = {
|
||||
open: false,
|
||||
children: <p>Some content</p>,
|
||||
intent: Intent.Warning,
|
||||
};
|
||||
|
||||
export const Success = Template.bind({});
|
||||
Success.args = {
|
||||
open: false,
|
||||
children: <p>Some content</p>,
|
||||
intent: Intent.Success,
|
||||
};
|
52
libs/ui-toolkit/src/components/popover/popover.tsx
Normal file
52
libs/ui-toolkit/src/components/popover/popover.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
||||
import classNames from 'classnames';
|
||||
import { getIntentBorder } from '../../utils/intent';
|
||||
import type { Intent } from '../../utils/intent';
|
||||
|
||||
export interface PopoverProps extends PopoverPrimitive.PopoverProps {
|
||||
trigger: React.ReactNode | string;
|
||||
children: React.ReactNode;
|
||||
open?: boolean;
|
||||
onChange?: (open: boolean) => void;
|
||||
intent?: Intent;
|
||||
}
|
||||
|
||||
export const Popover = ({
|
||||
trigger,
|
||||
children,
|
||||
open,
|
||||
onChange,
|
||||
intent,
|
||||
}: PopoverProps) => {
|
||||
return (
|
||||
<PopoverPrimitive.Root open={open} onOpenChange={(x) => onChange?.(x)}>
|
||||
<PopoverPrimitive.Trigger
|
||||
data-testid="popover-trigger"
|
||||
className={classNames(
|
||||
getIntentBorder(intent),
|
||||
'border-none',
|
||||
'ease-in-out duration-200'
|
||||
)}
|
||||
>
|
||||
{trigger}
|
||||
</PopoverPrimitive.Trigger>
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
data-testid="popover-content"
|
||||
className={classNames(
|
||||
' w-[100vw] h-full ',
|
||||
getIntentBorder(intent),
|
||||
'dark:bg-black bg-white',
|
||||
{
|
||||
'border-2': open,
|
||||
'border-none': !open,
|
||||
},
|
||||
'ease-in-out duration-75'
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</PopoverPrimitive.Content>
|
||||
</PopoverPrimitive.Portal>
|
||||
</PopoverPrimitive.Root>
|
||||
);
|
||||
};
|
@ -28,7 +28,7 @@ export const Tooltip = ({ children, description, open, align }: TooltipProps) =>
|
||||
<Arrow
|
||||
width={10}
|
||||
height={5}
|
||||
className="z-[1] mx-8 fill-black-60 dark:fill-white-60 z-0 translate-x-[1px] translate-y-[-1px]"
|
||||
className="mx-8 fill-black-60 dark:fill-white-60 z-0 translate-x-[1px] translate-y-[-1px]"
|
||||
/>
|
||||
<Arrow
|
||||
width={8}
|
||||
|
@ -25,6 +25,7 @@
|
||||
"@radix-ui/react-dialog": "^0.1.5",
|
||||
"@radix-ui/react-dropdown-menu": "^0.1.6",
|
||||
"@radix-ui/react-icons": "^1.1.1",
|
||||
"@radix-ui/react-popover": "^1.0.0",
|
||||
"@radix-ui/react-radio-group": "^0.1.5",
|
||||
"@radix-ui/react-select": "^0.1.1",
|
||||
"@radix-ui/react-slider": "^1.0.0",
|
||||
|
154
yarn.lock
154
yarn.lock
@ -2126,6 +2126,26 @@
|
||||
"@ethersproject/properties" "^5.6.0"
|
||||
"@ethersproject/strings" "^5.6.1"
|
||||
|
||||
"@floating-ui/core@^0.7.3":
|
||||
version "0.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-0.7.3.tgz#d274116678ffae87f6b60e90f88cc4083eefab86"
|
||||
integrity sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg==
|
||||
|
||||
"@floating-ui/dom@^0.5.3":
|
||||
version "0.5.4"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-0.5.4.tgz#4eae73f78bcd4bd553ae2ade30e6f1f9c73fe3f1"
|
||||
integrity sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==
|
||||
dependencies:
|
||||
"@floating-ui/core" "^0.7.3"
|
||||
|
||||
"@floating-ui/react-dom@0.7.2":
|
||||
version "0.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-0.7.2.tgz#0bf4ceccb777a140fc535c87eb5d6241c8e89864"
|
||||
integrity sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==
|
||||
dependencies:
|
||||
"@floating-ui/dom" "^0.5.3"
|
||||
use-isomorphic-layout-effect "^1.1.1"
|
||||
|
||||
"@gar/promisify@^1.0.1":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6"
|
||||
@ -3333,6 +3353,14 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "0.1.4"
|
||||
|
||||
"@radix-ui/react-arrow@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.0.tgz#c461f4c2cab3317e3d42a1ae62910a4cbb0192a1"
|
||||
integrity sha512-1MUuv24HCdepi41+qfv125EwMuxgQ+U+h0A9K3BjCO/J8nVRREKHHpkD9clwfnjEDk9hgGzCnff4aUKCPiRepw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.0"
|
||||
|
||||
"@radix-ui/react-collapsible@0.1.6":
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-0.1.6.tgz#3eeadac476761b3c9b8dd91e8a32eb1a547e5a06"
|
||||
@ -3439,6 +3467,18 @@
|
||||
"@radix-ui/react-use-callback-ref" "0.1.0"
|
||||
"@radix-ui/react-use-escape-keydown" "0.1.0"
|
||||
|
||||
"@radix-ui/react-dismissable-layer@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz#35b7826fa262fd84370faef310e627161dffa76b"
|
||||
integrity sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.0"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-primitive" "1.0.0"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.0"
|
||||
"@radix-ui/react-use-escape-keydown" "1.0.0"
|
||||
|
||||
"@radix-ui/react-dropdown-menu@^0.1.6":
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-0.1.6.tgz#3203229788cd57e552c9f19dcc7008e2b545919c"
|
||||
@ -3460,6 +3500,13 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-focus-guards@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz#339c1c69c41628c1a5e655f15f7020bf11aa01fa"
|
||||
integrity sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-focus-scope@0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-0.1.4.tgz#c830724e212d42ffaaa81aee49533213d09b47df"
|
||||
@ -3470,6 +3517,16 @@
|
||||
"@radix-ui/react-primitive" "0.1.4"
|
||||
"@radix-ui/react-use-callback-ref" "0.1.0"
|
||||
|
||||
"@radix-ui/react-focus-scope@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz#95a0c1188276dc8933b1eac5f1cdb6471e01ade5"
|
||||
integrity sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-primitive" "1.0.0"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.0"
|
||||
|
||||
"@radix-ui/react-icons@^1.1.1":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-icons/-/react-icons-1.1.1.tgz#38d2aa548035dd3b799c169bd17177b1cec3152b"
|
||||
@ -3483,6 +3540,14 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-layout-effect" "0.1.0"
|
||||
|
||||
"@radix-ui/react-id@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.0.tgz#8d43224910741870a45a8c9d092f25887bb6d11e"
|
||||
integrity sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.0"
|
||||
|
||||
"@radix-ui/react-label@0.1.5":
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-label/-/react-label-0.1.5.tgz#12cd965bfc983e0148121d4c99fb8e27a917c45c"
|
||||
@ -3518,6 +3583,28 @@
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "^2.4.0"
|
||||
|
||||
"@radix-ui/react-popover@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.0.0.tgz#5ee72013089fdf9038417fc1eb98a749c17457fd"
|
||||
integrity sha512-osxFFO0TiZ9ABpEOitZu0R1Fdd+tSpJgAqLZxRLLdZQ7ya0onSODcITp5hXDVuYQeVXH6pKEBGwXN6ZGjZ0a5g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.0"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-context" "1.0.0"
|
||||
"@radix-ui/react-dismissable-layer" "1.0.0"
|
||||
"@radix-ui/react-focus-guards" "1.0.0"
|
||||
"@radix-ui/react-focus-scope" "1.0.0"
|
||||
"@radix-ui/react-id" "1.0.0"
|
||||
"@radix-ui/react-popper" "1.0.0"
|
||||
"@radix-ui/react-portal" "1.0.0"
|
||||
"@radix-ui/react-presence" "1.0.0"
|
||||
"@radix-ui/react-primitive" "1.0.0"
|
||||
"@radix-ui/react-slot" "1.0.0"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.0"
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "2.5.4"
|
||||
|
||||
"@radix-ui/react-popper@0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-0.1.4.tgz#dfc055dcd7dfae6a2eff7a70d333141d15a5d029"
|
||||
@ -3533,6 +3620,22 @@
|
||||
"@radix-ui/react-use-size" "0.1.1"
|
||||
"@radix-ui/rect" "0.1.1"
|
||||
|
||||
"@radix-ui/react-popper@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.0.0.tgz#fb4f937864bf39c48f27f55beee61fa9f2bef93c"
|
||||
integrity sha512-k2dDd+1Wl0XWAMs9ZvAxxYsB9sOsEhrFQV4CINd7IUZf0wfdye4OHen9siwxvZImbzhgVeKTJi68OQmPRvVdMg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@floating-ui/react-dom" "0.7.2"
|
||||
"@radix-ui/react-arrow" "1.0.0"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-context" "1.0.0"
|
||||
"@radix-ui/react-primitive" "1.0.0"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.0"
|
||||
"@radix-ui/react-use-rect" "1.0.0"
|
||||
"@radix-ui/react-use-size" "1.0.0"
|
||||
"@radix-ui/rect" "1.0.0"
|
||||
|
||||
"@radix-ui/react-portal@0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.1.4.tgz#17bdce3d7f1a9a0b35cb5e935ab8bc562441a7d2"
|
||||
@ -3542,6 +3645,14 @@
|
||||
"@radix-ui/react-primitive" "0.1.4"
|
||||
"@radix-ui/react-use-layout-effect" "0.1.0"
|
||||
|
||||
"@radix-ui/react-portal@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.0.tgz#7220b66743394fabb50c55cb32381395cc4a276b"
|
||||
integrity sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.0"
|
||||
|
||||
"@radix-ui/react-presence@0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-0.1.2.tgz#9f11cce3df73cf65bc348e8b76d891f0d54c1fe3"
|
||||
@ -3551,6 +3662,15 @@
|
||||
"@radix-ui/react-compose-refs" "0.1.0"
|
||||
"@radix-ui/react-use-layout-effect" "0.1.0"
|
||||
|
||||
"@radix-ui/react-presence@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.0.tgz#814fe46df11f9a468808a6010e3f3ca7e0b2e84a"
|
||||
integrity sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.0"
|
||||
|
||||
"@radix-ui/react-primitive@0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.1.4.tgz#6c233cf08b0cb87fecd107e9efecb3f21861edc1"
|
||||
@ -3745,6 +3865,14 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-callback-ref" "0.1.0"
|
||||
|
||||
"@radix-ui/react-use-escape-keydown@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz#aef375db4736b9de38a5a679f6f49b45a060e5d1"
|
||||
integrity sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.0"
|
||||
|
||||
"@radix-ui/react-use-layout-effect@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-0.1.0.tgz#ebf71bd6d2825de8f1fbb984abf2293823f0f223"
|
||||
@ -3781,6 +3909,14 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/rect" "0.1.1"
|
||||
|
||||
"@radix-ui/react-use-rect@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz#b040cc88a4906b78696cd3a32b075ed5b1423b3e"
|
||||
integrity sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/rect" "1.0.0"
|
||||
|
||||
"@radix-ui/react-use-size@0.1.1":
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-0.1.1.tgz#f6b75272a5d41c3089ca78c8a2e48e5f204ef90f"
|
||||
@ -3811,6 +3947,13 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/rect@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.0.0.tgz#0dc8e6a829ea2828d53cbc94b81793ba6383bf3c"
|
||||
integrity sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@rollup/plugin-babel@^5.3.0":
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"
|
||||
@ -18096,6 +18239,17 @@ react-remove-scroll-bar@^2.3.3:
|
||||
react-style-singleton "^2.2.1"
|
||||
tslib "^2.0.0"
|
||||
|
||||
react-remove-scroll@2.5.4:
|
||||
version "2.5.4"
|
||||
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz#afe6491acabde26f628f844b67647645488d2ea0"
|
||||
integrity sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==
|
||||
dependencies:
|
||||
react-remove-scroll-bar "^2.3.3"
|
||||
react-style-singleton "^2.2.1"
|
||||
tslib "^2.1.0"
|
||||
use-callback-ref "^1.3.0"
|
||||
use-sidecar "^1.1.2"
|
||||
|
||||
react-remove-scroll@^2.4.0:
|
||||
version "2.5.5"
|
||||
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz#1e31a1260df08887a8a0e46d09271b52b3a37e77"
|
||||
|
Loading…
Reference in New Issue
Block a user