[251113] style: views - TReactPlayer.jsx, ProductVideo.module.less, Pro...

🕐 커밋 시간: 2025. 11. 13. 10:59:09

📊 변경 통계:
  • 총 파일: 5개
  • 추가: +173줄
  • 삭제: -76줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/components/VideoPlayer/TReactPlayer.jsx
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.module.less
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v2.jsx
  ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.jsx
  ~ com.twin.app.shoptime/src/views/MediaPanel/MediaPanel.module.less

🔧 주요 변경 내용:
  • UI 컴포넌트 아키텍처 개선
  • 중간 규모 기능 개선
This commit is contained in:
2025-11-13 10:59:11 +09:00
parent b8cf24dc0e
commit 74619cba4e
5 changed files with 173 additions and 76 deletions

View File

@@ -33,6 +33,14 @@ export default function TReactPlayer({
if (videoRef) {
const videoNode = playerRef.current.getInternalPlayer();
videoRef(videoNode);
const iframeEl =
typeof playerRef.current?.getInternalPlayer === "function"
? playerRef.current.getInternalPlayer("iframe")
: null;
if (iframeEl) {
iframeEl.setAttribute("tabIndex", "-1");
iframeEl.setAttribute("aria-hidden", "true");
}
if (
videoNode.tagName &&
!Object.prototype.hasOwnProperty.call(videoNode, "proportionPlayed")

View File

@@ -98,16 +98,21 @@
}
}
.youtubeSafe {
:global(.react-player),
:global(.react-player iframe) {
pointer-events: none !important;
}
}
// 전체화면 모드 (ProductVideoV2 엔터키 토글)
.fullscreen {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 1920px !important;
height: 1080px !important;
max-width: 1920px !important;
z-index: 99999 !important; // 최상위 레이어
margin: 0 !important;
width: 100vw !important;
height: 100vh !important;
z-index: 99999 !important; // 전체 화면 기준
border-radius: 0 !important;
background-color: @COLOR_BLACK;
@@ -115,12 +120,39 @@
border-radius: 0;
}
// 전체화면 모드에서 포커스가 밖으로 나가지 않도록
// Spotlight container가 이 영역만 관리
// 전체화면 모드에서 포커스가 벗어나지 않도록
// Spotlight container는 별도 관리
}
.fullscreenPlayer {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
border-radius: 0 !important;
z-index: 100000 !important;
background-color: @COLOR_BLACK;
:global(.videoPlayer) {
width: 100% !important;
height: 100% !important;
background-color: @COLOR_BLACK;
}
:global(video) {
width: 100% !important;
height: 100% !important;
object-fit: cover;
background-color: @COLOR_BLACK;
}
:global(.react-player),
:global(.react-player iframe) {
width: 100% !important;
height: 100% !important;
background-color: @COLOR_BLACK;
}
}
.videoThumbnailContainer {
@@ -158,8 +190,8 @@
position: fixed;
top: 0;
left: 0;
width: 1920px;
height: 1080px;
width: 100vw;
height: 100vh;
z-index: 99999;
background-color: @COLOR_BLACK;

View File

@@ -548,7 +548,7 @@ export function ProductVideoV2({
e.preventDefault();
e.stopPropagation();
// toggleOverlayVisibility();
toggleOverlayVisibility();
},
[isPlaying, isFullscreen, toggleOverlayVisibility]
);
@@ -558,23 +558,29 @@ export function ProductVideoV2({
(e) => {
if (!isPlaying || !isFullscreen) return;
// ESC 키만 오버레이 토글로 처리
const isOverlayVisible = mediaOverlayState.controls?.visible;
if (e.key === 'Escape' || e.keyCode === 27) {
debugLog('🖥️ [Fullscreen Container] ESC 키 - 오버레이 토글 실행');
debugLog('[Fullscreen Container] ESC key - toggling overlay');
e.preventDefault();
e.stopPropagation();
toggleOverlayVisibility();
return;
}
// Enter 키는 기본 동작 허용 (포커스된 요소의 동작 수행)
if (e.key === 'Enter' || e.keyCode === 13) {
debugLog('🖥️ [Fullscreen Container] Enter 키 - 포커스된 요소 동작 허용');
// Enter 키는 preventDefault하지 않고 기본 동작 허용
if (!isOverlayVisible) {
debugLog('[Fullscreen Container] Enter key - overlay hidden, showing controls');
e.preventDefault();
e.stopPropagation();
toggleOverlayVisibility();
return;
}
debugLog('[Fullscreen Container] Enter key - overlay visible, allow default behavior');
}
},
[isPlaying, isFullscreen, toggleOverlayVisibility]
[isPlaying, isFullscreen, toggleOverlayVisibility, mediaOverlayState.controls?.visible]
);
// 마우스 다운 (클릭) 이벤트 - capture phase에서 처리
@@ -764,6 +770,17 @@ export function ProductVideoV2({
}
}, [isFullscreen, isPlaying]);
useEffect(() => {
if (!isPlaying) return;
if (isFullscreen && isYoutube) {
videoPlayerRef.current?.showControls?.();
dispatch(setMediaControlShow());
dispatch(resetMediaAutoClose());
dispatch(startMediaAutoClose(5000));
}
}, [dispatch, isFullscreen, isPlaying, isYoutube]);
useEffect(() => {
const wasFullscreen = prevFullscreenRef.current;
if (wasFullscreen && !isFullscreen && isPlaying) {
@@ -924,11 +941,19 @@ export function ProductVideoV2({
const host = ensurePortalHost();
if (!host) return null;
const shouldDisableIframeInteraction = isYoutube && isFullscreen;
const wrapperClasses = [
css.videoPlayerWrapper,
isFullscreen ? css.fullscreenPlayer : '',
shouldDisableIframeInteraction ? css.youtubeSafe : '',
]
.filter(Boolean)
.join(' ');
return ReactDOM.createPortal(
<div
ref={videoPlayerWrapperRef}
className={`${css.videoPlayerWrapper} ${isFullscreen ? css.fullscreenPlayer : ''}`}
className={wrapperClasses}
onMouseMove={handleUserActivity}
onTouchMove={handleUserActivity}
onWheel={handleUserActivity}
@@ -1113,3 +1138,7 @@ export function ProductVideoV2({
</>
);
}

View File

@@ -100,6 +100,14 @@ const MediaPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
}
}, [panelInfo?.isPaused, panelInfo?.modal]);
useEffect(() => {
if (!videoPlayer.current) return;
if (!isYoutube) return;
if (panelInfo?.modal) return;
videoPlayer.current.showControls?.();
}, [isYoutube, panelInfo?.modal]);
const getPlayer = useCallback((ref) => {
videoPlayer.current = ref;
}, []);
@@ -463,6 +471,7 @@ const MediaPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
// minimized 상태일 때는 spotlightRestrict 해제 (포커스 이동 허용)
const containerSpotlightRestrict = panelInfo.isMinimized ? 'none' : 'self-only';
const shouldDisableIframeInteraction = isYoutube && !panelInfo.modal;
return (
<TPanel
@@ -477,12 +486,18 @@ const MediaPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
spotlightId="spotlightId-media-video-container"
>
{currentPlayingUrl && (
<div
className={classNames(
css.videoPlayerWrapper,
shouldDisableIframeInteraction && css.youtubeSafe
)}
>
<VideoPlayer
setApiProvider={getPlayer}
disabled={panelInfo.modal}
onEnded={onEnded}
noAutoPlay={false}
noAutoShowMediaControls={panelInfo.modal} // modal 상태에서는 자동으로 controls 보여주
noAutoShowMediaControls={panelInfo.modal} // modal 모드에서는 자동으로 controls 올라오 도록 설정
autoCloseTimeout={3000}
onBackButton={onClickBack}
onClick={onVideoClick}
@@ -503,14 +518,14 @@ const MediaPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
videoComponent={
(typeof window === 'object' && !window.PalmSystem) || isYoutube ? TReactPlayer : Media
}
// VideoOverlay props - 단순
// VideoOverlay props - 간소
type="MEDIA"
panelInfo={panelInfo}
captionEnable={false}
setIsSubtitleActive={setIsSubtitleActive}
setCurrentTime={setCurrentTime}
setIsVODPaused={setIsVODPaused}
// PlayerOverlayContents props ( 배열로 전달하여 null 에러 방지)
// PlayerOverlayContents props ( 배열로 전달하여 null 처리)
playListInfo={[]}
selectedIndex={0}
videoVerticalVisible={false}
@@ -528,6 +543,7 @@ const MediaPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
window.PalmSystem &&
currentSubtitleUrl && <track kind="subtitles" src={currentSubtitleUrl} default />}
</VideoPlayer>
</div>
)}
</Container>
</TPanel>

View File

@@ -60,3 +60,15 @@
}
}
}
.videoPlayerWrapper {
width: 100%;
height: 100%;
}
.youtubeSafe {
:global(.react-player),
:global(.react-player iframe) {
pointer-events: none !important;
}
}