실시간 차트 멀티모드 수정
This commit is contained in:
@@ -46,11 +46,12 @@ interface MarketSearchPanelProps {
|
||||
onSelect: (market: string) => void;
|
||||
onClose: () => void;
|
||||
/**
|
||||
* 슬롯 내 심볼 버튼 클릭 시 전달되는 슬롯 컨테이너 rect.
|
||||
* 제공되면 해당 슬롯 영역 안에서 패널이 표시된다.
|
||||
* 슬롯/버튼 rect. anchorPlacement 에 따라 패널 위치가 결정된다.
|
||||
* 없으면 기본(전체화면 좌측 고정) 위치로 표시된다.
|
||||
*/
|
||||
anchorRect?: DOMRect | null;
|
||||
/** slot=슬롯 영역 채움, dropdown=클릭 지점 아래 드롭다운 */
|
||||
anchorPlacement?: 'slot' | 'dropdown';
|
||||
/** overlay=팝업/고정패널, embedded=좌측 패널 인라인 목록 */
|
||||
variant?: 'overlay' | 'embedded';
|
||||
/** 외부 입력과 검색어 동기화 */
|
||||
@@ -67,7 +68,7 @@ interface MarketSearchPanelProps {
|
||||
}
|
||||
|
||||
export const MarketSearchPanel: React.FC<MarketSearchPanelProps> = ({
|
||||
currentMarket, onSelect, onClose, anchorRect, initialQuery = '',
|
||||
currentMarket, onSelect, onClose, anchorRect, anchorPlacement = 'slot', initialQuery = '',
|
||||
variant = 'overlay', query: controlledQuery, onQueryChange,
|
||||
actionMode = 'favorite', onAdd, addedMarkets,
|
||||
}) => {
|
||||
@@ -233,36 +234,60 @@ export const MarketSearchPanel: React.FC<MarketSearchPanelProps> = ({
|
||||
// 인라인 스타일로 z-index를 명시해 패널이 항상 최상단에 렌더되게 보장한다.
|
||||
const PANEL_Z = 99999;
|
||||
|
||||
const DROPDOWN_H = 480;
|
||||
const isCompactDropdown = anchorPlacement === 'dropdown' && !embedded;
|
||||
|
||||
const panelStyle: React.CSSProperties | undefined = embedded
|
||||
? undefined
|
||||
: anchorRect
|
||||
? {
|
||||
position: 'fixed',
|
||||
top: anchorRect.top + HEADER_H,
|
||||
left: anchorRect.left,
|
||||
width: Math.max(anchorRect.width, MIN_W),
|
||||
height: anchorRect.height - HEADER_H,
|
||||
maxWidth: anchorRect.width,
|
||||
zIndex: PANEL_Z,
|
||||
// document.body 포털 시 CSS var()가 상속되지 않아 배경이 투명해지는 문제 방지
|
||||
background: 'var(--bg2, #24283b)',
|
||||
color: 'var(--text, #c0caf5)',
|
||||
border: '1px solid var(--border, rgba(122,162,247,0.15))',
|
||||
}
|
||||
? anchorPlacement === 'dropdown'
|
||||
? {
|
||||
position: 'fixed',
|
||||
top: anchorRect.bottom + 4,
|
||||
left: anchorRect.left,
|
||||
width: Math.max(420, anchorRect.width, MIN_W),
|
||||
height: Math.min(DROPDOWN_H, window.innerHeight - anchorRect.bottom - 12),
|
||||
maxWidth: Math.min(520, window.innerWidth - anchorRect.left - 8),
|
||||
minHeight: 320,
|
||||
zIndex: PANEL_Z,
|
||||
background: 'var(--bg2, #24283b)',
|
||||
color: 'var(--text, #c0caf5)',
|
||||
border: '1px solid var(--border, rgba(122,162,247,0.15))',
|
||||
}
|
||||
: {
|
||||
position: 'fixed',
|
||||
top: anchorRect.top + HEADER_H,
|
||||
left: anchorRect.left,
|
||||
width: Math.max(anchorRect.width, MIN_W),
|
||||
height: anchorRect.height - HEADER_H,
|
||||
maxWidth: anchorRect.width,
|
||||
zIndex: PANEL_Z,
|
||||
// document.body 포털 시 CSS var()가 상속되지 않아 배경이 투명해지는 문제 방지
|
||||
background: 'var(--bg2, #24283b)',
|
||||
color: 'var(--text, #c0caf5)',
|
||||
border: '1px solid var(--border, rgba(122,162,247,0.15))',
|
||||
}
|
||||
: undefined; // undefined → CSS 기본값(.msp-panel) 그대로 사용
|
||||
|
||||
// 슬롯 전용 backdrop: 해당 슬롯 영역만 반투명 오버레이로 덮어 패널이 명확히 보이게 함
|
||||
// backdrop: dropdown=전체 화면, slot=해당 슬롯 영역만
|
||||
const backdropStyle: React.CSSProperties | undefined = anchorRect
|
||||
? {
|
||||
position: 'fixed',
|
||||
top: anchorRect.top,
|
||||
left: anchorRect.left,
|
||||
width: anchorRect.width,
|
||||
height: anchorRect.height,
|
||||
zIndex: PANEL_Z - 1,
|
||||
background: 'rgba(0,0,0,0.45)',
|
||||
backdropFilter: 'none',
|
||||
}
|
||||
? anchorPlacement === 'dropdown'
|
||||
? {
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
zIndex: PANEL_Z - 1,
|
||||
background: 'rgba(0,0,0,0.28)',
|
||||
}
|
||||
: {
|
||||
position: 'fixed',
|
||||
top: anchorRect.top,
|
||||
left: anchorRect.left,
|
||||
width: anchorRect.width,
|
||||
height: anchorRect.height,
|
||||
zIndex: PANEL_Z - 1,
|
||||
background: 'rgba(0,0,0,0.45)',
|
||||
backdropFilter: 'none',
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const handleRowSelect = useCallback((market: string) => {
|
||||
@@ -280,7 +305,12 @@ export const MarketSearchPanel: React.FC<MarketSearchPanelProps> = ({
|
||||
)}
|
||||
|
||||
<div
|
||||
className={`msp-panel${anchorRect ? ' msp-panel--slot' : ''}${embedded ? ' msp-panel--embedded' : ''}`}
|
||||
className={[
|
||||
'msp-panel',
|
||||
anchorRect && anchorPlacement === 'dropdown' ? 'msp-panel--dropdown' : '',
|
||||
anchorRect && anchorPlacement === 'slot' ? 'msp-panel--slot' : '',
|
||||
embedded ? 'msp-panel--embedded' : '',
|
||||
].filter(Boolean).join(' ')}
|
||||
style={panelStyle}
|
||||
>
|
||||
|
||||
@@ -317,14 +347,18 @@ export const MarketSearchPanel: React.FC<MarketSearchPanelProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={`msp-header${embedded ? ' msp-header--embedded' : ''}`}>
|
||||
<span className="msp-col msp-col-fav">{embedded ? '추가' : ''}</span>
|
||||
<span className="msp-col msp-col-name">한글명</span>
|
||||
<div className={`msp-header${embedded ? ' msp-header--embedded' : ''}${isCompactDropdown ? ' msp-header--compact' : ''}`}>
|
||||
{!isCompactDropdown && (
|
||||
<span className="msp-col msp-col-fav">{embedded ? '추가' : ''}</span>
|
||||
)}
|
||||
<span className="msp-col msp-col-name">{isCompactDropdown ? '종목명' : '한글명'}</span>
|
||||
{!embedded && (
|
||||
<>
|
||||
<span className="msp-col msp-col-price">현재가</span>
|
||||
<span className="msp-col msp-col-change">전일대비</span>
|
||||
<span className="msp-col msp-col-vol">거래대금</span>
|
||||
{!isCompactDropdown && (
|
||||
<span className="msp-col msp-col-vol">거래대금</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -348,40 +382,47 @@ export const MarketSearchPanel: React.FC<MarketSearchPanelProps> = ({
|
||||
return (
|
||||
<div
|
||||
key={m.market}
|
||||
className={`msp-item${embedded ? ' msp-item--embedded' : ''}${isActive ? ' active' : ''}`}
|
||||
className={[
|
||||
'msp-item',
|
||||
embedded ? 'msp-item--embedded' : '',
|
||||
isCompactDropdown ? 'msp-item--compact' : '',
|
||||
isActive ? 'active' : '',
|
||||
].filter(Boolean).join(' ')}
|
||||
onClick={() => handleRowSelect(m.market)}
|
||||
>
|
||||
{actionMode === 'add' ? (
|
||||
<button
|
||||
type="button"
|
||||
className={`msp-add-btn${isAdded ? ' msp-add-btn--done' : ''}`}
|
||||
onClick={e => handleAddClick(m.market, e)}
|
||||
disabled={isAdded}
|
||||
title={isAdded ? '이미 추가됨' : '투자대상에 추가'}
|
||||
>
|
||||
{isAdded ? (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
<polyline points="20 6 9 17 4 12"/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
<span
|
||||
className={`msp-col msp-col-fav msp-star${isFav ? ' starred' : ''}`}
|
||||
onClick={e => toggleFav(m.market, e)}
|
||||
title={isFav ? '즐겨찾기 해제' : '즐겨찾기 추가'}
|
||||
>
|
||||
{isFav ? '★' : '☆'}
|
||||
</span>
|
||||
{!isCompactDropdown && (
|
||||
actionMode === 'add' ? (
|
||||
<button
|
||||
type="button"
|
||||
className={`msp-add-btn${isAdded ? ' msp-add-btn--done' : ''}`}
|
||||
onClick={e => handleAddClick(m.market, e)}
|
||||
disabled={isAdded}
|
||||
title={isAdded ? '이미 추가됨' : '투자대상에 추가'}
|
||||
>
|
||||
{isAdded ? (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
<polyline points="20 6 9 17 4 12"/>
|
||||
</svg>
|
||||
) : (
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
||||
<line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/>
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
) : (
|
||||
<span
|
||||
className={`msp-col msp-col-fav msp-star${isFav ? ' starred' : ''}`}
|
||||
onClick={e => toggleFav(m.market, e)}
|
||||
title={isFav ? '즐겨찾기 해제' : '즐겨찾기 추가'}
|
||||
>
|
||||
{isFav ? '★' : '☆'}
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
|
||||
<span className="msp-col msp-col-name">
|
||||
<span className="msp-kor">{m.korean_name}</span>
|
||||
<span className="msp-sym">{m.symbol}</span>
|
||||
{!isCompactDropdown && <span className="msp-sym">{m.symbol}</span>}
|
||||
</span>
|
||||
|
||||
{!embedded && (
|
||||
@@ -396,9 +437,11 @@ export const MarketSearchPanel: React.FC<MarketSearchPanelProps> = ({
|
||||
? '-'
|
||||
: `${isUp ? '+' : ''}${safeToFixed(rate, 2)}%`}
|
||||
</span>
|
||||
<span className="msp-col msp-col-vol">
|
||||
{tk?.acc_trade_price_24h ? formatVol(tk.acc_trade_price_24h) : '-'}
|
||||
</span>
|
||||
{!isCompactDropdown && (
|
||||
<span className="msp-col msp-col-vol">
|
||||
{tk?.acc_trade_price_24h ? formatVol(tk.acc_trade_price_24h) : '-'}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user