[FeaturedBrandsPanel] LiveVideoCard 추가 및 반영

Detail Notes :

1. LiveVideoCard.jsx 추가 및 반영
This commit is contained in:
younghoon100.park
2024-01-30 18:01:22 +09:00
parent 696a5fec18
commit 645827ce54
6 changed files with 260 additions and 84 deletions

View File

@@ -2,9 +2,11 @@ import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import SectionTitle from "../../components/SectionTitle/SectionTitle";
import TPanel from "../../components/TPanel/TPanel"; import TPanel from "../../components/TPanel/TPanel";
import { getBrandLayoutInfo, getBrandLiveChannelInfo } from "../../features/brand/brandsSlice"; import {
getBrandLayoutInfo,
getBrandLiveChannelInfo,
} from "../../features/brand/brandsSlice";
import Banner from "./Banner/Banner"; import Banner from "./Banner/Banner";
import css from "./FeaturedBrandsPanel.module.less"; import css from "./FeaturedBrandsPanel.module.less";
import LiveChannels from "./LiveChannels/LiveChannels"; import LiveChannels from "./LiveChannels/LiveChannels";
@@ -19,9 +21,13 @@ export default function FeaturedBrandsPanel() {
const brandInfo = useSelector((state) => state.brand.brandInfoData.brandInfo); const brandInfo = useSelector((state) => state.brand.brandInfoData.brandInfo);
const brandTopImgInfo = useSelector((state) => state.brand.brandLayoutInfoData.brandTopImgInfo); const brandTopImgInfo = useSelector(
(state) => state.brand.brandLayoutInfoData.brandTopImgInfo
);
const brandChanInfo = useSelector((state) => state.brand.brandLiveChannelInfoData.brandChanInfo); const brandChanInfo = useSelector(
(state) => state.brand.brandLiveChannelInfoData.brandChanInfo
);
const brandChannelCnt = useSelector( const brandChannelCnt = useSelector(
(state) => state.brand.brandLiveChannelInfoData.brandChannelCnt (state) => state.brand.brandLiveChannelInfoData.brandChannelCnt
); );
@@ -56,12 +62,18 @@ export default function FeaturedBrandsPanel() {
)} )}
{selectedBrandInfo && ( {selectedBrandInfo && (
<Banner selectedBrandInfo={selectedBrandInfo} brandTopImgInfo={brandTopImgInfo} /> <Banner
selectedBrandInfo={selectedBrandInfo}
brandTopImgInfo={brandTopImgInfo}
/>
)} )}
<div className={css.sectionContainer}> <div className={css.sectionContainer}>
{brandChanInfo && ( {brandChanInfo && (
<LiveChannels brandChanInfo={brandChanInfo[0]} brandChannelCnt={brandChannelCnt} /> <LiveChannels
brandChanInfo={brandChanInfo[0]}
brandChannelCnt={brandChannelCnt}
/>
)} )}
</div> </div>
</TPanel> </TPanel>

View File

@@ -1,16 +1,13 @@
import Scroller from "@enact/sandstone/Scroller"; import Scroller from "@enact/sandstone/Scroller";
import SpotlightContainerDecorator from "@enact/spotlight/SpotlightContainerDecorator";
import Spottable from "@enact/spotlight/Spottable";
import SectionTitle from "../../../components/SectionTitle/SectionTitle"; import SectionTitle from "../../../components/SectionTitle/SectionTitle";
import TItemCard from "../../../components/TItemCard/TItemCard"; import TItemCard from "../../../components/TItemCard/TItemCard";
import { $L } from "../../../utils/helperMethods"; import { $L } from "../../../utils/helperMethods";
import LiveVideoCard from "../LiveVideoCard/LiveVideoCard";
import css from "./LiveChannels.module.less"; import css from "./LiveChannels.module.less";
const LIVE_CHANNELS_STRING = "LIVE CHANNELS"; const LIVE_CHANNELS_STRING = "LIVE CHANNELS";
const SpottableComponent = Spottable("div");
export default function LiveChannels({ brandChanInfo, brandChannelCnt }) { export default function LiveChannels({ brandChanInfo, brandChannelCnt }) {
const { const {
alamDispFlag, alamDispFlag,
@@ -60,65 +57,87 @@ export default function LiveChannels({ brandChanInfo, brandChannelCnt }) {
// } // }
return ( return (
<div className={css.container}> <>
<SectionTitle title={$L(LIVE_CHANNELS_STRING)} /> {brandChannelCnt > 0 && (
<div className={css.container}>
<SectionTitle title={$L(LIVE_CHANNELS_STRING)} />
{brandChannelCnt === 1 && ( {brandChannelCnt === 1 && (
<div> <div>
<SpottableComponent>Live 영상</SpottableComponent> <LiveVideoCard
<Scroller endTime={endDt}
className={css.scroller} liveChannelCount={brandChannelCnt}
direction="vertical" startTime={strtDt}
noScrollByWheel={true} thumbnailSource={thumbnailImgPath}
scrollMode="translate" title={showNm}
verticalScrollbar="hidden" />
> <Scroller
<ul> className={css.scroller}
{brandProductInfo && direction="vertical"
brandProductInfo.map( noScrollByWheel={true}
({ prdtId, prdtImgUrl, prdtNm, priceInfo, soldoutFlag }) => { scrollMode="translate"
return ( verticalScrollbar="hidden"
<TItemCard >
key={prdtId} <ul>
imageAlt={prdtNm} {brandProductInfo &&
imageSource={prdtImgUrl} brandProductInfo.map(
priceInfo={priceInfo} ({
productId={prdtId} prdtId,
productName={prdtNm} prdtImgUrl,
soldoutFlag={soldoutFlag} prdtNm,
type="horizontal" priceInfo,
/> soldoutFlag,
); }) => {
} return (
)} <TItemCard
</ul> key={prdtId}
</Scroller> imageAlt={prdtNm}
imageSource={prdtImgUrl}
priceInfo={priceInfo}
productId={prdtId}
productName={prdtNm}
soldoutFlag={soldoutFlag}
type="horizontal"
/>
);
}
)}
</ul>
</Scroller>
</div>
)}
{brandChannelCnt > 1 && (
<div>
{"brandChannelCnt(영상의 수)가 1보다 클 경우, type = vertical"}
<ul>
{brandProductInfo &&
brandProductInfo.map(
({
prdtId,
prdtImgUrl,
prdtNm,
priceInfo,
soldoutFlag,
}) => {
return (
<TItemCard
key={prdtId}
imageAlt={prdtNm}
imageSource={prdtImgUrl}
priceInfo={priceInfo}
productId={prdtId}
productName={prdtNm}
soldoutFlag={soldoutFlag}
/>
);
}
)}
</ul>
</div>
)}
</div> </div>
)} )}
</>
{brandChannelCnt > 1 && (
<div>
{"brandChannelCnt(영상의 수)가 1보다 클 경우, type = vertical"}
<ul>
{brandProductInfo &&
brandProductInfo.map(
({ prdtId, prdtImgUrl, prdtNm, priceInfo, soldoutFlag }) => {
return (
<TItemCard
key={prdtId}
imageAlt={prdtNm}
imageSource={prdtImgUrl}
priceInfo={priceInfo}
productId={prdtId}
productName={prdtNm}
soldoutFlag={soldoutFlag}
/>
);
}
)}
</ul>
</div>
)}
</div>
); );
} }

View File

@@ -12,23 +12,6 @@
display: flex; display: flex;
width: @globalWidth; width: @globalWidth;
// live container
> div:nth-child(1) {
.size(@w: 1002px, @h: 564px);
margin-right: 18px;
background-color: gainsboro;
border: solid 1px transparent;
border-radius: 12px;
/* focused */
&:focus-within {
// live container
border: solid 1px @PRIMARY_COLOR_RED;
box-shadow: inset 0 0 0 4px @PRIMARY_COLOR_RED,
0 0 50px 0 rgba(0, 0, 0, 0.5);
}
}
// product scroll container // product scroll container
> .scroller { > .scroller {
width: 720px; width: 720px;

View File

@@ -0,0 +1,79 @@
import { memo, useCallback, useState } from "react";
import Spottable from "@enact/spotlight/Spottable";
import IcWarning from "../../../../assets/icon/ic-warning.svg";
import IcLiveShow from "../../../../assets/icon/tag/tag-liveshow.svg";
import VideoOverlayWithPhoneNumber from "../../../components/VideoOverlayWithPhoneNumber/VideoOverlayWithPhoneNumber";
import { $L } from "../../../utils/helperMethods";
import css from "./LiveVideoCard.module.less";
const SpottableComponent = Spottable("div");
const LIVE_SHOW_STRING = "Live Show";
const WARNING_STRING = "Warning";
const WARNING_MESSAGE =
"This program was previously recorded. Offers, pricing, and availability may have changed.";
export default memo(function LiveVideoCard({
endTime,
liveChannelCount,
onVideoCardBlur,
onVideoCardClick,
onVideoCardFocus,
startTime,
thumbnailSource,
title,
...rest
}) {
const [isFocused, setIsFocused] = useState(false);
const handleBlur = useCallback(() => {
setIsFocused(false);
onVideoCardBlur && onVideoCardBlur();
}, [onVideoCardBlur]);
const handleClick = useCallback(() => {
onVideoCardClick && onVideoCardClick();
}, [onVideoCardClick]);
const handleFocus = useCallback(() => {
setIsFocused(true);
onVideoCardFocus && onVideoCardFocus();
}, [onVideoCardFocus]);
return (
<SpottableComponent
className={css.container}
{...rest}
onBlur={handleBlur}
onClick={handleClick}
onFocus={handleFocus}
>
<img src={thumbnailSource} alt={title} />
<div>
<div>
<img src={IcLiveShow} alt={LIVE_SHOW_STRING} />
<div>
<h3>{title}</h3>
<time>
{startTime} ~ {endTime}
</time>
</div>
</div>
{liveChannelCount && liveChannelCount === 1 && (
<div>
<img src={IcWarning} alt={WARNING_STRING} />
<span>{$L(WARNING_MESSAGE)}</span>
</div>
)}
</div>
<VideoOverlayWithPhoneNumber
className={css.callToOrder}
show={isFocused}
/>
</SpottableComponent>
);
});

View File

@@ -0,0 +1,77 @@
@import "../../../style/CommonStyle.module.less";
@import "../../../style/utils.module.less";
.container {
/* normal */
position: relative;
z-index: 10;
.size(@w: 1002px, @h: 564px);
margin-right: 18px;
border: 4px solid transparent;
border-radius: 12px;
overflow: hidden;
// image(thumbnail) area
> img {
width: 100%;
}
// contents area
> div:nth-child(2) {
.position(@position: absolute, @top: 0, @right: 0, @left: 0);
.flex(@direction: column, @justifyCenter: space-between, @alignCenter: flex-start);
.size(@w: 100%, @h: 100%);
background-image: linear-gradient(to top, transparent 55%, @COLOR_BLACK);
// top part of video
> div:nth-child(1) {
.flex(@justifyCenter: flex-start);
gap: 12px;
width: 100%;
padding: 18px;
color: @COLOR_WHITE;
img {
.size(@w: 108px, @h: 48px);
}
div {
h3 {
.font(@fontFamily: @baseFontBold, @fontSize: 30px);
.elip(@clamp:1);
word-break: break-all;
}
time {
.font(@fontFamily: @baseFont, @fontSize: 24px);
}
}
}
// bottom part of video
> div:nth-child(2) {
.flex(@justifyCenter: flex-start);
gap: 12px;
padding: 6px 18px 18px;
.size(@w: 100%, @h: 54px);
background-color: rgba(0, 0, 0, 0.9);
.font(@fontFamily: @baseFont, @fontSize: 20px);
color: @COLOR_GRAY04;
img {
.size(@w: 18px, @h: 18px);
}
}
}
/* focused */
&:focus-within {
border: 4px solid @PRIMARY_COLOR_RED;
.focusDropShadow();
}
}
.callToOrder {
bottom: 64px;
left: 18px;
}

View File

@@ -0,0 +1,6 @@
{
"main": "LiveVideoCard.jsx",
"styles": [
"LiveVideoCard.module.less"
]
}