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:
m.ray 2022-08-11 13:56:35 +02:00 committed by GitHub
parent 1be1a78a69
commit 0523b56e39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 1790 additions and 751 deletions

View File

@ -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', () => {

View File

@ -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');

View File

@ -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} />

View File

@ -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() {

View File

@ -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')}
/>
</>
);

View File

@ -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>

View File

@ -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 });
},
}));

View File

@ -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';

View File

@ -0,0 +1,3 @@
export * from './AccountFields';
export * from './AccountSubscribe';
export * from './Accounts';

View 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';

View File

@ -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';

View File

@ -0,0 +1,4 @@
export * from './CandleFields';
export * from './Candles';
export * from './CandlesSub';
export * from './Chart';

View File

@ -0,0 +1,3 @@
export * from './__generated__';
export * from './candles-chart';
export * from './data-source';

View File

@ -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.'

View File

@ -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>

View File

@ -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>
);

View File

@ -1,3 +1 @@
export * from './lib/depth-chart';
export * from './lib/orderbook-container';
export * from './lib/__generated__/MarketDepth';
export * from './lib';

View File

@ -0,0 +1,2 @@
export * from './MarketDepth';
export * from './MarketDepthSubscription';

View 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';

View File

@ -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 = {

View File

@ -1,2 +1 @@
export * from './lib/components';
export * from './lib/utils';
export * from './lib';

View File

@ -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;
}

View File

@ -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 {

View File

@ -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
*/

View File

@ -1,4 +1,3 @@
export * from './MarketDataFields';
export * from './MarketDataSub';
export * from './MarketList';
export * from './Markets';

View File

@ -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';

View File

@ -1,3 +0,0 @@
export * from './landing-dialog';
export * from './select-market-dialog';
export * from './select-market-list';

View File

@ -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>
);
};

View File

@ -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();
});
});

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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;
}

View File

@ -1,5 +1 @@
export * from './market-list-table';
export * from './markets-container';
export * from './markets-data-provider';
export * from './summary-cell';
export * from './__generated__';

View File

@ -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();
});
});
});

View File

@ -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

View File

@ -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}`)
}
/>

View 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>
);
};

View 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>
);
};

View File

@ -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,
],
},
};

View 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>
);
};

View File

@ -0,0 +1,4 @@
export * from './__generated__';
export * from './components';
export * from './utils';
export * from './markets-data-provider';

View File

@ -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);

View File

@ -1 +1 @@
export * from './market-list.utils';
export * from './market-utils';

View File

@ -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',
},
],
},
],
},
};

View File

@ -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']
);

View 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,
},
},
],
},
};

View 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();
};

View File

@ -24,7 +24,6 @@ const generateJsx = (
<OrderListTable
rowData={orders}
cancel={jest.fn()}
setEditOrderDialogOpen={jest.fn()}
setEditOrder={jest.fn()}
/>
</VegaWalletContext.Provider>

View File

@ -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 )

View File

@ -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) {

View File

@ -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>
);

View File

@ -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)}>

View File

@ -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';

View File

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

View 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,
};

View 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>
);
};

View File

@ -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}

View File

@ -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
View File

@ -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"