Skip to content

Feat(design-system): toast components 구현#38

Merged
jjangminii merged 9 commits intodevelopfrom
feat/#26/toast-components
Aug 27, 2025
Merged

Feat(design-system): toast components 구현#38
jjangminii merged 9 commits intodevelopfrom
feat/#26/toast-components

Conversation

@jjangminii
Copy link
Copy Markdown
Collaborator

@jjangminii jjangminii commented Aug 25, 2025

📌 Related Issues

관련된 Issue를 태그해주세요. (e.g. - close #25)

✅ 체크 리스트

  • PR 제목의 형식을 잘 작성했나요? e.g. Feat(client): PR 템플릿 작성
  • 빌드가 성공했나요? (pnpm build)

📄 Tasks

  • toast components 구현

⭐ PR Point (To Reviewer)

  • 텍스트에 \n이 포함되면 whitespace-pre-line으로 자연스럽게 줄바꿈 처리 되도록했습니다.
  • 고정 폭/높이 없이 내부 패딩만 지정(px-[1.6rem] py-[1.2rem]), 내용 길이에 따라 자동 확장됩니다
  • 토스트는 표시만 담당하고 자동 닫힘/위치 등 동작은 상위에서 제어하도록 했습니다.

useFadeOut 훅


  • 보이는시간(duration)과 사라지는 시간(fadeMs)을 따로 설정했어요

처음엔 false → opacity-100 클래스로 완전 보임.
duration 지나면 setFading(true) → opacity-0 클래스로 바뀌면서
transition-opacity + transitionDuration: fadeMs에 의해 서서히 사라짐.
그다음 타이머에서 visible을 false로 바꿔 언마운트(onClose 호출)되는 순서로 작동합니다.

구조를 둘로 나눈 이유
fading은 뷰를 남긴 채 투명도만 조절(애니메이션).
visible은 실제 DOM 존재 여부를 제어(언마운트 시점).

그래서 “바로 제거”가 아니라 “부드럽게 사라진 뒤 제거”되는 페이드아웃 효과를 적용했습니다

📷 Screenshot

Summary by CodeRabbit

  • New Features

    • 새로운 토스트(Toast) 컴포넌트를 추가했습니다. 접근성(alert, aria-live) 지원, 가독성 높은 스타일로 메시지를 표시합니다.
    • 자동 닫힘 토스트(AutoDismiss) 추가: 표시 시간과 페이드 지속시간 설정, 닫힘 콜백 지원으로 자동으로 사라집니다.
    • 디자인 시스템 공개 API로 제공되어 손쉽게 임포트해 사용 가능합니다.
  • Documentation

    • 스토리북 데모 추가: 버튼으로 띄우기(자동 닫힘), 정적 기본/긴 문구 예시 및 자동 사라짐 동작 검증 스토리.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Aug 25, 2025

Walkthrough

디자인 시스템에 접근성 중심의 Toast UI 컴포넌트와 자동 사라짐 래퍼(AutoDismissToast)를 추가하고, 이를 중앙 export에 노출하며 Storybook에 정적/수동 트리거 스토리와 플레이 테스트를 추가했습니다.

Changes

Cohort / File(s) Summary
Toast UI 컴포넌트
packages/design-system/src/components/toast/Toast.tsx
접근성 속성(role=alert, aria-live)과 스타일을 가진 단일 prop(text) 표시용 Toast 컴포넌트 추가.
AutoDismiss 래퍼
packages/design-system/src/components/toast/hooks/uesFadeOut.tsx
duration 후 fadeMs로 페이드아웃하고 unmount하는 AutoDismissToast 추가(visible/fading 상태, onClose 콜백, className 지원, 기본 duration=3000, fadeMs=200).
공개 API 노출
packages/design-system/src/components/index.ts
AutoDismissToastToast를 중앙 index에서 export로 추가(기존 Progress export 순서 변경 포함).
Storybook 스토리
packages/design-system/src/components/toast/Toast.stories.tsx
메타/argTypes/기본 args 정의, 정적 스토리 2종과 버튼 트리거(ManualTrigger) 스토리 및 play 자동화 테스트 추가(클릭 → 표시 → 소멸 대기).

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: 언마운트
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Assessment against linked issues

Objective Addressed Explanation
토스트 팝업 공통컴포넌트 구현 (#26)
Progress Bar 구현 및 분기 스타일링 (#25) 디자인 시스템에 Progress 관련 구현(컴포넌트/스타일/스토리)은 포함되지 않음.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Export order changed for Progress in packages/design-system/src/components/index.ts 인덱스 파일에서 export 순서가 조정됨(파일에 Progress export가 이동됨). 이 변경은 #25의 Progress 구현 목표와 직접적 관련이 없고 기능 추가가 아님.

Suggested reviewers

  • constantly-dev
  • jllee000

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 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 3c61c0a and b9c8f99.

📒 Files selected for processing (1)
  • packages/design-system/src/components/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/design-system/src/components/index.ts
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#26/toast-components

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@jjangminii jjangminii self-assigned this Aug 25, 2025
@github-actions github-actions bot added the feat 기능 개발하라 개발 달려라 달려 label Aug 25, 2025
@github-actions github-actions bot requested a review from jllee000 August 25, 2025 18:35
@github-actions
Copy link
Copy Markdown

github-actions bot commented Aug 25, 2025

✅ Storybook chromatic 배포 확인:
🐿️ storybook

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 8b443b0 and 60307fb.

📒 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.textcontrol: 'text' 지정으로 문구 실험성이 확보되어 좋습니다.

@@ -0,0 +1,39 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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.

Copy link
Copy Markdown
Member

@jllee000 jllee000 left a comment

Choose a reason for hiding this comment

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

굿! 고생하셨습니다! 기깔난 애니메이션까지 부탁훼요~
image

Comment on lines +10 to +11
role="alert"
aria-live="polite"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

aria-live 적용까지 꼼꼼하시네요

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 60307fb and cd8dd53.

📒 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 등)과 일관적입니다. 문제 없이 사용 가능해 보입니다.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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.

📥 Commits

Reviewing files that changed from the base of the PR and between cd8dd53 and 42c65fa.

📒 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 구성이 명확합니다.
  • 레이아웃 파라미터도 적절합니다.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 42c65fa and 3c61c0a.

📒 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)로 정리하는 패턴이 깔끔합니다. 필요 시 여러 토스트를 관리하는 예제로 확장해도 무리 없겠습니다. (예: 배열 상태로 큐 관리)

Comment on lines +4 to +6
export interface AutoDismissToastProps {
children: React.ReactNode;
duration?: number;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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.

@jjangminii jjangminii merged commit 9f36f04 into develop Aug 27, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 기능 개발하라 개발 달려라 달려

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 토스트 팝업 공통컴포넌트 구현

2 participants