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에서 한 번에 정리한다.
자산관리 프로젝트에서는 9-G(전체 마무리 Phase) 기준.
왜 중요한가¶
점진적 마이그레이션은 "한 번에 끝내지 않는다"는 전략적 선택이다. 이 전략이 올바르게 동작하려면 아직 교체하지 않은 부분을 깨뜨리지 않는다는 원칙이 각 단계에서 지켜져야 한다. AnimatePresence 래퍼 삭제는 범위를 초과한 조용한 회귀의 전형적인 패턴이다.
이 함정은 특히 "어차피 나중에 다 없앨 건데"라는 생각과 결합할 때 발생한다. 나중에 정리할 것을 지금 일찍 정리하는 것은 파일럿 원칙 위반이며, 지금 단계 완료 기준에도 없는 작업이다.
적용 시기¶
- 적용: 기존 framer-motion 모달이 있는 프로젝트에서 shadcn 모달을 단계적으로 교체할 때
- 적용: 여러 모달이 단일 AnimatePresence를 공유하는 구조에서 일부만 교체할 때
- 비적용: 처음부터 shadcn으로 시작하는 프로젝트 (AnimatePresence 공유 패턴이 없음)
- 비적용: 모든 모달을 한 Phase에서 한꺼번에 교체하는 경우 (래퍼 전체를 한 번에 제거하면 됨)
관련 자료¶
- 실제 적용 커밋: AssetManagement/Frontend
cf9a2ef(Phase 9-C) - 적용 파일:
src/pages/org/TeamPage.tsx,src/pages/org/BranchPage.tsx,src/pages/org/DeptPage.tsx,src/pages/accessories/AccessoriesPage.tsx,src/pages/licenses/LicensesPage.tsx,src/pages/master/MasterPage.tsx - 작업지시서 함정 5번: Planning/작업_지시서_Phase9C_Dialog_모달_교체
- 관련 기록: BestPractices/2026-04-10-shadcn-파일럿-검증-패턴-터치영역-함정 — 파일럿 원칙(공유 컴포넌트 수정 금지)