JSFiddle - React, Tailwind, and code Playground

by seulbiLee

HTML

<div class="wrapper">
  <p><a href="http://naver.com">포커스 테스트를 위한 링크</a></p>
  <button aria-haspopup="dialog" data-popup="popup1">팝업 열기</button>
  <div style="height:130vh; display: flex; align-items: flex-end;">
    <button aria-haspopup="dialog" data-popup="popup3">팝업 열기</button>
  </div>
</div>
<section class="wrap-layer-popup" data-popup="popup1" role="dialog">
  <div class="inner-layer-popup">
    <div class="wrap-layer-popup-title" tabindex="0">
      <h1>타이틀</h1>
    </div>
    <div class="layer-popup-contents">
      <div class="inner">
        내용<br/>내용<br/>내용<br/>내용<br/><a href="javascript:;">테스트테스트</a>내용<br/>내용<br/>내용<br/>내용<br/><a href="javascript:;">테스트테스트</a>내용<br/>내용<br/>내용<br/>
        <input type="checkbox" id="checkbox" />
        <label for="checkbox">
          <span>체크체크</span>
        </label>
        <button aria-haspopup="dialog" data-popup="popup2"><span>2차 팝업 열기</span></button>
        <button aria-haspopup="dialog" data-popup="popup4"><span>2차 컨펌 팝업 열기</span></button>
      </div>
    </div>
    <div class="layer-popup-bottom">
      <button popup-close="popup1" popup-close-all="true">팝업 모두 닫기</button>
      <button popup-close="popup1">현재 팝업 닫기</button>
    </div>
    <button class="btn-layer-close" popup-close="popup1"><span class="hidden">현재 팝업 닫기</span></button>
  </div>
</section>
<section class="wrap-layer-popup" data-popup="popup2" role="dialog">
  <div class="inner-layer-popup">
    <div class="wrap-layer-popup-title" tabindex="0">
      <h1>타이틀22</h1>
    </div>
    <div class="layer-popup-contents">
      <div class="inner">
        내용<br/><a href="javascript:;">테스트테스트</a>내용<br/>내용<br/><a href="javascript:;">테스트테스트</a>내용<br/>내용<br/>
        <button aria-haspopup="dialog" data-popup="popup3">3차 팝업 열기</button>
      </div>
    </div>
    <div class="layer-popup-bottom">
      <button popup-close="popup2">현재 팝업 닫기</button>
      <button popup-close="popup2" popup-close-all="true">팝업 모두 닫기</button>
    </div>
    <button...

CSS

.wrap-layer-popup {
  display: none;
  align-items: center;
  justify-content: center;
  position: fixed;
  z-index: 991;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
}
.wrap-layer-popup.popup-open {
  display: flex;
}
.wrap-layer-popup.popup-open > .inner-layer-popup {
  opacity: 1;
}
.wrap-layer-popup.prev-popup {
  z-index: 989;
}
.wrap-layer-popup > .inner-layer-popup {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-content: stretch;
  width: 400px;
  max-height: 100%;
  height: auto;
  opacity: 0;
  border-radius: 5px;
  overflow: hidden;
}
.wrap-layer-popup .wrap-layer-popup-title {
  width: 100%;
  padding: 10px;
  background-color: #fff;
}
.wrap-layer-popup .wrap-layer-popup-title h1 {
  margin: 0;
  font-size: 18px;
  font-weight: 700;
  line-height: 30px;
}

.wrap-layer-popup .layer-popup-contents {
  position: relative;
  width: 100%;
  height: 100%;
  overflow-y: auto;
  background-color: #fff;
}
.wrap-layer-popup .layer-popup-contents > .inner {
  /* max-height: calc(100% - 50px); */
  box-sizing: border-box;
  padding: 20px;
}
.wrap-layer-popup .layer-popup-bottom {
  display: flex;
  width: 100%;
}
.wrap-layer-popup .layer-popup-bottom button {
  width: 100%;
  height: 40px;
  border: 0;
  background-color: #666;
  color: #fff;
  box-sizing: border-box;
  border: 1px solid transparent;
}
.wrap-layer-popup .layer-popup-bottom button[popup-cancle] {
  background-color: #fff;
  color: #000;
  border-top-color: #ccc;
}
.popup-dimmed {
  position: fixed;
  z-index: 990;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0,0,0,.7);
  transition: opacity .2s;
}
.scroll-lock {
  overflow: hidden;
}
.wrapper {
  width: 900px;
  margin: 0 auto;
}
.hidden {
  position: relative;
  z-index: -1;
  display: inline-block;
  overflow: hidden;
  height: 1px;
  width: 1px;
  border: 0;
  clip: rect(1px,1px,1px,1px);
  clip-path: inset(50%);
  word-break: initial;
  word-wrap: initial;
}

.btn-layer-close {
 ...

JavaScript

const popupBtnAll = document.querySelectorAll('[aria-haspopup="dialog"]');
if (popupBtnAll) {
  let currentTarget, focusEl = [], popupDepth = 0, popupDimmed, keyEscapeEvt, KeyEvtEl;
  const _$this = this,
  popupAll = document.querySelectorAll('[role="dialog"]'),
  popupCloseBtnAll = document.querySelectorAll('[popup-close]');
  // ESC 누름 감지
  const keyEvent = {
    get keyEscape() {
      return this._state;
    },
    set keyEscape(state) {
      this._state = state;
      if (state) escKeyEvt(KeyEvtEl, keyEscapeEvt);
    },
  };
  keyEvent;
  // popup dimmed 생성
  const createdDimmed = () => {
    const createDiv = document.createElement('div');
    createDiv.classList.add('popup-dimmed');
    document.querySelector('body').appendChild(createDiv);
  };
  // popup dimmed click 시 팝업 닫기
  const dimmedClick = (e) => {
    if (e.target.classList.contains('wrap-layer-popup')) {
      popupCloseAll();
      keyEvent.keyEscape = false;
    }
  };
  // popup open
  const popupOpen = (e) => {
    currentTarget = e.target.tagName;
    currentTarget === 'BUTTON' || currentTarget === 'A' ? currentTarget = e.target : currentTarget = e.target.closest('button') || e.target.closest('a');
    
    popupDimmed = document.querySelectorAll('.popup-dimmed');
    if (popupDimmed.length === 0) createdDimmed();

    popupAll.forEach((popupEl) => {
      if (popupEl.getAttribute('data-popup') === currentTarget.getAttribute('data-popup')) {
        popupDepth += 1; // popup depth 저장
        focusEl.splice((popupDepth - 1), 0, currentTarget); // popup focus Element 저장
        popupEl.classList.add('popup-open'); // open class add
        popupEl.setAttribute('popup-depth', popupDepth); // popup depth 설정

        // dimmed click 이벤트 할당
        popupEl.removeEventListener('click', dimmedClick);
        popupEl.addEventListener('click', dimmedClick);

        document.body.classList.add('scroll-lock'); // popup scroll lock
        popupEl.querySelector('.wrap-layer-popup-title').focus(); //...