76c07992d3
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
200 lines
5.9 KiB
TypeScript
200 lines
5.9 KiB
TypeScript
import { type PinnedAsset } from '@vegaprotocol/accounts';
|
|
import { type Market } from '@vegaprotocol/markets';
|
|
// TODO: handle oracle banner
|
|
// import { OracleBanner } from '@vegaprotocol/markets';
|
|
import { useState } from 'react';
|
|
import classNames from 'classnames';
|
|
import {
|
|
Popover,
|
|
Splash,
|
|
VegaIcon,
|
|
VegaIconNames,
|
|
} from '@vegaprotocol/ui-toolkit';
|
|
import { useT } from '../../lib/use-t';
|
|
import { ErrorBoundary } from '../../components/error-boundary';
|
|
import { type TradingView } from './trade-views';
|
|
import { TradingViews } from './trade-views';
|
|
interface TradePanelsProps {
|
|
market: Market;
|
|
pinnedAsset?: PinnedAsset;
|
|
}
|
|
|
|
export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
|
|
const [topView, setTopView] = useState<TradingView>('chart');
|
|
const topViewCfg = TradingViews[topView];
|
|
const [bottomView, setBottomView] = useState<TradingView>('positions');
|
|
const bottomViewCfg = TradingViews[bottomView];
|
|
|
|
const renderView = (view: TradingView) => {
|
|
const Component = TradingViews[view].component;
|
|
|
|
if (!Component) {
|
|
throw new Error(`No component for view: ${view}`);
|
|
}
|
|
|
|
if (!market) return <NoMarketSplash />;
|
|
|
|
// Watch out here, we don't know what component is being rendered
|
|
// so watch out for clashes in props
|
|
return (
|
|
<ErrorBoundary feature={view}>
|
|
<Component marketId={market?.id} pinnedAsset={pinnedAsset} />
|
|
</ErrorBoundary>
|
|
);
|
|
};
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const renderMenu = (viewCfg: any) => {
|
|
if ('menu' in viewCfg || 'settings' in viewCfg) {
|
|
return (
|
|
<div className="flex items-center justify-end gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
|
|
{'menu' in viewCfg ? <viewCfg.menu /> : null}
|
|
{'settings' in viewCfg ? (
|
|
<Popover
|
|
align="end"
|
|
trigger={
|
|
<span className="ml-1 flex items-center justify-center h-6 w-6">
|
|
<VegaIcon name={VegaIconNames.COG} size={16} />
|
|
</span>
|
|
}
|
|
>
|
|
<div className="p-4 flex justify-end">
|
|
<viewCfg.settings />
|
|
</div>
|
|
</Popover>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="h-full flex flex-col lg:grid grid-rows-[min-content_min-content_1fr_min-content]">
|
|
<div className="flex flex-col w-full overflow-hidden">
|
|
<div className="flex flex-nowrap overflow-x-auto max-w-full border-t border-default">
|
|
{['chart', 'orderbook', 'trades', 'liquidity', 'fundingPayments']
|
|
// filter to control available views for the current market
|
|
// e.g. only perpetuals should get the funding views
|
|
.filter((_key) => {
|
|
const key = _key as TradingView;
|
|
const perpOnlyViews = ['funding', 'fundingPayments'];
|
|
|
|
if (
|
|
market?.tradableInstrument.instrument.product.__typename ===
|
|
'Perpetual'
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
if (perpOnlyViews.includes(key)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
})
|
|
.map((_key) => {
|
|
const key = _key as TradingView;
|
|
const isActive = topView === key;
|
|
return (
|
|
<ViewButton
|
|
key={key}
|
|
view={key}
|
|
isActive={isActive}
|
|
onClick={() => {
|
|
setTopView(key);
|
|
}}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
<div className="h-[50vh] lg:h-full relative">
|
|
<div>{renderMenu(topViewCfg)}</div>
|
|
<div className="overflow-auto h-full">{renderView(topView)}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex flex-col w-full grow">
|
|
<div className="flex flex-nowrap overflow-x-auto max-w-full border-t border-default">
|
|
{[
|
|
'positions',
|
|
'activeOrders',
|
|
'closedOrders',
|
|
'rejectedOrders',
|
|
'orders',
|
|
'stopOrders',
|
|
'collateral',
|
|
'fills',
|
|
].map((_key) => {
|
|
const key = _key as TradingView;
|
|
const isActive = bottomView === key;
|
|
return (
|
|
<ViewButton
|
|
key={key}
|
|
view={key}
|
|
isActive={isActive}
|
|
onClick={() => {
|
|
setBottomView(key);
|
|
}}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
<div className="relative grow">
|
|
<div className="flex flex-col">{renderMenu(bottomViewCfg)}</div>
|
|
<div className="overflow-auto h-full">{renderView(bottomView)}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const NoMarketSplash = () => {
|
|
const t = useT();
|
|
return <Splash>{t('No market')}</Splash>;
|
|
};
|
|
|
|
const ViewButton = ({
|
|
view,
|
|
isActive,
|
|
onClick,
|
|
}: {
|
|
view: TradingView;
|
|
isActive: boolean;
|
|
onClick: () => void;
|
|
}) => {
|
|
const label = useViewLabel(view);
|
|
const className = classNames('py-2 px-4 min-w-[100px] capitalize text-sm', {
|
|
'bg-vega-clight-500 dark:bg-vega-cdark-500': isActive,
|
|
});
|
|
|
|
return (
|
|
<button data-testid={view} onClick={onClick} className={className}>
|
|
{label}
|
|
</button>
|
|
);
|
|
};
|
|
|
|
const useViewLabel = (view: TradingView) => {
|
|
const t = useT();
|
|
|
|
const labels = {
|
|
chart: t('Chart'),
|
|
depth: t('Depth'),
|
|
liquidity: t('Liquidity'),
|
|
funding: t('Funding'),
|
|
fundingPayments: t('Funding'),
|
|
orderbook: t('Orderbook'),
|
|
trades: t('Trades'),
|
|
positions: t('Positions'),
|
|
activeOrders: t('Active'),
|
|
closedOrders: t('Closed'),
|
|
rejectedOrders: t('Rejected'),
|
|
orders: t('All'),
|
|
stopOrders: t('Stop'),
|
|
collateral: t('Collateral'),
|
|
fills: t('Fills'),
|
|
};
|
|
|
|
return labels[view];
|
|
};
|