가상매매 화면 멀티 레이아웃 수정
This commit is contained in:
@@ -1,13 +1,8 @@
|
||||
/**
|
||||
* 그리드형 n×n 레이아웃 선택 (TradingView 스타일 팝업)
|
||||
* 매매 시그널 알림 — 그리드 배치 선택
|
||||
*/
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
gridPresetsByGroup,
|
||||
getTradeNotificationGridPreset,
|
||||
type TradeNotificationGridPreset,
|
||||
type TradeNotificationGridPresetId,
|
||||
} from '../../utils/tradeNotificationGridPresets';
|
||||
import GridLayoutPicker from '../common/GridLayoutPicker';
|
||||
import type { TradeNotificationGridPresetId } from '../../utils/tradeNotificationGridPresets';
|
||||
|
||||
interface Props {
|
||||
value: TradeNotificationGridPresetId;
|
||||
@@ -15,193 +10,8 @@ interface Props {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
/** 20×20 와이어프레임 아이콘 */
|
||||
const LayoutIcon: React.FC<{ preset: TradeNotificationGridPreset }> = ({ preset }) => {
|
||||
const s = 20;
|
||||
const g = 1;
|
||||
const cells: Array<{ x: number; y: number; w: number; h: number }> = [];
|
||||
|
||||
switch (preset.id) {
|
||||
case '1x1':
|
||||
cells.push({ x: 0, y: 0, w: s, h: s });
|
||||
break;
|
||||
case '2x1':
|
||||
cells.push({ x: 0, y: 0, w: 9, h: s }, { x: 10, y: 0, w: 9, h: s });
|
||||
break;
|
||||
case '1x2':
|
||||
cells.push({ x: 0, y: 0, w: s, h: 9 }, { x: 0, y: 10, w: s, h: 9 });
|
||||
break;
|
||||
case '3x1':
|
||||
cells.push(
|
||||
{ x: 0, y: 0, w: 6, h: s },
|
||||
{ x: 7, y: 0, w: 6, h: s },
|
||||
{ x: 14, y: 0, w: 6, h: s },
|
||||
);
|
||||
break;
|
||||
case '1x3':
|
||||
cells.push(
|
||||
{ x: 0, y: 0, w: s, h: 6 },
|
||||
{ x: 0, y: 7, w: s, h: 6 },
|
||||
{ x: 0, y: 14, w: s, h: 6 },
|
||||
);
|
||||
break;
|
||||
case '2x1-31':
|
||||
cells.push({ x: 0, y: 0, w: 9, h: s }, { x: 10, y: 0, w: 9, h: 9 }, { x: 10, y: 10, w: 9, h: 9 });
|
||||
break;
|
||||
case '2x2':
|
||||
cells.push(
|
||||
{ x: 0, y: 0, w: 9, h: 9 },
|
||||
{ x: 10, y: 0, w: 9, h: 9 },
|
||||
{ x: 0, y: 10, w: 9, h: 9 },
|
||||
{ x: 10, y: 10, w: 9, h: 9 },
|
||||
);
|
||||
break;
|
||||
case '4x1':
|
||||
for (let i = 0; i < 4; i++) cells.push({ x: i * 5, y: 0, w: 4, h: s });
|
||||
break;
|
||||
case '1x4':
|
||||
for (let i = 0; i < 4; i++) cells.push({ x: 0, y: i * 5, w: s, h: 4 });
|
||||
break;
|
||||
case '2x1-211':
|
||||
cells.push(
|
||||
{ x: 0, y: 0, w: 9, h: 9 },
|
||||
{ x: 10, y: 0, w: 9, h: 9 },
|
||||
{ x: 0, y: 10, w: 9, h: 9 },
|
||||
{ x: 10, y: 10, w: 9, h: 9 },
|
||||
);
|
||||
break;
|
||||
case '5x1':
|
||||
for (let i = 0; i < 5; i++) cells.push({ x: i * 4, y: 0, w: 3, h: s });
|
||||
break;
|
||||
case '3x2':
|
||||
for (let c = 0; c < 3; c++) {
|
||||
for (let r = 0; r < 2; r++) {
|
||||
cells.push({ x: c * 7, y: r * 10, w: 6, h: 9 });
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '2x3':
|
||||
for (let c = 0; c < 2; c++) {
|
||||
for (let r = 0; r < 3; r++) {
|
||||
cells.push({ x: c * 10, y: r * 7, w: 9, h: 6 });
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '4x2':
|
||||
for (let c = 0; c < 4; c++) {
|
||||
for (let r = 0; r < 2; r++) {
|
||||
cells.push({ x: c * 5, y: r * 10, w: 4, h: 9 });
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '2x4':
|
||||
for (let c = 0; c < 2; c++) {
|
||||
for (let r = 0; r < 4; r++) {
|
||||
cells.push({ x: c * 10, y: r * 5, w: 9, h: 4 });
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
cells.push({ x: 0, y: 0, w: s, h: s });
|
||||
}
|
||||
|
||||
return (
|
||||
<svg width="22" height="22" viewBox="0 0 20 20" aria-hidden className="tnl-grid-preset-icon-svg">
|
||||
{cells.map((c, i) => (
|
||||
<rect
|
||||
key={i}
|
||||
x={c.x}
|
||||
y={c.y}
|
||||
width={c.w}
|
||||
height={c.h}
|
||||
rx={0.5}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={g}
|
||||
/>
|
||||
))}
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
const TradeNotificationGridLayoutPicker: React.FC<Props> = ({
|
||||
value,
|
||||
onChange,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const active = getTradeNotificationGridPreset(value);
|
||||
const groups = [...gridPresetsByGroup().entries()].sort((a, b) => a[0] - b[0]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const onDoc = (e: MouseEvent) => {
|
||||
if (!rootRef.current?.contains(e.target as Node)) setOpen(false);
|
||||
};
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') setOpen(false);
|
||||
};
|
||||
document.addEventListener('mousedown', onDoc);
|
||||
document.addEventListener('keydown', onKey);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', onDoc);
|
||||
document.removeEventListener('keydown', onKey);
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<div className="tnl-grid-layout-picker" ref={rootRef}>
|
||||
<button
|
||||
type="button"
|
||||
className={[
|
||||
'tnl-grid-layout-trigger',
|
||||
open ? 'tnl-grid-layout-trigger--open' : '',
|
||||
disabled ? 'tnl-grid-layout-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>
|
||||
|
||||
{open && (
|
||||
<div className="tnl-grid-layout-popover" role="dialog" aria-label="그리드 배치">
|
||||
<p className="tnl-grid-layout-popover-title">그리드 배치</p>
|
||||
{groups.map(([group, presets]) => (
|
||||
<div key={group} className="tnl-grid-layout-group">
|
||||
<span className="tnl-grid-layout-group-lbl">{group}</span>
|
||||
<div className="tnl-grid-layout-group-row">
|
||||
{presets.map(preset => (
|
||||
<button
|
||||
key={preset.id}
|
||||
type="button"
|
||||
className={[
|
||||
'tnl-grid-preset-btn',
|
||||
value === preset.id ? 'tnl-grid-preset-btn--on' : '',
|
||||
].filter(Boolean).join(' ')}
|
||||
title={`${preset.label} (${preset.cols}열)`}
|
||||
aria-label={`${preset.label} 배치`}
|
||||
aria-pressed={value === preset.id}
|
||||
onClick={() => {
|
||||
onChange(preset.id);
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<LayoutIcon preset={preset} />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
const TradeNotificationGridLayoutPicker: React.FC<Props> = props => (
|
||||
<GridLayoutPicker variant="tnl" popoverAlign="right" {...props} />
|
||||
);
|
||||
|
||||
export default TradeNotificationGridLayoutPicker;
|
||||
|
||||
Reference in New Issue
Block a user