[250929] feat: ProductAllSection 비디오 기능 수정

This commit is contained in:
2025-09-29 11:47:58 +09:00
parent 15c1176861
commit 9999a59fc7
3 changed files with 111 additions and 30 deletions

View File

@@ -1028,3 +1028,12 @@
}
}
}
// 비디오 컨테이너 스타일 (modal 영역 지정용)
.videoContainer {
position: relative;
width: 100%;
height: auto;
// PlayerPanel 모달이 이 영역에서만 재생되도록 설정
z-index: 1;
}

View File

@@ -1,8 +1,9 @@
import React, { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import React, { useCallback, useMemo, useState, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import Spottable from '@enact/spotlight/Spottable';
import { startVideoPlayer } from '../../../../actions/playActions';
import { startVideoPlayer, finishVideoPreview } from '../../../../actions/playActions';
import CustomImage from '../../../../components/CustomImage/CustomImage';
import { panel_names } from '../../../../utils/Config';
import playImg from '../../../../../assets/images/btn/btn-play-thumb-nor.png';
import css from './ProductVideo.module.less';
@@ -11,10 +12,51 @@ const SpottableComponent = Spottable('div');
export default function ProductVideo({ productInfo, videoUrl, thumbnailUrl }) {
const dispatch = useDispatch();
// Indicator.jsx의 canPlayVideo 로직 이식
// PlayerPanel 상태 체크를 위한 selectors 추가 (Indicator.jsx와 동일)
const panels = useSelector((state) => state.panels.panels);
const [isLaunchedFromPlayer, setIsLaunchedFromPlayer] = useState(false);
const [focused, setFocused] = useState(false);
const topPanel = panels[panels.length - 1];
// Indicator.jsx의 PlayerPanel 상태 체크 로직 추가
useEffect(() => {
if (
topPanel &&
topPanel.name === panel_names.PLAYER_PANEL &&
topPanel.panelInfo.modal === false
) {
return; // PlayerPanel이 전체화면 모드일 때는 처리하지 않음
}
}, [topPanel]);
// Indicator.jsx의 canPlayVideo 로직 완전 이식 (selectedIndex === 0 조건 포함)
const canPlayVideo = useMemo(() => {
return Boolean(productInfo?.prdtMediaUrl && videoUrl);
}, [productInfo, videoUrl]);
return Boolean(productInfo?.prdtMediaUrl);
}, [productInfo]);
// Indicator.jsx의 modalClassNameChange 로직 추가
const modalClassNameChange = useCallback(() => {
if (focused) {
return css.videoModal;
}
return '';
}, [focused]);
// focus 이벤트 핸들러 추가 (Indicator.jsx와 동일)
const videoContainerOnFocus = useCallback(() => {
if (canPlayVideo) {
setFocused(true);
}
}, [canPlayVideo]);
const videoContainerOnBlur = useCallback(() => {
if (canPlayVideo) {
setFocused(false);
// ProductVideo에서 포커스가 벗어나면 비디오 재생 종료
dispatch(finishVideoPreview());
}
}, [canPlayVideo, dispatch]);
// Indicator.jsx의 handleVideoOnClick 로직 완전히 이식
const handleVideoClick = useCallback(() => {
@@ -37,15 +79,18 @@ export default function ProductVideo({ productInfo, videoUrl, thumbnailUrl }) {
prdtNm: productInfo?.prdtNm,
thumbnailUrl: productInfo?.thumbnailUrl960,
shptmBanrTpNm: 'MEDIA',
// Indicator.jsx와 동일하게: 클릭 시 전체화면 모드
modal: false,
modalContainerId: 'product-video-player', // 우리 컴포넌트의 spotlightId
modalClassName: css.videoModal,
spotlightDisable: false,
modal: true,
modalContainerId: 'product-video-player',
modalClassName: modalClassNameChange(),
spotlightDisable: true,
})
);
}
}, [dispatch, productInfo, canPlayVideo]);
if (isLaunchedFromPlayer) {
setIsLaunchedFromPlayer(false);
}
}, [dispatch, productInfo, canPlayVideo, isLaunchedFromPlayer, modalClassNameChange]);
if (!canPlayVideo) return null;
@@ -53,6 +98,8 @@ export default function ProductVideo({ productInfo, videoUrl, thumbnailUrl }) {
<SpottableComponent
className={css.videoContainer}
onClick={handleVideoClick}
onFocus={videoContainerOnFocus}
onBlur={videoContainerOnBlur}
spotlightId="product-video-player"
aria-label={`${productInfo?.prdtNm} 동영상 재생`}
>

View File

@@ -1,19 +1,37 @@
@import "../../../../style/CommonStyle.module.less";
@import "../../../../style/utils.module.less";
.videoContainer {
position: relative;
width: 100%;
margin-bottom: 20px;
width: 1114px; // ProductDetail과 동일한 고정 크기
max-width: 1114px;
height: 670px; // ProductDetail과 동일한 고정 높이
margin-bottom: 30px; // ProductDetail과 동일한 간격
cursor: pointer;
border-radius: 8px;
overflow: hidden;
background-color: rgba(255, 255, 255, 1);
border-radius: 12px;
box-sizing: border-box;
padding: 6px; // 포커스 테두리를 위한 공간
.videoThumbnailWrapper {
position: relative;
width: 100%;
width: 658px; // ProductDetail과 동일한 썸네일 크기
height: 610px; // ProductDetail과 동일한 썸네일 높이
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto;
.videoThumbnail {
width: 100%;
height: auto;
display: block;
max-width: 100%;
max-height: 100%;
margin: 0;
padding: 0;
border: none;
object-fit: contain; // 비율 유지하며 컨테이너에 맞춤
background-color: @COLOR_WHITE;
border-radius: 8px;
transition: all 0.2s ease;
}
.playButtonOverlay {
@@ -38,22 +56,29 @@
}
}
// Spotlight 포커스 스타일
// ProductDetail과 동일한 포커스 스타일
&:focus {
outline: 2px solid #0078d4;
outline-offset: 2px;
&::after {
content: "";
position: absolute;
top: -6px;
left: -6px;
right: -6px;
bottom: -6px;
z-index: 19;
border: 6px solid @PRIMARY_COLOR_RED;
box-shadow: 0 0 22px 0 rgba(0, 0, 0, 0.5);
border-radius: 12px;
}
.videoThumbnailWrapper .videoThumbnail {
transform: scale(1.015); // 가로세로 10px 정도 확대 효과
}
.playButtonOverlay {
opacity: 0.8;
}
}
// webOS TV 환경에서의 포커스 스타일
&.spottable:focus {
outline: 3px solid #ffffff;
outline-offset: 3px;
box-shadow: 0 0 0 6px rgba(255, 255, 255, 0.3);
}
}
// 동영상 모달 클래스 (PlayerPanel에서 사용)