[251122] fix: DetailPaneel->ProductAllSection Focus\
🕐 커밋 시간: 2025. 11. 22. 06:07:46 📊 변경 통계: • 총 파일: 2개 • 추가: +40줄 • 삭제: -2줄 📝 수정된 파일: ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.jsx 🔧 함수 변경 내용: 📄 com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx (javascript): 🔄 Modified: SpotlightContainerDecorator(), extractProductMeta() 📄 com.twin.app.shoptime/src/views/DetailPanel/components/FavoriteBtn.jsx (javascript): 🔄 Modified: Spottable() 🔧 주요 변경 내용: • UI 컴포넌트 아키텍처 개선
This commit is contained in:
@@ -1,60 +1,37 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
// import { throttle } from 'lodash';
|
||||
import { PropTypes } from 'prop-types';
|
||||
import {
|
||||
useDispatch,
|
||||
useSelector,
|
||||
} from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import Spotlight from '@enact/spotlight';
|
||||
/* eslint-disable react/jsx-no-bind */
|
||||
// src/views/DetailPanel/ProductAllSection/ProductAllSection.jsx
|
||||
import SpotlightContainerDecorator
|
||||
from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
|
||||
import Spottable from '@enact/spotlight/Spottable';
|
||||
|
||||
import couponImg from '../../../../assets/images/icons/coupon.png';
|
||||
// import Spottable from '@enact/spotlight/Spottable';
|
||||
//image
|
||||
import arrowDown
|
||||
from '../../../../assets/images/icons/ic_arrow_down_3x_new.png';
|
||||
import indicatorDefaultImage
|
||||
from '../../../../assets/images/img-thumb-empty-144@3x.png';
|
||||
import {
|
||||
setHidePopup,
|
||||
setShowPopup,
|
||||
} from '../../../actions/commonActions.js';
|
||||
import arrowDown from '../../../../assets/images/icons/ic_arrow_down_3x_new.png';
|
||||
import indicatorDefaultImage from '../../../../assets/images/img-thumb-empty-144@3x.png';
|
||||
import { setHidePopup, setShowPopup } from '../../../actions/commonActions.js';
|
||||
import {
|
||||
getProductCouponDownload,
|
||||
getProductCouponSearch,
|
||||
getProductCouponTotDownload,
|
||||
} from '../../../actions/couponActions.js';
|
||||
// import { pushPanel } from '../../../actions/panelActions';
|
||||
import {
|
||||
minimizeModalMedia,
|
||||
restoreModalMedia,
|
||||
} from '../../../actions/mediaActions';
|
||||
import { minimizeModalMedia, restoreModalMedia } from '../../../actions/mediaActions';
|
||||
import { pauseFullscreenVideo } from '../../../actions/playActions';
|
||||
import { resetShowAllReviews } from '../../../actions/productActions';
|
||||
import {
|
||||
clearAllToasts,
|
||||
removeToast,
|
||||
showToast,
|
||||
} from '../../../actions/toastActions';
|
||||
import { clearAllToasts, removeToast, showToast } from '../../../actions/toastActions';
|
||||
import CustomImage from '../../../components/CustomImage/CustomImage.jsx';
|
||||
// ProductInfoSection imports
|
||||
import TButton from '../../../components/TButton/TButton';
|
||||
import TPopUp from '../../../components/TPopUp/TPopUp.jsx';
|
||||
import TVirtualGridList
|
||||
from '../../../components/TVirtualGridList/TVirtualGridList.jsx';
|
||||
import TVirtualGridList from '../../../components/TVirtualGridList/TVirtualGridList.jsx';
|
||||
import useReviews from '../../../hooks/useReviews/useReviews';
|
||||
import useScrollTo from '../../../hooks/useScrollTo';
|
||||
import { BUYNOW_CONFIG } from '../../../utils/BuyNowConfig';
|
||||
@@ -86,19 +63,13 @@ import StarRating from '../components/StarRating';
|
||||
// ProductContentSection imports
|
||||
import TScrollerDetail from '../components/TScroller/TScrollerDetail';
|
||||
import DetailPanelSkeleton from '../DetailPanelSkeleton/DetailPanelSkeleton';
|
||||
import ProductDescription
|
||||
from '../ProductContentSection/ProductDescription/ProductDescription';
|
||||
import ProductDetail
|
||||
from '../ProductContentSection/ProductDetail/ProductDetail.new';
|
||||
import {
|
||||
ProductVideoV2,
|
||||
} from '../ProductContentSection/ProductVideo/ProductVideo.v2.jsx';
|
||||
import ProductVideo
|
||||
from '../ProductContentSection/ProductVideo/ProductVideo.v3';
|
||||
import ProductDescription from '../ProductContentSection/ProductDescription/ProductDescription';
|
||||
import ProductDetail from '../ProductContentSection/ProductDetail/ProductDetail.new';
|
||||
import { ProductVideoV2 } from '../ProductContentSection/ProductVideo/ProductVideo.v2.jsx';
|
||||
import ProductVideo from '../ProductContentSection/ProductVideo/ProductVideo.v3';
|
||||
import UserReviews from '../ProductContentSection/UserReviews/UserReviews';
|
||||
// import ViewAllReviewsButton from '../ProductContentSection/UserReviews/ViewAllReviewsButton';
|
||||
import YouMayAlsoLike
|
||||
from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike';
|
||||
import YouMayAlsoLike from '../ProductContentSection/YouMayAlsoLike/YouMayAlsoLike';
|
||||
import QRCode from '../ProductInfoSection/QRCode/QRCode';
|
||||
import ProductOverview from '../ProductOverview/ProductOverview';
|
||||
// CSS imports
|
||||
@@ -139,7 +110,7 @@ const HorizontalContainer = SpotlightContainerDecorator(
|
||||
'div'
|
||||
);
|
||||
|
||||
const SpottableComponent = Spottable("div");
|
||||
const SpottableComponent = Spottable('div');
|
||||
|
||||
const getProductData = curry((productType, themeProductInfo, productInfo) =>
|
||||
pipe(
|
||||
@@ -192,15 +163,9 @@ export default function ProductAllSection({
|
||||
// YouMayLike 데이터는 API 응답 시간이 걸리므로 직접 구독
|
||||
const youmaylikeData = useSelector((state) => state.main.youmaylikeData);
|
||||
//coupon
|
||||
const { partnerCoupon } = useSelector(
|
||||
(state) => state.coupon.productCouponSearchData
|
||||
);
|
||||
const { userNumber } = useSelector(
|
||||
(state) => state.common.appStatus.loginUserData
|
||||
);
|
||||
const { popupVisible, activePopup } = useSelector(
|
||||
(state) => state.common.popup
|
||||
);
|
||||
const { partnerCoupon } = useSelector((state) => state.coupon.productCouponSearchData);
|
||||
const { userNumber } = useSelector((state) => state.common.appStatus.loginUserData);
|
||||
const { popupVisible, activePopup } = useSelector((state) => state.common.popup);
|
||||
// ProductVideo 버전 관리 (1: 기존 modal 방식, 2: 내장 방식 , 3: 비디오 생략)
|
||||
const [productVideoVersion, setProductVideoVersion] = useState(1);
|
||||
// 비디오 재생 여부 flag (재생 전에는 minimize/restore 로직 비활성화)
|
||||
@@ -235,10 +200,10 @@ export default function ProductAllSection({
|
||||
const [selectedCoupon, setSelectedCoupon] = useState();
|
||||
const [downloadCouponArr, setDownloadCouponArr] = useState([]);
|
||||
const [couponTypes, setCouponTypes] = useState(null);
|
||||
const [couponCodes, setCouponCodes] = useState("");
|
||||
const [couponCodes, setCouponCodes] = useState('');
|
||||
const [focused, setFocused] = useState(false);
|
||||
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
dispatch(
|
||||
getProductCouponSearch({
|
||||
patnrId: selectedPatnrId,
|
||||
@@ -246,18 +211,18 @@ export default function ProductAllSection({
|
||||
mbrNo: userNumber,
|
||||
})
|
||||
);
|
||||
},[dispatch])
|
||||
}, [dispatch]);
|
||||
|
||||
const getCouponCode = () => {
|
||||
const snoArray = [];
|
||||
|
||||
for (let i = 0; i < selectedCoupon.length; i++) {
|
||||
if (selectedCoupon[i].downloadYn === "Y") {
|
||||
if (selectedCoupon[i].downloadYn === 'Y') {
|
||||
snoArray.push(selectedCoupon[i].cpnSno);
|
||||
}
|
||||
}
|
||||
|
||||
setCouponCodes(snoArray.join(", "));
|
||||
setCouponCodes(snoArray.join(', '));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -269,7 +234,7 @@ export default function ProductAllSection({
|
||||
useEffect(() => {
|
||||
const newPromotions = [];
|
||||
if (partnerCoupon && partnerCoupon.length > 0) {
|
||||
newPromotions.push($L("SPECIAL PROMOTION"));
|
||||
newPromotions.push($L('SPECIAL PROMOTION'));
|
||||
}
|
||||
setPromotions(newPromotions);
|
||||
}, [partnerCoupon]);
|
||||
@@ -280,7 +245,7 @@ export default function ProductAllSection({
|
||||
dispatch(setShowPopup(Config.ACTIVE_POPUP.loginPopup));
|
||||
return;
|
||||
}
|
||||
if (promotion === "SPECIAL PROMOTION") {
|
||||
if (promotion === 'SPECIAL PROMOTION') {
|
||||
setSelectedCoupon(partnerCoupon);
|
||||
}
|
||||
setCouponTypes(idx);
|
||||
@@ -291,9 +256,7 @@ export default function ProductAllSection({
|
||||
|
||||
const handleCouponTotDownload = useCallback(() => {
|
||||
if (selectedCoupon && userNumber) {
|
||||
const couponCodesArray = couponCodes
|
||||
.split(",")
|
||||
.map((code) => parseInt(code.trim()));
|
||||
const couponCodesArray = couponCodes.split(',').map((code) => parseInt(code.trim()));
|
||||
setDownloadCouponArr((prevArr) => [...prevArr, ...couponCodesArray]);
|
||||
|
||||
dispatch(
|
||||
@@ -302,7 +265,7 @@ export default function ProductAllSection({
|
||||
cpnSnoAll: couponCodes,
|
||||
})
|
||||
);
|
||||
dispatch(showToast({message : "Your coupon download is complete."}));
|
||||
dispatch(showToast({ message: 'Your coupon download is complete.' }));
|
||||
}
|
||||
}, [dispatch, selectedCoupon, userNumber, couponCodes]);
|
||||
|
||||
@@ -311,7 +274,7 @@ export default function ProductAllSection({
|
||||
dispatch(setHidePopup());
|
||||
|
||||
let currentSpot;
|
||||
if (typeof spotlightId === "string") {
|
||||
if (typeof spotlightId === 'string') {
|
||||
currentSpot = spotlightId;
|
||||
} else {
|
||||
currentSpot = SpotlightIds.DETAIL_BUYNOW;
|
||||
@@ -324,7 +287,7 @@ export default function ProductAllSection({
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
)
|
||||
);
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ index, ...rest }) => {
|
||||
@@ -343,8 +306,8 @@ export default function ProductAllSection({
|
||||
duplDwldYn,
|
||||
} = selectedCoupon[index];
|
||||
|
||||
const couponAplyStartDate = cpnAplyStrtDtt.split(" ")[0];
|
||||
const couponAplyEndDate = cpnAplyEndDtt.split(" ")[0];
|
||||
const couponAplyStartDate = cpnAplyStrtDtt.split(' ')[0];
|
||||
const couponAplyEndDate = cpnAplyEndDtt.split(' ')[0];
|
||||
|
||||
const onFocus = (index) => {
|
||||
setSelectedCouponIndex(index);
|
||||
@@ -359,15 +322,13 @@ export default function ProductAllSection({
|
||||
if (
|
||||
downloadCouponArr.length > 0 &&
|
||||
downloadCouponArr.includes(cpnSno) &&
|
||||
(downloadYn === "N" || (duplDwldYn === "Y" && downloadYn === "Y"))
|
||||
(downloadYn === 'N' || (duplDwldYn === 'Y' && downloadYn === 'Y'))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setDownloadCouponArr((prevArr) => [...prevArr, cpnSno]);
|
||||
dispatch(
|
||||
getProductCouponDownload({ mbrNo: userNumber, cpnSno: cpnSno })
|
||||
);
|
||||
dispatch(showToast({message : "Your coupon download is complete."}));
|
||||
dispatch(getProductCouponDownload({ mbrNo: userNumber, cpnSno: cpnSno }));
|
||||
dispatch(showToast({ message: 'Your coupon download is complete.' }));
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -377,23 +338,17 @@ export default function ProductAllSection({
|
||||
{...rest}
|
||||
onFocus={() => onFocus(index)}
|
||||
onBlur={onBlur}
|
||||
onClick={downloadYn === "N" ? null : handleDownloadClick}
|
||||
onClick={downloadYn === 'N' ? null : handleDownloadClick}
|
||||
>
|
||||
<div
|
||||
className={css.couponItem}
|
||||
aria-label={
|
||||
"Purchase over " +
|
||||
cpnAplyMinPurcAmt +
|
||||
"up to " +
|
||||
cpnAplyMaxDcAmt +
|
||||
" off"
|
||||
}
|
||||
aria-label={'Purchase over ' + cpnAplyMinPurcAmt + 'up to ' + cpnAplyMaxDcAmt + ' off'}
|
||||
>
|
||||
<div className={css.couponTopContents}>
|
||||
{shptmDcTpCd === "CPN00401" && (
|
||||
{shptmDcTpCd === 'CPN00401' && (
|
||||
<span className={css.couponLate}>{`${currSign}${dcAmt}`}</span>
|
||||
)}
|
||||
{shptmDcTpCd === "CPN00402" && (
|
||||
{shptmDcTpCd === 'CPN00402' && (
|
||||
<span className={css.couponLate}>{`${cpnDctrt}%`}</span>
|
||||
)}
|
||||
<span className={css.title} aria-label={cpnTtl}>
|
||||
@@ -403,25 +358,21 @@ export default function ProductAllSection({
|
||||
|
||||
<div className={css.couponMiddleContents}>
|
||||
<span>
|
||||
{$L(
|
||||
"Purchase over ${cpnAplyMinPurcAmt} (up to ${cpnAplyMaxDcAmt} off)"
|
||||
)
|
||||
.replace("{cpnAplyMinPurcAmt}", cpnAplyMinPurcAmt)
|
||||
.replace("{cpnAplyMaxDcAmt}", cpnAplyMaxDcAmt)}
|
||||
{$L('Purchase over ${cpnAplyMinPurcAmt} (up to ${cpnAplyMaxDcAmt} off)')
|
||||
.replace('{cpnAplyMinPurcAmt}', cpnAplyMinPurcAmt)
|
||||
.replace('{cpnAplyMaxDcAmt}', cpnAplyMaxDcAmt)}
|
||||
</span>
|
||||
<span className={classNames(css.content, css.date)}>
|
||||
{couponAplyStartDate}~{couponAplyEndDate}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{downloadYn !== "N" && (
|
||||
{downloadYn !== 'N' && (
|
||||
<div
|
||||
className={classNames(
|
||||
css.couponBottomButton,
|
||||
downloadCouponArr.length > 0 &&
|
||||
downloadCouponArr.includes(cpnSno) &&
|
||||
css.disable,
|
||||
duplDwldYn === "N" && downloadYn === "Y" && css.disable,
|
||||
downloadCouponArr.length > 0 && downloadCouponArr.includes(cpnSno) && css.disable,
|
||||
duplDwldYn === 'N' && downloadYn === 'Y' && css.disable,
|
||||
!downloadCouponArr.includes(cpnSno) &&
|
||||
index === selectedCouponIndex &&
|
||||
focused &&
|
||||
@@ -429,13 +380,9 @@ export default function ProductAllSection({
|
||||
)}
|
||||
aria-label="Download Button"
|
||||
>
|
||||
{downloadCouponArr.length > 0 &&
|
||||
downloadCouponArr.includes(cpnSno)
|
||||
?
|
||||
$L("DOWNLOAD COMPLETED")
|
||||
:
|
||||
$L("DOWNLOAD")
|
||||
}
|
||||
{downloadCouponArr.length > 0 && downloadCouponArr.includes(cpnSno)
|
||||
? $L('DOWNLOAD COMPLETED')
|
||||
: $L('DOWNLOAD')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -666,13 +613,13 @@ export default function ProductAllSection({
|
||||
|
||||
//닫히도록
|
||||
const handleCloseToast = useCallback(() => {
|
||||
// 팝업이 열려있으면 닫지 않음
|
||||
if (popupVisible) {
|
||||
return; // 팝업이 활성이면 무시
|
||||
}
|
||||
dispatch(clearAllToasts());
|
||||
setOpenToast(false);
|
||||
}, [dispatch, popupVisible]);
|
||||
// 팝업이 열려있으면 닫지 않음
|
||||
if (popupVisible) {
|
||||
return; // 팝업이 활성이면 무시
|
||||
}
|
||||
dispatch(clearAllToasts());
|
||||
setOpenToast(false);
|
||||
}, [dispatch, popupVisible]);
|
||||
|
||||
// 스크롤 컨테이너의 클릭 이벤트 추적용 로깅
|
||||
const handleScrollContainerClick = useCallback((e) => {
|
||||
@@ -901,18 +848,20 @@ export default function ProductAllSection({
|
||||
);
|
||||
|
||||
// BUY NOW, ADD TO CART 버튼에서 arrow up 시: 항상 헤더 뒤로가기 버튼으로
|
||||
const handleSpotlightUpFromBuyButtons = useCallback((e) => {
|
||||
e.stopPropagation();
|
||||
if(promotions && promotions.length > 0){
|
||||
Spotlight.focus('detail-coupon-button');
|
||||
} else {
|
||||
// 쿠폰 버튼이 없을 때 뒤로가기 버튼으로 포커스 이동 (특정 Chrome 버전에서의 포커스 문제 해결)
|
||||
setTimeout(() => {
|
||||
Spotlight.focus('spotlightId_backBtn');
|
||||
}, 0);
|
||||
}
|
||||
}, [promotions]);
|
||||
|
||||
const handleSpotlightUpFromBuyButtons = useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
if (promotions && promotions.length > 0) {
|
||||
Spotlight.focus('detail-coupon-button');
|
||||
} else {
|
||||
// 쿠폰 버튼이 없을 때 뒤로가기 버튼으로 포커스 이동 (특정 Chrome 버전에서의 포커스 문제 해결)
|
||||
setTimeout(() => {
|
||||
Spotlight.focus('spotlightId_backBtn');
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
[promotions]
|
||||
);
|
||||
|
||||
const handleSpotlightUpFromCouponButtons = useCallback((e) => {
|
||||
e.stopPropagation();
|
||||
@@ -923,6 +872,28 @@ export default function ProductAllSection({
|
||||
e.stopPropagation();
|
||||
}, []);
|
||||
|
||||
// SHOP BY MOBILE에서 아래로 이동 시 제품 정보 영역으로 포커스 이동
|
||||
const handleSpotlightDownFromShopByMobile = useCallback((e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
if (!Spotlight.focus('product-details-button')) {
|
||||
Spotlight.focus('user-reviews-button');
|
||||
}
|
||||
|
||||
// 포커스가 비거나 다른 곳으로 튀는 경우를 방어
|
||||
setTimeout(() => {
|
||||
const current = Spotlight.getCurrent();
|
||||
const currentId = current?.dataset?.spotlightId;
|
||||
if (
|
||||
!current ||
|
||||
(currentId !== 'product-details-button' && currentId !== 'user-reviews-button')
|
||||
) {
|
||||
Spotlight.focus('product-details-button') || Spotlight.focus('user-reviews-button');
|
||||
}
|
||||
}, 0);
|
||||
}, []);
|
||||
|
||||
const onFavoriteFlagChanged = useCallback(
|
||||
(newFavoriteFlag) => setFavoriteOverride(newFavoriteFlag),
|
||||
[]
|
||||
@@ -1216,8 +1187,6 @@ export default function ProductAllSection({
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
// 초기 로딩 중에는 Skeleton 표시
|
||||
if (isInitialLoading) {
|
||||
return (
|
||||
@@ -1227,9 +1196,6 @@ export default function ProductAllSection({
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<HorizontalContainer className={css.detailArea} onClick={handleCloseToast}>
|
||||
{/* Left Margin Section - 60px */}
|
||||
@@ -1276,31 +1242,32 @@ export default function ProductAllSection({
|
||||
</div>
|
||||
</ProductOverview>
|
||||
|
||||
{userNumber && promotions.map((promotion, idx) => {
|
||||
return(
|
||||
<div className={css.couponContainer} key={idx}>
|
||||
<div className={css.couponTitleText}>
|
||||
<div className={css.firstTitle}>SPECIAL PROMOTION</div>
|
||||
<div className={css.secondTitle}>Coupon only applicable to this product!</div>
|
||||
</div>
|
||||
<TButton
|
||||
spotlightId="detail-coupon-button"
|
||||
className={css.couponButton}
|
||||
onClick={() => {
|
||||
handleCouponClick(idx, promotion);
|
||||
}}
|
||||
onSpotlightUp={handleSpotlightUpFromCouponButtons}
|
||||
onSpotlightDown={handleSpotlightDown}
|
||||
size="detail_very_small"
|
||||
>
|
||||
<div className={css.couponText}>
|
||||
COUPON
|
||||
{userNumber &&
|
||||
promotions.map((promotion, idx) => {
|
||||
return (
|
||||
<div className={css.couponContainer} key={idx}>
|
||||
<div className={css.couponTitleText}>
|
||||
<div className={css.firstTitle}>SPECIAL PROMOTION</div>
|
||||
<div className={css.secondTitle}>
|
||||
Coupon only applicable to this product!
|
||||
</div>
|
||||
</div>
|
||||
<img className={css.buttonImg} src={couponImg} />
|
||||
</TButton>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<TButton
|
||||
spotlightId="detail-coupon-button"
|
||||
className={css.couponButton}
|
||||
onClick={() => {
|
||||
handleCouponClick(idx, promotion);
|
||||
}}
|
||||
onSpotlightUp={handleSpotlightUpFromCouponButtons}
|
||||
onSpotlightDown={handleSpotlightDown}
|
||||
size="detail_very_small"
|
||||
>
|
||||
<div className={css.couponText}>COUPON</div>
|
||||
<img className={css.buttonImg} src={couponImg} />
|
||||
</TButton>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{isBillingProductVisible && (
|
||||
<div className={css.buyNowCartContainer}>
|
||||
<TButton
|
||||
@@ -1339,7 +1306,8 @@ export default function ProductAllSection({
|
||||
className={css.shopByMobileButton}
|
||||
onClick={handleShopByMobileOpen}
|
||||
onSpotlightUp={handleSpotlightUpToBackButton}
|
||||
onSpotlightDown={handleSpotlightDown}
|
||||
onSpotlightDown={handleSpotlightDownFromShopByMobile}
|
||||
data-spotlight-next-down="product-details-button"
|
||||
>
|
||||
<div className={css.shopByMobileText}>{$L('SHOP BY MOBILE')}</div>
|
||||
</TButton>
|
||||
@@ -1608,8 +1576,8 @@ export default function ProductAllSection({
|
||||
onClick={handleCouponTotDownload}
|
||||
onClose={onClose}
|
||||
hasButton
|
||||
button1Text={$L("DOWNLOAD ALL")}
|
||||
button2Text={$L("CLOSE")}
|
||||
button1Text={$L('DOWNLOAD ALL')}
|
||||
button2Text={$L('CLOSE')}
|
||||
>
|
||||
<Container className={css.itemWrap}>
|
||||
{selectedCoupon && selectedCoupon.length > 0 && (
|
||||
@@ -1625,9 +1593,7 @@ export default function ProductAllSection({
|
||||
/>
|
||||
)}
|
||||
</Container>
|
||||
<div
|
||||
className={css.couponRemain}
|
||||
>{`1/${selectedCoupon?.length}`}</div>
|
||||
<div className={css.couponRemain}>{`1/${selectedCoupon?.length}`}</div>
|
||||
</TPopUp>
|
||||
)}
|
||||
</HorizontalContainer>
|
||||
|
||||
@@ -66,16 +66,39 @@ export default function FavoriteBtn({
|
||||
onFavoriteFlagChanged(favoriteFlag === 'Y' ? 'N' : 'Y');
|
||||
}, [dispatch, favoriteFlag, onFavoriteFlagChanged]);
|
||||
|
||||
const handleFavoriteKeyDown = useCallback((e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleFavoriteClick();
|
||||
}
|
||||
}, [handleFavoriteClick]);
|
||||
const handleFavoriteKeyDown = useCallback(
|
||||
(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleFavoriteClick();
|
||||
}
|
||||
},
|
||||
[handleFavoriteClick]
|
||||
);
|
||||
|
||||
const handleSpotlightDown = useCallback((e) => {
|
||||
e.stopPropagation();
|
||||
}, []);
|
||||
const handleSpotlightDown = useCallback(
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
if (kind === 'item_detail') {
|
||||
e.preventDefault();
|
||||
if (!Spotlight.focus('product-details-button')) {
|
||||
Spotlight.focus('user-reviews-button');
|
||||
}
|
||||
// 포커스가 비거나 다른 곳으로 튀는 경우를 방어
|
||||
setTimeout(() => {
|
||||
const current = Spotlight.getCurrent();
|
||||
const currentId = current?.dataset?.spotlightId;
|
||||
if (
|
||||
!current ||
|
||||
(currentId !== 'product-details-button' && currentId !== 'user-reviews-button')
|
||||
) {
|
||||
Spotlight.focus('product-details-button') || Spotlight.focus('user-reviews-button');
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
},
|
||||
[kind]
|
||||
);
|
||||
|
||||
return (
|
||||
<SpottableDiv
|
||||
@@ -87,6 +110,7 @@ export default function FavoriteBtn({
|
||||
onClick={handleFavoriteClick}
|
||||
onKeyDown={handleFavoriteKeyDown}
|
||||
onSpotlightDown={handleSpotlightDown}
|
||||
data-spotlight-next-down={kind === 'item_detail' ? 'product-details-button' : undefined}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
|
||||
Reference in New Issue
Block a user