diff --git a/apps/explorer/src/app/routes/parties/id/Party-assets.graphql b/apps/explorer/src/app/routes/parties/id/Party-assets.graphql
index 72ae0fa38..1021cfc78 100644
--- a/apps/explorer/src/app/routes/parties/id/Party-assets.graphql
+++ b/apps/explorer/src/app/routes/parties/id/Party-assets.graphql
@@ -48,9 +48,11 @@ query ExplorerPartyAssets($partyId: ID!) {
}
stakingSummary {
currentStakeAvailable
- linkings(pagination: { first: 100 }) {
+ linkings(pagination: { last: 100 }) {
edges {
node {
+ type
+ status
amount
}
}
diff --git a/apps/explorer/src/app/routes/parties/id/__generated__/Party-assets.ts b/apps/explorer/src/app/routes/parties/id/__generated__/Party-assets.ts
index 77e334201..dfe632305 100644
--- a/apps/explorer/src/app/routes/parties/id/__generated__/Party-assets.ts
+++ b/apps/explorer/src/app/routes/parties/id/__generated__/Party-assets.ts
@@ -10,7 +10,7 @@ export type ExplorerPartyAssetsQueryVariables = Types.Exact<{
}>;
-export type ExplorerPartyAssetsQuery = { __typename?: 'Query', partiesConnection?: { __typename?: 'PartyConnection', edges: Array<{ __typename?: 'PartyEdge', node: { __typename?: 'Party', id: string, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string, name: string } } } | null> | null } | null, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string, linkings: { __typename?: 'StakesConnection', edges?: Array<{ __typename?: 'StakeLinkingEdge', node: { __typename?: 'StakeLinking', amount: string } } | null> | null } }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } | null } } | null> | null } | null } }> } | null };
+export type ExplorerPartyAssetsQuery = { __typename?: 'Query', partiesConnection?: { __typename?: 'PartyConnection', edges: Array<{ __typename?: 'PartyEdge', node: { __typename?: 'Party', id: string, delegationsConnection?: { __typename?: 'DelegationsConnection', edges?: Array<{ __typename?: 'DelegationEdge', node: { __typename?: 'Delegation', amount: string, epoch: number, node: { __typename?: 'Node', id: string, name: string } } } | null> | null } | null, stakingSummary: { __typename?: 'StakingSummary', currentStakeAvailable: string, linkings: { __typename?: 'StakesConnection', edges?: Array<{ __typename?: 'StakeLinkingEdge', node: { __typename?: 'StakeLinking', type: Types.StakeLinkingType, status: Types.StakeLinkingStatus, amount: string } } | null> | null } }, accountsConnection?: { __typename?: 'AccountsConnection', edges?: Array<{ __typename?: 'AccountEdge', node: { __typename?: 'AccountBalance', type: Types.AccountType, balance: string, asset: { __typename?: 'Asset', name: string, id: string, decimals: number, symbol: string, source: { __typename: 'BuiltinAsset' } | { __typename: 'ERC20', contractAddress: string } }, market?: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } | null } } | null> | null } | null } }> } | null };
export const ExplorerPartyAssetsAccountsFragmentDoc = gql`
fragment ExplorerPartyAssetsAccounts on AccountBalance {
@@ -64,9 +64,11 @@ export const ExplorerPartyAssetsDocument = gql`
}
stakingSummary {
currentStakeAvailable
- linkings(pagination: {first: 100}) {
+ linkings(pagination: {last: 100}) {
edges {
node {
+ type
+ status
amount
}
}
diff --git a/apps/explorer/src/app/routes/parties/id/components/party-block-stake.tsx b/apps/explorer/src/app/routes/parties/id/components/party-block-stake.tsx
index 831c15ccc..6b45e73a5 100644
--- a/apps/explorer/src/app/routes/parties/id/components/party-block-stake.tsx
+++ b/apps/explorer/src/app/routes/parties/id/components/party-block-stake.tsx
@@ -42,9 +42,15 @@ export const PartyBlockStake = ({
linkedLength && linkedLength > 0
? p?.stakingSummary?.linkings?.edges
?.reduce((total, e) => {
- return new BigNumber(total).plus(
- new BigNumber(e?.node.amount || 0)
- );
+ const accumulator = new BigNumber(total);
+ const diff = new BigNumber(e?.node.amount || 0);
+ if (e?.node.type === 'TYPE_LINK') {
+ return accumulator.plus(diff);
+ } else if (e?.node.type === 'TYPE_UNLINK') {
+ return accumulator.minus(diff);
+ } else {
+ return accumulator;
+ }
}, new BigNumber(0))
.toString()
: '0';
diff --git a/apps/governance-e2e/src/integration/flow/proposal-details.cy.ts b/apps/governance-e2e/src/integration/flow/proposal-details.cy.ts
index cef462d61..13b7e19bb 100644
--- a/apps/governance-e2e/src/integration/flow/proposal-details.cy.ts
+++ b/apps/governance-e2e/src/integration/flow/proposal-details.cy.ts
@@ -361,6 +361,34 @@ describe(
stakingPageDisassociateAllTokens();
});
+ it('Error message should be displayed if error returned from wallet when voting', function () {
+ const errorMsg =
+ 'Application error: party has already submitted the maximum number of transactions of this type per epoch (3)';
+
+ createRawProposal();
+ cy.get('@rawProposal').then((rawProposal) => {
+ getProposalFromTitle(rawProposal.rationale.title).within(() =>
+ cy.getByTestId(viewProposalButton).click()
+ );
+ cy.intercept('POST', '/api/v2/requests', {
+ jsonrpc: '2.0',
+ error: {
+ code: 2001,
+ message: 'Application error',
+ data: 'party has already submitted the maximum number of transactions of this type per epoch (3)',
+ },
+ id: '-PK5EGmErnjLhAmzMeclC',
+ });
+ cy.contains('Vote breakdown').should('be.visible', { timeout: 10000 });
+ cy.getByTestId('vote-buttons').contains('for').click();
+ cy.getByTestId('dialog-title').should(
+ 'have.text',
+ 'Transaction failed'
+ );
+ cy.getByTestId('Error').should('have.text', errorMsg);
+ });
+ });
+
it('Able to see successor market details with new and updated values', function () {
cy.createMarket();
cy.reload();
diff --git a/apps/governance-e2e/src/integration/view/home.cy.ts b/apps/governance-e2e/src/integration/view/home.cy.ts
index a84b15846..dc080b0ad 100644
--- a/apps/governance-e2e/src/integration/view/home.cy.ts
+++ b/apps/governance-e2e/src/integration/view/home.cy.ts
@@ -119,6 +119,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
});
});
+ // 0006-NETW-001 0006-NETW-002
it('should display network data', function () {
cy.getByTestId('git-network-data')
.should('contain.text', 'Reading network data from')
@@ -130,6 +131,37 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
});
});
+ // 0006-NETW-003 0006-NETW-008 0006-NETW-009 0006-NETW-010 0006-NETW-012 0006-NETW-013 0006-NETW-017 0006-NETW-018 0006-NETW-019 0006-NETW-020
+ it('should have option to switch to different network node', function () {
+ cy.getByTestId('git-network-data').within(() => {
+ cy.getByTestId('link').click();
+ });
+ cy.getByTestId('node-row').within(() => {
+ cy.getByTestId('node-url-0')
+ .parent()
+ .should('have.text', 'http://localhost:3008/graphql');
+ cy.getByTestId('response-time-cell')
+ .invoke('text')
+ .should('not.be.empty')
+ .and('not.eq', 'Checking');
+ cy.getByTestId('block-height-cell')
+ .invoke('text')
+ .should('not.be.empty')
+ .then((currentBlockHeight) => {
+ // Check that block height updates automatically
+ cy.getByTestId('block-height-cell')
+ .invoke('text')
+ .should('not.eq', currentBlockHeight);
+ });
+ cy.getByTestId('subscription-cell').should('have.text', 'Yes');
+ });
+ cy.getByTestId('connect').should('be.disabled');
+ cy.getByTestId('node-url-custom').click();
+ cy.get('input').should('exist');
+ cy.getByTestId('connect').should('be.disabled');
+ cy.getByTestId('icon-cross').click();
+ });
+
it('should display eth data', function () {
cy.getByTestId('git-eth-data')
.should('contain.text', 'Reading Ethereum data from')
@@ -138,6 +170,7 @@ context('Home Page - verify elements on page', { tags: '@smoke' }, function () {
});
});
+ // 0006-NETW-011
it('should contain link for known issues on Github', function () {
cy.getByTestId('git-info').within(() => {
cy.contains('Known issues and feedback on')
diff --git a/apps/governance-e2e/src/support/staking.functions.ts b/apps/governance-e2e/src/support/staking.functions.ts
index 4f99e0964..cd198783d 100644
--- a/apps/governance-e2e/src/support/staking.functions.ts
+++ b/apps/governance-e2e/src/support/staking.functions.ts
@@ -183,9 +183,8 @@ export function clickOnValidatorFromList(
cy.get(`[row-id="${validatorNumber}"]`)
.should('be.visible')
.first()
- .within(() => {
- cy.get(stakeValidatorListName).click();
- });
+ .as('validatorOnList');
+ cy.get('@validatorOnList').click();
}
}
diff --git a/apps/governance/src/routes/proposals/components/proposal/proposal.tsx b/apps/governance/src/routes/proposals/components/proposal/proposal.tsx
index 076ac0fd5..64790df1f 100644
--- a/apps/governance/src/routes/proposals/components/proposal/proposal.tsx
+++ b/apps/governance/src/routes/proposals/components/proposal/proposal.tsx
@@ -55,7 +55,7 @@ export const Proposal = ({
mostRecentlyEnactedAssociatedMarketProposal,
}: ProposalProps) => {
const { t } = useTranslation();
- const { submit, Dialog, finalizedVote } = useVoteSubmit();
+ const { submit, Dialog, finalizedVote, transaction } = useVoteSubmit();
const { voteState, voteDatetime } = useUserVote(proposal?.id, finalizedVote);
if (!proposal) {
@@ -215,6 +215,7 @@ export const Proposal = ({
}
submit={submit}
dialog={Dialog}
+ transaction={transaction}
voteState={voteState}
voteDatetime={voteDatetime}
/>
diff --git a/apps/governance/src/routes/proposals/components/vote-details/vega-transaction-dialog.spec.tsx b/apps/governance/src/routes/proposals/components/vote-details/vega-transaction-dialog.spec.tsx
new file mode 100644
index 000000000..6ef513333
--- /dev/null
+++ b/apps/governance/src/routes/proposals/components/vote-details/vega-transaction-dialog.spec.tsx
@@ -0,0 +1,110 @@
+import { render, screen } from '@testing-library/react';
+import { VoteTransactionDialog } from './vote-transaction-dialog';
+import { VoteState } from './use-user-vote';
+import { VegaTxStatus } from '@vegaprotocol/wallet';
+
+describe('VoteTransactionDialog', () => {
+ const mockTransactionDialog = jest.fn(({ title, content }) => (
+
+
{title}
+
{content?.Complete}
+
+ ));
+
+ it('renders without crashing', () => {
+ render(
+
+ );
+
+ expect(screen.getByTestId('vote-transaction-dialog')).toBeInTheDocument();
+ });
+
+ it('renders with txRequested title when voteState is Requested', () => {
+ render(
+
+ );
+
+ expect(screen.getByText('txRequested')).toBeInTheDocument();
+ });
+
+ it('renders with votePending title when voteState is Pending', () => {
+ render(
+
+ );
+
+ expect(screen.getByText('votePending')).toBeInTheDocument();
+ });
+
+ it('renders with no title when voteState is neither Requested nor Pending', () => {
+ render(
+
+ );
+
+ expect(screen.queryByText('txRequested')).not.toBeInTheDocument();
+ expect(screen.queryByText('votePending')).not.toBeInTheDocument();
+ });
+
+ it('renders custom error message when voteState is Failed and error message exists', () => {
+ render(
+
+ );
+
+ expect(screen.getByText('Custom error test message')).toBeInTheDocument();
+ });
+
+ it('renders default error message when voteState is failed and no error message exists on the tx', () => {
+ render(
+
+ );
+
+ expect(screen.getByText('voteError')).toBeInTheDocument();
+ });
+
+ it('renders default ui (i.e. not error) when not in a failed state', () => {
+ render(
+
+ );
+
+ expect(screen.queryByText('voteError')).not.toBeInTheDocument();
+ });
+});
diff --git a/apps/governance/src/routes/proposals/components/vote-details/vote-buttons.spec.tsx b/apps/governance/src/routes/proposals/components/vote-details/vote-buttons.spec.tsx
index 7f1ae36b6..9eab79c44 100644
--- a/apps/governance/src/routes/proposals/components/vote-details/vote-buttons.spec.tsx
+++ b/apps/governance/src/routes/proposals/components/vote-details/vote-buttons.spec.tsx
@@ -1,4 +1,4 @@
-import { render, screen, fireEvent } from '@testing-library/react';
+import { fireEvent, render, screen } from '@testing-library/react';
import BigNumber from 'bignumber.js';
import { VoteButtons } from './vote-buttons';
import { VoteState } from './use-user-vote';
@@ -24,6 +24,7 @@ describe('Vote buttons', () => {
currentStakeAvailable={new BigNumber(1)}
dialog={() => Blah
}
submit={() => Promise.resolve()}
+ transaction={null}
/>
@@ -47,6 +48,7 @@ describe('Vote buttons', () => {
currentStakeAvailable={new BigNumber(1)}
dialog={() => Blah
}
submit={() => Promise.resolve()}
+ transaction={null}
/>
@@ -81,6 +83,7 @@ describe('Vote buttons', () => {
currentStakeAvailable={new BigNumber(1)}
dialog={() => Blah
}
submit={() => Promise.resolve()}
+ transaction={null}
/>
@@ -105,6 +108,7 @@ describe('Vote buttons', () => {
currentStakeAvailable={new BigNumber(0)}
dialog={() => Blah
}
submit={() => Promise.resolve()}
+ transaction={null}
/>
@@ -132,6 +136,7 @@ describe('Vote buttons', () => {
currentStakeAvailable={new BigNumber(1)}
dialog={() => Blah
}
submit={() => Promise.resolve()}
+ transaction={null}
/>
@@ -159,6 +164,7 @@ describe('Vote buttons', () => {
currentStakeAvailable={new BigNumber(10)}
dialog={() => Blah
}
submit={() => Promise.resolve()}
+ transaction={null}
/>
@@ -183,6 +189,7 @@ describe('Vote buttons', () => {
currentStakeAvailable={new BigNumber(10)}
dialog={() => Blah
}
submit={() => Promise.resolve()}
+ transaction={null}
/>
diff --git a/apps/governance/src/routes/proposals/components/vote-details/vote-buttons.tsx b/apps/governance/src/routes/proposals/components/vote-details/vote-buttons.tsx
index 919c6b423..7199c9365 100644
--- a/apps/governance/src/routes/proposals/components/vote-details/vote-buttons.tsx
+++ b/apps/governance/src/routes/proposals/components/vote-details/vote-buttons.tsx
@@ -17,7 +17,7 @@ import { VoteState } from './use-user-vote';
import { ProposalMinRequirements, ProposalUserAction } from '../shared';
import { VoteTransactionDialog } from './vote-transaction-dialog';
import { useVoteButtonsQuery } from './__generated__/Stake';
-import type { DialogProps } from '@vegaprotocol/wallet';
+import type { DialogProps, VegaTxState } from '@vegaprotocol/wallet';
interface VoteButtonsContainerProps {
voteState: VoteState | null;
@@ -27,6 +27,7 @@ interface VoteButtonsContainerProps {
minVoterBalance: string | null | undefined;
spamProtectionMinTokens: string | null | undefined;
submit: (voteValue: VoteValue, proposalId: string | null) => Promise;
+ transaction: VegaTxState | null;
dialog: (props: DialogProps) => JSX.Element;
className?: string;
}
@@ -67,6 +68,7 @@ export const VoteButtons = ({
minVoterBalance,
spamProtectionMinTokens,
submit,
+ transaction,
dialog: Dialog,
}: VoteButtonsProps) => {
const { t } = useTranslation();
@@ -208,7 +210,11 @@ export const VoteButtons = ({
)
)}
-
+
>
);
};
diff --git a/apps/governance/src/routes/proposals/components/vote-details/vote-details.tsx b/apps/governance/src/routes/proposals/components/vote-details/vote-details.tsx
index db36adfce..b8fbb7c0a 100644
--- a/apps/governance/src/routes/proposals/components/vote-details/vote-details.tsx
+++ b/apps/governance/src/routes/proposals/components/vote-details/vote-details.tsx
@@ -12,7 +12,7 @@ import { VoteButtonsContainer } from './vote-buttons';
import { SubHeading } from '../../../../components/heading';
import { ProposalType } from '../proposal/proposal';
import type { VoteValue } from '@vegaprotocol/types';
-import type { DialogProps } from '@vegaprotocol/wallet';
+import type { DialogProps, VegaTxState } from '@vegaprotocol/wallet';
import type { ProposalFieldsFragment } from '../../proposals/__generated__/Proposals';
import type { ProposalQuery } from '../../proposal/__generated__/Proposal';
import type { VoteState } from './use-user-vote';
@@ -22,6 +22,7 @@ interface VoteDetailsProps {
minVoterBalance: string | null | undefined;
spamProtectionMinTokens: string | null | undefined;
proposalType: ProposalType | null;
+ transaction: VegaTxState | null;
submit: (voteValue: VoteValue, proposalId: string | null) => Promise;
dialog: (props: DialogProps) => JSX.Element;
voteState: VoteState | null;
@@ -34,6 +35,7 @@ export const VoteDetails = ({
spamProtectionMinTokens,
proposalType,
submit,
+ transaction,
dialog,
voteState,
voteDatetime,
@@ -228,6 +230,7 @@ export const VoteDetails = ({
spamProtectionMinTokens={spamProtectionMinTokens}
className="flex"
submit={submit}
+ transaction={transaction}
dialog={dialog}
/>
)
diff --git a/apps/governance/src/routes/proposals/components/vote-details/vote-transaction-dialog.tsx b/apps/governance/src/routes/proposals/components/vote-details/vote-transaction-dialog.tsx
index 59f04d705..c0d9b3da9 100644
--- a/apps/governance/src/routes/proposals/components/vote-details/vote-transaction-dialog.tsx
+++ b/apps/governance/src/routes/proposals/components/vote-details/vote-transaction-dialog.tsx
@@ -1,9 +1,10 @@
import { t } from '@vegaprotocol/i18n';
import { VoteState } from './use-user-vote';
-import type { DialogProps } from '@vegaprotocol/wallet';
+import type { DialogProps, VegaTxState } from '@vegaprotocol/wallet';
interface VoteTransactionDialogProps {
voteState: VoteState;
+ transaction: VegaTxState | null;
TransactionDialog: (props: DialogProps) => JSX.Element;
}
@@ -20,12 +21,15 @@ const dialogTitle = (voteState: VoteState): string | undefined => {
export const VoteTransactionDialog = ({
voteState,
+ transaction,
TransactionDialog,
}: VoteTransactionDialogProps) => {
// Render a custom message if the voting fails otherwise
// pass undefined so that the default vega transaction dialog UI gets used
const customMessage =
- voteState === VoteState.Failed ? {t('voteError')}
: undefined;
+ voteState === VoteState.Failed ? (
+ {transaction?.error?.message || t('voteError')}
+ ) : undefined;
return (
diff --git a/apps/trading-e2e/src/integration/closed-markets.cy.ts b/apps/trading-e2e/src/integration/closed-markets.cy.ts
index d689963d8..f0861de49 100644
--- a/apps/trading-e2e/src/integration/closed-markets.cy.ts
+++ b/apps/trading-e2e/src/integration/closed-markets.cy.ts
@@ -259,6 +259,12 @@ describe('Closed markets', { tags: '@smoke' }, () => {
.find('[data-testid="market-code"]')
.should('have.text', settledMarket.tradableInstrument.instrument.code);
+ // 6001-MARK-071
+ cy.get(rowSelector)
+ .first()
+ .find('[title="Future"]')
+ .should('have.text', 'Futr');
+
// 6001-MARK-002
cy.get(rowSelector)
.first()
diff --git a/apps/trading-e2e/src/integration/market-all.cy.ts b/apps/trading-e2e/src/integration/market-all.cy.ts
index 83183f3bc..f6c2c5f91 100644
--- a/apps/trading-e2e/src/integration/market-all.cy.ts
+++ b/apps/trading-e2e/src/integration/market-all.cy.ts
@@ -69,6 +69,12 @@ describe('markets all table', { tags: '@smoke' }, () => {
.find(colInstrumentCode)
.should('have.text', 'SOLUSD');
+ // 6001-MARK-073
+ cy.get(rowSelector)
+ .first()
+ .find('[title="Future"]')
+ .should('have.text', 'Futr');
+
// 6001-MARK-036
cy.get(rowSelector)
.first()
diff --git a/apps/trading-e2e/src/integration/markets-proposed.cy.ts b/apps/trading-e2e/src/integration/markets-proposed.cy.ts
index 5c8057477..7b8acd537 100644
--- a/apps/trading-e2e/src/integration/markets-proposed.cy.ts
+++ b/apps/trading-e2e/src/integration/markets-proposed.cy.ts
@@ -45,6 +45,12 @@ describe('markets proposed table', { tags: '@smoke' }, () => {
.find('[col-id="description"]')
.should('have.text', 'ETHUSD');
+ // 6001-MARK-074
+ cy.get(rowSelector)
+ .first()
+ .find('[title="Future"]')
+ .should('have.text', 'Futr');
+
// 6001-MARK-051
cy.get(rowSelector)
.first()
diff --git a/apps/trading-e2e/src/integration/settings.cy.ts b/apps/trading-e2e/src/integration/settings.cy.ts
deleted file mode 100644
index a09223e5e..000000000
--- a/apps/trading-e2e/src/integration/settings.cy.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-describe('Settings page', { tags: '@smoke' }, () => {
- beforeEach(() => {
- cy.clearLocalStorage();
-
- cy.mockTradingPage();
- cy.mockSubscription();
- cy.setOnBoardingViewed();
- cy.visit('/');
-
- // Only click if not already active otherwise sidebar will close
- cy.get('[data-testid="sidebar-content"]').then(($sidebarContent) => {
- if ($sidebarContent.find('h2').text() !== 'Settings') {
- cy.get('[data-testid="sidebar"] [data-testid="Settings"]').click();
- }
- });
- });
-
- it('telemetry checkbox should work well', () => {
- const telemetrySwitch = '#switch-settings-telemetry-switch';
- cy.get(telemetrySwitch).should('have.attr', 'data-state', 'unchecked');
- cy.get(telemetrySwitch).click();
- cy.get(telemetrySwitch).should('have.attr', 'data-state', 'checked');
- cy.reload();
- cy.get(telemetrySwitch).should('have.attr', 'data-state', 'checked');
- cy.get(telemetrySwitch).click();
- cy.get(telemetrySwitch).should('have.attr', 'data-state', 'unchecked');
- cy.reload();
- cy.get(telemetrySwitch).should('have.attr', 'data-state', 'unchecked');
- });
-});
diff --git a/apps/trading-e2e/src/integration/trading-chart.cy.ts b/apps/trading-e2e/src/integration/trading-chart.cy.ts
deleted file mode 100644
index 152ba334d..000000000
--- a/apps/trading-e2e/src/integration/trading-chart.cy.ts
+++ /dev/null
@@ -1,186 +0,0 @@
-interface ItemInfoType {
- name: string;
- infoText: string;
-}
-
-type CheckMenuItemsFnType = (
- triggerSelector: string,
- validTexts: string[],
- clickItem?: string
-) => void;
-type CheckMenuItemCheckboxFnType = (
- buttonText: string,
- items: ItemInfoType[]
-) => void;
-
-const menuItemRadio = 'div[role="menuitemradio"]';
-const menuItemCheckbox = 'div[role="menuitemcheckbox"]';
-const button = 'button';
-const indicatorInfo = '.indicator-info-wrapper';
-
-const checkMenuItems: CheckMenuItemsFnType = (
- triggerSelector,
- validTexts,
- clickItem
-) => {
- cy.get(triggerSelector).click();
-
- cy.get(menuItemRadio)
- .should('have.length', validTexts.length)
- .each(($el, index) => {
- const text = $el.text().trim();
- expect(text).to.equal(validTexts[index]);
- });
-
- if (clickItem) {
- cy.contains(menuItemRadio, clickItem).click();
- cy.get(triggerSelector).click();
- cy.get(`${menuItemRadio}[data-state="checked"]`)
- .invoke('text')
- .then((text: string) => {
- expect(text.trim()).to.equal(clickItem);
- });
- }
-};
-
-const checkMenuItemCheckbox: CheckMenuItemCheckboxFnType = (
- buttonText,
- items
-) => {
- items.forEach((item) => {
- cy.contains(button, buttonText).click();
- cy.contains(menuItemCheckbox, item.name).click();
- });
-
- cy.contains(button, buttonText).click();
- cy.get(menuItemCheckbox)
- .should('have.length', items.length)
- .each(($el, index) => {
- const text = $el.text();
- expect(text).to.equal(items[index].name);
- });
-
- items.forEach((item, index) => {
- cy.get(indicatorInfo)
- .eq(index + 1)
- .invoke('text')
- .should('eq', item.infoText);
- });
-
- cy.contains(button, buttonText).click({ force: true });
-};
-
-function getButtonSelectorByText(text: string): string {
- return `${button}[aria-haspopup="menu"]:contains(${text})`;
-}
-
-beforeEach(() => {
- cy.mockTradingPage();
- cy.mockSubscription();
- cy.setOnBoardingViewed();
- cy.visit('/#/markets/market-0');
- cy.wait('@Markets');
-});
-
-describe(
- 'chart display options',
- { tags: '@smoke', testIsolation: true },
- () => {
- it('change time interval', () => {
- // 6004-CHAR-001
- checkMenuItems(
- getButtonSelectorByText('Interval:'),
- ['1m', '5m', '15m', '1H', '6H', '1D'],
- '1m'
- );
- });
-
- it('change display type', () => {
- // 6004-CHAR-002
- // 6004-CHAR-003
- checkMenuItems(
- '[aria-label$="chart icon"]',
- ['Mountain', 'Candlestick', 'Line', 'OHLC'],
- 'Mountain'
- );
- });
-
- it('Overlays', () => {
- // 6004-CHAR-004
- // 6004-CHAR-008
- // 6004-CHAR-009
- // 6004-CHAR-034
- // 6004-CHAR-037
- // 6004-CHAR-039
- // 6004-CHAR-041
-
- const overlayInfo: ItemInfoType[] = [
- {
- name: 'Bollinger bands',
- infoText: 'Bollinger: Upper 174.78590Lower 173.38014',
- },
- {
- name: 'Envelope',
- infoText: 'Envelope: Upper 191.29000Lower 156.51000',
- },
- { name: 'EMA', infoText: 'EMA: 174.06793' },
- { name: 'Moving average', infoText: 'Moving average: 174.08302' },
- {
- name: 'Price monitoring bounds',
- infoText:
- 'Price Monitoring Bounds 1: Min 162.56291Max 182.96869Reference 172.47489',
- },
- ];
-
- checkMenuItemCheckbox('Overlays', overlayInfo);
- });
-
- it('Studies', () => {
- // 6004-CHAR-005
- // 6004-CHAR-006
- // 6004-CHAR-007
- // 6004-CHAR-042
- // 6004-CHAR-045
- // 6004-CHAR-047
- // 6004-CHAR-049
- // 6004-CHAR-051
- const studyInfo: ItemInfoType[] = [
- {
- name: 'Eldar-ray',
- infoText: 'Eldar-ray: Bull -0.08376Bear -0.58376',
- },
- { name: 'Force index', infoText: 'Force index: 987.48858' },
- { name: 'MACD', infoText: 'MACD: S -0.06420D 0.00359MACD -0.06062' },
- { name: 'RSI', infoText: 'RSI: 47.08648' },
- { name: 'Volume', infoText: 'Volume: 55,000' },
- ];
- cy.get(indicatorInfo).eq(1).realHover();
- cy.get('.chart__wrapper [data-testid="split-view-view"]')
- .last()
- .find('[role="button"][title="Close"]')
- .click({ force: true });
- cy.get(indicatorInfo).should('have.length', 1);
-
- checkMenuItemCheckbox('Studies', studyInfo);
- });
-
- it('price details', () => {
- // 6004-CHAR-010
- const expectedDateRegex = new RegExp(
- /^\d{2}:\d{2} \d{2} [A-Za-z]{3} \d{4}$/
- );
- const expectedOhlc = `O 173.60000H 174.00000L 173.50000C 173.90000Change −0.60000(−0.34%)`;
- cy.get(indicatorInfo)
- .eq(0)
- .invoke('text')
- .then((text) => {
- const actualDate = text.slice(0, -67);
- // eslint-disable-next-line no-console
- console.log(actualDate);
- const actualOhlc = text.slice(-67);
- assert.isTrue(expectedDateRegex.test(actualDate));
- assert.strictEqual(actualOhlc, expectedOhlc);
- });
- });
- }
-);
diff --git a/apps/trading-e2e/src/integration/trading-deal-ticket-basics.cy.ts b/apps/trading-e2e/src/integration/trading-deal-ticket-basics.cy.ts
index 57254632d..0b2d1036a 100644
--- a/apps/trading-e2e/src/integration/trading-deal-ticket-basics.cy.ts
+++ b/apps/trading-e2e/src/integration/trading-deal-ticket-basics.cy.ts
@@ -54,6 +54,15 @@ describe('deal ticket basics', { tags: '@smoke' }, () => {
cy.getByTestId(toggleLimit).next('input').should('be.checked');
cy.getByTestId(orderPriceField).should('have.value', '101');
});
+
+ it('sidebar should be open after reload', () => {
+ cy.mockTradingPage();
+ cy.getByTestId('deal-ticket-form').should('be.visible');
+ cy.getByTestId('Order').click();
+ cy.getByTestId('deal-ticket-form').should('not.exist');
+ cy.reload();
+ cy.getByTestId('deal-ticket-form').should('be.visible');
+ });
});
describe(
diff --git a/apps/trading-e2e/src/integration/trading-orders.cy.ts b/apps/trading-e2e/src/integration/trading-orders.cy.ts
index 31ceb2818..c595f5aab 100644
--- a/apps/trading-e2e/src/integration/trading-orders.cy.ts
+++ b/apps/trading-e2e/src/integration/trading-orders.cy.ts
@@ -222,12 +222,17 @@ describe('subscribe orders', { tags: '@smoke' }, () => {
it('must see a filled order', () => {
// 7002-SORD-046
+ // 7003-MORD-020
// NOT COVERED: Must be able to see/link to all trades that were created from this order
updateOrder({
id: orderId,
status: Schema.OrderStatus.STATUS_FILLED,
});
cy.getByTestId(`order-status-${orderId}`).should('have.text', 'Filled');
+ cy.get('[col-id="market.tradableInstrument.instrument.code"]').contains(
+ '[title="Future"]',
+ 'Futr'
+ );
});
it('must see a rejected order', () => {
diff --git a/apps/trading/client-pages/market/trade-grid.tsx b/apps/trading/client-pages/market/trade-grid.tsx
index b316a3f54..ab64f77b1 100644
--- a/apps/trading/client-pages/market/trade-grid.tsx
+++ b/apps/trading/client-pages/market/trade-grid.tsx
@@ -94,7 +94,11 @@ const MainGrid = memo(
>
-
+ }
+ >
(
diff --git a/apps/trading/components/market-selector/market-selector.tsx b/apps/trading/components/market-selector/market-selector.tsx
index d182a814f..59ec08fc5 100644
--- a/apps/trading/components/market-selector/market-selector.tsx
+++ b/apps/trading/components/market-selector/market-selector.tsx
@@ -2,7 +2,7 @@ import { t } from '@vegaprotocol/i18n';
import uniqBy from 'lodash/uniqBy';
import type { MarketMaybeWithDataAndCandles } from '@vegaprotocol/markets';
import {
- Input,
+ TradingInput,
TinyScroll,
VegaIcon,
VegaIconNames,
@@ -57,7 +57,7 @@ export const MarketSelector = ({
/>
-
setFilter((curr) => ({ ...curr, searchTerm: e.target.value }))
}
diff --git a/apps/trading/components/positions-container/positions-container.tsx b/apps/trading/components/positions-container/positions-container.tsx
index 518f7c2d6..f830d4666 100644
--- a/apps/trading/components/positions-container/positions-container.tsx
+++ b/apps/trading/components/positions-container/positions-container.tsx
@@ -5,6 +5,7 @@ import { Splash } from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import type { DataGridSlice } from '../../stores/datagrid-store-slice';
import { createDataGridSlice } from '../../stores/datagrid-store-slice';
+import type { StateCreator } from 'zustand';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { useMarketClickHandler } from '../../lib/hooks/use-market-click-handler';
@@ -13,6 +14,7 @@ export const PositionsContainer = ({ allKeys }: { allKeys?: boolean }) => {
const onMarketClick = useMarketClickHandler(true);
const { pubKey, pubKeys, isReadOnly } = useVegaWallet();
+ const showClosed = usePositionsStore((store) => store.showClosedMarkets);
const gridStore = usePositionsStore((store) => store.gridStore);
const updateGridStore = usePositionsStore((store) => store.updateGridStore);
const gridStoreCallbacks = useDataGridEvents(gridStore, updateGridStore);
@@ -40,12 +42,35 @@ export const PositionsContainer = ({ allKeys }: { allKeys?: boolean }) => {
onMarketClick={onMarketClick}
isReadOnly={isReadOnly}
gridProps={gridStoreCallbacks}
+ showClosed={showClosed}
/>
);
};
-const usePositionsStore = create
()(
- persist(createDataGridSlice, {
- name: 'vega_positions_store',
- })
+type PositionsStoreSlice = {
+ showClosedMarkets: boolean;
+ toggleClosedMarkets: () => void;
+};
+
+const createPositionStoreSlice: StateCreator = (set) => ({
+ showClosedMarkets: false,
+ toggleClosedMarkets: () => {
+ set((curr) => {
+ return {
+ showClosedMarkets: !curr.showClosedMarkets,
+ };
+ });
+ },
+});
+
+export const usePositionsStore = create()(
+ persist(
+ (...args) => ({
+ ...createPositionStoreSlice(...args),
+ ...createDataGridSlice(...args),
+ }),
+ {
+ name: 'vega_positions_store',
+ }
+ )
);
diff --git a/apps/trading/components/positions-menu/index.ts b/apps/trading/components/positions-menu/index.ts
new file mode 100644
index 000000000..12fc9ea53
--- /dev/null
+++ b/apps/trading/components/positions-menu/index.ts
@@ -0,0 +1 @@
+export * from './positions-menu';
diff --git a/apps/trading/components/positions-menu/positions-menu.tsx b/apps/trading/components/positions-menu/positions-menu.tsx
new file mode 100644
index 000000000..58dddee7b
--- /dev/null
+++ b/apps/trading/components/positions-menu/positions-menu.tsx
@@ -0,0 +1,18 @@
+import { t } from '@vegaprotocol/i18n';
+import { Intent, TradingButton } from '@vegaprotocol/ui-toolkit';
+import { usePositionsStore } from '../positions-container';
+
+export const PositionsMenu = () => {
+ const showClosed = usePositionsStore((store) => store.showClosedMarkets);
+ const toggle = usePositionsStore((store) => store.toggleClosedMarkets);
+ return (
+
+ {showClosed ? t('Hide closed markets') : t('Show closed markets')}
+
+ );
+};
diff --git a/apps/trading/components/sidebar/sidebar.tsx b/apps/trading/components/sidebar/sidebar.tsx
index 2dcf8d4a8..d32d0d8d0 100644
--- a/apps/trading/components/sidebar/sidebar.tsx
+++ b/apps/trading/components/sidebar/sidebar.tsx
@@ -14,12 +14,9 @@ import { Settings } from '../settings';
import { Tooltip } from '../../components/tooltip';
import { WithdrawContainer } from '../withdraw-container';
import { Routes as AppRoutes } from '../../pages/client-router';
-import { persist } from 'zustand/middleware';
import { GetStarted } from '../welcome-dialog';
import { useVegaWallet, useViewAsDialog } from '@vegaprotocol/wallet';
-const STORAGE_KEY = 'vega_sidebar_store';
-
export enum ViewType {
Order = 'Order',
Info = 'Info',
@@ -302,22 +299,14 @@ export const useSidebar = create<{
init: boolean;
view: SidebarView | null;
setView: (view: SidebarView | null) => void;
-}>()(
- persist(
- (set) => ({
- init: true,
- view: null,
- setView: (x) =>
- set(() => {
- if (x == null) {
- return { view: null, init: false };
- }
-
- return { view: x, init: false };
- }),
+}>()((set) => ({
+ init: true,
+ view: null,
+ setView: (x) =>
+ set(() => {
+ if (x == null) {
+ return { view: null, init: false };
+ }
+ return { view: x, init: false };
}),
- {
- name: STORAGE_KEY,
- }
- )
-);
+}));
diff --git a/apps/trading/components/welcome-dialog/telemetry-approval.tsx b/apps/trading/components/welcome-dialog/telemetry-approval.tsx
index c27506dfa..0fd49c983 100644
--- a/apps/trading/components/welcome-dialog/telemetry-approval.tsx
+++ b/apps/trading/components/welcome-dialog/telemetry-approval.tsx
@@ -1,4 +1,4 @@
-import { Checkbox } from '@vegaprotocol/ui-toolkit';
+import { TradingCheckbox } from '@vegaprotocol/ui-toolkit';
import { t } from '@vegaprotocol/i18n';
import { useTelemetryApproval } from '../../lib/hooks/use-telemetry-approval';
@@ -7,7 +7,7 @@ export const TelemetryApproval = ({ helpText }: { helpText: string }) => {
return (
-
{t('Share usage data')}}
checked={isApproved}
name="telemetry-approval"
diff --git a/apps/trading/pages/_document.page.tsx b/apps/trading/pages/_document.page.tsx
index 9a2552e07..53c42fb1a 100644
--- a/apps/trading/pages/_document.page.tsx
+++ b/apps/trading/pages/_document.page.tsx
@@ -1,40 +1,10 @@
-import { Html, Head, Main, NextScript } from 'next/document';
+import { Head, Html, Main, NextScript } from 'next/document';
export default function Document() {
return (
-
+ <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* eslint-disable-next-line @next/next/no-css-tags */}
-
-
-
-
-
-
+
+
+
+
+
+
+ >
);
}
diff --git a/apps/trading/pages/index.page.tsx b/apps/trading/pages/index.page.tsx
index dcc895c04..dda071b4b 100644
--- a/apps/trading/pages/index.page.tsx
+++ b/apps/trading/pages/index.page.tsx
@@ -1,3 +1,4 @@
+import Head from 'next/head';
import { ClientRouter } from './client-router';
/**
@@ -6,5 +7,60 @@ import { ClientRouter } from './client-router';
* have to serve a static site via next export
*/
export default function Index() {
- return ;
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* eslint-disable-next-line @next/next/no-css-tags */}
+
+
+
+
+
+ >
+ );
}
diff --git a/libs/accounts/src/lib/transfer-form.tsx b/libs/accounts/src/lib/transfer-form.tsx
index 7aa0dcc0b..a3e81c4c7 100644
--- a/libs/accounts/src/lib/transfer-form.tsx
+++ b/libs/accounts/src/lib/transfer-form.tsx
@@ -9,13 +9,13 @@ import {
import { t } from '@vegaprotocol/i18n';
import {
Button,
- FormGroup,
- Input,
- InputError,
- RichSelect,
- Select,
+ TradingFormGroup,
+ TradingInput,
+ TradingInputError,
+ TradingRichSelect,
+ TradingSelect,
Tooltip,
- Checkbox,
+ TradingCheckbox,
} from '@vegaprotocol/ui-toolkit';
import type { Transfer } from '@vegaprotocol/wallet';
import { normalizeTransfer } from '@vegaprotocol/wallet';
@@ -130,12 +130,16 @@ export const TransferForm = ({
className="text-sm"
data-testid="transfer-form"
>
-
+
setValue('toAddress', '')}
select={
-
+
{t('Please select')}
@@ -147,10 +151,10 @@ export const TransferForm = ({
{pk}
))}
-
+
}
input={
-
{errors.toAddress?.message && (
-
+
{errors.toAddress.message}
-
+
)}
-
-
+
+
(
-
))}
-
+
)}
/>
{errors.asset?.message && (
- {errors.asset.message}
+
+ {errors.asset.message}
+
)}
-
-
-
+
+
{errors.amount?.message && (
- {errors.amount.message}
+
+ {errors.amount.message}
+
)}
-
+
-
{
return (
-
+
{asset.name} {' '}
@@ -49,6 +49,6 @@ export const AssetOption = ({ asset, balance }: AssetOptionProps) => {
-
+
);
};
diff --git a/libs/datagrid/src/lib/filters/date-range-filter.tsx b/libs/datagrid/src/lib/filters/date-range-filter.tsx
index a2e8d8041..364d8289f 100644
--- a/libs/datagrid/src/lib/filters/date-range-filter.tsx
+++ b/libs/datagrid/src/lib/filters/date-range-filter.tsx
@@ -15,7 +15,7 @@ import {
} from 'date-fns';
import { formatForInput } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
-import { InputError } from '@vegaprotocol/ui-toolkit';
+import { TradingInputError } from '@vegaprotocol/ui-toolkit';
const defaultValue: Schema.DateRange = {};
export interface DateRangeFilterProps extends IFilterParams {
@@ -195,7 +195,7 @@ export const DateRangeFilter = forwardRef(
}, [value, props]);
const notification = useMemo(() => {
- const not = error ? {error} : null;
+ const not = error ? {error} : null;
return (
{not}
);
diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-limit-amount.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-limit-amount.tsx
index 6e307ce4e..327435b88 100644
--- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-limit-amount.tsx
+++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-limit-amount.tsx
@@ -1,4 +1,8 @@
-import { FormGroup, Input, InputError } from '@vegaprotocol/ui-toolkit';
+import {
+ TradingFormGroup,
+ TradingInput,
+ TradingInputError,
+} from '@vegaprotocol/ui-toolkit';
import { toDecimal, validateAmount } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import type { DealTicketAmountProps } from './deal-ticket-amount';
@@ -22,17 +26,17 @@ export const DealTicketLimitAmount = ({
const renderError = () => {
if (sizeError) {
return (
-
+
{sizeError}
-
+
);
}
if (priceError) {
return (
-
+
{priceError}
-
+
);
}
@@ -43,7 +47,7 @@ export const DealTicketLimitAmount = ({
- (
- (
+ e.currentTarget.blur()}
+ hasError={!!fieldState.error}
{...field}
/>
)}
/>
-
+
-
@
+
@
- (
- (
+ e.currentTarget.blur()}
+ hasError={!!fieldState.error}
{...field}
/>
)}
/>
-
+
{renderError()}
diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-market-amount.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-market-amount.tsx
index 9b3e77e66..82e003364 100644
--- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-market-amount.tsx
+++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-market-amount.tsx
@@ -4,7 +4,11 @@ import {
validateAmount,
} from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
-import { Input, InputError, Tooltip } from '@vegaprotocol/ui-toolkit';
+import {
+ TradingInput,
+ TradingInputError,
+ Tooltip,
+} from '@vegaprotocol/ui-toolkit';
import { isMarketInAuction } from '@vegaprotocol/markets';
import type { DealTicketAmountProps } from './deal-ticket-amount';
import { Controller } from 'react-hook-form';
@@ -33,7 +37,7 @@ export const DealTicketMarketAmount = ({
-
{t('Size')}
+
{t('Size')}
(
- (
+ e.currentTarget.blur()}
data-testid="order-size"
+ hasError={!!fieldState.error}
{...field}
/>
)}
/>
-
@
+
@
{inAuction && (
{priceFormatted && quoteName ? (
<>
@@ -85,12 +90,12 @@ export const DealTicketMarketAmount = ({
{sizeError && (
-
{sizeError}
-
+
)}
);
diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-size-iceberg.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-size-iceberg.tsx
index 40a5480d3..1b69cac2b 100644
--- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-size-iceberg.tsx
+++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-size-iceberg.tsx
@@ -4,9 +4,9 @@ import type { OrderFormValues } from '../../hooks/use-form-values';
import { toDecimal, validateAmount } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import {
- FormGroup,
- Input,
- InputError,
+ TradingFormGroup,
+ TradingInput,
+ TradingInputError,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
@@ -32,9 +32,9 @@ export const DealTicketSizeIceberg = ({
const renderPeakSizeError = () => {
if (peakSizeError) {
return (
-
+
{peakSizeError}
-
+
);
}
@@ -44,9 +44,9 @@ export const DealTicketSizeIceberg = ({
const renderMinimumSizeError = () => {
if (minimumVisibleSizeError) {
return (
-
+
{minimumVisibleSizeError}
-
+
);
}
@@ -57,7 +57,7 @@ export const DealTicketSizeIceberg = ({
{renderPeakSizeError()}
diff --git a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx
index 5e048d14e..dbe61bdf3 100644
--- a/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx
+++ b/libs/deal-ticket/src/components/deal-ticket/deal-ticket-stop-order.tsx
@@ -10,13 +10,13 @@ import {
import { useForm, Controller, useController } from 'react-hook-form';
import * as Schema from '@vegaprotocol/types';
import {
- Radio,
- RadioGroup,
- Input,
- Checkbox,
- FormGroup,
- InputError,
- Select,
+ TradingRadio,
+ TradingRadioGroup,
+ TradingInput,
+ TradingCheckbox,
+ TradingFormGroup,
+ TradingInputError,
+ TradingSelect,
Tooltip,
} from '@vegaprotocol/ui-toolkit';
import { getDerivedPrice, type Market } from '@vegaprotocol/markets';
@@ -187,9 +187,9 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
}}
/>
{errors.type && (
-
+
{errors.type.message}
-
+
)}
{
)}
/>
-
+
{
const { onChange, value } = field;
return (
-
- {
id="triggerDirection-risesAbove"
label={'Rises above'}
/>
- {
id="triggerDirection-fallsBelow"
label={'Falls below'}
/>
-
+
);
}}
/>
@@ -246,16 +246,17 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
validate: validateAmount(priceStep, 'Price'),
}}
control={control}
- render={({ field }) => {
+ render={({ field, fieldState }) => {
const { value, ...props } = field;
return (
-
@@ -263,9 +264,9 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
}}
/>
{errors.triggerPrice && (
-
+
{errors.triggerPrice.message}
-
+
)}
)}
@@ -294,16 +295,17 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
'Trailing percentage offset'
),
}}
- render={({ field }) => {
+ render={({ field, fieldState }) => {
const { value, ...props } = field;
return (
-
@@ -311,9 +313,9 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
}}
/>
{errors.triggerTrailingPercentOffset && (
-
+
{errors.triggerTrailingPercentOffset.message}
-
+
)}
)}
@@ -324,25 +326,29 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
render={({ field }) => {
const { onChange, value } = field;
return (
-
-
-
+
-
+
);
}}
/>
-
+
-
{
},
validate: validateAmount(sizeStep, 'Size'),
}}
- render={({ field }) => {
+ render={({ field, fieldState }) => {
const { value, ...props } = field;
return (
- {
onWheel={(e) => e.currentTarget.blur()}
data-testid="order-size"
value={value || ''}
+ hasError={!!fieldState.error}
{...props}
/>
);
}}
/>
-
-
@
+
+
@
{type === Schema.OrderType.TYPE_LIMIT ? (
-
{
},
validate: validateAmount(priceStep, 'Price'),
}}
- render={({ field }) => {
+ render={({ field, fieldState }) => {
const { value, ...props } = field;
return (
- {
data-testid="order-price"
onWheel={(e) => e.currentTarget.blur()}
value={value || ''}
+ hasError={!!fieldState.error}
{...props}
/>
);
}}
/>
-
+
) : (
{priceFormatted && quoteName
@@ -427,21 +435,21 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
{errors.size && (
-
+
{errors.size.message}
-
+
)}
{!errors.size &&
errors.price &&
type === Schema.OrderType.TYPE_LIMIT && (
-
+
{errors.price.message}
-
+
)}
- {
(
- (
+
{
>
{timeInForceLabel(Schema.OrderTimeInForce.TIME_IN_FORCE_FOK)}
-
+
)}
/>
-
+
{errors.timeInForce && (
-
+
{errors.timeInForce.message}
-
+
)}
@@ -485,29 +494,29 @@ export const StopOrder = ({ market, marketPrice, submit }: StopOrderProps) => {
render={({ field }) => {
const { onChange: onCheckedChange, value } = field;
return (
- {t('Expire')}}
+ label={t('Expire')}
/>
);
}}
/>
- {t(REDUCE_ONLY_TOOLTIP)}}>
- {t('Reduce only')}
+ <>{t('Reduce only')}>
}
/>
{expire && (
<>
-
{
control={control}
render={({ field }) => {
return (
-
-
+
-
-
+
);
}}
/>
-
+
(
- (
- (
-
-
+
{
'You need to connect your own wallet to start trading on this market'
}
-
+
);
}
@@ -613,9 +613,9 @@ const SummaryMessage = memo(
if (error?.message) {
return (
-
+
{error?.message}
-
+
);
}
diff --git a/libs/deal-ticket/src/components/deal-ticket/expiry-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/expiry-selector.tsx
index 811edc9b4..b174fcbda 100644
--- a/libs/deal-ticket/src/components/deal-ticket/expiry-selector.tsx
+++ b/libs/deal-ticket/src/components/deal-ticket/expiry-selector.tsx
@@ -1,4 +1,8 @@
-import { FormGroup, Input, InputError } from '@vegaprotocol/ui-toolkit';
+import {
+ TradingFormGroup,
+ TradingInput,
+ TradingInputError,
+} from '@vegaprotocol/ui-toolkit';
import { formatForInput } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n';
import { useRef } from 'react';
@@ -19,24 +23,25 @@ export const ExpirySelector = ({
const dateFormatted = formatForInput(date);
const minDate = formatForInput(date);
return (
-
- onSelect(e.target.value)}
min={minDate}
+ hasError={!!errorMessage}
/>
{errorMessage && (
-
+
{errorMessage}
-
+
)}
-
+
);
};
diff --git a/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx
index 8d1b09f3e..7fefd689c 100644
--- a/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx
+++ b/libs/deal-ticket/src/components/deal-ticket/time-in-force-selector.tsx
@@ -1,7 +1,7 @@
import {
- FormGroup,
- InputError,
- Select,
+ TradingFormGroup,
+ TradingInputError,
+ TradingSelect,
Tooltip,
SimpleGrid,
} from '@vegaprotocol/ui-toolkit';
@@ -90,12 +90,12 @@ export const TimeInForceSelector = ({
};
return (
-
- {
@@ -103,18 +103,19 @@ export const TimeInForceSelector = ({
}}
className="w-full"
data-testid="order-tif"
+ hasError={!!errorMessage}
>
{options.map(([key, value]) => (
{timeInForceLabel(value)}
))}
-
+
{errorMessage && (
-
+
{renderError(errorMessage)}
-
+
)}
-
+
);
};
diff --git a/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx b/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx
index f6798d813..55ce9ef61 100644
--- a/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx
+++ b/libs/deal-ticket/src/components/deal-ticket/type-selector.tsx
@@ -1,5 +1,5 @@
import {
- InputError,
+ TradingInputError,
SimpleGrid,
Tooltip,
TradingDropdown,
@@ -178,9 +178,9 @@ export const TypeSelector = ({
value={value}
/>
{errorMessage && (
-
+
{renderError(errorMessage as MarketModeValidationType)}
-
+
)}
>
);
diff --git a/libs/deposits/src/lib/deposit-form.tsx b/libs/deposits/src/lib/deposit-form.tsx
index 3468cce63..4dc0a8771 100644
--- a/libs/deposits/src/lib/deposit-form.tsx
+++ b/libs/deposits/src/lib/deposit-form.tsx
@@ -14,14 +14,14 @@ import { t } from '@vegaprotocol/i18n';
import { useLocalStorage } from '@vegaprotocol/react-helpers';
import {
Button,
- FormGroup,
- Input,
- InputError,
- RichSelect,
+ TradingFormGroup,
+ TradingInput,
+ TradingInputError,
+ TradingRichSelect,
Notification,
Intent,
ButtonLink,
- Select,
+ TradingSelect,
} from '@vegaprotocol/ui-toolkit';
import { useVegaWallet } from '@vegaprotocol/wallet';
import { useWeb3React } from '@web3-react/core';
@@ -151,7 +151,7 @@ export const DepositForm = ({
noValidate={true}
data-testid="deposit-form"
>
-
@@ -197,15 +197,17 @@ export const DepositForm = ({
}}
/>
{errors.from?.message && (
- {errors.from.message}
+
+ {errors.from.message}
+
)}
-
-
+
+
setValue('to', '')}
select={
-
+
{t('Please select')}
@@ -215,10 +217,10 @@ export const DepositForm = ({
{pk}
))}
-
+
}
input={
-
{errors.to?.message && (
-
+
{errors.to.message}
-
+
)}
-
-
+
+
(
-
))}
-
+
)}
/>
{errors.asset?.message && (
-
+
{errors.asset.message}
-
+
)}
{isActive && isFaucetable && selectedAsset && (
@@ -296,7 +298,7 @@ export const DepositForm = ({
{t('View asset details')}
)}
-
+
)}
{approved && (
-
-
+
{errors.amount?.message && (
-
+
{errors.amount.message}
-
+
)}
{selectedAsset && balances && (
)}
-
+
)}
void }) => {
`This app will only work on ${VEGA_ENV}. Select a node to connect to.`
)}
- setNodeRadio(value)}
>
@@ -112,7 +112,7 @@ export const NodeSwitcher = ({ closeDialog }: { closeDialog: () => void }) => {
/>
-
+
{nodes.length > 0 && (
-
-
{id !== CUSTOM_NODE_KEY && (
-
+
)}
;
-export type StopOrdersQuery = { __typename?: 'Query', stopOrders?: { __typename?: 'StopOrderConnection', edges?: Array<{ __typename?: 'StopOrderEdge', node?: { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, trigger?: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string } | null, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } } | null }> | null } | null };
+export type StopOrdersQuery = { __typename?: 'Query', stopOrders?: { __typename?: 'StopOrderConnection', edges?: Array<{ __typename?: 'StopOrderEdge', node?: { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } } | null }> | null } | null };
export type StopOrderByIdQueryVariables = Types.Exact<{
stopOrderId: Types.Scalars['ID'];
}>;
-export type StopOrderByIdQuery = { __typename?: 'Query', stopOrder?: { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, trigger?: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string } | null, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } } | null };
+export type StopOrderByIdQuery = { __typename?: 'Query', stopOrder?: { __typename?: 'StopOrder', id: string, ocoLinkId?: string | null, expiresAt?: any | null, expiryStrategy?: Types.StopOrderExpiryStrategy | null, triggerDirection: Types.StopOrderTriggerDirection, status: Types.StopOrderStatus, createdAt: any, updatedAt?: any | null, partyId: string, marketId: string, trigger: { __typename?: 'StopOrderPrice', price: string } | { __typename?: 'StopOrderTrailingPercentOffset', trailingPercentOffset: string }, submission: { __typename?: 'OrderSubmission', marketId: string, price: string, size: string, side: Types.Side, timeInForce: Types.OrderTimeInForce, expiresAt: any, type: Types.OrderType, reference?: string | null, postOnly?: boolean | null, reduceOnly?: boolean | null, peggedOrder?: { __typename?: 'PeggedOrder', reference: Types.PeggedReference, offset: string } | null } } | null };
export const OrderFieldsFragmentDoc = gql`
fragment OrderFields on Order {
diff --git a/libs/orders/src/lib/components/order-list/order-edit-dialog.tsx b/libs/orders/src/lib/components/order-list/order-edit-dialog.tsx
index 87093e790..4710035b7 100644
--- a/libs/orders/src/lib/components/order-list/order-edit-dialog.tsx
+++ b/libs/orders/src/lib/components/order-list/order-edit-dialog.tsx
@@ -9,9 +9,9 @@ import { t } from '@vegaprotocol/i18n';
import { Size } from '@vegaprotocol/datagrid';
import * as Schema from '@vegaprotocol/types';
import {
- FormGroup,
- Input,
- InputError,
+ TradingFormGroup,
+ TradingInput,
+ TradingInputError,
Button,
Dialog,
Icon,
@@ -102,8 +102,12 @@ export const OrderEditDialog = ({
noValidate
>
-
-
+
{errors.limitPrice?.message && (
-
+
{errors.limitPrice.message}
-
+
)}
-
-
-
+
+
{errors.size?.message && (
- {errors.size.message}
+
+ {errors.size.message}
+
)}
-
+
{t('Update')}
diff --git a/libs/positions/src/lib/positions-data-providers.spec.ts b/libs/positions/src/lib/positions-data-providers.spec.ts
index a8ca0aaa9..6c1f67093 100644
--- a/libs/positions/src/lib/positions-data-providers.spec.ts
+++ b/libs/positions/src/lib/positions-data-providers.spec.ts
@@ -2,7 +2,12 @@ import * as Schema from '@vegaprotocol/types';
import type { Account } from '@vegaprotocol/accounts';
import type { MarketWithData } from '@vegaprotocol/markets';
import type { PositionFieldsFragment } from './__generated__/Positions';
-import { getMetrics, rejoinPositionData } from './positions-data-providers';
+import type { Position } from './positions-data-providers';
+import {
+ getMetrics,
+ preparePositions,
+ rejoinPositionData,
+} from './positions-data-providers';
import { PositionStatus } from '@vegaprotocol/types';
const accounts = [
@@ -223,4 +228,29 @@ describe('getMetrics && rejoinPositionData', () => {
);
expect(metrics[1].status).toEqual(positions[1].positionStatus);
});
+
+ it('sorts and filters positions', () => {
+ const createPosition = (override?: Partial) =>
+ ({
+ marketState: Schema.MarketState.STATE_ACTIVE,
+ marketCode: 'a',
+ ...override,
+ } as Position);
+
+ const data = [
+ createPosition(),
+ createPosition({
+ marketCode: 'c',
+ marketState: Schema.MarketState.STATE_CANCELLED,
+ }),
+ createPosition({ marketCode: 'd' }),
+ createPosition({ marketCode: 'b' }),
+ ];
+
+ const withoutClosed = preparePositions(data, false);
+ expect(withoutClosed.map((p) => p.marketCode)).toEqual(['a', 'b', 'd']);
+
+ const withClosed = preparePositions(data, true);
+ expect(withClosed.map((p) => p.marketCode)).toEqual(['a', 'b', 'c', 'd']);
+ });
});
diff --git a/libs/positions/src/lib/positions-data-providers.ts b/libs/positions/src/lib/positions-data-providers.ts
index 8d44c0941..d5c533b83 100644
--- a/libs/positions/src/lib/positions-data-providers.ts
+++ b/libs/positions/src/lib/positions-data-providers.ts
@@ -41,6 +41,7 @@ export interface Position {
marketId: string;
marketCode: string;
marketTradingMode: Schema.MarketTradingMode;
+ marketState: Schema.MarketState;
markPrice: string | undefined;
notional: string | undefined;
openVolume: string;
@@ -119,6 +120,7 @@ export const getMetrics = (
marketId: market.id,
marketCode: market.tradableInstrument.instrument.code,
marketTradingMode: market.tradingMode,
+ marketState: market.state,
markPrice: marketData ? marketData.markPrice : undefined,
notional: notional
? notional.multipliedBy(10 ** marketDecimalPlaces).toFixed(0)
@@ -248,6 +250,26 @@ export const rejoinPositionData = (
return null;
};
+export const preparePositions = (metrics: Position[], showClosed: boolean) => {
+ return sortBy(metrics, 'marketCode').filter((p) => {
+ if (showClosed) {
+ return true;
+ }
+
+ if (
+ [
+ Schema.MarketState.STATE_ACTIVE,
+ Schema.MarketState.STATE_PENDING,
+ Schema.MarketState.STATE_SUSPENDED,
+ ].includes(p.marketState)
+ ) {
+ return true;
+ }
+
+ return false;
+ });
+};
+
export const positionsMarketsProvider = makeDerivedDataProvider<
string[],
never,
@@ -265,7 +287,7 @@ export const positionsMarketsProvider = makeDerivedDataProvider<
export const positionsMetricsProvider = makeDerivedDataProvider<
Position[],
Position[],
- PositionsQueryVariables & { marketIds: string[] }
+ PositionsQueryVariables & { marketIds: string[]; showClosed: boolean }
>(
[
(callback, client, variables) =>
@@ -281,10 +303,10 @@ export const positionsMetricsProvider = makeDerivedDataProvider<
marketIds: variables.marketIds,
}),
],
- ([positions, accounts, marketsData]) => {
+ ([positions, accounts, marketsData], variables) => {
const positionsData = rejoinPositionData(positions, marketsData);
const metrics = getMetrics(positionsData, accounts as Account[] | null);
- return sortBy(metrics, 'marketCode');
+ return preparePositions(metrics, variables.showClosed);
},
(data, delta, previousData) =>
data.filter((row) => {
diff --git a/libs/positions/src/lib/positions-manager.tsx b/libs/positions/src/lib/positions-manager.tsx
index f01f71cfa..521ae1b45 100644
--- a/libs/positions/src/lib/positions-manager.tsx
+++ b/libs/positions/src/lib/positions-manager.tsx
@@ -16,6 +16,7 @@ interface PositionsManagerProps {
onMarketClick?: (marketId: string) => void;
isReadOnly: boolean;
gridProps?: ReturnType;
+ showClosed?: boolean;
}
export const PositionsManager = ({
@@ -23,6 +24,7 @@ export const PositionsManager = ({
onMarketClick,
isReadOnly,
gridProps,
+ showClosed = false,
}: PositionsManagerProps) => {
const { pubKeys, pubKey } = useVegaWallet();
const create = useVegaTransactionStore((store) => store.create);
@@ -60,7 +62,7 @@ export const PositionsManager = ({
const { data, error } = useDataProvider({
dataProvider: positionsMetricsProvider,
- variables: { partyIds, marketIds: marketIds || [] },
+ variables: { partyIds, marketIds: marketIds || [], showClosed },
skip: !marketIds,
});
@@ -68,7 +70,7 @@ export const PositionsManager = ({
);
},
- minWidth: 75,
- maxWidth: 75,
+ minWidth: 55,
+ maxWidth: 55,
}
: null,
];
diff --git a/libs/proposals/src/lib/proposals-data-provider/proposals-data-provider.tsx b/libs/proposals/src/lib/proposals-data-provider/proposals-data-provider.tsx
index acdbd808a..c00cc18ff 100644
--- a/libs/proposals/src/lib/proposals-data-provider/proposals-data-provider.tsx
+++ b/libs/proposals/src/lib/proposals-data-provider/proposals-data-provider.tsx
@@ -17,4 +17,17 @@ export const proposalsDataProvider = makeDataProvider<
never,
never,
ProposalsListQueryVariables
->({ query: ProposalsListDocument, getData });
+>({
+ query: ProposalsListDocument,
+ getData,
+ /**
+ * Ignores errors for not found settlement asset for NewMarket proposals.
+ *
+ * It can happen that a NewMarket proposal is incomplete and does not contain
+ * `futureProduct` details. This guard protects against that.
+ *
+ * GQL Path: `terms.change.instrument.futureProduct.settlementAsset`
+ */
+ errorPolicyGuard: (errors) =>
+ errors.every((e) => e.message.match(/failed to get asset for ID/)),
+});
diff --git a/libs/react-helpers/package.json b/libs/react-helpers/package.json
index 586ef08c1..f345b4a64 100644
--- a/libs/react-helpers/package.json
+++ b/libs/react-helpers/package.json
@@ -1,4 +1,8 @@
{
"name": "@vegaprotocol/react-helpers",
- "version": "0.2.5"
+ "version": "0.2.5",
+ "peerDependencies": {
+ "react": "18.2.0",
+ "react-dom": "18.2.0"
+ }
}
diff --git a/libs/tailwindcss-config/src/theme.js b/libs/tailwindcss-config/src/theme.js
index 7234d3c59..111b0bf0f 100644
--- a/libs/tailwindcss-config/src/theme.js
+++ b/libs/tailwindcss-config/src/theme.js
@@ -172,7 +172,7 @@ module.exports = {
900: '#F9FAFA',
},
},
- danger: '#FF077F',
+ danger: '#EC003C',
warning: '#FF8700',
success: '#00F780',
},
diff --git a/libs/types/package.json b/libs/types/package.json
index c736eaf6d..550d812ba 100644
--- a/libs/types/package.json
+++ b/libs/types/package.json
@@ -1,4 +1,4 @@
{
"name": "@vegaprotocol/types",
- "version": "0.0.4"
+ "version": "0.0.5"
}
diff --git a/libs/types/src/__generated__/types.ts b/libs/types/src/__generated__/types.ts
index 18605c0bd..28441bfcf 100644
--- a/libs/types/src/__generated__/types.ts
+++ b/libs/types/src/__generated__/types.ts
@@ -4364,6 +4364,8 @@ export type StopOrder = {
marketId: Scalars['ID'];
/** If OCO (one-cancels-other) order, the ID of the associated order. */
ocoLinkId?: Maybe;
+ /** The order that was created when triggered. */
+ order?: Maybe;
/** Party that submitted the stop order. */
partyId: Scalars['ID'];
/** Status of the stop order */
@@ -4371,7 +4373,7 @@ export type StopOrder = {
/** Order to submit when the stop order is triggered. */
submission: OrderSubmission;
/** Price movement that will trigger the stop order */
- trigger?: Maybe;
+ trigger: StopOrderTrigger;
/** Direction the price is moving to trigger the stop order. */
triggerDirection: StopOrderTriggerDirection;
/** Time the stop order was last updated. */
diff --git a/libs/ui-toolkit/package.json b/libs/ui-toolkit/package.json
index 673911a2d..ccaca42f5 100644
--- a/libs/ui-toolkit/package.json
+++ b/libs/ui-toolkit/package.json
@@ -1,4 +1,8 @@
{
"name": "@vegaprotocol/ui-toolkit",
- "version": "0.12.7"
+ "version": "0.12.8",
+ "peerDependencies": {
+ "react": "18.2.0",
+ "react-dom": "18.2.0"
+ }
}
diff --git a/libs/ui-toolkit/src/components/index.ts b/libs/ui-toolkit/src/components/index.ts
index 4c4ee984d..2b76f30bd 100644
--- a/libs/ui-toolkit/src/components/index.ts
+++ b/libs/ui-toolkit/src/components/index.ts
@@ -16,8 +16,8 @@ export * from './form-group';
export * from './healthbar';
export * from './icon';
export * from './indicator';
-export * from './input-error';
export * from './input';
+export * from './input-error';
export * from './key-value-table';
export * from './link';
export * from './loader';
@@ -48,10 +48,18 @@ export * from './tiny-scroll';
export * from './toast';
export * from './toggle';
export * from './tooltip';
-export * from './trading-button';
-export * from './trading-dropdown';
export * from './traffic-light';
export * from './vega-icons';
export * from './vega-logo';
export * from './viewing-as-user';
export * from './pill';
+
+// Trading specific components
+export * from './trading-button';
+export * from './trading-checkbox';
+export * from './trading-dropdown';
+export * from './trading-form-group';
+export * from './trading-input-error';
+export * from './trading-input';
+export * from './trading-radio-group';
+export * from './trading-select';
diff --git a/libs/ui-toolkit/src/components/trading-checkbox/checkbox.spec.tsx b/libs/ui-toolkit/src/components/trading-checkbox/checkbox.spec.tsx
new file mode 100644
index 000000000..eab21b5f0
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-checkbox/checkbox.spec.tsx
@@ -0,0 +1,40 @@
+import { fireEvent, render, screen } from '@testing-library/react';
+import { TradingCheckbox } from './checkbox';
+
+describe('Checkbox', () => {
+ it('should render checkbox with label successfully', () => {
+ render( );
+ expect(screen.getByText('test')).toBeInTheDocument();
+ });
+
+ it('should render a checked checkbox if specified in state', () => {
+ render( );
+ expect(screen.getByTestId(/icon-/)).toBeInTheDocument();
+ });
+
+ it('should render an unchecked checkbox if specified in state', () => {
+ render( );
+ expect(screen.queryByTestId(/icon-/)).not.toBeInTheDocument();
+ });
+
+ it('should render an indeterminate checkbox if specified in state', () => {
+ render( );
+ expect(screen.getByTestId('indeterminate-icon')).toBeInTheDocument();
+ });
+
+ it('fires callback on change if provided', () => {
+ const callback = jest.fn();
+
+ render(
+
+ );
+
+ const checkbox = screen.getByText('onchange');
+ fireEvent.click(checkbox);
+ expect(callback).toHaveBeenCalled();
+ });
+});
diff --git a/libs/ui-toolkit/src/components/trading-checkbox/checkbox.stories.tsx b/libs/ui-toolkit/src/components/trading-checkbox/checkbox.stories.tsx
new file mode 100644
index 000000000..d5ae59e0d
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-checkbox/checkbox.stories.tsx
@@ -0,0 +1,38 @@
+import type { Meta, StoryFn } from '@storybook/react';
+import type { TradingCheckboxProps } from './checkbox';
+import { TradingCheckbox } from './checkbox';
+
+export default {
+ component: TradingCheckbox,
+ title: 'Checkbox',
+} as Meta;
+
+const Template: StoryFn = (args) => (
+
+);
+
+export const Default = Template.bind({});
+Default.args = {
+ name: 'default',
+ label: 'Regular checkbox',
+};
+
+export const Overflow = Template.bind({});
+Overflow.args = {
+ name: 'overflow',
+ label:
+ 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
+};
+
+export const Disabled = Template.bind({});
+Disabled.args = {
+ disabled: true,
+ label: 'Disabled',
+};
+
+export const Indeterminate = Template.bind({});
+Indeterminate.args = {
+ name: 'default',
+ checked: 'indeterminate',
+ label: 'Indeterminate checkbox',
+};
diff --git a/libs/ui-toolkit/src/components/trading-checkbox/checkbox.tsx b/libs/ui-toolkit/src/components/trading-checkbox/checkbox.tsx
new file mode 100644
index 000000000..9e1f6befe
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-checkbox/checkbox.tsx
@@ -0,0 +1,63 @@
+import { VegaIcon, VegaIconNames } from '../icon';
+import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
+import classNames from 'classnames';
+import type { ReactNode } from 'react';
+
+type CheckedState = boolean | 'indeterminate';
+export interface TradingCheckboxProps {
+ checked?: CheckedState;
+ label?: ReactNode;
+ name?: string;
+ onCheckedChange?: (checked: CheckedState) => void;
+ disabled?: boolean;
+}
+
+export const TradingCheckbox = ({
+ checked,
+ label,
+ name,
+ onCheckedChange,
+ disabled = false,
+}: TradingCheckboxProps) => {
+ const rootClasses = classNames(
+ 'relative flex justify-center items-center w-3 h-3',
+ 'border rounded-sm overflow-hidden',
+ 'border-vega-clight-500 dark:border-vega-cdark-500',
+ 'aria-checked:border-vega-clight-400 dark:aria-checked:border-vega-cdark-400',
+ 'disabled:border-vega-clight-600 dark:disabled:border-vega-cdark-600',
+ 'bg-vega-clight-700 dark:bg-vega-cdark-700'
+ );
+
+ return (
+
+
+
+ {checked === 'indeterminate' ? (
+
+ ) : (
+
+ )}
+
+
+
+ {label}
+
+
+ );
+};
diff --git a/libs/ui-toolkit/src/components/trading-checkbox/index.ts b/libs/ui-toolkit/src/components/trading-checkbox/index.ts
new file mode 100644
index 000000000..8d78b3e23
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-checkbox/index.ts
@@ -0,0 +1 @@
+export * from './checkbox';
diff --git a/libs/ui-toolkit/src/components/trading-form-group/form-group.spec.tsx b/libs/ui-toolkit/src/components/trading-form-group/form-group.spec.tsx
new file mode 100644
index 000000000..07ccce16d
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-form-group/form-group.spec.tsx
@@ -0,0 +1,23 @@
+import { render, screen } from '@testing-library/react';
+
+import { TradingFormGroup } from './form-group';
+
+describe('FormGroup', () => {
+ it('should render label if given a label', () => {
+ render(
+
+
+
+ );
+ expect(screen.getByLabelText('label')).toBeInTheDocument();
+ });
+
+ it('should render children', () => {
+ render(
+
+
+
+ );
+ expect(screen.getByTestId('foo')).toBeInTheDocument();
+ });
+});
diff --git a/libs/ui-toolkit/src/components/trading-form-group/form-group.tsx b/libs/ui-toolkit/src/components/trading-form-group/form-group.tsx
new file mode 100644
index 000000000..bb3cf26c9
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-form-group/form-group.tsx
@@ -0,0 +1,53 @@
+import classNames from 'classnames';
+import type { ReactNode } from 'react';
+
+export interface TradingFormGroupProps {
+ children: ReactNode;
+ className?: string;
+ label: string | ReactNode; // For accessibility reasons this must always be set for screen readers. If you want it to not show, then use the hideLabel prop"
+ labelFor: string; // Same as above
+ hideLabel?: boolean;
+ disabled?: boolean;
+ labelDescription?: string;
+ labelAlign?: 'left' | 'right';
+ compact?: boolean;
+}
+
+export const TradingFormGroup = ({
+ children,
+ className,
+ label,
+ labelFor,
+ labelDescription,
+ labelAlign = 'left',
+ hideLabel = false,
+ compact = false,
+ disabled = false,
+}: TradingFormGroupProps) => {
+ const wrapperClasses = classNames(
+ 'relative',
+ {
+ 'mb-2': compact,
+ 'mb-4': !compact,
+ },
+ className
+ );
+ const labelClasses = classNames('block mb-2 text-xs', {
+ 'text-right': labelAlign === 'right',
+ 'sr-only': hideLabel,
+ 'text-muted': disabled,
+ });
+ return (
+
+ {label && (
+
+ {label}
+ {labelDescription && (
+ {labelDescription}
+ )}
+
+ )}
+ {children}
+
+ );
+};
diff --git a/libs/ui-toolkit/src/components/trading-form-group/from-group.stories.tsx b/libs/ui-toolkit/src/components/trading-form-group/from-group.stories.tsx
new file mode 100644
index 000000000..0d98733f5
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-form-group/from-group.stories.tsx
@@ -0,0 +1,47 @@
+import type { StoryFn, Meta } from '@storybook/react';
+import { TradingInput } from '../trading-input';
+import type { TradingFormGroupProps } from './form-group';
+import { TradingFormGroup } from './form-group';
+export default {
+ component: TradingFormGroup,
+ title: 'FormGroup',
+ argTypes: {
+ label: {
+ type: 'string',
+ },
+ labelFor: {
+ type: 'string',
+ },
+ labelDescription: {
+ type: 'string',
+ },
+ className: {
+ type: 'string',
+ },
+ hasError: {
+ type: 'boolean',
+ },
+ disabled: {
+ type: 'boolean',
+ },
+ },
+} as Meta;
+
+const Template: StoryFn = (args) => (
+
+
+
+);
+
+export const Default = Template.bind({});
+Default.args = {
+ label: 'Label',
+ labelFor: 'labelFor',
+};
+
+export const WithLabelDescription = Template.bind({});
+WithLabelDescription.args = {
+ label: 'Label',
+ labelFor: 'labelFor',
+ labelDescription: 'Description text',
+};
diff --git a/libs/ui-toolkit/src/components/trading-form-group/index.tsx b/libs/ui-toolkit/src/components/trading-form-group/index.tsx
new file mode 100644
index 000000000..faeeafc70
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-form-group/index.tsx
@@ -0,0 +1 @@
+export * from './form-group';
diff --git a/libs/ui-toolkit/src/components/trading-input-error/index.ts b/libs/ui-toolkit/src/components/trading-input-error/index.ts
new file mode 100644
index 000000000..619f584f4
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-input-error/index.ts
@@ -0,0 +1 @@
+export * from './input-error';
diff --git a/libs/ui-toolkit/src/components/trading-input-error/input-error.spec.tsx b/libs/ui-toolkit/src/components/trading-input-error/input-error.spec.tsx
new file mode 100644
index 000000000..30b82d326
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-input-error/input-error.spec.tsx
@@ -0,0 +1,10 @@
+import { render } from '@testing-library/react';
+
+import { TradingInputError } from './input-error';
+
+describe('InputError', () => {
+ it('should render successfully', () => {
+ const { baseElement } = render( );
+ expect(baseElement).toBeTruthy();
+ });
+});
diff --git a/libs/ui-toolkit/src/components/trading-input-error/input-error.stories.tsx b/libs/ui-toolkit/src/components/trading-input-error/input-error.stories.tsx
new file mode 100644
index 000000000..f92b7f42f
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-input-error/input-error.stories.tsx
@@ -0,0 +1,20 @@
+import type { StoryFn, Meta } from '@storybook/react';
+import { TradingInputError } from './input-error';
+
+export default {
+ component: TradingInputError,
+ title: 'InputError',
+} as Meta;
+
+const Template: StoryFn = (args) => ;
+
+export const Danger = Template.bind({});
+Danger.args = {
+ children: 'An error that might have happened',
+};
+
+export const Warning = Template.bind({});
+Warning.args = {
+ intent: 'warning',
+ children: 'Something that might be an issue',
+};
diff --git a/libs/ui-toolkit/src/components/trading-input-error/input-error.tsx b/libs/ui-toolkit/src/components/trading-input-error/input-error.tsx
new file mode 100644
index 000000000..7d2ec2a78
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-input-error/input-error.tsx
@@ -0,0 +1,42 @@
+import classNames from 'classnames';
+import type { HTMLAttributes } from 'react';
+
+interface TradingInputErrorProps extends HTMLAttributes {
+ children?: React.ReactNode;
+ intent?: 'danger' | 'warning';
+ forInput?: string;
+ testId?: string;
+}
+
+export const TradingInputError = ({
+ intent = 'danger',
+ children,
+ forInput,
+ testId,
+ className,
+ ...props
+}: TradingInputErrorProps) => {
+ const effectiveClassName = classNames(
+ 'text-xs flex items-center first-letter:uppercase',
+ 'mt-2',
+ {
+ 'border-danger': intent === 'danger',
+ 'border-warning': intent === 'warning',
+ },
+ {
+ 'text-warning': intent === 'warning',
+ 'text-danger': intent === 'danger',
+ }
+ );
+ return (
+
+ {children}
+
+ );
+};
diff --git a/libs/ui-toolkit/src/components/trading-input/index.ts b/libs/ui-toolkit/src/components/trading-input/index.ts
new file mode 100644
index 000000000..e3365cb90
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-input/index.ts
@@ -0,0 +1 @@
+export * from './input';
diff --git a/libs/ui-toolkit/src/components/trading-input/input.spec.tsx b/libs/ui-toolkit/src/components/trading-input/input.spec.tsx
new file mode 100644
index 000000000..deecf35b4
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-input/input.spec.tsx
@@ -0,0 +1,10 @@
+import { render } from '@testing-library/react';
+
+import { TradingInput } from './input';
+
+describe('Input', () => {
+ it('should render successfully', () => {
+ const { baseElement } = render( );
+ expect(baseElement).toBeTruthy();
+ });
+});
diff --git a/libs/ui-toolkit/src/components/trading-input/input.stories.tsx b/libs/ui-toolkit/src/components/trading-input/input.stories.tsx
new file mode 100644
index 000000000..dafca8103
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-input/input.stories.tsx
@@ -0,0 +1,83 @@
+import type { StoryFn, Meta } from '@storybook/react';
+import { TradingInput } from './input';
+import { FormGroup } from '../form-group';
+export default {
+ component: TradingInput,
+ title: 'Input',
+} as Meta;
+
+const Template: StoryFn = (args) => (
+
+
+
+);
+
+const customElementPlaceholder = (
+
+ Ω
+
+);
+
+export const Default = Template.bind({});
+Default.args = {
+ id: 'input-default',
+};
+
+export const WithError = Template.bind({});
+WithError.args = {
+ hasError: true,
+ id: 'input-has-error',
+};
+
+export const Disabled = Template.bind({});
+Disabled.args = {
+ disabled: true,
+ id: 'input-disabled',
+};
+
+export const TypeDate = Template.bind({});
+TypeDate.args = {
+ type: 'date',
+ id: 'input-date',
+};
+
+export const TypeDateTime = Template.bind({});
+TypeDateTime.args = {
+ type: 'datetime-local',
+ id: 'input-datetime-local',
+ min: '2022-09-05T11:29:17',
+ max: '2023-09-05T10:29:49',
+};
+
+export const IconPrepend = Template.bind({});
+IconPrepend.args = {
+ prependIconName: 'search',
+ id: 'input-icon-prepend',
+};
+
+export const IconAppend = Template.bind({});
+IconAppend.args = {
+ value: 'I type words and even more words',
+ appendIconName: 'search',
+ id: 'input-icon-append',
+};
+
+export const ElementPrepend = Template.bind({});
+ElementPrepend.args = {
+ value: '<- custom element',
+ prependElement: customElementPlaceholder,
+ id: 'input-element-prepend',
+};
+
+export const ElementAppend = Template.bind({});
+ElementAppend.args = {
+ value: 'custom element ->',
+ appendElement: customElementPlaceholder,
+ id: 'input-element-append',
+};
diff --git a/libs/ui-toolkit/src/components/trading-input/input.tsx b/libs/ui-toolkit/src/components/trading-input/input.tsx
new file mode 100644
index 000000000..7bef3d2cc
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-input/input.tsx
@@ -0,0 +1,174 @@
+import type { InputHTMLAttributes, ReactNode } from 'react';
+import { forwardRef } from 'react';
+import classNames from 'classnames';
+import type { IconName } from '../icon';
+import { Icon } from '../icon';
+import { defaultFormElement } from '../../utils/shared';
+
+type InputRootProps = InputHTMLAttributes & {
+ hasError?: boolean;
+ disabled?: boolean;
+ className?: string;
+};
+
+type NoPrepend = {
+ prependIconName?: never;
+ prependIconDescription?: string;
+ prependElement?: never;
+};
+
+type NoAppend = {
+ appendIconName?: never;
+ appendIconDescription?: string;
+ appendElement?: never;
+};
+
+type InputPrepend = NoAppend &
+ (
+ | NoPrepend
+ | {
+ prependIconName: IconName;
+ prependIconDescription?: string;
+ prependElement?: never;
+ }
+ | {
+ prependIconName?: never;
+ prependIconDescription?: never;
+ prependElement: ReactNode;
+ }
+ );
+
+type InputAppend = NoPrepend &
+ (
+ | NoAppend
+ | {
+ appendIconName: IconName;
+ appendIconDescription?: string;
+ appendElement?: never;
+ }
+ | {
+ appendIconName?: never;
+ appendIconDescription?: never;
+ appendElement: ReactNode;
+ }
+ );
+
+type AffixProps = InputPrepend | InputAppend;
+
+export type TradingInputProps = InputRootProps & AffixProps;
+
+export const tradingInputStyle = ({
+ style,
+ disabled,
+}: {
+ style?: React.CSSProperties;
+ disabled?: boolean;
+}) =>
+ disabled
+ ? {
+ ...style,
+ backgroundImage:
+ 'url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAAXNSR0IArs4c6QAAACNJREFUGFdjtLS0/M8ABcePH2eEsRlJl4BpBdHIuuFmEi0BABqjEQVjx/LTAAAAAElFTkSuQmCC)',
+ }
+ : style;
+
+const getAffixElement = ({
+ prependElement,
+ prependIconName,
+ prependIconDescription,
+ appendElement,
+ appendIconName,
+ appendIconDescription,
+}: Pick) => {
+ const position = prependIconName || prependElement ? 'pre' : 'post';
+
+ const className = classNames(
+ ['fill-black dark:fill-white', 'absolute', 'z-10'],
+ {
+ 'left-3': position === 'pre',
+ 'right-3': position === 'post',
+ }
+ );
+
+ const element = prependElement || appendElement;
+ const iconName = prependIconName || appendIconName;
+ const iconDescription = prependIconDescription || appendIconDescription;
+
+ if (element) {
+ return {element}
;
+ }
+
+ if (iconName) {
+ return (
+
+ );
+ }
+
+ return null;
+};
+
+export const TradingInput = forwardRef(
+ (
+ {
+ prependIconName,
+ prependIconDescription,
+ appendIconName,
+ appendIconDescription,
+ prependElement,
+ appendElement,
+ className,
+ hasError,
+ ...props
+ },
+ ref
+ ) => {
+ const hasPrepended = !!(prependIconName || prependElement);
+ const hasAppended = !!(appendIconName || appendElement);
+
+ const inputClassName = classNames(
+ 'appearance-none dark:color-scheme-dark px-3 h-8',
+ className,
+ {
+ 'pl-9': hasPrepended,
+ 'pr-9': hasAppended,
+ }
+ );
+
+ const input = (
+
+ );
+
+ const element = getAffixElement({
+ prependIconName,
+ prependIconDescription,
+ appendIconName,
+ appendIconDescription,
+ prependElement,
+ appendElement,
+ });
+
+ if (element) {
+ return (
+
+ {hasPrepended && element}
+ {input}
+ {hasAppended && element}
+
+ );
+ }
+
+ return input;
+ }
+);
diff --git a/libs/ui-toolkit/src/components/trading-radio-group/index.ts b/libs/ui-toolkit/src/components/trading-radio-group/index.ts
new file mode 100644
index 000000000..ed40543ed
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-radio-group/index.ts
@@ -0,0 +1 @@
+export * from './radio-group';
diff --git a/libs/ui-toolkit/src/components/trading-radio-group/radio-group.stories.tsx b/libs/ui-toolkit/src/components/trading-radio-group/radio-group.stories.tsx
new file mode 100644
index 000000000..f7bd74a5b
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-radio-group/radio-group.stories.tsx
@@ -0,0 +1,22 @@
+import type { StoryFn, Meta } from '@storybook/react';
+import type { TradingRadioGroupProps } from './radio-group';
+import { TradingRadioGroup, TradingRadio } from './radio-group';
+
+export default {
+ component: TradingRadioGroup,
+ title: 'RadioGroup',
+} as Meta;
+
+const Template: StoryFn = (args) => (
+
+
+
+
+
+);
+
+export const Vertical = Template.bind({});
+export const Horizontal = Template.bind({});
+Horizontal.args = {
+ orientation: 'horizontal',
+};
diff --git a/libs/ui-toolkit/src/components/trading-radio-group/radio-group.tsx b/libs/ui-toolkit/src/components/trading-radio-group/radio-group.tsx
new file mode 100644
index 000000000..c9710979d
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-radio-group/radio-group.tsx
@@ -0,0 +1,99 @@
+import { forwardRef } from 'react';
+import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
+import classNames from 'classnames';
+import type { ReactNode } from 'react';
+
+export interface TradingRadioGroupProps {
+ name?: string;
+ children: ReactNode;
+ defaultValue?: string;
+ value?: string;
+ orientation?: 'horizontal' | 'vertical';
+ onChange?: (value: string) => void;
+ className?: string;
+}
+
+export const TradingRadioGroup = forwardRef<
+ HTMLDivElement,
+ TradingRadioGroupProps
+>(
+ (
+ {
+ children,
+ name,
+ value,
+ orientation = 'vertical',
+ onChange,
+ className,
+ }: TradingRadioGroupProps,
+ ref
+ ) => {
+ const groupClasses = classNames(
+ 'flex text-sm',
+ {
+ 'flex-col gap-2': orientation === 'vertical',
+ 'flex-row gap-4': orientation === 'horizontal',
+ },
+ className
+ );
+ return (
+
+ {children}
+
+ );
+ }
+);
+
+interface RadioProps {
+ id: string;
+ value: string;
+ label: string;
+ disabled?: boolean;
+}
+
+export const TradingRadio = ({ id, value, label, disabled }: RadioProps) => {
+ const wrapperClasses = classNames('flex items-center gap-1.5 text-xs');
+ const itemClasses = classNames(
+ 'flex justify-center items-center',
+ 'w-3 h-3 rounded-full border',
+ 'border-vega-clight-500 dark:border-vega-cdark-500',
+ 'aria-checked:border-vega-clight-400 dark:aria-checked:border-vega-cdark-400',
+ 'disabled:border-vega-clight-600 dark:disabled:border-vega-cdark-600',
+ 'bg-vega-clight-700 dark:bg-vega-cdark-700'
+ );
+ const indicatorClasses = classNames(
+ 'block w-2.5 h-2.5 border-2 rounded-full',
+ 'bg-vega-clight-50 dark:bg-vega-cdark-50',
+ 'border-vega-clight-700 dark:border-vega-cdark-700'
+ );
+ return (
+
+
+
+
+
+ {label}
+
+
+ );
+};
diff --git a/libs/ui-toolkit/src/components/trading-select/index.ts b/libs/ui-toolkit/src/components/trading-select/index.ts
new file mode 100644
index 000000000..c7396734d
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-select/index.ts
@@ -0,0 +1 @@
+export * from './select';
diff --git a/libs/ui-toolkit/src/components/trading-select/select.spec.tsx b/libs/ui-toolkit/src/components/trading-select/select.spec.tsx
new file mode 100644
index 000000000..8b20dfb80
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-select/select.spec.tsx
@@ -0,0 +1,37 @@
+import { render } from '@testing-library/react';
+import { TradingRichSelect, TradingSelect, TradingOption } from './select';
+
+describe('Select', () => {
+ it('should render successfully', () => {
+ const { baseElement } = render( );
+ expect(baseElement).toBeTruthy();
+ });
+});
+
+describe('RichSelect', () => {
+ it('should render select element with placeholder when no value is pre-selected', async () => {
+ const { findByTestId } = render(
+
+ 1
+ 2
+
+ );
+ const btn = (await findByTestId(
+ 'rich-select-trigger'
+ )) as HTMLButtonElement;
+ expect(btn.textContent).toEqual('Select');
+ });
+
+ it('should render select element with pre-selected value', async () => {
+ const { findByTestId } = render(
+
+ 1
+ 2
+
+ );
+ const btn = (await findByTestId(
+ 'rich-select-trigger'
+ )) as HTMLButtonElement;
+ expect(btn.textContent).toEqual('1');
+ });
+});
diff --git a/libs/ui-toolkit/src/components/trading-select/select.stories.tsx b/libs/ui-toolkit/src/components/trading-select/select.stories.tsx
new file mode 100644
index 000000000..1daa565f8
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-select/select.stories.tsx
@@ -0,0 +1,92 @@
+import type { StoryFn, Meta } from '@storybook/react';
+import { TradingOption, TradingSelect, TradingRichSelect } from './select';
+import { FormGroup } from '../form-group';
+
+export default {
+ component: TradingSelect,
+ title: 'Select',
+} as Meta;
+
+const Template: StoryFn = (args) => (
+
+
+ Option 1
+ Option 2
+ Option 3
+
+
+);
+
+const RichSelectTemplate: StoryFn = ({ placeholder, ...props }) => (
+
+
+
+);
+
+export const Default = Template.bind({});
+Default.args = {
+ id: 'select-default',
+};
+
+export const WithError = Template.bind({});
+WithError.args = {
+ id: 'select-has-error',
+ hasError: true,
+};
+
+export const Disabled = Template.bind({});
+Disabled.args = {
+ id: 'select-disabled',
+ disabled: true,
+};
+
+export const RichDefaultSelect = RichSelectTemplate.bind({});
+RichDefaultSelect.args = {
+ id: 'rich',
+ name: 'rich',
+ placeholder: 'Select an option',
+ onValueChange: (v: string) => {
+ // eslint-disable-next-line no-console
+ console.log(v);
+ },
+ children: (
+ <>
+
+
+ Option One
+ First option
+
+
+
+
+ Option Two
+ Second option
+
+
+
+
+ Option Three
+ Third option
+
+
+
+
+ Option Four
+ Fourth option
+
+
+
+
+ Option Five
+ Fifth option
+
+
+
+
+ Option Six
+ Sixth option
+
+
+ >
+ ),
+};
diff --git a/libs/ui-toolkit/src/components/trading-select/select.tsx b/libs/ui-toolkit/src/components/trading-select/select.tsx
new file mode 100644
index 000000000..a8869a661
--- /dev/null
+++ b/libs/ui-toolkit/src/components/trading-select/select.tsx
@@ -0,0 +1,129 @@
+import type { Ref, SelectHTMLAttributes } from 'react';
+import { useRef } from 'react';
+import { forwardRef } from 'react';
+import classNames from 'classnames';
+import { Icon } from '..';
+import { defaultSelectElement } from '../../utils/shared';
+import * as SelectPrimitive from '@radix-ui/react-select';
+
+export interface TradingSelectProps
+ extends SelectHTMLAttributes {
+ hasError?: boolean;
+ className?: string;
+ value?: string | number;
+ children?: React.ReactNode;
+}
+
+export const TradingSelect = forwardRef(
+ ({ className, hasError, ...props }, ref) => (
+
+
+
+
+ )
+);
+
+export type TradingRichSelectProps = React.ComponentProps<
+ typeof SelectPrimitive.Root
+> & {
+ placeholder: string;
+ hasError?: boolean;
+ id?: string;
+ 'data-testid'?: string;
+};
+export const TradingRichSelect = forwardRef<
+ React.ElementRef,
+ TradingRichSelectProps
+>(({ id, children, placeholder, hasError, ...props }, forwardedRef) => {
+ const containerRef = useRef();
+ const contentRef = useRef();
+
+ return (
+ }
+ className="flex items-center relative"
+ >
+
+
+
+
+
+
+
+
+ }
+ className={classNames(
+ 'relative',
+ 'z-20',
+ 'bg-white dark:bg-black',
+ 'border border-neutral-500 focus:border-black dark:focus:border-white rounded',
+ 'overflow-hidden',
+ 'shadow-lg'
+ )}
+ position={'item-aligned'}
+ side={'bottom'}
+ align={'center'}
+ >
+
+
+
+ {children}
+
+
+
+
+
+
+
+ );
+});
+
+export const TradingOption = forwardRef<
+ React.ElementRef,
+ React.ComponentProps
+>(({ children, className, ...props }, forwardedRef) => (
+
+ {children}
+
+
+
+
+));
diff --git a/libs/ui-toolkit/src/utils/shared.ts b/libs/ui-toolkit/src/utils/shared.ts
index 7ea258c01..dd407c1ea 100644
--- a/libs/ui-toolkit/src/utils/shared.ts
+++ b/libs/ui-toolkit/src/utils/shared.ts
@@ -1,18 +1,20 @@
import classnames from 'classnames';
-export const defaultSelectElement = (hasError?: boolean) =>
- classnames(defaultFormElement(hasError), 'pr-10 dark:bg-black');
+export const defaultSelectElement = (hasError?: boolean, disabled?: boolean) =>
+ classnames(defaultFormElement(hasError, disabled), 'pr-10 min-h-8 py-1');
-export const defaultFormElement = (hasError?: boolean) =>
+export const defaultFormElement = (hasError?: boolean, disabled?: boolean) =>
classnames(
'flex items-center w-full text-sm',
'p-2 rounded whitespace-nowrap text-ellipsis overflow-hidden',
- 'bg-transparent',
'border',
- 'focus:border-vega-light-300 dark:focus:border-vega-dark-300',
- 'disabled:opacity-60',
+ 'focus:border-vega-clight-400 dark:focus:border-vega-cdark-400',
{
- 'border-vega-pink text-vega-pink': hasError,
- 'border-vega-light-200 dark:border-vega-dark-200': !hasError,
+ 'bg-vega-clight-700 dark:bg-vega-cdark-700': !disabled && !hasError,
+ 'bg-transparent': disabled || hasError,
+ 'border-vega-clight-600 dark:border-vega-cdark-600': disabled,
+ 'border-vega-red-500': !disabled && hasError,
+ 'border-vega-clight-500 dark:border-vega-cdark-500':
+ !disabled && !hasError,
}
);
diff --git a/libs/utils/package.json b/libs/utils/package.json
index d94e8ca20..5b41a5820 100644
--- a/libs/utils/package.json
+++ b/libs/utils/package.json
@@ -1,5 +1,5 @@
{
"name": "@vegaprotocol/utils",
- "version": "0.0.7",
+ "version": "0.0.8",
"type": "commonjs"
}
diff --git a/libs/wallet/src/connect-dialog/connect-dialog.tsx b/libs/wallet/src/connect-dialog/connect-dialog.tsx
index 95006ac49..8714e5729 100644
--- a/libs/wallet/src/connect-dialog/connect-dialog.tsx
+++ b/libs/wallet/src/connect-dialog/connect-dialog.tsx
@@ -2,11 +2,11 @@ import classNames from 'classnames';
import { create } from 'zustand';
import {
Dialog,
- FormGroup,
- Input,
Intent,
Pill,
TradingButton,
+ TradingFormGroup,
+ TradingInput,
VegaIcon,
VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
@@ -421,17 +421,17 @@ const CustomUrlInput = ({
{t('Go back')}
-
- setWalletUrl(e.target.value)}
name="wallet-url"
/>
-
+
-
-
+
{errors.address?.message && (
- {errors.address.message}
+
+ {errors.address.message}
+
)}
-
+
;
}) => {
return (
- }
/>
))}
-
+
);
};
@@ -193,7 +193,7 @@ export const WithdrawForm = ({
noValidate={true}
data-testid="withdraw-form"
>
-
+
{errors.asset?.message && (
- {errors.asset.message}
+
+ {errors.asset.message}
+
)}
-
-
+
@@ -218,15 +220,17 @@ export const WithdrawForm = ({
clearErrors('to');
}}
/>
-
{errors.to?.message && (
- {errors.to.message}
+
+ {errors.to.message}
+
)}
-
+
{selectedAsset && threshold && (
)}
-
-
+
{errors.amount?.message && (
- {errors.amount.message}
+
+ {errors.amount.message}
+
)}
{selectedAsset && (
)}
-
+
1005-VEST-025
- **must** see how many tokens in each tranche are redeemable 1005-VEST-026
- **must** see an option to redeem from tranche 1005-VEST-027
- - **must** be warned if amount that can be redeemed from that tranche is greater than the un-associated balance for that Eth key (because this will cause the redeem function to fail) 1005-VEST-028
- **should** see how many tokens I'd need to disassociate to be able to run the redeem function (this should be rounded up to avoid the transaction failing due to more tokens having unlocked since the user looked at the form)
- **should** see link to [disassociate](1004-ASSO-associate.md)
diff --git a/specs/7004-POSI-positions.md b/specs/7004-POSI-positions.md
index 3bcb69493..5965f24f8 100644
--- a/specs/7004-POSI-positions.md
+++ b/specs/7004-POSI-positions.md
@@ -51,4 +51,8 @@
- **Must** be able to see if your realised PnL was affected by loss socialisation (7004-POSI-018 )
-- **Must** Must be able to see what type of product the position was opened on (7004-POSI-019 )
+- **Must** be able to see what type of product the position was opened on (7004-POSI-019 )
+
+- **Must** not see positions on markets which are closed (7004-POSI-020 )
+
+- **Must** be able to show closed markets (7004-POSI-021 )