Feat/348 select size of trade (#943)
* feat(ui-toolkit): add slider to ui-toolkit * feat(console-lite): add deal ticket size to use new slider * feat(console-lite): add use-maximum-position-size hook * feat(console-lite): add e2e tests * feat(console-lite): add position size value after selection * fix(console-lite): remove lingering console log * fix(console-lite): fix linting errors * fix(console-lite): fix cypress config with wrong app specified * fix(console-lite): fix react hooks bug after upgrade to react 18 * feat(console-lite): add proportional size selector and size amount input * fix(console-lite): add missing env variables * feat(console-lite): add missing tests and fix broken one after input button for size * fix(console-lite): fix async error for max trade size
This commit is contained in:
parent
1a1ab4db65
commit
b75ed62072
@ -12,8 +12,9 @@ module.exports = defineConfig({
|
||||
supportFile: './src/support/index.ts',
|
||||
video: true,
|
||||
videoUploadOnPasses: false,
|
||||
videosFolder: '../../dist/cypress/apps/explorer-e2e/videos',
|
||||
screenshotsFolder: '../../dist/cypress/apps/explorer-e2e/screenshots',
|
||||
videosFolder: '../../dist/cypress/apps/simple-trading-app-e2e/videos',
|
||||
screenshotsFolder:
|
||||
'../../dist/cypress/apps/simple-trading-app-e2e/screenshots',
|
||||
chromeWebSecurity: false,
|
||||
viewportWidth: 1440,
|
||||
viewportHeight: 900,
|
||||
|
@ -29,6 +29,7 @@ describe('Market trade', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('side selector should work well', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[0].id}`);
|
||||
@ -46,7 +47,7 @@ describe('Market trade', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('mobile view should work well', () => {
|
||||
it('side selector mobile view should work well', () => {
|
||||
if (markets?.length) {
|
||||
cy.viewport('iphone-xr');
|
||||
cy.visit(`/trading/${markets[0].id}`);
|
||||
@ -78,6 +79,79 @@ describe('Market trade', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('size slider should work well', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[1].id}`);
|
||||
connectVegaWallet();
|
||||
cy.get('#step-1-control [aria-label^="Selected value"]').click();
|
||||
cy.get('button[aria-label="Open short position"]').click();
|
||||
cy.get('#step-2-control').click();
|
||||
cy.get('#step-2-panel')
|
||||
.find('dd')
|
||||
.eq(0)
|
||||
.find('button')
|
||||
.should('have.text', '1');
|
||||
cy.get('#step-2-panel').find('[role="slider"]').type('{rightarrow}');
|
||||
|
||||
cy.get('#step-2-panel')
|
||||
.find('dd')
|
||||
.eq(0)
|
||||
.find('button')
|
||||
.should('have.text', '2');
|
||||
}
|
||||
});
|
||||
|
||||
it('percentage selection should work well', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[1].id}`);
|
||||
connectVegaWallet();
|
||||
cy.get('#step-1-control [aria-label^="Selected value"]').click();
|
||||
cy.get('button[aria-label="Open short position"]').click();
|
||||
cy.get('#step-2-control').click();
|
||||
cy.get('#step-2-panel')
|
||||
.find('dd')
|
||||
.eq(0)
|
||||
.find('button')
|
||||
.should('have.text', '1');
|
||||
cy.getByTestId('percentage-selector')
|
||||
.find('button')
|
||||
.contains('Max')
|
||||
.click();
|
||||
cy.get('#step-2-panel')
|
||||
.find('dd')
|
||||
.eq(0)
|
||||
.find('button')
|
||||
.should('have.text', '21');
|
||||
}
|
||||
});
|
||||
|
||||
it('size input should work well', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[1].id}`);
|
||||
connectVegaWallet();
|
||||
cy.get('#step-1-control [aria-label^="Selected value"]').click();
|
||||
cy.get('button[aria-label="Open short position"]').click();
|
||||
cy.get('#step-2-control').click();
|
||||
cy.get('#step-2-panel')
|
||||
.find('dd')
|
||||
.eq(0)
|
||||
.find('button')
|
||||
.should('have.text', '1');
|
||||
cy.get('#step-2-panel').find('dd').eq(0).find('button').click();
|
||||
cy.get('#step-2-panel')
|
||||
.find('dd')
|
||||
.eq(0)
|
||||
.find('input')
|
||||
.type('{backspace}2');
|
||||
cy.get('#step-2-panel').find('dd').eq(0).find('button').click();
|
||||
cy.get('#step-2-panel')
|
||||
.find('dd')
|
||||
.eq(0)
|
||||
.find('button')
|
||||
.should('have.text', '2');
|
||||
}
|
||||
});
|
||||
|
||||
it('order review should display proper calculations', () => {
|
||||
if (markets?.length) {
|
||||
cy.visit(`/trading/${markets[0].id}`);
|
||||
|
@ -4,6 +4,7 @@ export const generateMarketPositions = () => {
|
||||
id: '2e1ef32e5804e14232406aebaad719087d326afa5c648b7824d0823d8a46c8d1',
|
||||
accounts: [
|
||||
{
|
||||
type: 'General',
|
||||
asset: {
|
||||
decimals: 5,
|
||||
},
|
||||
@ -14,6 +15,7 @@ export const generateMarketPositions = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'Margin',
|
||||
asset: {
|
||||
decimals: 5,
|
||||
},
|
||||
|
@ -4,6 +4,7 @@ export const generatePartyBalance = () => {
|
||||
accounts: [
|
||||
{
|
||||
balance: '88474051',
|
||||
type: 'General',
|
||||
asset: {
|
||||
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||
symbol: 'tDAI',
|
||||
@ -15,6 +16,7 @@ export const generatePartyBalance = () => {
|
||||
},
|
||||
{
|
||||
balance: '100000000',
|
||||
type: 'General',
|
||||
asset: {
|
||||
id: '8b52d4a3a4b0ffe733cddbc2b67be273816cfeb6ca4c8b339bac03ffba08e4e4',
|
||||
symbol: 'tEURO',
|
||||
@ -26,6 +28,7 @@ export const generatePartyBalance = () => {
|
||||
},
|
||||
{
|
||||
balance: '3412867',
|
||||
type: 'General',
|
||||
asset: {
|
||||
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||
symbol: 'tDAI',
|
||||
@ -37,6 +40,7 @@ export const generatePartyBalance = () => {
|
||||
},
|
||||
{
|
||||
balance: '70007',
|
||||
type: 'General',
|
||||
asset: {
|
||||
id: '6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61',
|
||||
symbol: 'tDAI',
|
||||
|
@ -17,8 +17,11 @@ NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
|
||||
NX_URL=$URL
|
||||
NX_DEPLOY_URL=$DEPLOY_URL
|
||||
NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
|
||||
|
||||
NX_VEGA_CONFIG_URL="https://static.vega.xyz/assets/testnet-network.json"
|
||||
NX_VEGA_ENV = 'TESTNET'
|
||||
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_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
|
@ -3,3 +3,7 @@ NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json
|
||||
NX_VEGA_URL=https://n04.d.vega.xyz/query
|
||||
NX_VEGA_ENV=DEVNET
|
||||
NX_VEGA_REST=https://n04.d.vega.xyz/datanode/rest
|
||||
NX_VEGA_NETWORKS={\"MAINNET\":\"https://alpha.console.vega.xyz\"}
|
||||
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://ropsten.etherscan.io
|
||||
NX_VEGA_EXPLORER_URL=https://dev.explorer.vega.xyz
|
||||
|
@ -3,3 +3,7 @@ NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/mainnet-network.json
|
||||
NX_VEGA_URL=https://api.token.vega.xyz/query
|
||||
NX_VEGA_ENV=MAINNET
|
||||
NX_VEGA_REST=https://api.token.vega.xyz/
|
||||
NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}'
|
||||
NX_ETHEREUM_PROVIDER_URL=https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://etherscan.io
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.vega.xyz
|
||||
|
@ -3,3 +3,7 @@ NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet1-network.json
|
||||
NX_VEGA_URL=https://n03.s.vega.xyz/query
|
||||
NX_VEGA_ENV=STAGNET
|
||||
NX_VEGA_REST=https://n03.s.vega.xyz/datanode/rest
|
||||
NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}'
|
||||
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://ropsten.etherscan.io
|
||||
NX_VEGA_EXPLORER_URL=https://staging.explorer.vega.xyz
|
||||
|
@ -3,3 +3,7 @@ NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet2-network.json
|
||||
NX_VEGA_URL=https://n03.stagnet2.vega.xyz/query
|
||||
NX_VEGA_ENV=STAGNET2
|
||||
NX_VEGA_REST=https://n01.stagnet2.vega.xyz/datanode/rest
|
||||
NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}'
|
||||
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://ropsten.etherscan.io
|
||||
NX_VEGA_EXPLORER_URL=https://staging2.explorer.vega.xyz
|
||||
|
@ -3,3 +3,7 @@ NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
|
||||
NX_VEGA_URL=https://lb.testnet.vega.xyz/query
|
||||
NX_VEGA_ENV=TESTNET
|
||||
NX_VEGA_REST=https://lb.testnet.vega.xyz/datanode/rest
|
||||
NX_VEGA_NETWORKS='{\"MAINNET\":\"https://alpha.console.vega.xyz\"}'
|
||||
NX_ETHEREUM_PROVIDER_URL=https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
|
||||
NX_ETHERSCAN_URL=https://ropsten.etherscan.io
|
||||
NX_VEGA_EXPLORER_URL=https://explorer.fairground.wtf
|
||||
|
@ -3,6 +3,8 @@
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
import { AccountType } from "@vegaprotocol/types";
|
||||
|
||||
// ====================================================
|
||||
// GraphQL query operation: PartyBalanceQuery
|
||||
// ====================================================
|
||||
@ -29,6 +31,10 @@ export interface PartyBalanceQuery_party_accounts_asset {
|
||||
|
||||
export interface PartyBalanceQuery_party_accounts {
|
||||
__typename: "Account";
|
||||
/**
|
||||
* Account type (General, Margin, etc)
|
||||
*/
|
||||
type: AccountType;
|
||||
/**
|
||||
* Balance as string - current account balance (approx. as balances can be updated several times per second)
|
||||
*/
|
||||
|
@ -5,6 +5,7 @@ import type {
|
||||
PartyBalanceQuery_party_accounts_asset,
|
||||
} from './__generated__/PartyBalanceQuery';
|
||||
import { DealTicketBalance } from './deal-ticket-balance';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
|
||||
const tDAI: PartyBalanceQuery_party_accounts_asset = {
|
||||
__typename: 'Asset',
|
||||
@ -17,6 +18,7 @@ const tDAI: PartyBalanceQuery_party_accounts_asset = {
|
||||
const accounts: PartyBalanceQuery_party_accounts[] = [
|
||||
{
|
||||
__typename: 'Account',
|
||||
type: AccountType.General,
|
||||
balance: '1000000',
|
||||
asset: tDAI,
|
||||
},
|
||||
|
@ -4,6 +4,7 @@ import type { DealTicketQuery_market_tradableInstrument_instrument_product_settl
|
||||
import type { PartyBalanceQuery_party_accounts } from './__generated__/PartyBalanceQuery';
|
||||
import { useSettlementAccount } from '../../hooks/use-settlement-account';
|
||||
import { addDecimalsFormatNumber, t } from '@vegaprotocol/react-helpers';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
|
||||
interface DealTicketBalanceProps {
|
||||
settlementAsset: DealTicketQuery_market_tradableInstrument_instrument_product_settlementAsset;
|
||||
@ -20,7 +21,11 @@ export const DealTicketBalance = ({
|
||||
}: DealTicketBalanceProps) => {
|
||||
const settlementAssetId = settlementAsset?.id;
|
||||
const settlementAssetSymbol = settlementAsset?.symbol;
|
||||
const settlementAccount = useSettlementAccount(settlementAssetId, accounts);
|
||||
const settlementAccount = useSettlementAccount(
|
||||
settlementAssetId,
|
||||
accounts,
|
||||
AccountType.General
|
||||
);
|
||||
const formatedNumber =
|
||||
settlementAccount?.balance &&
|
||||
settlementAccount.asset.decimals &&
|
||||
|
@ -17,6 +17,7 @@ const PARTY_BALANCE_QUERY = gql`
|
||||
query PartyBalanceQuery($partyId: ID!) {
|
||||
party(id: $partyId) {
|
||||
accounts {
|
||||
type
|
||||
balance
|
||||
asset {
|
||||
id
|
||||
|
@ -0,0 +1,201 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
SliderRoot,
|
||||
SliderThumb,
|
||||
SliderTrack,
|
||||
SliderRange,
|
||||
Button,
|
||||
Input,
|
||||
FormGroup,
|
||||
} from '@vegaprotocol/ui-toolkit';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
|
||||
interface DealTicketSizeProps {
|
||||
step: number;
|
||||
min: number;
|
||||
max: number;
|
||||
value: number;
|
||||
onValueChange: (value: number[]) => void;
|
||||
name: string;
|
||||
quoteName: string;
|
||||
price: string;
|
||||
estCloseOut: string;
|
||||
estMargin: string;
|
||||
positionDecimalPlaces: number;
|
||||
}
|
||||
|
||||
const getSizeLabel = (value: number): string => {
|
||||
const MIN_LABEL = 'Min';
|
||||
const MAX_LABEL = 'Max';
|
||||
if (value === 0) {
|
||||
return MIN_LABEL;
|
||||
} else if (value === 100) {
|
||||
return MAX_LABEL;
|
||||
}
|
||||
|
||||
return `${value}%`;
|
||||
};
|
||||
|
||||
export const DealTicketSize = ({
|
||||
value,
|
||||
step,
|
||||
min,
|
||||
max,
|
||||
price,
|
||||
quoteName,
|
||||
onValueChange,
|
||||
estCloseOut,
|
||||
positionDecimalPlaces,
|
||||
}: DealTicketSizeProps) => {
|
||||
const sizeRatios = [0, 25, 50, 75, 100];
|
||||
const [inputValue, setInputValue] = useState(value);
|
||||
const [isInputVisible, setIsInputVisible] = useState(false);
|
||||
|
||||
const onInputValueChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = parseFloat(event.target.value);
|
||||
const isLessThanMin = value < min;
|
||||
const isMoreThanMax = value > max;
|
||||
if (value) {
|
||||
if (isLessThanMin) {
|
||||
onValueChange([min]);
|
||||
} else if (isMoreThanMax) {
|
||||
onValueChange([max]);
|
||||
} else {
|
||||
onValueChange([value]);
|
||||
}
|
||||
}
|
||||
setInputValue(value);
|
||||
},
|
||||
[min, max, onValueChange, setInputValue]
|
||||
);
|
||||
|
||||
const onButtonValueChange = useCallback(
|
||||
(size: number) => {
|
||||
if (isInputVisible) {
|
||||
setIsInputVisible(false);
|
||||
}
|
||||
const newVal = new BigNumber(size)
|
||||
.decimalPlaces(positionDecimalPlaces)
|
||||
.toNumber();
|
||||
onValueChange([newVal]);
|
||||
setInputValue(newVal);
|
||||
},
|
||||
[isInputVisible, onValueChange, positionDecimalPlaces]
|
||||
);
|
||||
|
||||
const toggleInput = useCallback(() => {
|
||||
setIsInputVisible(!isInputVisible);
|
||||
}, [isInputVisible]);
|
||||
|
||||
const onInputEnter = useCallback(
|
||||
(event: React.KeyboardEvent) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.stopPropagation();
|
||||
toggleInput();
|
||||
}
|
||||
},
|
||||
[toggleInput]
|
||||
);
|
||||
|
||||
return max === 0 ? (
|
||||
<p>Not enough balance to trade</p>
|
||||
) : (
|
||||
<div>
|
||||
<div className="flex justify-between text-black dark:text-white mb-8">
|
||||
<span>{min}</span>
|
||||
<span>{max}</span>
|
||||
</div>
|
||||
<SliderRoot
|
||||
className="mb-8"
|
||||
value={[value]}
|
||||
onValueChange={onValueChange}
|
||||
step={step}
|
||||
min={min}
|
||||
max={max}
|
||||
>
|
||||
<SliderTrack className="bg-lightGrey dark:bg-offBlack">
|
||||
<SliderRange className="!bg-black dark:!bg-white" />
|
||||
</SliderTrack>
|
||||
<SliderThumb />
|
||||
</SliderRoot>
|
||||
|
||||
<div
|
||||
data-testid="percentage-selector"
|
||||
className="flex w-full justify-between text-black dark:text-white mb-32"
|
||||
>
|
||||
{sizeRatios.map((size, index) => {
|
||||
const proportionalSize = size ? (size / 100) * max : min;
|
||||
return (
|
||||
<Button
|
||||
variant="inline-link"
|
||||
className="no-underline !text-blue"
|
||||
onClick={() => onButtonValueChange(proportionalSize)}
|
||||
key={index}
|
||||
>
|
||||
{getSizeLabel(size)}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<dl className="text-black dark:text-white">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<dt>
|
||||
<span>{t('Size')}</span>
|
||||
|
||||
<small>({quoteName})</small>
|
||||
</dt>
|
||||
<dd className="flex justify-end w-full">
|
||||
<FormGroup
|
||||
className="mb-0 flex items-center"
|
||||
labelClassName="mr-8 sr-only"
|
||||
label="Enter Size"
|
||||
labelFor="trade-size-input"
|
||||
>
|
||||
{isInputVisible ? (
|
||||
<>
|
||||
<Input
|
||||
id="input-order-size-market"
|
||||
type="number"
|
||||
step={step}
|
||||
min={min}
|
||||
max={max}
|
||||
className="w-full"
|
||||
value={inputValue}
|
||||
onKeyDown={onInputEnter}
|
||||
onChange={onInputValueChange}
|
||||
/>
|
||||
<Button
|
||||
variant="inline-link"
|
||||
className="no-underline !text-blue"
|
||||
onClick={toggleInput}
|
||||
>
|
||||
{t('set')}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
variant="inline-link"
|
||||
className="no-underline !text-blue"
|
||||
onClick={toggleInput}
|
||||
>
|
||||
{value}
|
||||
</Button>
|
||||
)}
|
||||
</FormGroup>
|
||||
</dd>
|
||||
</div>
|
||||
<div className="flex justify-between mb-8">
|
||||
<dt>{t('Est. price')}</dt>
|
||||
<dd>{price}</dd>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<dt>{t('Est. close out')}</dt>
|
||||
<dd>{estCloseOut}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,29 +1,34 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { Stepper } from '../stepper';
|
||||
import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
|
||||
import { InputError } from '@vegaprotocol/ui-toolkit';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import {
|
||||
DealTicketAmount,
|
||||
getOrderDialogTitle,
|
||||
getOrderDialogIntent,
|
||||
getOrderDialogIcon,
|
||||
MarketSelector,
|
||||
} from '@vegaprotocol/deal-ticket';
|
||||
import type { Order } from '@vegaprotocol/orders';
|
||||
import { VegaTxStatus } from '@vegaprotocol/wallet';
|
||||
import { useVegaWallet, VegaTxStatus } from '@vegaprotocol/wallet';
|
||||
import { t, addDecimal, toDecimal } from '@vegaprotocol/react-helpers';
|
||||
import {
|
||||
getDefaultOrder,
|
||||
useOrderValidation,
|
||||
useOrderSubmit,
|
||||
OrderFeedback,
|
||||
validateSize,
|
||||
} from '@vegaprotocol/orders';
|
||||
import { DealTicketSize } from './deal-ticket-size';
|
||||
import MarketNameRenderer from '../simple-market-list/simple-market-renderer';
|
||||
import SideSelector, { SIDE_NAMES } from './side-selector';
|
||||
import ReviewTrade from './review-trade';
|
||||
import type { PartyBalanceQuery } from './__generated__/PartyBalanceQuery';
|
||||
import useOrderCloseOut from '../../hooks/use-order-closeout';
|
||||
import useOrderMargin from '../../hooks/use-order-margin';
|
||||
import useMaximumPositionSize from '../../hooks/use-maximum-position-size';
|
||||
|
||||
interface DealTicketMarketProps {
|
||||
market: DealTicketQuery_market;
|
||||
@ -43,21 +48,48 @@ export const DealTicketSteps = ({
|
||||
);
|
||||
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
handleSubmit,
|
||||
watch,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<Order>({
|
||||
mode: 'onChange',
|
||||
defaultValues: getDefaultOrder(market),
|
||||
});
|
||||
|
||||
const [max, setMax] = useState<number | null>(null);
|
||||
const step = toDecimal(market.positionDecimalPlaces);
|
||||
const orderType = watch('type');
|
||||
const orderTimeInForce = watch('timeInForce');
|
||||
const orderSide = watch('side');
|
||||
const orderSize = watch('size');
|
||||
const order = watch();
|
||||
const estCloseOut = useOrderCloseOut({ order, market, partyData });
|
||||
const { keypair } = useVegaWallet();
|
||||
const estMargin = useOrderMargin({
|
||||
order,
|
||||
market,
|
||||
partyId: keypair?.pub || '',
|
||||
});
|
||||
|
||||
const maxTrade = useMaximumPositionSize({
|
||||
partyId: keypair?.pub || '',
|
||||
accounts: partyData?.party?.accounts || [],
|
||||
marketId: market.id,
|
||||
settlementAssetId:
|
||||
market.tradableInstrument.instrument.product.settlementAsset.id,
|
||||
price: market?.depth?.lastTrade?.price,
|
||||
order,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setMax(
|
||||
new BigNumber(maxTrade)
|
||||
.decimalPlaces(market.positionDecimalPlaces)
|
||||
.toNumber()
|
||||
);
|
||||
}, [maxTrade, market.positionDecimalPlaces]);
|
||||
|
||||
const { message: invalidText, isDisabled } = useOrderValidation({
|
||||
step,
|
||||
@ -70,6 +102,16 @@ export const DealTicketSteps = ({
|
||||
const { submit, transaction, finalizedOrder, TransactionDialog } =
|
||||
useOrderSubmit(market);
|
||||
|
||||
const onSizeChange = (value: number[]) => {
|
||||
const newVal = new BigNumber(value[0])
|
||||
.decimalPlaces(market.positionDecimalPlaces)
|
||||
.toString();
|
||||
const isValid = validateSize(step)(newVal);
|
||||
if (isValid !== 'step') {
|
||||
setValue('size', newVal);
|
||||
}
|
||||
};
|
||||
|
||||
const transactionStatus =
|
||||
transaction.status === VegaTxStatus.Requested ||
|
||||
transaction.status === VegaTxStatus.Pending
|
||||
@ -112,19 +154,29 @@ export const DealTicketSteps = ({
|
||||
},
|
||||
{
|
||||
label: t('Choose Position Size'),
|
||||
component: (
|
||||
<DealTicketAmount
|
||||
orderType={orderType}
|
||||
step={step}
|
||||
register={register}
|
||||
price={
|
||||
market.depth.lastTrade
|
||||
? addDecimal(market.depth.lastTrade.price, market.decimalPlaces)
|
||||
: undefined
|
||||
}
|
||||
quoteName={market.tradableInstrument.instrument.product.quoteName}
|
||||
/>
|
||||
),
|
||||
component:
|
||||
max !== null ? (
|
||||
<DealTicketSize
|
||||
step={step}
|
||||
min={step}
|
||||
max={max}
|
||||
onValueChange={onSizeChange}
|
||||
value={new BigNumber(orderSize).toNumber()}
|
||||
name="size"
|
||||
price={
|
||||
market.depth.lastTrade
|
||||
? addDecimal(market.depth.lastTrade.price, market.decimalPlaces)
|
||||
: ''
|
||||
}
|
||||
positionDecimalPlaces={market.positionDecimalPlaces}
|
||||
quoteName={market.tradableInstrument.instrument.product.quoteName}
|
||||
estCloseOut={estCloseOut}
|
||||
estMargin={estMargin || ' - '}
|
||||
/>
|
||||
) : (
|
||||
'loading...'
|
||||
),
|
||||
value: orderSize,
|
||||
},
|
||||
{
|
||||
label: t('Review Trade'),
|
||||
@ -140,7 +192,8 @@ export const DealTicketSteps = ({
|
||||
isDisabled={isDisabled}
|
||||
transactionStatus={transactionStatus}
|
||||
order={order}
|
||||
partyData={partyData}
|
||||
estCloseOut={estCloseOut}
|
||||
estMargin={estMargin || ' - '}
|
||||
/>
|
||||
<TransactionDialog
|
||||
title={getOrderDialogTitle(finalizedOrder?.status)}
|
||||
|
@ -1 +1,2 @@
|
||||
export { DealTicketContainer } from './deal-ticket-container';
|
||||
export * from './deal-ticket-container';
|
||||
export * from './deal-ticket-size';
|
||||
|
@ -10,17 +10,14 @@ import classNames from 'classnames';
|
||||
import type { DealTicketQuery_market } from '@vegaprotocol/deal-ticket';
|
||||
import type { Order } from '@vegaprotocol/orders';
|
||||
import { SIDE_NAMES } from './side-selector';
|
||||
import { useVegaWallet, VegaWalletOrderSide } from '@vegaprotocol/wallet';
|
||||
import { VegaWalletOrderSide } from '@vegaprotocol/wallet';
|
||||
import SimpleMarketExpires from '../simple-market-list/simple-market-expires';
|
||||
import { gql, useQuery } from '@apollo/client';
|
||||
import type {
|
||||
MarketTags,
|
||||
MarketTagsVariables,
|
||||
} from './__generated__/MarketTags';
|
||||
import useOrderMargin from '../../hooks/use-order-margin';
|
||||
import useOrderCloseOut from '../../hooks/use-order-closeout';
|
||||
import { IconNames } from '@blueprintjs/icons';
|
||||
import type { PartyBalanceQuery } from './__generated__/PartyBalanceQuery';
|
||||
|
||||
export const MARKET_TAGS_QUERY = gql`
|
||||
query MarketTags($marketId: ID!) {
|
||||
@ -41,7 +38,8 @@ interface Props {
|
||||
isDisabled: boolean;
|
||||
transactionStatus?: string;
|
||||
order: Order;
|
||||
partyData?: PartyBalanceQuery;
|
||||
estCloseOut: string;
|
||||
estMargin: string;
|
||||
}
|
||||
|
||||
export default ({
|
||||
@ -49,21 +47,16 @@ export default ({
|
||||
market,
|
||||
order,
|
||||
transactionStatus,
|
||||
partyData,
|
||||
estCloseOut,
|
||||
estMargin,
|
||||
}: Props) => {
|
||||
const { keypair } = useVegaWallet();
|
||||
const { data: tagsData } = useQuery<MarketTags, MarketTagsVariables>(
|
||||
MARKET_TAGS_QUERY,
|
||||
{
|
||||
variables: { marketId: market.id },
|
||||
}
|
||||
);
|
||||
const estMargin = useOrderMargin({
|
||||
order,
|
||||
market,
|
||||
partyId: keypair?.pub || '',
|
||||
});
|
||||
const estCloseOut = useOrderCloseOut({ order, market, partyData });
|
||||
|
||||
return (
|
||||
<div className="mb-8 text-black dark:text-white">
|
||||
<KeyValueTable>
|
||||
|
@ -0,0 +1,117 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import useMaximumPositionSize from './use-maximum-position-size';
|
||||
import type { PartyBalanceQuery_party_accounts } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import type { PositionMargin } from './use-market-positions';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
import {
|
||||
VegaWalletOrderSide,
|
||||
VegaWalletOrderTimeInForce,
|
||||
VegaWalletOrderType,
|
||||
} from '@vegaprotocol/wallet';
|
||||
|
||||
const defaultMockMarketPositions = {
|
||||
openVolume: new BigNumber(1),
|
||||
balance: new BigNumber(100000),
|
||||
};
|
||||
|
||||
let mockMarketPositions: PositionMargin | null = defaultMockMarketPositions;
|
||||
|
||||
const mockAccount: PartyBalanceQuery_party_accounts = {
|
||||
__typename: 'Account',
|
||||
type: AccountType.General,
|
||||
balance: '200000',
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
id: '5cfa87844724df6069b94e4c8a6f03af21907d7bc251593d08e4251043ee9f7c',
|
||||
symbol: 'tBTC',
|
||||
name: 'tBTC TEST',
|
||||
decimals: 5,
|
||||
},
|
||||
};
|
||||
|
||||
const mockOrder = {
|
||||
type: VegaWalletOrderType.Market,
|
||||
size: '1',
|
||||
side: VegaWalletOrderSide.Buy,
|
||||
timeInForce: VegaWalletOrderTimeInForce.IOC,
|
||||
};
|
||||
|
||||
jest.mock('./use-settlement-account', () => {
|
||||
return {
|
||||
useSettlementAccount: jest.fn(() => mockAccount),
|
||||
};
|
||||
});
|
||||
jest.mock('./use-market-positions', () => jest.fn(() => mockMarketPositions));
|
||||
|
||||
describe('useMaximumPositionSize Hook', () => {
|
||||
it('should return correct size when no open positions', () => {
|
||||
mockMarketPositions = null;
|
||||
const price = '50';
|
||||
const expected = 4000;
|
||||
const { result } = renderHook(() =>
|
||||
useMaximumPositionSize({
|
||||
marketId: '',
|
||||
partyId: '',
|
||||
price,
|
||||
settlementAssetId: '',
|
||||
order: mockOrder,
|
||||
accounts: [mockAccount],
|
||||
})
|
||||
);
|
||||
expect(result.current).toBe(expected);
|
||||
});
|
||||
|
||||
it('should return correct size when open positions and same side', () => {
|
||||
const price = '50';
|
||||
mockMarketPositions = defaultMockMarketPositions;
|
||||
const expected = 3999;
|
||||
const { result } = renderHook(() =>
|
||||
useMaximumPositionSize({
|
||||
marketId: '',
|
||||
partyId: '',
|
||||
price,
|
||||
settlementAssetId: '',
|
||||
order: mockOrder,
|
||||
accounts: [mockAccount],
|
||||
})
|
||||
);
|
||||
expect(result.current).toBe(expected);
|
||||
});
|
||||
|
||||
it('should return correct size when open positions and opposite side', () => {
|
||||
const price = '50';
|
||||
mockOrder.side = VegaWalletOrderSide.Sell;
|
||||
mockMarketPositions = defaultMockMarketPositions;
|
||||
const expected = 4001;
|
||||
const { result } = renderHook(() =>
|
||||
useMaximumPositionSize({
|
||||
marketId: '',
|
||||
partyId: '',
|
||||
price,
|
||||
settlementAssetId: '',
|
||||
order: mockOrder,
|
||||
accounts: [mockAccount],
|
||||
})
|
||||
);
|
||||
expect(result.current).toBe(expected);
|
||||
});
|
||||
|
||||
it('should return zero if no account balance', () => {
|
||||
mockAccount.balance = '0';
|
||||
const price = '50';
|
||||
mockMarketPositions = defaultMockMarketPositions;
|
||||
const expected = 0;
|
||||
const { result } = renderHook(() =>
|
||||
useMaximumPositionSize({
|
||||
marketId: '',
|
||||
partyId: '',
|
||||
price,
|
||||
settlementAssetId: '',
|
||||
order: mockOrder,
|
||||
accounts: [],
|
||||
})
|
||||
);
|
||||
expect(result.current).toBe(expected);
|
||||
});
|
||||
});
|
@ -0,0 +1,61 @@
|
||||
import useMarketPositions from './use-market-positions';
|
||||
import type { Order } from '@vegaprotocol/orders';
|
||||
import type { PartyBalanceQuery_party_accounts } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
|
||||
import { useSettlementAccount } from './use-settlement-account';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
import { VegaWalletOrderSide } from '@vegaprotocol/wallet';
|
||||
import { BigNumber } from 'bignumber.js';
|
||||
|
||||
interface Props {
|
||||
partyId: string;
|
||||
accounts: PartyBalanceQuery_party_accounts[];
|
||||
marketId: string;
|
||||
price?: string;
|
||||
settlementAssetId: string;
|
||||
order: Order;
|
||||
}
|
||||
|
||||
const getSize = (balance: string, price: string) =>
|
||||
new BigNumber(balance).dividedBy(new BigNumber(price));
|
||||
|
||||
export default ({
|
||||
marketId,
|
||||
accounts,
|
||||
partyId,
|
||||
price,
|
||||
settlementAssetId,
|
||||
order,
|
||||
}: Props): number => {
|
||||
const settlementAccount = useSettlementAccount(
|
||||
settlementAssetId,
|
||||
accounts,
|
||||
AccountType.General
|
||||
);
|
||||
|
||||
const marketPositions = useMarketPositions({ marketId: marketId, partyId });
|
||||
|
||||
if (
|
||||
!settlementAccount?.balance ||
|
||||
new BigNumber(settlementAccount?.balance || 0).isZero()
|
||||
) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const size = getSize(settlementAccount.balance, price || '');
|
||||
|
||||
if (!marketPositions) {
|
||||
return size.toNumber() || 0;
|
||||
}
|
||||
|
||||
const isSameSide =
|
||||
(marketPositions.openVolume.isPositive() &&
|
||||
order.side === VegaWalletOrderSide.Buy) ||
|
||||
(marketPositions.openVolume.isNegative() &&
|
||||
order.side === VegaWalletOrderSide.Sell);
|
||||
|
||||
const adjustedForVolume = new BigNumber(size)[isSameSide ? 'minus' : 'plus'](
|
||||
marketPositions.openVolume
|
||||
);
|
||||
|
||||
return adjustedForVolume.isNegative() ? 0 : adjustedForVolume.toNumber();
|
||||
};
|
@ -1,12 +1,14 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useSettlementAccount } from './use-settlement-account';
|
||||
import type { PartyBalanceQuery_party_accounts } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
|
||||
import { AccountType } from '@vegaprotocol/types';
|
||||
|
||||
describe('useSettlementAccount Hook', () => {
|
||||
it('should filter accounts by settlementAssetId', () => {
|
||||
const accounts: PartyBalanceQuery_party_accounts[] = [
|
||||
{
|
||||
__typename: 'Account',
|
||||
type: AccountType.General,
|
||||
balance: '2000000000000000000000',
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
@ -18,6 +20,7 @@ describe('useSettlementAccount Hook', () => {
|
||||
},
|
||||
{
|
||||
__typename: 'Account',
|
||||
type: AccountType.General,
|
||||
balance: '1000000000',
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
@ -29,6 +32,19 @@ describe('useSettlementAccount Hook', () => {
|
||||
},
|
||||
{
|
||||
__typename: 'Account',
|
||||
type: AccountType.General,
|
||||
balance: '5000000000000000000',
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
id: 'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55',
|
||||
symbol: 'VEGA',
|
||||
name: 'Vega (testnet)',
|
||||
decimals: 18,
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'Account',
|
||||
type: AccountType.Margin,
|
||||
balance: '5000000000000000000',
|
||||
asset: {
|
||||
__typename: 'Asset',
|
||||
@ -39,14 +55,23 @@ describe('useSettlementAccount Hook', () => {
|
||||
},
|
||||
},
|
||||
];
|
||||
const settlementAssetId =
|
||||
const tDAI =
|
||||
'6d9d35f657589e40ddfb448b7ad4a7463b66efb307527fedd2aa7df1bbd5ea61';
|
||||
const vega =
|
||||
'fc7fd956078fb1fc9db5c19b88f0874c4299b2a7639ad05a47a28c0aef291b55';
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useSettlementAccount(settlementAssetId, accounts)
|
||||
const { result: resultDai } = renderHook(() =>
|
||||
useSettlementAccount(tDAI, accounts)
|
||||
);
|
||||
expect(result.current?.balance).toBe(accounts[1].balance);
|
||||
expect(result.current?.asset).toEqual(accounts[1].asset);
|
||||
expect(resultDai.current?.balance).toBe(accounts[1].balance);
|
||||
expect(resultDai.current?.asset).toEqual(accounts[1].asset);
|
||||
|
||||
const { result: resultVega } = renderHook(() =>
|
||||
useSettlementAccount(vega, accounts, AccountType.Margin)
|
||||
);
|
||||
|
||||
expect(resultVega.current?.balance).toBe(accounts[3].balance);
|
||||
expect(resultVega.current?.asset).toEqual(accounts[3].asset);
|
||||
});
|
||||
|
||||
it('should return null if no accounts', () => {
|
||||
|
@ -1,12 +1,20 @@
|
||||
import type { PartyBalanceQuery_party_accounts } from '../components/deal-ticket/__generated__/PartyBalanceQuery';
|
||||
import type { AccountType } from '@vegaprotocol/types';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useSettlementAccount = (
|
||||
settlementAssetId: string,
|
||||
accounts: PartyBalanceQuery_party_accounts[]
|
||||
accounts: PartyBalanceQuery_party_accounts[],
|
||||
type?: AccountType
|
||||
): PartyBalanceQuery_party_accounts | null => {
|
||||
const callback = () =>
|
||||
accounts.find((account) => account.asset.id === settlementAssetId);
|
||||
const account = useMemo(callback, [accounts, settlementAssetId]);
|
||||
accounts.find((account) => {
|
||||
if (type) {
|
||||
return account.asset.id === settlementAssetId && account.type === type;
|
||||
}
|
||||
|
||||
return account.asset.id === settlementAssetId;
|
||||
});
|
||||
const account = useMemo(callback, [accounts, settlementAssetId, type]);
|
||||
return account as PartyBalanceQuery_party_accounts;
|
||||
};
|
||||
|
@ -31,3 +31,4 @@ export * from './theme-switcher';
|
||||
export * from './toggle';
|
||||
export * from './tooltip';
|
||||
export * from './vega-logo';
|
||||
export * from './slider';
|
||||
|
1
libs/ui-toolkit/src/components/slider/index.ts
Normal file
1
libs/ui-toolkit/src/components/slider/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './slider';
|
27
libs/ui-toolkit/src/components/slider/slider.stories.tsx
Normal file
27
libs/ui-toolkit/src/components/slider/slider.stories.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import type { Story, Meta } from '@storybook/react';
|
||||
import { Slider } from './slider';
|
||||
import { useState } from 'react';
|
||||
|
||||
export default {
|
||||
component: Slider,
|
||||
title: 'Slider',
|
||||
} as Meta;
|
||||
|
||||
const Template: Story = ({ value: val, ...args }) => {
|
||||
const [value, setValue] = useState(val);
|
||||
|
||||
const onValueChange = (val: [number]) => {
|
||||
setValue(val);
|
||||
};
|
||||
|
||||
return <Slider onValueChange={onValueChange} value={value} {...args} />;
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
|
||||
Default.args = {
|
||||
min: 0,
|
||||
max: 1000,
|
||||
step: 100,
|
||||
value: [100],
|
||||
};
|
84
libs/ui-toolkit/src/components/slider/slider.tsx
Normal file
84
libs/ui-toolkit/src/components/slider/slider.tsx
Normal file
@ -0,0 +1,84 @@
|
||||
import React from 'react';
|
||||
import * as SliderPrimitive from '@radix-ui/react-slider';
|
||||
import type {
|
||||
SliderProps,
|
||||
SliderTrackProps,
|
||||
SliderRangeProps,
|
||||
SliderThumbProps,
|
||||
} from '@radix-ui/react-slider';
|
||||
import classNames from 'classnames';
|
||||
|
||||
export const SliderRoot = ({
|
||||
children,
|
||||
className,
|
||||
orientation = 'horizontal',
|
||||
...props
|
||||
}: SliderProps) => {
|
||||
const defaultStyles = 'relative flex items-center select-none touch-none';
|
||||
const classes = classNames(
|
||||
defaultStyles,
|
||||
{
|
||||
'h-[20px] w-full': orientation === 'horizontal',
|
||||
'flex-col w-[20px] h-full': orientation === 'vertical',
|
||||
},
|
||||
className
|
||||
);
|
||||
|
||||
return (
|
||||
<SliderPrimitive.Root
|
||||
orientation={orientation}
|
||||
className={classes}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</SliderPrimitive.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export const SliderTrack = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: SliderTrackProps) => {
|
||||
const defaultStyles = 'bg-black dark:bg-white relative grow h-[3px]';
|
||||
return (
|
||||
<SliderPrimitive.Track
|
||||
className={classNames(defaultStyles, className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</SliderPrimitive.Track>
|
||||
);
|
||||
};
|
||||
|
||||
export const SliderRange = ({ className, ...props }: SliderRangeProps) => {
|
||||
const defaultStyles = 'absolute bg-blue h-full';
|
||||
return (
|
||||
<SliderPrimitive.Range
|
||||
className={classNames(defaultStyles, className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SliderThumb = ({ className, ...props }: SliderThumbProps) => {
|
||||
const defaultStyles =
|
||||
'block w-[20px] h-[20px] border-2 border-black dark:border-white bg-white dark:bg-black rounded-full';
|
||||
return (
|
||||
<SliderPrimitive.Thumb
|
||||
className={classNames(defaultStyles, className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Slider = (props: SliderProps) => {
|
||||
return (
|
||||
<SliderRoot {...props}>
|
||||
<SliderTrack>
|
||||
<SliderRange />
|
||||
</SliderTrack>
|
||||
<SliderThumb />
|
||||
</SliderRoot>
|
||||
);
|
||||
};
|
@ -27,6 +27,7 @@
|
||||
"@radix-ui/react-icons": "^1.1.1",
|
||||
"@radix-ui/react-radio-group": "^0.1.5",
|
||||
"@radix-ui/react-select": "^0.1.1",
|
||||
"@radix-ui/react-slider": "^1.0.0",
|
||||
"@radix-ui/react-tabs": "^0.1.5",
|
||||
"@radix-ui/react-tooltip": "^0.1.7",
|
||||
"@sentry/nextjs": "^6.19.3",
|
||||
|
117
yarn.lock
117
yarn.lock
@ -3281,6 +3281,13 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/number@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.0.0.tgz#4c536161d0de750b3f5d55860fc3de46264f897b"
|
||||
integrity sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/popper@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/popper/-/popper-0.1.0.tgz#c387a38f31b7799e1ea0d2bb1ca0c91c2931b063"
|
||||
@ -3296,6 +3303,13 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/primitive@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.0.tgz#e1d8ef30b10ea10e69c76e896f608d9276352253"
|
||||
integrity sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-accordion@^0.1.6":
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-accordion/-/react-accordion-0.1.6.tgz#b76613d56717ed24b8cf6cb1897cbd54f04714ed"
|
||||
@ -3345,6 +3359,17 @@
|
||||
"@radix-ui/react-primitive" "0.1.4"
|
||||
"@radix-ui/react-slot" "0.1.2"
|
||||
|
||||
"@radix-ui/react-collection@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.0.tgz#0ec4c72fabd35a03b5787075ac799e3b17ca5710"
|
||||
integrity sha512-8i1pf5dKjnq90Z8udnnXKzdCEV3/FYrfw0n/b6NvB6piXEn3fO1bOh7HBcpG8XrnIXzxlYu2oCcR38QpyLS/mg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-context" "1.0.0"
|
||||
"@radix-ui/react-primitive" "1.0.0"
|
||||
"@radix-ui/react-slot" "1.0.0"
|
||||
|
||||
"@radix-ui/react-compose-refs@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-0.1.0.tgz#cff6e780a0f73778b976acff2c2a5b6551caab95"
|
||||
@ -3352,6 +3377,13 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-compose-refs@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz#37595b1f16ec7f228d698590e78eeed18ff218ae"
|
||||
integrity sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-context@0.1.1":
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-0.1.1.tgz#06996829ea124d9a1bc1dbe3e51f33588fab0875"
|
||||
@ -3359,6 +3391,13 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-context@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.0.tgz#f38e30c5859a9fb5e9aa9a9da452ee3ed9e0aee0"
|
||||
integrity sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-dialog@^0.1.5":
|
||||
version "0.1.7"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-0.1.7.tgz#285414cf66f5bbf42bc9935314e0381abe01e7d0"
|
||||
@ -3380,6 +3419,13 @@
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "^2.4.0"
|
||||
|
||||
"@radix-ui/react-direction@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.0.tgz#a2e0b552352459ecf96342c79949dd833c1e6e45"
|
||||
integrity sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-dismissable-layer@0.1.5":
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-0.1.5.tgz#9379032351e79028d472733a5cc8ba4a0ea43314"
|
||||
@ -3513,6 +3559,14 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-slot" "0.1.2"
|
||||
|
||||
"@radix-ui/react-primitive@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz#376cd72b0fcd5e0e04d252ed33eb1b1f025af2b0"
|
||||
integrity sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-slot" "1.0.0"
|
||||
|
||||
"@radix-ui/react-radio-group@^0.1.5":
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-radio-group/-/react-radio-group-0.1.5.tgz#ca8a676123a18b44804aff10af46129e2c2b37c3"
|
||||
@ -3570,6 +3624,24 @@
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "^2.4.0"
|
||||
|
||||
"@radix-ui/react-slider@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-slider/-/react-slider-1.0.0.tgz#4cabadd243aa088eb45ac710cd7cdc518fafb07e"
|
||||
integrity sha512-LMZET7vn7HYwYSjsc9Jcen8Vn4cJXZZxQT7T+lGlqp+F+FofX+H86TBF2yDq+L51d99f1KLEsflTGBz9WRLSig==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/number" "1.0.0"
|
||||
"@radix-ui/primitive" "1.0.0"
|
||||
"@radix-ui/react-collection" "1.0.0"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
"@radix-ui/react-context" "1.0.0"
|
||||
"@radix-ui/react-direction" "1.0.0"
|
||||
"@radix-ui/react-primitive" "1.0.0"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.0"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.0"
|
||||
"@radix-ui/react-use-previous" "1.0.0"
|
||||
"@radix-ui/react-use-size" "1.0.0"
|
||||
|
||||
"@radix-ui/react-slot@0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-0.1.2.tgz#e6f7ad9caa8ce81cc8d532c854c56f9b8b6307c8"
|
||||
@ -3578,6 +3650,14 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "0.1.0"
|
||||
|
||||
"@radix-ui/react-slot@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.0.tgz#7fa805b99891dea1e862d8f8fbe07f4d6d0fd698"
|
||||
integrity sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "1.0.0"
|
||||
|
||||
"@radix-ui/react-tabs@^0.1.5":
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-0.1.5.tgz#ddcf860cc32e186d76477ae767dbb216d1944252"
|
||||
@ -3627,6 +3707,13 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-callback-ref@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz#9e7b8b6b4946fe3cbe8f748c82a2cce54e7b6a90"
|
||||
integrity sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-controllable-state@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-0.1.0.tgz#4fced164acfc69a4e34fb9d193afdab973a55de1"
|
||||
@ -3635,6 +3722,14 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-callback-ref" "0.1.0"
|
||||
|
||||
"@radix-ui/react-use-controllable-state@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz#a64deaafbbc52d5d407afaa22d493d687c538b7f"
|
||||
integrity sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.0"
|
||||
|
||||
"@radix-ui/react-use-direction@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-direction/-/react-use-direction-0.1.0.tgz#97ac1d52e497c974389e7988f809238ed72e7df7"
|
||||
@ -3657,6 +3752,13 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-layout-effect@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz#2fc19e97223a81de64cd3ba1dc42ceffd82374dc"
|
||||
integrity sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-previous@0.1.1":
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-0.1.1.tgz#0226017f72267200f6e832a7103760e96a6db5d0"
|
||||
@ -3664,6 +3766,13 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-previous@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-1.0.0.tgz#e48a69c3a7d8078a967084038df66d0d181c56ac"
|
||||
integrity sha512-RG2K8z/K7InnOKpq6YLDmT49HGjNmrK+fr82UCVKT2sW0GYfVnYp4wZWBooT/EYfQ5faA9uIjvsuMMhH61rheg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-rect@0.1.1":
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-0.1.1.tgz#6c15384beee59c086e75b89a7e66f3d2e583a856"
|
||||
@ -3679,6 +3788,14 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-size@1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.0.0.tgz#a0b455ac826749419f6354dc733e2ca465054771"
|
||||
integrity sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.0"
|
||||
|
||||
"@radix-ui/react-visually-hidden@0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.1.4.tgz#6c75eae34fb5d084b503506fbfc05587ced05f03"
|
||||
|
Loading…
Reference in New Issue
Block a user