[251008] feat v2 탭 컨테이너 추가 및 UI 조정
v2 탭 컨테이너를 도입하고, 아이템 크기와 간격을 조정해 UI를 개선했습니다. 🕐 커밋 시간: 2025. 10. 08. 21:38:17 📊 변경 통계: • 총 파일: 11개 • 추가: +56줄 • 삭제: -22줄 📁 추가된 파일: + com.twin.app.shoptime/src/components/TItemCard/TItemCard.v2.module.less + com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.v2.module.less + com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/YouMayLikeContents.v2.module.less + com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx + com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.module.less 📝 수정된 파일: ~ com.twin.app.shoptime/src/components/TItemCard/TItemCard.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/LiveChannelContents.v2.module.less ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/ShopNowContents.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/TabContents/YouMayLikeContents.jsx 🔧 주요 변경 내용: • UI 컴포넌트 아키텍처 개선 • 소규모 기능 개선 • 모듈 구조 개선
This commit is contained in:
@@ -13,7 +13,8 @@ import IcLiveShow from "../../../assets/images/tag/tag-liveshow.png";
|
|||||||
import usePriceInfo from "../../hooks/usePriceInfo";
|
import usePriceInfo from "../../hooks/usePriceInfo";
|
||||||
import { $L, removeSpecificTags } from "../../utils/helperMethods";
|
import { $L, removeSpecificTags } from "../../utils/helperMethods";
|
||||||
import CustomImage from "../CustomImage/CustomImage";
|
import CustomImage from "../CustomImage/CustomImage";
|
||||||
import css from "./TItemCard.module.less";
|
import css1 from "./TItemCard.module.less";
|
||||||
|
import css2 from "./TItemCard.v2.module.less";
|
||||||
import { sendLogTotalRecommend } from "../../actions/logActions";
|
import { sendLogTotalRecommend } from "../../actions/logActions";
|
||||||
|
|
||||||
const SpottableComponent = Spottable("div");
|
const SpottableComponent = Spottable("div");
|
||||||
@@ -81,8 +82,10 @@ export default memo(function TItemCard({
|
|||||||
nowCategory,
|
nowCategory,
|
||||||
nowProductTitle,
|
nowProductTitle,
|
||||||
contentId,
|
contentId,
|
||||||
|
version = 1,
|
||||||
...rest
|
...rest
|
||||||
}) {
|
}) {
|
||||||
|
const css = version === 2 ? css2 : css1;
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [defaultImage, setDefaultImage] = useState(null);
|
const [defaultImage, setDefaultImage] = useState(null);
|
||||||
const countryCode = useSelector((state) => state.common.httpHeader.cntry_cd);
|
const countryCode = useSelector((state) => state.common.httpHeader.cntry_cd);
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
@import "../../style/CommonStyle.module.less";
|
||||||
|
@import "../../style/utils.module.less";
|
||||||
|
|
||||||
|
// horizontal type for v2 (ShopNowItem.module.less 스타일 적용, TItemCard.jsx 클래스명 구조 유지)
|
||||||
|
.horizontal {
|
||||||
|
width: 310px;
|
||||||
|
padding: 18px;
|
||||||
|
background: rgba(51, 51, 51, 0.95);
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: flex-start, @direction: column);
|
||||||
|
gap: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
.imageWrap {
|
||||||
|
align-self: stretch;
|
||||||
|
height: 274px;
|
||||||
|
padding: 5px 4px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
> img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
// discount rate
|
||||||
|
> span {
|
||||||
|
.position(@position: absolute, @right: 12px, @bottom: 12px);
|
||||||
|
.size(@w: 60px, @h: 60px);
|
||||||
|
border-radius: 60px;
|
||||||
|
background-color: @PRIMARY_COLOR_RED;
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 60px;
|
||||||
|
color: @COLOR_WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sold out
|
||||||
|
.soldout {
|
||||||
|
.position(@position: absolute, @top: 0, @right: 0);
|
||||||
|
.flex();
|
||||||
|
.size(@w: 100%, @h: 100%);
|
||||||
|
background-color: rgba(26, 26, 26, 0.6);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 24px;
|
||||||
|
color: @COLOR_WHITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.descWrap {
|
||||||
|
align-self: stretch;
|
||||||
|
.flex(@display: flex, @justifyCenter: center, @alignCenter: flex-start, @direction: column);
|
||||||
|
gap: 15px;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
> img {
|
||||||
|
width: 60px;
|
||||||
|
min-height: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
align-self: stretch;
|
||||||
|
|
||||||
|
.productNameTitle {
|
||||||
|
align-self: stretch;
|
||||||
|
color: #EAEAEA;
|
||||||
|
font-size: 24px;
|
||||||
|
font-family: @baseFont;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 31px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
.elip(@clamp: 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.priceInfo {
|
||||||
|
align-self: stretch;
|
||||||
|
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: center);
|
||||||
|
gap: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 30px;
|
||||||
|
color: @PRIMARY_COLOR_RED;
|
||||||
|
font-family: @baseFont;
|
||||||
|
line-height: 35px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
color: #808080;
|
||||||
|
font-size: 24px;
|
||||||
|
font-family: @baseFont;
|
||||||
|
font-weight: 400;
|
||||||
|
text-decoration: line-through;
|
||||||
|
line-height: 18px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
> strong {
|
||||||
|
color: @PRIMARY_COLOR_RED;
|
||||||
|
font-size: 30px;
|
||||||
|
font-family: @baseFont;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 35px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.offerInfo {
|
||||||
|
align-self: stretch;
|
||||||
|
color: @PRIMARY_COLOR_RED;
|
||||||
|
font-size: 30px;
|
||||||
|
font-family: @baseFont;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 35px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: @PRIMARY_COLOR_RED;
|
||||||
|
outline: 4px @PRIMARY_COLOR_RED solid;
|
||||||
|
outline-offset: -4px;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
.focused(@boxShadow: 22px, @borderRadius: 12px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// vertical type (v2에서는 사용하지 않음)
|
||||||
|
.vertical {
|
||||||
|
}
|
||||||
|
|
||||||
|
// videoShow type (v2에서는 사용하지 않음)
|
||||||
|
.videoShow {
|
||||||
|
}
|
||||||
@@ -55,9 +55,10 @@ import PlayerOverlayQRCode from './PlayerOverlay/PlayerOverlayQRCode';
|
|||||||
import css from './PlayerPanel.module.less';
|
import css from './PlayerPanel.module.less';
|
||||||
import PlayerTabButton from './PlayerTabContents/TabButton/PlayerTabButton';
|
import PlayerTabButton from './PlayerTabContents/TabButton/PlayerTabButton';
|
||||||
import TabContainer from './PlayerTabContents/TabContainer';
|
import TabContainer from './PlayerTabContents/TabContainer';
|
||||||
import LiveShowContainer from './PlayerTabContents/v2/LiveShowContainer';
|
import TabContainerV2 from './PlayerTabContents/v2/TabContainer.v2';
|
||||||
import ShopNowContainer from './PlayerTabContents/v2/ShopNowContainer';
|
// import LiveShowContainer from './PlayerTabContents/v2/LiveShowContainer';
|
||||||
import ShopNowButton from './PlayerTabContents/v2/ShopNowButton';
|
// import ShopNowContainer from './PlayerTabContents/v2/ShopNowContainer';
|
||||||
|
// import ShopNowButton from './PlayerTabContents/v2/ShopNowButton';
|
||||||
|
|
||||||
const Container = SpotlightContainerDecorator(
|
const Container = SpotlightContainerDecorator(
|
||||||
{ enterTo: 'default-element', preserveld: true },
|
{ enterTo: 'default-element', preserveld: true },
|
||||||
@@ -203,6 +204,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
});
|
});
|
||||||
const [isVODPaused, setIsVODPaused] = USE_STATE('isVODPaused', false);
|
const [isVODPaused, setIsVODPaused] = USE_STATE('isVODPaused', false);
|
||||||
const [belowTabMode, setBelowTabMode] = USE_STATE('belowTabMode', 'liveShow');
|
const [belowTabMode, setBelowTabMode] = USE_STATE('belowTabMode', 'liveShow');
|
||||||
|
const [tabIndexV2, setTabIndexV2] = USE_STATE('tabIndexV2', 1); // 0: ShopNow, 1: LiveChannel, 2: ShopNowButton
|
||||||
|
|
||||||
const panels = USE_SELECTOR('panels', (state) => state.panels.panels);
|
const panels = USE_SELECTOR('panels', (state) => state.panels.panels);
|
||||||
const chatData = USE_SELECTOR('chatData', (state) => state.play.chatData);
|
const chatData = USE_SELECTOR('chatData', (state) => state.play.chatData);
|
||||||
@@ -2076,7 +2078,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{shouldShowBelowTab && (
|
{/* {shouldShowBelowTab && (
|
||||||
<>
|
<>
|
||||||
{belowTabMode === 'liveShow' && (
|
{belowTabMode === 'liveShow' && (
|
||||||
<LiveShowContainer
|
<LiveShowContainer
|
||||||
@@ -2111,6 +2113,25 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
)} */}
|
||||||
|
|
||||||
|
{shouldShowBelowTab && (
|
||||||
|
<TabContainerV2
|
||||||
|
panelInfo={panelInfo}
|
||||||
|
playListInfo={playListInfo}
|
||||||
|
shopNowInfo={shopNowInfo}
|
||||||
|
selectedIndex={selectedIndex}
|
||||||
|
setSelectedIndex={setSelectedIndex}
|
||||||
|
liveChannelInfos={liveChannelInfos || liveShowInfos}
|
||||||
|
videoVerticalVisible={videoVerticalVisible}
|
||||||
|
handleItemFocus={handleItemFocus}
|
||||||
|
prevChannelIndex={prevChannelIndex}
|
||||||
|
currentTime={currentTime}
|
||||||
|
spotlightId="tab-container-v2-spotlight-id"
|
||||||
|
tabIndex={tabIndexV2}
|
||||||
|
onShopNowButtonClick={() => setTabIndexV2(0)}
|
||||||
|
onLiveChannelButtonClick={() => setTabIndexV2(2)}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
|
|||||||
@@ -142,9 +142,9 @@ export default function LiveChannelContents({
|
|||||||
dataSize={liveInfos.length}
|
dataSize={liveInfos.length}
|
||||||
direction={direction}
|
direction={direction}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
itemWidth={version === 2 ? 280 : videoVerticalVisible ? 540 : 600}
|
itemWidth={version === 2 ? 470 : videoVerticalVisible ? 540 : 600}
|
||||||
itemHeight={version === 2 ? 78 : 236}
|
itemHeight={version === 2 ? 155 : 236}
|
||||||
spacing={12}
|
spacing={version === 2 ? 30 : 12}
|
||||||
noScrollByWheel={false}
|
noScrollByWheel={false}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 175px;
|
height: 155px;
|
||||||
|
|
||||||
> div:nth-child(1) {
|
> div:nth-child(1) {
|
||||||
.size(@w: 100%, @h: 100%);
|
.size(@w: 100%, @h: 100%);
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ import useScrollTo from "../../../../hooks/useScrollTo";
|
|||||||
import { LOG_CONTEXT_NAME, LOG_MENU, LOG_MESSAGE_ID, panel_names } from "../../../../utils/Config";
|
import { LOG_CONTEXT_NAME, LOG_MENU, LOG_MESSAGE_ID, panel_names } from "../../../../utils/Config";
|
||||||
import { scaleH } from "../../../../utils/helperMethods";
|
import { scaleH } from "../../../../utils/helperMethods";
|
||||||
import ListEmptyContents from "../TabContents/ListEmptyContents/ListEmptyContents";
|
import ListEmptyContents from "../TabContents/ListEmptyContents/ListEmptyContents";
|
||||||
import css from "./ShopNowContents.module.less";
|
import css1 from "./ShopNowContents.module.less";
|
||||||
|
import cssV2 from "./ShopNowContents.v2.module.less";
|
||||||
import { sendLogTotalRecommend } from "../../../../actions/logActions";
|
import { sendLogTotalRecommend } from "../../../../actions/logActions";
|
||||||
|
|
||||||
|
|
||||||
@@ -49,8 +50,11 @@ export default function ShopNowContents({
|
|||||||
tabIndex,
|
tabIndex,
|
||||||
handleItemFocus,
|
handleItemFocus,
|
||||||
panelInfo,
|
panelInfo,
|
||||||
tabTitle
|
tabTitle,
|
||||||
|
version = 1,
|
||||||
|
direction = "vertical"
|
||||||
}) {
|
}) {
|
||||||
|
const css = version === 2 ? cssV2 : css1;
|
||||||
const { getScrollTo, scrollTop } = useScrollTo();
|
const { getScrollTo, scrollTop } = useScrollTo();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const paenls = useSelector((state) => state.panels.panels[1]?.panelInfo);
|
const paenls = useSelector((state) => state.panels.panels[1]?.panelInfo);
|
||||||
@@ -175,10 +179,11 @@ export default function ShopNowContents({
|
|||||||
onClick={handleItemClick}
|
onClick={handleItemClick}
|
||||||
onFocus={handleFocus()}
|
onFocus={handleFocus()}
|
||||||
type={TYPES.horizontal}
|
type={TYPES.horizontal}
|
||||||
|
version={version}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[shopNowInfo, videoVerticalVisible, panelInfo?.shptmBanrTpNm, priceInfoMap, tabTitle, tabIndex, playListInfo, dispatch]
|
[shopNowInfo, videoVerticalVisible, panelInfo?.shptmBanrTpNm, priceInfoMap, tabTitle, tabIndex, playListInfo, dispatch, version]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -189,11 +194,11 @@ export default function ShopNowContents({
|
|||||||
style={gridStyle}
|
style={gridStyle}
|
||||||
cbScrollTo={getScrollTo}
|
cbScrollTo={getScrollTo}
|
||||||
dataSize={shopNowInfo.length}
|
dataSize={shopNowInfo.length}
|
||||||
direction="vertical"
|
direction={direction}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
itemWidth={videoVerticalVisible ? 540 : 600}
|
itemWidth={version === 2 ? 310 : (videoVerticalVisible ? 540 : 600)}
|
||||||
itemHeight={236}
|
itemHeight={version === 2 ? 420 : 236}
|
||||||
spacing={12}
|
spacing={version === 2 ? 30 : 12}
|
||||||
className={
|
className={
|
||||||
videoVerticalVisible ? css.verticalItemList : css.itemList
|
videoVerticalVisible ? css.verticalItemList : css.itemList
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
@import "../../../../style/CommonStyle.module.less";
|
||||||
|
@import "../../../../style/utils.module.less";
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
height: 420px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> div:nth-child(1) {
|
||||||
|
.size(@w: 100%, @h: 100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemList {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
> div {
|
||||||
|
> div {
|
||||||
|
> div {
|
||||||
|
> div {
|
||||||
|
.size(@w: 310px, @h: 420px);
|
||||||
|
> div:nth-child(1) {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.verticalItemList {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
> div {
|
||||||
|
> div {
|
||||||
|
> div {
|
||||||
|
> div {
|
||||||
|
.size(@w: 310px, @h: 420px);
|
||||||
|
> div:nth-child(1) {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,13 +7,17 @@ import TItemCard, { TYPES } from "../../../../components/TItemCard/TItemCard";
|
|||||||
import TVirtualGridList from "../../../../components/TVirtualGridList/TVirtualGridList";
|
import TVirtualGridList from "../../../../components/TVirtualGridList/TVirtualGridList";
|
||||||
import { LOG_MENU, panel_names } from "../../../../utils/Config";
|
import { LOG_MENU, panel_names } from "../../../../utils/Config";
|
||||||
import { $L, scaleH } from "../../../../utils/helperMethods";
|
import { $L, scaleH } from "../../../../utils/helperMethods";
|
||||||
import css from "./YouMayLikeContents.module.less";
|
import css1 from "./YouMayLikeContents.module.less";
|
||||||
|
import cssV2 from "./YouMayLikeContents.v2.module.less";
|
||||||
|
|
||||||
export default function YouMayLikeContents({
|
export default function YouMayLikeContents({
|
||||||
shopNowInfo,
|
shopNowInfo,
|
||||||
handleItemFocus,
|
handleItemFocus,
|
||||||
playListInfo,
|
playListInfo,
|
||||||
|
version = 1,
|
||||||
|
direction = "vertical"
|
||||||
}) {
|
}) {
|
||||||
|
const css = version === 2 ? cssV2 : css1;
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [height, setHeight] = useState(scaleH(236));
|
const [height, setHeight] = useState(scaleH(236));
|
||||||
const youmaylikeInfos = useSelector((state) => state.main.youmaylikeInfos);
|
const youmaylikeInfos = useSelector((state) => state.main.youmaylikeInfos);
|
||||||
@@ -72,10 +76,11 @@ export default function YouMayLikeContents({
|
|||||||
onClick={handleItemClick}
|
onClick={handleItemClick}
|
||||||
onFocus={handleFocus()}
|
onFocus={handleFocus()}
|
||||||
type={TYPES.horizontal}
|
type={TYPES.horizontal}
|
||||||
|
version={version}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[youmaylikeInfos]
|
[youmaylikeInfos, version, handleFocus, dispatch, playListInfo]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -88,11 +93,11 @@ export default function YouMayLikeContents({
|
|||||||
<TVirtualGridList
|
<TVirtualGridList
|
||||||
style={gridStyle}
|
style={gridStyle}
|
||||||
dataSize={youmaylikeInfos.length}
|
dataSize={youmaylikeInfos.length}
|
||||||
direction="vertical"
|
direction={direction}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
itemWidth={600}
|
itemWidth={version === 2 ? 310 : 600}
|
||||||
itemHeight={236}
|
itemHeight={version === 2 ? 420 : 236}
|
||||||
spacing={12}
|
spacing={version === 2 ? 30 : 12}
|
||||||
className={css.itemList}
|
className={css.itemList}
|
||||||
noScrollByWheel={false}
|
noScrollByWheel={false}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
@import "../../../../style/CommonStyle.module.less";
|
||||||
|
@import "../../../../style/utils.module.less";
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: #EAEAEA;
|
||||||
|
font-size: 30px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-top: 40px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
.size(@w: 100%, @h: 1px);
|
||||||
|
opacity: 0.3;
|
||||||
|
background-color: #d9d9d9;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemList {
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
> div {
|
||||||
|
> div {
|
||||||
|
> div {
|
||||||
|
> div {
|
||||||
|
.size(@w: 310px, @h: 420px);
|
||||||
|
> div:nth-child(1) {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
import React, { useCallback, useEffect } from "react";
|
||||||
|
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
import Spotlight from "@enact/spotlight";
|
||||||
|
import Spottable from "@enact/spotlight/Spottable";
|
||||||
|
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
|
||||||
|
|
||||||
|
import { LOG_MENU } from "../../../../utils/Config";
|
||||||
|
import css from "./TabContainer.v2.module.less";
|
||||||
|
import LiveChannelContents from "../TabContents/LiveChannelContents";
|
||||||
|
import ShopNowContents from "../TabContents/ShopNowContents";
|
||||||
|
import YouMayLikeContents from "../TabContents/YouMayLikeContents";
|
||||||
|
import ShopNowButton from "./ShopNowButton";
|
||||||
|
import icon_arrow_dwon from "../../../../../assets/images/player/icon_tabcontainer_arrow_down.png";
|
||||||
|
import icon_shop_now from "../../../../../assets/images/player/icon_tabcontainer_shopnow.png";
|
||||||
|
|
||||||
|
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,
|
||||||
|
}) {
|
||||||
|
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]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container
|
||||||
|
className={classNames(
|
||||||
|
css.tabContainer,
|
||||||
|
videoVerticalVisible && css.vertical,
|
||||||
|
css[`tabIndex${tabIndex}`]
|
||||||
|
)}
|
||||||
|
spotlightId={spotlightId}
|
||||||
|
>
|
||||||
|
{tabIndex === 0 && (
|
||||||
|
<>
|
||||||
|
<div className={css.shopNowHeader}>
|
||||||
|
<div className={css.shopNowIconWrapper}>
|
||||||
|
<img src={icon_shop_now} alt="shop now icon" className={css.shopNowIcon} />
|
||||||
|
</div>
|
||||||
|
<div className={css.shopNowHeaderText}>SHOP NOW</div>
|
||||||
|
</div>
|
||||||
|
<ShopNowContents
|
||||||
|
shopNowInfo={shopNowInfo}
|
||||||
|
playListInfo={playListInfo && playListInfo[selectedIndex]}
|
||||||
|
videoVerticalVisible={videoVerticalVisible}
|
||||||
|
panelInfo={panelInfo}
|
||||||
|
tabIndex={tabIndex}
|
||||||
|
handleItemFocus={_handleItemFocus}
|
||||||
|
version={2}
|
||||||
|
direction="horizontal"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{tabIndex === 1 && (
|
||||||
|
<>
|
||||||
|
<SpottableDiv
|
||||||
|
className={css.liveChannelButton}
|
||||||
|
onClick={onLiveChannelButtonClick}
|
||||||
|
spotlightId="below-tab-live-channel-button"
|
||||||
|
>
|
||||||
|
<span className={css.buttonText}>LIVE CHANNEL</span>
|
||||||
|
<div className={css.arrowIcon}>
|
||||||
|
<img src={icon_arrow_dwon} alt="arrow down" />
|
||||||
|
</div>
|
||||||
|
</SpottableDiv>
|
||||||
|
|
||||||
|
{panelInfo?.shptmBanrTpNm === "LIVE" && playListInfo && (
|
||||||
|
<LiveChannelContents
|
||||||
|
selectedIndex={selectedIndex}
|
||||||
|
setSelectedIndex={setSelectedIndex}
|
||||||
|
videoVerticalVisible={videoVerticalVisible}
|
||||||
|
currentVideoShowId={playListInfo[selectedIndex]?.showId}
|
||||||
|
liveInfos={playListInfo}
|
||||||
|
tabIndex={tabIndex}
|
||||||
|
handleItemFocus={_handleItemFocus}
|
||||||
|
panelInfo={panelInfo}
|
||||||
|
currentTime={currentTime}
|
||||||
|
version={2}
|
||||||
|
direction="horizontal"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{tabIndex === 2 && (
|
||||||
|
<ShopNowButton onClick={onShopNowButtonClick} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{shopNowInfo && shopNowInfo.length < 3 && tabIndex === 0 && (
|
||||||
|
<YouMayLikeContents
|
||||||
|
shopNowInfo={shopNowInfo}
|
||||||
|
handleItemFocus={_handleItemFocus}
|
||||||
|
playListInfo={playListInfo && playListInfo[selectedIndex]}
|
||||||
|
version={2}
|
||||||
|
direction="horizontal"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
@import "../../../../style/CommonStyle.module.less";
|
||||||
|
@import "../../../../style/utils.module.less";
|
||||||
|
|
||||||
|
.tabContainer {
|
||||||
|
.position(@position: fixed, @bottom: 0, @left: 0);
|
||||||
|
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: flex-start, @direction: column);
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 5;
|
||||||
|
|
||||||
|
// tabIndex = 0: ShopNow 스타일 (ShopNowContainer 참고)
|
||||||
|
&.tabIndex0 {
|
||||||
|
.size(@w: 100%, @h: 675px);
|
||||||
|
padding: 60px;
|
||||||
|
background: linear-gradient(270deg, rgba(0, 0, 0, 0.80) 0%, rgba(0, 0, 0, 0.62) 30%, rgba(0, 0, 0, 0) 65%),
|
||||||
|
linear-gradient(0deg, rgba(0, 0, 0, 0.53) 0%, rgba(20.56, 4.68, 32.71, 0.53) 60%, rgba(199, 32, 84, 0) 98%),
|
||||||
|
linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, black 45%, black 100%),
|
||||||
|
rgba(0, 0, 0, 0.56);
|
||||||
|
border-top: 1px solid rgba(234, 234, 234, 0.8);
|
||||||
|
gap: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
// tabIndex = 1: LiveShow 스타일 (LiveShowContainer 참고)
|
||||||
|
&.tabIndex1 {
|
||||||
|
.size(@w: 100%, @h: 365px);
|
||||||
|
padding: 60px;
|
||||||
|
background: linear-gradient(
|
||||||
|
to top,
|
||||||
|
rgba(0, 0, 0, 0.95) 0%,
|
||||||
|
rgba(0, 0, 0, 0.85) 40%,
|
||||||
|
rgba(0, 0, 0, 0.6) 70%,
|
||||||
|
rgba(0, 0, 0, 0) 100%
|
||||||
|
);
|
||||||
|
border-top: 1px solid rgba(234, 234, 234, 0.3);
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
.position(@position: absolute, @top: 0, @left: 0);
|
||||||
|
.size(@w: 100%, @h: 100%);
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(30, 30, 30, 0.4) 0%,
|
||||||
|
rgba(50, 50, 50, 0.25) 30%,
|
||||||
|
transparent 50%,
|
||||||
|
rgba(50, 50, 50, 0.25) 70%,
|
||||||
|
rgba(30, 30, 30, 0.4) 100%
|
||||||
|
);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tabIndex = 2: ShopNowButton만 (특별한 컨테이너 스타일 없음)
|
||||||
|
&.tabIndex2 {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
// vertical 모드일 때 추가 스타일 (필요시)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SHOP NOW 헤더 스타일 (ShopNowContainer 참고)
|
||||||
|
.shopNowHeader {
|
||||||
|
width: 300px;
|
||||||
|
height: 70px;
|
||||||
|
padding: 20px 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 100px;
|
||||||
|
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: center);
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shopNowIconWrapper {
|
||||||
|
.size(@w: 40px, @h: 40px);
|
||||||
|
.position(@position: relative);
|
||||||
|
background: white;
|
||||||
|
border-radius: 100px;
|
||||||
|
.flex(@display: flex, @justifyCenter: center, @alignCenter: center);
|
||||||
|
}
|
||||||
|
|
||||||
|
.shopNowIcon {
|
||||||
|
.size(@w: 20.67px, @h: 20.67px);
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shopNowHeaderText {
|
||||||
|
color: #EAEAEA;
|
||||||
|
font-size: 24px;
|
||||||
|
font-family: @baseFont;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 31px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LIVE CHANNEL 버튼 스타일 (LiveShowContainer 참고)
|
||||||
|
.liveChannelButton {
|
||||||
|
.size(@w: 300px, @h: 70px);
|
||||||
|
padding: 20px 30px;
|
||||||
|
border-radius: 100px;
|
||||||
|
border: 1px solid rgba(234, 234, 234, 0.5);
|
||||||
|
overflow: hidden;
|
||||||
|
.flex(@display: flex, @justifyCenter: space-between, @alignCenter: center);
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
.buttonText {
|
||||||
|
color: @COLOR_WHITE;
|
||||||
|
font-size: 24px;
|
||||||
|
font-family: @baseFont;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 35px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrowIcon {
|
||||||
|
.size(@w: 26.25px, @h: 15.63px);
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
border-color: rgba(234, 234, 234, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
background: rgba(199, 8, 80, 0.2);
|
||||||
|
border-color: @PRIMARY_COLOR_RED;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
.focused(@boxShadow: 22px, @borderRadius: 100px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user