custom 툴발 적용

This commit is contained in:
Macbook
2026-06-03 01:09:06 +09:00
parent 76e5b12d19
commit 88fe92d8c3
8 changed files with 1110 additions and 22 deletions
+146 -1
View File
@@ -3,6 +3,7 @@
* 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';
@@ -35,6 +36,13 @@ export interface ChartRightToolbarProps {
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;
}
const BTN_SIZE = 32;
@@ -159,6 +167,24 @@ const IconEye = ({ hidden }: { hidden?: boolean }) => hidden ? (
</svg>
);
const CUSTOM_MENU_ID = '__custom_overlay__';
const CUSTOM1_MENU_ID = '__custom1_overlay__';
const IconCustom = () => (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"
stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
<path d="M2 4h10M2 7h10M2 10h6"/>
<circle cx="10.5" cy="10" r="1.2" fill="currentColor" stroke="none"/>
</svg>
);
const IconCheck = () => (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="2,7.5 5.5,11 12,3"/>
</svg>
);
const IconGear = () => (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.4">
<circle cx="7" cy="7" r="2.3"/>
@@ -166,6 +192,62 @@ const IconGear = () => (
</svg>
);
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(
<div
className="tv-side-flyout tv-side-flyout--right-anchor tv-side-flyout--custom-overlay"
style={{ top: flyoutPos.top, left: flyoutPos.left }}
onMouseEnter={onCancelClose}
onMouseLeave={onScheduleClose}
>
<div className="tv-side-flyout-header">{title}</div>
<button
type="button"
className={`tv-side-flyout-item${overlayActive ? ' active' : ''}`}
onClick={e => {
e.stopPropagation();
onToggleActive();
onCloseFlyout();
}}
>
<span className="tv-side-flyout-icon"><IconCheck /></span>
<span className="tv-side-flyout-title">
{overlayActive ? '미적용' : '적용'}
</span>
</button>
<button
type="button"
className="tv-side-flyout-item"
onClick={e => {
e.stopPropagation();
onOpenSettings();
onCloseFlyout();
}}
>
<span className="tv-side-flyout-icon"><IconGear /></span>
<span className="tv-side-flyout-title"></span>
</button>
</div>,
document.body,
);
interface MenuItemDef {
key: string;
title: string;
@@ -266,6 +348,12 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
manager, indicators, chartHeight, paused = false,
candleOverlayToggles,
onToggleCandleOverlay,
customOverlayActive = false,
onCustomOverlayActiveChange,
onOpenCustomOverlaySettings,
custom1OverlayActive = false,
onCustom1OverlayActiveChange,
onOpenCustom1OverlaySettings,
onSplit, onRemove, onDuplicate,
onExpand, onRestore, focusedId,
onDragStart, onToggleHidden, onSettings,
@@ -341,6 +429,10 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
const candlePane = paneLayouts.find(l => l.paneIndex === 0);
const showOverlayIcons = !!(candleOverlayToggles?.length && candlePane && onToggleCandleOverlay);
const showCustomBtn = !!(showOverlayIcons && onCustomOverlayActiveChange && onOpenCustomOverlaySettings);
const showCustom1Btn = !!(showOverlayIcons && onCustom1OverlayActiveChange && onOpenCustom1OverlaySettings);
const customMenuOpen = openMenuId === CUSTOM_MENU_ID;
const custom1MenuOpen = openMenuId === CUSTOM1_MENU_ID;
const focusedLayoutId = focusedId
? resolveFocusedPaneLayoutId(focusedId, indicators)
@@ -395,6 +487,33 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
{OVERLAY_ICONS[item.key]}
</button>
))}
{showCustomBtn && (
<button
type="button"
className={`tv-side-btn tv-side-group-btn chart-right-toolbar-btn chart-right-toolbar-btn--custom${customOverlayActive ? ' active' : ''}${customMenuOpen ? ' active' : ''}`}
title="Custom 표시"
style={{ marginTop: BTN_GAP }}
onMouseEnter={e => openFlyout(CUSTOM_MENU_ID, e.currentTarget)}
onMouseLeave={scheduleClose}
>
<IconCustom />
<span className="tv-side-expand-arrow" />
</button>
)}
{showCustom1Btn && (
<button
type="button"
className={`tv-side-btn tv-side-group-btn chart-right-toolbar-btn chart-right-toolbar-btn--custom1${custom1OverlayActive ? ' active' : ''}${custom1MenuOpen ? ' active' : ''}`}
title="Custom1 표시"
style={{ marginTop: BTN_GAP }}
onMouseEnter={e => openFlyout(CUSTOM1_MENU_ID, e.currentTarget)}
onMouseLeave={scheduleClose}
>
<IconCustom />
<span className="chart-right-toolbar-custom1-badge">1</span>
<span className="tv-side-expand-arrow" />
</button>
)}
</div>
)}
@@ -457,7 +576,33 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
})}
</div>
{openItem && openMenuId && (() => {
{customMenuOpen && showCustomBtn && (
<CustomOverlayFlyoutMenu
title="Custom"
flyoutPos={flyoutPos}
overlayActive={customOverlayActive}
onToggleActive={() => onCustomOverlayActiveChange?.(!customOverlayActive)}
onOpenSettings={() => onOpenCustomOverlaySettings?.()}
onCancelClose={cancelClose}
onScheduleClose={scheduleClose}
onCloseFlyout={closeFlyout}
/>
)}
{custom1MenuOpen && showCustom1Btn && (
<CustomOverlayFlyoutMenu
title="Custom1"
flyoutPos={flyoutPos}
overlayActive={custom1OverlayActive}
onToggleActive={() => onCustom1OverlayActiveChange?.(!custom1OverlayActive)}
onOpenSettings={() => onOpenCustom1OverlaySettings?.()}
onCancelClose={cancelClose}
onScheduleClose={scheduleClose}
onCloseFlyout={closeFlyout}
/>
)}
{openItem && openMenuId && openMenuId !== CUSTOM_MENU_ID && openMenuId !== CUSTOM1_MENU_ID && (() => {
const menuItems = buildMenuItems(openItem, indicators, paneItems, {
focusedId, onSplit, onDuplicate, onExpand, onRestore, onRemove,
onToggleHidden, onSettings,