Files
goldenChart/frontend/src/components/TradeAlertPopupMenubarSelect.tsx
T
2026-05-23 15:11:48 +09:00

132 lines
4.5 KiB
TypeScript

/**
* 상단 메뉴바 — 알림 팝업 표시 위치·방식 (아이콘 드롭다운)
*/
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import TradeAlertPopupModeIcon from './TradeAlertPopupModeIcon';
import {
TRADE_ALERT_GRID_COL_OPTIONS,
buildTradeAlertPopupModeOptions,
formatTradeAlertPopupMode,
normalizeTradeAlertGridCols,
normalizeTradeAlertPopupLayout,
normalizeTradeAlertPopupPosition,
tradeAlertPopupModeKey,
type TradeAlertPopupLayout,
type TradeAlertPopupPosition,
} from '../utils/tradeAlertPopupLayout';
interface Props {
position?: string;
layout?: string;
gridCols?: number;
onPositionChange?: (v: TradeAlertPopupPosition) => void;
onLayoutChange?: (v: TradeAlertPopupLayout) => void;
onGridColsChange?: (v: number) => void;
}
export const TradeAlertPopupMenubarSelect: React.FC<Props> = ({
position: positionRaw = 'right',
layout: layoutRaw = 'stack',
gridCols: gridColsRaw = 2,
onPositionChange,
onLayoutChange,
onGridColsChange,
}) => {
const position = normalizeTradeAlertPopupPosition(positionRaw);
const layout = normalizeTradeAlertPopupLayout(layoutRaw);
const gridCols = normalizeTradeAlertGridCols(gridColsRaw);
const modeKey = tradeAlertPopupModeKey(position, layout);
const modeOptions = useMemo(() => buildTradeAlertPopupModeOptions(), []);
const [open, setOpen] = useState(false);
const rootRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!open) return;
const onDoc = (e: MouseEvent) => {
if (rootRef.current && !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]);
const selectMode = useCallback((
nextPos: TradeAlertPopupPosition,
nextLay: TradeAlertPopupLayout,
) => {
if (nextPos !== position) onPositionChange?.(nextPos);
if (nextLay !== layout) onLayoutChange?.(nextLay);
setOpen(false);
}, [position, layout, onPositionChange, onLayoutChange]);
const currentLabel = formatTradeAlertPopupMode(position, layout);
return (
<div className="tmb-notify-layout" ref={rootRef}>
<button
type="button"
className={`tmb-notify-layout-trigger${open ? ' tmb-notify-layout-trigger--open' : ''}`}
onClick={() => setOpen(v => !v)}
aria-label={`알림 표시 방식: ${currentLabel}`}
aria-expanded={open}
aria-haspopup="listbox"
title={`알림 표시: ${currentLabel}`}
>
<TradeAlertPopupModeIcon position={position} layout={layout} size={20} />
</button>
{open && (
<div className="tmb-notify-layout-menu" role="listbox" aria-label="알림 표시 방식">
{modeOptions.map(opt => {
const active = opt.key === modeKey;
return (
<button
key={opt.key}
type="button"
role="option"
aria-selected={active}
className={`tmb-notify-layout-option${active ? ' tmb-notify-layout-option--active' : ''}`}
onClick={() => selectMode(opt.position, opt.layout)}
>
<span className="tmb-notify-layout-option-icon">
<TradeAlertPopupModeIcon position={opt.position} layout={opt.layout} size={22} />
</span>
<span className="tmb-notify-layout-option-label">{opt.label}</span>
</button>
);
})}
{layout === 'grid' && (
<div className="tmb-notify-layout-cols-row">
<span className="tmb-notify-layout-cols-label">그리드 </span>
<div className="tmb-notify-layout-cols-btns">
{TRADE_ALERT_GRID_COL_OPTIONS.map(n => (
<button
key={n}
type="button"
className={`tmb-notify-layout-col-btn${gridCols === n ? ' tmb-notify-layout-col-btn--active' : ''}`}
onClick={() => onGridColsChange?.(n)}
>
{n}
</button>
))}
</div>
</div>
)}
</div>
)}
</div>
);
};
export default TradeAlertPopupMenubarSelect;