feat: trading page market summary & select markets modal opening from market title & fix: positions table realised PnL (#505)
* feat: [#456] select markets modal opening from market title * feat: add a global zustand store for managing connect dialogs and landing dialog * feat: add tests * feat: [#456] make arrow configurable * feat: [#456] make arrow configurable * feat: [#456] trading tab active only on portfolio * chore: update tranches Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix: [#445] shallow routing from index (#484) * fix: [#445] shallow routing from index * fix: [#445] use link to redirect to market - an attempt to fix reload * fix: [#445] remove stretched link from last link - it makes all the other links unusable * fix: [#445] fix lint on select market list - remove stretched link * fix: [#456] put everything in landing folder to avoid conflicts * fix: remove condition for cypress for auto connecting * feat: [#456] add global store and fix href routing * feat: [#456] add global store and fix href routing * feat: [#456] add one more test * feat: [#154] pull market data summary * feat: [#154] move header above the trade grid child sections * feat: [#154] flex oerflow and styling updates for market summary * feat: [#154] fix styling * fix: [154] fix cyp tests and styling * fix: [#154] fix markets navigation cypress step * fix: [#154] fix for navigate to markets link * fix: failing tests from market change * fix: [#154] set nav items based on market id and show last viewed market on landing * fix: [#412] invalid decimal place on realised PnL field * fix: [#154] remove redundant curly braces * fix: [#154] show hyphen on volume if market data is undefined Co-authored-by: Matthew Russell <mattrussell36@gmail.com> Co-authored-by: dexturr <dexturr@users.noreply.github.com> Co-authored-by: Joe <joe@vega.xyz>
This commit is contained in:
parent
29aa93dd3c
commit
a65c52d7d4
@ -1,6 +1,6 @@
|
||||
export default class BasePage {
|
||||
closeDialogBtn = 'dialog-close';
|
||||
porfolioUrl = '/portfolio';
|
||||
portfolioUrl = '/portfolio';
|
||||
marketsUrl = '/markets';
|
||||
assetSelectField = 'select[name="asset"]';
|
||||
toAddressField = 'input[name="to"]';
|
||||
@ -10,21 +10,22 @@ export default class BasePage {
|
||||
dialogText = 'dialog-text';
|
||||
|
||||
closeDialog() {
|
||||
cy.getByTestId(this.closeDialogBtn, { timeout: 8000 }).click({
|
||||
cy.getByTestId(this.closeDialogBtn, { timeout: 8000 })?.click({
|
||||
force: true,
|
||||
});
|
||||
}
|
||||
|
||||
navigateToPortfolio() {
|
||||
cy.get(`a[href='${this.porfolioUrl}']`).should('be.visible').click();
|
||||
cy.get(`a[href='${this.portfolioUrl}']`)
|
||||
.should('be.visible')
|
||||
.click({ force: true });
|
||||
cy.url().should('include', '/portfolio');
|
||||
cy.getByTestId('portfolio');
|
||||
}
|
||||
|
||||
navigateToMarkets() {
|
||||
cy.get(`a[href='${this.marketsUrl}']`).should('be.visible').click();
|
||||
cy.getByTestId('markets-link').should('be.visible').click({ force: true });
|
||||
cy.url().should('include', '/markets');
|
||||
cy.getByTestId('markets');
|
||||
}
|
||||
|
||||
verifyFormErrorDisplayed(expectedError: string, expectedNumErrors: number) {
|
||||
@ -35,7 +36,7 @@ export default class BasePage {
|
||||
);
|
||||
}
|
||||
|
||||
updateTransactionform(args?: {
|
||||
updateTransactionForm(args?: {
|
||||
asset?: string;
|
||||
to?: string;
|
||||
amount?: string;
|
||||
|
@ -1,13 +1,14 @@
|
||||
import BasePage from './base-page';
|
||||
|
||||
export default class MarketPage extends BasePage {
|
||||
marketRowHeaderClassname = '.ag-header-cell-text';
|
||||
marketRowHeaderClassname = 'div > span.ag-header-cell-text';
|
||||
marketRowNameColumn = 'tradableInstrument.instrument.code';
|
||||
marketRowSymbolColumn =
|
||||
'tradableInstrument.instrument.product.settlementAsset.symbol';
|
||||
marketRowPrices = 'flash-cell';
|
||||
marketRowDescription = 'name';
|
||||
marketStateColId = 'data';
|
||||
openMarketMenu = 'arrow-down';
|
||||
|
||||
validateMarketsAreDisplayed() {
|
||||
// We need this to ensure that ag-grid is fully rendered before asserting
|
||||
@ -27,16 +28,12 @@ export default class MarketPage extends BasePage {
|
||||
'Description',
|
||||
];
|
||||
|
||||
cy.get(this.marketRowHeaderClassname)
|
||||
.each(($marketHeader, index) => {
|
||||
cy.wrap($marketHeader).should(
|
||||
'have.text',
|
||||
expectedMarketHeaders[index]
|
||||
);
|
||||
})
|
||||
.then(($list) => {
|
||||
cy.wrap($list).should('have.length', expectedMarketHeaders.length);
|
||||
});
|
||||
for (let index = 0; index < expectedMarketHeaders.length; index++) {
|
||||
cy.get(this.marketRowHeaderClassname).should(
|
||||
'contain.text',
|
||||
expectedMarketHeaders[index]
|
||||
);
|
||||
}
|
||||
|
||||
cy.get(`[col-id='${this.marketRowNameColumn}']`).each(($marketName) => {
|
||||
cy.wrap($marketName).should('not.be.empty');
|
||||
@ -65,4 +62,8 @@ export default class MarketPage extends BasePage {
|
||||
'portfolio=orders&trade=orderbook'
|
||||
);
|
||||
}
|
||||
|
||||
clickOpenMarketMenu() {
|
||||
cy.getByTestId(this.openMarketMenu).click();
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,10 @@ import { Given } from 'cypress-cucumber-preprocessor/steps';
|
||||
import { hasOperationName } from '..';
|
||||
import { generateMarketList } from '../mocks/generate-market-list';
|
||||
import BasePage from '../pages/base-page';
|
||||
import MarketPage from '../pages/markets-page';
|
||||
|
||||
const basePage = new BasePage();
|
||||
const marketPage = new MarketPage();
|
||||
|
||||
Given('I am on the homepage', () => {
|
||||
cy.mockGQL('MarketsList', (req) => {
|
||||
@ -15,4 +17,5 @@ Given('I am on the homepage', () => {
|
||||
});
|
||||
cy.visit('/');
|
||||
basePage.closeDialog();
|
||||
marketPage.validateMarketsAreDisplayed();
|
||||
});
|
||||
|
@ -30,7 +30,7 @@ Then('I can see the deposit form', () => {
|
||||
});
|
||||
|
||||
When('I submit a deposit with empty fields', () => {
|
||||
depositsPage.updateTransactionform();
|
||||
depositsPage.updateTransactionForm();
|
||||
depositsPage.submitForm();
|
||||
});
|
||||
|
||||
@ -39,7 +39,7 @@ Then('I can see empty form validation errors present', () => {
|
||||
});
|
||||
|
||||
Then('I enter the following deposit details in deposit form', (table) => {
|
||||
depositsPage.updateTransactionform({
|
||||
depositsPage.updateTransactionForm({
|
||||
asset: table.rowsHash().asset,
|
||||
to: Cypress.env(table.rowsHash().to),
|
||||
amount: table.rowsHash().amount,
|
||||
@ -59,7 +59,7 @@ Then('Amount too small message shown', () => {
|
||||
});
|
||||
|
||||
And('I enter a valid amount', () => {
|
||||
depositsPage.updateTransactionform({ amount: '1' });
|
||||
depositsPage.updateTransactionForm({ amount: '1' });
|
||||
});
|
||||
|
||||
Then('Not approved message shown', () => {
|
||||
|
@ -19,6 +19,7 @@ const mockMarkets = () => {
|
||||
Then('I navigate to markets page', () => {
|
||||
mockMarkets();
|
||||
marketsPage.navigateToMarkets();
|
||||
marketsPage.clickOpenMarketMenu();
|
||||
cy.wait('@Markets');
|
||||
});
|
||||
|
||||
|
@ -88,7 +88,7 @@ Given('I am on the trading page for an active market', () => {
|
||||
|
||||
cy.visit('/markets/market-id');
|
||||
cy.wait('@Market');
|
||||
cy.contains('Market: ACTIVE MARKET');
|
||||
cy.contains('ACTIVE MARKET');
|
||||
});
|
||||
|
||||
Given('I am on the trading page for a suspended market', () => {
|
||||
@ -96,7 +96,7 @@ Given('I am on the trading page for a suspended market', () => {
|
||||
|
||||
cy.visit('/markets/market-id');
|
||||
cy.wait('@Market');
|
||||
cy.contains('Market: SUSPENDED MARKET');
|
||||
cy.contains('SUSPENDED MARKET');
|
||||
});
|
||||
|
||||
When('I click on {string} mocked market', (marketType) => {
|
||||
@ -115,11 +115,11 @@ Then('trading page for {string} market is displayed', (marketType) => {
|
||||
switch (marketType) {
|
||||
case 'active':
|
||||
cy.wait('@Market');
|
||||
cy.contains('Market: ACTIVE MARKET');
|
||||
cy.contains('ACTIVE MARKET');
|
||||
break;
|
||||
case 'suspended':
|
||||
cy.wait('@Market');
|
||||
cy.contains('Market: SUSPENDED MARKET');
|
||||
cy.contains('SUSPENDED MARKET');
|
||||
break;
|
||||
}
|
||||
tradingPage.clickOnTradesTab();
|
||||
|
@ -27,6 +27,7 @@ When('I connect to Vega Wallet', () => {
|
||||
Cypress.env('TRADING_TEST_VEGA_WALLET_PASSPHRASE')
|
||||
);
|
||||
vegaWallet.clickConnectVegaWallet();
|
||||
vegaWallet.validateWalletConnected();
|
||||
});
|
||||
|
||||
When('I open wallet dialog', () => {
|
||||
|
@ -1,13 +1,16 @@
|
||||
import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps';
|
||||
import MarketPage from '../pages/markets-page';
|
||||
import PortfolioPage from '../pages/portfolio-page';
|
||||
import WithdrawalsPage from '../pages/withdrawals-page';
|
||||
|
||||
const marketPage = new MarketPage();
|
||||
const portfolioPage = new PortfolioPage();
|
||||
const withdrawalsPage = new WithdrawalsPage();
|
||||
|
||||
Given('I navigate to withdrawal page', () => {
|
||||
cy.visit('/');
|
||||
portfolioPage.closeDialog();
|
||||
marketPage.validateMarketsAreDisplayed();
|
||||
portfolioPage.navigateToPortfolio();
|
||||
portfolioPage.navigateToWithdraw();
|
||||
});
|
||||
@ -26,14 +29,14 @@ When('click submit', () => {
|
||||
});
|
||||
|
||||
When('I enter an invalid ethereum address', () => {
|
||||
withdrawalsPage.updateTransactionform({
|
||||
withdrawalsPage.updateTransactionForm({
|
||||
to: '0x0dAAACaa868f87BB4666F918742141cAEAe893Fa',
|
||||
});
|
||||
withdrawalsPage.clickSubmit();
|
||||
});
|
||||
|
||||
When('I select {string}', (selectedAsset) => {
|
||||
withdrawalsPage.updateTransactionform({
|
||||
withdrawalsPage.updateTransactionForm({
|
||||
asset: selectedAsset,
|
||||
});
|
||||
});
|
||||
@ -47,7 +50,7 @@ When('I click Use maximum', () => {
|
||||
});
|
||||
|
||||
When('I enter the following details in withdrawal form', (table) => {
|
||||
withdrawalsPage.updateTransactionform({
|
||||
withdrawalsPage.updateTransactionForm({
|
||||
asset: table.rowsHash().asset,
|
||||
to: table.rowsHash().to,
|
||||
amount: table.rowsHash().amount,
|
||||
@ -56,7 +59,7 @@ When('I enter the following details in withdrawal form', (table) => {
|
||||
});
|
||||
|
||||
When('I succesfully fill in and submit withdrawal form', () => {
|
||||
withdrawalsPage.updateTransactionform({
|
||||
withdrawalsPage.updateTransactionForm({
|
||||
asset: Cypress.env('WITHDRAWAL_ASSET_ID'),
|
||||
amount: '0.1',
|
||||
});
|
||||
|
@ -20,11 +20,11 @@ export default class DealTicket {
|
||||
);
|
||||
|
||||
if (isBuy == false) {
|
||||
cy.getByTestId(this.sellOrder).click();
|
||||
cy.getByTestId(this.sellOrder)?.click();
|
||||
}
|
||||
|
||||
cy.getByTestId(this.orderSizeField).clear().type(orderSize);
|
||||
cy.getByTestId(this.orderTypeDropDown).select(orderType);
|
||||
cy.getByTestId(this.orderSizeField)?.clear().type(orderSize);
|
||||
cy.getByTestId(this.orderTypeDropDown)?.select(orderType);
|
||||
}
|
||||
|
||||
placeLimitOrder(
|
||||
@ -33,10 +33,10 @@ export default class DealTicket {
|
||||
orderPrice: string,
|
||||
orderType: string
|
||||
) {
|
||||
cy.getByTestId(this.limitOrderType).click();
|
||||
cy.getByTestId(this.limitOrderType)?.click();
|
||||
|
||||
if (isBuy == false) {
|
||||
cy.getByTestId(this.sellOrder).click();
|
||||
cy.getByTestId(this.sellOrder)?.click();
|
||||
}
|
||||
|
||||
cy.getByTestId(this.orderSizeField).clear().type(orderSize);
|
||||
|
@ -50,6 +50,10 @@ export default class VegaWallet {
|
||||
);
|
||||
}
|
||||
|
||||
validateWalletConnected() {
|
||||
cy.getByTestId(this.connectVegaBtn).should('contain.text', '…');
|
||||
}
|
||||
|
||||
selectPublicKey() {
|
||||
cy.getByTestId(this.selectPublicKeyBtn).click();
|
||||
}
|
||||
|
@ -2,9 +2,38 @@ import { useRouter } from 'next/router';
|
||||
import { Vega } from '../icons/vega';
|
||||
import Link from 'next/link';
|
||||
import { AnchorButton } from '@vegaprotocol/ui-toolkit';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { LocalStorage, t } from '@vegaprotocol/react-helpers';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const Navbar = () => {
|
||||
const initNavItemsState = [
|
||||
{
|
||||
name: t('Portfolio'),
|
||||
path: '/portfolio',
|
||||
testId: 'portfolio-link',
|
||||
slug: '',
|
||||
},
|
||||
];
|
||||
const [navItems, setNavItems] = useState(initNavItemsState);
|
||||
const marketId = LocalStorage.getItem('marketId') ?? '';
|
||||
|
||||
useEffect(() => {
|
||||
setNavItems([
|
||||
{
|
||||
name: t('Trading'),
|
||||
path: '/markets',
|
||||
testId: 'markets-link',
|
||||
slug: marketId,
|
||||
},
|
||||
{
|
||||
name: t('Portfolio'),
|
||||
path: '/portfolio',
|
||||
testId: 'portfolio-link',
|
||||
slug: '',
|
||||
},
|
||||
]);
|
||||
}, [marketId]);
|
||||
|
||||
return (
|
||||
<nav className="flex items-center">
|
||||
<Link href="/" passHref={true}>
|
||||
@ -12,10 +41,7 @@ export const Navbar = () => {
|
||||
<Vega className="fill-black dark:fill-white" />
|
||||
</a>
|
||||
</Link>
|
||||
{[
|
||||
{ name: t('Trading'), path: '/markets' },
|
||||
{ name: t('Portfolio'), path: '/portfolio' },
|
||||
].map((route) => (
|
||||
{navItems.map((route) => (
|
||||
<NavLink key={route.path} {...route} />
|
||||
))}
|
||||
</nav>
|
||||
@ -26,20 +52,30 @@ interface NavLinkProps {
|
||||
name: string;
|
||||
path: string;
|
||||
exact?: boolean;
|
||||
testId?: string;
|
||||
slug?: string;
|
||||
}
|
||||
|
||||
const NavLink = ({ name, path, exact }: NavLinkProps) => {
|
||||
const NavLink = ({
|
||||
name,
|
||||
path,
|
||||
exact,
|
||||
testId = name,
|
||||
slug = '',
|
||||
}: NavLinkProps) => {
|
||||
const router = useRouter();
|
||||
const isActive =
|
||||
router.asPath === path || (!exact && router.asPath.startsWith(path));
|
||||
const href = slug !== '' ? `${path}/${slug}` : path;
|
||||
return (
|
||||
<AnchorButton
|
||||
variant={isActive ? 'accent' : 'inline'}
|
||||
className="px-16 py-6 h-[38px] uppercase border-0 self-end xs:text-ui sm:text-body-large md:text-h5 lg:text-h4"
|
||||
href={path}
|
||||
data-testid={testId}
|
||||
href={href}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
router.push(path);
|
||||
router.push(href);
|
||||
}}
|
||||
>
|
||||
{name}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import { LocalStorage } from '@vegaprotocol/react-helpers';
|
||||
import { MarketTradingMode } from '@vegaprotocol/types';
|
||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||
import sortBy from 'lodash/sortBy';
|
||||
@ -35,10 +36,13 @@ export function Index() {
|
||||
// should be the oldest market that is currently trading in continuous mode(i.e. not in auction).
|
||||
const { data, error, loading } = useQuery<MarketsLanding>(MARKETS_QUERY);
|
||||
const setLandingDialog = useGlobalStore((state) => state.setLandingDialog);
|
||||
const lastSelectedMarketId = LocalStorage.getItem('marketId');
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const marketId = marketList(data)[0]?.id;
|
||||
const marketId = lastSelectedMarketId
|
||||
? lastSelectedMarketId
|
||||
: marketList(data)[0]?.id;
|
||||
|
||||
// If a default market is found, go to it with the landing dialog open
|
||||
if (marketId) {
|
||||
@ -50,7 +54,7 @@ export function Index() {
|
||||
replace('/markets');
|
||||
}
|
||||
}
|
||||
}, [data, replace, setLandingDialog]);
|
||||
}, [data, lastSelectedMarketId, replace, setLandingDialog]);
|
||||
|
||||
return (
|
||||
<AsyncRenderer data={data} loading={loading} error={error}>
|
||||
|
@ -1,21 +1,55 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import type { Market, MarketVariables } from './__generated__/Market';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
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 { LocalStorage, 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';
|
||||
|
||||
// Top level page query
|
||||
const MARKET_QUERY = gql`
|
||||
query Market($marketId: ID!) {
|
||||
query Market($marketId: ID!, $interval: Interval!, $since: String!) {
|
||||
market(id: $marketId) {
|
||||
id
|
||||
name
|
||||
tradingMode
|
||||
state
|
||||
decimalPlaces
|
||||
data {
|
||||
market {
|
||||
id
|
||||
}
|
||||
markPrice
|
||||
indicativeVolume
|
||||
bestBidVolume
|
||||
bestOfferVolume
|
||||
bestStaticBidVolume
|
||||
bestStaticOfferVolume
|
||||
indicativeVolume
|
||||
}
|
||||
tradableInstrument {
|
||||
instrument {
|
||||
name
|
||||
code
|
||||
metadata {
|
||||
tags
|
||||
}
|
||||
}
|
||||
}
|
||||
marketTimestamps {
|
||||
open
|
||||
close
|
||||
}
|
||||
candles(interval: $interval, since: $since) {
|
||||
open
|
||||
close
|
||||
volume
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -29,6 +63,9 @@ const MarketPage = ({ id }: { id?: string }) => {
|
||||
const marketId =
|
||||
id || (Array.isArray(query.marketId) ? query.marketId[0] : query.marketId);
|
||||
|
||||
const yesterday = Math.round(new Date().getTime() / 1000) - 24 * 3600;
|
||||
const yTimestamp = new Date(yesterday * 1000).toISOString();
|
||||
|
||||
if (!marketId) {
|
||||
return (
|
||||
<Splash>
|
||||
@ -37,12 +74,15 @@ const MarketPage = ({ id }: { id?: string }) => {
|
||||
);
|
||||
}
|
||||
|
||||
LocalStorage.setItem('marketId', marketId);
|
||||
return (
|
||||
<PageQueryContainer<Market, MarketVariables>
|
||||
query={MARKET_QUERY}
|
||||
options={{
|
||||
variables: {
|
||||
marketId,
|
||||
interval: Interval.I1H,
|
||||
since: yTimestamp,
|
||||
},
|
||||
fetchPolicy: 'network-only',
|
||||
}}
|
||||
|
145
apps/trading/pages/markets/__generated__/Market.ts
generated
145
apps/trading/pages/markets/__generated__/Market.ts
generated
@ -3,10 +3,112 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { Interval, MarketTradingMode, MarketState } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: Market
|
||||
// ====================================================
|
||||
|
||||
export interface Market_market_data_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
* Market ID
|
||||
*/
|
||||
id: string;
|
||||
}
|
||||
|
||||
export interface Market_market_data {
|
||||
__typename: "MarketData";
|
||||
/**
|
||||
* market id of the associated mark price
|
||||
*/
|
||||
market: Market_market_data_market;
|
||||
/**
|
||||
* the mark price (actually an unsigned int)
|
||||
*/
|
||||
markPrice: string;
|
||||
/**
|
||||
* indicative volume if the auction ended now, 0 if not in auction mode
|
||||
*/
|
||||
indicativeVolume: string;
|
||||
/**
|
||||
* the aggregated volume being bid at the best bid price.
|
||||
*/
|
||||
bestBidVolume: string;
|
||||
/**
|
||||
* the aggregated volume being offered at the best offer price.
|
||||
*/
|
||||
bestOfferVolume: string;
|
||||
/**
|
||||
* the aggregated volume being offered at the best static bid price, excluding pegged orders
|
||||
*/
|
||||
bestStaticBidVolume: string;
|
||||
/**
|
||||
* the aggregated volume being offered at the best static offer price, excluding pegged orders.
|
||||
*/
|
||||
bestStaticOfferVolume: string;
|
||||
}
|
||||
|
||||
export interface Market_market_tradableInstrument_instrument_metadata {
|
||||
__typename: "InstrumentMetadata";
|
||||
/**
|
||||
* An arbitrary list of tags to associated to associate to the Instrument (string list)
|
||||
*/
|
||||
tags: string[] | null;
|
||||
}
|
||||
|
||||
export interface Market_market_tradableInstrument_instrument {
|
||||
__typename: "Instrument";
|
||||
/**
|
||||
* Full and fairly descriptive name for the instrument
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* A short non necessarily unique code used to easily describe the instrument (e.g: FX:BTCUSD/DEC18) (string)
|
||||
*/
|
||||
code: string;
|
||||
/**
|
||||
* Metadata for this instrument
|
||||
*/
|
||||
metadata: Market_market_tradableInstrument_instrument_metadata;
|
||||
}
|
||||
|
||||
export interface Market_market_tradableInstrument {
|
||||
__typename: "TradableInstrument";
|
||||
/**
|
||||
* An instance of or reference to a fully specified instrument.
|
||||
*/
|
||||
instrument: Market_market_tradableInstrument_instrument;
|
||||
}
|
||||
|
||||
export interface Market_market_marketTimestamps {
|
||||
__typename: "MarketTimestamps";
|
||||
/**
|
||||
* Time when the market is open and ready to accept trades
|
||||
*/
|
||||
open: string | null;
|
||||
/**
|
||||
* Time when the market is closed
|
||||
*/
|
||||
close: string | null;
|
||||
}
|
||||
|
||||
export interface Market_market_candles {
|
||||
__typename: "Candle";
|
||||
/**
|
||||
* Open price (uint64)
|
||||
*/
|
||||
open: string;
|
||||
/**
|
||||
* Close price (uint64)
|
||||
*/
|
||||
close: string;
|
||||
/**
|
||||
* Volume price (uint64)
|
||||
*/
|
||||
volume: string;
|
||||
}
|
||||
|
||||
export interface Market_market {
|
||||
__typename: "Market";
|
||||
/**
|
||||
@ -17,6 +119,47 @@ export interface Market_market {
|
||||
* Market full name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Current mode of execution of the market
|
||||
*/
|
||||
tradingMode: MarketTradingMode;
|
||||
/**
|
||||
* Current state of the market
|
||||
*/
|
||||
state: MarketState;
|
||||
/**
|
||||
* 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: Market_market_data | null;
|
||||
/**
|
||||
* An instance of or reference to a tradable instrument.
|
||||
*/
|
||||
tradableInstrument: Market_market_tradableInstrument;
|
||||
/**
|
||||
* timestamps for state changes in the market
|
||||
*/
|
||||
marketTimestamps: Market_market_marketTimestamps;
|
||||
/**
|
||||
* Candles on a market, for the 'last' n candles, at 'interval' seconds as specified by params
|
||||
*/
|
||||
candles: (Market_market_candles | null)[] | null;
|
||||
}
|
||||
|
||||
export interface Market {
|
||||
@ -28,4 +171,6 @@ export interface Market {
|
||||
|
||||
export interface MarketVariables {
|
||||
marketId: string;
|
||||
interval: Interval;
|
||||
since: string;
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
import { MarketsContainer } from '@vegaprotocol/market-list';
|
||||
|
||||
const Markets = () => {
|
||||
return <MarketsContainer />;
|
||||
};
|
||||
const Markets = () => <MarketsContainer />;
|
||||
|
||||
Markets.getInitialProps = () => ({
|
||||
page: 'markets',
|
||||
|
@ -13,6 +13,9 @@ import { t } from '@vegaprotocol/react-helpers';
|
||||
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, PriceCellChange } from '@vegaprotocol/ui-toolkit';
|
||||
import type { CandleClose } from '@vegaprotocol/types';
|
||||
|
||||
const TradingViews = {
|
||||
Candles: CandlesChartContainer,
|
||||
@ -31,6 +34,58 @@ interface TradeGridProps {
|
||||
market: Market_market;
|
||||
}
|
||||
|
||||
export const TradeMarketHeader = ({ market }: TradeGridProps) => {
|
||||
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 itemClassName =
|
||||
'font-sans font-normal mb-0 text-dark/80 dark:text-white/80 text-ui-small';
|
||||
const itemValueClassName =
|
||||
'capitalize font-sans tracking-tighter text-black dark:text-white text-ui';
|
||||
return (
|
||||
<header className="w-full p-8">
|
||||
<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 dark:text-vega-yellow text-black text-h5 flex items-center gap-8 px-4 py-0 h-37 hover:bg-vega-yellow dark:hover:bg-white/20"
|
||||
>
|
||||
<span className="break-words text-left">{market.name}</span>
|
||||
<ArrowDown color="yellow" borderX={8} borderTop={12} />
|
||||
</button>
|
||||
|
||||
<div className="flex flex-auto items-start gap-64 overflow-x-scroll whitespace-nowrap w-[400px]">
|
||||
<div className={headerItemClassName}>
|
||||
<span className={itemClassName}>Change (24h)</span>
|
||||
<PriceCellChange
|
||||
candles={candlesClose}
|
||||
decimalPlaces={market.decimalPlaces}
|
||||
/>
|
||||
</div>
|
||||
<div className={headerItemClassName}>
|
||||
<span className={itemClassName}>Volume</span>
|
||||
<span className={itemValueClassName}>
|
||||
{market.data && market.data.indicativeVolume !== '0'
|
||||
? market.data.indicativeVolume
|
||||
: '-'}
|
||||
</span>
|
||||
</div>
|
||||
<div className={headerItemClassName}>
|
||||
<span className={itemClassName}>Trading mode</span>
|
||||
<span className={itemValueClassName}>{market.tradingMode}</span>
|
||||
</div>
|
||||
<div className={headerItemClassName}>
|
||||
<span className={itemClassName}>State</span>
|
||||
<span className={itemValueClassName}>{market.state}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export const TradeGrid = ({ market }: TradeGridProps) => {
|
||||
const wrapperClasses = classNames(
|
||||
'h-full max-h-full',
|
||||
@ -38,50 +93,49 @@ export const TradeGrid = ({ market }: TradeGridProps) => {
|
||||
'bg-black-10 dark:bg-white-10',
|
||||
'text-ui'
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={wrapperClasses}>
|
||||
<header className="col-start-1 col-end-2 row-start-1 row-end-1 p-8">
|
||||
<h1>
|
||||
{t('Market')}: {market.name}
|
||||
</h1>
|
||||
</header>
|
||||
<TradeGridChild className="col-start-1 col-end-2">
|
||||
<GridTabs group="chart">
|
||||
<GridTab id="candles" name={t('Candles')}>
|
||||
<TradingViews.Candles marketId={market.id} />
|
||||
</GridTab>
|
||||
<GridTab id="depth" name={t('Depth')}>
|
||||
<TradingViews.Depth marketId={market.id} />
|
||||
</GridTab>
|
||||
</GridTabs>
|
||||
</TradeGridChild>
|
||||
<TradeGridChild className="row-start-1 row-end-3">
|
||||
<TradingViews.Ticket marketId={market.id} />
|
||||
</TradeGridChild>
|
||||
<TradeGridChild className="row-start-1 row-end-3">
|
||||
<GridTabs group="trade">
|
||||
<GridTab id="trades" name={t('Trades')}>
|
||||
<TradingViews.Trades marketId={market.id} />
|
||||
</GridTab>
|
||||
<GridTab id="orderbook" name={t('Orderbook')}>
|
||||
<TradingViews.Orderbook marketId={market.id} />
|
||||
</GridTab>
|
||||
</GridTabs>
|
||||
</TradeGridChild>
|
||||
<TradeGridChild className="col-span-3">
|
||||
<GridTabs group="portfolio">
|
||||
<GridTab id="orders" name={t('Orders')}>
|
||||
<TradingViews.Orders />
|
||||
</GridTab>
|
||||
<GridTab id="positions" name={t('Positions')}>
|
||||
<TradingViews.Positions />
|
||||
</GridTab>
|
||||
<GridTab id="accounts" name={t('Accounts')}>
|
||||
<TradingViews.Accounts />
|
||||
</GridTab>
|
||||
</GridTabs>
|
||||
</TradeGridChild>
|
||||
</div>
|
||||
<>
|
||||
<TradeMarketHeader market={market} />
|
||||
<div className={wrapperClasses}>
|
||||
<TradeGridChild className="row-start-1 row-end-3">
|
||||
<GridTabs group="chart">
|
||||
<GridTab id="candles" name={t('Candles')}>
|
||||
<TradingViews.Candles marketId={market.id} />
|
||||
</GridTab>
|
||||
<GridTab id="depth" name={t('Depth')}>
|
||||
<TradingViews.Depth marketId={market.id} />
|
||||
</GridTab>
|
||||
</GridTabs>
|
||||
</TradeGridChild>
|
||||
<TradeGridChild className="row-start-1 row-end-3">
|
||||
<TradingViews.Ticket marketId={market.id} />
|
||||
</TradeGridChild>
|
||||
<TradeGridChild className="row-start-1 row-end-3">
|
||||
<GridTabs group="trade">
|
||||
<GridTab id="trades" name={t('Trades')}>
|
||||
<TradingViews.Trades marketId={market.id} />
|
||||
</GridTab>
|
||||
<GridTab id="orderbook" name={t('Orderbook')}>
|
||||
<TradingViews.Orderbook marketId={market.id} />
|
||||
</GridTab>
|
||||
</GridTabs>
|
||||
</TradeGridChild>
|
||||
<TradeGridChild className="col-span-3">
|
||||
<GridTabs group="portfolio">
|
||||
<GridTab id="orders" name={t('Orders')}>
|
||||
<TradingViews.Orders />
|
||||
</GridTab>
|
||||
<GridTab id="positions" name={t('Positions')}>
|
||||
<TradingViews.Positions />
|
||||
</GridTab>
|
||||
<GridTab id="accounts" name={t('Accounts')}>
|
||||
<TradingViews.Accounts />
|
||||
</GridTab>
|
||||
</GridTabs>
|
||||
</TradeGridChild>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -124,11 +178,7 @@ export const TradePanels = ({ market }: TradePanelsProps) => {
|
||||
|
||||
return (
|
||||
<div className="h-full grid grid-rows-[min-content_1fr_min-content]">
|
||||
<header className="p-8">
|
||||
<h1>
|
||||
{t('Market')}: {market.name}
|
||||
</h1>
|
||||
</header>
|
||||
<TradeMarketHeader market={market} />
|
||||
<div className="h-full">
|
||||
<AutoSizer>
|
||||
{({ width, height }) => (
|
||||
|
@ -1,2 +1,3 @@
|
||||
export * from './landing-dialog';
|
||||
export * from './select-market-dialog';
|
||||
export * from './select-market-list';
|
||||
|
@ -0,0 +1,33 @@
|
||||
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();
|
||||
});
|
||||
});
|
@ -0,0 +1,28 @@
|
||||
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.Prompt}
|
||||
open={dialogOpen}
|
||||
onChange={() => setDialogOpen(false)}
|
||||
titleClassNames="font-bold font-sans text-3xl tracking-tight mb-0 pl-8"
|
||||
contentClassNames="w-full md:w-[1120px]"
|
||||
>
|
||||
<div className="h-[200px] w-full">
|
||||
<MarketsContainer />
|
||||
</div>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
@ -3,19 +3,21 @@ import {
|
||||
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 SelectMarketListProps {
|
||||
export interface SelectMarketListDataProps {
|
||||
data: MarketList | undefined;
|
||||
onSelect: (id: string) => void;
|
||||
}
|
||||
|
||||
type CandleClose = Required<string>;
|
||||
|
||||
export const SelectMarketList = ({ data, onSelect }: SelectMarketListProps) => {
|
||||
export const SelectMarketList = ({
|
||||
data,
|
||||
onSelect,
|
||||
}: SelectMarketListDataProps) => {
|
||||
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 =
|
||||
|
@ -13,7 +13,7 @@ import type {
|
||||
import { marketsDataProvider } from './markets-data-provider';
|
||||
|
||||
export const MarketsContainer = () => {
|
||||
const { pathname, push } = useRouter();
|
||||
const { push } = useRouter();
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const update = useCallback(
|
||||
(delta: Markets_markets_data) => {
|
||||
@ -57,7 +57,7 @@ export const MarketsContainer = () => {
|
||||
ref={gridRef}
|
||||
data={data}
|
||||
onRowClicked={(id) =>
|
||||
push(`${pathname}/${id}?portfolio=orders&trade=orderbook`)
|
||||
push(`/markets/${id}?portfolio=orders&trade=orderbook`)
|
||||
}
|
||||
/>
|
||||
</AsyncRenderer>
|
||||
|
@ -4,7 +4,7 @@ import type { Positions_party_positions } from './__generated__/Positions';
|
||||
import { MarketTradingMode } from '@vegaprotocol/types';
|
||||
|
||||
const singleRow: Positions_party_positions = {
|
||||
realisedPNL: '5',
|
||||
realisedPNL: '520000000',
|
||||
openVolume: '100',
|
||||
unrealisedPNL: '895000',
|
||||
averageEntryPrice: '1129935',
|
||||
@ -93,7 +93,7 @@ it('Correct formatting applied', async () => {
|
||||
'+100',
|
||||
'11.29935',
|
||||
'11.38885',
|
||||
'+5',
|
||||
'+5,200.000',
|
||||
];
|
||||
cells.forEach((cell, i) => {
|
||||
expect(cell).toHaveTextContent(expectedValues[i]);
|
||||
|
@ -131,8 +131,10 @@ export const PositionsTable = forwardRef<AgGridReact, PositionsTableProps>(
|
||||
'color-vega-red': ({ value }: { value: string }) =>
|
||||
Number(value) < 0,
|
||||
}}
|
||||
valueFormatter={({ value }: ValueFormatterParams) =>
|
||||
volumePrefix(value)
|
||||
valueFormatter={({ value, data }: ValueFormatterParams) =>
|
||||
volumePrefix(
|
||||
addDecimalsFormatNumber(value, data.market.decimalPlaces, 3)
|
||||
)
|
||||
}
|
||||
cellRenderer="PriceFlashCell"
|
||||
/>
|
||||
|
@ -46,8 +46,8 @@ interface GetDelta<SubscriptionData, Delta> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param subscriptionQuery query that will beused for subscription
|
||||
* @param update function that will be execued on each onNext, it should update data base on delta, it can restart data provider
|
||||
* @param subscriptionQuery query that will be used for subscription
|
||||
* @param update function that will be executed on each onNext, it should update data base on delta, it can restart data provider
|
||||
* @param getData transforms received query data to format that will be stored in data provider
|
||||
* @param getDelta transforms delta data to format that will be stored in data provider
|
||||
* @param fetchPolicy
|
||||
@ -63,7 +63,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
||||
): Subscribe<Data, Delta> {
|
||||
// list of callbacks passed through subscribe call
|
||||
const callbacks: UpdateCallback<Data, Delta>[] = [];
|
||||
// subscription is started before inital query, all deltas that will arrive before inital query response are put on queue
|
||||
// subscription is started before initial query, all deltas that will arrive before initial query response are put on queue
|
||||
const updateQueue: Delta[] = [];
|
||||
|
||||
let variables: OperationVariables | undefined = undefined;
|
||||
@ -88,7 +88,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
||||
callbacks.forEach((callback) => notify(callback, delta));
|
||||
};
|
||||
|
||||
const initalFetch = async () => {
|
||||
const initialFetch = async () => {
|
||||
if (!client) {
|
||||
return;
|
||||
}
|
||||
@ -99,7 +99,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
||||
fetchPolicy,
|
||||
});
|
||||
data = getData(res.data);
|
||||
// if there was some updates received from subscription during initial query loading apply them on just reveived data
|
||||
// if there was some updates received from subscription during initial query loading apply them on just received data
|
||||
if (data && updateQueue && updateQueue.length > 0) {
|
||||
data = produce(data, (draft) => {
|
||||
while (updateQueue.length) {
|
||||
@ -135,7 +135,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
||||
} else {
|
||||
loading = true;
|
||||
error = undefined;
|
||||
initalFetch();
|
||||
initialFetch();
|
||||
}
|
||||
};
|
||||
|
||||
@ -176,7 +176,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
||||
},
|
||||
() => restart()
|
||||
);
|
||||
await initalFetch();
|
||||
await initialFetch();
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
@ -242,8 +242,8 @@ const memoize = <Data, Delta>(
|
||||
|
||||
/**
|
||||
* @param query Query<QueryData>
|
||||
* @param subscriptionQuery Query<SubscriptionData> query that will beused for subscription
|
||||
* @param update Update<Data, Delta> function that will be execued on each onNext, it should update data base on delta, it can restart data provider
|
||||
* @param subscriptionQuery Query<SubscriptionData> query that will be used for subscription
|
||||
* @param update Update<Data, Delta> function that will be executed on each onNext, it should update data base on delta, it can restart data provider
|
||||
* @param getData transforms received query data to format that will be stored in data provider
|
||||
* @param getDelta transforms delta data to format that will be stored in data provider
|
||||
* @param fetchPolicy
|
||||
|
@ -13,7 +13,7 @@ export const PriceCell = React.memo(
|
||||
return <span data-testid="price">-</span>;
|
||||
}
|
||||
return (
|
||||
<span className="font-mono relative" data-testid="price">
|
||||
<span className="font-mono relative text-ui-small" data-testid="price">
|
||||
{valueFormatted}
|
||||
</span>
|
||||
);
|
||||
|
1
libs/types/src/candle.ts
Normal file
1
libs/types/src/candle.ts
Normal file
@ -0,0 +1 @@
|
||||
export type CandleClose = Required<string>;
|
@ -1 +1,2 @@
|
||||
export * from './__generated__/globalTypes';
|
||||
export * from './candle';
|
||||
|
@ -1,13 +1,38 @@
|
||||
export const ArrowUp = () => (
|
||||
export interface ArrowStyleProps {
|
||||
color?: string;
|
||||
borderX?: number;
|
||||
borderTop?: number;
|
||||
borderBottom?: number;
|
||||
}
|
||||
|
||||
export const ArrowUp = ({
|
||||
color = 'green',
|
||||
borderX = 4,
|
||||
borderBottom = 4,
|
||||
}: ArrowStyleProps) => (
|
||||
<span
|
||||
data-testid="arrow-up"
|
||||
className="w-0 h-0 border-x border-x-[4px] border-solid border-x-transparent border-b-[4px] border-b-green-dark dark:border-b-green"
|
||||
style={{
|
||||
borderLeft: `${borderX}px solid transparent`,
|
||||
borderRight: `${borderX}px solid transparent`,
|
||||
borderBottom: `${borderBottom}px solid`,
|
||||
}}
|
||||
className={`w-0 h-0 border-b-${color}-dark dark:border-b-${color}`}
|
||||
></span>
|
||||
);
|
||||
export const ArrowDown = () => (
|
||||
export const ArrowDown = ({
|
||||
color = 'red',
|
||||
borderX = 4,
|
||||
borderTop = 4,
|
||||
}: ArrowStyleProps) => (
|
||||
<span
|
||||
data-testid="arrow-down"
|
||||
className="w-0 h-0 border-x border-x-[4px] border-solid border-x-transparent border-t-[4px] border-t-red-dark dark:border-t-red"
|
||||
style={{
|
||||
borderLeft: `${borderX}px solid transparent`,
|
||||
borderRight: `${borderX}px solid transparent`,
|
||||
borderTop: `${borderTop}px solid`,
|
||||
}}
|
||||
className={`w-0 h-0 border-t-${color}-dark dark:border-t-${color}`}
|
||||
></span>
|
||||
);
|
||||
|
||||
|
@ -215,12 +215,12 @@ export const AnchorButton = forwardRef<HTMLAnchorElement, AnchorButtonProps>(
|
||||
className,
|
||||
prependIconName,
|
||||
appendIconName,
|
||||
...prosp
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
return (
|
||||
<a ref={ref} className={classes(className, variant)} {...prosp}>
|
||||
<a ref={ref} className={classes(className, variant)} {...props}>
|
||||
{getContent(children, prependIconName, appendIconName)}
|
||||
</a>
|
||||
);
|
||||
|
@ -12,6 +12,7 @@ interface DialogProps {
|
||||
title?: string;
|
||||
intent?: Intent;
|
||||
titleClassNames?: string;
|
||||
contentClassNames?: string;
|
||||
}
|
||||
|
||||
export function Dialog({
|
||||
@ -21,13 +22,15 @@ export function Dialog({
|
||||
title,
|
||||
intent,
|
||||
titleClassNames,
|
||||
contentClassNames,
|
||||
}: DialogProps) {
|
||||
const contentClasses = classNames(
|
||||
// Positions the modal in the center of screen
|
||||
'z-20 fixed w-full md:w-[520px] 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)
|
||||
getIntentShadow(intent),
|
||||
contentClassNames
|
||||
);
|
||||
return (
|
||||
<DialogPrimitives.Root open={open} onOpenChange={(x) => onChange(x)}>
|
||||
|
Loading…
Reference in New Issue
Block a user