Feat(design-system): toast components 구현#38
Conversation
Walkthrough디자인 시스템에 접근성 중심의 Toast UI 컴포넌트와 자동 사라짐 래퍼(AutoDismissToast)를 추가하고, 이를 중앙 export에 노출하며 Storybook에 정적/수동 트리거 스토리와 플레이 테스트를 추가했습니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User as 사용자
participant SB as Storybook (ManualTrigger)
participant C as ManualTriggerExample
participant AD as AutoDismissToast
participant T as Toast
User->>SB: 스토리 로드
SB->>C: render(args)
User->>C: "토스트 띄우기" 클릭
C->>AD: visible=true, fading=false (표시)
AD->>T: children 렌더링 (Toast)
Note right of AD: duration 경과 → fading=true (fade 시작)
Note right of AD: fadeMs 경과 → visible=false, onClose 호출
AD-->>T: 언마운트
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Suggested reviewers
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. 📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
|
✅ Storybook chromatic 배포 확인: |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
packages/design-system/src/components/toast/Toast.tsx (1)
7-20: 알림 강도/의도 제어를 위한 확장성 포인트 제안현재 컴포넌트는 항상 동일한 ARIA 의미로 렌더됩니다. 소비처에서 “정보/성공/경고/에러” 등 의도를 구분하거나 알림 강도(alert vs. status)를 선택할 수 있도록 선택적 prop을 제공하면 재사용성이 좋아집니다. 예:
tone?: 'status' | 'alert'(default: 'status').예시 diff:
-export interface ToastProps { - text: string; -} +export interface ToastProps { + text: string; + /** 보조기기에 전달할 알림 강도: 비긴급('status') 또는 긴급('alert') */ + tone?: 'status' | 'alert'; +} -export default function Toast({ text }: ToastProps) { +export default function Toast({ text, tone = 'status' }: ToastProps) { return ( <div - role="alert" - aria-live="polite" + role={tone} className={cn( 'bg-gray800 text-white-bg', 'rounded-[0.8rem] px-[1.6rem] py-[1.2rem]', 'common-shadow' )} > <p className="caption2-sb whitespace-pre-line">{text}</p> </div> ); }packages/design-system/src/components/toast/Toast.stories.tsx (3)
11-15: 문서 설명과 컴포넌트의 ARIA 의미 불일치 확인 필요Docs에는
role="alert"를 명시하지만, 컴포넌트는 현재aria-live="polite"를 함께 사용하고 있어 의미 충돌이 있습니다. 컴포넌트 쪽에서 alert 또는 status로 정리되면 이 설명도 함께 맞춰 주세요. 예: “role="status"로 정중한 알림을 제공합니다.” 또는 “긴급 알림의 경우role="alert"를 사용합니다.”
29-31: 중복 render 제거로 스토리 간소화별도 렌더러 없이 기본 렌더링을 사용해도 동일하게 동작합니다. 불필요한 중복을 줄여 유지보수성을 높일 수 있습니다.
-export const Basic: Story = { - render: (args) => <Toast {...args} />, -}; +export const Basic: Story = {}; export const LongText: Story = { name: '긴 문구', args: { text: '네트워크 상태가 불안정합니다. 잠시 후 다시 시도해주세요. 문제가 계속되면 관리자에게 문의해 주세요.', }, - render: (args) => <Toast {...args} />, };Also applies to: 33-39
22-22: 문구 내 개행 뒤 선행 공백 제거 제안
'\n 다시'처럼 개행 직후 공백이 포함되어 있어whitespace-pre-line환경에서 의도치 않은 들여쓰기가 생길 수 있습니다. 다음처럼 공백을 제거하는 것을 권장합니다.- text: '저장에 실패했어요.\n 다시 시도해주세요.', + text: '저장에 실패했어요.\n다시 시도해주세요.',
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
packages/design-system/src/components/toast/Toast.stories.tsx(1 hunks)packages/design-system/src/components/toast/Toast.tsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
packages/design-system/src/components/toast/Toast.stories.tsx (1)
packages/design-system/src/components/toast/Toast.tsx (1)
Toast(7-21)
packages/design-system/src/components/toast/Toast.tsx (1)
packages/design-system/src/lib/utils.ts (1)
cn(108-110)
🔇 Additional comments (3)
packages/design-system/src/components/toast/Toast.tsx (2)
18-18: 여러 줄 텍스트 처리 LGTM
whitespace-pre-line로 개행(\n)을 자연스럽게 표시하는 접근은 요구사항과 잘 맞습니다.
10-11: (run pending)packages/design-system/src/components/toast/Toast.stories.tsx (1)
19-20: Controls 설정 적절 — 스토리에서 텍스트 변경 가능
argTypes.text에control: 'text'지정으로 문구 실험성이 확보되어 좋습니다.
| @@ -0,0 +1,39 @@ | |||
| import type { Meta, StoryObj } from '@storybook/react-vite'; | |||
There was a problem hiding this comment.
Storybook 타입 import 경로 오류 가능성(@storybook/react 권장)
타입은 일반적으로 @storybook/react에서 가져옵니다. @storybook/react-vite는 프레임워크 설정 패키지로 TS 타입을 내보내지 않는 구성이 많아 빌드가 실패할 수 있습니다. 아래처럼 수정해 주세요.
-import type { Meta, StoryObj } from '@storybook/react-vite';
+import type { Meta, StoryObj } from '@storybook/react';📝 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.
| import type { Meta, StoryObj } from '@storybook/react-vite'; | |
| import type { Meta, StoryObj } from '@storybook/react'; |
🤖 Prompt for AI Agents
In packages/design-system/src/components/toast/Toast.stories.tsx around line 1,
the Storybook type import uses '@storybook/react-vite' which may not export TS
types and can break builds; change the import to pull Meta and StoryObj from
'@storybook/react' instead so types come from the official React Storybook
package.
| role="alert" | ||
| aria-live="polite" |
into feat/#26/toast-components
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
packages/design-system/src/components/index.ts (1)
9-9: ToastProps 타입 배럴 재-export 제안 (DX 개선)검증 결과
Toast.tsx에서ToastProps가 아직 export되지 않아, 배럴 파일(packages/design-system/src/components/index.ts)에 재-export를 추가하려면 먼저Toast.tsx에 타입 export를 정의해야 합니다.
packages/design-system/src/components/toast/Toast.tsx
ToastProps타입을 export하도록 추가합니다.packages/design-system/src/components/index.ts
Toast컴포넌트와 함께ToastProps타입을 재-export합니다.--- a/packages/design-system/src/components/toast/Toast.tsx +++ b/packages/design-system/src/components/toast/Toast.tsx @@ -1,6 +1,11 @@ -import React from 'react'; +import React from 'react'; +// Toast 컴포넌트에 전달되는 Props 타입 정의 및 export +export type ToastProps = { + text: string; +}; export default function Toast({ text }: ToastProps) { return ( <div>…</div> ); }--- a/packages/design-system/src/components/index.ts +++ b/packages/design-system/src/components/index.ts export { default as Toast } from './toast/Toast'; +export type { ToastProps } from './toast/Toast';
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
packages/design-system/src/components/index.ts(1 hunks)
🔇 Additional comments (1)
packages/design-system/src/components/index.ts (1)
9-9: Toast 공개 export 추가 LGTM기존 배럴(index.ts)에서 default export를 별칭으로 노출하는 패턴(Button/Badge/Input 등)과 일관적입니다. 문제 없이 사용 가능해 보입니다.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
packages/design-system/src/components/toast/Toast.stories.tsx (1)
2-2: Storybook 타입 import 경로 수정(@storybook/react 권장)
@storybook/react-vite는 타입을 보장하지 않는 구성이 많아 빌드가 실패할 수 있습니다. 공식 타입 패키지에서 가져오도록 바꿔주세요.-import type { Meta, StoryObj } from '@storybook/react-vite'; +import type { Meta, StoryObj } from '@storybook/react';
🧹 Nitpick comments (5)
packages/design-system/src/components/toast/AutoDismissToast.tsx (2)
41-44: 사용자 선호도(prefers-reduced-motion) 존중모션 감소 설정을 사용하는 이용자에 대해 전환을 비활성화하십시오. Tailwind를 쓰고 있으므로
motion-safe/motion-reduce유틸을 활용하면 의존성 없이 처리 가능합니다.- className={cn( - 'transition-opacity ease-out', - fading ? 'opacity-0' : 'opacity-100' - )} + className={cn( + 'ease-out', + 'motion-safe:transition-opacity motion-reduce:transition-none', + fading ? 'opacity-0' : 'opacity-100' + )}
20-22: 음수 duration/fadeMs에 대한 방어적 처리외부에서 음수가 들어오면 즉시 실행·예상치 못한 타이밍이 발생할 수 있습니다. 0 이상으로 클램핑해 안정성을 높이세요.
const [visible, setVisible] = useState(true); const [fading, setFading] = useState(false); + const safeDuration = Math.max(0, duration); + const safeFadeMs = Math.max(0, fadeMs); + useEffect(() => { - const t1 = window.setTimeout(() => setFading(true), duration); + const t1 = window.setTimeout(() => setFading(true), safeDuration); const t2 = window.setTimeout(() => { setVisible(false); onClose?.(); - }, duration + fadeMs); + }, safeDuration + safeFadeMs); return () => { clearTimeout(t1); clearTimeout(t2); }; - }, [duration, fadeMs, onClose]); + }, [safeDuration, safeFadeMs, onClose]); ... - style={{ transitionDuration: `${fadeMs}ms` }} + style={{ transitionDuration: `${safeFadeMs}ms` }}Also applies to: 24-28, 44-44
packages/design-system/src/components/toast/Toast.stories.tsx (3)
33-47: 스토리 내 자동 닫힘/페이드 로직 중복 제거 → AutoDismissToast 재사용ManualTrigger 예제의 상태/타이머/트랜지션 구현이 AutoDismissToast와 중복됩니다. 디자인 시스템의 단일 소스 유지와 테스트 신뢰도를 위해 스토리에서 AutoDismissToast를 사용하세요.
import type { Meta, StoryObj } from '@storybook/react-vite'; import React from 'react'; import { within, userEvent, expect, waitFor } from '@storybook/test'; import Toast from './Toast'; +import AutoDismissToast from './AutoDismissToast'; /** 버튼 클릭으로 띄우기 → 자동 페이드아웃 */ const ManualTriggerExample: React.FC<{ text: string }> = ({ text }) => { const [show, setShow] = React.useState(false); - const [fading, setFading] = React.useState(false); - - React.useEffect(() => { - if (!show) return; - setFading(false); - const t1 = window.setTimeout(() => setFading(true), 1200); - const t2 = window.setTimeout(() => setShow(false), 1400); - return () => { - clearTimeout(t1); - clearTimeout(t2); - }; - }, [show]); return ( <div className="pb-50 p-10"> <button style={{ background: 'gray', color: 'white', padding: '8px 16px', borderRadius: 4, }} type="button" onClick={() => setShow(true)} > 토스트 띄우기 </button> - {show && ( - <div className="fixed mt-[1.6rem]"> - <div - className={`transition-opacity duration-200 ease-out ${fading ? 'opacity-0' : 'opacity-100'}`} - > - <Toast text={text} /> - </div> - </div> - )} + {show && ( + <div className="fixed mt-[1.6rem]"> + <AutoDismissToast + text={text} + duration={1200} + fadeMs={200} + onClose={() => setShow(false)} + /> + </div> + )} </div> ); };Also applies to: 63-71, 5-5
49-49: Tailwind 클래스 검증:pb-50유효성 확인 필요기본 Tailwind 스페이싱 스케일에는
50이 없을 수 있습니다. 임의 값이 필요하면 대괄호 표기 또는 근사 스케일로 교체하세요.- <div className="pb-50 p-10"> + <div className="pb-[50px] p-10">Tailwind 설정에
spacing.50이 이미 있다면 본 변경은 불필요합니다.
85-90: 인터랙션 테스트의 타임아웃을 실제 동작에 맞추어 플래키성 완화현재 스토리는 ~1.4s 내에 사라지지만 타임아웃이 2.5s로 넉넉합니다. 불필요하게 오래 기다리지 않도록 여유 버퍼(예: 400ms)만 더한 값으로 줄이는 것을 권장합니다.
- { timeout: 2500 } + { timeout: 1800 }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
packages/design-system/src/components/index.ts(1 hunks)packages/design-system/src/components/toast/AutoDismissToast.tsx(1 hunks)packages/design-system/src/components/toast/Toast.stories.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/design-system/src/components/index.ts
🧰 Additional context used
🧬 Code graph analysis (2)
packages/design-system/src/components/toast/AutoDismissToast.tsx (2)
packages/design-system/src/lib/utils.ts (1)
cn(108-110)packages/design-system/src/components/toast/Toast.tsx (1)
Toast(7-21)
packages/design-system/src/components/toast/Toast.stories.tsx (1)
packages/design-system/src/components/toast/Toast.tsx (1)
Toast(7-21)
🔇 Additional comments (3)
packages/design-system/src/components/toast/AutoDismissToast.tsx (2)
20-33: 타이머/페이드 상태 관리 전반적으로 견고합니다
- 초기 표시 → 페이드 → 언마운트 순서가 명확하고, 클린업도 잘 되어 있습니다.
- 의존성 배열에 duration/fadeMs/onClose 포함도 적절합니다.
13-19: 설계 원칙과의 정렬 확인(표시 전용 vs. 자동 닫힘 컴포넌트 병행 제공)PR 요약·이슈(#26)에서 “토스트는 표시만 담당, 자동 닫힘/위치는 상위 제어” 원칙을 강조하고 있습니다. 본 컴포넌트는 편의용 래퍼로 보이므로 공개 API로 제공하되, 기본 가이드는 Toast(표시 전용) 사용을 권장하고 AutoDismissToast는 선택적 유틸임을 문서에 명시하는지 확인 부탁드립니다. 스토리/문서에 두 접근 모두를 예시로 담으면 혼선이 줄어듭니다.
packages/design-system/src/components/toast/Toast.stories.tsx (1)
7-27: 메타/문서/컨트롤 구성 적절
- 접근성 설명과 기본 args/argTypes 구성이 명확합니다.
- 레이아웃 파라미터도 적절합니다.
into feat/#26/toast-components
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
packages/design-system/src/components/toast/Toast.stories.tsx (1)
1-1: Storybook 타입 import 경로 수정 필요(@storybook/react 권장)
Meta,StoryObj타입은@storybook/react에서 가져와야 합니다. 현 구성은 타입 미노출/호환성 문제로 빌드가 깨질 수 있습니다.적용 diff:
-import type { Meta, StoryObj } from '@storybook/react-vite'; +import type { Meta, StoryObj } from '@storybook/react';
🧹 Nitpick comments (5)
packages/design-system/src/components/toast/hooks/uesFadeOut.tsx (3)
1-50: 파일명 오타 및 역할/경로 불일치: uesFadeOut → useFadeOut 또는 AutoDismissToast.tsx로 정리현재 파일명이
uesFadeOut.tsx로 오타처럼 보입니다. 또한 “hooks” 디렉터리 하위지만 훅이 아니라 컴포넌트를 기본 export합니다. 검색·가독성·관례 측면에서:
- 파일명:
useFadeOut로 훅을 제공하거나, 현재처럼 컴포넌트를 제공한다면AutoDismissToast.tsx로 변경- 디렉터리: 컴포넌트라면
components/toast/하위로 이동하거나, 훅을 분리(useFadeOut.ts)하고 컴포넌트는 현 위치 외부로 배치Storybook과 index export 경로도 함께 업데이트해 주세요.
22-33: 타이머 타입 안정성 및 엣지 케이스 보강(Math.max 클램프, window.setTimeout/clearTimeout 사용)
- 일부 TS 설정/타겟에서
setTimeout반환 타입 충돌 가능성(브라우저 number vs NodeJS.Timeout)이 있습니다.- 음수
duration,fadeMs가 들어올 경우 즉시 타임아웃이 걸리며 의도치 않은 동작을 유발할 수 있습니다.경량 보강안을 제안드립니다.
적용 diff:
useEffect(() => { - const t1 = setTimeout(() => setFading(true), duration); - const t2 = setTimeout(() => { + const safeDuration = Math.max(0, duration); + const safeFadeMs = Math.max(0, fadeMs); + + const t1 = window.setTimeout(() => setFading(true), safeDuration); + const t2 = window.setTimeout(() => { setVisible(false); onClose?.(); - }, duration + fadeMs); + }, safeDuration + safeFadeMs); return () => { - clearTimeout(t1); - clearTimeout(t2); + window.clearTimeout(t1); + window.clearTimeout(t2); }; }, [duration, fadeMs, onClose]);
36-44: 접근성: 사용자의 ‘prefers-reduced-motion’ 설정 존중(페이드 비활성화 옵션)모션 최소화 환경에서 페이드 애니메이션을 비활성화하면 접근성이 향상됩니다. 간단히
matchMedia('(prefers-reduced-motion: reduce)')로 감지해transitionDuration: 0ms로 처리하는 방법을 권장합니다.예시 코드:
// 컴포넌트 내부 const [reduceMotion, setReduceMotion] = useState(false); useEffect(() => { const mq = window.matchMedia?.('(prefers-reduced-motion: reduce)'); if (mq) { const apply = () => setReduceMotion(mq.matches); apply(); mq.addEventListener?.('change', apply); return () => mq.removeEventListener?.('change', apply); } }, []); // ... style={{ transitionDuration: `${reduceMotion ? 0 : fadeMs}ms` }}packages/design-system/src/components/toast/Toast.stories.tsx (2)
4-5: 스토리에서 공개 API를 통한 import 고려패키지 외부 사용 예시로서 Storybook은 가능하면 공개 export 경로(components/index.ts 재export)에서 가져오는 편이 문서화/리그레션 방지에 유리합니다. 이번 PR에서 index에 export가 노출되어 있다면 그 경로를 사용해 주세요. (로컬 상대경로 유지도 무방하므로 선택 사항입니다.)
80-98: play 테스트가 사용 시나리오를 잘 검증합니다
- 버튼 클릭 →
role="alert"검사 →duration + fadeMs후 사라짐까지 확인하는 흐름이 명확합니다.- 여유 버퍼(+400ms)로 플레이크를 줄인 점도 좋습니다.
추가 제안(선택):
duration/fadeMs를 0으로 둔 엣지 케이스 스토리를 하나 더 두면 회귀 테스트에 도움이 됩니다.
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
packages/design-system/src/components/index.ts(1 hunks)packages/design-system/src/components/toast/Toast.stories.tsx(1 hunks)packages/design-system/src/components/toast/hooks/uesFadeOut.tsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/design-system/src/components/index.ts
🧰 Additional context used
🧬 Code graph analysis (2)
packages/design-system/src/components/toast/hooks/uesFadeOut.tsx (1)
packages/design-system/src/lib/utils.ts (1)
cn(108-110)
packages/design-system/src/components/toast/Toast.stories.tsx (2)
packages/design-system/src/components/toast/hooks/uesFadeOut.tsx (1)
AutoDismissToast(12-49)packages/design-system/src/components/toast/Toast.tsx (1)
Toast(7-21)
🔇 Additional comments (2)
packages/design-system/src/components/toast/hooks/uesFadeOut.tsx (1)
34-49: 상태 분리(visible/fading)와 언마운트 타이밍 설계 깔끔합니다표시/페이드/언마운트 흐름이 명확하고,
onClose시점도 페이드 종료 후로 일관적입니다. 스펙과 구현이 잘 부합합니다.packages/design-system/src/components/toast/Toast.stories.tsx (1)
43-79: 수동 트리거 예제 구조 적절 — onClose로 상위 상태 정리 보장
onClose에서setShow(false)로 정리하는 패턴이 깔끔합니다. 필요 시 여러 토스트를 관리하는 예제로 확장해도 무리 없겠습니다. (예: 배열 상태로 큐 관리)
| export interface AutoDismissToastProps { | ||
| children: React.ReactNode; | ||
| duration?: number; |
There was a problem hiding this comment.
React 타입 네임스페이스 직접 참조로 인한 TS 오류 가능성 — ReactNode를 명시 import 권장
children: React.ReactNode를 사용하면서 React 타입 네임스페이스를 가져오지 않았습니다. TS 설정에 따라 “Cannot find namespace 'React'”로 빌드가 실패할 수 있습니다. 타입만 가져오도록 수정해 주세요.
적용 diff:
-import { useEffect, useState } from 'react';
+import { useEffect, useState } from 'react';
+import type { ReactNode } from 'react';
@@
export interface AutoDismissToastProps {
- children: React.ReactNode;
+ children: ReactNode;
duration?: number;
fadeMs?: number;
onClose?: () => void;
className?: string;
}📝 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.
| export interface AutoDismissToastProps { | |
| children: React.ReactNode; | |
| duration?: number; | |
| import { useEffect, useState } from 'react'; | |
| import type { ReactNode } from 'react'; | |
| export interface AutoDismissToastProps { | |
| children: ReactNode; | |
| duration?: number; | |
| fadeMs?: number; | |
| onClose?: () => void; | |
| className?: string; | |
| } |
🤖 Prompt for AI Agents
In packages/design-system/src/components/toast/hooks/uesFadeOut.tsx around lines
4 to 6, the code uses React.ReactNode without importing React, which can cause
"Cannot find namespace 'React'". Fix by adding a type-only import and using the
imported type: import type { ReactNode } from 'react'; then change the prop to
children: ReactNode; to avoid referencing the React namespace.

📌 Related Issues
✅ 체크 리스트
📄 Tasks
⭐ PR Point (To Reviewer)
useFadeOut 훅
처음엔 false → opacity-100 클래스로 완전 보임.
duration 지나면 setFading(true) → opacity-0 클래스로 바뀌면서
transition-opacity + transitionDuration: fadeMs에 의해 서서히 사라짐.
그다음 타이머에서 visible을 false로 바꿔 언마운트(onClose 호출)되는 순서로 작동합니다.
구조를 둘로 나눈 이유
fading은 뷰를 남긴 채 투명도만 조절(애니메이션).
visible은 실제 DOM 존재 여부를 제어(언마운트 시점).
그래서 “바로 제거”가 아니라 “부드럽게 사라진 뒤 제거”되는 페이드아웃 효과를 적용했습니다
📷 Screenshot
Summary by CodeRabbit
New Features
Documentation