102 lines
3.7 KiB
TypeScript
102 lines
3.7 KiB
TypeScript
import type { FormEvent, KeyboardEvent, FocusEvent, MutableRefObject } from "react";
|
|
import { StockSearchForm } from "@/features/trade/components/search/StockSearchForm";
|
|
import { StockSearchHistory } from "@/features/trade/components/search/StockSearchHistory";
|
|
import { StockSearchResults } from "@/features/trade/components/search/StockSearchResults";
|
|
import type {
|
|
DashboardStockSearchHistoryItem,
|
|
DashboardStockSearchItem,
|
|
} from "@/features/trade/types/trade.types";
|
|
|
|
interface TradeSearchSectionProps {
|
|
canSearch: boolean;
|
|
isSearchPanelOpen: boolean;
|
|
isSearching: boolean;
|
|
keyword: string;
|
|
selectedSymbol?: string;
|
|
searchResults: DashboardStockSearchItem[];
|
|
searchHistory: DashboardStockSearchHistoryItem[];
|
|
searchShellRef: MutableRefObject<HTMLDivElement | null>;
|
|
onKeywordChange: (value: string) => void;
|
|
onSearchSubmit: (event: FormEvent) => void;
|
|
onSearchFocus: () => void;
|
|
onSearchShellBlur: (event: FocusEvent<HTMLDivElement>) => void;
|
|
onSearchShellKeyDown: (event: KeyboardEvent<HTMLDivElement>) => void;
|
|
onSelectStock: (item: DashboardStockSearchItem) => void;
|
|
onRemoveHistory: (symbol: string) => void;
|
|
onClearHistory: () => void;
|
|
}
|
|
|
|
/**
|
|
* @description 트레이드 화면 상단의 검색 입력/결과/히스토리 드롭다운 영역을 렌더링합니다.
|
|
* @see features/trade/components/TradeContainer.tsx TradeContainer에서 검색 섹션을 분리해 렌더 복잡도를 줄입니다.
|
|
* @see features/trade/hooks/useTradeSearchPanel.ts 패널 열림/닫힘 및 포커스 핸들러를 전달받습니다.
|
|
*/
|
|
export function TradeSearchSection({
|
|
canSearch,
|
|
isSearchPanelOpen,
|
|
isSearching,
|
|
keyword,
|
|
selectedSymbol,
|
|
searchResults,
|
|
searchHistory,
|
|
searchShellRef,
|
|
onKeywordChange,
|
|
onSearchSubmit,
|
|
onSearchFocus,
|
|
onSearchShellBlur,
|
|
onSearchShellKeyDown,
|
|
onSelectStock,
|
|
onRemoveHistory,
|
|
onClearHistory,
|
|
}: TradeSearchSectionProps) {
|
|
return (
|
|
<div className="z-30 flex-none border-b bg-background/95 p-4 backdrop-blur-sm dark:border-brand-800/45 dark:bg-brand-900/22">
|
|
{/* ========== SEARCH SHELL ========== */}
|
|
<div
|
|
ref={searchShellRef}
|
|
onBlurCapture={onSearchShellBlur}
|
|
onKeyDownCapture={onSearchShellKeyDown}
|
|
className="relative mx-auto max-w-2xl"
|
|
>
|
|
<StockSearchForm
|
|
keyword={keyword}
|
|
onKeywordChange={onKeywordChange}
|
|
onSubmit={onSearchSubmit}
|
|
onInputFocus={onSearchFocus}
|
|
disabled={!canSearch}
|
|
isLoading={isSearching}
|
|
/>
|
|
|
|
{/* ========== SEARCH DROPDOWN ========== */}
|
|
{isSearchPanelOpen && canSearch && (
|
|
<div className="absolute left-0 right-0 top-full z-50 mt-1 max-h-80 overflow-x-hidden overflow-y-auto rounded-md border bg-background shadow-lg dark:border-brand-800/45 dark:bg-brand-950/95">
|
|
{searchResults.length > 0 ? (
|
|
<StockSearchResults
|
|
items={searchResults}
|
|
onSelect={onSelectStock}
|
|
selectedSymbol={selectedSymbol}
|
|
/>
|
|
) : keyword.trim() ? (
|
|
<div className="px-3 py-2 text-sm text-muted-foreground">
|
|
{isSearching ? "검색 중..." : "검색 결과가 없습니다."}
|
|
</div>
|
|
) : searchHistory.length > 0 ? (
|
|
<StockSearchHistory
|
|
items={searchHistory}
|
|
onSelect={onSelectStock}
|
|
onRemove={onRemoveHistory}
|
|
onClear={onClearHistory}
|
|
selectedSymbol={selectedSymbol}
|
|
/>
|
|
) : (
|
|
<div className="px-3 py-2 text-sm text-muted-foreground">
|
|
최근 검색 종목이 없습니다.
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|