[251005] feat BelowTabContainer 컴포넌트 추가

PlayerPanel에 새로운 BelowTabContainer 컴포넌트와 스타일을 추가해 탭 UI를 확장했습니다.

🕐 커밋 시간: 2025. 10. 05. 23:14:06

📊 변경 통계:
  • 총 파일: 4개
  • 추가: +50줄

📁 추가된 파일:
  + com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/BelowTabContainer.jsx
  + com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/BelowTabContainer.module.less
  + com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/BelowTablContainer.figma.jsx

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx (javascript):
     Added: PlayerPanel()
    🔄 Modified: getLogTpNo()
  📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/BelowTabContainer.jsx (javascript):
     Added: handleChannelClick()
  📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/BelowTabContainer.module.less (unknown):
     Added: position(), gradient(), focused()
This commit is contained in:
2025-10-05 23:14:08 +09:00
parent a5ce003d3e
commit bb74f9827a
4 changed files with 1150 additions and 428 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,93 @@
import React from 'react';
import classNames from 'classnames';
import Spottable from '@enact/spotlight/Spottable';
import CustomImage from '../../../../components/CustomImage/CustomImage';
import css from './BelowTabContainer.module.less';
const SpottableDiv = Spottable('div');
const ChannelCard = ({
channelName,
programName,
logoSrc,
thumbnailSrc,
progressPercent = 65,
backgroundColor = '#FFFFFF',
onFocus,
onBlur,
onClick,
spotlightId,
}) => {
return (
<SpottableDiv
className={css.channelCard}
onFocus={onFocus}
onBlur={onBlur}
onClick={onClick}
spotlightId={spotlightId}
>
<div className={css.thumbnail}>
<CustomImage src={thumbnailSrc} alt={channelName} />
</div>
<div className={css.cardContent}>
<div className={css.channelHeader}>
<div className={css.logoWrapper} style={{ background: backgroundColor }}>
<CustomImage src={logoSrc} alt={`${channelName} logo`} className={css.logo} />
</div>
<h3 className={css.channelName}>{channelName}</h3>
</div>
<div className={css.programName}>{programName}</div>
<div className={css.progressBarWrapper}>
<div className={css.progressBar}>
<div className={css.progressTrack} />
<div className={css.progressFill} style={{ width: `${progressPercent}%` }} />
</div>
</div>
</div>
</SpottableDiv>
);
};
export default function BelowTabContainer({
channelList = [],
onChannelClick,
onLiveChannelButtonClick,
}) {
const handleChannelClick = (channel, index) => () => {
if (onChannelClick) {
onChannelClick(channel, index);
}
};
return (
<div className={css.container}>
<SpottableDiv
className={css.liveChannelButton}
onClick={onLiveChannelButtonClick}
spotlightId="below-tab-live-channel-button"
>
<span className={css.buttonText}>LIVE CHANNEL</span>
<div className={css.arrowIcon} />
</SpottableDiv>
<div className={css.channelList}>
{channelList.map((channel, index) => (
<ChannelCard
key={`channel-${index}`}
channelName={channel.channelName}
programName={channel.programName}
logoSrc={channel.logoSrc}
thumbnailSrc={channel.thumbnailSrc}
progressPercent={channel.progressPercent}
backgroundColor={channel.backgroundColor}
onClick={handleChannelClick(channel, index)}
spotlightId={`below-tab-channel-${index}`}
/>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,196 @@
@import "../../../../style/CommonStyle.module.less";
@import "../../../../style/utils.module.less";
.container {
.size(@w: 100%, @h: 365px);
.position(@position: fixed, @bottom: 0, @left: 0);
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);
overflow: hidden;
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: flex-start, @direction: column);
gap: 20px;
z-index: 5;
&::before {
content: "";
.position(@position: absolute, @top: 0, @left: 0);
.size(@w: 100%, @h: 100%);
background: linear-gradient(
90deg,
rgba(199, 8, 80, 0.15) 0%,
rgba(199, 8, 80, 0.05) 30%,
transparent 50%,
rgba(199, 8, 80, 0.05) 70%,
rgba(199, 8, 80, 0.15) 100%
);
pointer-events: none;
z-index: -1;
}
}
.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);
background: @COLOR_WHITE;
// 실제 화살표 아이콘이 필요한 경우 background-image로 대체
}
&: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);
}
}
}
.channelList {
width: 100%;
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: flex-start);
gap: 30px;
overflow-x: auto;
overflow-y: hidden;
}
.channelCard {
.size(@w: 470px, @h: 155px);
padding: 18px;
background: rgba(44, 44, 44, 0.85);
backdrop-filter: blur(10px);
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.1);
flex-shrink: 0;
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: flex-start);
gap: 10px;
transition: all 0.3s ease;
.thumbnail {
flex: 1 1 0;
.size(@w: 212px, @h: 119px);
overflow: hidden;
.flex(@display: flex, @justifyCenter: center, @alignCenter: center);
> img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.cardContent {
flex: 1 1 0;
height: 110px;
.flex(@display: flex, @justifyCenter: space-between, @alignCenter: flex-start, @direction: column);
.channelHeader {
width: 100%;
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: center);
gap: 9px;
.logoWrapper {
.size(@w: 40px, @h: 40px);
border-radius: 100px;
.position(@position: relative);
overflow: hidden;
.flex(@display: flex, @justifyCenter: center, @alignCenter: center);
.logo {
max-width: 80%;
max-height: 80%;
object-fit: contain;
}
}
.channelName {
color: #EAEAEA;
font-size: 24px;
font-family: @baseFont;
font-weight: 700;
line-height: 31px;
margin: 0;
}
}
.programName {
width: 100%;
color: #EAEAEA;
font-size: 24px;
font-family: @baseFont;
font-weight: 400;
line-height: 31px;
.elip(@clamp: 1);
}
.progressBarWrapper {
width: 100%;
.flex(@display: flex, @justifyCenter: flex-start, @alignCenter: flex-start);
gap: 10px;
.progressBar {
flex: 1 1 0;
height: 5px;
.position(@position: relative);
overflow: hidden;
.progressTrack {
.size(@w: 100%, @h: 5px);
.position(@position: absolute, @top: 0, @left: 0);
background: #D4D4D4;
}
.progressFill {
height: 5px;
.position(@position: absolute, @top: 0, @left: 0);
background: #7D848C;
transition: width 0.3s ease;
}
}
}
}
&:hover {
background: rgba(44, 44, 44, 0.95);
border-color: rgba(255, 255, 255, 0.2);
}
&:focus {
background: rgba(44, 44, 44, 1);
border-color: @PRIMARY_COLOR_RED;
&::after {
.focused(@boxShadow: 22px, @borderRadius: 12px);
}
}
}

View File

@@ -0,0 +1,562 @@
<div
style={{
width: '100%',
height: '365px',
padding: 60,
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%), rgba(0, 0, 0, 0.56), linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, black 45%, black 100%)',
overflow: 'hidden',
borderTop: '1px rgba(234.30, 234.30, 234.30, 0.80) solid',
flexDirection: 'column',
justifyContent: 'flex-start',
alignItems: 'flex-start',
gap: 20,
display: 'inline-flex',
}}
>
<div
style={{
width: 300,
height: 70,
paddingLeft: 30,
paddingRight: 30,
paddingTop: 20,
paddingBottom: 20,
overflow: 'hidden',
borderRadius: 100,
outline: '1px rgba(234.30, 234.30, 234.30, 0.80) solid',
outlineOffset: '-1px',
justifyContent: 'space-between',
alignItems: 'center',
display: 'inline-flex',
}}
>
<div
style={{
color: 'white',
fontSize: 24,
fontFamily: 'LG Smart UI',
fontWeight: '600',
lineHeight: 35,
wordWrap: 'break-word',
}}
>
LIVE CHANNEL
</div>
<div style={{ width: 26.25, height: 15.63, background: 'white' }} />
</div>
<div
style={{
alignSelf: 'stretch',
justifyContent: 'flex-start',
alignItems: 'flex-start',
gap: 30,
display: 'inline-flex',
}}
>
<div
style={{
width: 470,
padding: 18,
background: 'linear-gradient(0deg, 0%, 100%), #2C2C2C',
borderRadius: 12,
justifyContent: 'flex-start',
alignItems: 'flex-start',
gap: 10,
display: 'flex',
}}
>
<img
style={{
flex: '1 1 0',
height: 119.25,
paddingLeft: 0.41,
paddingRight: 0.41,
paddingTop: 0.51,
paddingBottom: 0.51,
}}
src="https://placehold.co/212x119"
/>
<div
style={{
flex: '1 1 0',
height: 110,
flexDirection: 'column',
justifyContent: 'space-between',
alignItems: 'flex-start',
display: 'inline-flex',
}}
>
<div
style={{
alignSelf: 'stretch',
justifyContent: 'flex-start',
alignItems: 'center',
gap: 9,
display: 'inline-flex',
}}
>
<div
style={{
width: 40,
height: 40,
position: 'relative',
background: 'white',
borderRadius: 100,
}}
>
<img
style={{ width: 20.67, height: 20.67, left: 9.67, top: 9.67, position: 'absolute' }}
src="https://placehold.co/21x21"
/>
</div>
<div
style={{
color: '#EAEAEA',
fontSize: 24,
fontFamily: 'LG Smart UI',
fontWeight: '700',
lineHeight: 31,
wordWrap: 'break-word',
}}
>
QVC
</div>
</div>
<div
style={{
alignSelf: 'stretch',
color: '#EAEAEA',
fontSize: 24,
fontFamily: 'LG Smart UI',
fontWeight: '400',
lineHeight: 31,
wordWrap: 'break-word',
}}
>
JAI Jewelry
</div>
<div
style={{
alignSelf: 'stretch',
justifyContent: 'flex-start',
alignItems: 'flex-start',
gap: 10,
display: 'inline-flex',
}}
>
<div
style={{
flex: '1 1 0',
justifyContent: 'flex-start',
alignItems: 'center',
gap: 11,
display: 'flex',
}}
>
<div style={{ flex: '1 1 0', height: 5, position: 'relative', overflow: 'hidden' }}>
<div
style={{
width: 212,
height: 5,
left: 0,
top: 0,
position: 'absolute',
background: '#D4D4D4',
}}
/>
<div
style={{
width: 137.66,
height: 5,
left: 0,
top: 0,
position: 'absolute',
background: '#7D848C',
}}
/>
</div>
</div>
</div>
</div>
</div>
<div
style={{
width: 470,
padding: 18,
background: 'linear-gradient(0deg, 0%, 100%), #2C2C2C',
borderRadius: 12,
justifyContent: 'flex-start',
alignItems: 'flex-start',
gap: 10,
display: 'flex',
}}
>
<img
style={{
flex: '1 1 0',
height: 119.25,
paddingLeft: 0.41,
paddingRight: 0.41,
paddingTop: 0.51,
paddingBottom: 0.51,
}}
src="https://placehold.co/212x119"
/>
<div
style={{
flex: '1 1 0',
height: 110,
flexDirection: 'column',
justifyContent: 'space-between',
alignItems: 'flex-start',
display: 'inline-flex',
}}
>
<div
style={{
alignSelf: 'stretch',
justifyContent: 'flex-start',
alignItems: 'center',
gap: 9,
display: 'inline-flex',
}}
>
<div
style={{
width: 40,
height: 40,
position: 'relative',
background: 'white',
borderRadius: 100,
}}
>
<img
style={{ width: 30.7, height: 30.7, left: 4.65, top: 4.65, position: 'absolute' }}
src="https://placehold.co/31x31"
/>
</div>
<div
style={{
color: '#EAEAEA',
fontSize: 24,
fontFamily: 'LG Smart UI',
fontWeight: '700',
lineHeight: 31,
wordWrap: 'break-word',
}}
>
ShopLC
</div>
</div>
<div
style={{
alignSelf: 'stretch',
color: '#EAEAEA',
fontSize: 24,
fontFamily: 'LG Smart UI',
fontWeight: '400',
lineHeight: 31,
wordWrap: 'break-word',
}}
>
JAI Jewelry
</div>
<div
style={{
alignSelf: 'stretch',
justifyContent: 'flex-start',
alignItems: 'flex-start',
gap: 10,
display: 'inline-flex',
}}
>
<div
style={{
flex: '1 1 0',
justifyContent: 'flex-start',
alignItems: 'center',
gap: 11,
display: 'flex',
}}
>
<div style={{ flex: '1 1 0', height: 5, position: 'relative', overflow: 'hidden' }}>
<div
style={{
width: 212,
height: 5,
left: 0,
top: 0,
position: 'absolute',
background: '#D4D4D4',
}}
/>
<div
style={{
width: 137.66,
height: 5,
left: 0,
top: 0,
position: 'absolute',
background: '#7D848C',
}}
/>
</div>
</div>
</div>
</div>
</div>
<div
style={{
width: 470,
padding: 18,
background: 'linear-gradient(0deg, 0%, 100%), #2C2C2C',
borderRadius: 12,
justifyContent: 'flex-start',
alignItems: 'flex-start',
gap: 10,
display: 'flex',
}}
>
<img
style={{
flex: '1 1 0',
height: 119.25,
paddingLeft: 0.41,
paddingRight: 0.41,
paddingTop: 0.51,
paddingBottom: 0.51,
}}
src="https://placehold.co/212x119"
/>
<div
style={{
flex: '1 1 0',
height: 110,
flexDirection: 'column',
justifyContent: 'space-between',
alignItems: 'flex-start',
display: 'inline-flex',
}}
>
<div
style={{
alignSelf: 'stretch',
justifyContent: 'flex-start',
alignItems: 'center',
gap: 9,
display: 'inline-flex',
}}
>
<div
style={{
width: 40,
height: 40,
position: 'relative',
background: '#1E77BC',
borderRadius: 100,
}}
>
<img
style={{ width: 30.37, height: 30.37, left: 4.82, top: 4.82, position: 'absolute' }}
src="https://placehold.co/30x30"
/>
</div>
<div
style={{
color: '#EAEAEA',
fontSize: 24,
fontFamily: 'LG Smart UI',
fontWeight: '700',
lineHeight: 31,
wordWrap: 'break-word',
}}
>
HSN
</div>
</div>
<div
style={{
alignSelf: 'stretch',
color: '#EAEAEA',
fontSize: 24,
fontFamily: 'LG Smart UI',
fontWeight: '400',
lineHeight: 31,
wordWrap: 'break-word',
}}
>
JAI Jewelry
</div>
<div
style={{
alignSelf: 'stretch',
justifyContent: 'flex-start',
alignItems: 'flex-start',
gap: 10,
display: 'inline-flex',
}}
>
<div
style={{
flex: '1 1 0',
justifyContent: 'flex-start',
alignItems: 'center',
gap: 11,
display: 'flex',
}}
>
<div style={{ flex: '1 1 0', height: 5, position: 'relative', overflow: 'hidden' }}>
<div
style={{
width: 212,
height: 5,
left: 0,
top: 0,
position: 'absolute',
background: '#D4D4D4',
}}
/>
<div
style={{
width: 137.66,
height: 5,
left: 0,
top: 0,
position: 'absolute',
background: '#7D848C',
}}
/>
</div>
</div>
</div>
</div>
</div>
<div
style={{
width: 470,
padding: 18,
background: 'linear-gradient(0deg, 0%, 100%), #2C2C2C',
borderRadius: 12,
justifyContent: 'flex-start',
alignItems: 'flex-start',
gap: 10,
display: 'flex',
}}
>
<img
style={{
flex: '1 1 0',
height: 119.25,
paddingLeft: 0.41,
paddingRight: 0.41,
paddingTop: 0.51,
paddingBottom: 0.51,
}}
src="https://placehold.co/212x119"
/>
<div
style={{
flex: '1 1 0',
height: 110,
flexDirection: 'column',
justifyContent: 'space-between',
alignItems: 'flex-start',
display: 'inline-flex',
}}
>
<div
style={{
alignSelf: 'stretch',
justifyContent: 'flex-start',
alignItems: 'center',
gap: 9,
display: 'inline-flex',
}}
>
<div
style={{
width: 40,
height: 40,
position: 'relative',
background: 'white',
borderRadius: 100,
}}
>
<img
style={{ width: 20.67, height: 20.67, left: 9.67, top: 9.67, position: 'absolute' }}
src="https://placehold.co/21x21"
/>
</div>
<div
style={{
color: '#EAEAEA',
fontSize: 24,
fontFamily: 'LG Smart UI',
fontWeight: '700',
lineHeight: 31,
wordWrap: 'break-word',
}}
>
QVC
</div>
</div>
<div
style={{
alignSelf: 'stretch',
color: '#EAEAEA',
fontSize: 24,
fontFamily: 'LG Smart UI',
fontWeight: '400',
lineHeight: 31,
wordWrap: 'break-word',
}}
>
JAI Jewelry
</div>
<div
style={{
alignSelf: 'stretch',
justifyContent: 'flex-start',
alignItems: 'flex-start',
gap: 10,
display: 'inline-flex',
}}
>
<div
style={{
flex: '1 1 0',
justifyContent: 'flex-start',
alignItems: 'center',
gap: 11,
display: 'flex',
}}
>
<div style={{ flex: '1 1 0', height: 5, position: 'relative', overflow: 'hidden' }}>
<div
style={{
width: 212,
height: 5,
left: 0,
top: 0,
position: 'absolute',
background: '#D4D4D4',
}}
/>
<div
style={{
width: 137.66,
height: 5,
left: 0,
top: 0,
position: 'absolute',
background: '#7D848C',
}}
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>;