매매 시그널 알림 우측패널 동작 수정

This commit is contained in:
Macbook
2026-05-29 01:38:58 +09:00
parent 094a285363
commit 3db7e85e2c
7 changed files with 241 additions and 88 deletions
@@ -22,12 +22,17 @@ export interface TradeNotificationHScrollPaneProps {
/** 스크린리더용 영역 설명 */
label: string;
children: React.ReactNode;
/** 스크롤 시 뷰포트에 완전히 보이지 않는 차트 카드 라벨 숨김 */
clipPeekLabels?: boolean;
}
const PEEK_LABEL_THRESHOLD = 0.99;
const TradeNotificationHScrollPane: React.FC<TradeNotificationHScrollPaneProps> = ({
className = '',
label,
children,
clipPeekLabels = false,
}) => {
const viewportRef = useRef<HTMLDivElement>(null);
const [canScroll, setCanScroll] = useState(false);
@@ -46,6 +51,31 @@ const TradeNotificationHScrollPane: React.FC<TradeNotificationHScrollPaneProps>
setAtEnd(!overflow || el.scrollLeft >= max - 2);
}, []);
const bindPeekLabelClip = useCallback(() => {
const viewport = viewportRef.current;
if (!viewport || !clipPeekLabels) return () => {};
const applyPeek = (card: HTMLElement, ratio: number) => {
card.classList.toggle('tnl-chart-card--peek', ratio < PEEK_LABEL_THRESHOLD);
};
const cards = Array.from(viewport.querySelectorAll<HTMLElement>('.tnl-chart-card'));
if (cards.length === 0) return () => {};
const io = new IntersectionObserver(
entries => {
entries.forEach(entry => applyPeek(entry.target as HTMLElement, entry.intersectionRatio));
},
{ root: viewport, threshold: [0, 0.5, 0.99, 1] },
);
cards.forEach(card => io.observe(card));
return () => {
io.disconnect();
cards.forEach(card => card.classList.remove('tnl-chart-card--peek'));
};
}, [clipPeekLabels]);
useEffect(() => {
const el = viewportRef.current;
if (!el) return;
@@ -60,6 +90,23 @@ const TradeNotificationHScrollPane: React.FC<TradeNotificationHScrollPaneProps>
};
}, [syncScrollState, children]);
useEffect(() => {
if (!clipPeekLabels) return;
let cleanup = bindPeekLabelClip();
const viewport = viewportRef.current;
if (!viewport) return cleanup;
const mo = new MutationObserver(() => {
cleanup();
cleanup = bindPeekLabelClip();
});
mo.observe(viewport, { childList: true, subtree: true });
return () => {
mo.disconnect();
cleanup();
};
}, [clipPeekLabels, bindPeekLabelClip, children]);
const scrollByStep = useCallback((dir: -1 | 1) => {
const el = viewportRef.current;
if (!el) return;
@@ -399,6 +399,7 @@ const TradeNotificationListRow: React.FC<Props> = ({
indicatorScrollNeeded ? 'tnl-row-gallery-indicators--scroll' : '',
].filter(Boolean).join(' ')}
label="보조지표"
clipPeekLabels={indicatorScrollNeeded}
>
<div className="tnl-charts-track tnl-charts-track--indicators-only">
{indicatorCards.map(card => (
@@ -225,6 +225,7 @@ const TradeSignalMiniChart: React.FC<Props> = ({
showHoverToolbar={false}
seriesPriceLabelsEnabled={false}
paneLayoutClamp
showPaneLegend={false}
/>
</div>
);