[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 { $L, removeSpecificTags } from "../../utils/helperMethods";
|
||||
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";
|
||||
|
||||
const SpottableComponent = Spottable("div");
|
||||
@@ -81,8 +82,10 @@ export default memo(function TItemCard({
|
||||
nowCategory,
|
||||
nowProductTitle,
|
||||
contentId,
|
||||
version = 1,
|
||||
...rest
|
||||
}) {
|
||||
const css = version === 2 ? css2 : css1;
|
||||
const dispatch = useDispatch();
|
||||
const [defaultImage, setDefaultImage] = useState(null);
|
||||
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 PlayerTabButton from './PlayerTabContents/TabButton/PlayerTabButton';
|
||||
import TabContainer from './PlayerTabContents/TabContainer';
|
||||
import LiveShowContainer from './PlayerTabContents/v2/LiveShowContainer';
|
||||
import ShopNowContainer from './PlayerTabContents/v2/ShopNowContainer';
|
||||
import ShopNowButton from './PlayerTabContents/v2/ShopNowButton';
|
||||
import TabContainerV2 from './PlayerTabContents/v2/TabContainer.v2';
|
||||
// import LiveShowContainer from './PlayerTabContents/v2/LiveShowContainer';
|
||||
// import ShopNowContainer from './PlayerTabContents/v2/ShopNowContainer';
|
||||
// import ShopNowButton from './PlayerTabContents/v2/ShopNowButton';
|
||||
|
||||
const Container = SpotlightContainerDecorator(
|
||||
{ enterTo: 'default-element', preserveld: true },
|
||||
@@ -203,6 +204,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
});
|
||||
const [isVODPaused, setIsVODPaused] = USE_STATE('isVODPaused', false);
|
||||
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 chatData = USE_SELECTOR('chatData', (state) => state.play.chatData);
|
||||
@@ -2076,7 +2078,7 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
|
||||
/>
|
||||
)}
|
||||
|
||||
{shouldShowBelowTab && (
|
||||
{/* {shouldShowBelowTab && (
|
||||
<>
|
||||
{belowTabMode === 'liveShow' && (
|
||||
<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>
|
||||
|
||||
|
||||
@@ -142,9 +142,9 @@ export default function LiveChannelContents({
|
||||
dataSize={liveInfos.length}
|
||||
direction={direction}
|
||||
renderItem={renderItem}
|
||||
itemWidth={version === 2 ? 280 : videoVerticalVisible ? 540 : 600}
|
||||
itemHeight={version === 2 ? 78 : 236}
|
||||
spacing={12}
|
||||
itemWidth={version === 2 ? 470 : videoVerticalVisible ? 540 : 600}
|
||||
itemHeight={version === 2 ? 155 : 236}
|
||||
spacing={version === 2 ? 30 : 12}
|
||||
noScrollByWheel={false}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 175px;
|
||||
height: 155px;
|
||||
|
||||
> div:nth-child(1) {
|
||||
.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 { scaleH } from "../../../../utils/helperMethods";
|
||||
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";
|
||||
|
||||
|
||||
@@ -49,8 +50,11 @@ export default function ShopNowContents({
|
||||
tabIndex,
|
||||
handleItemFocus,
|
||||
panelInfo,
|
||||
tabTitle
|
||||
tabTitle,
|
||||
version = 1,
|
||||
direction = "vertical"
|
||||
}) {
|
||||
const css = version === 2 ? cssV2 : css1;
|
||||
const { getScrollTo, scrollTop } = useScrollTo();
|
||||
const dispatch = useDispatch();
|
||||
const paenls = useSelector((state) => state.panels.panels[1]?.panelInfo);
|
||||
@@ -175,10 +179,11 @@ export default function ShopNowContents({
|
||||
onClick={handleItemClick}
|
||||
onFocus={handleFocus()}
|
||||
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 (
|
||||
@@ -189,11 +194,11 @@ export default function ShopNowContents({
|
||||
style={gridStyle}
|
||||
cbScrollTo={getScrollTo}
|
||||
dataSize={shopNowInfo.length}
|
||||
direction="vertical"
|
||||
direction={direction}
|
||||
renderItem={renderItem}
|
||||
itemWidth={videoVerticalVisible ? 540 : 600}
|
||||
itemHeight={236}
|
||||
spacing={12}
|
||||
itemWidth={version === 2 ? 310 : (videoVerticalVisible ? 540 : 600)}
|
||||
itemHeight={version === 2 ? 420 : 236}
|
||||
spacing={version === 2 ? 30 : 12}
|
||||
className={
|
||||
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 { LOG_MENU, panel_names } from "../../../../utils/Config";
|
||||
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({
|
||||
shopNowInfo,
|
||||
handleItemFocus,
|
||||
playListInfo,
|
||||
version = 1,
|
||||
direction = "vertical"
|
||||
}) {
|
||||
const css = version === 2 ? cssV2 : css1;
|
||||
const dispatch = useDispatch();
|
||||
const [height, setHeight] = useState(scaleH(236));
|
||||
const youmaylikeInfos = useSelector((state) => state.main.youmaylikeInfos);
|
||||
@@ -72,10 +76,11 @@ export default function YouMayLikeContents({
|
||||
onClick={handleItemClick}
|
||||
onFocus={handleFocus()}
|
||||
type={TYPES.horizontal}
|
||||
version={version}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[youmaylikeInfos]
|
||||
[youmaylikeInfos, version, handleFocus, dispatch, playListInfo]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -88,11 +93,11 @@ export default function YouMayLikeContents({
|
||||
<TVirtualGridList
|
||||
style={gridStyle}
|
||||
dataSize={youmaylikeInfos.length}
|
||||
direction="vertical"
|
||||
direction={direction}
|
||||
renderItem={renderItem}
|
||||
itemWidth={600}
|
||||
itemHeight={236}
|
||||
spacing={12}
|
||||
itemWidth={version === 2 ? 310 : 600}
|
||||
itemHeight={version === 2 ? 420 : 236}
|
||||
spacing={version === 2 ? 30 : 12}
|
||||
className={css.itemList}
|
||||
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