가상투자 메뉴 기능 구현

This commit is contained in:
Macbook
2026-05-25 06:41:45 +09:00
parent 0cfe7fc84c
commit 8b373b11e3
33 changed files with 3897 additions and 99 deletions
@@ -1,4 +1,4 @@
import React, { useMemo, useRef, useState } from 'react';
import React, { useCallback, useMemo, useRef, useState } from 'react';
import type { Theme } from '../../types';
import { readStoredSize, storeSize, usePanelResize } from '../strategyEditor/usePanelResize';
import '../../styles/strategyEditorTheme.css';
@@ -7,6 +7,9 @@ import '../../styles/builderPageShell.css';
const LEFT_MIN = 220;
const LEFT_MAX = 520;
const LEFT_DEFAULT = 280;
const RIGHT_MIN = 260;
const RIGHT_MAX = 520;
const RIGHT_DEFAULT = 280;
const FOOTER_MIN = 88;
const FOOTER_MAX = 420;
const FOOTER_DEFAULT = 140;
@@ -29,6 +32,9 @@ export interface BuilderPageShellProps {
footerLabel?: string;
footer?: React.ReactNode;
leftStorageKey?: string;
leftDefaultWidth?: number;
rightStorageKey?: string;
rightDefaultWidth?: number;
footerStorageKey?: string;
loading?: boolean;
loadingText?: string;
@@ -52,15 +58,21 @@ export default function BuilderPageShell({
footerLabel,
footer,
leftStorageKey = 'bps-left-width',
leftDefaultWidth = LEFT_DEFAULT,
rightStorageKey = 'bps-right-width',
rightDefaultWidth = RIGHT_DEFAULT,
footerStorageKey = 'bps-footer-height',
loading = false,
loadingText = '로딩 중…',
}: BuilderPageShellProps) {
const [leftWidth, setLeftWidth] = useState(() => readStoredSize(leftStorageKey, LEFT_DEFAULT));
const [leftWidth, setLeftWidth] = useState(() => readStoredSize(leftStorageKey, leftDefaultWidth));
const [rightWidth, setRightWidth] = useState(() => readStoredSize(rightStorageKey, rightDefaultWidth));
const [footerHeight, setFooterHeight] = useState(() => readStoredSize(footerStorageKey, FOOTER_DEFAULT));
const leftWidthRef = useRef(leftWidth);
const rightWidthRef = useRef(rightWidth);
const footerHeightRef = useRef(footerHeight);
leftWidthRef.current = leftWidth;
rightWidthRef.current = rightWidth;
footerHeightRef.current = footerHeight;
const onLeftSplitter = usePanelResize(
@@ -72,6 +84,37 @@ export default function BuilderPageShell({
v => storeSize(leftStorageKey, v),
);
const handleRightSplitter = useCallback((e: React.PointerEvent<HTMLDivElement>) => {
const startX = e.clientX;
const start = rightWidthRef.current;
const cursor = 'col-resize';
document.body.style.cursor = cursor;
document.body.style.userSelect = 'none';
e.currentTarget.setPointerCapture(e.pointerId);
e.currentTarget.classList.add('se-splitter--active');
const splitter = e.currentTarget;
const onMove = (ev: PointerEvent) => {
const delta = ev.clientX - startX;
const next = Math.min(RIGHT_MAX, Math.max(RIGHT_MIN, start - delta));
setRightWidth(next);
};
const onUp = (ev: PointerEvent) => {
document.body.style.cursor = '';
document.body.style.userSelect = '';
splitter.releasePointerCapture(ev.pointerId);
splitter.classList.remove('se-splitter--active');
window.removeEventListener('pointermove', onMove);
window.removeEventListener('pointerup', onUp);
storeSize(rightStorageKey, rightWidthRef.current);
};
window.addEventListener('pointermove', onMove);
window.addEventListener('pointerup', onUp);
}, [rightStorageKey]);
const onFooterSplitter = usePanelResize(
'horizontal',
setFooterHeight,
@@ -165,14 +208,26 @@ export default function BuilderPageShell({
</main>
{right && (
<aside className="bps-right">
{rightTabs ?? (rightTitle ? (
<div className="bps-panel-head" style={{ margin: 0, padding: '12px 12px 10px', borderBottom: '1px solid var(--se-border)' }}>
<h2 className="bps-panel-title">{rightTitle}</h2>
</div>
) : null)}
<div className="bps-right-body">{right}</div>
</aside>
<>
<div
className="bps-splitter bps-splitter--v se-splitter se-splitter--v"
role="separator"
aria-orientation="vertical"
aria-label="우측 패널 너비 조절"
onPointerDown={handleRightSplitter}
/>
<aside
className="bps-right"
style={{ width: rightWidth, flex: `0 0 ${rightWidth}px` }}
>
{rightTabs ?? (rightTitle ? (
<div className="bps-panel-head" style={{ margin: 0, padding: '12px 12px 10px', borderBottom: '1px solid var(--se-border)' }}>
<h2 className="bps-panel-title">{rightTitle}</h2>
</div>
) : null)}
<div className="bps-right-body">{right}</div>
</aside>
</>
)}
</div>
</div>