vega-frontend-monorepo/apps/trading/client-pages/market/trade-panels.tsx
m.ray 76c07992d3
feat(trading): update mobile layout (#5718)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2024-02-08 13:24:48 +00:00

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];
};