Files
trade-frontend/apps/visualizer/src/features/chart/ChartSideToolbar.tsx
2026-01-06 12:33:47 +01:00

221 lines
7.1 KiB
TypeScript

import { useEffect, useMemo, useState } from 'react';
import ChartToolMenu, { type ToolMenuSection } from './ChartToolMenu';
import {
IconBrush,
IconCrosshair,
IconCursor,
IconEye,
IconFib,
IconLock,
IconPlus,
IconRuler,
IconSmile,
IconText,
IconResetView,
IconTrash,
IconTrendline,
IconZoom,
IconZoomOut,
} from './ChartIcons';
type ActiveTool = 'cursor' | 'fib-retracement';
type Props = {
timeframe: string;
activeTool: ActiveTool;
hasFib: boolean;
onToolChange: (tool: ActiveTool) => void;
onZoomIn: () => void;
onZoomOut: () => void;
onResetView: () => void;
onClearFib: () => void;
};
export default function ChartSideToolbar({
timeframe,
activeTool,
hasFib,
onToolChange,
onZoomIn,
onZoomOut,
onResetView,
onClearFib,
}: Props) {
const [openMenu, setOpenMenu] = useState<'fib' | null>(null);
useEffect(() => {
if (!openMenu) return;
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') setOpenMenu(null);
};
window.addEventListener('keydown', onKeyDown);
return () => window.removeEventListener('keydown', onKeyDown);
}, [openMenu]);
const fibMenuSections: ToolMenuSection[] = useMemo(
() => [
{
id: 'fib',
title: 'FIBONACCI',
items: [
{ id: 'fib-retracement', label: 'Fib Retracement', icon: <IconFib />, shortcut: 'Click 2 points' },
{ id: 'fib-tb-ext', label: 'Trend-Based Fib Extension', icon: <IconTrendline /> },
{ id: 'fib-channel', label: 'Fib Channel', icon: <IconFib /> },
{ id: 'fib-time-zone', label: 'Fib Time Zone', icon: <IconFib /> },
{ id: 'fib-speed-fan', label: 'Fib Speed Resistance Fan', icon: <IconFib /> },
{ id: 'fib-tb-time', label: 'Trend-Based Fib Time', icon: <IconTrendline /> },
{ id: 'fib-circles', label: 'Fib Circles', icon: <IconFib /> },
{ id: 'fib-spiral', label: 'Fib Spiral', icon: <IconFib /> },
{ id: 'fib-speed-arcs', label: 'Fib Speed Resistance Arcs', icon: <IconFib /> },
{ id: 'fib-wedge', label: 'Fib Wedge', icon: <IconFib /> },
{ id: 'pitchfan', label: 'Pitchfan', icon: <IconTrendline /> },
],
},
{
id: 'gann',
title: 'GANN',
items: [
{ id: 'gann-box', label: 'Gann Box', icon: <IconTrendline /> },
{ id: 'gann-square-fixed', label: 'Gann Square Fixed', icon: <IconTrendline /> },
{ id: 'gann-fan', label: 'Gann Fan', icon: <IconTrendline /> },
],
},
],
[]
);
return (
<div className="chartTools">
<div className="chartSideToolbar">
<button
type="button"
className={['chartToolBtn', activeTool === 'cursor' ? 'chartToolBtn--active' : ''].filter(Boolean).join(' ')}
title="Cursor"
aria-label="Cursor"
onClick={() => {
onToolChange('cursor');
setOpenMenu(null);
}}
>
<span className="chartToolBtn__icon" aria-hidden="true">
<IconCursor />
</span>
</button>
<button type="button" className="chartToolBtn" title="Crosshair" aria-label="Crosshair" disabled>
<span className="chartToolBtn__icon" aria-hidden="true">
<IconCrosshair />
</span>
</button>
<button type="button" className="chartToolBtn" title="Add" aria-label="Add" disabled>
<span className="chartToolBtn__icon" aria-hidden="true">
<IconPlus />
</span>
</button>
<button type="button" className="chartToolBtn" title="Trendline" aria-label="Trendline" disabled>
<span className="chartToolBtn__icon" aria-hidden="true">
<IconTrendline />
</span>
</button>
<button
type="button"
className={['chartToolBtn', activeTool === 'fib-retracement' ? 'chartToolBtn--active' : ''].filter(Boolean).join(' ')}
title="Fibonacci"
aria-label="Fibonacci"
onClick={() => setOpenMenu((m) => (m === 'fib' ? null : 'fib'))}
>
<span className="chartToolBtn__icon" aria-hidden="true">
<IconFib />
</span>
</button>
<button type="button" className="chartToolBtn" title="Brush" aria-label="Brush" disabled>
<span className="chartToolBtn__icon" aria-hidden="true">
<IconBrush />
</span>
</button>
<button type="button" className="chartToolBtn" title="Text" aria-label="Text" disabled>
<span className="chartToolBtn__icon" aria-hidden="true">
<IconText />
</span>
</button>
<button type="button" className="chartToolBtn" title="Emoji" aria-label="Emoji" disabled>
<span className="chartToolBtn__icon" aria-hidden="true">
<IconSmile />
</span>
</button>
<button type="button" className="chartToolBtn" title="Ruler" aria-label="Ruler" disabled>
<span className="chartToolBtn__icon" aria-hidden="true">
<IconRuler />
</span>
</button>
<div className="chartToolBtn__spacer" />
<button type="button" className="chartToolBtn" title="Zoom In" aria-label="Zoom In" onClick={onZoomIn}>
<span className="chartToolBtn__icon" aria-hidden="true">
<IconZoom />
</span>
</button>
<button type="button" className="chartToolBtn" title="Zoom Out" aria-label="Zoom Out" onClick={onZoomOut}>
<span className="chartToolBtn__icon" aria-hidden="true">
<IconZoomOut />
</span>
</button>
<button type="button" className="chartToolBtn" title="Reset View" aria-label="Reset View" onClick={onResetView}>
<span className="chartToolBtn__icon" aria-hidden="true">
<IconResetView />
</span>
</button>
<button type="button" className="chartToolBtn" title="Lock" aria-label="Lock" disabled>
<span className="chartToolBtn__icon" aria-hidden="true">
<IconLock />
</span>
</button>
<button
type="button"
className="chartToolBtn"
title="Clear Fib"
aria-label="Clear Fib"
onClick={onClearFib}
disabled={!hasFib}
>
<span className="chartToolBtn__icon" aria-hidden="true">
<IconTrash />
</span>
</button>
<button type="button" className="chartToolBtn" title="Visibility" aria-label="Visibility" disabled>
<span className="chartToolBtn__icon" aria-hidden="true">
<IconEye />
</span>
</button>
</div>
{openMenu === 'fib' ? (
<>
<div className="chartToolMenuBackdrop" onClick={() => setOpenMenu(null)} />
<ChartToolMenu
timeframeLabel={timeframe}
sections={fibMenuSections}
onSelectItem={(id) => {
if (id === 'fib-retracement') onToolChange('fib-retracement');
setOpenMenu(null);
}}
/>
</>
) : null}
</div>
);
}