가상투자 메뉴 기능 구현
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user