diff --git a/apps/trading/pages/markets/[marketId].page.tsx b/apps/trading/pages/markets/[marketId].page.tsx index fd8d9428e..41e43c891 100644 --- a/apps/trading/pages/markets/[marketId].page.tsx +++ b/apps/trading/pages/markets/[marketId].page.tsx @@ -1,18 +1,10 @@ import { gql } from '@apollo/client'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import classNames from 'classnames'; import { useRouter } from 'next/router'; -import React, { - Children, - isValidElement, - ReactNode, - useCallback, - useEffect, - useState, -} from 'react'; +import React, { useEffect, useState } from 'react'; import debounce from 'lodash/debounce'; -import { Market, MarketVariables, Market_market } from './__generated__/Market'; +import { Market, MarketVariables } from './__generated__/Market'; import { PageQueryContainer } from '../../components/page-query-container'; +import { TradeGrid, TradePanels } from './trade-grid'; // Top level page query const MARKET_QUERY = gql` @@ -56,239 +48,6 @@ const MarketPage = () => { export default MarketPage; -interface TradeGridProps { - market: Market_market; -} - -const TradeGrid = ({ market }: TradeGridProps) => { - const wrapperClasses = classNames( - 'h-full max-h-full', - 'grid gap-[1px] grid-cols-[1fr_325px_325px] grid-rows-[min-content_1fr_200px]', - 'bg-neutral-200', - 'text-ui' - ); - return ( -
-
-

Market: {market.name}

-
- - - - - - - - - -
{JSON.stringify(market.trades, null, 2)}
-
- - - -
-
- - - - - - - - - - - - - -
- ); -}; - -interface TradeGridChildProps { - children: ReactNode; - className?: string; -} - -const TradeGridChild = ({ children, className }: TradeGridChildProps) => { - const gridChildClasses = classNames('bg-white', className); - return ( -
- - {({ width, height }) => ( -
- {children} -
- )} -
-
- ); -}; - -interface GridTabsProps { - children: ReactNode; - group: string; -} - -const GridTabs = ({ children, group }: GridTabsProps) => { - const { query, asPath, replace } = useRouter(); - const [activeTab, setActiveTab] = useState(() => { - if (query[group]) { - return query[group]; - } - - // Default to first tab - return children[0].props.name; - }); - - // Using replace inside an effect causes a render loop. Seems like its not using useCallback - // eslint-disable-next-line - const safeReplace = useCallback((path: string) => replace(path), []); - - useEffect(() => { - const [url, queryString] = asPath.split('?'); - const searchParams = new URLSearchParams(queryString); - searchParams.set(group, activeTab as string); - safeReplace(`${url}?${searchParams.toString()}`); - }, [activeTab, group, asPath, safeReplace]); - - return ( -
- {/* the tabs */} -
- {Children.map(children, (child) => { - if (!isValidElement(child)) return null; - const isActive = activeTab === child.props.name; - const buttonClass = classNames( - 'py-4', - 'px-12', - 'border-t border-neutral-200', - 'capitalize', - { - 'text-vega-pink': isActive, - 'bg-white': isActive, - } - ); - return ( - - ); - })} -
- {/* the content */} -
- {Children.map(children, (child) => { - if (isValidElement(child) && activeTab === child.props.name) { - return ( -
- {child.props.children} -
- ); - } - return null; - })} -
-
- ); -}; - -interface GridTabProps { - children: ReactNode; - name: string; -} - -const GridTab = ({ children }: GridTabProps) => { - return
{children}
; -}; - -///// SMALL SCREENS /////// - -type View = keyof typeof Views; - -interface TradePanelsProps { - market: Market_market; -} - -const TradePanels = ({ market }: TradePanelsProps) => { - const [view, setView] = React.useState('chart'); - - const renderView = () => { - const Component = Views[view]; - - if (!Component) { - throw new Error(`No component for view: ${view}`); - } - - return ; - }; - - return ( -
-
-

Market: {market.name}

-
-
- - {({ width, height }) => ( -
{renderView()}
- )} -
-
-
- {Object.keys(Views).map((key: View) => { - const className = classNames( - 'p-8', - 'border-t', - 'border-neutral-200', - 'capitalize', - { - 'text-vega-pink': view === key, - 'bg-white': view === key, - } - ); - return ( - - ); - })} -
-
- ); -}; - -const Chart = () =>
TODO: Chart
; -const Ticket = () =>
TODO: Ticket
; -const Orderbook = () =>
TODO: Orderbook
; -const Orders = () =>
TODO: Orders
; -const Positions = () =>
TODO: Positions
; -const Collateral = () =>
TODO: Collateral
; - -const Views = { - chart: Chart, - ticket: Ticket, - orderbook: Orderbook, - orders: Orders, - positions: Positions, - collateral: Collateral, -}; - const useWindowSize = () => { const [windowSize, setWindowSize] = useState(() => { if (typeof window !== 'undefined') { diff --git a/apps/trading/pages/markets/grid-tabs.tsx b/apps/trading/pages/markets/grid-tabs.tsx new file mode 100644 index 000000000..39860a91f --- /dev/null +++ b/apps/trading/pages/markets/grid-tabs.tsx @@ -0,0 +1,140 @@ +import classNames from 'classnames'; +import { useRouter } from 'next/router'; +import { + Children, + isValidElement, + ReactNode, + useCallback, + useEffect, + useState, +} from 'react'; + +interface GridTabsProps { + children: ReactNode; + group: string; +} + +export const GridTabs = ({ children, group }: GridTabsProps) => { + const { query, asPath, replace } = useRouter(); + const [activeTab, setActiveTab] = useState(() => { + if (query[group]) { + return query[group]; + } + + // Default to first tab + return children[0].props.name; + }); + + // Using replace inside an effect causes a render loop. Seems like its not using useCallback + // eslint-disable-next-line + const safeReplace = useCallback((path: string) => replace(path), []); + + // Update the query string in the url when the active tab changes + // uses group property as the query stirng key + useEffect(() => { + const [url, queryString] = asPath.split('?'); + const searchParams = new URLSearchParams(queryString); + searchParams.set(group, activeTab as string); + safeReplace(`${url}?${searchParams.toString()}`); + }, [activeTab, group, asPath, safeReplace]); + + return ( +
+ {/* the tabs */} +
+ {Children.map(children, (child) => { + if (isValidElement(child)) { + return ( + setActiveTab(child.props.name)} + /> + ); + } + + return null; + })} +
+ {/* the content */} +
+ {Children.map(children, (child) => { + if (isValidElement(child) && activeTab === child.props.name) { + return ( + + {child.props.children} + + ); + } + + return null; + })} +
+
+ ); +}; + +interface GridTabProps { + children: ReactNode; + name: string; +} + +export const GridTab = ({ children }: GridTabProps) => { + return
{children}
; +}; + +interface GridTabControlProps { + group: string; + name: string; + isActive: boolean; + onClick: () => void; +} + +const GridTabControl = ({ + group, + name, + isActive, + onClick, +}: GridTabControlProps) => { + const buttonClass = classNames( + 'py-4', + 'px-12', + 'border-t border-neutral-200', + 'capitalize', + { + 'text-vega-pink': isActive, + 'bg-white': isActive, + } + ); + return ( + + ); +}; + +interface GridTabPanelProps { + group: string; + name: string; + children: ReactNode; +} + +const GridTabPanel = ({ group, name, children }: GridTabPanelProps) => { + return ( +
+ {children} +
+ ); +}; diff --git a/apps/trading/pages/markets/trade-grid.tsx b/apps/trading/pages/markets/trade-grid.tsx new file mode 100644 index 000000000..7e985ac5f --- /dev/null +++ b/apps/trading/pages/markets/trade-grid.tsx @@ -0,0 +1,133 @@ +import classNames from 'classnames'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import { useState, ReactNode } from 'react'; +import { Market_market } from './__generated__/Market'; +import { Views } from './trading-components'; +import { GridTab, GridTabs } from './grid-tabs'; + +interface TradeGridProps { + market: Market_market; +} + +export const TradeGrid = ({ market }: TradeGridProps) => { + const wrapperClasses = classNames( + 'h-full max-h-full', + 'grid gap-[1px] grid-cols-[1fr_325px_325px] grid-rows-[min-content_1fr_200px]', + 'bg-neutral-200', + 'text-ui' + ); + return ( +
+
+

Market: {market.name}

+
+ + + + + + + + + +
{JSON.stringify(market.trades, null, 2)}
+
+ + + +
+
+ + + + + + + + + + + + + +
+ ); +}; + +interface TradeGridChildProps { + children: ReactNode; + className?: string; +} + +const TradeGridChild = ({ children, className }: TradeGridChildProps) => { + const gridChildClasses = classNames('bg-white', className); + return ( +
+ + {({ width, height }) => ( +
+ {children} +
+ )} +
+
+ ); +}; + +type View = keyof typeof Views; + +interface TradePanelsProps { + market: Market_market; +} + +export const TradePanels = ({ market }: TradePanelsProps) => { + const [view, setView] = useState('chart'); + + const renderView = () => { + const Component = Views[view]; + + if (!Component) { + throw new Error(`No component for view: ${view}`); + } + + return ; + }; + + return ( +
+
+

Market: {market.name}

+
+
+ + {({ width, height }) => ( +
{renderView()}
+ )} +
+
+
+ {Object.keys(Views).map((key: View) => { + const className = classNames( + 'p-8', + 'border-t', + 'border-neutral-200', + 'capitalize', + { + 'text-vega-pink': view === key, + 'bg-white': view === key, + } + ); + return ( + + ); + })} +
+
+ ); +}; diff --git a/apps/trading/pages/markets/trading-components.tsx b/apps/trading/pages/markets/trading-components.tsx new file mode 100644 index 000000000..73b00c453 --- /dev/null +++ b/apps/trading/pages/markets/trading-components.tsx @@ -0,0 +1,15 @@ +export const Chart = () =>
TODO: Chart
; +export const Ticket = () =>
TODO: Ticket
; +export const Orderbook = () =>
TODO: Orderbook
; +export const Orders = () =>
TODO: Orders
; +export const Positions = () =>
TODO: Positions
; +export const Collateral = () =>
TODO: Collateral
; + +export const Views = { + chart: Chart, + ticket: Ticket, + orderbook: Orderbook, + orders: Orders, + positions: Positions, + collateral: Collateral, +};