모의투자, 백테스팅 레이아웃 수정
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user