From 9ab6337e4214aee66c2682cac20b9a164bbeef94 Mon Sep 17 00:00:00 2001 From: "m.ray" <16125548+MadalinaRaicu@users.noreply.github.com> Date: Mon, 23 May 2022 15:21:54 +0300 Subject: [PATCH] Feat/305 add console v2 first view screen (#424) * [#305] add initial landing dialog on markets page and fix some typos * [#305] market-list utils and generate schema * [#305] initial styling of the landing dialog and add arrows * [#305] routing to markets and add hover and market list tests * [#305] fix z-index on dialog overlay * [#305] default market shoulde be oldest market that is currently trading in continuous mode * [#305] refactor market-list library * [#305] add arrow unit tests * Update libs/market-list/src/lib/components/landing/landing-dialog.tsx Co-authored-by: Dexter Edwards * Update libs/market-list/src/lib/components/landing/select-market-list.tsx Co-authored-by: Dexter Edwards * Update libs/market-list/src/lib/components/landing/select-market-list.tsx Co-authored-by: Dexter Edwards * test: fix failing tests from homepage change * [#305] sort by id after sorting by date * test: increase timeout for failing tests in CI * [#305] destructuring all over the place and some code tweaks, arrows and percentage changes * [#305] update sparkline to show colour * [#305] fix order of market list * [#305] stretchedLink class plus a-tag href for navigation - accessibility updates * [#305] use href only and remove log * [#305] use bignumber.js for price calculations * [#305] change to bg-white/50 on dark mode overlay as asked from UX * [#305] change to bg-white/50 on dark mode overlay as asked from UX * [#305] toLocaleString fix * [#305] toLocaleString fix * [#305] add price-change-cell and use formatNumber * [#305] add extra test for select market list * Update apps/trading/specs/index.spec.tsx Co-authored-by: Dexter Edwards * [#305] use memo, sort by date and id lodash Co-authored-by: Dexter Edwards Co-authored-by: Joe --- README.md | 10 ++ .../src/support/pages/base-page.ts | 7 + .../support/step_definitions/common.step.ts | 4 + apps/trading/components/app-loader/index.tsx | 2 +- .../pages/__generated__/MarketsLanding.ts | 41 +++++ apps/trading/pages/index.page.tsx | 56 ++++--- .../trading/pages/markets/[marketId].page.tsx | 7 +- apps/trading/pages/markets/grid-tabs.tsx | 2 +- apps/trading/specs/index.spec.tsx | 26 ++- libs/market-depth/src/lib/orderbook-data.ts | 2 +- libs/market-list/src/index.ts | 7 +- .../__generated__/MarketDataFields.ts | 0 .../__generated__/MarketDataSub.ts | 0 .../components/__generated__/MarketList.ts | 103 ++++++++++++ .../{ => components}/__generated__/Markets.ts | 0 .../src/lib/components/__generated__/index.ts | 4 + libs/market-list/src/lib/components/index.ts | 3 + .../src/lib/components/landing/index.ts | 2 + .../lib/components/landing/landing-dialog.tsx | 58 +++++++ .../landing/select-market-list.spec.tsx | 120 ++++++++++++++ .../components/landing/select-market-list.tsx | 95 +++++++++++ .../lib/components/markets-container/index.ts | 4 + .../market-list-table.spec.tsx | 5 +- .../markets-container}/market-list-table.tsx | 6 +- .../markets-container}/markets-container.tsx | 2 +- .../markets-data-provider.ts | 7 +- .../markets-container}/summary-cell.tsx | 0 libs/market-list/src/lib/utils/index.ts | 1 + .../src/lib/utils/market-list.utils.spec.tsx | 151 ++++++++++++++++++ .../src/lib/utils/market-list.utils.ts | 31 ++++ libs/react-helpers/src/lib/format/number.ts | 6 + libs/tailwindcss-config/src/theme.js | 5 +- .../src/components/arrows/arrow.spec.tsx | 23 +++ .../src/components/arrows/arrow.tsx | 20 +++ .../ui-toolkit/src/components/arrows/index.ts | 1 + .../src/components/dialog/dialog.stories.tsx | 12 +- .../src/components/dialog/dialog.tsx | 10 +- libs/ui-toolkit/src/components/index.ts | 6 +- .../src/components/price-change/index.ts | 1 + .../price-change/price-change-cell.spec.tsx | 21 +++ .../price-change-cell.stories.tsx | 23 +++ .../price-change/price-change-cell.tsx | 91 +++++++++++ .../components/sparkline/sparkline.spec.tsx | 12 +- .../src/components/sparkline/sparkline.tsx | 17 +- 44 files changed, 941 insertions(+), 63 deletions(-) create mode 100644 apps/trading/pages/__generated__/MarketsLanding.ts rename libs/market-list/src/lib/{ => components}/__generated__/MarketDataFields.ts (100%) rename libs/market-list/src/lib/{ => components}/__generated__/MarketDataSub.ts (100%) create mode 100644 libs/market-list/src/lib/components/__generated__/MarketList.ts rename libs/market-list/src/lib/{ => components}/__generated__/Markets.ts (100%) create mode 100644 libs/market-list/src/lib/components/__generated__/index.ts create mode 100644 libs/market-list/src/lib/components/index.ts create mode 100644 libs/market-list/src/lib/components/landing/index.ts create mode 100644 libs/market-list/src/lib/components/landing/landing-dialog.tsx create mode 100644 libs/market-list/src/lib/components/landing/select-market-list.spec.tsx create mode 100644 libs/market-list/src/lib/components/landing/select-market-list.tsx create mode 100644 libs/market-list/src/lib/components/markets-container/index.ts rename libs/market-list/src/lib/{ => components/markets-container}/market-list-table.spec.tsx (78%) rename libs/market-list/src/lib/{ => components/markets-container}/market-list-table.tsx (98%) rename libs/market-list/src/lib/{ => components/markets-container}/markets-container.tsx (97%) rename libs/market-list/src/lib/{ => components/markets-container}/markets-data-provider.ts (91%) rename libs/market-list/src/lib/{ => components/markets-container}/summary-cell.tsx (100%) create mode 100644 libs/market-list/src/lib/utils/index.ts create mode 100644 libs/market-list/src/lib/utils/market-list.utils.spec.tsx create mode 100644 libs/market-list/src/lib/utils/market-list.utils.ts create mode 100644 libs/ui-toolkit/src/components/arrows/arrow.spec.tsx create mode 100644 libs/ui-toolkit/src/components/arrows/arrow.tsx create mode 100644 libs/ui-toolkit/src/components/arrows/index.ts create mode 100644 libs/ui-toolkit/src/components/price-change/index.ts create mode 100644 libs/ui-toolkit/src/components/price-change/price-change-cell.spec.tsx create mode 100644 libs/ui-toolkit/src/components/price-change/price-change-cell.stories.tsx create mode 100644 libs/ui-toolkit/src/components/price-change/price-change-cell.tsx diff --git a/README.md b/README.md index 72548957d..c2176f202 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,16 @@ Run `nx build my-app` to build the project. The build artifacts will be stored i Run `nx serve my-app` for a dev server. Navigate to the port specified in `app//project.json`. The app will automatically reload if you change any of the source files. +### Using Apollo GraphQL and Generate Types + +In order to generate the schemas for your GraphQL queries, you can run `nx run types:generate`. +If it is the first time you are running the command, make sure you are setting up the environment variable from `apollo.config.js`. + +```bash +export NX_VEGA_URL=https://lb.testnet.vega.xyz/query +yarn nx run types:generate +``` + ### Running tests Run `yarn nx run -e2e:e2e` to execute the e2e tests with [cypress](https://docs.cypress.io/), or `nx affected:e2e` will execute just the end-to-end tests affected by a change. You can use the `--watch` flag to open the cypress tests UI in watch mode, see [cypress executor](https://nx.dev/packages/cypress/executors/cypress) for all CLI flags. diff --git a/apps/trading-e2e/src/support/pages/base-page.ts b/apps/trading-e2e/src/support/pages/base-page.ts index 3d895409b..9b65bf50f 100644 --- a/apps/trading-e2e/src/support/pages/base-page.ts +++ b/apps/trading-e2e/src/support/pages/base-page.ts @@ -1,7 +1,14 @@ export default class BasePage { + closeDialogBtn = 'dialog-close'; porfolioUrl = '/portfolio'; marketsUrl = '/markets'; + closeDialog() { + cy.getByTestId(this.closeDialogBtn, { timeout: 8000 }).click({ + force: true, + }); + } + navigateToPortfolio() { cy.get(`a[href='${this.porfolioUrl}']`).should('be.visible').click(); cy.url().should('include', '/portfolio'); diff --git a/apps/trading-e2e/src/support/step_definitions/common.step.ts b/apps/trading-e2e/src/support/step_definitions/common.step.ts index f200eecad..15005b24c 100644 --- a/apps/trading-e2e/src/support/step_definitions/common.step.ts +++ b/apps/trading-e2e/src/support/step_definitions/common.step.ts @@ -1,5 +1,9 @@ import { Given } from 'cypress-cucumber-preprocessor/steps'; +import BasePage from '../pages/base-page'; + +const basePage = new BasePage(); Given('I am on the homepage', () => { cy.visit('/'); + basePage.closeDialog(); }); diff --git a/apps/trading/components/app-loader/index.tsx b/apps/trading/components/app-loader/index.tsx index 9be590ce4..2b93a86bf 100644 --- a/apps/trading/components/app-loader/index.tsx +++ b/apps/trading/components/app-loader/index.tsx @@ -7,7 +7,7 @@ interface AppLoaderProps { } /** - * Component to handle any app initialization, startup querys and other things + * Component to handle any app initialization, startup queries and other things * that must happen for it can be used */ export function AppLoader({ children }: AppLoaderProps) { diff --git a/apps/trading/pages/__generated__/MarketsLanding.ts b/apps/trading/pages/__generated__/MarketsLanding.ts new file mode 100644 index 000000000..266864354 --- /dev/null +++ b/apps/trading/pages/__generated__/MarketsLanding.ts @@ -0,0 +1,41 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { MarketTradingMode } from "@vegaprotocol/types"; + +// ==================================================== +// GraphQL query operation: MarketsLanding +// ==================================================== + +export interface MarketsLanding_markets_marketTimestamps { + __typename: "MarketTimestamps"; + /** + * Time when the market is open and ready to accept trades + */ + open: string | null; +} + +export interface MarketsLanding_markets { + __typename: "Market"; + /** + * Market ID + */ + id: string; + /** + * Current mode of execution of the market + */ + tradingMode: MarketTradingMode; + /** + * timestamps for state changes in the market + */ + marketTimestamps: MarketsLanding_markets_marketTimestamps; +} + +export interface MarketsLanding { + /** + * One or more instruments that are trading on the VEGA network + */ + markets: MarketsLanding_markets[] | null; +} diff --git a/apps/trading/pages/index.page.tsx b/apps/trading/pages/index.page.tsx index 6c9c811c2..25cfbe67b 100644 --- a/apps/trading/pages/index.page.tsx +++ b/apps/trading/pages/index.page.tsx @@ -1,24 +1,44 @@ -import { Button, Callout, Intent } from '@vegaprotocol/ui-toolkit'; +import { gql, useQuery } from '@apollo/client'; +import { LandingDialog } from '@vegaprotocol/market-list'; +import { MarketTradingMode } from '@vegaprotocol/types'; +import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; +import sortBy from 'lodash/sortBy'; +import MarketPage from './markets/[marketId].page'; +import type { MarketsLanding } from './__generated__/MarketsLanding'; + +const MARKETS_QUERY = gql` + query MarketsLanding { + markets { + id + tradingMode + marketTimestamps { + open + } + } + } +`; + +const marketList = ({ markets }: MarketsLanding) => + sortBy( + markets?.filter( + ({ marketTimestamps, tradingMode }) => + marketTimestamps.open && tradingMode === MarketTradingMode.Continuous + ) || [], + 'marketTimestamps.open', + 'id' + ); export function Index() { + // The default market selected in the platform behind the overlay + // should be the oldest market that is currently trading in continuous mode(i.e. not in auction). + const { data, error, loading } = useQuery(MARKETS_QUERY); return ( -
-
- -
-
With a longer explaination
- -
-
-
-
+ <> + + + + + ); } diff --git a/apps/trading/pages/markets/[marketId].page.tsx b/apps/trading/pages/markets/[marketId].page.tsx index fc2f94c2a..321b3b88d 100644 --- a/apps/trading/pages/markets/[marketId].page.tsx +++ b/apps/trading/pages/markets/[marketId].page.tsx @@ -18,14 +18,13 @@ const MARKET_QUERY = gql` } `; -const MarketPage = () => { +const MarketPage = ({ id }: { id?: string }) => { const { query } = useRouter(); const { w } = useWindowSize(); // Default to first marketId query item if found - const marketId = Array.isArray(query.marketId) - ? query.marketId[0] - : query.marketId; + const marketId = + id || (Array.isArray(query.marketId) ? query.marketId[0] : query.marketId); if (!marketId) { return ( diff --git a/apps/trading/pages/markets/grid-tabs.tsx b/apps/trading/pages/markets/grid-tabs.tsx index de7d54108..d92b84f4a 100644 --- a/apps/trading/pages/markets/grid-tabs.tsx +++ b/apps/trading/pages/markets/grid-tabs.tsx @@ -23,7 +23,7 @@ export const GridTabs = ({ children, group }: GridTabsProps) => { }); // Update the query string in the url when the active tab changes - // uses group property as the query stirng key + // uses group property as the query string key useEffect(() => { const [url, queryString] = asPath.split('?'); const searchParams = new URLSearchParams(queryString); diff --git a/apps/trading/specs/index.spec.tsx b/apps/trading/specs/index.spec.tsx index c287a13df..1b03d8900 100644 --- a/apps/trading/specs/index.spec.tsx +++ b/apps/trading/specs/index.spec.tsx @@ -1,6 +1,7 @@ import React from 'react'; -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; import Index from '../pages/index.page'; +import { MockedProvider } from '@apollo/react-testing'; jest.mock('@vegaprotocol/ui-toolkit', () => { const original = jest.requireActual('@vegaprotocol/ui-toolkit'); @@ -10,9 +11,26 @@ jest.mock('@vegaprotocol/ui-toolkit', () => { }; }); +jest.mock('next/router', () => ({ + useRouter() { + return { + route: '/', + pathname: '', + query: '', + asPath: '', + }; + }, +})); + describe('Index', () => { - it('should render successfully', () => { - render(); - expect(true).toBeTruthy(); + it('should render successfully', async () => { + act(() => { + const { baseElement } = render( + + + + ); + expect(baseElement).toBeTruthy(); + }); }); }); diff --git a/libs/market-depth/src/lib/orderbook-data.ts b/libs/market-depth/src/lib/orderbook-data.ts index f3e3172b7..edb117510 100644 --- a/libs/market-depth/src/lib/orderbook-data.ts +++ b/libs/market-depth/src/lib/orderbook-data.ts @@ -143,7 +143,7 @@ export const compactData = ( ), [] ); - // order by price, it's safe to cast to number price diff sould not exceed Number.MAX_SAFE_INTEGER + // order by price, it's safe to cast to number price diff should not exceed Number.MAX_SAFE_INTEGER orderbookData.sort((a, b) => Number(BigInt(b.price) - BigInt(a.price))); // count cumulative volumes if (orderbookData.length > 1) { diff --git a/libs/market-list/src/index.ts b/libs/market-list/src/index.ts index 4b95f4cf9..da443ffb8 100644 --- a/libs/market-list/src/index.ts +++ b/libs/market-list/src/index.ts @@ -1,5 +1,2 @@ -export * from './lib/market-list-table'; -export * from './lib/markets-container'; -export * from './lib/__generated__/Markets'; -export * from './lib/__generated__/MarketDataFields'; -export * from './lib/__generated__/MarketDataSub'; +export * from './lib/components'; +export * from './lib/utils'; diff --git a/libs/market-list/src/lib/__generated__/MarketDataFields.ts b/libs/market-list/src/lib/components/__generated__/MarketDataFields.ts similarity index 100% rename from libs/market-list/src/lib/__generated__/MarketDataFields.ts rename to libs/market-list/src/lib/components/__generated__/MarketDataFields.ts diff --git a/libs/market-list/src/lib/__generated__/MarketDataSub.ts b/libs/market-list/src/lib/components/__generated__/MarketDataSub.ts similarity index 100% rename from libs/market-list/src/lib/__generated__/MarketDataSub.ts rename to libs/market-list/src/lib/components/__generated__/MarketDataSub.ts diff --git a/libs/market-list/src/lib/components/__generated__/MarketList.ts b/libs/market-list/src/lib/components/__generated__/MarketList.ts new file mode 100644 index 000000000..41843ac97 --- /dev/null +++ b/libs/market-list/src/lib/components/__generated__/MarketList.ts @@ -0,0 +1,103 @@ +/* tslint:disable */ +/* eslint-disable */ +// @generated +// This file was automatically generated and should not be edited. + +import { Interval } from "@vegaprotocol/types"; + +// ==================================================== +// GraphQL query operation: MarketList +// ==================================================== + +export interface MarketList_markets_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; +} + +export interface MarketList_markets_tradableInstrument { + __typename: "TradableInstrument"; + /** + * An instance of or reference to a fully specified instrument. + */ + instrument: MarketList_markets_tradableInstrument_instrument; +} + +export interface MarketList_markets_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 MarketList_markets_candles { + __typename: "Candle"; + /** + * Open price (uint64) + */ + open: string; + /** + * Close price (uint64) + */ + close: string; +} + +export interface MarketList_markets { + __typename: "Market"; + /** + * Market ID + */ + id: 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; + /** + * An instance of or reference to a tradable instrument. + */ + tradableInstrument: MarketList_markets_tradableInstrument; + /** + * timestamps for state changes in the market + */ + marketTimestamps: MarketList_markets_marketTimestamps; + /** + * Candles on a market, for the 'last' n candles, at 'interval' seconds as specified by params + */ + candles: (MarketList_markets_candles | null)[] | null; +} + +export interface MarketList { + /** + * One or more instruments that are trading on the VEGA network + */ + markets: MarketList_markets[] | null; +} + +export interface MarketListVariables { + interval: Interval; + since: string; +} diff --git a/libs/market-list/src/lib/__generated__/Markets.ts b/libs/market-list/src/lib/components/__generated__/Markets.ts similarity index 100% rename from libs/market-list/src/lib/__generated__/Markets.ts rename to libs/market-list/src/lib/components/__generated__/Markets.ts diff --git a/libs/market-list/src/lib/components/__generated__/index.ts b/libs/market-list/src/lib/components/__generated__/index.ts new file mode 100644 index 000000000..807f552b1 --- /dev/null +++ b/libs/market-list/src/lib/components/__generated__/index.ts @@ -0,0 +1,4 @@ +export * from './MarketDataFields'; +export * from './MarketDataSub'; +export * from './MarketList'; +export * from './Markets'; diff --git a/libs/market-list/src/lib/components/index.ts b/libs/market-list/src/lib/components/index.ts new file mode 100644 index 000000000..4fe60a8ed --- /dev/null +++ b/libs/market-list/src/lib/components/index.ts @@ -0,0 +1,3 @@ +export * from './__generated__'; +export * from './landing'; +export * from './markets-container'; diff --git a/libs/market-list/src/lib/components/landing/index.ts b/libs/market-list/src/lib/components/landing/index.ts new file mode 100644 index 000000000..1acd77dfa --- /dev/null +++ b/libs/market-list/src/lib/components/landing/index.ts @@ -0,0 +1,2 @@ +export * from './landing-dialog'; +export * from './select-market-list'; diff --git a/libs/market-list/src/lib/components/landing/landing-dialog.tsx b/libs/market-list/src/lib/components/landing/landing-dialog.tsx new file mode 100644 index 000000000..df79a8cee --- /dev/null +++ b/libs/market-list/src/lib/components/landing/landing-dialog.tsx @@ -0,0 +1,58 @@ +import { gql, useQuery } from '@apollo/client'; +import { Interval } from '@vegaprotocol/types'; +import { AsyncRenderer, Dialog, Intent } from '@vegaprotocol/ui-toolkit'; +import { useState } from 'react'; + +import type { MarketList } from '../__generated__/MarketList'; +import { SelectMarketList } from './select-market-list'; + +const MARKET_LIST_QUERY = gql` + query MarketList($interval: Interval!, $since: String!) { + markets { + id + decimalPlaces + tradableInstrument { + instrument { + name + code + } + } + marketTimestamps { + open + close + } + candles(interval: $interval, since: $since) { + open + close + } + } + } +`; + +export const LandingDialog = () => { + const [open, setOpen] = useState(true); + 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(MARKET_LIST_QUERY, { + variables: { interval: Interval.I1H, since: yTimestamp }, + }); + + return ( + + { + + + + } + + ); +}; diff --git a/libs/market-list/src/lib/components/landing/select-market-list.spec.tsx b/libs/market-list/src/lib/components/landing/select-market-list.spec.tsx new file mode 100644 index 000000000..f20ab7105 --- /dev/null +++ b/libs/market-list/src/lib/components/landing/select-market-list.spec.tsx @@ -0,0 +1,120 @@ +import { render, screen } from '@testing-library/react'; +import type { MarketList } from '../__generated__/MarketList'; +import { SelectMarketList } from './select-market-list'; + +describe('SelectMarketList', () => { + it('should render', () => { + render(); + expect(screen.getByText('AAPL.MF21')).toBeTruthy(); + expect(screen.getByText('-3.14%')).toBeTruthy(); + expect(screen.getByText('141.75')).toBeTruthy(); + expect(screen.getByText('Or view full market list')).toBeTruthy(); + }); +}); + +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', + }, + ], + }, + ], + }, +}; diff --git a/libs/market-list/src/lib/components/landing/select-market-list.tsx b/libs/market-list/src/lib/components/landing/select-market-list.tsx new file mode 100644 index 000000000..c771ea6b3 --- /dev/null +++ b/libs/market-list/src/lib/components/landing/select-market-list.tsx @@ -0,0 +1,95 @@ +import { + addDecimalsFormatNumber, + PriceCell, +} from '@vegaprotocol/react-helpers'; +import { PriceCellChange, Sparkline } from '@vegaprotocol/ui-toolkit'; +import { mapDataToMarketList } from '../../utils'; +import type { MarketList } from '../__generated__/MarketList'; + +export interface SelectMarketListProps { + data: MarketList | undefined; +} + +type CandleClose = Required; + +export const SelectMarketList = ({ data }: SelectMarketListProps) => { + 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'; + const stretchedLink = `after:content-[''] after:inset-0 after:z-[1] after:absolute after:box-border`; + return ( +
+ + + + + + + + + + + {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 ( + + + + + + + ); + })} + +
MarketLast priceChange (24h)
+ + {marketName} + + + {lastPrice && ( + + )} + + + + {candles && ( + Number(c))} + /> + )} +
+ + + {'Or view full market list'} + +
+ ); +}; diff --git a/libs/market-list/src/lib/components/markets-container/index.ts b/libs/market-list/src/lib/components/markets-container/index.ts new file mode 100644 index 000000000..8773477ea --- /dev/null +++ b/libs/market-list/src/lib/components/markets-container/index.ts @@ -0,0 +1,4 @@ +export * from './market-list-table'; +export * from './markets-container'; +export * from './markets-data-provider'; +export * from './summary-cell'; diff --git a/libs/market-list/src/lib/market-list-table.spec.tsx b/libs/market-list/src/lib/components/markets-container/market-list-table.spec.tsx similarity index 78% rename from libs/market-list/src/lib/market-list-table.spec.tsx rename to libs/market-list/src/lib/components/markets-container/market-list-table.spec.tsx index 19d64ed6c..733161bef 100644 --- a/libs/market-list/src/lib/market-list-table.spec.tsx +++ b/libs/market-list/src/lib/components/markets-container/market-list-table.spec.tsx @@ -8,7 +8,10 @@ describe('MarketListTable', () => { await act(async () => { const { baseElement } = render( - + null)} + /> ); expect(baseElement).toBeTruthy(); diff --git a/libs/market-list/src/lib/market-list-table.tsx b/libs/market-list/src/lib/components/markets-container/market-list-table.tsx similarity index 98% rename from libs/market-list/src/lib/market-list-table.tsx rename to libs/market-list/src/lib/components/markets-container/market-list-table.tsx index 8c4ba23fc..b4956ce01 100644 --- a/libs/market-list/src/lib/market-list-table.tsx +++ b/libs/market-list/src/lib/components/markets-container/market-list-table.tsx @@ -6,12 +6,12 @@ import { t, } from '@vegaprotocol/react-helpers'; import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit'; +import { AgGridColumn } from 'ag-grid-react'; +import type { AgGridReact } from 'ag-grid-react'; import type { Markets_markets, Markets_markets_data_market, -} from './__generated__/Markets'; -import { AgGridColumn } from 'ag-grid-react'; -import type { AgGridReact } from 'ag-grid-react'; +} from '../__generated__/Markets'; interface MarketListTableProps { data: Markets_markets[] | null; diff --git a/libs/market-list/src/lib/markets-container.tsx b/libs/market-list/src/lib/components/markets-container/markets-container.tsx similarity index 97% rename from libs/market-list/src/lib/markets-container.tsx rename to libs/market-list/src/lib/components/markets-container/markets-container.tsx index 70431f3b4..59064ade3 100644 --- a/libs/market-list/src/lib/markets-container.tsx +++ b/libs/market-list/src/lib/components/markets-container/markets-container.tsx @@ -9,7 +9,7 @@ import type { AgGridReact } from 'ag-grid-react'; import type { Markets_markets, Markets_markets_data, -} from './__generated__/Markets'; +} from '../../components/__generated__/Markets'; import { marketsDataProvider } from './markets-data-provider'; export const MarketsContainer = () => { diff --git a/libs/market-list/src/lib/markets-data-provider.ts b/libs/market-list/src/lib/components/markets-container/markets-data-provider.ts similarity index 91% rename from libs/market-list/src/lib/markets-data-provider.ts rename to libs/market-list/src/lib/components/markets-container/markets-data-provider.ts index 4d1e91210..ff94d8db6 100644 --- a/libs/market-list/src/lib/markets-data-provider.ts +++ b/libs/market-list/src/lib/components/markets-container/markets-data-provider.ts @@ -1,11 +1,14 @@ import { gql } from '@apollo/client'; -import type { Markets, Markets_markets } from './__generated__/Markets'; +import type { + Markets, + Markets_markets, +} from '../../components/__generated__/Markets'; import { makeDataProvider } from '@vegaprotocol/react-helpers'; import type { MarketDataSub, MarketDataSub_marketData, -} from './__generated__/MarketDataSub'; +} from '../../components/__generated__/MarketDataSub'; const MARKET_DATA_FRAGMENT = gql` fragment MarketDataFields on MarketData { diff --git a/libs/market-list/src/lib/summary-cell.tsx b/libs/market-list/src/lib/components/markets-container/summary-cell.tsx similarity index 100% rename from libs/market-list/src/lib/summary-cell.tsx rename to libs/market-list/src/lib/components/markets-container/summary-cell.tsx diff --git a/libs/market-list/src/lib/utils/index.ts b/libs/market-list/src/lib/utils/index.ts new file mode 100644 index 000000000..f08cd3d08 --- /dev/null +++ b/libs/market-list/src/lib/utils/index.ts @@ -0,0 +1 @@ +export * from './market-list.utils'; diff --git a/libs/market-list/src/lib/utils/market-list.utils.spec.tsx b/libs/market-list/src/lib/utils/market-list.utils.spec.tsx new file mode 100644 index 000000000..02c314b6a --- /dev/null +++ b/libs/market-list/src/lib/utils/market-list.utils.spec.tsx @@ -0,0 +1,151 @@ +import type { MarketList } from '../components/__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', + }, + ], + }, + ], + }, +}; diff --git a/libs/market-list/src/lib/utils/market-list.utils.ts b/libs/market-list/src/lib/utils/market-list.utils.ts new file mode 100644 index 000000000..08b660960 --- /dev/null +++ b/libs/market-list/src/lib/utils/market-list.utils.ts @@ -0,0 +1,31 @@ +import sortBy from 'lodash/sortBy'; +import type { + MarketList, + MarketList_markets, +} from '../components/__generated__/MarketList'; + +export const lastPrice = ({ candles }: MarketList_markets) => + candles && candles.length > 0 + ? candles && candles[candles?.length - 1]?.close + : undefined; + +export const mapDataToMarketList = ({ markets }: MarketList) => + sortBy( + markets?.map((m) => { + return { + id: m.id, + decimalPlaces: m.decimalPlaces, + marketName: m.tradableInstrument.instrument?.code, + lastPrice: lastPrice(m), + 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, + }; + }) || [], + 'open', + 'id' + ); diff --git a/libs/react-helpers/src/lib/format/number.ts b/libs/react-helpers/src/lib/format/number.ts index 21d0842e3..cd87e62de 100644 --- a/libs/react-helpers/src/lib/format/number.ts +++ b/libs/react-helpers/src/lib/format/number.ts @@ -40,3 +40,9 @@ export const addDecimalsFormatNumber = ( return formatNumber(x, formatDecimals); }; + +export const formatNumberPercentage = (value: BigNumber, decimals?: number) => { + const decimalPlaces = + typeof decimals === 'undefined' ? Math.max(value.dp(), 2) : decimals; + return `${value.dp(decimalPlaces).toFormat(decimalPlaces)}%`; +}; diff --git a/libs/tailwindcss-config/src/theme.js b/libs/tailwindcss-config/src/theme.js index 68cf36368..7c3981142 100644 --- a/libs/tailwindcss-config/src/theme.js +++ b/libs/tailwindcss-config/src/theme.js @@ -12,8 +12,6 @@ module.exports = { colors: { transparent: 'transparent', current: 'currentColor', - bullish: '#26FF8A', - bearish: '#ED1515', vega: { yellow: '#EDFF22', pink: '#FF2D5E', @@ -23,11 +21,12 @@ module.exports = { DEFAULT: '#ED1515', transparent: 'rgba(255, 38, 65, 0.3)', vega: '#FF261A', + dark: '#EB001B', }, green: { DEFAULT: '#26FF8A', transparent: 'rgba(38, 255, 138, 0.3)', - dark: '#246340', + dark: '#008545', vega: '#00F780', }, text: '#C7C7C7', diff --git a/libs/ui-toolkit/src/components/arrows/arrow.spec.tsx b/libs/ui-toolkit/src/components/arrows/arrow.spec.tsx new file mode 100644 index 000000000..117f8faa1 --- /dev/null +++ b/libs/ui-toolkit/src/components/arrows/arrow.spec.tsx @@ -0,0 +1,23 @@ +import { render, screen } from '@testing-library/react'; + +import { Arrow } from './arrow'; + +describe('Arrow', () => { + it('should render successfully for positive values', () => { + render(); + expect(screen.queryByTestId('arrow-up')).toBeTruthy(); + expect(screen.queryByTestId('arrow-down')).toBeFalsy(); + }); + + it('should render successfully for negative values', () => { + render(); + expect(screen.queryByTestId('arrow-down')).toBeTruthy(); + expect(screen.queryByTestId('arrow-up')).toBeFalsy(); + }); + + it('should not render successfully for zero values', () => { + render(); + expect(screen.queryByTestId('arrow-down')).toBeNull(); + expect(screen.queryByTestId('arrow-up')).toBeNull(); + }); +}); diff --git a/libs/ui-toolkit/src/components/arrows/arrow.tsx b/libs/ui-toolkit/src/components/arrows/arrow.tsx new file mode 100644 index 000000000..744ec7ef2 --- /dev/null +++ b/libs/ui-toolkit/src/components/arrows/arrow.tsx @@ -0,0 +1,20 @@ +export const ArrowUp = () => ( + +); +export const ArrowDown = () => ( + +); + +// Arrow +export interface ArrowProps { + value: number | bigint; +} + +export const Arrow = ({ value }: ArrowProps) => + value === 0 ? null : value > 0 ? : ; diff --git a/libs/ui-toolkit/src/components/arrows/index.ts b/libs/ui-toolkit/src/components/arrows/index.ts new file mode 100644 index 000000000..2c9555f11 --- /dev/null +++ b/libs/ui-toolkit/src/components/arrows/index.ts @@ -0,0 +1 @@ +export * from './arrow'; diff --git a/libs/ui-toolkit/src/components/dialog/dialog.stories.tsx b/libs/ui-toolkit/src/components/dialog/dialog.stories.tsx index 4d3752813..2a288c534 100644 --- a/libs/ui-toolkit/src/components/dialog/dialog.stories.tsx +++ b/libs/ui-toolkit/src/components/dialog/dialog.stories.tsx @@ -24,7 +24,6 @@ export const Default = Template.bind({}); Default.args = { open: false, title: 'Title', - setOpen: () => undefined, children:

Some content

, }; @@ -32,7 +31,6 @@ export const Danger = Template.bind({}); Danger.args = { open: false, title: 'Danger', - setOpen: () => undefined, children:

Some content

, intent: Intent.Danger, }; @@ -41,7 +39,6 @@ export const Success = Template.bind({}); Success.args = { open: false, title: 'Success', - setOpen: () => undefined, children:

Some content

, intent: Intent.Success, }; @@ -50,7 +47,14 @@ export const Warning = Template.bind({}); Warning.args = { open: false, title: 'Warning', - setOpen: () => undefined, children:

Some content

, intent: Intent.Warning, }; + +export const Modal = Template.bind({}); +Modal.args = { + open: false, + title: 'Modal (Prompt)', + children:

Some content

, + intent: Intent.Prompt, +}; diff --git a/libs/ui-toolkit/src/components/dialog/dialog.tsx b/libs/ui-toolkit/src/components/dialog/dialog.tsx index d09057d92..f838f9136 100644 --- a/libs/ui-toolkit/src/components/dialog/dialog.tsx +++ b/libs/ui-toolkit/src/components/dialog/dialog.tsx @@ -11,6 +11,7 @@ interface DialogProps { onChange: (isOpen: boolean) => void; title?: string; intent?: Intent; + titleClassNames?: string; } export function Dialog({ @@ -19,10 +20,11 @@ export function Dialog({ onChange, title, intent, + titleClassNames, }: DialogProps) { const contentClasses = classNames( // Positions the modal in the center of screen - 'fixed w-[520px] px-28 py-24 top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]', + '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) @@ -30,7 +32,7 @@ export function Dialog({ return ( onChange(x)}> - + {title && ( -

+

{title}

)} diff --git a/libs/ui-toolkit/src/components/index.ts b/libs/ui-toolkit/src/components/index.ts index d149f6a32..ff192e72b 100644 --- a/libs/ui-toolkit/src/components/index.ts +++ b/libs/ui-toolkit/src/components/index.ts @@ -1,4 +1,5 @@ export * from './ag-grid'; +export * from './arrows'; export * from './async-renderer'; export * from './button'; export * from './callout'; @@ -15,13 +16,14 @@ export * from './input-error'; export * from './key-value-table'; export * from './loader'; export * from './lozenge'; +export * from './price-change'; export * from './radio-group'; export * from './select'; +export * from './sparkline'; export * from './splash'; +export * from './syntax-highlighter'; export * from './text-area'; export * from './theme-switcher'; export * from './toggle'; export * from './tooltip'; export * from './vega-logo'; -export * from './syntax-highlighter'; -export * from './sparkline'; diff --git a/libs/ui-toolkit/src/components/price-change/index.ts b/libs/ui-toolkit/src/components/price-change/index.ts new file mode 100644 index 000000000..667cc5c33 --- /dev/null +++ b/libs/ui-toolkit/src/components/price-change/index.ts @@ -0,0 +1 @@ +export * from './price-change-cell'; diff --git a/libs/ui-toolkit/src/components/price-change/price-change-cell.spec.tsx b/libs/ui-toolkit/src/components/price-change/price-change-cell.spec.tsx new file mode 100644 index 000000000..282387ecf --- /dev/null +++ b/libs/ui-toolkit/src/components/price-change/price-change-cell.spec.tsx @@ -0,0 +1,21 @@ +import { render, screen } from '@testing-library/react'; +import { PriceCellChange } from '..'; + +describe('PriceChangeCell', () => { + it('renders correctly and calculates the price change', () => { + render( + + ); + expect(screen.getByText('-48.51%')).toBeInTheDocument(); + expect(screen.getByText('-22.100')).toBeInTheDocument(); + }); + + it('renders correctly and calculates the price change without decimals', () => { + render(); + expect(screen.getByText('-48.51%')).toBeInTheDocument(); + expect(screen.getByText('-22,100.000')).toBeInTheDocument(); + }); +}); diff --git a/libs/ui-toolkit/src/components/price-change/price-change-cell.stories.tsx b/libs/ui-toolkit/src/components/price-change/price-change-cell.stories.tsx new file mode 100644 index 000000000..c10695ea7 --- /dev/null +++ b/libs/ui-toolkit/src/components/price-change/price-change-cell.stories.tsx @@ -0,0 +1,23 @@ +import type { Story, Meta } from '@storybook/react'; +import { PriceCellChange } from './price-change-cell'; + +export default { + component: PriceCellChange, + title: 'PriceCellChange', +} as Meta; + +const Template: Story = (args) => ( + +); + +export const Increased = Template.bind({}); +Increased.args = { + candles: ['4564', '5674', '6784'], + decimalPlaces: 3, +}; + +export const Decreased = Template.bind({}); +Decreased.args = { + candles: ['6784', '4564', '5674'], + decimalPlaces: 3, +}; diff --git a/libs/ui-toolkit/src/components/price-change/price-change-cell.tsx b/libs/ui-toolkit/src/components/price-change/price-change-cell.tsx new file mode 100644 index 000000000..91aa77027 --- /dev/null +++ b/libs/ui-toolkit/src/components/price-change/price-change-cell.tsx @@ -0,0 +1,91 @@ +import { + addDecimalsFormatNumber, + formatNumberPercentage, + PriceCell, +} from '@vegaprotocol/react-helpers'; +import BigNumber from 'bignumber.js'; +import React from 'react'; + +import { Arrow } from '../arrows/arrow'; + +export interface PriceChangeCellProps { + /** either candle `close`or `open` values to be filtered and used here in order to calculate the price change */ + candles: string[]; + decimalPlaces?: number; +} + +export const priceChangePercentage = (candles: string[]) => { + const change = priceChange(candles); + if (change && candles && candles.length > 0) { + const yesterdayLastPrice = candles[0] && BigInt(candles[0]); + if (yesterdayLastPrice) { + return new BigNumber(change.toString()) + .dividedBy(new BigNumber(yesterdayLastPrice.toString())) + .multipliedBy(100) + .toNumber(); + } + return 0; + } + return 0; +}; + +export const priceChange = (candles: string[]) => { + return candles && + candles[candles.length - 1] !== undefined && + candles[0] !== undefined + ? BigInt(candles[candles.length - 1] ?? 0) - BigInt(candles[0] ?? 0) + : 0; +}; + +const priceChangeClassNames = (value: number | bigint) => + value === 0 + ? 'text-black dark:text-white' + : value > 0 + ? `text-green-dark dark:text-green-vega ` + : `text-red-dark dark:text-red-vega`; + +export const PriceCellChange = React.memo( + ({ candles, decimalPlaces }: PriceChangeCellProps) => { + const change = priceChange(candles); + const changePercentage = priceChangePercentage(candles); + return ( + + + + + { + + } +   + + + ( + { + + } + ) + + + + ); + } +); + +PriceCellChange.displayName = 'PriceCellChange'; diff --git a/libs/ui-toolkit/src/components/sparkline/sparkline.spec.tsx b/libs/ui-toolkit/src/components/sparkline/sparkline.spec.tsx index a3ca2b004..77386c15e 100644 --- a/libs/ui-toolkit/src/components/sparkline/sparkline.spec.tsx +++ b/libs/ui-toolkit/src/components/sparkline/sparkline.spec.tsx @@ -7,6 +7,7 @@ const props = { 1, 2, 3, 4, 5, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 6, 7, 8, 9, 10, 11, 12, ], + muted: true, }; it('Renders an svg with a single path', () => { @@ -17,7 +18,7 @@ it('Renders an svg with a single path', () => { expect(path).toBeInTheDocument(); expect(path).toHaveAttribute('d', expect.any(String)); expect(path).toHaveAttribute('stroke', expect.any(String)); - expect(path).toHaveAttribute('stroke-width', '2'); + expect(path).toHaveAttribute('stroke-width', '1'); expect(path).toHaveAttribute('fill', 'transparent'); }); @@ -34,16 +35,21 @@ it('Renders a red line if the last value is less than the first', () => { render(); const paths = screen.getAllByTestId('sparkline-path'); const path = paths[0]; - expect(path).toHaveClass('stroke-bearish'); + expect(path).toHaveClass( + '[vector-effect:non-scaling-stroke] stroke-red-dark dark:stroke-red' + ); }); it('Renders a green line if the last value is greater than the first', () => { props.data[0] = 5; props.data[props.data.length - 1] = 10; + props.muted = true; render(); const paths = screen.getAllByTestId('sparkline-path'); const path = paths[0]; - expect(path).toHaveClass('stroke-bullish'); + expect(path).toHaveClass( + '[vector-effect:non-scaling-stroke] stroke-green-dark dark:stroke-green' + ); }); it('Renders a white line if the first and last values are equal', () => { diff --git a/libs/ui-toolkit/src/components/sparkline/sparkline.tsx b/libs/ui-toolkit/src/components/sparkline/sparkline.tsx index ce89b3be9..8a0dd8376 100644 --- a/libs/ui-toolkit/src/components/sparkline/sparkline.tsx +++ b/libs/ui-toolkit/src/components/sparkline/sparkline.tsx @@ -8,8 +8,8 @@ function colorByChange(a: number, b: number) { return a === b ? 'stroke-black/40 dark:stroke-white/40' : a < b - ? 'stroke-bullish' - : 'stroke-bearish'; + ? 'stroke-green-dark dark:stroke-green' + : 'stroke-red-dark dark:stroke-red'; } export interface SparklineProps { @@ -18,6 +18,7 @@ export interface SparklineProps { height?: number; points?: number; className?: string; + muted?: boolean; } export const SparklineView = ({ @@ -25,6 +26,7 @@ export const SparklineView = ({ width = 60, height = 15, points = 25, + muted = false, className, }: SparklineProps) => { // How many points are missing. If market is 12 hours old the 25 - 12 @@ -69,10 +71,11 @@ export const SparklineView = ({ // Get the color of the marketData line const [firstVal, lastVal] = [data[0], data[data.length - 1]]; - const strokeClassName = - data.length >= 24 + const strokeClassName = muted + ? data.length >= 24 ? colorByChange(firstVal, lastVal) - : 'stroke-black/40 dark:stroke-white/40'; + : 'stroke-black/40 dark:stroke-white/40' + : colorByChange(firstVal, lastVal); // Create paths const preMarketCreationPath = lineSeries(preMarketData); @@ -93,7 +96,7 @@ export const SparklineView = ({ className={`[vector-effect:non-scaling-stroke] ${strokeClassName}`} d={preMarketCreationPath} stroke="strokeCurrent" - strokeWidth={2} + strokeWidth={1} fill="transparent" /> )} @@ -103,7 +106,7 @@ export const SparklineView = ({ d={mainPath} className={strokeClassName} stroke="strokeCurrent" - strokeWidth={1} + strokeWidth={2} fill="transparent" /> )}