모의투자, 백테스팅 레이아웃 수정

This commit is contained in:
Macbook
2026-05-24 20:13:21 +09:00
parent 958b813f3b
commit af230a4233
23 changed files with 2394 additions and 782 deletions
@@ -0,0 +1,182 @@
import React, { useMemo, useRef, useState } from 'react';
import type { Theme } from '../../types';
import { readStoredSize, storeSize, usePanelResize } from '../strategyEditor/usePanelResize';
import '../../styles/strategyEditorTheme.css';
import '../../styles/builderPageShell.css';
const LEFT_MIN = 220;
const LEFT_MAX = 520;
const LEFT_DEFAULT = 280;
const FOOTER_MIN = 88;
const FOOTER_MAX = 420;
const FOOTER_DEFAULT = 140;
export interface BuilderPageShellProps {
theme: Theme;
title: string;
subtitle?: string;
headerActions?: React.ReactNode;
banner?: React.ReactNode;
leftTitle?: string;
leftTabs?: React.ReactNode;
leftActions?: React.ReactNode;
left: React.ReactNode;
centerHead?: React.ReactNode;
center: React.ReactNode;
rightTitle?: string;
rightTabs?: React.ReactNode;
right?: React.ReactNode;
footerLabel?: string;
footer?: React.ReactNode;
leftStorageKey?: string;
footerStorageKey?: string;
loading?: boolean;
loadingText?: string;
}
export default function BuilderPageShell({
theme,
title,
subtitle,
headerActions,
banner,
leftTitle,
leftTabs,
leftActions,
left,
centerHead,
center,
rightTitle,
rightTabs,
right,
footerLabel,
footer,
leftStorageKey = 'bps-left-width',
footerStorageKey = 'bps-footer-height',
loading = false,
loadingText = '로딩 중…',
}: BuilderPageShellProps) {
const [leftWidth, setLeftWidth] = useState(() => readStoredSize(leftStorageKey, LEFT_DEFAULT));
const [footerHeight, setFooterHeight] = useState(() => readStoredSize(footerStorageKey, FOOTER_DEFAULT));
const leftWidthRef = useRef(leftWidth);
const footerHeightRef = useRef(footerHeight);
leftWidthRef.current = leftWidth;
footerHeightRef.current = footerHeight;
const onLeftSplitter = usePanelResize(
'vertical',
setLeftWidth,
() => leftWidthRef.current,
LEFT_MIN,
LEFT_MAX,
v => storeSize(leftStorageKey, v),
);
const onFooterSplitter = usePanelResize(
'horizontal',
setFooterHeight,
() => footerHeightRef.current,
FOOTER_MIN,
FOOTER_MAX,
v => storeSize(footerStorageKey, v),
);
const bodyStyle = useMemo(() => ({
'--bps-footer-height': `${footerHeight}px`,
}) as React.CSSProperties, [footerHeight]);
if (loading) {
return (
<div className={`bps-page se-page se-page--${theme}`}>
<header className="bps-header">
<div className="bps-header-left">
<h1 className="bps-title">{title}</h1>
{subtitle && <span className="bps-subtitle">{subtitle}</span>}
</div>
</header>
<div className="bps-loading">{loadingText}</div>
</div>
);
}
return (
<div className={`bps-page se-page se-page--${theme}`}>
<header className="bps-header">
<div className="bps-header-left">
<h1 className="bps-title">{title}</h1>
{subtitle && <span className="bps-subtitle">{subtitle}</span>}
</div>
{headerActions && <div className="bps-header-actions">{headerActions}</div>}
</header>
{banner}
<div className="bps-body" style={bodyStyle}>
<aside className="bps-left" style={{ width: leftWidth }}>
<div className="bps-panel">
{leftTabs ? (
<>
{leftTabs}
<div className="bps-panel-body bps-panel-body--tabs">{left}</div>
</>
) : (
<>
<div className="bps-panel-head">
<h2 className="bps-panel-title">{leftTitle}</h2>
{leftActions && <div className="bps-panel-actions">{leftActions}</div>}
</div>
<div className="bps-panel-body">{left}</div>
</>
)}
</div>
</aside>
<div
className="bps-splitter bps-splitter--v se-splitter se-splitter--v"
role="separator"
aria-orientation="vertical"
aria-label="좌측 패널 너비 조절"
onPointerDown={onLeftSplitter}
/>
<div className="bps-main">
<div className="bps-main-row">
<main className="bps-center">
<div className="bps-center-work">
{centerHead && <div className="bps-center-head">{centerHead}</div>}
<div className="bps-center-content">{center}</div>
</div>
{footer && (
<>
<div
className="bps-splitter bps-splitter--h se-splitter se-splitter--h"
role="separator"
aria-orientation="horizontal"
aria-label="하단 패널 높이 조절"
onPointerDown={onFooterSplitter}
/>
<footer className="bps-footer" style={{ height: footerHeight }}>
{footerLabel && <div className="bps-footer-label">{footerLabel}</div>}
<div className="bps-footer-body">{footer}</div>
</footer>
</>
)}
</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>
</div>
</div>
</div>
);
}