diff --git a/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.figma.jsx b/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.figma.jsx new file mode 100644 index 00000000..d88ccfbd --- /dev/null +++ b/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.figma.jsx @@ -0,0 +1,257 @@ +
+
+
+ +
+
+ How about these? +
+
+
+
+
+
+ SEE MORE +
+
+
+
+
+
+
+ What are some luxury skincare products. +
+
+
+
+ Popular makeup gift sets uitable for mother. +
+
+
+
+ Which anti-aging cosmetics in 50s or 60s. +
+
+
+
+ Elegant fragrance or cosmetic bundles. +
+
+
+
+ What beauty brands offer special gift boxes. +
+
+
+
; diff --git a/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.jsx b/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.jsx new file mode 100644 index 00000000..f151cb87 --- /dev/null +++ b/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.jsx @@ -0,0 +1,126 @@ +import React, { useCallback, useMemo } from 'react'; + +import PropTypes from 'prop-types'; +import { useDispatch } from 'react-redux'; + +import Spottable from '@enact/spotlight/Spottable'; +import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator'; + +import { getShopperHouseSearch } from '../../../actions/searchActions'; +import css from './HowAboutThese.module.less'; + +const OverlayContainer = SpotlightContainerDecorator( + { + enterTo: 'default-element', + restrict: 'self-only', + }, + 'div' +); + +const SpottableBubble = Spottable('div'); +const SpottableSeeMoreButton = Spottable('div'); + +const HowAboutThese = ({ relativeQueries = [], onQueryClick, onClose }) => { + const dispatch = useDispatch(); + + // 기본 relativeQueries가 없는 경우를 위한 fallback + const queries = useMemo(() => { + return relativeQueries.length > 0 + ? relativeQueries + : [ + 'What are some luxury skincare products', + 'Popular makeup gift sets suitable for mother', + 'Which anti-aging cosmetics in 50s or 60s', + 'Elegant fragrance or cosmetic bundles', + 'What beauty brands offer special gift boxes', + ]; + }, [relativeQueries]); + + // 검색어 클릭 핸들러 + const handleQueryClick = useCallback( + (query) => { + console.log('[HowAboutThese] Query clicked:', query); + + // 외부에서 전달된 onQueryClick이 있으면 사용 + if (onQueryClick) { + onQueryClick(query); + return; + } + + // 기본적으로 ShopperHouse API를 통해 재검색 + dispatch(getShopperHouseSearch(query)); + + // 팝업 닫기 + if (onClose) { + onClose(); + } + }, + [dispatch, onQueryClick, onClose] + ); + + // "COLLAPSE" 버튼 클릭 핸들러 (Full 버전을 Small로 축소) + const handleCollapseClick = useCallback(() => { + console.log('[HowAboutThese] Collapse clicked - 축소하여 Small 버전으로 전환'); + if (onClose) { + onClose(); + } + }, [onClose]); + + // 첫 번째 쿼리는 특별 스타일 (핑크색 버블) + const firstQuery = queries[0]; + const remainingQueries = queries.slice(1); + + return ( + + {/* Header Section */} +
+
+
+ {/* 아이콘 자리 - 현재는 비워둠 */} +
+
+
How about these?
+
+
+
+
+ + COLLAPSE + +
+
+ + {/* Bubbles Section */} +
+ {/* 첫 번째 버블 - 핑크색 */} + {firstQuery && ( + handleQueryClick(firstQuery)} + > + {firstQuery} + + )} + + {/* 나머지 버블들 - 흰색 */} + {remainingQueries.map((query, index) => ( + handleQueryClick(query)} + > + {query} + + ))} +
+ + ); +}; + +HowAboutThese.propTypes = { + relativeQueries: PropTypes.array, + onQueryClick: PropTypes.func, + onClose: PropTypes.func, +}; + +export default HowAboutThese; diff --git a/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.module.less b/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.module.less new file mode 100644 index 00000000..5cd8ca61 --- /dev/null +++ b/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.module.less @@ -0,0 +1,209 @@ +.container { + width: 100%; + height: 860px; + padding: 30px 60px; + display: inline-flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + gap: 19px; + background: linear-gradient( + 360deg, + rgba(221.25, 221.25, 221.25, 0) 0%, + rgba(221.25, 221.25, 221.25, 0.85) 57%, + rgba(221.25, 221.25, 221.25, 0.9) 80%, + rgba(221, 221, 221, 0.9) 100% + ), + linear-gradient( + 360deg, + rgba(221.25, 221.25, 221.25, 0) 35%, + #DDDDDD 80%, + #DDDDDD 100% + ); +} + +/* Header Section */ +.header { + align-self: stretch; + display: inline-flex; + justify-content: flex-start; + align-items: center; +} + +.headerLeft { + flex: 1; + height: 48px; + position: relative; +} + +.headerContent { + display: flex; + align-items: center; + height: 100%; +} + +.iconPlaceholder { + width: 48px; + height: 48px; + position: absolute; + left: 0; + top: 0; + /* TODO: Add icon styling when needed */ +} + +.titleContainer { + position: absolute; + left: 53px; + top: 10px; + border-bottom: 1px solid black; + display: inline-flex; + justify-content: center; + align-items: center; + gap: 10px; +} + +.title { + color: #272727; + font-size: 24px; + font-family: 'Roboto', sans-serif; + font-weight: 700; + text-align: center; + display: flex; + justify-content: center; + flex-direction: column; + word-wrap: break-word; +} + +.headerRight { + flex: 1 1 0; + display: inline-flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-end; + gap: 10px; +} + +.seeMoreButton { + width: 200px; + height: 60px; + background: #808080; + border-radius: 6px; + display: inline-flex; + justify-content: center; + align-items: center; + padding: 0 20px; + box-sizing: border-box; + cursor: pointer; + transition: background-color 0.2s ease; + + &:focus { + outline: 2px solid #00a0e9; + outline-offset: 2px; + } + + &:hover { + background: #666666; + } +} + +.seeMoreText { + color: white; + font-size: 24px; + font-family: 'LG Smart UI', sans-serif; + font-weight: 700; + line-height: 24px; + text-align: center; + width: 100%; +} + +/* Bubbles Section */ +.bubblesContainer { + align-self: stretch; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + gap: 15px; + overflow: hidden; +} + +/* Primary Bubble (Pink) */ +.bubblePrimary { + height: 64px; + padding: 16px 20px; + background: #CE1C5E; + border-radius: 30px; + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); + outline: 1px solid #C70850; + outline-offset: -1px; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + gap: 20px; + cursor: pointer; + transition: all 0.2s ease; + box-sizing: border-box; + + &:focus { + outline: 2px solid #00a0e9; + outline-offset: 2px; + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25), 0 0 0 2px #00a0e9; + } + + &:hover { + background: #b91a52; + transform: translateY(-1px); + } + + &:active { + transform: translateY(0); + } +} + +/* Secondary Bubbles (White) */ +.bubbleSecondary { + padding: 20px; + background: white; + border-radius: 100px; + outline: 1px solid #DADADA; + outline-offset: -1px; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + gap: 10px; + cursor: pointer; + transition: all 0.2s ease; + box-sizing: border-box; + + &:focus { + outline: 2px solid #00a0e9; + outline-offset: 2px; + } + + &:hover { + background: #f8f8f8; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + } + + &:active { + transform: translateY(0); + } +} + +.bubbleText { + color: #EAEAEA; + font-size: 24px; + font-family: 'LG Smart UI', sans-serif; + font-weight: 700; + line-height: 24px; + text-align: center; + word-wrap: break-word; + width: 100%; + + .bubbleSecondary & { + color: black; + } +} \ No newline at end of file diff --git a/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.small.figama.jsx b/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.small.figama.jsx new file mode 100644 index 00000000..5bfb3375 --- /dev/null +++ b/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.small.figama.jsx @@ -0,0 +1,188 @@ +
+
+ +
+
+ How about these? +
+
+
+
+
+
+ What are some luxury skincare products. +
+
+
+
+ Popular makeup gift sets uitable for mother. +
+
+
+
+
+
+ Popular makeup gift sets uitable for mother. +
+
+
+
+
+
+ SEE MORE +
+
+
; diff --git a/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.small.jsx b/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.small.jsx new file mode 100644 index 00000000..d7c5e52f --- /dev/null +++ b/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.small.jsx @@ -0,0 +1,109 @@ +import React, { useCallback } from 'react'; + +import PropTypes from 'prop-types'; +import { useDispatch } from 'react-redux'; + +import Spottable from '@enact/spotlight/Spottable'; + +import { getShopperHouseSearch } from '../../../actions/searchActions'; +import css from './HowAboutThese.small.module.less'; + +const SpottableBubble = Spottable('div'); +const SpottableSeeMoreButton = Spottable('div'); + +const HowAboutTheseSmall = ({ relativeQueries = [], onQueryClick, onSeeMoreClick }) => { + const dispatch = useDispatch(); + + // 기본 relativeQueries가 없는 경우를 위한 fallback + const queries = + relativeQueries.length > 0 + ? relativeQueries + : [ + 'What are some luxury skincare products', + 'Popular makeup gift sets suitable for mother', + 'Which anti-aging cosmetics in 50s or 60s', + ]; + + // 검색어 클릭 핸들러 + const handleQueryClick = useCallback( + (query) => { + console.log('[HowAboutTheseSmall] Query clicked:', query); + + // 외부에서 전달된 onQueryClick이 있으면 사용 + if (onQueryClick) { + onQueryClick(query); + return; + } + + // 기본적으로 ShopperHouse API를 통해 재검색 + dispatch(getShopperHouseSearch(query)); + }, + [dispatch, onQueryClick] + ); + + // "SEE MORE" 버튼 클릭 핸들러 + const handleSeeMoreClick = useCallback(() => { + console.log('[HowAboutTheseSmall] See More clicked'); + + // 외부에서 전달된 onSeeMoreClick이 있으면 사용 + if (onSeeMoreClick) { + onSeeMoreClick(); + return; + } + + // 기본 동작: 확장된 뷰 표시 (나중에 구현) + console.log('[HowAboutTheseSmall] TODO: Show expanded view'); + }, [onSeeMoreClick]); + + // 첫 번째 두 개의 쿼리만 표시 (small 버전) + const displayQueries = queries.slice(0, 2); + + return ( +
+ {/* Header Section */} +
+ {/* 아이콘 자리 - 현재는 비워둠 */} +
+
+
How about these?
+
+
+ + {/* Bubbles Section */} +
+ {displayQueries.map((query, index) => ( + handleQueryClick(query)} + > +
{query}
+
+ ))} + + {/* Fade 효과를 위한 추가 요소 (피그마 디자인 참고) */} + {queries.length > 2 && ( +
+
+ +
{queries[2]}
+
+
+ )} +
+ + {/* See More Button */} + +
SEE MORE
+
+
+ ); +}; + +HowAboutTheseSmall.propTypes = { + relativeQueries: PropTypes.array, + onQueryClick: PropTypes.func, + onSeeMoreClick: PropTypes.func, +}; + +export default HowAboutTheseSmall; diff --git a/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.small.module.less b/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.small.module.less new file mode 100644 index 00000000..e61cb045 --- /dev/null +++ b/com.twin.app.shoptime/src/views/SearchPanel/HowAboutThese/HowAboutThese.small.module.less @@ -0,0 +1,151 @@ +.container { + width: 100%; + height: 100%; + padding: 30px 60px; // 상하 30px, 좌우 60px + background: #DDDDDD; + display: inline-flex; + justify-content: flex-start; + align-items: center; + gap: 19px; +} + +/* Header Section */ +.header { + width: 254px; + height: 48px; + position: relative; + flex-shrink: 0; // 고정 너비 유지 + display: flex; + align-items: center; +} + +.iconPlaceholder { + width: 48px; + height: 48px; + flex-shrink: 0; + /* TODO: Add icon styling when needed */ +} + +.titleContainer { + margin-left: 5px; // 아이콘과 제목 사이 간격 (53 - 48 = 5) + border-bottom: 1px solid black; + display: inline-flex; + justify-content: center; + align-items: center; + gap: 10px; + white-space: nowrap; // ⭐ 1줄로 표시 +} + +.title { + color: #272727; + font-size: 24px; + font-family: 'Roboto', sans-serif; + font-weight: 700; + white-space: nowrap; // ⭐ 1줄로 표시 + line-height: 1; // 텍스트 높이 조정 +} + +/* Bubbles Section */ +.bubblesContainer { + flex: 1 1 0; + overflow: hidden; + display: flex; // horizontal layout + justify-content: flex-start; + align-items: flex-start; + gap: 15px; +} + +.bubble { + padding: 20px; + background: white; + border-radius: 100px; + outline: 1px solid #DADADA; + outline-offset: -1px; + display: inline-flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + gap: 10px; + cursor: pointer; + transition: all 0.2s ease; + flex-shrink: 0; // 버블이 줄어들지 않도록 + + &:focus { + outline: 2px solid #00a0e9; + outline-offset: 2px; + } + + &:hover { + background: #f8f8f8; + transform: translateY(-1px); + } +} + +.fadeContainer { + width: 1031px; + height: 64px; + position: relative; + flex-shrink: 0; +} + +.fadeOverlay { + width: 508px; + height: 64px; + padding: 20px; + left: 0; + top: 0; + position: absolute; + background: linear-gradient(90deg, black 0%, rgba(102, 102, 102, 0) 60%); + border-radius: 100px; + border: 1px solid #DADADA; + pointer-events: none; // 클릭 방지 +} + +.fadeContainer .bubble { + left: 0; + top: 0; + position: absolute; +} + +.bubbleText { + text-align: center; + color: black; + font-size: 24px; + font-family: 'LG Smart UI', sans-serif; + font-weight: 700; + line-height: 24px; + word-wrap: break-word; +} + +/* See More Button */ +.seeMoreButton { + width: 200px; + height: 60px; + background: #808080; + border-radius: 6px; + display: flex; + justify-content: center; // 피그마: center + align-items: center; + cursor: pointer; + transition: background-color 0.2s ease; + flex-shrink: 0; // 고정 너비 유지 + + &:focus { + outline: 2px solid #00a0e9; + outline-offset: 2px; + } + + &:hover { + background: #666666; + } +} + +.seeMoreText { + text-align: center; + color: white; + font-size: 24px; + font-family: 'LG Smart UI', sans-serif; + font-weight: 700; + line-height: 24px; + word-wrap: break-word; +} \ No newline at end of file diff --git a/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.jsx b/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.jsx index 4e082e14..c4ed181e 100644 --- a/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.jsx +++ b/com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.jsx @@ -39,7 +39,7 @@ import usePrevious from '../../hooks/usePrevious'; import { LOG_CONTEXT_NAME, LOG_MENU, LOG_MESSAGE_ID, panel_names } from '../../utils/Config'; import SearchInputOverlay from './SearchInpuOverlay'; import css from './SearchPanel.new.module.less'; -import SearchResultsNew from './SearchResults.new'; +import SearchResultsNew from './SearchResults.new.v2'; import TInput, { ICONS, KINDS } from './TInput/TInput'; import VoiceInputOverlay, { VOICE_MODES } from './VoiceInputOverlay/VoiceInputOverlay'; diff --git a/com.twin.app.shoptime/src/views/SearchPanel/SearchResults.new.v2.jsx b/com.twin.app.shoptime/src/views/SearchPanel/SearchResults.new.v2.jsx new file mode 100644 index 00000000..0188342d --- /dev/null +++ b/com.twin.app.shoptime/src/views/SearchPanel/SearchResults.new.v2.jsx @@ -0,0 +1,318 @@ +import React, { useCallback, useMemo, useRef, useState } from 'react'; + +import classNames from 'classnames'; + +import Spottable from '@enact/spotlight/Spottable'; + +import downBtnImg from '../../../assets/images/btn/search_btn_down_arrow.png'; +import upBtnImg from '../../../assets/images/btn/search_btn_up_arrow.png'; +import CustomImage from '../../components/CustomImage/CustomImage'; +import TButtonTab, { LIST_TYPE } from '../../components/TButtonTab/TButtonTab'; +import TDropDown from '../../components/TDropDown/TDropDown'; +import TVirtualGridList from '../../components/TVirtualGridList/TVirtualGridList'; +import { $L } from '../../utils/helperMethods'; +import { SpotlightIds } from '../../utils/SpotlightIds'; +import css from './SearchResults.new.v2.module.less'; +import ItemCard from './SearchResultsNew/ItemCard'; +import ShowCard from './SearchResultsNew/ShowCard'; +import HowAboutThese from './HowAboutThese/HowAboutThese'; +import HowAboutTheseSmall from './HowAboutThese/HowAboutThese.small'; + +const ITEMS_PER_PAGE = 10; + +// HowAboutThese 모드 상수 +export const HOW_ABOUT_THESE_MODES = { + SMALL: 'small', // 작은 버전 (기본) + FULL: 'full', // 전체 버전 (팝업) +}; + +const SearchResultsNew = ({ itemInfo, showInfo, themeInfo, shopperHouseInfo, keywordClick }) => { + // ShopperHouse 데이터를 ItemCard 형식으로 변환 + const convertedShopperHouseItems = useMemo(() => { + if (!shopperHouseInfo || !shopperHouseInfo.results || shopperHouseInfo.results.length === 0) { + return null; + } + + const resultData = shopperHouseInfo.results[0]; + const docs = resultData.docs || []; + + return docs.map((doc) => { + const contentId = doc.contentId; + const tokens = contentId.split('_'); + const patnrId = tokens?.[4] || ''; + const prdtId = tokens?.[5] || ''; + + return { + thumbnail: doc.thumbnail || doc.imgPath || '', // 이미지 경로 (API 필드명 수정) + title: doc.title || doc.prdtName || '', // 제목 + dcPrice: doc.dcPrice || doc.price || '', // 할인가격 + price: doc.price || '', // 원가 + soldout: doc.soldout || 'N', // 품절 여부 + contentId, // 콘텐트 아이디 + reviewGrade: doc.reviewGrade || '', // 리뷰 점수 + partnerName: doc.partnerName || '', // 파트너 네임 + partnerLogo: doc.partnerLogo || '', // 파트너 로고 (API 명세서 추가) + rankInfo: doc.rankInfo || 0, // 랭킹 정보 (API 명세서 추가) + patnrId, // 파트너 아이디 + prdtId, // 상품 아이디 + // results 레벨 추가 정보 + searchId: resultData.searchId || '', + sortingType: resultData.sortingType || '', + rangeType: resultData.rangeType || '', + }; + }); + }, [shopperHouseInfo]); + const getButtonTabList = () => { + // ShopperHouse 데이터가 있으면 그것을 사용, 없으면 기존 검색 결과 사용 + const itemLength = convertedShopperHouseItems?.length || itemInfo?.length || 0; + const showLength = showInfo?.length || 0; + + return [ + itemLength && $L(`ITEM (${itemLength})`), + showLength && $L(`SHOWS (${showLength})`), + ].filter(Boolean); + }; + + let buttonTabList = null; + + //탭 + const [tab, setTab] = useState(0); + //드롭다운 + const [dropDownTab, setDropDownTab] = useState(0); + //표시할 아이템 개수 + const [visibleCount, setVisibleCount] = useState(ITEMS_PER_PAGE); + + const [styleChange, setStyleChange] = useState(false); + const filterMethods = []; + const cbChangePageRef = useRef(null); + + // HowAboutThese 모드 상태 관리 + const [howAboutTheseMode, setHowAboutTheseMode] = useState(HOW_ABOUT_THESE_MODES.SMALL); + + // HowAboutThese 모드 전환 핸들러 + const handleShowFullHowAboutThese = useCallback(() => { + setHowAboutTheseMode(HOW_ABOUT_THESE_MODES.FULL); + }, []); + + // HowAboutThese 닫기 핸들러 (Full -> Small) + const handleCloseHowAboutThese = useCallback(() => { + setHowAboutTheseMode(HOW_ABOUT_THESE_MODES.SMALL); + }, []); + + // 쿼리 클릭 핸들러 (Small 버전용) + const handleSmallQueryClick = useCallback( + (query) => { + if (keywordClick) { + keywordClick(query); + } + // 쿼리 클릭 시에는 모드 유지 (small 계속 표시) + }, + [keywordClick] + ); + + // 쿼리 클릭 핸들러 (Full 버전용) + const handleFullQueryClick = useCallback( + (query) => { + if (keywordClick) { + keywordClick(query); + } + setHowAboutTheseMode(HOW_ABOUT_THESE_MODES.SMALL); + }, + [keywordClick] + ); + + if (!buttonTabList) { + buttonTabList = getButtonTabList(); + } + + // 현재 탭의 데이터 가져오기 - ShopperHouse 데이터 우선 + const currentData = tab === 0 ? convertedShopperHouseItems || itemInfo : showInfo; + + // 표시할 데이터 (처음부터 visibleCount 개수만큼) + const displayedData = useMemo(() => { + if (!currentData) return []; + return currentData.slice(0, visibleCount); + }, [currentData, visibleCount]); + + // 더 불러올 데이터가 있는지 확인 + const hasMore = currentData && visibleCount < currentData.length; + + const handleStyle = useCallback(() => { + setStyleChange(true); + }, []); + + const handleStyleOut = useCallback(() => { + setStyleChange(false); + }, []); + + //탭 클릭 + const handleButtonTabClick = useCallback( + ({ index }) => { + if (index === tab) { + return; + } + + setTab(index); + setVisibleCount(ITEMS_PER_PAGE); // 탭 변경시 표시 개수 리셋 + if (cbChangePageRef.current) { + cbChangePageRef.current(0, false, false); + } + }, + [tab] + ); + + //필터선택 + const handleSelectFilter = useCallback( + ({ selected }) => { + if (selected === dropDownTab) { + return; + } + + setDropDownTab(selected); + setVisibleCount(ITEMS_PER_PAGE); // 필터 변경시 표시 개수 리셋 + }, + [dropDownTab] + ); + + const SpottableLi = Spottable('li'); + const SpottableDiv = Spottable('div'); + + // 맨 처음으로 이동 (위 버튼) + const upBtnClick = () => { + if (cbChangePageRef.current) { + cbChangePageRef.current(0, true); + } + }; + + // 10개씩 추가 로드 (아래 버튼) + const downBtnClick = useCallback(() => { + if (hasMore) { + setVisibleCount((prev) => prev + ITEMS_PER_PAGE); + } + }, [hasMore]); + + // ProductCard 컴포넌트 + const renderItem = useCallback( + ({ index, ...rest }) => { + const { bgImgPath, title, partnerLogo, partnerName, keyword } = themeInfo[index]; + return ( + +
+ {title} +
+
+
+ {partnerName} +
+
+ {keyword && ( +
+ {keyword.map((item, keywordIndex) => ( + # {item} + ))} +
+ )} +
{title}
+
+
+
+ ); + }, + [themeInfo] + ); + + // relativeQuerys 가져오기 (ShopperHouse API 응답) + const relativeQuerys = useMemo(() => { + if (shopperHouseInfo?.results?.[0]?.relativeQuerys) { + return shopperHouseInfo.results[0].relativeQuerys; + } + // 기본값 + return ['Puppy food', 'Dog toy', 'Fitness']; + }, [shopperHouseInfo]); + + return ( +
+ {/* HowAboutThese Small 버전 - 기본 인라인 표시 */} + + + {/* HowAboutThese Full 버전 - 오버레이로 표시 */} + {howAboutTheseMode === HOW_ABOUT_THESE_MODES.FULL && ( +
+ +
+ )} + {themeInfo && themeInfo?.length > 0 && ( +
+
+
+
Hot Picks ({themeInfo?.length})
+
+
+ +
+
+ )} +
+
+ + {/* 2025/10/17 김영진 부장과 이야기 하여 일반에서는 아직 필터 검토하지않아 빼달라고 하여 우선 일반검색에서는 미노출 처리 추후 처리 필요함 */} + {tab === 0 && !itemInfo?.length && ( + + {filterMethods} + + )} +
+ {tab === 0 && } + {tab === 1 && } +
+
+ {hasMore && ( + + + + )} + + + +
+
+ ); +}; + +export default SearchResultsNew; diff --git a/com.twin.app.shoptime/src/views/SearchPanel/SearchResults.new.v2.module.less b/com.twin.app.shoptime/src/views/SearchPanel/SearchResults.new.v2.module.less new file mode 100644 index 00000000..0bd3dfaf --- /dev/null +++ b/com.twin.app.shoptime/src/views/SearchPanel/SearchResults.new.v2.module.less @@ -0,0 +1,362 @@ +@import "../../style/CommonStyle.module.less"; +@import "../../style/utils.module.less"; + +.searchBox { + width: 100%; + height: 100%; + position: relative; // ⭐ 추가: absolute positioning을 위한 기준점 + .topBox { + width: 100%; + height: 100%; + padding: 30px 60px; + background: #DDDDDD; + display: inline-flex; + justify-content: flex-start; + align-items: center; + gap: 19px; + + .topBoxTitle { + width: 254px; + height: 48px; + position: relative; + + .titleText { + left: 53px; + top: 10px; + position: absolute; + border-bottom: 1px black solid; + display: inline-flex; + justify-content: center; + align-items: center; + gap: 10px; + + .text { + text-align: center; + justify-content: center; + display: flex; + flex-direction: column; + color: #272727; + font-size: 24px; + font-family: 'Roboto', sans-serif; + font-weight: 700; + word-wrap: break-word; + } + } + } + + .topBoxList { + flex: 1 1 0; + overflow: hidden; + display: flex; + justify-content: flex-start; + align-items: flex-start; + gap: 15px; + + .topBoxListItem { + padding: 20px; + background: white; + border-radius: 100px; + outline: 1px #DADADA solid; + outline-offset: -1px; + display: inline-flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + gap: 10px; + cursor: pointer; + transition: all 0.2s ease; + + .text { + text-align: center; + color: black; + font-size: 24px; + font-family: 'LG Smart UI', sans-serif; + font-weight: 700; + line-height: 24px; + word-wrap: break-word; + } + + &:focus { + outline: 2px solid #00a0e9; + outline-offset: 2px; + } + + &:hover { + background: #f8f8f8; + transform: translateY(-1px); + } + } + } + + .seeMoreButton { + width: 200px; + height: 60px; + background: #808080; + border-radius: 6px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + transition: background-color 0.2s ease; + + &:focus { + outline: 2px solid #00a0e9; + outline-offset: 2px; + } + + &:hover { + background: #666666; + } + + .seeMoreText { + text-align: center; + color: white; + font-size: 24px; + font-family: 'LG Smart UI', sans-serif; + font-weight: 700; + line-height: 24px; + word-wrap: break-word; + } + } + } + .hotpicksSection { + padding-top: 63px; + padding-left: 60px; + width: 1800px; + height: 580px; + .sectionHeader { + width: 1800px; + height: 42px; + justify-content: flex-start; + align-items: center; + display: inline-flex; + + > * { + margin-right: 12px; + + &:last-child { + margin-right: 0; + } + } + + .sectionIndicator { + width: 6px; + height: 36px; + background: #c70850; + } + + .sectionTitle { + text-align: center; + color: black; + font-size: 42px; + font-family: "LG Smart UI"; + font-weight: 700; + line-height: 42px; + word-wrap: break-word; + } + } + // 상품 리스트 스타일 (Hot Picks for You) + .productList { + padding-top: 30px; + .size(@w: 100%, @h: inherit); + > div:nth-child(1) { + .size(@w: 100%, @h: inherit); + } + + .productCard { + width: 546px; + padding: 18px; + background: white; + border-radius: 12px; + border: 5px solid #dadada; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + display: inline-flex; + cursor: pointer; + transition: all 0.2s ease; + + &: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; + } + + .productImageWrapper { + align-self: stretch; + height: 287px; + position: relative; + + .productImage { + width: 510px; + height: 287px; + position: absolute; + left: 0; + top: 0; + object-fit: cover; + border-radius: 8px; + } + } + + .productInfo { + align-self: stretch; + padding-top: 15px; + padding-bottom: 15px; + justify-content: flex-start; + align-items: flex-start; + display: inline-flex; + + > * { + margin-right: 10px; + + &:last-child { + margin-right: 0; + } + } + + .productBrandWrapper { + justify-content: flex-start; + align-items: center; + display: flex; + + > * { + margin-right: 10px; + + &:last-child { + margin-right: 0; + } + } + + .brandLogo { + width: 60px; + height: 60px; + border-radius: 8px; + object-fit: cover; + } + } + + .productDetails { + flex: 1; + flex-direction: column; + justify-content: center; + align-items: flex-start; + display: inline-flex; + + > * { + margin-bottom: 10px; + + &:last-child { + margin-bottom: 0; + } + } + + .brandName { + text-align: center; + color: #808080; + font-size: 18px; + font-family: "LG Smart UI"; + font-weight: 700; + line-height: 18px; + word-wrap: break-word; + } + + .productTitle { + align-self: stretch; + color: black; + font-size: 24px; + font-family: "LG Smart UI"; + font-weight: 400; + line-height: 24px; + word-wrap: break-word; + } + } + } + } + } + } + .itemBox { + margin-top: 60px; + width: 100%; + padding-left: 60px; + overflow: unset; + .title { + padding: 38px 0 33px 0; + } + + .tabContainer { + width: -webkit-fit-content; + height: auto; + position: relative; + + .dropdown { + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); + &.categoryDropdown { + > div { + > div { + border-radius: 6px 6px 0 0 !important; + } + } + } + } + } + } + .buttonContainer { + width: 100%; + display: flex; + justify-content: center; + align-items: center; + padding: 30px 0; + .downBtn { + width: 100px; + height: 100px; + background: #4f172c; + margin-right: 10px; + border-radius: 100%; + display: flex; + justify-content: center; + align-items: center; + border: 4px solid #4f172c; + .btnImg { + width: 42px; + } + &:focus { + border: 4px solid @PRIMARY_COLOR_RED; + } + } + .upBtn { + width: 100px; + height: 100px; + background: #4f172c; + border-radius: 100%; + display: flex; + justify-content: center; + align-items: center; + border: 4px solid #4f172c; + .btnImg { + width: 42px; + } + &:focus { + border: 4px solid @PRIMARY_COLOR_RED; + } + } + } + + // HowAboutThese Full 버전 오버레이 + .howAboutTheseOverlay { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 860px; // HowAboutThese의 높이 + z-index: 100; + pointer-events: auto; // 오버레이 전체를 클릭 가능하게 (배경 클릭 방지) + } +}