Skip to content

[✨ Feat/#51] ConfirmModal 공통 컴포넌트 구현#52

Open
Lseojeong wants to merge 9 commits into
devfrom
feat/#51
Open

[✨ Feat/#51] ConfirmModal 공통 컴포넌트 구현#52
Lseojeong wants to merge 9 commits into
devfrom
feat/#51

Conversation

@Lseojeong

@Lseojeong Lseojeong commented Jun 29, 2026

Copy link
Copy Markdown
Member

#️⃣연관된 이슈

체크 사항

  • UI 동작 및 레이아웃 확인
  • 불필요한 console 제거
  • 기능 정상 동작
  • 팀 컨벤션에 맞게 구현했는지

📝작업 내용

ConfirmModal 공통 컴포넌트 구현

  • Surfacemodal variant를 기반으로 ConfirmModal을 구현했습니다.
  • 제목, 설명, 확인 버튼, 취소 버튼을 지원합니다.
  • onConfirm, onCancel 이벤트를 제공합니다.
  • isLoading 상태에서 확인 버튼 로딩 표시와 dismiss 닫기 방지를 처리했습니다.
  • 접근성을 위해 aria-labelledby, aria-describedby를 연결했습니다.

Storybook 문서화

  • Shared/ConfirmModal Storybook을 추가했습니다.

Surface 애니메이션 및 유틸 정리

  • 중앙 모달에 overlay fade-in, modal slide-up 애니메이션을 적용했습니다.
  • prefers-reduced-motion 환경에서는 애니메이션을 비활성화합니다.
  • Slot 유틸을 shared/ui 내부에서 shared/utils로 이동해 공용 유틸 위치로 정리했습니다.

스크린샷 (선택)

image

추가한 라이브러리 (선택)

  • 없음

💬리뷰 요구사항(선택)

Summary by CodeRabbit

  • New Features

    • 새 확인 모달이 추가되어, 제목·설명·확인/취소 버튼을 갖춘 표준 확인 흐름을 제공합니다.
    • 모달 오버레이와 콘텐츠에 부드러운 등장 애니메이션이 적용되었습니다.
    • 로딩 중에는 닫기 동작이 제한되고 취소 버튼이 비활성화되어 더 안전하게 동작합니다.
  • Documentation

    • 확인 모달의 다양한 사용 예시가 Storybook에 추가되었습니다.
  • Bug Fixes

    • 모션 감소 설정을 사용하는 환경에서는 애니메이션이 자동으로 비활성화됩니다.
    • 전역 스타일에 관련 컴포넌트 스타일이 포함되도록 정리되었습니다.

@Lseojeong Lseojeong self-assigned this Jun 29, 2026
@Lseojeong Lseojeong added the ✨Feature 새로운 기능 구현 label Jun 29, 2026
@Lseojeong Lseojeong linked an issue Jun 29, 2026 that may be closed by this pull request
8 tasks
@vercel

vercel Bot commented Jun 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
rewrite Ready Ready Preview, Comment Jun 29, 2026 12:44pm

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

워크스루

ConfirmModal 공통 컴포넌트를 신규 추가하고, Surface 모달 애니메이션을 CSS 키프레임 기반으로 전환한다. 슬롯 유틸 import 경로를 @/shared/utils/slot으로 통일하고, 버튼 테두리 두께를 border-2에서 border로 변경한다.

변경 사항

ConfirmModal 컴포넌트 추가 및 Surface 개선

Layer / File(s) 요약
Surface 모달 CSS 애니메이션 정의 및 적용
src/shared/styles/components/surface.css, src/shared/styles/globals.css, src/shared/ui/surface/SurfaceContent.tsx, src/shared/ui/surface/SurfaceOverlay.tsx
surface.css에 오버레이 페이드인(surfaceOverlayFadeIn)·모달 슬라이드업(surfaceModalSlideUp) 키프레임과 클래스를 추가하고 globals.css에 import한다. SurfaceContent는 modal variant에 surface-modal-animation, SurfaceOverlaysurface-modal-overlay-animation을 적용하도록 변경한다. prefers-reduced-motion 미디어 쿼리로 두 애니메이션을 비활성화한다.
ConfirmModalProps 타입 정의 및 배럴 export
src/shared/ui/confirm-modal/ConfirmModal.types.ts, src/shared/ui/confirm-modal/index.ts
ConfirmModalProps 인터페이스(버튼 문구, 닫기/포커스/스크롤 옵션, 콜백, 필수 title 등)를 정의하고, index.ts에서 ConfirmModal을 재export한다.
ConfirmModal 컴포넌트 구현
src/shared/ui/confirm-modal/ConfirmModal.tsx
Surface(variant="modal") 기반 Portal/Overlay/Content/Header/Footer 구조를 구성한다. useIdaria-labelledby/aria-describedby를 연결하고, isLoading=true일 때 canClose/ESC/외부 클릭을 비활성화하며 취소 버튼을 disabled, 확인 버튼에 로딩 표시를 적용한다.
ConfirmModal Storybook 스토리
src/shared/ui/confirm-modal/ConfirmModal.stories.tsx, .storybook/main.ts
Default, Loading, Controlled, LongDescription 4가지 스토리와 예제 컴포넌트를 추가한다. metaargTypes/controls/배경색 파라미터를 설정하고, Storybook typescript.reactDocgenreact-docgen-typescript로 지정한다.
슬롯 import 경로 통일 및 버튼 테두리 조정
src/shared/ui/surface/SurfaceClose.tsx, src/shared/ui/surface/SurfaceTrigger.tsx, src/shared/ui/accordion/AccordionLabel.tsx, src/shared/ui/button/buttonVariants.ts
슬롯 유틸 import 경로를 @/shared/ui/utils/Slot@/shared/utils/slot으로 통일하고, buttonVariantsprimary/ghost/outline variant 테두리를 border-2에서 border로 변경한다.

추정 코드 리뷰 난이도

🎯 3 (Moderate) | ⏱️ ~20 minutes

관련 PR

  • Rewrite-Team/Rewrite-FE#50: SurfaceContent/SurfaceOverlay 클래스 구성 변경이 해당 PR에서 구현된 Surface 복합 컴포넌트를 직접 수정한다.
  • Rewrite-Team/Rewrite-FE#40: buttonVariants.ts의 테두리 두께 조정이 해당 PR에서 구현된 공통 Button/buttonVariants와 동일한 파일을 수정한다.
  • Rewrite-Team/Rewrite-FE#48: AccordionLabel.tsx의 import 경로 변경이 해당 PR에서 구현된 AccordionLabel과 동일한 파일을 수정한다.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 ConfirmModal 공통 컴포넌트 구현이라는 핵심 변경을 정확히 요약합니다.
Linked Issues check ✅ Passed ConfirmModal, 타이틀/설명, 확인·취소 버튼, 로딩 상태, 이벤트, Storybook 요구사항을 모두 반영했습니다.
Out of Scope Changes check ✅ Passed 애니메이션, Storybook 설정, Slot 이동 등도 모달 구현과 문서화를 위한 지원 작업으로 보여 명확한 범위 이탈은 없습니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/#51

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

🧪 테스트 결과

항목 결과
✅ Jest 테스트 success

@github-actions

Copy link
Copy Markdown

🚦 CI 검증 결과

항목 결과
🧠 TypeScript 타입 체크 success
🧹 ESLint 검사 success
🎨 Prettier 검사 success
🏗️ Build 검증 success

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/shared/ui/confirm-modal/index.ts (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

배럴에서 ConfirmModalProps도 함께 내보내세요.

지금 공개 엔트리에서는 컴포넌트만 import할 수 있어서, 소비자 쪽에서 props 타입을 쓰려면 구현 파일로 깊은 import를 해야 합니다. shared UI 공개 경로를 추가한 PR이라면 타입도 같은 배럴에서 re-export하는 편이 API 표면을 안정적으로 유지합니다.

예시 변경
 export { ConfirmModal } from './ConfirmModal';
+export type { ConfirmModalProps } from './ConfirmModal';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/ui/confirm-modal/index.ts` at line 1, The confirm-modal barrel
currently re-exports only ConfirmModal, so consumers cannot import
ConfirmModalProps from the public entry. Update the index barrel in the
confirm-modal module to also re-export ConfirmModalProps from the same source as
ConfirmModal, keeping the shared UI API surface stable and avoiding deep
imports.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/shared/styles/components/surface.css`:
- Around line 1-29: The keyframes in surface.css use non-kebab-case names, so
update both `@keyframes` identifiers and every animation reference in the
surfaceOverlayFadeIn and surfaceModalSlideUp styles to kebab-case consistently.
Make sure the .surface-modal-overlay-animation and .surface-modal-animation
rules point to the renamed keyframes so Stylelint no longer flags the animation
names.

In `@src/shared/ui/confirm-modal/ConfirmModal.stories.tsx`:
- Around line 69-84: The LoadingConfirmModalExample story can get stuck because
ConfirmModal stays non-dismissible while isLoading is true and there is no way
to release it. Update LoadingConfirmModalExample in ConfirmModal.stories.tsx so
the example has a reset/close path, such as toggling isLoading off after a short
timer or adding a separate control to clear loading and close the modal; use the
existing LoadingConfirmModalExample and ConfirmModal props as the place to fix
it.

In `@src/shared/ui/confirm-modal/ConfirmModal.tsx`:
- Around line 92-95: ConfirmModal의 description 렌더링이 ReactNode를 항상 p로 감싸서 블록 요소가
들어올 때 잘못된 중첩이 생깁니다. ConfirmModalProps.description과 ConfirmModal 컴포넌트의
description 처리 방식을 함께 확인해서, description 타입을 문자열/인라인 콘텐츠로 제한하거나 렌더링 컨테이너를 p에서
div로 변경해 다양한 ReactNode를 안전하게 받도록 수정하세요.

---

Nitpick comments:
In `@src/shared/ui/confirm-modal/index.ts`:
- Line 1: The confirm-modal barrel currently re-exports only ConfirmModal, so
consumers cannot import ConfirmModalProps from the public entry. Update the
index barrel in the confirm-modal module to also re-export ConfirmModalProps
from the same source as ConfirmModal, keeping the shared UI API surface stable
and avoiding deep imports.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8c176748-2548-4204-b1ea-6e622ba8913d

📥 Commits

Reviewing files that changed from the base of the PR and between f883ac2 and 9736403.

📒 Files selected for processing (14)
  • .storybook/main.ts
  • src/shared/styles/components/surface.css
  • src/shared/styles/globals.css
  • src/shared/ui/accordion/AccordionLabel.tsx
  • src/shared/ui/button/buttonVariants.ts
  • src/shared/ui/confirm-modal/ConfirmModal.stories.tsx
  • src/shared/ui/confirm-modal/ConfirmModal.tsx
  • src/shared/ui/confirm-modal/ConfirmModal.types.ts
  • src/shared/ui/confirm-modal/index.ts
  • src/shared/ui/surface/SurfaceClose.tsx
  • src/shared/ui/surface/SurfaceContent.tsx
  • src/shared/ui/surface/SurfaceOverlay.tsx
  • src/shared/ui/surface/SurfaceTrigger.tsx
  • src/shared/utils/slot.ts

Comment on lines +1 to +29
@keyframes surfaceOverlayFadeIn {
from {
opacity: 0;
}

to {
opacity: 1;
}
}

@keyframes surfaceModalSlideUp {
from {
opacity: 0;
transform: translate3d(0, 40px, 0);
}

to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}

.surface-modal-overlay-animation {
animation: surfaceOverlayFadeIn 350ms ease-out both;
}

.surface-modal-animation {
will-change: transform, opacity;
animation: surfaceModalSlideUp 350ms ease-out both;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

키프레임 이름을 kebab-case로 맞춰 주세요.

Stylelint가 현재 surfaceOverlayFadeIn, surfaceModalSlideUp를 규칙 위반으로 보고 있습니다. @keyframes 이름과 animation 참조를 함께 바꾸지 않으면 린트가 계속 깨집니다.

수정 예시
-@keyframes surfaceOverlayFadeIn {
+@keyframes surface-overlay-fade-in {
@@
-@keyframes surfaceModalSlideUp {
+@keyframes surface-modal-slide-up {
@@
 .surface-modal-overlay-animation {
-  animation: surfaceOverlayFadeIn 350ms ease-out both;
+  animation: surface-overlay-fade-in 350ms ease-out both;
 }
@@
 .surface-modal-animation {
   will-change: transform, opacity;
-  animation: surfaceModalSlideUp 350ms ease-out both;
+  animation: surface-modal-slide-up 350ms ease-out both;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@keyframes surfaceOverlayFadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes surfaceModalSlideUp {
from {
opacity: 0;
transform: translate3d(0, 40px, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
.surface-modal-overlay-animation {
animation: surfaceOverlayFadeIn 350ms ease-out both;
}
.surface-modal-animation {
will-change: transform, opacity;
animation: surfaceModalSlideUp 350ms ease-out both;
`@keyframes` surface-overlay-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
`@keyframes` surface-modal-slide-up {
from {
opacity: 0;
transform: translate3d(0, 40px, 0);
}
to {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
.surface-modal-overlay-animation {
animation: surface-overlay-fade-in 350ms ease-out both;
}
.surface-modal-animation {
will-change: transform, opacity;
animation: surface-modal-slide-up 350ms ease-out both;
🧰 Tools
🪛 Stylelint (17.13.0)

[error] 1-1: Expected keyframe name "surfaceOverlayFadeIn" to be kebab-case (keyframes-name-pattern)

(keyframes-name-pattern)


[error] 11-11: Expected keyframe name "surfaceModalSlideUp" to be kebab-case (keyframes-name-pattern)

(keyframes-name-pattern)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/styles/components/surface.css` around lines 1 - 29, The keyframes
in surface.css use non-kebab-case names, so update both `@keyframes` identifiers
and every animation reference in the surfaceOverlayFadeIn and
surfaceModalSlideUp styles to kebab-case consistently. Make sure the
.surface-modal-overlay-animation and .surface-modal-animation rules point to the
renamed keyframes so Stylelint no longer flags the animation names.

Source: Linters/SAST tools

Comment on lines +69 to +84
function LoadingConfirmModalExample() {
const [isOpen, setIsOpen] = useState(false);

return (
<div className="flex min-h-80 items-center justify-center">
<Button onClick={() => setIsOpen(true)}>Loading ConfirmModal 열기</Button>
<ConfirmModal
cancelLabel="아니오"
confirmLabel="저장하기"
description="저장 중에는 모달을 닫을 수 없습니다."
isLoading
onConfirm={() => undefined}
onOpenChange={setIsOpen}
open={isOpen}
title="해당 자기소개서를 저장하시겠습니까?"
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Loading 스토리는 한 번 열면 캔버스에서 빠져나올 수 없습니다.

ConfirmModalisLoading일 때 취소 버튼, ESC, 바깥 클릭 dismiss를 모두 막는데 이 예제는 로딩을 해제하거나 리셋할 경로가 없습니다. 문서 예제가 그대로 갇힌 상태가 되므로, 타이머로 isLoading을 풀어주거나 별도 reset/close 컨트롤을 두는 편이 좋겠습니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/ui/confirm-modal/ConfirmModal.stories.tsx` around lines 69 - 84,
The LoadingConfirmModalExample story can get stuck because ConfirmModal stays
non-dismissible while isLoading is true and there is no way to release it.
Update LoadingConfirmModalExample in ConfirmModal.stories.tsx so the example has
a reset/close path, such as toggling isLoading off after a short timer or adding
a separate control to clear loading and close the modal; use the existing
LoadingConfirmModalExample and ConfirmModal props as the place to fix it.

Comment on lines +92 to +95
{description ? (
<p className="m-0 body-14 font-medium text-gray-100" id={descriptionId}>
{description}
</p>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

description<p>로 고정하면 조합 가능한 ReactNode와 충돌합니다.

ConfirmModalProps.descriptionReactNode인데 여기서는 항상 <p> 안에 렌더링하고 있습니다. 호출부가 블록 요소나 다른 문단을 넘기면 잘못된 HTML 중첩이 생기므로, 타입을 문자열/인라인 콘텐츠로 좁히거나 컨테이너를 <div>로 바꾸는 쪽이 안전합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/shared/ui/confirm-modal/ConfirmModal.tsx` around lines 92 - 95,
ConfirmModal의 description 렌더링이 ReactNode를 항상 p로 감싸서 블록 요소가 들어올 때 잘못된 중첩이 생깁니다.
ConfirmModalProps.description과 ConfirmModal 컴포넌트의 description 처리 방식을 함께 확인해서,
description 타입을 문자열/인라인 콘텐츠로 제한하거나 렌더링 컨테이너를 p에서 div로 변경해 다양한 ReactNode를 안전하게
받도록 수정하세요.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨Feature 새로운 기능 구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[✨ Feature] ConfirmModal 공통 컴포넌트 구현

1 participant