[251015] fix: VoiceInputOverlay dim click
🕐 커밋 시간: 2025. 10. 15. 21:00:03 📊 변경 통계: • 총 파일: 6개 • 추가: +2줄 • 삭제: -1781줄 📁 추가된 파일: + com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoiceListening.figma.jsx 📝 수정된 파일: ~ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/VoiceInputOverlay.module.less ~ com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/modes/VoicePromptScreen.module.less 🗑️ 삭제된 파일: - com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new copy.jsx - com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.module copy.less - com.twin.app.shoptime/src/views/SearchPanel/VoiceInputOverlay/OverlayFirst.figma.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new copy.jsx (javascript): ❌ Deleted: _onFocus(), _onBlur() 📄 com.twin.app.shoptime/src/views/SearchPanel/SearchPanel.new.module copy.less (unknown): ❌ Deleted: translateY(), child(), media() Performance: 코드 최적화로 성능 개선 기대
This commit is contained in:
@@ -1,868 +0,0 @@
|
||||
// src/views/SearchPanel/SearchPanel.new.jsx
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
|
||||
import { Job } from '@enact/core/util';
|
||||
import Spotlight from '@enact/spotlight';
|
||||
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 { getMyRecommandedKeyword } from '../../actions/myPageActions';
|
||||
import {
|
||||
popPanel,
|
||||
updatePanel,
|
||||
} from '../../actions/panelActions';
|
||||
import {
|
||||
getSearch,
|
||||
resetSearch,
|
||||
searchMain,
|
||||
} from '../../actions/searchActions';
|
||||
import {
|
||||
showErrorToast,
|
||||
showInfoToast,
|
||||
showSearchErrorToast,
|
||||
showSearchSuccessToast,
|
||||
showSuccessToast,
|
||||
showWarningToast,
|
||||
} from '../../actions/toastActions';
|
||||
import TBody from '../../components/TBody/TBody';
|
||||
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 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 { SpotlightIds } from '../../utils/SpotlightIds';
|
||||
import NoSearchResults from './NoSearchResults/NoSearchResults';
|
||||
import RecommendedKeywords from './RecommendedKeywords/RecommendedKeywords';
|
||||
import css from './SearchPanel.new.module.less';
|
||||
import SearchResultsNew from './SearchResults.new';
|
||||
import VoiceInputOverlay, { VOICE_MODES } from './VoiceInputOverlay/VoiceInputOverlay';
|
||||
|
||||
const ContainerBasic = SpotlightContainerDecorator(
|
||||
{ enterTo: "last-focused" },
|
||||
"div"
|
||||
);
|
||||
|
||||
// 검색 입력 영역 컨테이너
|
||||
const InputContainer = 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 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",
|
||||
};
|
||||
|
||||
export default function SearchPanel({
|
||||
panelInfo,
|
||||
isOnTop,
|
||||
spotlightId,
|
||||
scrollOptions = [],
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const loadingComplete = useSelector((state) => state.common?.loadingComplete);
|
||||
const recommandedKeywords = useSelector(
|
||||
(state) => state.myPage.recommandedKeywordData.data?.keywords
|
||||
);
|
||||
const { searchDatas: searchDatas } = useSelector((state) => state.search);
|
||||
const searchPerformed = useSelector((state) => state.search.searchPerformed);
|
||||
const panels = useSelector((state) => state.panels.panels);
|
||||
|
||||
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 [position, setPosition] = useState(null);
|
||||
const [showVirtualKeyboard, setShowVirtualKeyboard] = useState(false);
|
||||
const [isVoiceOverlayVisible, setIsVoiceOverlayVisible] = useState(false);
|
||||
const [voiceMode, setVoiceMode] = useState(VOICE_MODES.PROMPT);
|
||||
|
||||
//인풋창 포커스 구분을 위함
|
||||
const [inputFocus, setInputFocus] = useState(false);
|
||||
const _onFocus = () => {
|
||||
setInputFocus(true);
|
||||
};
|
||||
const _onBlur = () => {
|
||||
setInputFocus(false);
|
||||
};
|
||||
|
||||
let searchQueryRef = usePrevious(searchQuery);
|
||||
let isOnTopRef = usePrevious(isOnTop);
|
||||
|
||||
const firstButtonSpotlightId = "first-keyword-button";
|
||||
const cbChangePageRef = useRef(null);
|
||||
const [focusedContainerId, setFocusedContainerId] = useState(
|
||||
panelInfo?.focusedContainerId
|
||||
);
|
||||
const focusedContainerIdRef = usePrevious(focusedContainerId);
|
||||
|
||||
// 가짜 데이터 - 실제로는 Redux store나 API에서 가져와야 함
|
||||
const recentSearches = useMemo(
|
||||
() => ["Puppy food", "Dog toy", "Fitness"],
|
||||
[]
|
||||
);
|
||||
|
||||
const recentResultSearches = useMemo(
|
||||
() => [
|
||||
"Puppy food",
|
||||
"Dog toy",
|
||||
"Mather's Day",
|
||||
"Gift",
|
||||
"Easter Day",
|
||||
"Royal Canin puppy food",
|
||||
"Shark",
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const topSearches = useMemo(
|
||||
() => [
|
||||
"Mather's Day",
|
||||
"Gift",
|
||||
"Easter Day",
|
||||
"Royal Canin puppy food",
|
||||
"Fitness",
|
||||
"Parrot",
|
||||
],
|
||||
[]
|
||||
);
|
||||
const popularBrands = useMemo(
|
||||
() => ["Shark", "Ninja", "Skechers", "LocknLock", "8Greens", "LGE"],
|
||||
[]
|
||||
);
|
||||
const hotPicks = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 1,
|
||||
image: hotPicksImage,
|
||||
brandLogo: hotPicksBrandImage,
|
||||
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!",
|
||||
isForYou: false,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
image: hotPicksImage,
|
||||
brandLogo: hotPicksBrandImage,
|
||||
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!",
|
||||
isForYou: true,
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
// Voice overlay suggestions (동적으로 변경 가능)
|
||||
const voiceSuggestions = useMemo(
|
||||
() => [
|
||||
'" Can you recommend a good budget cordless vacuum? "',
|
||||
'" Show me trending skincare. "',
|
||||
'" Find the newest Nike sneakers. "',
|
||||
'" Show me snail cream that helps with sensitive skin. "',
|
||||
'" Recommend a tasty melatonin gummy. "',
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (loadingComplete && !recommandedKeywords) {
|
||||
dispatch(getMyRecommandedKeyword());
|
||||
}
|
||||
}, [loadingComplete]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOnTop) {
|
||||
let menu;
|
||||
if (!searchPerformed) menu = LOG_MENU.SEARCH_SEARCH;
|
||||
else {
|
||||
if (searchQueryRef.current)
|
||||
menu =
|
||||
Object.keys(searchDatas).length > 0
|
||||
? LOG_MENU.SEARCH_RESULT
|
||||
: LOG_MENU.SEARCH_BEST_SELLER;
|
||||
}
|
||||
dispatch(sendLogGNB(menu));
|
||||
}
|
||||
}, [isOnTop, searchDatas, searchPerformed]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!searchQuery) {
|
||||
dispatch(resetSearch());
|
||||
}
|
||||
}, [dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
if (recommandedKeywords) {
|
||||
const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
|
||||
const endIndex = startIndex + ITEMS_PER_PAGE;
|
||||
|
||||
setPaginatedKeywords(recommandedKeywords.slice(startIndex, endIndex));
|
||||
}
|
||||
}, [recommandedKeywords, currentPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (pageChanged && paginatedKeywords.length > 0) {
|
||||
Spotlight.focus(firstButtonSpotlightId);
|
||||
|
||||
setPageChanged(false);
|
||||
}
|
||||
}, [pageChanged, paginatedKeywords]);
|
||||
|
||||
const handleSearchChange = useCallback((e) => {
|
||||
const query = e.value;
|
||||
|
||||
if (query.length <= 255) {
|
||||
setSearchQuery(query);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const result = Object.values(searchDatas).reduce((acc, curr) => {
|
||||
return acc + curr.length;
|
||||
}, 0);
|
||||
|
||||
if (searchQuery) {
|
||||
dispatch(
|
||||
sendLogTotalRecommend({
|
||||
query: searchQuery,
|
||||
searchType: searchPerformed ? "query" : "keyword",
|
||||
result: result,
|
||||
contextName: LOG_CONTEXT_NAME.SEARCH,
|
||||
messageId: LOG_MESSAGE_ID.SEARCH_ITEM,
|
||||
})
|
||||
);
|
||||
|
||||
// 검색 완료 후 결과에 따른 Toast 표시
|
||||
if (searchPerformed && searchQuery.trim()) {
|
||||
if (result > 0) {
|
||||
dispatch(showSearchSuccessToast(searchQuery, result));
|
||||
} else {
|
||||
dispatch(showSearchErrorToast(searchQuery));
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [searchDatas, searchPerformed, searchQuery, dispatch]);
|
||||
|
||||
const handleSearchSubmit = useCallback(
|
||||
(query) => {
|
||||
if (!searchPerformed && !query) return;
|
||||
if (query.trim()) {
|
||||
dispatch(
|
||||
getSearch({
|
||||
service: "com.lgshop.app",
|
||||
query: query,
|
||||
domain: "theme,show,item",
|
||||
})
|
||||
);
|
||||
|
||||
// 검색 시작 알림 (선택사항)
|
||||
dispatch(showSuccessToast(`"${query}" 검색 중...`, { duration: 2000 }));
|
||||
} else {
|
||||
dispatch(resetSearch());
|
||||
}
|
||||
setSearchQuery(query);
|
||||
// 검색 시 가상 키보드 숨김
|
||||
setShowVirtualKeyboard(false);
|
||||
},
|
||||
[dispatch, searchPerformed, searchDatas, searchQuery]
|
||||
);
|
||||
|
||||
const handleNext = useCallback(() => {
|
||||
if (!isOnTopRef.current) {
|
||||
return;
|
||||
}
|
||||
setCurrentPage((prev) => prev + 1);
|
||||
setPageChanged(true);
|
||||
}, [currentPage]);
|
||||
|
||||
const handlePrev = useCallback(() => {
|
||||
if (!isOnTopRef.current) {
|
||||
return;
|
||||
}
|
||||
setCurrentPage((prev) => (prev > 1 ? prev - 1 : prev));
|
||||
setPageChanged(true);
|
||||
}, [currentPage]);
|
||||
|
||||
const hasPrevPage = currentPage > 1;
|
||||
const hasNextPage =
|
||||
currentPage * ITEMS_PER_PAGE < recommandedKeywords?.length;
|
||||
|
||||
useEffect(() => {
|
||||
if (panelInfo && isOnTop) {
|
||||
if (panelInfo.currentSpot && firstSpot) {
|
||||
Spotlight.focus(panel_names.SEARCH_PANEL);
|
||||
}
|
||||
}
|
||||
}, [panelInfo, isOnTop]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
dispatch(
|
||||
updatePanel({
|
||||
name: panel_names.SEARCH_PANEL,
|
||||
panelInfo: {
|
||||
searchVal: searchQueryRef.current,
|
||||
focusedContainerId: focusedContainerIdRef.current,
|
||||
},
|
||||
})
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleKeydown = useCallback(
|
||||
(e) => {
|
||||
if (!isOnTopRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Enter 키 처리
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
if (showVirtualKeyboard) {
|
||||
// 가상 키보드가 열려있으면 검색 실행하고 키보드 닫기
|
||||
handleSearchSubmit(searchQuery);
|
||||
} else {
|
||||
// 가상 키보드가 닫혀있으면 키보드 열기
|
||||
setShowVirtualKeyboard(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 방향키 처리 - Spotlight 네비게이션 허용
|
||||
const arrowKeys = [
|
||||
"ArrowLeft",
|
||||
"ArrowRight",
|
||||
"ArrowUp",
|
||||
"ArrowDown",
|
||||
"Left",
|
||||
"Right",
|
||||
"Up",
|
||||
"Down",
|
||||
];
|
||||
if (arrowKeys.includes(e.key)) {
|
||||
// 입력 필드가 비어있고 왼쪽 화살표인 경우에만 방지
|
||||
if (
|
||||
position === 0 &&
|
||||
(e.key === "Left" || e.key === "ArrowLeft") &&
|
||||
!searchQuery
|
||||
) {
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
// 오른쪽 화살표 키 처리 - 포커스 이동 허용
|
||||
if (e.key === "ArrowRight" || e.key === "Right") {
|
||||
// 커서가 텍스트 끝에 있을 때만 포커스 이동 허용
|
||||
const input = document.querySelector(
|
||||
`[data-spotlight-id="input-field-box"] > input`
|
||||
);
|
||||
if (input && position === input.value.length) {
|
||||
// 커서가 텍스트 끝에 있으면 포커스 이동 허용
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 나머지 방향키는 Spotlight가 처리하도록 허용
|
||||
return;
|
||||
}
|
||||
},
|
||||
[searchQuery, position, handleSearchSubmit, showVirtualKeyboard]
|
||||
);
|
||||
|
||||
const cursorPosition = useCallback(() => {
|
||||
const input = document.querySelector(
|
||||
`[data-spotlight-id="input-field-box"] > input`
|
||||
);
|
||||
if (input) {
|
||||
setPosition(input.selectionStart);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onClickMic = useCallback(() => {
|
||||
if (!isOnTopRef.current) {
|
||||
return;
|
||||
}
|
||||
// 마이크 버튼 클릭 시 voice overlay 토글
|
||||
setIsVoiceOverlayVisible((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const onCancel = useCallback(() => {
|
||||
if (!isOnTopRef.current) {
|
||||
return;
|
||||
}
|
||||
if (searchQuery === null || searchQuery === "") {
|
||||
dispatch(popPanel(panel_names.SEARCH_PANEL));
|
||||
} else {
|
||||
setSearchQuery("");
|
||||
setCurrentPage(1);
|
||||
dispatch(resetSearch());
|
||||
Spotlight.focus(SPOTLIGHT_IDS.SEARCH_INPUT_BOX);
|
||||
}
|
||||
}, [searchQuery, dispatch]);
|
||||
|
||||
const onFocusedContainerId = useCallback(
|
||||
(containerId) => {
|
||||
setFocusedContainerId(containerId);
|
||||
if (!firstSpot) {
|
||||
setTimeout(() => {
|
||||
Spotlight.resume();
|
||||
setFirstSpot(true);
|
||||
if (panelInfo.currentSpot) {
|
||||
if (panels[panels.length - 1]?.name === "searchpanel") {
|
||||
Spotlight.focus(panelInfo.currentSpot);
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
[panelInfo, firstSpot]
|
||||
);
|
||||
|
||||
const panelInfoFall = useMemo(() => {
|
||||
const newPanelInfo = { ...panelInfo };
|
||||
if (firstSpot) {
|
||||
newPanelInfo.currentSpot = null;
|
||||
}
|
||||
return newPanelInfo;
|
||||
}, [panelInfo, firstSpot]);
|
||||
|
||||
// 키워드 클릭 핸들러
|
||||
const handleKeywordClick = useCallback(
|
||||
(keyword) => {
|
||||
setSearchQuery(keyword);
|
||||
handleSearchSubmit(keyword);
|
||||
// 키워드 선택 알림
|
||||
dispatch(
|
||||
showSuccessToast(`"${keyword}" 키워드로 검색합니다.`, {
|
||||
duration: 2000,
|
||||
})
|
||||
);
|
||||
},
|
||||
[handleSearchSubmit, dispatch]
|
||||
);
|
||||
|
||||
// 상품 클릭 핸들러
|
||||
const handleProductClick = useCallback((product) => {
|
||||
// 상품 상세 페이지로 이동하는 로직 구현
|
||||
console.log("Product clicked:", product);
|
||||
}, []);
|
||||
|
||||
// 테스트용 Toast 핸들러들
|
||||
const handleTestToasts = useCallback(() => {
|
||||
// 간단한 Toast 테스트
|
||||
dispatch(showSuccessToast("테스트 메시지입니다", { duration: 3000 }));
|
||||
}, [dispatch]);
|
||||
|
||||
// ProductCard 컴포넌트
|
||||
const renderItem = useCallback(
|
||||
(
|
||||
// {
|
||||
// product,
|
||||
// index,
|
||||
// onClick,
|
||||
// showBrandLogo = true,
|
||||
// showBrandName = true,
|
||||
// showProductTitle = true,
|
||||
// ...rest
|
||||
// }
|
||||
{ index, ...rest }
|
||||
) => {
|
||||
const {
|
||||
showBrandLogo = true,
|
||||
showBrandName = true,
|
||||
showProductTitle = true,
|
||||
image,
|
||||
title,
|
||||
brandLogo,
|
||||
brandName,
|
||||
} = hotPicks[index];
|
||||
return (
|
||||
<SpottableProduct
|
||||
key={`product-${index}`}
|
||||
className={css.productCard}
|
||||
spotlightId={`product-${index}`}
|
||||
{...rest}
|
||||
>
|
||||
<div className={css.productImageWrapper}>
|
||||
<img src={image} alt={title} className={css.productImage} />
|
||||
</div>
|
||||
<div className={css.productInfo}>
|
||||
{showBrandLogo && (
|
||||
<div className={css.productBrandWrapper}>
|
||||
<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>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</SpottableProduct>
|
||||
);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
//test
|
||||
useEffect(() => {
|
||||
console.log("###searchDatas", searchDatas);
|
||||
console.log("###panelInfo", panelInfo);
|
||||
}, [searchDatas, panelInfo]);
|
||||
|
||||
return (
|
||||
<TPanel
|
||||
className={css.container}
|
||||
handleCancel={onCancel}
|
||||
spotlightId={spotlightId}
|
||||
>
|
||||
<TBody
|
||||
className={css.tBody}
|
||||
scrollable={true}
|
||||
spotlightDisabled={!isOnTop}
|
||||
>
|
||||
<ContainerBasic>
|
||||
{isOnTop && (
|
||||
<TVerticalPagenator
|
||||
className={css.tVerticalPagenator}
|
||||
spotlightId={SPOTLIGHT_IDS.SEARCH_VERTICAL_PAGENATOR}
|
||||
defaultContainerId={panelInfo?.focusedContainerId}
|
||||
disabled={!isOnTop}
|
||||
onFocusedContainerId={onFocusedContainerId}
|
||||
cbChangePageRef={cbChangePageRef}
|
||||
topMargin={36}
|
||||
scrollable={true}
|
||||
>
|
||||
{/* 검색 내용있을때 검색 부분 */}
|
||||
{/* 검색 입력 영역 - overlay 열릴 때 숨김 */}
|
||||
{!isVoiceOverlayVisible && (
|
||||
<InputContainer
|
||||
className={classNames(
|
||||
css.inputContainer,
|
||||
inputFocus === true && css.inputFocus,
|
||||
searchDatas &&
|
||||
css.searchValue /* 이건 결과값 있을때만. 조건 추가필요 */
|
||||
)}
|
||||
data-wheel-point={true}
|
||||
spotlightId={SPOTLIGHT_IDS.SEARCH_INPUT_LAYER}
|
||||
>
|
||||
<div className={css.searchInputWrapper}>
|
||||
<TInput
|
||||
className={css.inputBox}
|
||||
kind={KINDS.withIcon}
|
||||
icon={ICONS.search}
|
||||
value={searchQuery}
|
||||
onChange={handleSearchChange}
|
||||
onIconClick={() => {
|
||||
if (showVirtualKeyboard) {
|
||||
handleSearchSubmit(searchQuery);
|
||||
} else {
|
||||
setShowVirtualKeyboard(true);
|
||||
}
|
||||
}}
|
||||
onKeyDown={handleKeydown}
|
||||
onKeyUp={cursorPosition}
|
||||
spotlightId={SPOTLIGHT_IDS.SEARCH_INPUT_BOX}
|
||||
forcedSpotlight="recent-keyword-0"
|
||||
tabIndex={0}
|
||||
spotlightBoxDisabled={true}
|
||||
onFocus={_onFocus}
|
||||
onBlur={_onBlur}
|
||||
/>
|
||||
<SpottableMicButton
|
||||
className={css.microphoneButton}
|
||||
onClick={onClickMic}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
onClickMic();
|
||||
}
|
||||
}}
|
||||
spotlightId={SPOTLIGHT_IDS.MICROPHONE_BUTTON}
|
||||
>
|
||||
<div className={css.microphoneCircle}>
|
||||
<img
|
||||
src={micIcon}
|
||||
alt="Microphone"
|
||||
className={css.microphoneIcon}
|
||||
/>
|
||||
</div>
|
||||
</SpottableMicButton>
|
||||
|
||||
{/* 테스트용 Toast 버튼 (개발용) */}
|
||||
{/* <SpottableMicButton
|
||||
className={css.testToastButton}
|
||||
onClick={handleTestToasts}
|
||||
spotlightId="test-toast-button"
|
||||
>
|
||||
<div className={css.testButtonCircle}>🧪</div>
|
||||
</SpottableMicButton> */}
|
||||
</div>
|
||||
</InputContainer>
|
||||
)}
|
||||
|
||||
{/* 검색내용이 존재하고, 인풋창에 포커스가 가서 노출 시작 */}
|
||||
{inputFocus === true &&
|
||||
(searchDatas?.item?.length > 0 ||
|
||||
searchDatas?.show?.length > 0) && (
|
||||
<>
|
||||
<div className={css.overLay}></div>
|
||||
<div className={css.overLayRecent}>
|
||||
{recentResultSearches.map((keyword, index) => (
|
||||
<SpottableKeyword
|
||||
key={`recentResult-${index}`}
|
||||
className={css.keywordButton}
|
||||
onClick={() => handleKeywordClick(keyword)}
|
||||
spotlightId={`recent-Resultkeyword-${index}`}
|
||||
>
|
||||
{keyword}
|
||||
</SpottableKeyword>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{/* 검색내용이 존재하고, 인풋창에 포커스가 가서 노출 끝! */}
|
||||
|
||||
{/* 결과갑 부분 작업중 시작! */}
|
||||
|
||||
{/* 결과갑 부분 작업중 끝! */}
|
||||
{/* 검색 결과 표시 영역 */}
|
||||
{searchPerformed && searchQuery !== null ? (
|
||||
<SearchResultsNew
|
||||
themeInfo={searchDatas.theme}
|
||||
itemInfo={searchDatas.item}
|
||||
showInfo={searchDatas.show}
|
||||
/>
|
||||
) : (
|
||||
<ContainerBasic
|
||||
className={css.contentContainer}
|
||||
>
|
||||
{/* 노출 조건 변경 필요. 포커스 블러만으로는 안됌.(가상 키보드 노출시가 맞을듯) */}
|
||||
{/* {inputFocus === false ? ( */}
|
||||
{/* {inputFocus === false && ( */}
|
||||
<>
|
||||
{/* 최근 검색어 섹션 */}
|
||||
<SectionContainer
|
||||
className={css.section}
|
||||
data-wheel-point={true}
|
||||
spotlightId={SPOTLIGHT_IDS.RECENT_SEARCHES_SECTION}
|
||||
>
|
||||
<div className={css.sectionHeader}>
|
||||
<div className={css.sectionIndicator}></div>
|
||||
<div className={css.sectionTitle}>
|
||||
Your Recent Searches
|
||||
</div>
|
||||
</div>
|
||||
<div className={css.keywordList}>
|
||||
{recentSearches.map((keyword, index) => (
|
||||
<SpottableKeyword
|
||||
key={`recent-${index}`}
|
||||
className={css.keywordButton}
|
||||
onClick={() => handleKeywordClick(keyword)}
|
||||
spotlightId={`recent-keyword-${index}`}
|
||||
>
|
||||
{keyword}
|
||||
</SpottableKeyword>
|
||||
))}
|
||||
</div>
|
||||
</SectionContainer>
|
||||
|
||||
{/* 인기 검색어 섹션 */}
|
||||
<SectionContainer
|
||||
className={css.section}
|
||||
data-wheel-point={true}
|
||||
spotlightId={SPOTLIGHT_IDS.TOP_SEARCHES_SECTION}
|
||||
>
|
||||
<div className={css.sectionHeader}>
|
||||
<div className={css.sectionIndicator}></div>
|
||||
<div className={css.sectionTitle}>Top Searches</div>
|
||||
</div>
|
||||
<div className={css.keywordList}>
|
||||
{topSearches.map((keyword, index) => (
|
||||
<SpottableKeyword
|
||||
key={`top-${index}`}
|
||||
className={css.keywordButton}
|
||||
onClick={() => handleKeywordClick(keyword)}
|
||||
spotlightId={`top-keyword-${index}`}
|
||||
>
|
||||
{keyword}
|
||||
</SpottableKeyword>
|
||||
))}
|
||||
</div>
|
||||
</SectionContainer>
|
||||
|
||||
{/* 인기 브랜드 섹션 */}
|
||||
<SectionContainer
|
||||
className={css.section}
|
||||
data-wheel-point={true}
|
||||
spotlightId={SPOTLIGHT_IDS.POPULAR_BRANDS_SECTION}
|
||||
>
|
||||
<div className={css.sectionHeader}>
|
||||
<div className={css.sectionIndicator}></div>
|
||||
<div className={css.sectionTitle}>Popular Brands</div>
|
||||
</div>
|
||||
<div className={css.keywordList}>
|
||||
{popularBrands.map((brand, index) => (
|
||||
<SpottableKeyword
|
||||
key={`brand-${index}`}
|
||||
className={css.keywordButton}
|
||||
onClick={() => handleKeywordClick(brand)}
|
||||
spotlightId={`brand-${index}`}
|
||||
>
|
||||
{brand}
|
||||
</SpottableKeyword>
|
||||
))}
|
||||
</div>
|
||||
</SectionContainer>
|
||||
|
||||
{/* Hot Picks for You 섹션 */}
|
||||
<SectionContainer
|
||||
className={css.hotpicksSection}
|
||||
data-wheel-point={true}
|
||||
spotlightId={SPOTLIGHT_IDS.HOT_PICKS_SECTION}
|
||||
>
|
||||
<div className={css.sectionHeader}>
|
||||
<div className={css.sectionIndicator}></div>
|
||||
<div className={css.sectionTitle}>
|
||||
Hot Picks for You
|
||||
</div>
|
||||
</div>
|
||||
<div className={css.productList}>
|
||||
{hotPicks && hotPicks.length > 0 && (
|
||||
<TVirtualGridList
|
||||
dataSize={hotPicks.length}
|
||||
direction="horizontal"
|
||||
renderItem={renderItem}
|
||||
// itemWidth={546}
|
||||
itemWidth={416}
|
||||
itemHeight={436}
|
||||
spacing={20}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</SectionContainer>
|
||||
</>
|
||||
{/* )} */}
|
||||
{/* ) : ( */}
|
||||
{/* <div className={css.inputFocusBox}>
|
||||
<div className={css.keywordList}>
|
||||
{recentSearches.map((keyword, index) => (
|
||||
<SpottableKeyword
|
||||
key={`recent-${index}`}
|
||||
className={css.keywordButton}
|
||||
onClick={() => handleKeywordClick(keyword)}
|
||||
spotlightId={`recent-keyword-${index}`}
|
||||
>
|
||||
{keyword}
|
||||
</SpottableKeyword>
|
||||
))}
|
||||
</div>
|
||||
</div> */}
|
||||
{/* )} */}
|
||||
</ContainerBasic>
|
||||
)}
|
||||
</TVerticalPagenator>
|
||||
)}
|
||||
</ContainerBasic>
|
||||
</TBody>
|
||||
|
||||
{/* Virtual Keyboard */}
|
||||
{/* <VirtualKeyboardContainer
|
||||
isVisible={showVirtualKeyboard}
|
||||
onClose={() => setShowVirtualKeyboard(false)}
|
||||
/> */}
|
||||
|
||||
{/* Voice Input Overlay */}
|
||||
<VoiceInputOverlay
|
||||
isVisible={isVoiceOverlayVisible}
|
||||
onClose={() => setIsVoiceOverlayVisible(false)}
|
||||
mode={voiceMode}
|
||||
suggestions={voiceSuggestions}
|
||||
searchQuery={searchQuery}
|
||||
onSearchChange={handleSearchChange}
|
||||
onSearchSubmit={handleSearchSubmit}
|
||||
/>
|
||||
</TPanel>
|
||||
);
|
||||
}
|
||||
@@ -1,725 +0,0 @@
|
||||
// src/views/SearchPanel/SearchPanel.module.less
|
||||
@import "../../style/CommonStyle.module.less";
|
||||
@import "../../style/utils.module.less";
|
||||
|
||||
.container {
|
||||
background-color: @BG_COLOR_01;
|
||||
|
||||
.tBody {
|
||||
height: 100%;
|
||||
> div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.focusedContainerId {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// 검색 입력 영역 스타일
|
||||
.inputContainer {
|
||||
padding-top: 180px;
|
||||
padding-bottom: 94px;
|
||||
padding-left: 60px;
|
||||
padding-right: 60px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
&.inputFocus {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
&.searchValue {
|
||||
padding-bottom: 55px;
|
||||
padding-top: 55px;
|
||||
}
|
||||
> * {
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.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: 1001;
|
||||
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;
|
||||
}
|
||||
|
||||
// TInput 내부 컨테이너의 포커스 스타일 완전 제거
|
||||
&[data-spotlight-container="true"] {
|
||||
outline: none !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
outline: none !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-within,
|
||||
&:focus {
|
||||
border: 5px solid @PRIMARY_COLOR_RED;
|
||||
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
// TInput 컴포넌트 자체의 내부 포커스 스타일 제거
|
||||
> div {
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
outline: none !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 모든 내부 요소의 포커스 스타일 완전 제거
|
||||
* {
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
outline: none !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// TInput 내부 Container의 포커스 제거
|
||||
> div[data-spotlight-container="true"] {
|
||||
outline: none !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
outline: none !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// InputField의 포커스 제거
|
||||
input {
|
||||
outline: none !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
outline: none !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
// 검색 아이콘 스타일
|
||||
.searchIcon {
|
||||
width: 41px;
|
||||
height: 41px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
width: 36.27px;
|
||||
height: 36.27px;
|
||||
position: absolute;
|
||||
left: 1.95px;
|
||||
top: 1.95px;
|
||||
border: 3.9px solid black;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 마이크 버튼 스타일
|
||||
.microphoneButton {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
z-index: 1001;
|
||||
|
||||
.microphoneCircle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
background: white;
|
||||
overflow: hidden;
|
||||
border-radius: 1000px;
|
||||
border: 5px solid #ccc;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.microphoneIcon {
|
||||
height: 50px;
|
||||
box-sizing: border-box;
|
||||
transition: filter 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.microphoneCircle {
|
||||
border-color: @PRIMARY_COLOR_RED;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
.microphoneCircle {
|
||||
border-color: @PRIMARY_COLOR_RED;
|
||||
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// active 상태 (음성 입력 모드)
|
||||
&.active {
|
||||
.microphoneCircle {
|
||||
background-color: @PRIMARY_COLOR_RED;
|
||||
border-color: @PRIMARY_COLOR_RED;
|
||||
box-shadow: 0 0 22px 0 rgba(229, 9, 20, 0.5);
|
||||
|
||||
.microphoneIcon {
|
||||
filter: brightness(0) invert(1); // 아이콘을 흰색으로 변경
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 테스트용 Toast 버튼 스타일
|
||||
.testToastButton {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0;
|
||||
margin-left: 15px;
|
||||
|
||||
.testButtonCircle {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
background: #ff6b6b;
|
||||
overflow: hidden;
|
||||
border-radius: 1000px;
|
||||
border: 5px solid #ff4757;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 24px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.testButtonCircle {
|
||||
border-color: @PRIMARY_COLOR_RED;
|
||||
background: #ff5252;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
.testButtonCircle {
|
||||
border-color: @PRIMARY_COLOR_RED;
|
||||
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 컨텐츠 컨테이너
|
||||
.contentContainer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
display: inline-flex;
|
||||
.inputFocusBox {
|
||||
width: 935px;
|
||||
height: 355px;
|
||||
margin: 0 auto;
|
||||
.keywordList {
|
||||
align-self: stretch;
|
||||
padding-top: 10px;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
> * {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.keywordButton {
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 100px;
|
||||
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;
|
||||
height: 64px;
|
||||
> * {
|
||||
margin-bottom: 10px;
|
||||
|
||||
&: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);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border: 5px solid @PRIMARY_COLOR_RED;
|
||||
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 섹션 공통 스타일
|
||||
.section {
|
||||
align-self: stretch;
|
||||
padding-top: 63px;
|
||||
padding-left: 60px;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
|
||||
> * {
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
// 키워드 리스트 스타일 (최근 검색어, 인기 검색어, 브랜드)
|
||||
.keywordList {
|
||||
align-self: stretch;
|
||||
padding-top: 30px;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> * {
|
||||
margin-right: 19px;
|
||||
margin-bottom: 19px;
|
||||
}
|
||||
|
||||
.keywordButton {
|
||||
padding: 20px;
|
||||
background: white;
|
||||
border-radius: 100px;
|
||||
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;
|
||||
|
||||
> * {
|
||||
margin-bottom: 10px;
|
||||
|
||||
&: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);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border: 5px solid @PRIMARY_COLOR_RED;
|
||||
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 스크롤 스타일
|
||||
.tVerticalPagenator {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// 스크롤 동작 개선
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
// 반응형 디자인 (필요시)
|
||||
@media (max-width: 1920px) {
|
||||
.section {
|
||||
.sectionHeader {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spotlight 포커스 스타일
|
||||
[data-spotlight-id] {
|
||||
&:focus {
|
||||
// outline: 2px solid @PRIMARY_COLOR_RED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.overLay {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.overLayRecent {
|
||||
position: absolute;
|
||||
left: 403px;
|
||||
top: 172px;
|
||||
width: 995px;
|
||||
height: 488px;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
* {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
<div style={{ width: '642px', height: '437px', position: 'relative', borderRadius: 12 }}>
|
||||
<div
|
||||
style={{
|
||||
width: 642,
|
||||
left: 0,
|
||||
top: 0,
|
||||
position: 'absolute',
|
||||
textAlign: 'center',
|
||||
color: 'white',
|
||||
fontSize: 42,
|
||||
fontFamily: 'LG Smart UI',
|
||||
fontWeight: '700',
|
||||
lineHeight: 42,
|
||||
wordWrap: 'break-word',
|
||||
}}
|
||||
>
|
||||
Try saying
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
left: 0,
|
||||
top: 57,
|
||||
position: 'absolute',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'center',
|
||||
gap: 15,
|
||||
display: 'inline-flex',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
paddingLeft: 30,
|
||||
paddingRight: 30,
|
||||
paddingTop: 20,
|
||||
paddingBottom: 20,
|
||||
background: 'rgba(68, 68, 68, 0.50)',
|
||||
boxShadow: '0px 10px 30px rgba(0, 0, 0, 0.35)',
|
||||
borderRadius: 1000,
|
||||
outline: '2px rgba(251, 251, 251, 0.20) solid',
|
||||
outlineOffset: '-2px',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
color: '#EAEAEA',
|
||||
fontSize: 24,
|
||||
fontFamily: 'LG Smart UI',
|
||||
fontWeight: '700',
|
||||
lineHeight: 24,
|
||||
wordWrap: 'break-word',
|
||||
}}
|
||||
>
|
||||
“ Can you recommend a good budget cordless vacuum? ”
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
paddingLeft: 30,
|
||||
paddingRight: 30,
|
||||
paddingTop: 20,
|
||||
paddingBottom: 20,
|
||||
background: 'rgba(68, 68, 68, 0.50)',
|
||||
boxShadow: '0px 10px 30px rgba(0, 0, 0, 0.35)',
|
||||
borderRadius: 1000,
|
||||
outline: '2px rgba(251, 251, 251, 0.20) solid',
|
||||
outlineOffset: '-2px',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
color: '#EAEAEA',
|
||||
fontSize: 24,
|
||||
fontFamily: 'LG Smart UI',
|
||||
fontWeight: '700',
|
||||
lineHeight: 24,
|
||||
wordWrap: 'break-word',
|
||||
}}
|
||||
>
|
||||
“ Show me trending skincare. ”
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
paddingLeft: 30,
|
||||
paddingRight: 30,
|
||||
paddingTop: 20,
|
||||
paddingBottom: 20,
|
||||
background: 'rgba(68, 68, 68, 0.50)',
|
||||
boxShadow: '0px 10px 30px rgba(0, 0, 0, 0.35)',
|
||||
borderRadius: 1000,
|
||||
outline: '2px rgba(251, 251, 251, 0.20) solid',
|
||||
outlineOffset: '-2px',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
color: '#EAEAEA',
|
||||
fontSize: 24,
|
||||
fontFamily: 'LG Smart UI',
|
||||
fontWeight: '700',
|
||||
lineHeight: 24,
|
||||
wordWrap: 'break-word',
|
||||
}}
|
||||
>
|
||||
“ Find the newest Nike sneakers. ”
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
paddingLeft: 30,
|
||||
paddingRight: 30,
|
||||
paddingTop: 20,
|
||||
paddingBottom: 20,
|
||||
background: 'rgba(68, 68, 68, 0.50)',
|
||||
boxShadow: '0px 10px 30px rgba(0, 0, 0, 0.35)',
|
||||
borderRadius: 1000,
|
||||
outline: '2px rgba(251, 251, 251, 0.20) solid',
|
||||
outlineOffset: '-2px',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
color: '#EAEAEA',
|
||||
fontSize: 24,
|
||||
fontFamily: 'LG Smart UI',
|
||||
fontWeight: '700',
|
||||
lineHeight: 24,
|
||||
wordWrap: 'break-word',
|
||||
}}
|
||||
>
|
||||
“ Show me snail cream that helps with sensitive skin. ”
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
paddingLeft: 30,
|
||||
paddingRight: 30,
|
||||
paddingTop: 20,
|
||||
paddingBottom: 20,
|
||||
background: 'rgba(68, 68, 68, 0.50)',
|
||||
boxShadow: '0px 10px 30px rgba(0, 0, 0, 0.35)',
|
||||
borderRadius: 1000,
|
||||
outline: '2px rgba(251, 251, 251, 0.20) solid',
|
||||
outlineOffset: '-2px',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'flex-start',
|
||||
alignItems: 'flex-start',
|
||||
display: 'flex',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
color: '#EAEAEA',
|
||||
fontSize: 24,
|
||||
fontFamily: 'LG Smart UI',
|
||||
fontWeight: '700',
|
||||
lineHeight: 24,
|
||||
wordWrap: 'break-word',
|
||||
}}
|
||||
>
|
||||
“ Recommend a tasty melatonin gummy. ”
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
@@ -185,5 +185,5 @@
|
||||
margin-top: 100px;
|
||||
padding-right: 120px;
|
||||
z-index: 1002;
|
||||
pointer-events: all; // 컨텐츠 영역은 클릭 가능
|
||||
pointer-events: none; // 빈 공간 클릭 시 dimBackground로 이벤트 전달
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
height: 437px;
|
||||
position: relative;
|
||||
border-radius: 12px;
|
||||
pointer-events: all; // 실제 컨텐츠는 클릭 가능
|
||||
}
|
||||
|
||||
.title {
|
||||
|
||||
Reference in New Issue
Block a user