feat: [console-lite] - order review - add info tooltips (#1029)

* feat: [console-lite] - order review - add info tooltips

* feat: [console-lite] - order review - add info tooltips - upgrade lib version, fix tests

* feat: [console-lite] - info tooltip improve

* feat: [console-lite] - make tooltip focusable for mobile handling

* feat: [console-lite] - make tooltip add lame int test for mobile

* feat: [console-lite] - make tooltip - refactor component with tooltip

* feat: [console-lite] - make tooltip - fix failling int test

Co-authored-by: maciek <maciek@vegaprotocol.io>
This commit is contained in:
macqbat 2022-08-17 11:46:49 +02:00 committed by GitHub
parent 8174962574
commit dac9b8b015
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 163 additions and 13 deletions

View File

@ -113,6 +113,9 @@ describe('Market trade', () => {
.eq(0)
.find('button')
.should('have.text', '1');
cy.getByTestId('max-label').should('have.text', '21');
cy.getByTestId('percentage-selector')
.find('button')
.contains('Max')
@ -203,6 +206,14 @@ describe('Market trade', () => {
connectVegaWallet();
cy.get('h3').contains('Review Trade').click();
cy.getByTestId('review-trade')
.get('#contracts_tooltip_trigger')
.trigger('click')
.realTouch();
cy.get('[data-radix-popper-content-wrapper]').contains(
'The number of contracts determines'
);
cy.get('#step-3-panel').find('dd').eq(1).should('have.text', '1');
cy.get('#step-3-panel').find('dd').eq(2).should('have.text', '98.93006');
@ -221,4 +232,23 @@ describe('Market trade', () => {
);
}
});
it('info tooltip on mobile view should work well', () => {
if (markets?.length) {
cy.viewport('iphone-xr');
cy.visit(`/trading/${markets[0].id}`);
cy.get('h3').contains('Review Trade').click();
cy.getByTestId('review-trade')
.get('#contracts_tooltip_trigger')
.realTouch();
cy.get('[data-radix-popper-content-wrapper]').contains(
'The number of contracts determines'
);
cy.getByTestId('review-trade').get('div.cursor-help').eq(1).realTouch();
cy.get('[data-radix-popper-content-wrapper]').contains(
'The notional size represents the position size'
);
}
});
});

View File

@ -23,5 +23,5 @@ NX_VEGA_URL="https://lb.testnet.vega.xyz/query"
NX_VEGA_WALLET_URL=http://localhost:1789/api/v1
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://ropsten.etherscan.io
NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\"}
NX_VEGA_NETWORKS={"MAINNET":"https://alpha.console.vega.xyz"}
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf

View File

@ -0,0 +1,17 @@
import { t } from '@vegaprotocol/react-helpers';
export const EST_MARGIN_TOOLTIP_TEXT = t(
'When opening a position on a futures market, you must post margin to cover any potential losses that you may incur. The margin is typically a fraction of the notional position size. For example, for a notional position size of $500, if the margin requirement is 10%, then the estimated margin would be approximately $50.'
);
export const CONTRACTS_MARGIN_TOOLTIP_TEXT = t(
'The number of contracts determines how many units of the futures contract to buy or sell. For example, this is similar to buying one share of a listed company. The value of 1 contract is equivalent to the price of the contract. For example, if the current price is $50, then one contract is worth $50.'
);
export const EST_CLOSEOUT_TOOLTIP_TEXT = t(
'Because you only need to post a fraction of your position size as margin when trading futures, it is possible to obtain leverage meaning your notional position size exceeds your account balance. In this scenario, if the market moves against your position, it will sometimes be necessary to force close your position due to insufficient funds. The estimated close out tells you the price at which that would happen based on current position and account balance.'
);
export const NOTIONAL_SIZE_TOOLTIP_TEXT = t(
'The notional size represents the position size in the settlement asset of the futures contract. The notional size is calculated by multiplying the number of contracts by the price of the contract. For example, ten contracts traded at a price of $50 has a notional size of $500.'
);
export const EST_FEES_TOOLTIP_TEXT = t(
'When you execute a new buy or sell order, you must pay a small amount of commission to the network for doing so. This fee is used to provide income to the node operates of the network and market makers who make prices on the futures market you are trading.'
);

View File

@ -1,6 +1,9 @@
import React from 'react';
import type { ReactNode } from 'react';
import { t } from '@vegaprotocol/react-helpers';
import { Icon, Tooltip } from '@vegaprotocol/ui-toolkit';
import { IconNames } from '@blueprintjs/icons';
import * as constants from './constants';
interface DealTicketEstimatesProps {
quoteName?: string;
@ -24,6 +27,27 @@ const DataTitle = ({ children, quoteName = '' }: DataTitleProps) => (
</dt>
);
interface ValueTooltipProps {
value: string;
description: string;
id?: string;
}
const ValueTooltipRow = ({ value, description, id }: ValueTooltipProps) => (
<dd className="flex gap-x-5 items-center">
{value}
<Tooltip align="center" description={description}>
<div className="cursor-help" id={id || ''} tabIndex={-1}>
<Icon
name={IconNames.ISSUE}
className="block rotate-180"
ariaLabel={description}
/>
</div>
</Tooltip>
</dd>
);
export const DealTicketEstimates = ({
price,
quoteName,
@ -36,8 +60,12 @@ export const DealTicketEstimates = ({
<dl className="text-black dark:text-white">
{size && (
<div className="flex justify-between mb-8">
<DataTitle>{t('No. of Contracts')}</DataTitle>
<dd>{size}</dd>
<DataTitle>{t('Contracts')}</DataTitle>
<ValueTooltipRow
value={size}
description={constants.CONTRACTS_MARGIN_TOOLTIP_TEXT}
id="contracts_tooltip_trigger"
/>
</div>
)}
{price && (
@ -49,19 +77,28 @@ export const DealTicketEstimates = ({
{notionalSize && (
<div className="flex justify-between mb-8">
<DataTitle quoteName={quoteName}>{t('Est. Position Size')}</DataTitle>
<dd>{notionalSize}</dd>
<ValueTooltipRow
value={notionalSize}
description={constants.NOTIONAL_SIZE_TOOLTIP_TEXT}
/>
</div>
)}
{fees && (
<div className="flex justify-between mb-8">
<DataTitle quoteName={quoteName}>{t('Est. Fees')}</DataTitle>
<dd>{fees}</dd>
<ValueTooltipRow
value={fees}
description={constants.EST_FEES_TOOLTIP_TEXT}
/>
</div>
)}
{estMargin && (
<div className="flex justify-between mb-8">
<DataTitle quoteName={quoteName}>{t('Est. Margin')}</DataTitle>
<dd>{estMargin}</dd>
<ValueTooltipRow
value={estMargin}
description={constants.EST_MARGIN_TOOLTIP_TEXT}
/>
</div>
)}
{estCloseOut && (
@ -71,7 +108,10 @@ export const DealTicketEstimates = ({
&nbsp;
<small>({quoteName})</small>
</dt>
<dd>{estCloseOut}</dd>
<ValueTooltipRow
value={estCloseOut}
description={constants.EST_CLOSEOUT_TOOLTIP_TEXT}
/>
</div>
)}
</dl>

View File

@ -109,8 +109,8 @@ export const DealTicketSize = ({
) : (
<div>
<div className="flex justify-between text-black dark:text-white mb-8">
<span>{min}</span>
<span>{max}</span>
<span data-testid="min-label">{min}</span>
<span data-testid="max-label">{max}</span>
</div>
<SliderRoot
className="mb-8"

View File

@ -64,7 +64,7 @@ export default ({
);
return (
<div className="mb-8 text-black dark:text-white">
<div className="mb-8 text-black dark:text-white" data-testid="review-trade">
<KeyValueTable>
<KeyValueTableRow noBorder>
<div className="flex flex-none gap-x-5 items-center">

View File

@ -51,6 +51,7 @@ export default ({ marketId, partyId }: Props): PositionMargin => {
pollInterval: 5000,
variables: { partyId },
skip: !partyId,
fetchPolicy: 'no-cache',
}
);

View File

@ -23,8 +23,11 @@ export function createClient(base?: string) {
const cache = new InMemoryCache({
typePolicies: {
Market: {
merge: true,
},
Party: {
keyFields: false,
merge: true,
},
Query: {},
Account: {

View File

@ -87,6 +87,35 @@ const vegaCustomClassesLite = plugin(function ({ addUtilities }) {
color: theme.colors.black.DEFAULT,
},
},
'.tooltip-content': {
'& > div': {
fontSize: '12px',
borderWeight: '1px',
borderRadius: '7px',
borderColor: theme.colors.black.DEFAULT,
backgroundColor: theme.colors.black.DEFAULT,
color: theme.colors.white.DEFAULT,
},
'& svg[width="10"]': {
fill: theme.colors.black.DEFAULT,
},
'& svg[width="8"]': {
fill: theme.colors.black.DEFAULT,
},
},
'.tooltip-content-dark': {
'& > div': {
borderColor: theme.colors.black.DEFAULT,
backgroundColor: '#dcdcc8',
color: '#333333',
},
'& svg[width="10"]': {
fill: theme.colors.white.DEFAULT,
},
'& svg[width="8"]': {
fill: '#dcdcc8',
},
},
});
});

View File

@ -1,7 +1,19 @@
import { render, screen } from '@testing-library/react';
import { fireEvent, render, screen } from '@testing-library/react';
import { Tooltip } from './tooltip';
it('Renders a tooltip', async () => {
const props = {
description: 'description',
children: <button>Tooltip</button>,
};
render(<Tooltip {...props} />);
// radix applies the data-state attribute
expect(screen.getByRole('button')).toHaveAttribute('data-state', 'closed');
fireEvent.pointerMove(screen.getByRole('button'));
expect(await screen.findByRole('tooltip')).toBeInTheDocument();
});
it('Renders a controlled tooltip', async () => {
const props = {
description: 'description',
children: <button>Tooltip</button>,
@ -15,6 +27,20 @@ it('Renders a tooltip', async () => {
});
it('Doesnt render a tooltip if no description provided', () => {
const props = {
description: undefined,
children: <button>Tooltip</button>,
};
render(<Tooltip {...props} />);
expect(screen.getByRole('button')).not.toHaveAttribute(
'data-state',
'closed'
);
fireEvent.pointerMove(screen.getByRole('button'));
expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
});
it('Doesnt render a controlled tooltip if no description provided', () => {
const props = {
description: undefined,
children: <button>Tooltip</button>,

View File

@ -24,7 +24,11 @@ export const Tooltip = ({ children, description, open, align }: TooltipProps) =>
<Trigger asChild>{children}</Trigger>
{description && (
<Portal>
<Content align={align} alignOffset={8}>
<Content
align={align}
alignOffset={8}
className="tooltip-content dark:tooltip-content-dark"
>
<div className="relative z-0 p-8 bg-black-50 border border-black-60 text-white rounded-sm max-w-sm text-ui">
{description}
</div>