fix: 영상 상세 모달을 social-posting-modal 구조로 통일하고 video 삐져나옴 버그 수정

This commit is contained in:
김성경 2026-08-11 14:57:35 +09:00
parent 40ebe2cca0
commit 1c01dab748
5 changed files with 81 additions and 53 deletions

View File

@ -249,6 +249,7 @@ const VideoDetailContent: React.FC<VideoDetailContentProps> = ({ videoId, isModa
)} )}
</div> </div>
<div className="video-detail-body">
{loading ? ( {loading ? (
<div className="ado2-contents-loading"> <div className="ado2-contents-loading">
<div className="loading-spinner"></div> <div className="loading-spinner"></div>
@ -262,6 +263,7 @@ const VideoDetailContent: React.FC<VideoDetailContentProps> = ({ videoId, isModa
src={video.result_movie_url} src={video.result_movie_url}
controls controls
autoPlay autoPlay
playsInline
controlsList="nodownload" controlsList="nodownload"
onContextMenu={(e) => e.preventDefault()} onContextMenu={(e) => e.preventDefault()}
className="video-detail-player" className="video-detail-player"
@ -477,6 +479,7 @@ const VideoDetailContent: React.FC<VideoDetailContentProps> = ({ videoId, isModa
</div> </div>
</div> </div>
) : null} ) : null}
</div>
{showLoginModal && ( {showLoginModal && (
<LoginPromptModal onClose={() => setShowLoginModal(false)} /> <LoginPromptModal onClose={() => setShowLoginModal(false)} />

View File

@ -27,29 +27,8 @@ const VideoDetailModal: React.FC<VideoDetailModalProps> = ({ videoId, onClose })
const overlayCloseHandlers = useOverlayClose(onClose); const overlayCloseHandlers = useOverlayClose(onClose);
return ( return (
<div <div className="video-detail-overlay" {...overlayCloseHandlers}>
style={{ <div className="video-detail-modal-box" onClick={(e) => e.stopPropagation()}>
position: 'fixed',
inset: 0,
background: 'rgba(0, 0, 0, 0.85)',
zIndex: 2000,
display: 'flex',
overflowY: 'auto',
padding: '24px 16px',
}}
{...overlayCloseHandlers}
>
<div
style={{
background: '#01282A',
borderRadius: '16px',
width: '100%',
maxWidth: '800px',
margin: 'auto',
position: 'relative',
}}
onClick={(e) => e.stopPropagation()}
>
<VideoDetailContent videoId={videoId} isModal={true} onClose={onClose} /> <VideoDetailContent videoId={videoId} isModal={true} onClose={onClose} />
</div> </div>
</div> </div>

View File

@ -290,6 +290,7 @@ const SsulDetailContent: React.FC<SsulDetailContentProps> = ({
)} )}
</div> </div>
<div className="video-detail-body">
{loading ? ( {loading ? (
<div className="ado2-contents-loading"> <div className="ado2-contents-loading">
<div className="loading-spinner"></div> <div className="loading-spinner"></div>
@ -303,6 +304,7 @@ const SsulDetailContent: React.FC<SsulDetailContentProps> = ({
src={content.video_url} src={content.video_url}
controls controls
autoPlay autoPlay
playsInline
controlsList="nodownload" controlsList="nodownload"
onContextMenu={(e) => e.preventDefault()} onContextMenu={(e) => e.preventDefault()}
className="video-detail-player" className="video-detail-player"
@ -513,6 +515,7 @@ const SsulDetailContent: React.FC<SsulDetailContentProps> = ({
</div> </div>
</div> </div>
) : null} ) : null}
</div>
{showLoginModal && ( {showLoginModal && (
<LoginPromptModal onClose={() => setShowLoginModal(false)} /> <LoginPromptModal onClose={() => setShowLoginModal(false)} />

View File

@ -39,29 +39,8 @@ const SsulViewerModal: React.FC<SsulViewerModalProps> = ({ item, onClose }) => {
if (!item) return null; if (!item) return null;
return ( return (
<div <div className="video-detail-overlay" {...overlayCloseHandlers}>
style={{ <div className="video-detail-modal-box" onClick={(e) => e.stopPropagation()}>
position: 'fixed',
inset: 0,
background: 'rgba(0, 0, 0, 0.85)',
zIndex: 2000,
display: 'flex',
overflowY: 'auto',
padding: '24px 16px',
}}
{...overlayCloseHandlers}
>
<div
style={{
background: '#01282A',
borderRadius: '16px',
width: '100%',
maxWidth: '800px',
margin: 'auto',
position: 'relative',
}}
onClick={(e) => e.stopPropagation()}
>
<SsulDetailContent <SsulDetailContent
contentId={String(item.video_id)} contentId={String(item.video_id)}
isModal={true} isModal={true}

View File

@ -646,10 +646,56 @@
/* ── VideoDetailPage / VideoDetailModal ─────────────────────────── */ /* ── VideoDetailPage / VideoDetailModal ─────────────────────────── */
/* 모달 내부 콘텐츠 (풀페이지의 video-detail-page와 동일 스타일, 배경 제외) */ /* social-posting-overlay 와 동일한 구조: 배경 + 중앙 정렬 + 내부 스크롤 박스 */
.video-detail-overlay {
position: fixed;
inset: 0;
text-align: left;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
padding: 1rem;
}
/* social-posting-modal 과 동일한 박스 스타일: 헤더 고정 + 본문만 스크롤 */
.video-detail-modal-box {
width: 100%;
max-width: 800px;
max-height: 90vh;
background-color: #01282A;
border-radius: 16px;
border: 1px solid rgba(166, 255, 234, 0.1);
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
/* 일부 모바일 브라우저는 video 를 별도 하드웨어 컴포지팅 레이어로 그려
overflow:hidden + border-radius 클리핑을 무시하고 밖으로 삐져나온다.
contain:paint 로 이 박스 밖에는 절대 그려지지 않도록 강제한다. */
contain: paint;
}
/* 모달 내부 콘텐츠 (풀페이지의 video-detail-page와 동일 스타일, 배경 제외).
헤더는 여기의 일반 흐름 자식(스크롤 없음)이고, .video-detail-body 만 스크롤된다.
과거엔 헤더에 position:sticky 를 써서 흉내냈으나, 자동재생 <video> 와 같은
스크롤 컨테이너 안에 sticky 요소가 있으면 iOS Safari 에서 video 가 별도
레이어로 떨어져나가 모달 밖으로 삐져나오는 버그가 있어 구조 자체를 분리했다. */
.video-detail-modal-content { .video-detail-modal-content {
color: #fff; color: #fff;
padding: 24px 32px; flex: 1;
min-height: 0;
display: flex;
flex-direction: column;
}
/* social-posting-content 에 대응하는 스크롤 영역 */
.video-detail-modal-content > .video-detail-body {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 0 32px 24px;
} }
.video-detail-page-content { .video-detail-page-content {
@ -693,10 +739,12 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 1.25rem 1.5rem;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
} }
.video-detail-modal-title { .video-detail-modal-title {
font-size: 20px; font-size: 24px;
font-weight: 600; font-weight: 600;
color: #FFFFFF; color: #FFFFFF;
margin: 0; margin: 0;
@ -737,6 +785,7 @@
display: block; display: block;
width: 100%; width: 100%;
height: auto; height: auto;
padding: 1rem;
max-width: 360px; max-width: 360px;
border-radius: 12px; border-radius: 12px;
flex-shrink: 0; flex-shrink: 0;
@ -752,7 +801,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
padding-top: 8px; padding-top: 1rem;
align-self: flex-start; align-self: flex-start;
} }
@ -1183,6 +1232,21 @@
} }
} }
/* social-posting-modal 의 Mobile Adjustments 와 동일한 기준(768px) */
@media (max-width: 768px) {
.video-detail-modal-box {
max-height: 95vh;
}
.video-detail-modal-content > .video-detail-header {
padding: 1rem;
}
.video-detail-modal-content > .video-detail-body {
padding: 0 1rem 1rem;
}
}
.pagination-info { .pagination-info {
color: #9BCACC; color: #9BCACC;
font-size: 14px; font-size: 14px;