매매 시그널 알림 우측패널 동작 수정
This commit is contained in:
+12
-4
@@ -11502,15 +11502,23 @@ html.theme-light .tam-disclaimer { color: #90a4ae; }
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.tnl-page--with-right .tnl-right {
|
.tnl-page--with-right .tnl-right {
|
||||||
|
flex: 0 0 0;
|
||||||
|
width: 0;
|
||||||
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-left: none;
|
||||||
|
background: var(--bg2);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: width 0.22s cubic-bezier(0.4, 0, 0.2, 1), flex-basis 0.22s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tnl-page--with-right .tnl-side-wrap--right.tnl-side-wrap--open .tnl-right.tnl-right--open {
|
||||||
flex: 0 0 min(380px, 38vw);
|
flex: 0 0 min(380px, 38vw);
|
||||||
width: min(380px, 38vw);
|
width: min(380px, 38vw);
|
||||||
min-width: 300px;
|
min-width: 300px;
|
||||||
max-width: 420px;
|
max-width: 420px;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
border-left: 1px solid var(--border);
|
border-left: 1px solid var(--border);
|
||||||
background: var(--bg2);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
.tnl-right-tabs {
|
.tnl-right-tabs {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ import PaperSplitPanel from './paper/PaperSplitPanel';
|
|||||||
import { useUpbitOrderbook } from '../hooks/useUpbitOrderbook';
|
import { useUpbitOrderbook } from '../hooks/useUpbitOrderbook';
|
||||||
import { useUpbitRecentTrades } from '../hooks/useUpbitRecentTrades';
|
import { useUpbitRecentTrades } from '../hooks/useUpbitRecentTrades';
|
||||||
import { useAppSettings } from '../hooks/useAppSettings';
|
import { useAppSettings } from '../hooks/useAppSettings';
|
||||||
|
import { readStoredBool, storeBool } from './strategyEditor/usePanelResize';
|
||||||
import '../styles/strategyEditorTheme.css';
|
import '../styles/strategyEditorTheme.css';
|
||||||
import '../styles/virtualTradingDashboard.css';
|
import '../styles/virtualTradingDashboard.css';
|
||||||
|
import '../styles/builderPageShell.css';
|
||||||
import '../styles/tradeNotificationList.css';
|
import '../styles/tradeNotificationList.css';
|
||||||
import TradeNotificationListRow from './tradeNotification/TradeNotificationListRow';
|
import TradeNotificationListRow from './tradeNotification/TradeNotificationListRow';
|
||||||
import TradeNotificationGridLayoutPicker from './tradeNotification/TradeNotificationGridLayoutPicker';
|
import TradeNotificationGridLayoutPicker from './tradeNotification/TradeNotificationGridLayoutPicker';
|
||||||
@@ -32,6 +34,8 @@ import {
|
|||||||
|
|
||||||
type RightTab = 'trade' | 'orderbook';
|
type RightTab = 'trade' | 'orderbook';
|
||||||
|
|
||||||
|
const TNL_RIGHT_OPEN_KEY = 'tnl-right-open';
|
||||||
|
|
||||||
const IcTrash = () => (
|
const IcTrash = () => (
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
|
||||||
<polyline points="3 6 5 6 21 6" />
|
<polyline points="3 6 5 6 21 6" />
|
||||||
@@ -106,6 +110,7 @@ export const TradeNotificationListPage: React.FC<Props> = ({
|
|||||||
const [summary, setSummary] = useState<PaperSummaryDto | null>(null);
|
const [summary, setSummary] = useState<PaperSummaryDto | null>(null);
|
||||||
const orderAnchorRef = useRef<HTMLDivElement>(null);
|
const orderAnchorRef = useRef<HTMLDivElement>(null);
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
const [rightOpen, setRightOpen] = useState(() => readStoredBool(TNL_RIGHT_OPEN_KEY, true));
|
||||||
const { settings: appSettings } = useAppSettings();
|
const { settings: appSettings } = useAppSettings();
|
||||||
const chartLiveReceiveHighlight = appSettings.chartLiveReceiveHighlight ?? true;
|
const chartLiveReceiveHighlight = appSettings.chartLiveReceiveHighlight ?? true;
|
||||||
|
|
||||||
@@ -244,11 +249,30 @@ export const TradeNotificationListPage: React.FC<Props> = ({
|
|||||||
saveTradeNotificationGridPreset(preset);
|
saveTradeNotificationGridPreset(preset);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const toggleRightPanel = useCallback(() => {
|
||||||
|
setRightOpen(prev => {
|
||||||
|
const next = !prev;
|
||||||
|
storeBool(TNL_RIGHT_OPEN_KEY, next);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const isListView = listLayout === 'list';
|
const isListView = listLayout === 'list';
|
||||||
const gridCols = getTradeNotificationGridPreset(gridPreset).cols;
|
const gridCols = getTradeNotificationGridPreset(gridPreset).cols;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`tnl-page tnl-page--with-right bps-page--vtd se-page se-page--${theme} app ${theme}`}>
|
<div
|
||||||
|
className={[
|
||||||
|
'tnl-page',
|
||||||
|
'tnl-page--with-right',
|
||||||
|
'bps-page--vtd',
|
||||||
|
'se-page',
|
||||||
|
`se-page--${theme}`,
|
||||||
|
'app',
|
||||||
|
theme,
|
||||||
|
rightOpen ? '' : 'tnl-page--right-collapsed',
|
||||||
|
].filter(Boolean).join(' ')}
|
||||||
|
>
|
||||||
<div className="tnl-body">
|
<div className="tnl-body">
|
||||||
<div className="tnl-main" style={{ containerType: 'inline-size', containerName: 'tnl-main' }}>
|
<div className="tnl-main" style={{ containerType: 'inline-size', containerName: 'tnl-main' }}>
|
||||||
<div className="tnl-header">
|
<div className="tnl-header">
|
||||||
@@ -375,91 +399,123 @@ export const TradeNotificationListPage: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<aside className="tnl-right" aria-label="매매·호가">
|
<div className={`tnl-side-wrap tnl-side-wrap--right${rightOpen ? ' tnl-side-wrap--open' : ''}`}>
|
||||||
<div className="bps-right-tabs tnl-right-tabs">
|
<button
|
||||||
<button
|
type="button"
|
||||||
type="button"
|
className={`bps-panel-handle bps-panel-handle--right${rightOpen ? ' bps-panel-handle--open' : ''}`}
|
||||||
className={`bps-right-tab${rightTab === 'trade' ? ' bps-right-tab--on' : ''}`}
|
onClick={toggleRightPanel}
|
||||||
onClick={() => setRightTab('trade')}
|
title={rightOpen ? '매매·호가 패널 닫기' : '매매·호가 패널 열기'}
|
||||||
|
aria-expanded={rightOpen}
|
||||||
|
aria-label={rightOpen ? '매매·호가 패널 닫기' : '매매·호가 패널 열기'}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
width="8"
|
||||||
|
height="14"
|
||||||
|
viewBox="0 0 8 14"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="1.8"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
aria-hidden
|
||||||
>
|
>
|
||||||
매매
|
{rightOpen ? (
|
||||||
</button>
|
<polyline points="2,1 6,7 2,13" />
|
||||||
<button
|
) : (
|
||||||
type="button"
|
<polyline points="6,1 2,7 6,13" />
|
||||||
className={`bps-right-tab${rightTab === 'orderbook' ? ' bps-right-tab--on' : ''}`}
|
)}
|
||||||
onClick={() => setRightTab('orderbook')}
|
</svg>
|
||||||
>
|
</button>
|
||||||
호가
|
<aside
|
||||||
</button>
|
className={`tnl-right${rightOpen ? ' tnl-right--open' : ''}`}
|
||||||
</div>
|
aria-label="매매·호가"
|
||||||
<div className="tnl-right-body ptd-right-body" ref={orderAnchorRef}>
|
aria-hidden={!rightOpen}
|
||||||
{rightTab === 'trade' ? (
|
>
|
||||||
<PaperSplitPanel
|
<div className="bps-right-tabs tnl-right-tabs">
|
||||||
className="ptd-split-panel--right"
|
<button
|
||||||
topTitle="매수"
|
type="button"
|
||||||
bottomTitle="매도"
|
className={`bps-right-tab${rightTab === 'trade' ? ' bps-right-tab--on' : ''}`}
|
||||||
top={(
|
onClick={() => setRightTab('trade')}
|
||||||
<div className="ptd-order-card">
|
>
|
||||||
<TradeOrderPanel
|
매매
|
||||||
side="buy"
|
</button>
|
||||||
market={selectedMarket}
|
<button
|
||||||
tradePrice={tradePrice}
|
type="button"
|
||||||
availableKrw={coerceFiniteNumber(summary?.cashBalance) ?? 0}
|
className={`bps-right-tab${rightTab === 'orderbook' ? ' bps-right-tab--on' : ''}`}
|
||||||
fillRequest={fillBuy}
|
onClick={() => setRightTab('orderbook')}
|
||||||
searchAnchorRef={orderAnchorRef}
|
>
|
||||||
onMarketSelect={m => handleSelectMarket(m)}
|
호가
|
||||||
showSymbolField
|
</button>
|
||||||
paperTradingEnabled={paperTradingEnabled}
|
</div>
|
||||||
paperAutoTradeEnabled={paperAutoTradeEnabled}
|
<div className="tnl-right-body ptd-right-body" ref={orderAnchorRef}>
|
||||||
onPaperOrderFilled={() => void refreshPaperData()}
|
{rightTab === 'trade' ? (
|
||||||
/>
|
<PaperSplitPanel
|
||||||
</div>
|
className="ptd-split-panel--right"
|
||||||
)}
|
topTitle="매수"
|
||||||
bottom={(
|
bottomTitle="매도"
|
||||||
<div className="ptd-order-card">
|
top={(
|
||||||
<TradeOrderPanel
|
<div className="ptd-order-card">
|
||||||
side="sell"
|
<TradeOrderPanel
|
||||||
market={selectedMarket}
|
side="buy"
|
||||||
tradePrice={tradePrice}
|
market={selectedMarket}
|
||||||
availableCoinQty={posQty}
|
tradePrice={tradePrice}
|
||||||
fillRequest={fillSell}
|
availableKrw={coerceFiniteNumber(summary?.cashBalance) ?? 0}
|
||||||
onMarketSelect={m => handleSelectMarket(m)}
|
fillRequest={fillBuy}
|
||||||
showSymbolField={false}
|
searchAnchorRef={orderAnchorRef}
|
||||||
paperTradingEnabled={paperTradingEnabled}
|
onMarketSelect={m => handleSelectMarket(m)}
|
||||||
paperAutoTradeEnabled={paperAutoTradeEnabled}
|
showSymbolField
|
||||||
onPaperOrderFilled={() => void refreshPaperData()}
|
paperTradingEnabled={paperTradingEnabled}
|
||||||
/>
|
paperAutoTradeEnabled={paperAutoTradeEnabled}
|
||||||
</div>
|
onPaperOrderFilled={() => void refreshPaperData()}
|
||||||
)}
|
/>
|
||||||
/>
|
</div>
|
||||||
) : (
|
)}
|
||||||
<div className="rsp-ob-stack ptd-ob-stack--fill">
|
bottom={(
|
||||||
<div className="rsp-trade-card rsp-ob-card">
|
<div className="ptd-order-card">
|
||||||
<div className="rsp-trade-card-title rsp-ob-card-title">실시간 호가</div>
|
<TradeOrderPanel
|
||||||
<div className="rsp-trade-card-body rsp-ob-card-body">
|
side="sell"
|
||||||
<OrderbookPanel
|
market={selectedMarket}
|
||||||
market={selectedMarket}
|
tradePrice={tradePrice}
|
||||||
asks={orderbook.asks}
|
availableCoinQty={posQty}
|
||||||
bids={orderbook.bids}
|
fillRequest={fillSell}
|
||||||
totalAskSize={orderbook.totalAskSize}
|
onMarketSelect={m => handleSelectMarket(m)}
|
||||||
totalBidSize={orderbook.totalBidSize}
|
showSymbolField={false}
|
||||||
wsStatus={obWsStatus}
|
paperTradingEnabled={paperTradingEnabled}
|
||||||
bestAsk={spread.bestAsk}
|
paperAutoTradeEnabled={paperAutoTradeEnabled}
|
||||||
bestBid={spread.bestBid}
|
onPaperOrderFilled={() => void refreshPaperData()}
|
||||||
spread={spread.spread}
|
/>
|
||||||
spreadPct={spread.pct}
|
</div>
|
||||||
prevClose={orderbookPrevClose}
|
)}
|
||||||
tickerInfo={orderbookTickerInfo}
|
/>
|
||||||
recentTrades={recentTrades}
|
) : (
|
||||||
tradeStrength={tradeStrength}
|
<div className="rsp-ob-stack ptd-ob-stack--fill">
|
||||||
onRowClick={handleOrderbookRowClick}
|
<div className="rsp-trade-card rsp-ob-card">
|
||||||
/>
|
<div className="rsp-trade-card-title rsp-ob-card-title">실시간 호가</div>
|
||||||
|
<div className="rsp-trade-card-body rsp-ob-card-body">
|
||||||
|
<OrderbookPanel
|
||||||
|
market={selectedMarket}
|
||||||
|
asks={orderbook.asks}
|
||||||
|
bids={orderbook.bids}
|
||||||
|
totalAskSize={orderbook.totalAskSize}
|
||||||
|
totalBidSize={orderbook.totalBidSize}
|
||||||
|
wsStatus={obWsStatus}
|
||||||
|
bestAsk={spread.bestAsk}
|
||||||
|
bestBid={spread.bestBid}
|
||||||
|
spread={spread.spread}
|
||||||
|
spreadPct={spread.pct}
|
||||||
|
prevClose={orderbookPrevClose}
|
||||||
|
tickerInfo={orderbookTickerInfo}
|
||||||
|
recentTrades={recentTrades}
|
||||||
|
tradeStrength={tradeStrength}
|
||||||
|
onRowClick={handleOrderbookRowClick}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
</div>
|
||||||
</div>
|
</aside>
|
||||||
</aside>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -135,6 +135,8 @@ interface TradingChartProps {
|
|||||||
paneSeparatorOptions?: ChartPaneSeparatorOptions;
|
paneSeparatorOptions?: ChartPaneSeparatorOptions;
|
||||||
/** true: pane 합산 높이로 chart-container 를 키우지 않고 래퍼 높이에 맞춤 (미니 차트·알림 목록) */
|
/** true: pane 합산 높이로 chart-container 를 키우지 않고 래퍼 높이에 맞춤 (미니 차트·알림 목록) */
|
||||||
paneLayoutClamp?: boolean;
|
paneLayoutClamp?: boolean;
|
||||||
|
/** 보조지표 pane 레전드(이름·값 오버레이) 표시 (기본 true, 알림 미니차트는 false) */
|
||||||
|
showPaneLegend?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TradingChart: React.FC<TradingChartProps> = ({
|
const TradingChart: React.FC<TradingChartProps> = ({
|
||||||
@@ -167,6 +169,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
|||||||
chartVisible = true,
|
chartVisible = true,
|
||||||
paneSeparatorOptions,
|
paneSeparatorOptions,
|
||||||
paneLayoutClamp = false,
|
paneLayoutClamp = false,
|
||||||
|
showPaneLegend = true,
|
||||||
}) => {
|
}) => {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null); // 스크롤 래퍼
|
const wrapperRef = useRef<HTMLDivElement>(null); // 스크롤 래퍼
|
||||||
@@ -1396,7 +1399,7 @@ const TradingChart: React.FC<TradingChartProps> = ({
|
|||||||
onRestore={() => setCandleOnlyMode(false)}
|
onRestore={() => setCandleOnlyMode(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{chartMgr && !candleOnlyMode && (
|
{chartMgr && !candleOnlyMode && showPaneLegend && (
|
||||||
<PaneLegendPortal
|
<PaneLegendPortal
|
||||||
manager={chartMgr}
|
manager={chartMgr}
|
||||||
indicators={indicators}
|
indicators={indicators}
|
||||||
|
|||||||
@@ -22,12 +22,17 @@ export interface TradeNotificationHScrollPaneProps {
|
|||||||
/** 스크린리더용 영역 설명 */
|
/** 스크린리더용 영역 설명 */
|
||||||
label: string;
|
label: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
/** 스크롤 시 뷰포트에 완전히 보이지 않는 차트 카드 라벨 숨김 */
|
||||||
|
clipPeekLabels?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PEEK_LABEL_THRESHOLD = 0.99;
|
||||||
|
|
||||||
const TradeNotificationHScrollPane: React.FC<TradeNotificationHScrollPaneProps> = ({
|
const TradeNotificationHScrollPane: React.FC<TradeNotificationHScrollPaneProps> = ({
|
||||||
className = '',
|
className = '',
|
||||||
label,
|
label,
|
||||||
children,
|
children,
|
||||||
|
clipPeekLabels = false,
|
||||||
}) => {
|
}) => {
|
||||||
const viewportRef = useRef<HTMLDivElement>(null);
|
const viewportRef = useRef<HTMLDivElement>(null);
|
||||||
const [canScroll, setCanScroll] = useState(false);
|
const [canScroll, setCanScroll] = useState(false);
|
||||||
@@ -46,6 +51,31 @@ const TradeNotificationHScrollPane: React.FC<TradeNotificationHScrollPaneProps>
|
|||||||
setAtEnd(!overflow || el.scrollLeft >= max - 2);
|
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(() => {
|
useEffect(() => {
|
||||||
const el = viewportRef.current;
|
const el = viewportRef.current;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
@@ -60,6 +90,23 @@ const TradeNotificationHScrollPane: React.FC<TradeNotificationHScrollPaneProps>
|
|||||||
};
|
};
|
||||||
}, [syncScrollState, children]);
|
}, [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 scrollByStep = useCallback((dir: -1 | 1) => {
|
||||||
const el = viewportRef.current;
|
const el = viewportRef.current;
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
|
|||||||
@@ -399,6 +399,7 @@ const TradeNotificationListRow: React.FC<Props> = ({
|
|||||||
indicatorScrollNeeded ? 'tnl-row-gallery-indicators--scroll' : '',
|
indicatorScrollNeeded ? 'tnl-row-gallery-indicators--scroll' : '',
|
||||||
].filter(Boolean).join(' ')}
|
].filter(Boolean).join(' ')}
|
||||||
label="보조지표"
|
label="보조지표"
|
||||||
|
clipPeekLabels={indicatorScrollNeeded}
|
||||||
>
|
>
|
||||||
<div className="tnl-charts-track tnl-charts-track--indicators-only">
|
<div className="tnl-charts-track tnl-charts-track--indicators-only">
|
||||||
{indicatorCards.map(card => (
|
{indicatorCards.map(card => (
|
||||||
|
|||||||
@@ -225,6 +225,7 @@ const TradeSignalMiniChart: React.FC<Props> = ({
|
|||||||
showHoverToolbar={false}
|
showHoverToolbar={false}
|
||||||
seriesPriceLabelsEnabled={false}
|
seriesPriceLabelsEnabled={false}
|
||||||
paneLayoutClamp
|
paneLayoutClamp
|
||||||
|
showPaneLegend={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -148,6 +148,7 @@
|
|||||||
background: color-mix(in srgb, var(--bg) 40%, var(--bg2));
|
background: color-mix(in srgb, var(--bg) 40%, var(--bg2));
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
isolation: isolate;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tnl-hscroll-pane__viewport {
|
.tnl-hscroll-pane__viewport {
|
||||||
@@ -217,6 +218,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tnl-hscroll-pane__rail {
|
.tnl-hscroll-pane__rail {
|
||||||
|
position: relative;
|
||||||
|
z-index: 4;
|
||||||
flex: 0 0 32px;
|
flex: 0 0 32px;
|
||||||
width: 32px;
|
width: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -789,6 +792,12 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 보조지표 스크롤 — 뷰포트에 완전히 들어오지 않은 카드는 라벨 숨김 */
|
||||||
|
.tnl-chart-card--peek .tnl-chart-card-head {
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.tnl-chart-card-meta {
|
.tnl-chart-card-meta {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
color: var(--text3);
|
color: var(--text3);
|
||||||
@@ -907,6 +916,34 @@
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 우측 매매·호가 패널 접기/펼치기 */
|
||||||
|
.tnl-side-wrap--right {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-height: 0;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
z-index: 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tnl-page--right-collapsed .tnl-main {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tnl-side-wrap--right .tnl-right-tabs,
|
||||||
|
.tnl-side-wrap--right .tnl-right-body {
|
||||||
|
min-width: min(380px, 38vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tnl-side-wrap--right:not(.tnl-side-wrap--open) .tnl-right-tabs,
|
||||||
|
.tnl-side-wrap--right:not(.tnl-side-wrap--open) .tnl-right-body {
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* 툴바 — 좌: 필터·액션 · 우: 목록/그리드·배치·삭제 */
|
/* 툴바 — 좌: 필터·액션 · 우: 목록/그리드·배치·삭제 */
|
||||||
.tnl-page--with-right .tnl-toolbar {
|
.tnl-page--with-right .tnl-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user