Feat/526 consolelite design update market list (#635)
* feat: [console-lite] - market list - improve list view * feat: [console-lite] - market list - add column sorting, improve ag-grid styles * feat: [console-lite] - market list - remove unnecessary changes * feat: [console-lite] - market list - fixes for eslint errors * feat: [console-lite] - market list - remove redundant changes * feat: [console-lite] - market list - add resize handler and other small improvements Co-authored-by: maciek <maciek@vegaprotocol.io>
This commit is contained in:
parent
5c57d0e433
commit
51712f4c20
@ -47,6 +47,10 @@ export interface SimpleMarkets_markets_tradableInstrument_instrument_product_set
|
|||||||
|
|
||||||
export interface SimpleMarkets_markets_tradableInstrument_instrument_product {
|
export interface SimpleMarkets_markets_tradableInstrument_instrument_product {
|
||||||
__typename: "Future";
|
__typename: "Future";
|
||||||
|
/**
|
||||||
|
* String representing the quote (e.g. BTCUSD -> USD is quote)
|
||||||
|
*/
|
||||||
|
quoteName: string;
|
||||||
/**
|
/**
|
||||||
* The name of the asset (string)
|
* The name of the asset (string)
|
||||||
*/
|
*/
|
||||||
|
@ -1,19 +1,5 @@
|
|||||||
import { Intent } from '@vegaprotocol/ui-toolkit';
|
|
||||||
import { MarketState } from '@vegaprotocol/types';
|
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
import { themelite as theme } from '@vegaprotocol/tailwindcss-config';
|
||||||
export const MARKET_STATUS: Record<MarketState | '', Intent> = {
|
|
||||||
[MarketState.Active]: Intent.Success,
|
|
||||||
[MarketState.Cancelled]: Intent.Primary,
|
|
||||||
[MarketState.Closed]: Intent.None,
|
|
||||||
[MarketState.Pending]: Intent.Warning,
|
|
||||||
[MarketState.Proposed]: Intent.Warning,
|
|
||||||
[MarketState.Rejected]: Intent.Danger,
|
|
||||||
[MarketState.Settled]: Intent.Primary,
|
|
||||||
[MarketState.Suspended]: Intent.Warning,
|
|
||||||
[MarketState.TradingTerminated]: Intent.Danger,
|
|
||||||
'': Intent.Primary,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const STATES_FILTER = [
|
export const STATES_FILTER = [
|
||||||
{ value: 'all', text: t('All') },
|
{ value: 'all', text: t('All') },
|
||||||
@ -27,3 +13,115 @@ export const STATES_FILTER = [
|
|||||||
{ value: 'Suspended', text: t('Suspended') },
|
{ value: 'Suspended', text: t('Suspended') },
|
||||||
{ value: 'TradingTerminated', text: t('TradingTerminated') },
|
{ value: 'TradingTerminated', text: t('TradingTerminated') },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const agGridLightVariables = `
|
||||||
|
.ag-theme-balham {
|
||||||
|
--ag-row-border-color: ${theme.colors.transparent};
|
||||||
|
--ag-row-hover-color: ${theme.colors.transparent};
|
||||||
|
--ag-font-size: 15px;
|
||||||
|
}
|
||||||
|
.ag-theme-balham .ag-row-hover {
|
||||||
|
--ag-row-border-color: ${theme.colors.black[100]};
|
||||||
|
}
|
||||||
|
.ag-theme-balham [col-id="status"] .ag-header-cell-label,
|
||||||
|
.ag-theme-balham [col-id="asset"] .ag-header-cell-label,
|
||||||
|
.ag-theme-balham [col-id="change"] .ag-header-cell-label{
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.ag-theme-balham .ag-header-row .ag-header-cell:first-child{
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.ag-theme-balham .ag-ltr .ag-header-cell::after, .ag-theme-balham .ag-ltr .ag-header-group-cell::after {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.ag-theme-balham .ag-header-cell::after{
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
.ag-theme-balham .ag-header{
|
||||||
|
border-bottom-width: 0;
|
||||||
|
}
|
||||||
|
.ag-theme-balham .ag-has-focus .ag-row.ag-row-focus .ag-cell-focus {
|
||||||
|
outline: none;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
.ag-theme-balham .ag-header-label-icon .ag-icon{
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.ag-theme-balham .ag-icon::before{
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 12px;
|
||||||
|
position: absolute;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
top: -6px;
|
||||||
|
right: -14px;
|
||||||
|
content: "◾";
|
||||||
|
background: -webkit-linear-gradient(135deg, rgba(0,0,0,0.54) 0%, rgba(0,0,0,0.54) 40%, rgba(0,0,0,0) 40%, rgba(0,0,0,0) 52%, rgba(0,0,0,0.54) 52%, rgba(0,0,0,0.54) 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
.ag-theme-balham .ag-icon-desc::before{
|
||||||
|
background: -webkit-linear-gradient(135deg, #000 0%, #000 40%, rgba(0,0,0,0) 40%, rgba(0,0,0,0) 52%, rgba(0,0,0,0.54) 52%, rgba(0,0,0,0.54) 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
.ag-theme-balham .ag-icon-asc::before{
|
||||||
|
background: -webkit-linear-gradient(135deg, rgba(0,0,0,0.54) 0%, rgba(0,0,0,0.54) 40%, rgba(0,0,0,0) 40%, rgba(0,0,0,0) 52%, #000 52%, #000 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const agGridDarkVariables = `
|
||||||
|
.ag-theme-balham-dark {
|
||||||
|
--ag-row-border-color: ${theme.colors.transparent};
|
||||||
|
--ag-row-hover-color: ${theme.colors.transparent};
|
||||||
|
--ag-font-size: 15px;
|
||||||
|
}
|
||||||
|
.ag-theme-balham-dark .ag-row-hover {
|
||||||
|
--ag-row-border-color: ${theme.colors.white[100]};
|
||||||
|
}
|
||||||
|
.ag-theme-balham-dark [col-id="status"] .ag-header-cell-label,
|
||||||
|
.ag-theme-balham-dark [col-id="asset"] .ag-header-cell-label,
|
||||||
|
.ag-theme-balham-dark [col-id="change"] .ag-header-cell-label{
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.ag-theme-balham-dark .ag-header-row .ag-header-cell:first-child{
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.ag-theme-balham-dark .ag-header-cell::after{
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
.ag-theme-balham-dark .ag-header{
|
||||||
|
border-bottom-width: 0;
|
||||||
|
}
|
||||||
|
.ag-theme-balham-dark .ag-has-focus .ag-row.ag-row-focus .ag-cell-focus {
|
||||||
|
outline: none;
|
||||||
|
border-width: 0;
|
||||||
|
}
|
||||||
|
.ag-theme-balham-dark .ag-header-label-icon .ag-icon{
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.ag-theme-balham-dark .ag-icon::before{
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 12px;
|
||||||
|
position: absolute;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
top: -6px;
|
||||||
|
right: -14px;
|
||||||
|
content: "◾";
|
||||||
|
background: -webkit-linear-gradient(135deg, rgba(245, 245, 245, 0.64) 0%, rgba(245, 245, 245, 0.64) 40%, rgba(0,0,0,0) 40%, rgba(0,0,0,0) 52%, rgba(245, 245, 245, 0.64) 52%, rgba(245, 245, 245, 0.64) 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
.ag-theme-balham-dark .ag-icon-desc::before{
|
||||||
|
background: -webkit-linear-gradient(135deg, #fff 0%, #fff 40%, rgba(0,0,0,0) 40%, rgba(0,0,0,0) 52%, rgba(245, 245, 245, 0.64) 52%, rgba(245, 245, 245, 0.64) 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
.ag-theme-balham-dark .ag-icon-asc::before{
|
||||||
|
background: -webkit-linear-gradient(135deg, rgba(245, 245, 245, 0.64) 0%, rgba(245, 245, 245, 0.64) 40%, rgba(0,0,0,0) 40%, rgba(0,0,0,0) 52%, #fff 52%, #fff 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
@ -36,6 +36,7 @@ export const MARKETS_QUERY = gql`
|
|||||||
product {
|
product {
|
||||||
__typename
|
__typename
|
||||||
... on Future {
|
... on Future {
|
||||||
|
quoteName
|
||||||
settlementAsset {
|
settlementAsset {
|
||||||
symbol
|
symbol
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { act } from 'react-dom/test-utils';
|
import { act } from 'react-dom/test-utils';
|
||||||
import { render, screen, waitFor } from '@testing-library/react';
|
import {
|
||||||
|
render,
|
||||||
|
screen,
|
||||||
|
waitFor,
|
||||||
|
cleanup,
|
||||||
|
getAllByRole,
|
||||||
|
} from '@testing-library/react';
|
||||||
import { MockedProvider } from '@apollo/client/testing';
|
import { MockedProvider } from '@apollo/client/testing';
|
||||||
import type { MockedResponse } from '@apollo/client/testing';
|
import type { MockedResponse } from '@apollo/client/testing';
|
||||||
import { BrowserRouter } from 'react-router-dom';
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
@ -37,6 +43,7 @@ describe('SimpleMarketList', () => {
|
|||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
cleanup();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be properly renderer as empty', async () => {
|
it('should be properly renderer as empty', async () => {
|
||||||
@ -130,8 +137,12 @@ describe('SimpleMarketList', () => {
|
|||||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||||
});
|
});
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('simple-market-list')).toBeInTheDocument();
|
expect(
|
||||||
|
document.querySelector('.ag-center-cols-container')
|
||||||
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
expect(screen.getByTestId('simple-market-list').children).toHaveLength(2);
|
|
||||||
|
const container = document.querySelector('.ag-center-cols-container');
|
||||||
|
expect(getAllByRole(container as HTMLDivElement, 'row')).toHaveLength(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,17 +1,29 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { subDays } from 'date-fns';
|
import { subDays } from 'date-fns';
|
||||||
|
import type { AgGridReact } from 'ag-grid-react';
|
||||||
|
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
|
||||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||||
import { t } from '@vegaprotocol/react-helpers';
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
import { AsyncRenderer, Lozenge, Splash } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
import { ThemeContext } from '@vegaprotocol/react-helpers';
|
||||||
import type { MarketState } from '@vegaprotocol/types';
|
import type { MarketState } from '@vegaprotocol/types';
|
||||||
import SimpleMarketPercentChange from './simple-market-percent-change';
|
|
||||||
import SimpleMarketExpires from './simple-market-expires';
|
|
||||||
import DataProvider from './data-provider';
|
|
||||||
import { MARKET_STATUS } from './constants';
|
|
||||||
import SimpleMarketToolbar from './simple-market-toolbar';
|
|
||||||
import useMarketsFilterData from '../../hooks/use-markets-filter-data';
|
import useMarketsFilterData from '../../hooks/use-markets-filter-data';
|
||||||
|
import useColumnDefinitions from '../../hooks/use-column-definitions';
|
||||||
|
import DataProvider from './data-provider';
|
||||||
|
import * as constants from './constants';
|
||||||
|
import SimpleMarketToolbar from './simple-market-toolbar';
|
||||||
|
import type { SimpleMarkets_markets } from './__generated__/SimpleMarkets';
|
||||||
|
|
||||||
|
export type SimpleMarketsType = SimpleMarkets_markets & {
|
||||||
|
percentChange?: number | '-';
|
||||||
|
};
|
||||||
|
|
||||||
export type RouterParams = Partial<{
|
export type RouterParams = Partial<{
|
||||||
product: string;
|
product: string;
|
||||||
@ -22,8 +34,9 @@ export type RouterParams = Partial<{
|
|||||||
const SimpleMarketList = () => {
|
const SimpleMarketList = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const params = useParams<RouterParams>();
|
const params = useParams<RouterParams>();
|
||||||
|
const theme = useContext(ThemeContext);
|
||||||
const statusesRef = useRef<Record<string, MarketState | ''>>({});
|
const statusesRef = useRef<Record<string, MarketState | ''>>({});
|
||||||
|
const gridRef = useRef<AgGridReact | null>(null);
|
||||||
const variables = useMemo(
|
const variables = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
CandleSince: subDays(Date.now(), 1).toJSON(),
|
CandleSince: subDays(Date.now(), 1).toJSON(),
|
||||||
@ -40,7 +53,14 @@ const SimpleMarketList = () => {
|
|||||||
update,
|
update,
|
||||||
variables
|
variables
|
||||||
);
|
);
|
||||||
const localData = useMarketsFilterData(data || [], params);
|
const localData: Array<SimpleMarketsType> = useMarketsFilterData(
|
||||||
|
data || [],
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOnGridReady = useCallback(() => {
|
||||||
|
gridRef.current?.api.sizeColumnsToFit();
|
||||||
|
}, [gridRef]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const statuses: Record<string, MarketState | ''> = {};
|
const statuses: Record<string, MarketState | ''> = {};
|
||||||
@ -50,6 +70,11 @@ const SimpleMarketList = () => {
|
|||||||
statusesRef.current = statuses;
|
statusesRef.current = statuses;
|
||||||
}, [data, statusesRef]);
|
}, [data, statusesRef]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('resize', handleOnGridReady);
|
||||||
|
return () => window.removeEventListener('resize', handleOnGridReady);
|
||||||
|
}, [handleOnGridReady]);
|
||||||
|
|
||||||
const onClick = useCallback(
|
const onClick = useCallback(
|
||||||
(marketId) => {
|
(marketId) => {
|
||||||
navigate(`/trading/${marketId}`);
|
navigate(`/trading/${marketId}`);
|
||||||
@ -57,62 +82,34 @@ const SimpleMarketList = () => {
|
|||||||
[navigate]
|
[navigate]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { columnDefs, defaultColDef } = useColumnDefinitions({ onClick });
|
||||||
|
|
||||||
|
const getRowId = useCallback(({ data }) => data.id, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="h-full grid grid-rows-[min-content,1fr]">
|
||||||
<SimpleMarketToolbar />
|
<SimpleMarketToolbar />
|
||||||
<AsyncRenderer loading={loading} error={error} data={localData}>
|
<AsyncRenderer loading={loading} error={error} data={localData}>
|
||||||
{localData && localData.length > 0 ? (
|
<AgGrid
|
||||||
<ul
|
className="mb-32 min-h-[300px]"
|
||||||
className="list-none relative pt-8 pb-8"
|
defaultColDef={defaultColDef}
|
||||||
data-testid="simple-market-list"
|
columnDefs={columnDefs}
|
||||||
>
|
rowData={localData}
|
||||||
{localData?.map((market) => (
|
rowHeight={60}
|
||||||
<li
|
customThemeParams={
|
||||||
className="w-full relative flex justify-start items-center no-underline box-border text-left py-8 mb-10"
|
theme === 'dark'
|
||||||
key={market.id}
|
? constants.agGridDarkVariables
|
||||||
>
|
: constants.agGridLightVariables
|
||||||
<div className="w-full grid sm:grid-cols-2">
|
}
|
||||||
<div className="w-full grid sm:auto-rows-auto">
|
onGridReady={handleOnGridReady}
|
||||||
<div className="font-extrabold py-2">{market.name}</div>
|
ref={gridRef}
|
||||||
<SimpleMarketExpires
|
overlayNoRowsTemplate={t('No data to display')}
|
||||||
tags={market.tradableInstrument.instrument.metadata.tags}
|
suppressContextMenu
|
||||||
/>
|
getRowId={getRowId}
|
||||||
<div className="py-2">{`${t('settled in')} ${
|
suppressMovableColumns
|
||||||
market.tradableInstrument.instrument.product
|
/>
|
||||||
.settlementAsset.symbol
|
|
||||||
}`}</div>
|
|
||||||
</div>
|
|
||||||
<div className="w-full grid sm:grid-rows-2">
|
|
||||||
<div>
|
|
||||||
<SimpleMarketPercentChange
|
|
||||||
candles={market.candles}
|
|
||||||
marketId={market.id}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<Lozenge
|
|
||||||
variant={MARKET_STATUS[market.data?.market.state || '']}
|
|
||||||
>
|
|
||||||
{market.data?.market.state}
|
|
||||||
</Lozenge>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="absolute right-16 top-1/2 -translate-y-1/2">
|
|
||||||
<Button
|
|
||||||
onClick={() => onClick(market.id)}
|
|
||||||
variant="inline-link"
|
|
||||||
prependIconName="chevron-right"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
) : (
|
|
||||||
<Splash>{t('No data to display')}</Splash>
|
|
||||||
)}
|
|
||||||
</AsyncRenderer>
|
</AsyncRenderer>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -7,11 +7,16 @@ import type { SimpleMarkets_markets_candles } from './__generated__/SimpleMarket
|
|||||||
|
|
||||||
describe('SimpleMarketPercentChange should parse proper change', () => {
|
describe('SimpleMarketPercentChange should parse proper change', () => {
|
||||||
let candles: (SimpleMarkets_markets_candles | null)[] | null;
|
let candles: (SimpleMarkets_markets_candles | null)[] | null;
|
||||||
|
const setValue = () => undefined;
|
||||||
it('empty array', () => {
|
it('empty array', () => {
|
||||||
candles = [];
|
candles = [];
|
||||||
render(
|
render(
|
||||||
<MockedProvider>
|
<MockedProvider>
|
||||||
<SimpleMarketPercentChange candles={candles} marketId={'1'} />
|
<SimpleMarketPercentChange
|
||||||
|
candles={candles}
|
||||||
|
marketId={'1'}
|
||||||
|
setValue={setValue}
|
||||||
|
/>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
);
|
);
|
||||||
expect(screen.getByText('-')).toBeInTheDocument();
|
expect(screen.getByText('-')).toBeInTheDocument();
|
||||||
@ -20,7 +25,11 @@ describe('SimpleMarketPercentChange should parse proper change', () => {
|
|||||||
candles = null;
|
candles = null;
|
||||||
render(
|
render(
|
||||||
<MockedProvider>
|
<MockedProvider>
|
||||||
<SimpleMarketPercentChange candles={candles} marketId={'1'} />
|
<SimpleMarketPercentChange
|
||||||
|
candles={candles}
|
||||||
|
marketId={'1'}
|
||||||
|
setValue={setValue}
|
||||||
|
/>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
);
|
);
|
||||||
expect(screen.getByText('-')).toBeInTheDocument();
|
expect(screen.getByText('-')).toBeInTheDocument();
|
||||||
@ -33,7 +42,11 @@ describe('SimpleMarketPercentChange should parse proper change', () => {
|
|||||||
];
|
];
|
||||||
render(
|
render(
|
||||||
<MockedProvider>
|
<MockedProvider>
|
||||||
<SimpleMarketPercentChange candles={candles} marketId={'1'} />
|
<SimpleMarketPercentChange
|
||||||
|
candles={candles}
|
||||||
|
marketId={'1'}
|
||||||
|
setValue={setValue}
|
||||||
|
/>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
);
|
);
|
||||||
expect(screen.getByText('100.000%')).toBeInTheDocument();
|
expect(screen.getByText('100.000%')).toBeInTheDocument();
|
||||||
@ -49,7 +62,11 @@ describe('SimpleMarketPercentChange should parse proper change', () => {
|
|||||||
];
|
];
|
||||||
render(
|
render(
|
||||||
<MockedProvider>
|
<MockedProvider>
|
||||||
<SimpleMarketPercentChange candles={candles} marketId={'1'} />
|
<SimpleMarketPercentChange
|
||||||
|
candles={candles}
|
||||||
|
marketId={'1'}
|
||||||
|
setValue={setValue}
|
||||||
|
/>
|
||||||
</MockedProvider>
|
</MockedProvider>
|
||||||
);
|
);
|
||||||
expect(screen.getByText('-50.000%')).toBeInTheDocument();
|
expect(screen.getByText('-50.000%')).toBeInTheDocument();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { InView } from 'react-intersection-observer';
|
import { InView } from 'react-intersection-observer';
|
||||||
import { useSubscription } from '@apollo/client';
|
import { useSubscription } from '@apollo/client';
|
||||||
import { themelite as theme } from '@vegaprotocol/tailwindcss-config';
|
import { themelite as theme } from '@vegaprotocol/tailwindcss-config';
|
||||||
@ -12,6 +12,7 @@ import { CANDLE_SUB } from './data-provider';
|
|||||||
interface Props {
|
interface Props {
|
||||||
candles: (SimpleMarkets_markets_candles | null)[] | null;
|
candles: (SimpleMarkets_markets_candles | null)[] | null;
|
||||||
marketId: string;
|
marketId: string;
|
||||||
|
setValue: (arg: unknown) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getChange = (
|
const getChange = (
|
||||||
@ -51,20 +52,32 @@ const SimpleMarketPercentChangeWrapper = (props: Props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
// @ts-ignore falsy wrong type?
|
// @ts-ignore falsy wrong type?
|
||||||
<InView onChange={setInView}>
|
<InView
|
||||||
|
onChange={setInView}
|
||||||
|
className="flex h-full items-center justify-center"
|
||||||
|
>
|
||||||
{inView && <SimpleMarketPercentChange {...props} />}
|
{inView && <SimpleMarketPercentChange {...props} />}
|
||||||
</InView>
|
</InView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const SimpleMarketPercentChange = ({ candles, marketId }: Props) => {
|
const SimpleMarketPercentChange = ({ candles, marketId, setValue }: Props) => {
|
||||||
const { data: { candles: { close = undefined } = {} } = {} } =
|
const { data: { candles: { close = undefined } = {} } = {} } =
|
||||||
useSubscription<CandleLive, CandleLiveVariables>(CANDLE_SUB, {
|
useSubscription<CandleLive, CandleLiveVariables>(CANDLE_SUB, {
|
||||||
variables: { marketId },
|
variables: { marketId },
|
||||||
});
|
});
|
||||||
const change = getChange(candles, close);
|
const change = getChange(candles, close);
|
||||||
const color = getColor(change);
|
const color = getColor(change);
|
||||||
return <p style={{ color }}>{change}</p>;
|
useEffect(() => {
|
||||||
|
const value = parseFloat(change);
|
||||||
|
setValue(isNaN(value) ? '-' : value);
|
||||||
|
}, [setValue, change]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex text-center" style={{ color }}>
|
||||||
|
{change}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SimpleMarketPercentChangeWrapper;
|
export default SimpleMarketPercentChangeWrapper;
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import type { SimpleMarkets_markets } from './__generated__/SimpleMarkets';
|
||||||
|
import SimpleMarketExpires from './simple-market-expires';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: SimpleMarkets_markets;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MarketNameRenderer = ({ data }: Props) => {
|
||||||
|
return (
|
||||||
|
<div className="flex h-full items-center grid grid-rows-2 grid-flow-col gap-x-8 gap-y-0 grid-cols-[min-content,1fr,1fr]">
|
||||||
|
<div className="w-60 row-span-2 bg-pink rounded-full w-44 h-44 bg-gradient-to-br from-white-60 to--white-80 opacity-30" />
|
||||||
|
<div className="col-span-2 uppercase justify-start text-black dark:text-white text-market self-end">
|
||||||
|
{data.name}{' '}
|
||||||
|
<SimpleMarketExpires
|
||||||
|
tags={data.tradableInstrument.instrument.metadata.tags}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2 ui-small text-deemphasise dark:text-midGrey self-start leading-3">
|
||||||
|
{data.tradableInstrument.instrument.product.quoteName}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MarketNameRenderer;
|
@ -67,7 +67,7 @@ const SimpleMarketToolbar = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-max mb-16 font-alpha">
|
<div className="w-max mb-32 font-alpha">
|
||||||
<ul
|
<ul
|
||||||
ref={slideContRef}
|
ref={slideContRef}
|
||||||
className="grid grid-flow-col auto-cols-min gap-8 relative pb-4 mb-16"
|
className="grid grid-flow-col auto-cols-min gap-8 relative pb-4 mb-16"
|
||||||
@ -162,7 +162,7 @@ const SimpleMarketToolbar = () => {
|
|||||||
</div>
|
</div>
|
||||||
{activeNumber > 0 && (
|
{activeNumber > 0 && (
|
||||||
<ul
|
<ul
|
||||||
className="grid grid-flow-col auto-cols-min md:gap-16 sm:gap-12 pb-4 md:ml-16"
|
className="grid grid-flow-col auto-cols-min md:gap-16 gap-12 pb-4 md:ml-16"
|
||||||
data-testid="market-assets-menu"
|
data-testid="market-assets-menu"
|
||||||
aria-label={t('Asset on the market')}
|
aria-label={t('Asset on the market')}
|
||||||
>
|
>
|
||||||
|
125
apps/simple-trading-app/src/app/hooks/use-column-definitions.tsx
Normal file
125
apps/simple-trading-app/src/app/hooks/use-column-definitions.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import React, { useMemo } from 'react';
|
||||||
|
import { t } from '@vegaprotocol/react-helpers';
|
||||||
|
import type { SimpleMarkets_markets } from '../components/simple-market-list/__generated__/SimpleMarkets';
|
||||||
|
import MarketNameRenderer from '../components/simple-market-list/simple-market-renderer';
|
||||||
|
import SimpleMarketPercentChange from '../components/simple-market-list/simple-market-percent-change';
|
||||||
|
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import type { ValueSetterParams } from 'ag-grid-community';
|
||||||
|
import type { SimpleMarketsType } from '../components/simple-market-list/simple-market-list';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
onClick: (marketId: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useColumnDefinitions = ({ onClick }: Props) => {
|
||||||
|
const columnDefs = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
colId: 'market',
|
||||||
|
headerName: t('Markets'),
|
||||||
|
headerClass: 'uppercase',
|
||||||
|
minWidth: 300,
|
||||||
|
field: 'name',
|
||||||
|
cellRenderer: ({ data }: { data: SimpleMarketsType }) => (
|
||||||
|
<MarketNameRenderer data={data} />
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
colId: 'asset',
|
||||||
|
headerName: t('Settlement asset'),
|
||||||
|
headerClass: 'uppercase',
|
||||||
|
minWidth: 100,
|
||||||
|
cellClass: 'uppercase flex h-full items-center',
|
||||||
|
field: 'tradableInstrument.instrument.product.settlementAsset.symbol',
|
||||||
|
cellRenderer: ({ data }: { data: SimpleMarketsType }) => (
|
||||||
|
<div className="flex h-full items-center justify-center">
|
||||||
|
{data.tradableInstrument.instrument.product.settlementAsset.symbol}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
colId: 'change',
|
||||||
|
headerName: t('24h change'),
|
||||||
|
headerClass: 'uppercase',
|
||||||
|
field: 'percentChange',
|
||||||
|
minWidth: 100,
|
||||||
|
valueSetter: (params: ValueSetterParams): boolean => {
|
||||||
|
const { oldValue, newValue, api, data } = params;
|
||||||
|
if (oldValue !== newValue) {
|
||||||
|
const newdata = { percentChange: newValue, ...data };
|
||||||
|
api.applyTransaction({ update: [newdata] });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
cellRenderer: ({
|
||||||
|
data,
|
||||||
|
setValue,
|
||||||
|
}: {
|
||||||
|
data: SimpleMarketsType;
|
||||||
|
setValue: (arg: unknown) => void;
|
||||||
|
}) => (
|
||||||
|
<SimpleMarketPercentChange
|
||||||
|
candles={data.candles}
|
||||||
|
marketId={data.id}
|
||||||
|
setValue={setValue}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
comparator: (valueA: number | '-', valueB: number | '-') => {
|
||||||
|
if (valueA === valueB) return 0;
|
||||||
|
if (valueA === '-') {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (valueB === '-') {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return valueA > valueB ? 1 : -1;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
colId: 'status',
|
||||||
|
headerName: t('Status'),
|
||||||
|
field: 'data.market.state',
|
||||||
|
headerClass: 'uppercase',
|
||||||
|
minWidth: 100,
|
||||||
|
cellRenderer: ({ data }: { data: SimpleMarkets_markets }) => (
|
||||||
|
<div className="uppercase flex h-full items-center justify-center">
|
||||||
|
<div className="border text-center px-8">
|
||||||
|
{data.data?.market.state}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
colId: 'trade',
|
||||||
|
headerName: '',
|
||||||
|
headerClass: 'uppercase',
|
||||||
|
sortable: false,
|
||||||
|
minWidth: 100,
|
||||||
|
cellRenderer: ({ data }: { data: SimpleMarkets_markets }) => (
|
||||||
|
<div className="h-full flex h-full items-center justify-end">
|
||||||
|
<Button
|
||||||
|
onClick={() => onClick(data.id)}
|
||||||
|
variant="inline-link"
|
||||||
|
appendIconName="arrow-top-right"
|
||||||
|
className="uppercase no-underline hover:no-underline"
|
||||||
|
>
|
||||||
|
{t('Trade')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}, [onClick]);
|
||||||
|
|
||||||
|
const defaultColDef = useMemo(() => {
|
||||||
|
return {
|
||||||
|
sortable: true,
|
||||||
|
unSortIcon: true,
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return { columnDefs, defaultColDef };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useColumnDefinitions;
|
@ -12,9 +12,21 @@ module.exports = {
|
|||||||
mint: '#00F780',
|
mint: '#00F780',
|
||||||
pink: '#FF077F',
|
pink: '#FF077F',
|
||||||
blue: '#2E6DE5',
|
blue: '#2E6DE5',
|
||||||
|
vega: {
|
||||||
|
...theme.colors.vega,
|
||||||
|
'highlight-item': '#000',
|
||||||
|
'highlight-item-dark': '#fff',
|
||||||
|
},
|
||||||
|
'dropdown-bg-dark': theme.colors.black['100'],
|
||||||
},
|
},
|
||||||
fontSize: {
|
fontSize: {
|
||||||
...theme.fontSize,
|
...theme.fontSize,
|
||||||
capMenu: ['15px', { lineHeight: '24px', letterSpacing: '-0.01em' }],
|
capMenu: ['15px', { lineHeight: '24px', letterSpacing: '-0.01em' }],
|
||||||
|
market: ['15px', { lineHeight: '24px' }],
|
||||||
|
},
|
||||||
|
boxShadow: {
|
||||||
|
...theme.boxShadow,
|
||||||
|
'inset-black': '',
|
||||||
|
'inset-white': '',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -55,6 +55,8 @@ const colours = {
|
|||||||
'green-dark': '#008545',
|
'green-dark': '#008545',
|
||||||
red: '#FF261A',
|
red: '#FF261A',
|
||||||
'red-dark': '#EB001B',
|
'red-dark': '#EB001B',
|
||||||
|
'highlight-item': '#FF077F',
|
||||||
|
'highlight-item-dark': '#DFFF0B',
|
||||||
},
|
},
|
||||||
blue: '#1DA2FB',
|
blue: '#1DA2FB',
|
||||||
coral: '#FF6057',
|
coral: '#FF6057',
|
||||||
@ -65,6 +67,8 @@ const colours = {
|
|||||||
selected: '#DFFF0B',
|
selected: '#DFFF0B',
|
||||||
success: '#00F780',
|
success: '#00F780',
|
||||||
'danger-bg': '#9E0025', // for white text
|
'danger-bg': '#9E0025', // for white text
|
||||||
|
'dropdown-bg': '#FFF',
|
||||||
|
'dropdown-bg-dark': shadeOfGray(100 - 60),
|
||||||
};
|
};
|
||||||
|
|
||||||
const boxShadowPosition = {
|
const boxShadowPosition = {
|
||||||
|
@ -27,9 +27,16 @@ const agGridDarkVariables = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const AgGrid = (props: { children: ReactNode }) => (
|
export const AgGrid = ({
|
||||||
|
children,
|
||||||
|
customThemeParams,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
customThemeParams?: string;
|
||||||
|
}) => (
|
||||||
<>
|
<>
|
||||||
<style>{agGridDarkVariables}</style>
|
<style>{agGridDarkVariables}</style>
|
||||||
{props.children}
|
{customThemeParams && <style>{customThemeParams}</style>}
|
||||||
|
{children}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -8,6 +8,7 @@ import 'ag-grid-community/dist/styles/ag-grid.css';
|
|||||||
|
|
||||||
interface GridProps {
|
interface GridProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
customThemeParams: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AgGridLightTheme = dynamic<GridProps>(
|
const AgGridLightTheme = dynamic<GridProps>(
|
||||||
@ -24,11 +25,13 @@ export const AgGridThemed = ({
|
|||||||
style,
|
style,
|
||||||
className,
|
className,
|
||||||
gridRef,
|
gridRef,
|
||||||
|
customThemeParams = '',
|
||||||
...props
|
...props
|
||||||
}: (AgGridReactProps | AgReactUiProps) & {
|
}: (AgGridReactProps | AgReactUiProps) & {
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
className?: string;
|
className?: string;
|
||||||
gridRef?: React.ForwardedRef<AgGridReact>;
|
gridRef?: React.ForwardedRef<AgGridReact>;
|
||||||
|
customThemeParams?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useContext(ThemeContext);
|
const theme = useContext(ThemeContext);
|
||||||
const defaultProps = { rowHeight: 20, headerHeight: 22 };
|
const defaultProps = { rowHeight: 20, headerHeight: 22 };
|
||||||
@ -40,11 +43,11 @@ export const AgGridThemed = ({
|
|||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
{theme === 'dark' ? (
|
{theme === 'dark' ? (
|
||||||
<AgGridDarkTheme>
|
<AgGridDarkTheme customThemeParams={customThemeParams}>
|
||||||
<AgGridReact {...defaultProps} {...props} ref={gridRef} />
|
<AgGridReact {...defaultProps} {...props} ref={gridRef} />
|
||||||
</AgGridDarkTheme>
|
</AgGridDarkTheme>
|
||||||
) : (
|
) : (
|
||||||
<AgGridLightTheme>
|
<AgGridLightTheme customThemeParams={customThemeParams}>
|
||||||
<AgGridReact {...defaultProps} {...props} ref={gridRef} />
|
<AgGridReact {...defaultProps} {...props} ref={gridRef} />
|
||||||
</AgGridLightTheme>
|
</AgGridLightTheme>
|
||||||
)}
|
)}
|
||||||
|
@ -11,6 +11,7 @@ type Props = (AgGridReactProps | AgReactUiProps) & {
|
|||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
className?: string;
|
className?: string;
|
||||||
gridRef?: React.Ref<AgGridReact>;
|
gridRef?: React.Ref<AgGridReact>;
|
||||||
|
customThemeParams?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/69433673/nextjs-reactdomserver-does-not-yet-support-suspense
|
// https://stackoverflow.com/questions/69433673/nextjs-reactdomserver-does-not-yet-support-suspense
|
||||||
|
@ -27,9 +27,16 @@ const agGridLightVariables = `
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const AgGrid = (props: { children: ReactNode }) => (
|
export const AgGrid = ({
|
||||||
|
children,
|
||||||
|
customThemeParams,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
customThemeParams?: string;
|
||||||
|
}) => (
|
||||||
<>
|
<>
|
||||||
<style>{agGridLightVariables}</style>
|
<style>{agGridLightVariables}</style>
|
||||||
{props.children}
|
{customThemeParams && <style>{customThemeParams}</style>}
|
||||||
|
{children}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -13,7 +13,7 @@ const itemClass = classNames(
|
|||||||
'hover:cursor-pointer',
|
'hover:cursor-pointer',
|
||||||
'select-none',
|
'select-none',
|
||||||
'whitespace-nowrap',
|
'whitespace-nowrap',
|
||||||
'focus:bg-vega-pink dark:focus:bg-vega-yellow',
|
'focus:bg-vega-highlight-item dark:focus:bg-vega-highlight-item-dark',
|
||||||
'focus:text-white dark:focus:text-black',
|
'focus:text-white dark:focus:text-black',
|
||||||
'focus:outline-none'
|
'focus:outline-none'
|
||||||
);
|
);
|
||||||
@ -23,7 +23,7 @@ function getItemClasses(inset: boolean, checked?: boolean) {
|
|||||||
itemClass,
|
itemClass,
|
||||||
inset ? 'pl-28' : 'pl-8',
|
inset ? 'pl-28' : 'pl-8',
|
||||||
checked
|
checked
|
||||||
? 'bg-vega-pink dark:bg-vega-yellow text-white dark:text-black'
|
? 'bg-vega-highlight-item dark:bg-vega-highlight-item-dark text-white dark:text-black'
|
||||||
: 'text-black dark:text-white'
|
: 'text-black dark:text-white'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -73,7 +73,7 @@ export const DropdownMenuContent = forwardRef<
|
|||||||
{...contentProps}
|
{...contentProps}
|
||||||
ref={forwardedRef}
|
ref={forwardedRef}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'inline-block box-border border-1 border-black bg-white dark:bg-black-60',
|
'inline-block box-border border-1 border-black bg-dropdown-bg dark:bg-dropdown-bg-dark',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
Loading…
Reference in New Issue
Block a user