가상매매 그리드 배치 오류수정
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
/**
|
||||
* 그리드형 n×n 레이아웃 선택 (TradingView 스타일 팝업) — 알림 목록·가상매매 공용
|
||||
*/
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import {
|
||||
gridPresetsByGroup,
|
||||
getTradeNotificationGridPreset,
|
||||
@@ -134,6 +135,8 @@ const LayoutIcon: React.FC<{ preset: TradeNotificationGridPreset }> = ({ preset
|
||||
);
|
||||
};
|
||||
|
||||
const POPOVER_WIDTH = 320;
|
||||
|
||||
const GridLayoutPicker: React.FC<Props> = ({
|
||||
value,
|
||||
onChange,
|
||||
@@ -142,14 +145,46 @@ const GridLayoutPicker: React.FC<Props> = ({
|
||||
popoverAlign = 'right',
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [popoverPos, setPopoverPos] = useState<{ top: number; left: number } | null>(null);
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const popoverRef = useRef<HTMLDivElement>(null);
|
||||
const active = getTradeNotificationGridPreset(value);
|
||||
const groups = [...gridPresetsByGroup().entries()].sort((a, b) => a[0] - b[0]);
|
||||
|
||||
const updatePopoverPos = useCallback(() => {
|
||||
const el = rootRef.current;
|
||||
if (!el) return;
|
||||
const rect = el.getBoundingClientRect();
|
||||
const width = Math.min(POPOVER_WIDTH, window.innerWidth - 48);
|
||||
const left = popoverAlign === 'left'
|
||||
? rect.left
|
||||
: rect.right - width;
|
||||
setPopoverPos({
|
||||
top: rect.bottom + 8,
|
||||
left: Math.max(8, Math.min(left, window.innerWidth - width - 8)),
|
||||
});
|
||||
}, [popoverAlign]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setPopoverPos(null);
|
||||
return;
|
||||
}
|
||||
updatePopoverPos();
|
||||
window.addEventListener('resize', updatePopoverPos);
|
||||
window.addEventListener('scroll', updatePopoverPos, true);
|
||||
return () => {
|
||||
window.removeEventListener('resize', updatePopoverPos);
|
||||
window.removeEventListener('scroll', updatePopoverPos, true);
|
||||
};
|
||||
}, [open, updatePopoverPos]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const onDoc = (e: MouseEvent) => {
|
||||
if (!rootRef.current?.contains(e.target as Node)) setOpen(false);
|
||||
const t = e.target as Node;
|
||||
if (rootRef.current?.contains(t) || popoverRef.current?.contains(t)) return;
|
||||
setOpen(false);
|
||||
};
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') setOpen(false);
|
||||
@@ -164,30 +199,25 @@ const GridLayoutPicker: React.FC<Props> = ({
|
||||
|
||||
const popoverClass = [
|
||||
cls(variant, 'popover'),
|
||||
cls(variant, 'popover--portal'),
|
||||
popoverAlign === 'left' ? cls(variant, 'popover--align-left') : '',
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
return (
|
||||
<div className={cls(variant, 'picker')} ref={rootRef}>
|
||||
<button
|
||||
type="button"
|
||||
className={[
|
||||
cls(variant, 'trigger'),
|
||||
open ? cls(variant, 'trigger--open') : '',
|
||||
disabled ? cls(variant, 'trigger--disabled') : '',
|
||||
].filter(Boolean).join(' ')}
|
||||
title={`그리드 배치: ${active.label}`}
|
||||
aria-label="그리드 배치 선택"
|
||||
aria-expanded={open}
|
||||
aria-haspopup="dialog"
|
||||
disabled={disabled}
|
||||
onClick={() => !disabled && setOpen(o => !o)}
|
||||
const popover = open && popoverPos ? (
|
||||
<div
|
||||
ref={popoverRef}
|
||||
className={popoverClass}
|
||||
role="dialog"
|
||||
aria-label="그리드 배치"
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: popoverPos.top,
|
||||
left: popoverPos.left,
|
||||
right: 'auto',
|
||||
zIndex: 10000,
|
||||
width: Math.min(POPOVER_WIDTH, window.innerWidth - 48),
|
||||
}}
|
||||
>
|
||||
<LayoutIcon preset={active} />
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div className={popoverClass} role="dialog" aria-label="그리드 배치">
|
||||
<p className={cls(variant, 'popover-title')}>그리드 배치</p>
|
||||
{groups.map(([group, presets]) => (
|
||||
<div key={group} className={cls(variant, 'group')}>
|
||||
@@ -216,7 +246,28 @@ const GridLayoutPicker: React.FC<Props> = ({
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<div className={cls(variant, 'picker')} ref={rootRef}>
|
||||
<button
|
||||
type="button"
|
||||
className={[
|
||||
cls(variant, 'trigger'),
|
||||
open ? cls(variant, 'trigger--open') : '',
|
||||
disabled ? cls(variant, 'trigger--disabled') : '',
|
||||
].filter(Boolean).join(' ')}
|
||||
title={`그리드 배치: ${active.label}`}
|
||||
aria-label="그리드 배치 선택"
|
||||
aria-expanded={open}
|
||||
aria-haspopup="dialog"
|
||||
disabled={disabled}
|
||||
onClick={() => !disabled && setOpen(o => !o)}
|
||||
>
|
||||
<LayoutIcon preset={active} />
|
||||
</button>
|
||||
|
||||
{popover && createPortal(popover, document.body)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -133,7 +133,6 @@ export const VirtualViewHeaderControls: React.FC<ViewProps> = ({
|
||||
<VirtualGridLayoutPicker
|
||||
value={gridPreset}
|
||||
onChange={onGridPresetChange}
|
||||
disabled={!showSignalChartToggle}
|
||||
/>
|
||||
)}
|
||||
{showSignalChartToggle && onGlobalDisplayModeChange && (
|
||||
|
||||
Reference in New Issue
Block a user