Skip to content

Widgets

Overview

Widgets are the building blocks of the trading terminal. Each dashboard contains a collection of widgets that can be freely positioned, resized, minimized, and grouped.

Widget Types

All widget types are defined in src/types/dashboard.ts as a Zod enum:

TypeComponentDescription
chartChart.tsxOHLCV candlestick chart (Night Vision library)
portfolioPortfolio.tsxAccount portfolio overview
userBalancesUserBalancesWidget.tsxExchange balances with pie chart (Recharts)
userTradingDataUserTradingDataWidget.tsxTabs: orders, trades, positions
orderFormOrderForm.tsxPlace buy/sell orders
transactionHistoryTransactionHistory.tsxRecent transaction log
orderbookOrderBookWidget.tsxLive order book (bid/ask depth)
tradesTradesWidget.tsxLive trade feed
dealsDealsWidget.tsxAggregated deals/positions tracking
dataProviderSettingsDataProviderSettingsWidget.tsxConfigure data providers
dataProviderSetupDataProviderSetupWidget.tsxInitial provider setup wizard
dataProviderDebugDataProviderDebugWidget.tsxDebug provider state
dataProviderDemoDataProviderDemoWidget.tsxDemo of provider capabilities
exchangesExchangesWidget.tsxExchange browser/selector
marketsMarketsWidget.tsxMarket type selector (spot/futures)
pairsPairsWidget.tsxTrading pair browser
notificationTestNotificationTestWidget.tsxTest notification system
debugUserDataDebugUserData.tsxDebug user store
debugCCXTCacheDebugCCXTCache.tsxDebug CCXT instance cache
debugBingXDebugBingXWidget.tsxDebug BingX exchange API

Widget Container: WidgetSimple

Every widget is wrapped in WidgetSimple (src/components/WidgetSimple.tsx), which provides:

  • Drag — click and drag the title bar
  • Resize — 8 resize handles (edges + corners)
  • Minimize — collapses to a small bar at the bottom
  • Maximize — fills the viewport, restores on second click
  • Title editing — double-click the title to rename
  • Close — removes the widget from the dashboard
  • Settings — gear icon, opens widget-specific settings panel
  • Group selector — colored circle showing the widget’s group
  • Z-index — click brings widget to front

Props

interface WidgetSimpleProps {
id: string;
title: string;
defaultTitle: string;
userTitle?: string;
children: ReactNode; // The actual widget content
position: { x: number; y: number };
size: { width: number; height: number };
zIndex: number;
isActive: boolean;
groupId?: string;
widgetType: string;
showGroupSelector?: boolean;
headerActions?: ReactNode; // Extra buttons in the title bar
onRemove: () => void;
}

Widget Grouping

Widgets can be assigned to groups via the groupStore. A group shares context (exchange, market, trading pair, account) across all its widgets. This means selecting “BTC/USDT on Binance” in one group member automatically updates all others.

Groups are identified by a color indicator on each widget’s title bar.

How to Create a New Widget

1. Add the widget type

In src/types/dashboard.ts, add your type to the WidgetSchema type enum:

type: z.enum([
'chart', 'portfolio', /* ... existing types ... */,
'myNewWidget' // <-- add here
]),

2. Create the component

Create src/components/widgets/MyNewWidget.tsx:

import React from 'react';
const MyNewWidget: React.FC = () => {
return (
<div className="p-4 h-full overflow-auto">
<h3 className="text-sm font-medium text-terminal-text">My Widget</h3>
{/* Widget content */}
</div>
);
};
export default MyNewWidget;

3. Export from the widget index

In src/components/widgets/index.ts:

export { default as MyNewWidget } from './MyNewWidget';

4. Register in TradingTerminal

In src/pages/TradingTerminal.tsx, add to the widgetComponents map:

import MyNewWidget from '@/components/widgets/MyNewWidget';
const widgetComponents: Record<string, React.FC<any>> = {
// ... existing entries
myNewWidget: MyNewWidget,
};

5. Add to widget menu (optional)

Add an entry in the widget menu/right-click context menu so users can add your widget to their dashboard.

6. Use market data (optional)

If your widget needs market data, use the data provider store:

import { useDataProviderStore } from '@/store/dataProviderStore';
const MyNewWidget: React.FC = () => {
const subscribe = useDataProviderStore(s => s.subscribe);
const unsubscribe = useDataProviderStore(s => s.unsubscribe);
const getTrades = useDataProviderStore(s => s.getTrades);
useEffect(() => {
const widgetId = 'my-widget-123';
subscribe(widgetId, 'binance', 'BTC/USDT', 'trades', undefined, 'spot');
return () => {
unsubscribe(widgetId, 'binance', 'BTC/USDT', 'trades', undefined, 'spot');
};
}, []);
const trades = getTrades('binance', 'BTC/USDT', 'spot');
// render trades...
};

Widget-Specific Stores

Some complex widgets have their own dedicated Zustand stores:

StoreFilePurpose
chartWidgetStorestore/chartWidgetStore.tsChart settings per widget instance
orderBookWidgetStorestore/orderBookWidgetStore.tsOrder book display settings
tradesWidgetStorestore/tradesWidgetStore.tsTrades feed settings
userBalancesWidgetStorestore/userBalancesWidgetStore.tsBalance display preferences
userTradingDataWidgetStorestore/userTradingDataWidgetStore.tsTrading data tab state
placeOrderStorestore/placeOrderStore.tsOrder form state

Performance Notes

  • Widgets with large data sets (trades, orderbook) use TanStack Virtual for virtualized scrolling
  • Pre-calculate row heights for the virtualizer to avoid layout thrashing
  • Use Zustand selectors (useStore(s => s.field)) to avoid re-rendering on unrelated state changes
  • Subscription deduplication ensures multiple widgets watching the same stream share a single connection