[search] searcinputoverlay

- searchInputoverlay작성 및 수정
- 패널 및 검색결과부분 수정.(버튼링크등등)
This commit is contained in:
junghoon86.park
2025-10-16 20:57:34 +09:00
parent 659b760c8c
commit 473f58c68c
4 changed files with 572 additions and 113 deletions

View File

@@ -0,0 +1,162 @@
import React, {
useCallback,
useEffect,
useMemo,
} from 'react';
import { useDispatch } from 'react-redux';
import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
import { getSearch } from '../../actions/searchActions';
import TFullPopup from '../../components/TFullPopup/TFullPopup';
import TInput, {
ICONS,
KINDS,
} from '../../components/TInput/TInput';
import css from './SearchInputOverlay.module.less';
const OverlayContainer = SpotlightContainerDecorator(
{
enterTo: "default-element",
restrict: "self-only", // 포커스를 overlay 내부로만 제한
},
"div"
);
const SpottableKeyword = Spottable("div");
const SearchInputOverlay = ({
isVisible,
onClose,
searchQuery = "",
onSearchChange,
onSearchSubmit,
handleClick,
}) => {
const dispatch = useDispatch();
const recentResultSearches = useMemo(
() => [
"Puppy food",
"Dog toy",
"Mather's Day",
"Gift",
"Easter Day",
"Royal Canin puppy food2",
"Shark",
],
[]
);
// Input 창에서 API 호출 핸들러 (돋보기 아이콘 클릭 시에만)
const handleSearchSubmit = useCallback(
(query) => {
console.log("[SearchInputOverlay] Search submit:", searchQuery);
if (searchQuery && searchQuery.trim()) {
dispatch(
getSearch({
service: "com.lgshop.app",
query: query,
domain: "theme,show,item",
})
);
// API 호출 후 Input 박스로 포커스 이동
setTimeout(() => {
onClose();
Spotlight.focus("voice-overlay-input-box");
}, 100);
}
},
[dispatch, searchQuery]
);
// Input 창에서 엔터키 핸들러 (API 호출하지 않음)
const handleInputKeyDown = useCallback((e) => {
if (e.key === "Enter" || e.keyCode === 13) {
e.preventDefault();
// Enter 키로는 API 호출하지 않음
// 돋보기 아이콘 클릭/Enter로만 API 호출
}
}, []);
const handleDimClick = useCallback(
(e) => {
console.log("[VoiceInputOverlay] dimBackground clicked");
onClose();
},
[onClose]
);
useEffect(() => {
const spotTime = setTimeout(() => {
Spotlight.focus("search_input_overlay");
}, 100);
return () => {
clearTimeout(spotTime);
};
}, []);
return (
<TFullPopup
open={isVisible}
onClose={onClose}
noAutoDismiss={true}
spotlightRestrict="self-only"
spotlightId={"search_input_overlay_container"}
noAnimation={false}
scrimType="transparent"
className={css.tFullPopupWrapper}
>
<div className={css.overlayContainer}>
{/* 배경 dim 레이어 - 클릭하면 닫힘 */}
<div className={css.dimBackground} onClick={handleDimClick} />
<OverlayContainer
className={css.contentArea}
spotlightId={"search_input_overlay"}
spotlightDisabled={!isVisible}
>
{/* 입력창과 마이크 버튼 - SearchPanel.inputContainer와 동일한 구조 */}
<div
className={css.inputWrapper}
onClick={(e) => e.stopPropagation()}
>
<div className={css.searchInputWrapper}>
<TInput
className={css.inputBox}
kind={KINDS.withIcon}
icon={ICONS.search}
value={searchQuery}
onChange={onSearchChange}
onKeyDown={handleInputKeyDown}
onIconClick={() => {
handleSearchSubmit(searchQuery);
}}
spotlightId={"search_overlay_input_box"}
/>
</div>
</div>
{/* 이곳에 작업 */}
<div className={css.overLayRecent}>
{recentResultSearches.map((keyword, index) => (
<SpottableKeyword
key={`recentResult-${index}`}
className={css.keywordButton}
onClick={() => handleClick(keyword)}
spotlightId={`recent-Resultkeyword-${index}`}
>
{keyword}
</SpottableKeyword>
))}
</div>
</OverlayContainer>
</div>
</TFullPopup>
);
};
export default SearchInputOverlay;

View File

@@ -0,0 +1,171 @@
@import "../../style/CommonStyle.module.less";
// TFullPopup wrapper - TFullPopup의 기본 스타일을 override하지 않음
.tFullPopupWrapper {
// TFullPopup의 기본 동작 유지
}
.overlayContainer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
pointer-events: all;
}
.dimBackground {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 1000;
cursor: pointer;
}
.contentArea {
position: absolute;
top: 0;
left: 0;
padding-left: 120px;
width: 100%;
height: 100%;
z-index: 1002;
pointer-events: none; // 빈 공간 클릭 시 dimBackground로 이벤트 전달
display: flex;
flex-direction: column;
align-items: center;
}
// 입력창과 마이크 버튼 영역 - SearchPanel.inputContainer와 동일 (210px 높이)
.inputWrapper {
width: 100%;
padding-top: 55px;
padding-bottom: 30px;
padding-left: 60px;
padding-right: 60px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 1003;
position: relative;
pointer-events: all; // 입력 영역은 클릭 가능
.searchInputWrapper {
height: 100px;
display: flex;
justify-content: flex-start;
align-items: center;
min-height: 100px;
max-height: 100px;
> * {
margin-right: 15px;
&:last-child {
margin-right: 0;
}
}
}
}
.inputBox {
width: 880px;
height: 100px !important;
padding-left: 50px;
padding-right: 40px;
background: white;
border-radius: 1000px;
border: 5px solid #ccc;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 1003;
position: relative;
> div:first-child {
margin: 0 !important;
width: calc(100% - 121px) !important;
height: 90px !important;
padding: 20px 40px 20px 0px !important;
border: none !important;
background-color: #fff !important;
input {
text-align: left;
color: black;
font-size: 42px;
font-family: "LG Smart UI";
font-weight: 700;
line-height: 42px;
outline: none;
border: none;
background: transparent;
}
* {
outline: none !important;
border: none !important;
}
}
&:focus-within,
&:focus {
border: 5px solid @PRIMARY_COLOR_RED;
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
}
}
.overLayRecent {
width: 790px;
height: 488px;
z-index: 2;
display: flex;
flex-direction: column;
justify-content: center;
* {
margin-bottom: 5px;
}
.keywordButton {
height: 64px;
background: white;
border-radius: 100px;
border: 5px solid #dadada;
padding: 0 20px;
flex-direction: column;
justify-content: center;
align-items: flex-start;
display: inline-flex;
cursor: pointer;
transition: all 0.2s ease;
width: fit-content;
> * {
margin-bottom: 5px;
&:last-child {
margin-bottom: 0;
}
}
color: black;
font-size: 24px;
font-family: "LG Smart UI";
font-weight: 700;
line-height: 24px;
text-align: center;
word-wrap: break-word;
&:hover {
border-color: @PRIMARY_COLOR_RED;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
&:focus {
border: 5px solid @PRIMARY_COLOR_RED;
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
outline: none;
}
}
}

View File

@@ -1,22 +1,43 @@
// src/views/SearchPanel/SearchPanel.new.jsx
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import classNames from 'classnames';
import { useDispatch, useSelector } from 'react-redux';
import {
useDispatch,
useSelector,
} from 'react-redux';
import { Job } from '@enact/core/util';
import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
import SpotlightContainerDecorator
from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
import { setContainerLastFocusedElement } from '@enact/spotlight/src/container';
import micIcon from '../../../assets/images/searchpanel/image-mic.png';
import hotPicksImage from '../../../assets/images/searchpanel/img-hotpicks.png';
import hotPicksBrandImage from '../../../assets/images/searchpanel/img-search-hotpicks.png';
import { sendLogGNB, sendLogTotalRecommend } from '../../actions/logActions';
import hotPicksBrandImage
from '../../../assets/images/searchpanel/img-search-hotpicks.png';
import {
sendLogGNB,
sendLogTotalRecommend,
} from '../../actions/logActions';
import { getMyRecommandedKeyword } from '../../actions/myPageActions';
import { popPanel, updatePanel } from '../../actions/panelActions';
import { getSearch, resetSearch, searchMain } from '../../actions/searchActions';
import {
popPanel,
updatePanel,
} from '../../actions/panelActions';
import {
getSearch,
resetSearch,
searchMain,
} from '../../actions/searchActions';
import {
showErrorToast,
showInfoToast,
@@ -26,50 +47,77 @@ import {
showWarningToast,
} from '../../actions/toastActions';
import TBody from '../../components/TBody/TBody';
import TInput, { ICONS, KINDS } from '../../components/TInput/TInput';
import TInput, {
ICONS,
KINDS,
} from '../../components/TInput/TInput';
import TPanel from '../../components/TPanel/TPanel';
import TScroller from '../../components/TScroller/TScroller';
import TVerticalPagenator from '../../components/TVerticalPagenator/TVerticalPagenator';
import TVirtualGridList from '../../components/TVirtualGridList/TVirtualGridList';
import TVerticalPagenator
from '../../components/TVerticalPagenator/TVerticalPagenator';
import TVirtualGridList
from '../../components/TVirtualGridList/TVirtualGridList';
// import VirtualKeyboardContainer from "../../components/TToast/VirtualKeyboardContainer";
import usePrevious from '../../hooks/usePrevious';
import { LOG_CONTEXT_NAME, LOG_MENU, LOG_MESSAGE_ID, panel_names } from '../../utils/Config';
import {
LOG_CONTEXT_NAME,
LOG_MENU,
LOG_MESSAGE_ID,
panel_names,
} from '../../utils/Config';
import { SpotlightIds } from '../../utils/SpotlightIds';
import NoSearchResults from './NoSearchResults/NoSearchResults';
import RecommendedKeywords from './RecommendedKeywords/RecommendedKeywords';
import SearchInputOverlay from './SearchInpuOverlay';
import css from './SearchPanel.new.module.less';
import SearchResultsNew from './SearchResults.new';
import VoiceInputOverlay, { VOICE_MODES } from './VoiceInputOverlay/VoiceInputOverlay';
import VoiceInputOverlay, {
VOICE_MODES,
} from './VoiceInputOverlay/VoiceInputOverlay';
const ContainerBasic = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
const ContainerBasic = SpotlightContainerDecorator(
{ enterTo: "last-focused" },
"div"
);
// 검색 입력 영역 컨테이너
const InputContainer = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
const InputContainer = SpotlightContainerDecorator(
{ enterTo: "last-focused" },
"div"
);
// 콘텐츠 섹션 컨테이너
const SectionContainer = SpotlightContainerDecorator({ enterTo: 'last-focused' }, 'div');
const SectionContainer = SpotlightContainerDecorator(
{ enterTo: "last-focused" },
"div"
);
// Spottable 컴포넌트들
const SpottableMicButton = Spottable('div');
const SpottableKeyword = Spottable('div');
const SpottableProduct = Spottable('div');
const SpottableLi = Spottable('li');
const SpottableMicButton = Spottable("div");
const SpottableKeyword = Spottable("div");
const SpottableProduct = Spottable("div");
const SpottableLi = Spottable("li");
const ITEMS_PER_PAGE = 9;
// Spotlight ID 상수
const SPOTLIGHT_IDS = {
SEARCH_INPUT_LAYER: 'search-input-layer',
SEARCH_INPUT_BOX: 'search-input-box',
MICROPHONE_BUTTON: 'microphone-button',
RECENT_SEARCHES_SECTION: 'recent-searches-section',
TOP_SEARCHES_SECTION: 'top-searches-section',
POPULAR_BRANDS_SECTION: 'popular-brands-section',
HOT_PICKS_SECTION: 'hot-picks-section',
SEARCH_VERTICAL_PAGENATOR: 'search_verticalPagenator',
SEARCH_INPUT_LAYER: "search-input-layer",
SEARCH_INPUT_BOX: "search-input-box",
MICROPHONE_BUTTON: "microphone-button",
RECENT_SEARCHES_SECTION: "recent-searches-section",
TOP_SEARCHES_SECTION: "top-searches-section",
POPULAR_BRANDS_SECTION: "popular-brands-section",
HOT_PICKS_SECTION: "hot-picks-section",
SEARCH_VERTICAL_PAGENATOR: "search_verticalPagenator",
};
export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOptions = [] }) {
export default function SearchPanel({
panelInfo,
isOnTop,
spotlightId,
scrollOptions = [],
}) {
const dispatch = useDispatch();
const loadingComplete = useSelector((state) => state.common?.loadingComplete);
const recommandedKeywords = useSelector(
@@ -78,57 +126,74 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
const { searchDatas: searchDatas } = useSelector((state) => state.search);
const searchPerformed = useSelector((state) => state.search.searchPerformed);
const panels = useSelector((state) => state.panels.panels);
const shopperHouseData = useSelector((state) => state.search.shopperHouseData);
const shopperHouseData = useSelector(
(state) => state.search.shopperHouseData
);
const [firstSpot, setFirstSpot] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
const [paginatedKeywords, setPaginatedKeywords] = useState([]);
const [pageChanged, setPageChanged] = useState(false);
const [searchQuery, setSearchQuery] = useState(panelInfo.searchVal ? panelInfo.searchVal : null);
const [searchQuery, setSearchQuery] = useState(
panelInfo.searchVal ? panelInfo.searchVal : null
);
const [position, setPosition] = useState(null);
const [showVirtualKeyboard, setShowVirtualKeyboard] = useState(false);
const [isVoiceOverlayVisible, setIsVoiceOverlayVisible] = useState(false);
const [isSearchOverlayVisible, setIsSearchOverlayVisible] = useState(false);
const [voiceMode, setVoiceMode] = useState(VOICE_MODES.PROMPT);
//인풋창 포커스 구분을 위함
const [inputFocus, setInputFocus] = useState(false);
const _onFocus = () => {
const _onFocus = useCallback(() => {
setInputFocus(true);
};
const _onBlur = () => {
}, [inputFocus]);
const _onBlur = useCallback(() => {
setInputFocus(false);
};
}, [inputFocus]);
let searchQueryRef = usePrevious(searchQuery);
let isOnTopRef = usePrevious(isOnTop);
const firstButtonSpotlightId = 'first-keyword-button';
const firstButtonSpotlightId = "first-keyword-button";
const cbChangePageRef = useRef(null);
const [focusedContainerId, setFocusedContainerId] = useState(panelInfo?.focusedContainerId);
const [focusedContainerId, setFocusedContainerId] = useState(
panelInfo?.focusedContainerId
);
const focusedContainerIdRef = usePrevious(focusedContainerId);
// 가짜 데이터 - 실제로는 Redux store나 API에서 가져와야 함
const recentSearches = useMemo(() => ['Puppy food', 'Dog toy', 'Fitness'], []);
const recentSearches = useMemo(
() => ["Puppy food", "Dog toy", "Fitness"],
[]
);
const recentResultSearches = useMemo(
() => [
'Puppy food',
'Dog toy',
"Puppy food",
"Dog toy",
"Mather's Day",
'Gift',
'Easter Day',
'Royal Canin puppy food',
'Shark',
"Gift",
"Easter Day",
"Royal Canin puppy food2",
"Shark",
],
[]
);
const topSearches = useMemo(
() => ["Mather's Day", 'Gift', 'Easter Day', 'Royal Canin puppy food', 'Fitness', 'Parrot'],
() => [
"Mather's Day",
"Gift",
"Easter Day",
"Royal Canin puppy food",
"Fitness",
"Parrot",
],
[]
);
const popularBrands = useMemo(
() => ['Shark', 'Ninja', 'Skechers', 'LocknLock', '8Greens', 'LGE'],
() => ["Shark", "Ninja", "Skechers", "LocknLock", "8Greens", "LGE"],
[]
);
const hotPicks = useMemo(
@@ -137,32 +202,32 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
id: 1,
image: hotPicksImage,
brandLogo: hotPicksBrandImage,
brandName: 'Product Name',
title: 'New Shark Vacuum! Your pet Hair Solution!',
brandName: "Product Name",
title: "New Shark Vacuum! Your pet Hair Solution!",
isForYou: false,
},
{
id: 2,
image: hotPicksImage,
brandLogo: hotPicksBrandImage,
brandName: 'Product Name',
title: 'New Shark Vacuum! Your pet Hair Solution!',
brandName: "Product Name",
title: "New Shark Vacuum! Your pet Hair Solution!",
isForYou: false,
},
{
id: 3,
image: hotPicksImage,
brandLogo: hotPicksBrandImage,
brandName: 'Product Name',
title: 'New Shark Vacuum! Your pet Hair Solution!',
brandName: "Product Name",
title: "New Shark Vacuum! Your pet Hair Solution!",
isForYou: false,
},
{
id: 4,
image: hotPicksImage,
brandLogo: hotPicksBrandImage,
brandName: 'Product Name',
title: 'New Shark Vacuum! Your pet Hair Solution!',
brandName: "Product Name",
title: "New Shark Vacuum! Your pet Hair Solution!",
isForYou: true,
},
],
@@ -242,7 +307,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
dispatch(
sendLogTotalRecommend({
query: searchQuery,
searchType: searchPerformed ? 'query' : 'keyword',
searchType: searchPerformed ? "query" : "keyword",
result: result,
contextName: LOG_CONTEXT_NAME.SEARCH,
messageId: LOG_MESSAGE_ID.SEARCH_ITEM,
@@ -266,9 +331,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
if (query.trim()) {
dispatch(
getSearch({
service: 'com.lgshop.app',
service: "com.lgshop.app",
query: query,
domain: 'theme,show,item',
domain: "theme,show,item",
})
);
@@ -301,7 +366,8 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
}, [currentPage]);
const hasPrevPage = currentPage > 1;
const hasNextPage = currentPage * ITEMS_PER_PAGE < recommandedKeywords?.length;
const hasNextPage =
currentPage * ITEMS_PER_PAGE < recommandedKeywords?.length;
useEffect(() => {
if (panelInfo && isOnTop) {
@@ -332,7 +398,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
}
// Enter 키 처리
if (e.key === 'Enter') {
if (e.key === "Enter") {
e.preventDefault();
if (showVirtualKeyboard) {
// 가상 키보드가 열려있으면 검색 실행하고 키보드 닫기
@@ -346,26 +412,32 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
// 방향키 처리 - Spotlight 네비게이션 허용
const arrowKeys = [
'ArrowLeft',
'ArrowRight',
'ArrowUp',
'ArrowDown',
'Left',
'Right',
'Up',
'Down',
"ArrowLeft",
"ArrowRight",
"ArrowUp",
"ArrowDown",
"Left",
"Right",
"Up",
"Down",
];
if (arrowKeys.includes(e.key)) {
// 입력 필드가 비어있고 왼쪽 화살표인 경우에만 방지
if (position === 0 && (e.key === 'Left' || e.key === 'ArrowLeft') && !searchQuery) {
if (
position === 0 &&
(e.key === "Left" || e.key === "ArrowLeft") &&
!searchQuery
) {
e.preventDefault();
return;
}
// 오른쪽 화살표 키 처리 - 포커스 이동 허용
if (e.key === 'ArrowRight' || e.key === 'Right') {
if (e.key === "ArrowRight" || e.key === "Right") {
// 커서가 텍스트 끝에 있을 때만 포커스 이동 허용
const input = document.querySelector(`[data-spotlight-id="input-field-box"] > input`);
const input = document.querySelector(
`[data-spotlight-id="input-field-box"] > input`
);
if (input && position === input.value.length) {
// 커서가 텍스트 끝에 있으면 포커스 이동 허용
return;
@@ -380,7 +452,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
);
const cursorPosition = useCallback(() => {
const input = document.querySelector(`[data-spotlight-id="input-field-box"] > input`);
const input = document.querySelector(
`[data-spotlight-id="input-field-box"] > input`
);
if (input) {
setPosition(input.selectionStart);
}
@@ -403,10 +477,10 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
setIsVoiceOverlayVisible(false);
return;
}
if (searchQuery === null || searchQuery === '') {
if (searchQuery === null || searchQuery === "") {
dispatch(popPanel(panel_names.SEARCH_PANEL));
} else {
setSearchQuery('');
setSearchQuery("");
setCurrentPage(1);
dispatch(resetSearch());
Spotlight.focus(SPOTLIGHT_IDS.SEARCH_INPUT_BOX);
@@ -421,7 +495,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
Spotlight.resume();
setFirstSpot(true);
if (panelInfo.currentSpot) {
if (panels[panels.length - 1]?.name === 'searchpanel') {
if (panels[panels.length - 1]?.name === "searchpanel") {
Spotlight.focus(panelInfo.currentSpot);
}
}
@@ -443,27 +517,31 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
const handleKeywordClick = useCallback(
(keyword) => {
setSearchQuery(keyword);
handleSearchSubmit(keyword);
// 키워드 선택 알림
dispatch(
showSuccessToast(`"${keyword}" 키워드로 검색합니다.`, {
duration: 2000,
})
);
setIsSearchOverlayVisible(false);
setInputFocus(false);
},
[handleSearchSubmit, dispatch]
);
const handleKeywordInput = (keyword) => {
setSearchQuery(keyword);
setIsSearchOverlayVisible(false);
setInputFocus(false);
};
// 상품 클릭 핸들러
const handleProductClick = useCallback((product) => {
// 상품 상세 페이지로 이동하는 로직 구현
console.log('Product clicked:', product);
console.log("Product clicked:", product);
}, []);
// 테스트용 Toast 핸들러들
const handleTestToasts = useCallback(() => {
// 간단한 Toast 테스트
dispatch(showSuccessToast('테스트 메시지입니다', { duration: 3000 }));
dispatch(showSuccessToast("테스트 메시지입니다", { duration: 3000 }));
}, [dispatch]);
// ProductCard 컴포넌트
@@ -502,12 +580,20 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
<div className={css.productInfo}>
{showBrandLogo && (
<div className={css.productBrandWrapper}>
<img src={brandLogo} alt={brandName} className={css.brandLogo} />
<img
src={brandLogo}
alt={brandName}
className={css.brandLogo}
/>
</div>
)}
<div className={css.productDetails}>
{showBrandName && <div className={css.brandName}>{brandName}</div>}
{showProductTitle && <div className={css.productTitle}>{title}</div>}
{showBrandName && (
<div className={css.brandName}>{brandName}</div>
)}
{showProductTitle && (
<div className={css.productTitle}>{title}</div>
)}
</div>
</div>
</SpottableProduct>
@@ -516,15 +602,26 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
[]
);
//test
useEffect(() => {
console.log('###searchDatas', searchDatas);
console.log('###panelInfo', panelInfo);
}, [searchDatas, panelInfo]);
if (searchPerformed && searchQuery !== null && inputFocus === true) {
setIsSearchOverlayVisible(true);
if (cbChangePageRef.current) {
cbChangePageRef.current(0, true);
}
}
}, [searchPerformed, searchQuery, inputFocus]);
return (
<TPanel className={css.container} handleCancel={onCancel} spotlightId={spotlightId}>
<TBody className={css.tBody} scrollable={true} spotlightDisabled={!isOnTop}>
<TPanel
className={css.container}
handleCancel={onCancel}
spotlightId={spotlightId}
>
<TBody
className={css.tBody}
scrollable={true}
spotlightDisabled={!isOnTop}
>
<ContainerBasic>
{isOnTop && (
<TVerticalPagenator
@@ -543,8 +640,10 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
className={classNames(
css.inputContainer,
inputFocus === true && css.inputFocus,
searchDatas && css.searchValue /* 이건 결과값 있을때만. 조건 추가필요 */,
isVoiceOverlayVisible && css.hidden
searchDatas &&
css.searchValue /* 이건 결과값 있을때만. 조건 추가필요 */,
(isVoiceOverlayVisible || isSearchOverlayVisible) &&
css.hidden
)}
data-wheel-point={true}
spotlightId={SPOTLIGHT_IDS.SEARCH_INPUT_LAYER}
@@ -576,14 +675,18 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
className={css.microphoneButton}
onClick={onClickMic}
onKeyDown={(e) => {
if (e.key === 'Enter') {
if (e.key === "Enter") {
onClickMic();
}
}}
spotlightId={SPOTLIGHT_IDS.MICROPHONE_BUTTON}
>
<div className={css.microphoneCircle}>
<img src={micIcon} alt="Microphone" className={css.microphoneIcon} />
<img
src={micIcon}
alt="Microphone"
className={css.microphoneIcon}
/>
</div>
</SpottableMicButton>
@@ -598,9 +701,11 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
</div>
</InputContainer>
{/* 검색내용이 존재하고, 인풋창에 포커스가 가서 노출 시작 */}
{inputFocus === true &&
(searchDatas?.item?.length > 0 || searchDatas?.show?.length > 0) && (
{/* 검색내용이 존재하고, 인풋창에 포커스가 가서 노출 시작 searchInputOverlay 변경중*/}
{/* {inputFocus === true &&
(searchDatas?.item?.length > 0 ||
searchDatas?.show?.length > 0) && (
<>
<div className={css.overLay}></div>
<div className={css.overLayRecent}>
@@ -616,7 +721,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
))}
</div>
</>
)}
)} */}
{/* 검색내용이 존재하고, 인풋창에 포커스가 가서 노출 끝! */}
{/* 결과갑 부분 작업중 시작! */}
@@ -629,6 +734,7 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
itemInfo={searchDatas.item}
showInfo={searchDatas.show}
shopperHouseInfo={shopperHouseData}
keywordClick={handleKeywordClick}
/>
) : (
<ContainerBasic className={css.contentContainer}>
@@ -644,7 +750,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
>
<div className={css.sectionHeader}>
<div className={css.sectionIndicator}></div>
<div className={css.sectionTitle}>Your Recent Searches</div>
<div className={css.sectionTitle}>
Your Recent Searches
</div>
</div>
<div className={css.keywordList}>
{recentSearches.map((keyword, index) => (
@@ -716,7 +824,9 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
>
<div className={css.sectionHeader}>
<div className={css.sectionIndicator}></div>
<div className={css.sectionTitle}>Hot Picks for You</div>
<div className={css.sectionTitle}>
Hot Picks for You
</div>
</div>
<div className={css.productList}>
{hotPicks && hotPicks.length > 0 && (
@@ -775,6 +885,16 @@ export default function SearchPanel({ panelInfo, isOnTop, spotlightId, scrollOpt
onSearchSubmit={handleSearchSubmit}
/>
)}
{isSearchOverlayVisible && (
<SearchInputOverlay
isVisible={isSearchOverlayVisible}
onClose={() => setIsSearchOverlayVisible(false)}
searchQuery={searchQuery}
onSearchChange={handleSearchChange}
onSearchSubmit={handleSearchSubmit}
handleClick={handleKeywordInput}
/>
)}
</TPanel>
);
}

View File

@@ -35,7 +35,7 @@ const SearchResultsNew = ({
showInfo,
themeInfo,
shopperHouseInfo,
onClick,
keywordClick,
}) => {
// ShopperHouse 데이터를 ItemCard 형식으로 변환
const convertedShopperHouseItems = useMemo(() => {
@@ -204,23 +204,29 @@ const SearchResultsNew = ({
[themeInfo]
);
const _onClick = useCallback(
(e) => {
if (onClick) {
onClick(e);
}
},
[onClick]
);
return (
<div className={css.searchBox}>
<div className={css.topBox}>
<span className={css.topBoxTitle}>How about these?</span>
<ul className={css.topBoxList}>
<SpottableLi className={css.topBoxListItem}>Puppy food</SpottableLi>
<SpottableLi className={css.topBoxListItem}>Dog toy</SpottableLi>
<SpottableLi className={css.topBoxListItem}>Fitness</SpottableLi>
<SpottableLi
className={css.topBoxListItem}
onClick={() => keywordClick("Puppy food")}
>
Puppy food
</SpottableLi>
<SpottableLi
className={css.topBoxListItem}
onClick={() => keywordClick("Dog toy")}
>
Dog toy
</SpottableLi>
<SpottableLi
className={css.topBoxListItem}
onClick={() => keywordClick("Fitness")}
>
Fitness
</SpottableLi>
</ul>
</div>
{themeInfo && themeInfo?.length > 0 && (