/** * 차트 우측 세로 툴바 — 캔들·보조지표 pane 과 높이 동기화. * pane 하단: 드래그 핸들 + 펼침메뉴(분리·복사·전체보기·종료) */ import React, { useState, useEffect, useCallback, useRef } from 'react'; import { createPortal } from 'react-dom'; import type { IndicatorConfig } from '../types'; import { ChartManager } from '../utils/ChartManager'; import { getPaneHostId } from '../utils/indicatorPaneMerge'; import { isIndicatorPaneFocused, resolveFocusedPaneLayoutId } from '../utils/indicatorPaneFocus'; import { buildPaneItems, resolvePaneItemLayout } from './paneLegendLayout'; import type { ChartOverlayToggleKey } from '../utils/chartOverlayVisibility'; export interface CandleOverlayToggleItem { key: ChartOverlayToggleKey; label: string; visible: boolean; } export interface ChartRightToolbarProps { manager: ChartManager; indicators: IndicatorConfig[]; chartHeight: number; paused?: boolean; /** 캔들 pane 상단 — 이동평균·일목·볼린저 표시/숨김 (투자관리 차트 탭) */ candleOverlayToggles?: CandleOverlayToggleItem[]; onToggleCandleOverlay?: (key: ChartOverlayToggleKey) => void; onSplit?: (hostId: string) => void; onRemove?: (id: string) => void; onDuplicate?: (id: string) => void; onExpand?: (id: string) => void; onRestore?: () => void; focusedId?: string | null; onDragStart?: (id: string, e: React.PointerEvent) => void; /** 지표명 툴바 — 표시/숨기기 */ onToggleHidden?: (id: string) => void; /** 지표명 툴바 — 설정 모달 */ onSettings?: (id: string) => void; /** Custom / Custom1 오버레이 — 적용 여부 (동시 적용 불가, 설정은 슬롯별 분리) */ customOverlayActive?: boolean; onCustomOverlayActiveChange?: (active: boolean) => void; onOpenCustomOverlaySettings?: () => void; custom1OverlayActive?: boolean; onCustom1OverlayActiveChange?: (active: boolean) => void; onOpenCustom1OverlaySettings?: () => void; /** true — 캔들 pane 상단 오버레이 토글만 (미니 차트 등) */ overlayControlsOnly?: boolean; /** 9·20일 신고가·신저가 라벨 표시 (실시간 차트) */ priceExtremeLabelsVisible?: boolean; onTogglePriceExtremeLabels?: () => void; } const BTN_SIZE = 32; const BTN_GAP = 2; const IconCopy = () => ( ); const IconSplit = () => ( ); const IconClose = () => ( ); const IconExpand = () => ( ); const IconRestore = () => ( ); const IconDrag = () => ( ); const IconMore = () => ( ); const IconMa = () => ( ); const IconBollinger = () => ( ); const IconCandle = () => ( ); const IconIchimoku = () => ( ); /** 신고가(상향)·신저가(하향) */ const IconPriceExtreme = () => ( ); const OVERLAY_ICONS: Record = { ma: , bollinger: , ichimoku: , candle: , }; const IconEye = ({ hidden }: { hidden?: boolean }) => hidden ? ( ) : ( ); const CUSTOM_MENU_ID = '__custom_overlay__'; const CUSTOM1_MENU_ID = '__custom1_overlay__'; const IconCustom = () => ( ); const IconCheck = () => ( ); const IconGear = () => ( ); const CustomOverlayFlyoutMenu: React.FC<{ title: string; flyoutPos: { top: number; left: number }; overlayActive: boolean; onToggleActive: () => void; onOpenSettings: () => void; onCancelClose: () => void; onScheduleClose: () => void; onCloseFlyout: () => void; }> = ({ title, flyoutPos, overlayActive, onToggleActive, onOpenSettings, onCancelClose, onScheduleClose, onCloseFlyout, }) => createPortal(
{title}
, document.body, ); interface MenuItemDef { key: string; title: string; icon: React.ReactNode; active?: boolean; danger?: boolean; onClick: () => void; } function buildMenuItems( item: ReturnType[number], indicators: IndicatorConfig[], paneItems: ReturnType, opts: { focusedId?: string | null; onSplit?: (hostId: string) => void; onDuplicate?: (id: string) => void; onExpand?: (id: string) => void; onRestore?: () => void; onRemove?: (id: string) => void; onToggleHidden?: (id: string) => void; onSettings?: (id: string) => void; onDone?: () => void; }, ): MenuItemDef[] { const samePaneItems = paneItems.filter(p => p.paneIndex === item.paneIndex); const itemHostId = getPaneHostId(item.id, indicators); const isMergedPane = samePaneItems.length > 1; const showSplit = isMergedPane && item.id === itemHostId && !!opts.onSplit; const isFocused = isIndicatorPaneFocused(item.id, opts.focusedId, indicators); const showExpand = !!(opts.onExpand || (opts.focusedId && opts.onRestore)); const done = opts.onDone ?? (() => {}); const cfg = indicators.find(ind => ind.id === item.id); const isHidden = cfg?.hidden === true; const items: MenuItemDef[] = []; if (opts.onToggleHidden) { items.push({ key: 'visibility', title: isHidden ? '표시' : '숨기기', icon: