가상매매 그리드 배치 오류수정

This commit is contained in:
Macbook
2026-06-08 21:09:02 +09:00
parent 041db42741
commit 80c78d75bc
2 changed files with 84 additions and 34 deletions
@@ -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 && (