콘텐츠로 이동

shadcn 점진적 모달 교체 시 AnimatePresence 공유 래퍼 보존 패턴

맥락

기존 프로젝트에서 여러 모달을 단일 <AnimatePresence> 래퍼 안에 함께 선언하는 패턴이 흔하다.

// 기존 패턴 — 여러 모달이 AnimatePresence를 공유
<AnimatePresence>
  {isFormOpen    && <AssignFormModal ... />}
  {isHistoryOpen && <ProvideHistoryModal ... />}
  {isDeleteOpen  && <DeleteConfirmModal ... />}  // ← 이것만 교체 대상
</AnimatePresence>

shadcn 모달 마이그레이션을 점진적으로(한 Phase씩) 진행할 때, 교체 대상 모달만 shadcn Dialog로 옮기고 나머지는 다음 Phase에서 처리하는 경우 AnimatePresence 처리를 잘못하면 회귀가 발생한다.

자산관리 프로젝트에서 Phase 9-C가 ConfirmDialog 계열만 교체하고 AssignFormModal 등은 9-D 이후로 미룬 상태에서, 4개 페이지(MasterPage·TeamPage·BranchPage·DeptPage)와 LicensesPage·AccessoriesPage에서 이 패턴을 직접 적용했다.

가이드

원칙: AnimatePresence 래퍼는 남기고, shadcn으로 교체한 자식만 밖으로 뺀다.

// ✅ 교체 후 — AnimatePresence 래퍼 보존, ConfirmDialog는 밖에서 독립 선언
<AnimatePresence>
  {isFormOpen    && <AssignFormModal ... />}   // ← 유지
  {isHistoryOpen && <ProvideHistoryModal ... />}  // ← 유지
  {/* DeleteConfirmModal 제거 — shadcn Dialog는 AnimatePresence 불필요 */}
</AnimatePresence>

{/* shadcn AlertDialog — AnimatePresence 밖에서 독립 선언 */}
<ConfirmDialog
  open={isDeleteOpen}
  onOpenChange={(o) => !o && setIsDeleteOpen(false)}
  ...
/>

왜 AnimatePresence를 삭제하면 안 되는가:

AnimatePresence는 자식 컴포넌트가 React 트리에서 제거될 때 exit 애니메이션을 실행하는 역할을 한다. 래퍼를 삭제하면 아직 교체하지 않은 모달들(AssignFormModal, ProvideHistoryModal 등)의 슬라이드 인/아웃 exit 애니메이션이 즉시 사라진다. 빌드와 린트는 통과하지만 UX가 조용히 깨진다.

shadcn Dialog가 AnimatePresence 밖에서도 괜찮은 이유:

Radix Dialog/AlertDialog는 자체적으로 mount/unmount 애니메이션(data-[state=open] / data-[state=closed] CSS 속성 기반)을 처리하므로 framer-motion AnimatePresence가 필요하지 않다.

AnimatePresence 래퍼를 완전히 제거하는 시점:

래퍼 안의 모든 자식 모달이 shadcn으로 교체 완료된 Phase에서 한 번에 정리한다.

# AnimatePresence 래퍼가 남은 파일 확인
grep -rn "AnimatePresence" src/pages/ --include="*.tsx"

자산관리 프로젝트에서는 9-G(전체 마무리 Phase) 기준.

왜 중요한가

점진적 마이그레이션은 "한 번에 끝내지 않는다"는 전략적 선택이다. 이 전략이 올바르게 동작하려면 아직 교체하지 않은 부분을 깨뜨리지 않는다는 원칙이 각 단계에서 지켜져야 한다. AnimatePresence 래퍼 삭제는 범위를 초과한 조용한 회귀의 전형적인 패턴이다.

이 함정은 특히 "어차피 나중에 다 없앨 건데"라는 생각과 결합할 때 발생한다. 나중에 정리할 것을 지금 일찍 정리하는 것은 파일럿 원칙 위반이며, 지금 단계 완료 기준에도 없는 작업이다.

적용 시기

  • 적용: 기존 framer-motion 모달이 있는 프로젝트에서 shadcn 모달을 단계적으로 교체할 때
  • 적용: 여러 모달이 단일 AnimatePresence를 공유하는 구조에서 일부만 교체할 때
  • 비적용: 처음부터 shadcn으로 시작하는 프로젝트 (AnimatePresence 공유 패턴이 없음)
  • 비적용: 모든 모달을 한 Phase에서 한꺼번에 교체하는 경우 (래퍼 전체를 한 번에 제거하면 됨)

관련 자료