[251012] fix: PlayerPanel turnback , Focus Policy

🕐 커밋 시간: 2025. 10. 12. 22:02:25

📊 변경 통계:
  • 총 파일: 12개
  • 추가: +239줄
  • 삭제: -78줄

📝 수정된 파일:
  ~ com.twin.app.shoptime/src/components/VideoPlayer/VideoPlayer.js
  ~ com.twin.app.shoptime/src/views/DetailPanel/DetailPanelSkeleton/DetailPanelSkeleton.module.less
  ~ com.twin.app.shoptime/src/views/DetailPanel/ProductAllSection/ProductAllSection.module.less
  ~ com.twin.app.shoptime/src/views/IntroPanel/IntroPanel.new.module.less
  ~ com.twin.app.shoptime/src/views/MyPagePanel/MyPageSub/TermsOfService/OptionalTermsInfo.module.less
  ~ com.twin.app.shoptime/src/views/MyPagePanel/MyPageSub/TermsOfService/TermsOfService.module.less
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx
  ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/ShopNowButton.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/views/UserReview/components/UserReviewsList.module.less

🔧 함수 변경 내용:
  📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerOverlay/PlayerOverlayContents.jsx (javascript):
    🔄 Modified: SpotlightContainerDecorator()
  📄 com.twin.app.shoptime/src/views/PlayerPanel/PlayerTabContents/v2/TabContainer.v2.jsx (javascript):
    🔄 Modified: Spottable()

🔧 주요 변경 내용:
  • UI 컴포넌트 아키텍처 개선
  • API 서비스 레이어 개선
This commit is contained in:
2025-10-12 22:02:30 +09:00
parent aa811980dd
commit 469dd62444
12 changed files with 239 additions and 78 deletions

View File

@@ -859,28 +859,6 @@ const VideoPlayerBase = class extends React.Component {
}
}
// TabContainerV2와 mediaControls 동기화
if (
this.props.tabContainerVersion === 2 &&
this.props.belowContentsVisible !== undefined &&
prevProps.belowContentsVisible !== this.props.belowContentsVisible
) {
console.log('[VideoPlayer] belowContentsVisible 변경 감지:', {
prev: prevProps.belowContentsVisible,
current: this.props.belowContentsVisible,
mediaControlsVisible: this.state.mediaControlsVisible,
});
if (this.props.belowContentsVisible && !this.state.mediaControlsVisible) {
// TabContainerV2가 표시될 때 controls도 표시
console.log('[VideoPlayer] TabContainerV2 표시 - controls 강제 표시');
this.showControls();
} else if (!this.props.belowContentsVisible && this.state.mediaControlsVisible) {
// TabContainerV2가 숨겨질 때 controls도 숨김
console.log('[VideoPlayer] TabContainerV2 숨김 - controls 숨김');
this.hideControls();
}
}
if (
(!this.state.mediaControlsVisible &&
prevState.mediaControlsVisible !== this.state.mediaControlsVisible) ||
@@ -1044,12 +1022,6 @@ const VideoPlayerBase = class extends React.Component {
// detection of when "more" is pressed vs when the state is updated is mismatched. Using an
// instance variable that's only set and used for this express purpose seems cleanest.
// TabContainerV2가 표시 중이면 자동으로 닫지 않음
if (this.props.tabContainerVersion === 2 && this.props.belowContentsVisible) {
console.log('[VideoPlayer] TabContainerV2 표시 중 - autoClose 비활성화');
return;
}
if (this.props.autoCloseTimeout && !this.props.sideContentsVisible) {
this.autoCloseJob.startAfter(this.props.autoCloseTimeout);
}
@@ -1981,15 +1953,6 @@ const VideoPlayerBase = class extends React.Component {
return;
}
// tabContainerVersion === 2일 때 belowContentsVisible도 함께 토글
if (this.props.tabContainerVersion === 2 && this.props.setBelowContentsVisible) {
const willShowControls = !this.state.mediaControlsVisible;
console.log('[VideoPlayer] 클릭 - 상태 동기화 토글:', willShowControls);
// belowContentsVisible을 먼저 변경 (componentDidUpdate에서 mediaControls 동기화됨)
this.props.setBelowContentsVisible(willShowControls);
}
this.toggleControls();
};

View File

@@ -157,7 +157,13 @@
width: 100%;
display: flex;
flex-direction: column;
gap: 10px;
> * {
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
}
}
.skeletonTitle {
@@ -190,8 +196,14 @@
.skeletonButtons {
width: 100%;
display: flex;
gap: 10px;
align-items: center;
> * {
margin-right: 10px;
&:last-child {
margin-right: 0;
}
}
}
.skeletonShopButton {
@@ -218,7 +230,13 @@
width: 100%;
display: flex;
flex-direction: column;
gap: 5px;
> * {
margin-bottom: 5px;
&:last-child {
margin-bottom: 0;
}
}
}
.skeletonActionButton {
@@ -248,7 +266,13 @@
width: 100%;
display: flex;
flex-direction: column;
gap: 15px;
> * {
margin-bottom: 15px;
&:last-child {
margin-bottom: 0;
}
}
}
.skeletonDescTitle {
@@ -274,7 +298,13 @@
width: 100%;
display: flex;
flex-direction: column;
gap: 20px;
> * {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
}
.skeletonReviewTitle {
@@ -286,8 +316,14 @@
.skeletonReviewItem {
width: 100%;
display: flex;
gap: 15px;
align-items: flex-start;
> * {
margin-right: 15px;
&:last-child {
margin-right: 0;
}
}
}
.skeletonReviewAvatar {
@@ -302,7 +338,13 @@
flex: 1;
display: flex;
flex-direction: column;
gap: 10px;
> * {
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
}
}
.skeletonReviewHeader {
@@ -328,7 +370,13 @@
width: 100%;
display: flex;
flex-direction: column;
gap: 25px;
> * {
margin-bottom: 25px;
&:last-child {
margin-bottom: 0;
}
}
}
.skeletonYouMayLikeTitle {
@@ -340,16 +388,28 @@
.skeletonProductGrid {
width: 100%;
display: flex;
gap: 20px;
justify-content: flex-start;
> * {
margin-right: 20px;
&:last-child {
margin-right: 0;
}
}
}
.skeletonProductCard {
width: 300px;
display: flex;
flex-direction: column;
gap: 10px;
flex-shrink: 0;
> * {
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
}
}
.skeletonProductImage {

View File

@@ -140,7 +140,7 @@
// 스크롤바 thumb에 hover 효과 적용
&:hover::-webkit-scrollbar-thumb {
background: #c72054;
background: @PRIMARY_COLOR_RED;
}
}
@@ -280,15 +280,24 @@
}
// 포커스 상태 추가
&:focus,
&.active {
&:focus {
background: @PRIMARY_COLOR_RED !important; // 포커스시 빨간색 배경
outline: 2px solid @PRIMARY_COLOR_RED !important;
animation: focusShadowPulse 0.4s ease-out !important;
.shopByMobileText {
color: white !important; // 포커스시에도 텍스트는 흰색 유지
}
}
&.active {
background: @PRIMARY_COLOR_RED !important;
outline: 2px solid @PRIMARY_COLOR_RED !important;
.shopByMobileText {
color: white !important;
}
}
}
.favoriteBtnWrapper {
@@ -398,9 +407,13 @@
font-weight: 400; // Bold에서 Regular로 변경
line-height: 35px;
&:focus,
&:focus {
background: @PRIMARY_COLOR_RED; // 포커스시만 빨간색
animation: focusShadowPulse 0.4s ease-out;
}
&.active {
background: #c72054; // 포커스시만 빨간색
background: @PRIMARY_COLOR_RED;
}
}
@@ -412,7 +425,8 @@
margin-top: 10px;
&:focus {
background: #c72054;
background: @PRIMARY_COLOR_RED;
animation: focusShadowPulse 0.4s ease-out;
}
}
@@ -611,7 +625,7 @@
// 스크롤바 thumb에 hover 효과 적용
&:hover::-webkit-scrollbar-thumb {
background: #c72054;
background: @PRIMARY_COLOR_RED;
}
}
@@ -750,6 +764,7 @@
&:focus {
background: @PRIMARY_COLOR_RED !important; // 포커스시 빨간색 배경
outline: 2px solid @PRIMARY_COLOR_RED !important;
animation: focusShadowPulse 0.4s ease-out !important;
.shopByMobileText {
color: white !important; // 포커스시에도 텍스트는 흰색 유지
@@ -864,7 +879,8 @@
line-height: 35px;
&:focus {
background: #c72054; // 포커스시만 빨간색
background: @PRIMARY_COLOR_RED; // 포커스시만 빨간색
animation: focusShadowPulse 0.4s ease-out;
}
}
@@ -876,7 +892,8 @@
margin-top: 10px;
&:focus {
background: #c72054;
background: @PRIMARY_COLOR_RED;
animation: focusShadowPulse 0.4s ease-out;
}
}
@@ -999,7 +1016,13 @@
display: flex;
justify-content: flex-start;
align-items: center;
gap: 10px;
> * {
margin-right: 10px;
&:last-child {
margin-right: 0;
}
}
}
.buyNowButton {
@@ -1026,6 +1049,7 @@
&:focus {
background: @PRIMARY_COLOR_RED !important;
outline: 2px solid @PRIMARY_COLOR_RED !important;
animation: focusShadowPulse 0.4s ease-out !important;
.buyNowText {
color: white !important;
@@ -1057,6 +1081,7 @@
&:focus {
background: @PRIMARY_COLOR_RED !important;
outline: 2px solid @PRIMARY_COLOR_RED !important;
animation: focusShadowPulse 0.4s ease-out !important;
.addToCartText {
color: white !important;
@@ -1072,3 +1097,16 @@
// PlayerPanel 모달이 이 영역에서만 재생되도록 설정
z-index: 1;
}
// 포커스 Pulse 애니메이션 - Box Shadow 방식
@keyframes focusShadowPulse {
0% {
box-shadow: 0 0 0 0 @PRIMARY_COLOR_RED;
}
50% {
box-shadow: 0 0 0 6px fade(@PRIMARY_COLOR_RED, 50%);
}
100% {
box-shadow: 0 0 0 0 @PRIMARY_COLOR_RED;
}
}

View File

@@ -26,26 +26,44 @@
flex-direction: column;
justify-content: space-between;
align-items: center;
gap: 40px;
display: inline-flex;
background-color: @BG_COLOR_03;
> * {
margin-bottom: 40px;
&:last-child {
margin-bottom: 0;
}
}
// 첫 번째 영역: 헤더 섹션 (타이틀 + 설명)
.headerSection {
width: 1834px;
flex-direction: column;
justify-content: flex-start;
align-items: center;
gap: 40px;
display: flex;
> * {
margin-bottom: 40px;
&:last-child {
margin-bottom: 0;
}
}
.titleContainer {
align-self: stretch;
justify-content: center;
align-items: center;
gap: 14px;
display: inline-flex;
> * {
margin-right: 14px;
&:last-child {
margin-right: 0;
}
}
.welcomeText {
color: #807f81;
font-size: 62px;
@@ -89,9 +107,15 @@
align-self: stretch;
justify-content: center;
align-items: center;
gap: 10px;
display: inline-flex;
> * {
margin-right: 10px;
&:last-child {
margin-right: 0;
}
}
.descriptionText {
width: 1012.49px;
text-align: center;
@@ -225,9 +249,15 @@
height: 100px;
position: relative;
display: flex;
gap: 40px;
justify-content: center;
> * {
margin-right: 40px;
&:last-child {
margin-right: 0;
}
}
.agreeButton,
.disagreeButton {
width: 450px;

View File

@@ -67,8 +67,15 @@
display: flex;
align-items: center;
justify-content: center;
gap: 20px;
flex-wrap: wrap;
> * {
margin-right: 20px;
margin-bottom: 20px;
&:last-child {
margin-right: 0;
}
}
}
.benefitImage {

View File

@@ -159,14 +159,26 @@
.popup_container {
display: flex !important;
flex-direction: column !important;
gap: 30px !important;
> * {
margin-bottom: 30px !important;
&:last-child {
margin-bottom: 0 !important;
}
}
}
.button_container {
display: flex !important;
flex-direction: row !important;
justify-content: center !important;
gap: 12px !important;
> * {
margin-right: 12px !important;
&:last-child {
margin-right: 0 !important;
}
}
.button {
width: 380px !important;
@@ -187,8 +199,14 @@
flex-direction: column;
justify-content: center;
align-items: center;
gap: 30px;
box-sizing: border-box;
> * {
margin-bottom: 30px;
&:last-child {
margin-bottom: 0;
}
}
}
.disagreePopupText {
@@ -202,7 +220,13 @@
display: flex;
flex-direction: row;
justify-content: center;
gap: 12px;
> * {
margin-right: 12px;
&:last-child {
margin-right: 0;
}
}
}
.disagreePopupButton {

View File

@@ -42,6 +42,12 @@ export default function PlayerOverlayContents({
const cntry_cd = useSelector((state) => state.common.httpHeader?.cntry_cd);
const dispatch = useDispatch();
const onClickBack = (ev) => {
// TabContainerV2가 표시된 상태에서 백버튼 클릭 시 이벤트 버블링 방지
// (Overlay의 onClick으로 전파되어 toggleControls()가 호출되는 것을 막음)
if (tabContainerVersion === 2 && belowContentsVisible) {
ev.stopPropagation();
}
if (onClick) {
onClick(ev);
}

View File

@@ -835,9 +835,14 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
(ev, isEnd) => {
//modal로부터 Full 전환된 경우 다시 preview 모드로 돌아감.
if (sideContentsVisible && !videoVerticalVisible && panelInfo.shptmBanrTpNm !== 'MEDIA') {
// TabContainer(v1)만: Side Contents가 보이는 경우 먼저 숨기고 return
if (
tabContainerVersion === 1 &&
sideContentsVisible &&
!videoVerticalVisible &&
panelInfo.shptmBanrTpNm !== 'MEDIA'
) {
setSideContentsVisible(false);
setBelowContentsVisible(false);
ev?.stopPropagation();
// ev?.preventDefault();
return;
@@ -888,9 +893,10 @@ const PlayerPanel = ({ isTabActivated, panelInfo, isOnTop, spotlightId, ...props
nowMenu,
videoPlayer,
sideContentsVisible,
setBelowContentsVisible,
videoVerticalVisible,
backupInitialIndex,
panels,
tabContainerVersion,
]
);

View File

@@ -21,18 +21,29 @@
display: inline-flex;
justify-content: center;
align-items: center;
gap: 10px;
cursor: pointer;
transition: all 0.3s ease;
> * {
margin-right: 10px;
&:last-child {
margin-right: 0;
}
}
&:hover {
background: rgba(0, 0, 0, 0.5);
border-color: rgba(234, 234, 234, 0.7);
}
&:focus {
background: rgba(199, 8, 80, 0.2);
background: @PRIMARY_COLOR_RED;
border-color: @PRIMARY_COLOR_RED;
.buttonText {
color: white;
}
&::after {
.focused(@boxShadow: 22px, @borderRadius: 12px);
}

View File

@@ -102,9 +102,14 @@ export default function TabContainerV2({
Spotlight.focus(SpotlightIds.PLAYER_BACK_BUTTON);
}, []);
// useEffect(()=>{
// console.log('[tabIndex]',tabIndex)
// },[tabIndex])
// tabIndex가 2(ShopNowButton)로 변경되면 자동으로 포커스 이동
useEffect(() => {
if (tabIndex === 2) {
setTimeout(() => {
Spotlight.focus('below-tab-shop-now-button');
}, 100);
}
}, [tabIndex]);
return (
<Container

View File

@@ -149,8 +149,9 @@
color: white;
}
&:focus {
background: rgba(199, 8, 80, 0.2);
background: @PRIMARY_COLOR_RED;
border-color: @PRIMARY_COLOR_RED;
color: white;
}
}
@@ -190,9 +191,13 @@
}
&:focus {
background: rgba(199, 8, 80, 0.2);
background: @PRIMARY_COLOR_RED;
border-color: @PRIMARY_COLOR_RED;
.buttonText {
color: white;
}
&::after {
.focused(@boxShadow: 22px, @borderRadius: 100px);
}

View File

@@ -82,8 +82,14 @@
width: 100%;
display: flex;
flex-direction: column;
gap: 10px;
background-color: @COLOR_WHITE;
> * {
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
}
}
.noReviews {