Files
shoptime/com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx
optrader 4dfa15b4c0 [251217] fix: TabContainer.v2.jsx ShopNowButton 포커스 10ms
🕐 커밋 시간: 2025. 12. 17. 09:13:23

📊 변경 통계:
  • 총 파일: 2개
  • 추가: +5줄
  • 삭제: -1줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductDescription/ProductDescription.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx
2025-12-17 09:13:23 +09:00

378 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useCallback, useEffect, useMemo } from 'react';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
import Spotlight from '@enact/spotlight';
import SpotlightContainerDecorator from '@enact/spotlight/SpotlightContainerDecorator';
import Spottable from '@enact/spotlight/Spottable';
// import icon_arrow_right from '../../../../../assets/images/icons';
import icon_arrow_dwon from '../../../../../assets/images/player/icon_tabcontainer_arrow_down.png';
import usePrevious from '../../../../hooks/usePrevious';
import { LOG_MENU } from '../../../../utils/Config';
import { createDebugHelpers } from '../../../../utils/debug';
import { $L } from '../../../../utils/helperMethods';
import { SpotlightIds } from '../../../../utils/SpotlightIds';
import FeaturedShowContents from '../TabContents/FeaturedShowContents';
import LiveChannelContents from '../TabContents/LiveChannelContents';
import ShopNowContents from '../TabContents/ShopNowContents';
import LiveChannelNext from './LiveChannelNext';
import ShopNowButton from './ShopNowButton';
import css from './TabContainer.v2.module.less';
// 디버그 헬퍼 설정
const DEBUG_MODE = false;
const { dlog, dwarn, derror } = createDebugHelpers(DEBUG_MODE);
const Container = SpotlightContainerDecorator(
{ enterTo: 'last-focused' },
'div'
);
const SpottableDiv = Spottable('div');
export default function TabContainerV2({
panelInfo,
playListInfo,
shopNowInfo,
selectedIndex,
setSelectedIndex,
liveChannelInfos,
videoVerticalVisible,
handleItemFocus,
prevChannelIndex,
currentTime,
spotlightId,
tabIndex = 1, // tabIndex prop으로 제어 (0: ShopNow, 1: LiveChannel, 2: ShopNowButton)
onShopNowButtonClick,
onLiveChannelButtonClick,
onLiveNext,
onTabClose, // 탭 닫기 콜백 함수
tabVisible,
}) {
const youmaylikeInfos = useSelector((state) => state.main.youmaylikeInfos);
// 다음 재생 가능한 쇼 찾기
const findNextPlayableShow = useCallback((currentPlayList, currentIndex) => {
if (!currentPlayList || currentPlayList.length === 0) return null;
let nextIndex =
currentIndex === currentPlayList.length - 1 ? 0 : currentIndex + 1;
let initialIndex = nextIndex;
let attempts = 0;
// 유효한 showId를 가진 다음 쇼 찾기
while (
!currentPlayList[nextIndex]?.showId &&
attempts < currentPlayList.length
) {
nextIndex = nextIndex === currentPlayList.length - 1 ? 0 : nextIndex + 1;
attempts++;
if (nextIndex === initialIndex) break;
}
if (currentPlayList[nextIndex]?.showId) {
return currentPlayList[nextIndex];
}
return null;
}, []);
// 다음 쇼 정보 계산
const nextShowInfo = useMemo(() => {
return findNextPlayableShow(playListInfo, selectedIndex);
}, [playListInfo, selectedIndex, findNextPlayableShow]);
// ✨ DEBUG: youmaylikeInfos 데이터 로그
useEffect(() => {
dlog('[DEBUG] TabContainerV2 - youmaylikeInfos:', {
exists: !!youmaylikeInfos,
length: youmaylikeInfos?.length,
data: youmaylikeInfos,
shopNowInfo_length: shopNowInfo?.length,
shouldShowYouMayAlso:
shopNowInfo &&
shopNowInfo.length < 3 &&
youmaylikeInfos &&
youmaylikeInfos.length > 0,
});
}, [youmaylikeInfos, shopNowInfo]);
const tabList = [
$L('SHOP NOW'),
panelInfo?.shptmBanrTpNm === 'LIVE'
? $L('LIVE CHANNEL')
: $L('FEATURED SHOWS'),
];
useEffect(() => {
let nowMenu;
if (tabIndex === 0) {
nowMenu = LOG_MENU.FULL_SHOP_NOW;
}
if (tabIndex === 1) {
const isLive = panelInfo?.shptmBanrTpNm === 'LIVE';
nowMenu = isLive
? LOG_MENU.FULL_LIVE_CHANNELS
: LOG_MENU.FULL_FEATURED_SHOWS;
}
if (nowMenu) {
handleItemFocus(nowMenu);
}
}, [handleItemFocus, panelInfo?.shptmBanrTpNm, tabIndex]);
const _handleItemFocus = useCallback(
(nowMenu) => {
if (handleItemFocus) {
handleItemFocus(nowMenu);
}
},
[handleItemFocus]
);
const onSpotlightIndicatorUpButton = useCallback(
(e) => {
if (videoVerticalVisible) {
e.stopPropagation();
e.preventDefault();
Spotlight.focus('spotlightId-video-contaienr');
}
},
[videoVerticalVisible]
);
const handleCloseButtonClick = useCallback(
(e) => {
e.stopPropagation();
e.preventDefault();
if (onTabClose) {
onTabClose(2); // tabIndex를 2로 설정
}
},
[onTabClose]
);
// 위 방향 포커스 이동 시 백 버튼으로 이동
const handleSpotlightUpToBackButton = useCallback((e) => {
e.stopPropagation();
e.preventDefault();
// VideoPlayer가 belowContentsVisible prop을 감지해서 이미 controls를 표시했으므로
// 바로 포커스 이동
Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
}, []);
// 이전 tabIndex 값 추적
const prevTabIndexRef = usePrevious(tabIndex);
const prevTabIndex = prevTabIndexRef.current;
// 하나의 함수에서 모든 tabIndex 변화 처리
const handleTabIndexChange = useCallback((newTabIndex, oldTabIndex) => {
console.log(
`[TabIndexChange] Tab changed from ${oldTabIndex} to ${newTabIndex}`
);
if (newTabIndex === 0) {
// tabIndex = 0 (ShopNow)
const timeoutId = setTimeout(() => {
Spotlight.focus('shownow_close_button');
}, 100);
return () => clearTimeout(timeoutId);
}
if (newTabIndex === 1) {
// tabIndex = 1 (LiveChannel)
const timeoutId = setTimeout(() => {
Spotlight.focus('below-tab-live-channel-button');
}, 100);
return () => clearTimeout(timeoutId);
}
if (newTabIndex === 2) {
// tabIndex = 2 (ShopNowButton)
const timeoutId = setTimeout(() => {
Spotlight.focus('below-tab-shop-now-button');
}, 10);
return () => clearTimeout(timeoutId);
}
}, []);
// tabIndex 변화 감지 및 처리
useEffect(() => {
// 초기 렌더링이 아닐 때만 실행 (prevTabIndex가 정의되었을 때)
if (prevTabIndex !== undefined && prevTabIndex !== tabIndex) {
handleTabIndexChange(tabIndex, prevTabIndex);
}
}, [tabIndex, prevTabIndex, handleTabIndexChange]);
return (
<Container
className={classNames(
css.tabContainer,
videoVerticalVisible && css.vertical,
css[`tabIndex${tabIndex}`]
)}
spotlightId={spotlightId}
>
{tabVisible && tabIndex === 0 && (
<>
<div className={css.shopNowHeaderContainer}>
<div className={css.shopNowHeader}>
<SpottableDiv
className={css.shopNowHeaderLeft}
spotlightId="shownow_close_button"
onClick={handleCloseButtonClick}
onSpotlightUp={handleSpotlightUpToBackButton}
onSpotlightDown={(e) => {
// 첫 번째 ShopNow 아이템으로 포커스 이동
e.stopPropagation();
e.preventDefault();
Spotlight.focus('shop-now-item-0');
}}
>
<div
className={classNames(
css.shopNowIconWrapper,
playListInfo[selectedIndex]?.patncNm === 'QVC' &&
css.shopNowQvcIconWrapper
)}
>
<img
src={playListInfo[selectedIndex]?.patncLogoPath}
alt="shop now icon"
className={css.shopNowIcon}
/>
</div>
<div className={css.shopNowHeaderText}>SHOP NOW</div>
<div className={css.arrowIcon}>
<img src={icon_arrow_dwon} alt="arrow down" />
</div>
</SpottableDiv>
{/* <SpottableDiv
className={css.closeButton}
spotlightId="below-tab-close-button"
onClick={handleCloseButtonClick}
onSpotlightUp={handleSpotlightUpToBackButton}
onSpotlightDown={(e) => {
// 첫 번째 ShopNow 아이템으로 포커스 이동
e.stopPropagation();
e.preventDefault();
Spotlight.focus("shop-now-item-0");
}}
>
×
</SpottableDiv> */}
</div>
{/* YouMayAlso Like 헤더 (ShopNow 아이템 < 3 && YouMayLike 데이터 존재) */}
{shopNowInfo &&
shopNowInfo.length < 3 &&
youmaylikeInfos &&
youmaylikeInfos.length > 0 && (
<div className={css.youMayAlsoLikeHeader}>
<div className={css.youMayAlsoLikeText}>
You may also like
</div>
</div>
)}
</div>
<ShopNowContents
tabTitle={tabList}
shopNowInfo={shopNowInfo}
playListInfo={playListInfo && playListInfo[selectedIndex]}
videoVerticalVisible={videoVerticalVisible}
panelInfo={panelInfo}
tabIndex={tabIndex}
handleItemFocus={_handleItemFocus}
version={2}
direction="horizontal"
/>
</>
)}
{tabVisible && tabIndex === 1 && (
<>
<SpottableDiv
className={css.liveChannelButton}
onClick={onLiveChannelButtonClick}
spotlightId={
panelInfo?.shptmBanrTpNm === 'LIVE'
? 'below-tab-live-channel-button'
: 'below-tab-featured-show-button'
}
onSpotlightUp={handleSpotlightUpToBackButton}
onSpotlightDown={(e) => {
// 첫 번째 PlayerItem으로 포커스 이동
Spotlight.focus('tabChannel-video-0');
}}
onSpotlightFocus={() => {
console.log('[TabContainerV2] below-tab button focused');
}}
>
<span className={css.buttonText}>{tabList[1]}</span>
<div className={css.arrowIcon}>
<img src={icon_arrow_dwon} alt="arrow down" />
</div>
</SpottableDiv>
{panelInfo?.shptmBanrTpNm === 'LIVE' && playListInfo && (
<LiveChannelContents
tabTitle={tabList}
selectedIndex={selectedIndex}
setSelectedIndex={setSelectedIndex}
videoVerticalVisible={videoVerticalVisible}
currentVideoShowId={playListInfo[selectedIndex]?.showId}
liveInfos={playListInfo}
tabIndex={tabIndex}
handleItemFocus={_handleItemFocus}
panelInfo={panelInfo}
currentTime={currentTime}
version={2}
direction="horizontal"
/>
)}
{panelInfo?.shptmBanrTpNm === 'VOD' && playListInfo && (
<FeaturedShowContents
tabTitle={tabList}
featuredShowsInfos={playListInfo}
currentVideoInfo={playListInfo[selectedIndex]}
setSelectedIndex={setSelectedIndex}
selectedIndex={selectedIndex}
videoVerticalVisible={videoVerticalVisible}
currentVideoShowId={playListInfo[selectedIndex]?.showId}
tabIndex={tabIndex}
handleItemFocus={_handleItemFocus}
panelInfo={panelInfo}
version={2}
direction="horizontal"
/>
)}
</>
)}
{tabVisible && tabIndex === 2 && (
<>
<LiveChannelNext
channelLogo={nextShowInfo?.patncLogoPath}
channelName={nextShowInfo?.patncNm || 'ShopLC'}
programName={nextShowInfo?.showNm || 'Live Channel'}
backgroundColor={
nextShowInfo?.dfltThumbnailImgPath ||
'linear-gradient(180deg, #284998 0%, #06B0EE 100%)'
}
onClick={onLiveNext}
spotlightId="live-channel-next-button"
onFocus={onLiveNext}
/>
<ShopNowButton onClick={onShopNowButtonClick} />
</>
)}
</Container>
);
}