Skip to content

페이지 인터랙션 — 모바일 앱 (2depth · 풀레이어 팝업) ​

모바일 앱 전용

이 페이지의 규칙은 모바일 앱(직원 앱 · hereby Works) 에만 적용합니다. admin · hr-system 같은 웹 앱은 대상이 아닙니다 — 웹의 모달 · 패널 모달은 모달 규칙을 따릅니다.

화면 위에 화면이 열리는 두 가지 방법

모바일 앱에서 지금 화면 위로 화면 한 장이 통째로 열리는 방법은 두 가지뿐입니다 — 「뒤로(<)」로 돌아가는 2depth 페이지와, 「닫기(X)」로 닫는 풀레이어 팝업. 이 페이지가 둘을 한곳에 정리합니다: 언제 무엇을 쓰는지, 구조 · 헤더 · 여백 · 하단 버튼 · 닫기, 그리고 열리고 닫힐 때의 움직임(2depth 는 옆에서, 풀레이어 팝업은 아래에서)과 iOS 엣지 스와이프(2026-10-12).

규칙 (강제) · 코딩 에이전트용

2depth · 풀레이어 팝업의 강제 규칙(모바일 앱 전용 — 웹 앱은 대상 아님). 상세·근거는 아래 본문.

고르기

  • 화면 한 장 전체를 여는 방법은 2depth 와 풀레이어 팝업 둘뿐이다. 들어가서 보고 돌아오는 하위 화면은 2depth(「뒤로(<)」), 무언가를 작성 · 고르고 끝내는 화면은 풀레이어 팝업(「닫기(X)」). 둘을 섞지 않는다 — 2depth 에 X 를, 풀레이어 팝업에 < 를 두지 않는다.

2depth 페이지

  • 헤더는 앱 셸의 앱 헤더 하나를 쓴다(showBack 으로 깊이를 가름). 화면 안에 제목 · 뒤로 버튼을 따로 그리지 않는다.
  • 루트에 x-show + x-depth-page. x-if 를 쓰지 않는다 — 조건이 꺼지는 순간 DOM 이 지워져 이탈 애니메이션이 없다.
  • 본문 좌우 여백 12(px-3). 하단 고정 버튼은 .page-footer-cta(바) 또는 .page-footer-cta--flow(본문 끝).

풀레이어 팝업

  • 구조는 딤 루트(.modal-backdrop) > 시트(.modal-sheet.modal-sheet--full) > 본문(.modal-full__body, 안에 제목 줄 .modal-full__header) + 하단 바(.modal-full__footer). 공용 모달을 풀레이어로 쓸 때는 .ds-modal--full(호스트가 fullLayer: true).
  • 제목은 <div role="heading" aria-level="3" class="modal-full__title"> — 2depth 제목과 같은 규격(text-b3 · semibold · 한 줄 말줄임 · 화면 설정과 무관한 고정 크기). <h3> 에 크기 클래스를 주지 않는다(태그 기본값에 밀린다).
  • 닫기(X)는 제목 줄 오른쪽 .ds-btn--icon.modal-full__close, 본문 선(오른쪽 12)에 둔다 — 2depth 뒤로(<)의 좌우 대칭.
  • 본문만 스크롤하고 하단 바는 고정한다. 좌우 여백 12. 입력칸 ds-input--48, 라벨 text-b4 이상, 하단 버튼 ds-btn--lg · 둘까지(셋이면 「취소」를 빼고 X 에 맡긴다).
  • <template x-if> 대신 <template x-layer-if>, x-show 로 여는 공용 모달은 x-layer-page="<풀레이어일 때만 참>".
  • 새 풀레이어 클래스를 만들면 직원 앱 main.css 의 글자 배율 보정 선택자(html[data-font-scale="lg"] .modal-sheet--full, … .ds-modal--full)에 같이 넣는다.

움직임

  • 2depth 는 우 → 좌, 풀레이어 팝업은 아래 → 위. 세 번째 방향(왼쪽에서 · 위에서 · 페이드만 · 확대)을 만들지 않는다.
  • 풀레이어 팝업은 시트만 움직이고 딤은 배경색만 옅어진다. 딤 루트 바로 아래 자식이 시트(.modal-sheet · .ds-modal)여야 한다.
  • 길이 260ms · 곡선 cubic-bezier(0.32, 0.72, 0, 1) 하나(apps/employee-app/src/directives/page-motion.ts). 화면마다 바꾸지 않고, x-transition 클래스로 따로 그리지 않는다.
  • 모션 줄이기(prefers-reduced-motion: reduce)면 바로 열고 닫는다 — 디렉티브가 처리한다.
  • 엣지 스와이프는 2depth 에만, iOS 네이티브 앱에만. 풀레이어 팝업 · 안드로이드 · 웹에는 걸지 않는다. 대상 뒤로 버튼에 data-edge-swipe-back(앱 헤더 「뒤로(<)」에는 이미 있음). 모양만 < 인 버튼(이전 달)에는 달지 않는다.

대상 아님 — 바텀시트(.modal-sheet 단독 — 확인 시트 · 이미지 자르기), 컨펌 다이얼로그(.ds-dialog), 오른쪽 서랍(AI 이전 대화), 1depth 탭 전환, 토스트 · 배너.

  • 다크 모드(2026-10-19) — 2depth 페이지 · 풀레이어 팝업의 면 · 글자 · 선은 직원 앱 전 화면 이관으로 시멘틱을 읽는다: 풀레이어 바탕 bg-bg-container, 하단 버튼 영역 윗선 .modal-full__footer line-normal(전 neutral-100 고정 — 다크에서 흰선), 흰 면 화면 표시 app-surface-white 가 넘기는 헤더 · 하단 내비 면 bg-container. 사용 안내(투어) 말풍선 제목 · 본문은 label-normal · label-body. 확인: Playground 「미리보기 테마 → 다크」.

두 종류 한눈에 ​

2depth 페이지풀레이어 팝업
언제목록 · 홈에서 들어가 보고 돌아오는 하위 화면무언가를 작성 · 고르고 끝내는 화면
예결재 상세, 급여명세서, 알림, 내 정보, 나의 결재선, 근무 기록휴가 · 연장근무 · 외근 신청, 보고서, 출퇴근 정정, 서류 제출, 인센티브 등록, 결재자 지정, 명세서 상세, 불편 · 오류 신고
돌아가는 버튼왼쪽 뒤로(<) — 44 유리 원오른쪽 닫기(X) — 32 투명 버튼
헤더앱 셸의 앱 헤더(.ds-app-header--depth2)시트 안 제목 줄(.modal-full__header)
제목text-b3 · semibold · hr-gray-900 · 한 줄같은 규격(.modal-full__title)
하단 내비숨김시트가 덮음
하단 버튼.page-footer-cta.modal-full__footer
진입우 → 좌 — 오른쪽 밖에서 제자리로아래 → 위 — 화면 아래 밖에서 제자리로 · 딤이 짙어짐
이탈좌 → 우 — 그 아래로 1depth 가 드러남위 → 아래 — 딤이 옅어지며 아래 화면이 드러남
엣지 스와이프 (iOS)있음없음 — 「닫기(X)」로만 닫음
마크업x-show + x-depth-page<template x-layer-if> / x-show + x-layer-page
구현directives/depth-page.tsdirectives/layer-page.ts

무엇을 쓸까 ​

  • 돌아갈 곳을 보여 주는 화면이면 2depth — 사용자가 「보고」 돌아갑니다. 그 안에서 또 들어가면 3depth 이상이지만 헤더 모양은 2depth 와 같습니다(깊이를 세지 않고 「뒤로 갈 곳이 있는가」만 봄).
  • 끝내야 하는 일이면 풀레이어 팝업 — 사용자가 「쓰고 · 고르고」 닫습니다. 저장하지 않고 닫을 때 물어볼 수 있는 것도 이쪽입니다.
  • 짧은 확인 · 선택이면 둘 다 아닙니다 — 바텀시트나 컨펌 다이얼로그를 씁니다. 풀레이어 팝업은 입력이 있는 폼에 어울립니다.

공통 — 움직임 값 ​

항목값
길이260ms — 두 종류 같음
곡선cubic-bezier(0.32, 0.72, 0, 1) — 빠르게 출발해 부드럽게 멈추는 iOS 시트 곡선
모션 줄이기움직이지 않고 바로 열고 닫음(prefers-reduced-motion: reduce)
정본apps/employee-app/src/directives/page-motion.ts

DS 모션 토큰은 --duration-fast(100ms) · --duration-normal(200ms) 두 단계뿐이라 페이지 전환 값이 없어, 직원 앱 한 곳에 상수로 둡니다.

2depth 페이지 ​

목록 줄을 눌러 들어가고 「뒤로(<)」로 돌아와 보세요. 플랫폼을 iOS 로 두고 왼쪽 가장자리를 끌면 엣지 스와이프입니다.

옵션을 바꾸면 미리보기와 코드가 함께 바뀌어요.
미리보기 테마
더보기

목록 줄을 누르면 2depth 가 오른쪽에서 들어와요.

목록 줄을 누르면 2depth 가 오른쪽에서 들어오고, 「뒤로(<)」를 누르면 오른쪽으로 빠지며 1depth 가 드러납니다. 플랫폼이 iOS일 때 2depth 화면의 왼쪽 가장자리(주황 띠, 20px)를 마우스로 잡고 오른쪽으로 끌어 보세요 — 폭의 35% 이상 밀거나 빠르게 튕기면 뒤로 가고, 아니면 제자리로 돌아옵니다. 안드로이드 · 웹에서는 띠가 회색이고 스와이프가 동작하지 않습니다.
<!-- 헤더는 앱 셸의 앱 헤더 하나 — showBack 으로 1depth · 2depth 를 가른다 -->
<div class="contents" x-data="appHeader({ …, onBack: () => goBack(), showBack: () => isDepth })"> … </div>

<!-- 2depth 페이지 — 우 → 좌. 여는 조건은 반드시 x-show (x-if 는 이탈 애니메이션이 없다) -->
<div x-show="showPayslips" x-cloak x-depth-page class="px-3 pt-4">
  …
  <div class="page-footer-cta">                <!-- 하단 고정 버튼(필요할 때만) -->
    <button class="ds-btn ds-btn--solid ds-btn--primary ds-btn--lg">저장</button>
  </div>
</div>

// 엣지 스와이프 — iOS 네이티브만, 2depth 가 열려 있을 때만 (capacitor-init.ts)
if (Capacitor.getPlatform() === "ios") {
  const edgeSwipe = installEdgeSwipeBack();
  Alpine.effect(() => edgeSwipe.setArmed(Alpine.store("router").depth !== null));
}

<!-- 스와이프 대상 뒤로 버튼 — 앱 헤더 「뒤로(<)」에는 이미 달려 있다 -->
<button data-edge-swipe-back aria-label="뒤로"> … </button>

구조 ​

html
<!-- 앱 셸 헤더 하나가 1depth · 2depth 를 오간다 — showBack 으로 깊이를 가른다 -->
<div class="contents" x-data="appHeader({ …, onBack: () => goBack(), showBack: () => isDepth })"> … </div>

<!-- 2depth 화면 — 루트에 x-show + x-depth-page, 본문 좌우 12 -->
<div x-show="showPayslips" x-cloak x-depth-page class="px-3 pt-4">
  …
  <div class="page-footer-cta">                     <!-- 하단 고정 버튼(필요할 때만) -->
    <button class="ds-btn ds-btn--solid ds-btn--primary ds-btn--lg">저장</button>
  </div>
</div>
  • 헤더는 화면이 그리지 않습니다. 앱 셸의 앱 헤더 하나가 showBack 으로 1depth · 2depth 를 오가고, 2depth 면 왼쪽에 뒤로(<) 유리 원(44)과 화면 제목이 붙습니다. 벨 · 아바타는 보통 끕니다. 상세 → 앱 헤더 · 깊이.
  • 좌우 여백 12 — 1depth 와 같습니다(2026-10-12). 헤더도 같은 선(--app-depth-side)에 섭니다.
  • 하단 내비는 숨습니다. 그래서 하단 고정 버튼(.page-footer-cta)이 safe-area 여백을 직접 갖습니다 — 흰 바탕 · 윗선 · 좌우 12 · 아래 16 + safe-area, 버튼은 반반(flex: 1). 바가 아니라 본문 끝에 얹으려면 .page-footer-cta--flow(내 정보).

움직임 — 우 → 좌 ​

  • 진입은 오른쪽 밖 → 제자리, 이탈은 제자리 → 오른쪽 밖이고 그 아래로 1depth 가 드러납니다.
  • 2depth 화면 대부분은 자기 배경이 없어(앱 셸 배경이 비침), 이탈할 때 디렉티브가 조상에서 실제로 칠해진 배경색을 찾아 떠나는 화면에 입힙니다. 그래야 그 아래 다시 보이기 시작한 1depth 가 비쳐 보이지 않습니다.
  • 떠나는 화면은 지금 그려진 자리 그대로 흐름에서 빠집니다(절대배치). 그대로 두면 같은 순간 다시 보이기 시작한 1depth 와 세로로 쌓여 목록이 밀린 채 움직입니다.
  • 공용 템플릿(메신저)은 값을 줘 조건부로 씁니다 — x-depth-page="useHostHeader". 거짓이면 아무것도 심지 않아 그 요소의 기존 x-transition(PC 드로어 등)이 그대로 삽니다.

엣지 스와이프 — iOS 한정 ​

iOS 네이티브 앱에서는 2depth 화면의 왼쪽 가장자리(20px)를 오른쪽으로 밀면 「뒤로(<)」를 누른 것과 같습니다.

항목값
켜지는 곳iOS 네이티브 앱만(Capacitor.getPlatform() === "ios"). 안드로이드는 시스템 뒤로 제스처가 이미 앱의 뒤로 가기로 이어져 있어 겹치고, Safari 웹은 브라우저 자체 스와이프와 겹쳐서 켜지 않습니다.
켜지는 때2depth 가 열려 있을 때만입니다($store.router.depth). 1depth 탭 첫 화면에서는 리스너를 걸지 않아 긴 목록의 스크롤에 영향이 없습니다.
대상 버튼화면 맨 위에 실제로 보이는 data-edge-swipe-back 버튼(앱 헤더의 「뒤로(<)」)입니다. 다이얼로그 · 튜토리얼 · 풀레이어 팝업이 덮고 있으면 동작하지 않습니다.
따라오기가장자리에서 시작해 가로로 밀면 2depth 화면이 손가락을 따라 오른쪽으로 밀립니다.
손을 떼면화면 폭의 35% 이상 밀었거나 빠르게 튕겼으면 뒤로 갑니다. 이때 그 버튼을 그대로 누르므로 저장 확인 같은 화면별 뒤로 가기 규칙도 똑같이 탑니다. 이탈 슬라이드는 밀린 위치에서 이어집니다. 그렇지 않으면 제자리로 돌아옵니다.
햅틱없음

풀레이어 팝업(「닫기(X)」)에는 엣지 스와이프를 걸지 않습니다. 아래에서 올라온 화면이라 옆으로 밀어 닫는 동작이 방향과 맞지 않습니다.

풀레이어 팝업 ​

「휴가 신청」을 눌러 열고 「닫기(X)」로 닫아 보세요. 속도를 느리게 하면 시트만 올라오고 딤은 짙어지는 것이 보입니다.

옵션을 바꾸면 미리보기와 코드가 함께 바뀌어요.
미리보기 테마
근무
오늘 근무 1
오늘 근무 2
오늘 근무 3

「휴가 신청」을 누르면 풀레이어 팝업이 아래에서 올라와요.

「휴가 신청」을 누르면 시트가 아래에서 올라오고(딤이 짙어짐), 「닫기(X)」나 「신청」을 누르면 아래로 내려갑니다(딤이 옅어짐). 움직이는 것은 시트 하나이고, 엣지 스와이프로는 닫히지 않습니다.
<!-- 풀레이어 팝업 — 아래 → 위. x-if 대신 x-layer-if (닫으면 내려간 뒤 지운다) -->
<template x-teleport="body">
  <template x-layer-if="showLeaveModal">
    <div class="modal-backdrop">                                    <!-- 딤 루트 -->
      <div class="modal-sheet modal-sheet--full">                   <!-- 시트 · 이것만 움직인다 -->
        <div class="modal-full__body flex flex-col px-3 pt-5 pb-5"> <!-- 이것만 스크롤 -->
          <div class="modal-full__header flex items-center justify-between mb-4">
            <div role="heading" aria-level="3" class="modal-full__title">휴가 신청</div>
            <button class="ds-btn ds-btn--icon ds-btn--muted modal-full__close" aria-label="닫기"
                    @click="requestCloseForm('leave')">
              <span class="ds-ico" aria-hidden="true" x-html="$icon('ic-close')"></span>
            </button>
          </div>
          … 입력 …
        </div>
        <div class="modal-full__footer">                            <!-- 하단 고정 바 -->
          <button class="ds-btn ds-btn--solid ds-btn--primary ds-btn--lg">신청</button>
        </div>
      </div>
    </div>
  </template>
</template>

<!-- x-show 로 여는 공용 모달의 풀레이어 모드 -->
<div x-show="isOpen" x-layer-page="fullLayer" class="ds-modal-backdrop"> … </div>

구조 ​

html
<template x-teleport="body">
  <template x-layer-if="showLeaveModal">
    <div class="modal-backdrop">                                    <!-- 딤 루트 -->
      <div class="modal-sheet modal-sheet--full">                   <!-- 시트 = 화면 전체 · 이것만 움직인다 -->
        <div class="modal-full__body flex flex-col px-3 pt-5 pb-5"> <!-- 이것만 스크롤 -->
          <div class="modal-full__header flex items-center justify-between mb-4">
            <div role="heading" aria-level="3" class="modal-full__title">휴가 신청</div>
            <button class="ds-btn ds-btn--icon ds-btn--muted modal-full__close" aria-label="닫기"
                    @click="requestCloseForm('leave')">
              <span class="ds-ico" aria-hidden="true" x-html="$icon('ic-close')"></span>
            </button>
          </div>
          … 입력 …
        </div>
        <div class="modal-full__footer">                            <!-- 하단 고정 바 -->
          <button class="ds-btn ds-btn--solid ds-btn--primary ds-btn--lg">신청</button>
        </div>
      </div>
    </div>
  </template>
</template>

시트는 화면 전체(100dvh · 모서리 0 · 위 safe-area)를 덮는 flex 컬럼입니다. 본문(.modal-full__body)만 스크롤하고 하단 바는 바닥에 남습니다. 제목 줄은 본문 안 첫 줄입니다(앱 헤더가 아니라 스크롤 상자 안에 있어 유리 효과 대상이 아님).

제목 줄 — 제목 · 닫기(X) ​

요소규격
제목<div role="heading" aria-level="3" class="modal-full__title"> — text-b3 · semibold · hr-gray-900 · 한 줄 말줄임. 2depth 제목과 같은 규격이고, 화면 설정(작게 · 기본 · 크게)과 무관한 고정 크기(calc(var(--text-b3) * var(--app-nav-zoom))).
닫기(X)제목 줄 오른쪽 .ds-btn--icon.modal-full__close — 박스 32 · 글리프 20 · hr-gray-800. 본문 선 그대로(오른쪽 12)에 둬 2depth 뒤로(<) 원(왼쪽 12)과 좌우 대칭.
세로 위치2depth 헤더와 같은 높이(--app-header-top-in-body = 상태바 + 8). 버튼만이 아니라 줄 전체를 맞춘다.
글자 버튼(「도움」)제목과 같이 크기 고정(.modal-full__header .ds-btn-text).
  • ⚠️ 아직 맞추지 않은 것 — 닫기(X)는 32 투명 버튼이고 화면 설정을 따라 커지고 작아집니다. 2depth 뒤로(<)의 44 유리 원과는 크기 · 재질이 다릅니다(알고 두는 차이).
  • 제목에 <h3> 를 쓰고 클래스로 크기를 주면 unlayered 태그 기본값에 밀려 조용히 무시됩니다 → 타이포그래피.

본문 · 여백 ​

  • 좌우 12 — 본문 px-3, 하단 바도 12. 2depth · 1depth 와 같은 선입니다(2026-10-12, 예전 20).
  • 입력칸 ds-input--48 / ds-select--48(손가락 타깃), 라벨 · 설명 text-b4 이상. 직원 앱 최소 글자는 13px(text-b5)입니다.
  • 키보드 — 네이티브 셸이라 키보드가 뜨면 body.keyboard-open + --kb-height 로 시트 높이를 줄입니다. 웹 모달에는 옮기지 않습니다.
  • ⚠️ 글자 배율 보정 — 직원 앱은 화면 설정을 body { zoom } 으로 걸어 100dvh 가 배율만큼 넘칩니다. apps/employee-app/src/styles/main.css 의 html[data-font-scale="lg"] .modal-sheet--full, … .ds-modal--full 선택자에 새 풀레이어 클래스를 같이 넣어야 합니다. 빠뜨리면 기본 배율(1.2)에서만 하단 바가 화면 밖으로 밀리고, 개발자 브라우저(1.0)에서는 멀쩡해 보입니다.
  • iOS 에서 14px 입력칸을 탭하면 자동 확대되는 문제는 아직 규칙이 없습니다 → 모달 · iOS 자동 확대.
변형클래스모양
고정 바(기본).modal-full__footer시트 바닥에 고정 · 흰 바탕 · 위 12 · 좌우 12 · 아래 16 + safe-area · 버튼 사이 8
본문 끝.modal-full__footer--flow본문 안 마지막 자식 — 내용이 짧으면 바닥까지 내려가고 길면 내용 끝에 붙음(신청서 4종)
  • 버튼은 ds-btn--lg, 둘이면 반반(flex: 1).
  • 둘까지. 셋이면 각각 1/3 로 쪼개져 라벨이 줄바꿈됩니다 — 「취소」는 빼고 제목 줄의 X 에 맡깁니다.

닫기 ​

  • 닫는 길은 X 하나(+ 안드로이드 시스템 뒤로, 하단 「완료」류 버튼). 시트가 화면 전체를 덮어 휴대폰에는 「바깥」이 없으므로 딤 클릭으로 닫는 동작에 기대지 않습니다.
  • 써 넣은 것이 있으면 닫기 전에 묻습니다(신청서 — requestCloseForm). 저장 확인은 컨펌 다이얼로그입니다.
  • 엣지 스와이프로는 닫지 않습니다(위).

공용 모달을 풀레이어로 — .ds-modal--full ​

공용 템플릿의 모달(불편 · 오류 신고)을 직원 앱에서 띄우면, 가운데 뜬 카드 대신 같은 풀레이어로 그립니다. 호스트가 켭니다.

html
<!-- 직원 앱 index.html -->
<div x-data="csReport({ fullLayer: true })" data-cs-report-host>

.ds-modal--full 은 일반 모달의 표시 변형이라 __header/__title/__body/__footer 구조는 그대로이고, 직원 앱에서는 위와 같은 값(좌우 12 · 제목 규격 · X 위치)을 받습니다. 제목 색만 DS label-normal 그대로입니다(.ds-modal__title 에 색을 얹지 않는 규칙). 켜는 방법 · 폭과의 관계 → 모달 · 풀레이어.

움직임 — 아래 → 위 ​

시트가 화면 아래 밖에서 제자리로 올라오고(딤은 함께 짙어짐), 닫으면 아래 밖으로 내려갑니다(딤은 함께 옅어짐). 시트만 움직입니다 — 루트(딤)째 움직이면 딤까지 같이 올라와 화면이 통째로 밀려 오는 것처럼 읽힙니다.

x-if 로 여는 팝업 — x-layer-if ​

직원 앱 풀레이어 팝업은 열 때마다 폼을 새로 그리려고 x-if 를 씁니다. x-if 는 조건이 꺼지는 순간 DOM 을 지워 이탈 애니메이션을 걸 수 없으므로, x-if 를 x-layer-if 로 바꿉니다(위 구조의 마크업). 나머지는 그대로입니다.

  • 열 때 — x-if 와 똑같이 새로 그린 뒤 시트를 아래에서 올립니다.
  • 닫을 때 — 팝업을 먼저 Alpine 에서 떼고(그 순간의 모습으로 굳힘) 아래로 내린 뒤 지웁니다. 닫으면서 비운 상태(selectedPayslip = null 등)에 바인딩이 다시 반응해 빈 화면이 된 채 내려가지 않게 하려는 것입니다. 내려가는 동안에는 터치가 아래 화면으로 갑니다.
  • 내려가는 도중 다시 열면 남은 한 장을 바로 지우고 새로 올립니다 — 두 장이 겹치지 않습니다.
  • 올라오는 도중 닫으면 지금 위치에서 이어 내려갑니다.

x-show 로 여는 팝업 — x-layer-page ​

공용 모달은 x-show 로 열고 닫으므로 루트(딤)에 x-layer-page 를 붙입니다. 값을 주면 조건부입니다.

html
<div x-show="isOpen" x-cloak x-layer-page="fullLayer" class="ds-modal-backdrop ds-modal-backdrop--report">
  <div class="ds-modal" :class="fullLayer ? 'ds-modal--full' : 'mx-4'"> … </div>
</div>

x-layer-page 는 직원 앱이 등록하는 디렉티브라, 같은 공용 템플릿을 카드 모달로 쓰는 다른 앱(admin 등)에서는 아무 일도 하지 않습니다.

적용 현황 — 직원 앱 ​

종류화면
풀레이어 팝업 · x-layer-if휴가 신청 · 연장근무 신청 · 외근 신청 · 주말 당직 세션 신청 · 연장근무 보고서 · 외근 보고서 · 출퇴근 정정 · 서류 제출 · 인센티브 등록 · 결재자 지정(신청서 「변경」) · 급여명세서 상세
풀레이어 팝업 · x-layer-page불편 · 오류 신고(공용 cs-report.html, fullLayer 일 때)
2depth · x-depth-page결재 상세(신청 · 받은 결재) · 나의 결재선 · 결재선 편집 · 결재자 지정 · 급여명세서 · 근무 기록 · 근무 설정 · 근무 형태 변경 · 팀 근태 · 내 정보 · 화면 설정 · 서명 · 알림 · 앱 정보 · 드라이브 · AI 어시스턴트 · 홈 할 일 · 공지 · 축하 · 메신저 대화방 · 연락처 프로필 · 공지 상세

새 화면을 만들면 — 「뒤로(<)」 화면이면 x-depth-page, 「닫기(X)」 풀레이어 팝업이면 x-layer-if(또는 x-layer-page)를 붙이고 위 표에 한 줄을 더합니다.

단일 출처 ​

파트파일
2depth 전환apps/employee-app/src/directives/depth-page.ts
풀레이어 팝업 전환apps/employee-app/src/directives/layer-page.ts
전환 길이 · 곡선apps/employee-app/src/directives/page-motion.ts
엣지 스와이프apps/employee-app/src/utils/edge-swipe-back.ts · 켜는 곳 capacitor-init.ts
풀레이어 팝업 스타일(.modal-sheet--full · .modal-full__*) · 2depth 하단 버튼(.page-footer-cta) · 좌우 12apps/employee-app/src/styles/main.css
2depth 헤더packages/components/html/app-header.html → 앱 헤더
공용 풀레이어(.ds-modal--full)packages/static/styles/design-system.css → 모달