/** * 중도 이탈 모달 — "테스트를 종료하시겠습니까?" (PRD §3.1 공통 규칙) * z-index를 최상위(900)로 두고, 버튼은 type="button" + stopPropagation으로 * 다른 요소(pointer capture/재렌더)에 클릭이 삼켜지지 않게 한다. * * 주의: ✕(BackBtn)를 눌러 이 모달이 열린 상태에서 손을 떼면, * 팝업이 ✕를 덮으면서 클릭이 배경(Overlay)으로 새어 나가 모달이 닫힐 수 있다. * → startedInsideRef: 포인터를 누른 시점이 모달 안이었을 때만 배경 클릭으로 인정한다. */ import { useRef } from 'react'; import styled from 'styled-components'; const Overlay = styled.div` position: absolute; inset: 0; z-index: 900; background: rgba(0, 0, 0, 0.45); display: flex; align-items: center; justify-content: center; padding: 24px; `; const Box = styled.div` position: relative; z-index: 901; background: #fff; border-radius: 20px; padding: 28px 20px; width: 100%; max-width: 320px; max-height: 100%; overflow-y: auto; text-align: center; pointer-events: auto; `; const Msg = styled.p` font-size: 17px; font-weight: 600; margin-bottom: 20px; `; const Row = styled.div` display: flex; gap: 10px; button { flex: 1; padding: 13px; border-radius: 12px; font-weight: 600; border: none; cursor: pointer; } .continue { background: var(--pcnt-primary); color: #fff; } .exit { background: #fff; border: 1px solid var(--pcnt-border); color: var(--pcnt-text); } `; export default function ExitModal({ onContinue, onExit, }: { onContinue: () => void; onExit: () => void; }) { const startedInsideRef = useRef(false); const handleContinue = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); onContinue(); }; const handleExit = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); onExit(); }; const handleBoxClick = (e: React.MouseEvent) => { e.stopPropagation(); }; return ( { // 이번 터치가 모달 안(배경/버튼)에서 시작했는지 기록 startedInsideRef.current = true; }} onClick={(e) => { e.preventDefault(); e.stopPropagation(); // ✕ 를 누른 채로 모달이 열렸다 손을 떼는 경우(모달 밖에서 시작한 클릭)는 // 배경 클릭으로 취급하지 않아 닫히지 않게 한다. if (startedInsideRef.current) onContinue(); startedInsideRef.current = false; }} > 테스트를 종료하시겠습니까? ); }