feat: added asset details dialog to market select (1226) (#1260)
* feat: added asset details dialog to market select (1226) * Update libs/ui-toolkit/src/components/dialog/dialog.tsx Co-authored-by: Matthew Russell <mattrussell36@gmail.com> * removed focus complexity from popover, refactored * chore: after rebase fixes * fix: fixed console-lite asset details dialog func - after rebase * fix: added mock for one failing test, removed console.log * fix: removed increased timeout - not needed Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
@@ -28,11 +28,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const AccountsManager = ({ partyId }: Props) => {
|
||||
const {
|
||||
isAssetDetailsDialogOpen,
|
||||
assetDetailsDialogSymbol,
|
||||
setAssetDetailsDialogOpen,
|
||||
} = useAssetDetailsDialogStore();
|
||||
const { isOpen, symbol, setOpen } = useAssetDetailsDialogStore();
|
||||
const gridRef = useRef<AgGridReact | null>(null);
|
||||
const variables = useMemo(() => ({ partyId }), [partyId]);
|
||||
const update = useMemo(() => accountsManagerUpdate(gridRef), []);
|
||||
@@ -58,9 +54,9 @@ const AccountsManager = ({ partyId }: Props) => {
|
||||
/>
|
||||
</AsyncRenderer>
|
||||
<AssetDetailsDialog
|
||||
assetSymbol={assetDetailsDialogSymbol}
|
||||
open={isAssetDetailsDialogOpen}
|
||||
onChange={(open) => setAssetDetailsDialogOpen(open)}
|
||||
assetSymbol={symbol}
|
||||
open={isOpen}
|
||||
onChange={(open) => setOpen(open)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -42,8 +42,7 @@ const comparator = (
|
||||
};
|
||||
|
||||
const useAccountColumnDefinitions = () => {
|
||||
const { setAssetDetailsDialogOpen, setAssetDetailsDialogSymbol } =
|
||||
useAssetDetailsDialogStore();
|
||||
const { open } = useAssetDetailsDialogStore();
|
||||
const columnDefs: ColDef[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
@@ -60,8 +59,7 @@ const useAccountColumnDefinitions = () => {
|
||||
<button
|
||||
className="hover:underline"
|
||||
onClick={() => {
|
||||
setAssetDetailsDialogOpen(true);
|
||||
setAssetDetailsDialogSymbol(value);
|
||||
open(value);
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
@@ -97,7 +95,7 @@ const useAccountColumnDefinitions = () => {
|
||||
addDecimalsFormatNumber(value, data.asset.decimals),
|
||||
},
|
||||
];
|
||||
}, [setAssetDetailsDialogOpen, setAssetDetailsDialogSymbol]);
|
||||
}, [open]);
|
||||
|
||||
const defaultColDef = useMemo(() => {
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { connectVegaWallet } from '../support/vega-wallet';
|
||||
import { aliasQuery } from '@vegaprotocol/cypress';
|
||||
import { generateNetworkParameters } from '../support/mocks/generate-network-parameters';
|
||||
|
||||
describe('vega wallet', { tags: '@smoke' }, () => {
|
||||
const connectVegaBtn = 'connect-vega-wallet';
|
||||
@@ -66,11 +68,16 @@ describe('ethereum wallet', { tags: '@smoke' }, () => {
|
||||
cy.mockWeb3Provider();
|
||||
// Using portfolio withdrawals tab is it requires Ethereum wallet connection
|
||||
cy.visit('/portfolio');
|
||||
cy.mockGQL((req) => {
|
||||
aliasQuery(req, 'NetworkParamsQuery', generateNetworkParameters());
|
||||
});
|
||||
cy.mockGQLSubscription();
|
||||
cy.get('main[data-testid="portfolio"]').should('exist');
|
||||
cy.getByTestId('Withdrawals').click();
|
||||
});
|
||||
|
||||
it('can connect', () => {
|
||||
cy.wait('@NetworkParamsQuery');
|
||||
cy.getByTestId('connect-eth-wallet-btn').click();
|
||||
cy.getByTestId('web3-connector-list').should('exist');
|
||||
cy.getByTestId('web3-connector-MetaMask').click();
|
||||
|
||||
@@ -20,11 +20,7 @@ function AppBody({ Component, pageProps }: AppProps) {
|
||||
connectDialog: store.connectDialog,
|
||||
update: store.update,
|
||||
}));
|
||||
const {
|
||||
isAssetDetailsDialogOpen,
|
||||
assetDetailsDialogSymbol,
|
||||
setAssetDetailsDialogOpen,
|
||||
} = useAssetDetailsDialogStore();
|
||||
const { isOpen, symbol, trigger, setOpen } = useAssetDetailsDialogStore();
|
||||
const [theme, toggleTheme] = useThemeSwitcher();
|
||||
|
||||
return (
|
||||
@@ -46,9 +42,10 @@ function AppBody({ Component, pageProps }: AppProps) {
|
||||
setDialogOpen={(open) => update({ connectDialog: open })}
|
||||
/>
|
||||
<AssetDetailsDialog
|
||||
assetSymbol={assetDetailsDialogSymbol}
|
||||
open={isAssetDetailsDialogOpen}
|
||||
onChange={(open) => setAssetDetailsDialogOpen(open)}
|
||||
assetSymbol={symbol}
|
||||
trigger={trigger || null}
|
||||
open={isOpen}
|
||||
onChange={setOpen}
|
||||
/>
|
||||
<RiskNoticeDialog />
|
||||
</AppLoader>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { SelectMarketDialog } from '@vegaprotocol/market-list';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { ColumnKind, SelectMarketDialog } from '@vegaprotocol/market-list';
|
||||
import { t } from '@vegaprotocol/react-helpers';
|
||||
import { Interval } from '@vegaprotocol/types';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
@@ -91,6 +92,7 @@ const MarketPage = ({ id }: { id?: string }) => {
|
||||
const { update: updateStore } = useGlobalStore((store) => ({
|
||||
update: store.update,
|
||||
}));
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
|
||||
// Default to first marketId query item if found
|
||||
const marketId =
|
||||
@@ -152,6 +154,11 @@ const MarketPage = ({ id }: { id?: string }) => {
|
||||
update({ landingDialog: isOpen })
|
||||
}
|
||||
onSelect={onSelect}
|
||||
onCellClick={(e, kind, value) => {
|
||||
if (value && kind === ColumnKind.Asset) {
|
||||
openAssetDetailsDialog(value, e.target as HTMLElement);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { DealTicketContainer } from '@vegaprotocol/deal-ticket';
|
||||
import { MarketInfoContainer } from '@vegaprotocol/market-info';
|
||||
import { OrderbookContainer } from '@vegaprotocol/market-depth';
|
||||
import { ColumnKind, SelectMarketPopover } from '@vegaprotocol/market-list';
|
||||
import type { OnCellClickHandler } from '@vegaprotocol/market-list';
|
||||
import { OrderListContainer } from '@vegaprotocol/orders';
|
||||
import { FillsContainer } from '@vegaprotocol/fills';
|
||||
import { PositionsContainer } from '@vegaprotocol/positions';
|
||||
@@ -28,7 +30,6 @@ import {
|
||||
getDateFormat,
|
||||
t,
|
||||
} from '@vegaprotocol/react-helpers';
|
||||
import { SelectMarketPopover } from '@vegaprotocol/market-list';
|
||||
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
|
||||
import { useEnvironment } from '@vegaprotocol/environment';
|
||||
import type { CandleClose } from '@vegaprotocol/types';
|
||||
@@ -116,8 +117,7 @@ export const TradeMarketHeader = ({
|
||||
onSelect,
|
||||
}: TradeMarketHeaderProps) => {
|
||||
const { VEGA_EXPLORER_URL } = useEnvironment();
|
||||
const { setAssetDetailsDialogOpen, setAssetDetailsDialogSymbol } =
|
||||
useAssetDetailsDialogStore();
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
|
||||
const candlesClose: string[] = (market?.candlesConnection?.edges || [])
|
||||
.map((candle) => candle?.node.close)
|
||||
@@ -125,12 +125,19 @@ export const TradeMarketHeader = ({
|
||||
const symbol =
|
||||
market.tradableInstrument.instrument.product?.settlementAsset?.symbol;
|
||||
|
||||
const onCellClick: OnCellClickHandler = (e, kind, value) => {
|
||||
if (value && kind === ColumnKind.Asset) {
|
||||
openAssetDetailsDialog(value, e.target as HTMLElement);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Header
|
||||
title={
|
||||
<SelectMarketPopover
|
||||
marketName={market.tradableInstrument.instrument.name}
|
||||
onSelect={onSelect}
|
||||
onCellClick={onCellClick}
|
||||
/>
|
||||
}
|
||||
>
|
||||
@@ -196,9 +203,8 @@ export const TradeMarketHeader = ({
|
||||
<HeaderStat heading={t('Settlement asset')}>
|
||||
<div data-testid="trading-mode">
|
||||
<ButtonLink
|
||||
onClick={() => {
|
||||
setAssetDetailsDialogOpen(true);
|
||||
setAssetDetailsDialogSymbol(symbol);
|
||||
onClick={(e) => {
|
||||
openAssetDetailsDialog(symbol, e.target as HTMLElement);
|
||||
}}
|
||||
>
|
||||
{symbol}
|
||||
|
||||
@@ -13,7 +13,6 @@ export const WithdrawalsContainer = () => {
|
||||
const { withdrawals, loading, error } = useWithdrawals();
|
||||
const [withdrawDialog, setWithdrawDialog] = useState(false);
|
||||
|
||||
console.log('render');
|
||||
return (
|
||||
<Web3Container>
|
||||
<VegaWalletContainer>
|
||||
|
||||
@@ -92,8 +92,7 @@ const comparator = (
|
||||
|
||||
export const AccountsTable = forwardRef<AgGridReact, AccountsTableProps>(
|
||||
({ data }, ref) => {
|
||||
const { setAssetDetailsDialogOpen, setAssetDetailsDialogSymbol } =
|
||||
useAssetDetailsDialogStore();
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
@@ -130,9 +129,8 @@ export const AccountsTable = forwardRef<AgGridReact, AccountsTableProps>(
|
||||
value && value.length > 0 ? (
|
||||
<button
|
||||
className="hover:underline"
|
||||
onClick={() => {
|
||||
setAssetDetailsDialogOpen(true);
|
||||
setAssetDetailsDialogSymbol(value);
|
||||
onClick={(e) => {
|
||||
openAssetDetailsDialog(value, e.target as HTMLElement);
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
query Asset($assetId: ID!) {
|
||||
asset(assetId: $assetId) {
|
||||
id
|
||||
name
|
||||
symbol
|
||||
decimals
|
||||
quantum
|
||||
source {
|
||||
... on ERC20 {
|
||||
contractAddress
|
||||
lifetimeLimit
|
||||
withdrawThreshold
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import * as Types from '@vegaprotocol/types/types';
|
||||
|
||||
import { gql } from '@apollo/client';
|
||||
import * as Apollo from '@apollo/client';
|
||||
const defaultOptions = {} as const;
|
||||
export type AssetQueryVariables = Types.Exact<{
|
||||
assetId: Types.Scalars['ID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type AssetQuery = { __typename?: 'Query', asset?: { __typename?: 'Asset', id: string, name: string, symbol: string, decimals: number, quantum: string, source: { __typename?: 'BuiltinAsset' } | { __typename?: 'ERC20', contractAddress: string, lifetimeLimit: string, withdrawThreshold: string } } | null };
|
||||
|
||||
|
||||
export const AssetDocument = gql`
|
||||
query Asset($assetId: ID!) {
|
||||
asset(assetId: $assetId) {
|
||||
id
|
||||
name
|
||||
symbol
|
||||
decimals
|
||||
quantum
|
||||
source {
|
||||
... on ERC20 {
|
||||
contractAddress
|
||||
lifetimeLimit
|
||||
withdrawThreshold
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useAssetQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useAssetQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useAssetQuery` returns an object from Apollo Client that contains loading, error, and data properties
|
||||
* you can use to render your UI.
|
||||
*
|
||||
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
|
||||
*
|
||||
* @example
|
||||
* const { data, loading, error } = useAssetQuery({
|
||||
* variables: {
|
||||
* assetId: // value for 'assetId'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useAssetQuery(baseOptions: Apollo.QueryHookOptions<AssetQuery, AssetQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<AssetQuery, AssetQueryVariables>(AssetDocument, options);
|
||||
}
|
||||
export function useAssetLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<AssetQuery, AssetQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<AssetQuery, AssetQueryVariables>(AssetDocument, options);
|
||||
}
|
||||
export type AssetQueryHookResult = ReturnType<typeof useAssetQuery>;
|
||||
export type AssetLazyQueryHookResult = ReturnType<typeof useAssetLazyQuery>;
|
||||
export type AssetQueryResult = Apollo.QueryResult<AssetQuery, AssetQueryVariables>;
|
||||
@@ -14,21 +14,27 @@ import type { Schema } from '@vegaprotocol/types';
|
||||
import create from 'zustand';
|
||||
|
||||
export type AssetDetailsDialogStore = {
|
||||
isAssetDetailsDialogOpen: boolean;
|
||||
assetDetailsDialogSymbol: string | Asset;
|
||||
setAssetDetailsDialogOpen: (isOpen: boolean) => void;
|
||||
setAssetDetailsDialogSymbol: (symbol: string | Asset) => void;
|
||||
isOpen: boolean;
|
||||
symbol: string | Asset;
|
||||
trigger: HTMLElement | null | undefined;
|
||||
setOpen: (isOpen: boolean) => void;
|
||||
open: (symbol: string | Asset, trigger?: HTMLElement | null) => void;
|
||||
};
|
||||
|
||||
export const useAssetDetailsDialogStore = create<AssetDetailsDialogStore>(
|
||||
(set) => ({
|
||||
isAssetDetailsDialogOpen: false,
|
||||
assetDetailsDialogSymbol: '',
|
||||
setAssetDetailsDialogOpen: (isOpen: boolean) => {
|
||||
set({ isAssetDetailsDialogOpen: isOpen });
|
||||
isOpen: false,
|
||||
symbol: '',
|
||||
trigger: null,
|
||||
setOpen: (isOpen) => {
|
||||
set({ isOpen: isOpen });
|
||||
},
|
||||
setAssetDetailsDialogSymbol: (symbol: string | Asset) => {
|
||||
set({ assetDetailsDialogSymbol: symbol });
|
||||
open: (symbol, trigger?) => {
|
||||
set({
|
||||
isOpen: true,
|
||||
symbol: symbol,
|
||||
trigger: trigger,
|
||||
});
|
||||
},
|
||||
})
|
||||
);
|
||||
@@ -42,12 +48,14 @@ type AssetDetails = {
|
||||
|
||||
export interface AssetDetailsDialogProps {
|
||||
assetSymbol: string | Asset;
|
||||
trigger?: HTMLElement | null;
|
||||
open: boolean;
|
||||
onChange: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export const AssetDetailsDialog = ({
|
||||
assetSymbol,
|
||||
trigger,
|
||||
open,
|
||||
onChange,
|
||||
}: AssetDetailsDialogProps) => {
|
||||
@@ -148,10 +156,25 @@ export const AssetDetailsDialog = ({
|
||||
icon={<Icon name="info-sign"></Icon>}
|
||||
open={open}
|
||||
onChange={(isOpen) => onChange(isOpen)}
|
||||
onCloseAutoFocus={(e) => {
|
||||
/**
|
||||
* This mimics radix's default behaviour that focuses the dialog's
|
||||
* trigger after closing itself
|
||||
*/
|
||||
if (trigger) {
|
||||
e.preventDefault();
|
||||
trigger.focus();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
<div className="w-1/4">
|
||||
<Button fill={true} size="sm" onClick={() => onChange(false)}>
|
||||
<Button
|
||||
data-testid="close-asset-details-dialog"
|
||||
fill={true}
|
||||
size="sm"
|
||||
onClick={() => onChange(false)}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -65,8 +65,7 @@ export const DepositForm = ({
|
||||
allowance,
|
||||
isFaucetable,
|
||||
}: DepositFormProps) => {
|
||||
const { setAssetDetailsDialogOpen, setAssetDetailsDialogSymbol } =
|
||||
useAssetDetailsDialogStore();
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
const { account } = useWeb3React();
|
||||
const { keypair } = useVegaWallet();
|
||||
const {
|
||||
@@ -185,9 +184,8 @@ export const DepositForm = ({
|
||||
<button
|
||||
data-testid="view-asset-details"
|
||||
className="text-sm underline"
|
||||
onClick={() => {
|
||||
setAssetDetailsDialogOpen(true);
|
||||
setAssetDetailsDialogSymbol(selectedAsset);
|
||||
onClick={(e) => {
|
||||
openAssetDetailsDialog(selectedAsset, e.target as HTMLElement);
|
||||
}}
|
||||
>
|
||||
{t('View asset details')}
|
||||
|
||||
@@ -37,8 +37,7 @@ type MarketListTableValueFormatterParams = Omit<
|
||||
export const getRowId = ({ data }: { data: { id: string } }) => data.id;
|
||||
|
||||
export const MarketListTable = forwardRef<AgGridReact, Props>((props, ref) => {
|
||||
const { setAssetDetailsDialogOpen, setAssetDetailsDialogSymbol } =
|
||||
useAssetDetailsDialogStore();
|
||||
const { open: openAssetDetailsDialog } = useAssetDetailsDialogStore();
|
||||
return (
|
||||
<AgGrid
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
@@ -67,9 +66,8 @@ export const MarketListTable = forwardRef<AgGridReact, Props>((props, ref) => {
|
||||
value && value.length > 0 ? (
|
||||
<button
|
||||
className="hover:underline"
|
||||
onClick={() => {
|
||||
setAssetDetailsDialogOpen(true);
|
||||
setAssetDetailsDialogSymbol(value);
|
||||
onClick={(e) => {
|
||||
openAssetDetailsDialog(value, e.target as HTMLElement);
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
|
||||
@@ -45,65 +45,96 @@ const FeesInfo = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export enum ColumnKind {
|
||||
Market,
|
||||
LastPrice,
|
||||
Change24,
|
||||
Asset,
|
||||
Sparkline,
|
||||
High24,
|
||||
Low24,
|
||||
TradingMode,
|
||||
Volume,
|
||||
Fee,
|
||||
Position,
|
||||
FullName,
|
||||
}
|
||||
|
||||
export interface Column {
|
||||
kind: ColumnKind;
|
||||
value: string | React.ReactNode;
|
||||
className: string;
|
||||
onlyOnDetailed: boolean;
|
||||
dataTestId?: string;
|
||||
}
|
||||
|
||||
export const columnHeadersPositionMarkets: Column[] = [
|
||||
const headers: Column[] = [
|
||||
{
|
||||
kind: ColumnKind.Market,
|
||||
value: t('Market'),
|
||||
className: cellClassNames,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.LastPrice,
|
||||
value: t('Last price'),
|
||||
className: cellClassNames,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Change24,
|
||||
value: t('Change (24h)'),
|
||||
className: cellClassNames,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Asset,
|
||||
value: t('Settlement asset'),
|
||||
className: `${cellClassNames} hidden sm:table-cell`,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Sparkline,
|
||||
value: t(''),
|
||||
className: `${cellClassNames} hidden lg:table-cell`,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.High24,
|
||||
value: t('24h high'),
|
||||
className: `${cellClassNames} hidden xl:table-cell`,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Low24,
|
||||
value: t('24h low'),
|
||||
className: `${cellClassNames} hidden xl:table-cell`,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.TradingMode,
|
||||
value: t('Trading mode'),
|
||||
className: `${cellClassNames} hidden lg:table-cell`,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Volume,
|
||||
value: t('Volume'),
|
||||
className: `${cellClassNames} hidden lg:table-cell`,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Fee,
|
||||
value: <FeesInfo />,
|
||||
className: `${cellClassNames} hidden xl:table-cell`,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const columnHeadersPositionMarkets: Column[] = [
|
||||
...headers,
|
||||
{
|
||||
kind: ColumnKind.Position,
|
||||
value: t('Position'),
|
||||
className: `${cellClassNames} hidden xxl:table-cell`,
|
||||
onlyOnDetailed: true,
|
||||
@@ -111,68 +142,27 @@ export const columnHeadersPositionMarkets: Column[] = [
|
||||
];
|
||||
|
||||
export const columnHeaders: Column[] = [
|
||||
...headers,
|
||||
{
|
||||
value: t('Market'),
|
||||
className: cellClassNames,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: t('Last price'),
|
||||
className: cellClassNames,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: t('Change (24h)'),
|
||||
className: cellClassNames,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: t('Settlement asset'),
|
||||
className: `${cellClassNames} hidden sm:table-cell`,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: t(''),
|
||||
className: `${cellClassNames} hidden lg:table-cell`,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value: t('24h high'),
|
||||
className: `${cellClassNames} hidden xl:table-cell`,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: t('24h low'),
|
||||
className: `${cellClassNames} hidden xl:table-cell`,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: t('Trading mode'),
|
||||
className: `${cellClassNames} hidden lg:table-cell`,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: t('Volume'),
|
||||
className: `${cellClassNames} hidden lg:table-cell`,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
value: <FeesInfo />,
|
||||
className: `${cellClassNames} hidden xl:table-cell`,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.FullName,
|
||||
value: t('Full name'),
|
||||
className: `${cellClassNames} hidden xxl:block`,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
];
|
||||
|
||||
export type OnCellClickHandler = (
|
||||
e: React.MouseEvent,
|
||||
kind: ColumnKind,
|
||||
value: string
|
||||
) => void;
|
||||
|
||||
export const columns = (
|
||||
market: Market,
|
||||
marketData: MarketData | undefined,
|
||||
candles: Candle[] | undefined,
|
||||
onSelect: (id: string) => void
|
||||
onSelect: (id: string) => void,
|
||||
onCellClick: OnCellClickHandler
|
||||
) => {
|
||||
const candlesClose = candles
|
||||
?.map((candle) => candle?.close)
|
||||
@@ -189,6 +179,7 @@ export const columns = (
|
||||
const candleHigh = candles && calcCandleHigh(candles);
|
||||
const selectMarketColumns: Column[] = [
|
||||
{
|
||||
kind: ColumnKind.Market,
|
||||
value: (
|
||||
<Link href={`/markets/${market.id}`} passHref={true}>
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid,jsx-a11y/no-static-element-interactions */}
|
||||
@@ -207,6 +198,7 @@ export const columns = (
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.LastPrice,
|
||||
value: marketData?.markPrice ? (
|
||||
<PriceCell
|
||||
value={Number(marketData?.markPrice)}
|
||||
@@ -223,6 +215,7 @@ export const columns = (
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Change24,
|
||||
value: candlesClose && (
|
||||
<PriceCellChange
|
||||
candles={candlesClose}
|
||||
@@ -233,13 +226,29 @@ export const columns = (
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value:
|
||||
market.tradableInstrument.instrument.product.settlementAsset.symbol,
|
||||
kind: ColumnKind.Asset,
|
||||
value: (
|
||||
<button
|
||||
data-dialog-trigger
|
||||
className="inline hover:underline"
|
||||
onClick={(e) =>
|
||||
onCellClick(
|
||||
e,
|
||||
ColumnKind.Asset,
|
||||
market.tradableInstrument.instrument.product.settlementAsset
|
||||
.symbol
|
||||
)
|
||||
}
|
||||
>
|
||||
{market.tradableInstrument.instrument.product.settlementAsset.symbol}
|
||||
</button>
|
||||
),
|
||||
dataTestId: 'settlement-asset',
|
||||
className: `${cellClassNames} hidden sm:table-cell`,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Sparkline,
|
||||
value: candles && (
|
||||
<Sparkline
|
||||
width={100}
|
||||
@@ -252,6 +261,7 @@ export const columns = (
|
||||
onlyOnDetailed: false && candlesClose,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.High24,
|
||||
value: candleHigh ? (
|
||||
<PriceCell
|
||||
value={Number(candleHigh)}
|
||||
@@ -268,6 +278,7 @@ export const columns = (
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Low24,
|
||||
value: candleLow ? (
|
||||
<PriceCell
|
||||
value={Number(candleLow)}
|
||||
@@ -284,6 +295,7 @@ export const columns = (
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.TradingMode,
|
||||
value:
|
||||
market.tradingMode ===
|
||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
||||
@@ -297,6 +309,7 @@ export const columns = (
|
||||
dataTestId: 'trading-mode',
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Volume,
|
||||
value:
|
||||
marketData?.indicativeVolume && marketData.indicativeVolume !== '0'
|
||||
? addDecimalsFormatNumber(
|
||||
@@ -309,12 +322,14 @@ export const columns = (
|
||||
dataTestId: 'market-volume',
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Fee,
|
||||
value: <FeesCell feeFactors={market.fees.factors} />,
|
||||
className: `${cellClassNames} hidden xl:table-cell font-mono`,
|
||||
onlyOnDetailed: true,
|
||||
dataTestId: 'taker-fee',
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.FullName,
|
||||
value: market.tradableInstrument.instrument.name,
|
||||
className: `${cellClassNames} hidden xxl:block`,
|
||||
onlyOnDetailed: true,
|
||||
@@ -329,7 +344,8 @@ export const columnsPositionMarkets = (
|
||||
marketData: MarketData | undefined,
|
||||
candles: Candle[] | undefined,
|
||||
onSelect: (id: string) => void,
|
||||
openVolume?: string
|
||||
openVolume?: string,
|
||||
onCellClick?: OnCellClickHandler
|
||||
) => {
|
||||
const candlesClose = candles
|
||||
?.map((candle) => candle?.close)
|
||||
@@ -346,6 +362,7 @@ export const columnsPositionMarkets = (
|
||||
};
|
||||
const selectMarketColumns: Column[] = [
|
||||
{
|
||||
kind: ColumnKind.Market,
|
||||
value: (
|
||||
<Link href={`/markets/${market.id}`} passHref={true}>
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-is-valid,jsx-a11y/no-static-element-interactions */}
|
||||
@@ -364,6 +381,7 @@ export const columnsPositionMarkets = (
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.LastPrice,
|
||||
value: marketData?.markPrice ? (
|
||||
<PriceCell
|
||||
value={Number(marketData.markPrice)}
|
||||
@@ -380,6 +398,7 @@ export const columnsPositionMarkets = (
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Change24,
|
||||
value: candlesClose && (
|
||||
<PriceCellChange
|
||||
candles={candlesClose}
|
||||
@@ -390,12 +409,29 @@ export const columnsPositionMarkets = (
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
value:
|
||||
market.tradableInstrument.instrument.product.settlementAsset.symbol,
|
||||
kind: ColumnKind.Asset,
|
||||
value: (
|
||||
<button
|
||||
data-dialog-trigger
|
||||
className="inline hover:underline"
|
||||
onClick={(e) => {
|
||||
if (!onCellClick) return;
|
||||
onCellClick(
|
||||
e,
|
||||
ColumnKind.Asset,
|
||||
market.tradableInstrument.instrument.product.settlementAsset
|
||||
.symbol
|
||||
);
|
||||
}}
|
||||
>
|
||||
{market.tradableInstrument.instrument.product.settlementAsset.symbol}
|
||||
</button>
|
||||
),
|
||||
className: `${cellClassNames} hidden sm:table-cell`,
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Sparkline,
|
||||
value: candlesClose && (
|
||||
<Sparkline
|
||||
width={100}
|
||||
@@ -408,6 +444,7 @@ export const columnsPositionMarkets = (
|
||||
onlyOnDetailed: false,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.High24,
|
||||
value: candleHigh ? (
|
||||
<PriceCell
|
||||
value={Number(candleHigh)}
|
||||
@@ -424,6 +461,7 @@ export const columnsPositionMarkets = (
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Low24,
|
||||
value: candleLow ? (
|
||||
<PriceCell
|
||||
value={Number(candleLow)}
|
||||
@@ -440,6 +478,7 @@ export const columnsPositionMarkets = (
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.TradingMode,
|
||||
value:
|
||||
market.tradingMode ===
|
||||
MarketTradingMode.TRADING_MODE_MONITORING_AUCTION &&
|
||||
@@ -453,6 +492,7 @@ export const columnsPositionMarkets = (
|
||||
dataTestId: 'trading-mode',
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Volume,
|
||||
value:
|
||||
marketData && marketData.indicativeVolume !== '0'
|
||||
? addDecimalsFormatNumber(
|
||||
@@ -464,11 +504,13 @@ export const columnsPositionMarkets = (
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Fee,
|
||||
value: <FeesCell feeFactors={market.fees.factors} />,
|
||||
className: `${cellClassNames} hidden xl:table-cell font-mono`,
|
||||
onlyOnDetailed: true,
|
||||
},
|
||||
{
|
||||
kind: ColumnKind.Position,
|
||||
value: (
|
||||
<p className={signedNumberCssClass(openVolume || '')}>{openVolume}</p>
|
||||
),
|
||||
|
||||
@@ -8,7 +8,7 @@ export const SelectMarketTableHeader = ({
|
||||
}) => {
|
||||
return (
|
||||
<tr className="sticky top-0 z-10 border-b border-default bg-inherit">
|
||||
{headers.map(({ value, className, onlyOnDetailed }, i) => {
|
||||
{headers.map(({ kind, value, className, onlyOnDetailed }) => {
|
||||
const thClass = classNames(
|
||||
'font-normal text-neutral-500 dark:text-neutral-400',
|
||||
className
|
||||
@@ -16,7 +16,7 @@ export const SelectMarketTableHeader = ({
|
||||
|
||||
if (!onlyOnDetailed || detailed === onlyOnDetailed) {
|
||||
return (
|
||||
<th key={i} className={thClass}>
|
||||
<th key={kind} className={thClass}>
|
||||
{value}
|
||||
</th>
|
||||
);
|
||||
@@ -39,11 +39,11 @@ export const SelectMarketTableRow = ({
|
||||
<tr
|
||||
className={`hover:bg-neutral-200 dark:hover:bg-neutral-700 cursor-pointer relative h-[34px]`}
|
||||
>
|
||||
{columns.map(({ value, className, dataTestId, onlyOnDetailed }, i) => {
|
||||
{columns.map(({ kind, value, className, dataTestId, onlyOnDetailed }) => {
|
||||
if (!onlyOnDetailed || detailed === onlyOnDetailed) {
|
||||
const tdClass = classNames(className);
|
||||
return (
|
||||
<td key={i} data-testid={dataTestId} className={tdClass}>
|
||||
<td key={kind} data-testid={dataTestId} className={tdClass}>
|
||||
{value}
|
||||
</td>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import type { Column } from './select-market-columns';
|
||||
import type { Column, OnCellClickHandler } from './select-market-columns';
|
||||
import {
|
||||
columnHeadersPositionMarkets,
|
||||
columnsPositionMarkets,
|
||||
@@ -36,11 +36,13 @@ export const SelectMarketLandingTable = ({
|
||||
marketsData,
|
||||
marketsCandles,
|
||||
onSelect,
|
||||
onCellClick,
|
||||
}: {
|
||||
markets: Market[] | undefined;
|
||||
marketsData: MarketData[] | undefined;
|
||||
marketsCandles: MarketCandles[] | undefined;
|
||||
onSelect: (id: string) => void;
|
||||
onCellClick: OnCellClickHandler;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
@@ -65,7 +67,8 @@ export const SelectMarketLandingTable = ({
|
||||
marketsCandles?.find(
|
||||
(marketCandles) => marketCandles.marketId === market.id
|
||||
)?.candles,
|
||||
onSelect
|
||||
onSelect,
|
||||
onCellClick
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
@@ -83,9 +86,10 @@ export const SelectAllMarketsTableBody = ({
|
||||
marketsCandles,
|
||||
positions,
|
||||
onSelect,
|
||||
onCellClick,
|
||||
headers = columnHeaders,
|
||||
tableColumns = (market, marketData, candles) =>
|
||||
columns(market, marketData, candles, onSelect),
|
||||
columns(market, marketData, candles, onSelect, onCellClick),
|
||||
}: {
|
||||
markets: Market[] | undefined;
|
||||
marketsData: MarketData[] | undefined;
|
||||
@@ -93,6 +97,7 @@ export const SelectAllMarketsTableBody = ({
|
||||
positions?: Positions_party_positionsConnection_edges_node[];
|
||||
title?: string;
|
||||
onSelect: (id: string) => void;
|
||||
onCellClick: OnCellClickHandler;
|
||||
headers?: Column[];
|
||||
tableColumns?: (
|
||||
market: Market,
|
||||
@@ -134,9 +139,11 @@ export const SelectAllMarketsTableBody = ({
|
||||
export const SelectMarketPopover = ({
|
||||
marketName,
|
||||
onSelect,
|
||||
onCellClick,
|
||||
}: {
|
||||
marketName: string;
|
||||
onSelect: (id: string) => void;
|
||||
onCellClick: OnCellClickHandler;
|
||||
}) => {
|
||||
const triggerClasses =
|
||||
'sm:text-lg md:text-xl lg:text-2xl flex items-center gap-2 whitespace-nowrap hover:text-neutral-500 dark:hover:text-neutral-300';
|
||||
@@ -203,6 +210,7 @@ export const SelectMarketPopover = ({
|
||||
?.filter((edge) => edge.node)
|
||||
.map((edge) => edge.node)}
|
||||
onSelect={onSelectMarket}
|
||||
onCellClick={onCellClick}
|
||||
headers={columnHeadersPositionMarkets}
|
||||
tableColumns={(market, marketData, candles, openVolume) =>
|
||||
columnsPositionMarkets(
|
||||
@@ -210,7 +218,8 @@ export const SelectMarketPopover = ({
|
||||
marketData,
|
||||
candles,
|
||||
onSelectMarket,
|
||||
openVolume
|
||||
openVolume,
|
||||
onCellClick
|
||||
)
|
||||
}
|
||||
/>
|
||||
@@ -223,6 +232,7 @@ export const SelectMarketPopover = ({
|
||||
marketsData={data?.marketsData}
|
||||
marketsCandles={data?.marketsCandles}
|
||||
onSelect={onSelectMarket}
|
||||
onCellClick={onCellClick}
|
||||
/>
|
||||
</table>
|
||||
</>
|
||||
@@ -248,11 +258,13 @@ export const SelectMarketDialog = ({
|
||||
dialogOpen,
|
||||
setDialogOpen,
|
||||
onSelect,
|
||||
onCellClick,
|
||||
}: {
|
||||
dialogOpen: boolean;
|
||||
setDialogOpen: (open: boolean) => void;
|
||||
title?: string;
|
||||
onSelect: (id: string) => void;
|
||||
onCellClick: OnCellClickHandler;
|
||||
}) => {
|
||||
const onSelectMarket = (id: string) => {
|
||||
onSelect(id);
|
||||
@@ -267,16 +279,23 @@ export const SelectMarketDialog = ({
|
||||
onChange={() => setDialogOpen(false)}
|
||||
size="small"
|
||||
>
|
||||
<LandingDialogContainer onSelect={onSelectMarket} />
|
||||
<LandingDialogContainer
|
||||
onSelect={onSelectMarket}
|
||||
onCellClick={onCellClick}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
interface LandingDialogContainerProps {
|
||||
onSelect: (id: string) => void;
|
||||
onCellClick: OnCellClickHandler;
|
||||
}
|
||||
|
||||
const LandingDialogContainer = ({ onSelect }: LandingDialogContainerProps) => {
|
||||
const LandingDialogContainer = ({
|
||||
onSelect,
|
||||
onCellClick,
|
||||
}: LandingDialogContainerProps) => {
|
||||
const { data, loading, error } = useMarketList();
|
||||
if (error) {
|
||||
return (
|
||||
@@ -300,6 +319,7 @@ const LandingDialogContainer = ({ onSelect }: LandingDialogContainerProps) => {
|
||||
marketsData={data?.marketsData}
|
||||
marketsCandles={data?.marketsCandles}
|
||||
onSelect={onSelect}
|
||||
onCellClick={onCellClick}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ interface DialogProps {
|
||||
children: ReactNode;
|
||||
open: boolean;
|
||||
onChange?: (isOpen: boolean) => void;
|
||||
onCloseAutoFocus?: (e: Event) => void;
|
||||
title?: string;
|
||||
icon?: ReactNode;
|
||||
intent?: Intent;
|
||||
@@ -21,6 +22,7 @@ export function Dialog({
|
||||
children,
|
||||
open,
|
||||
onChange,
|
||||
onCloseAutoFocus,
|
||||
title,
|
||||
icon,
|
||||
intent,
|
||||
@@ -59,7 +61,10 @@ export function Dialog({
|
||||
className="fixed inset-0 bg-black/50 z-10"
|
||||
data-testid="dialog-overlay"
|
||||
/>
|
||||
<DialogPrimitives.Content className={contentClasses}>
|
||||
<DialogPrimitives.Content
|
||||
className={contentClasses}
|
||||
onCloseAutoFocus={onCloseAutoFocus}
|
||||
>
|
||||
<div className={wrapperClasses}>
|
||||
{onChange && (
|
||||
<DialogPrimitives.Close
|
||||
|
||||
Reference in New Issue
Block a user