Skip to content

[FEAT] 태그/투두 개수 제한 안내 토스트 UI 추가 - #100

Merged
kimminna merged 11 commits into
developfrom
feat/ui/99-toast-limit-notification
Jul 8, 2026
Merged

[FEAT] 태그/투두 개수 제한 안내 토스트 UI 추가#100
kimminna merged 11 commits into
developfrom
feat/ui/99-toast-limit-notification

Conversation

@kimminna

@kimminna kimminna commented Jul 6, 2026

Copy link
Copy Markdown
Member

ISSUE 🔗

close #99



What is this PR? 🔍

태그·투두 개수 제한 안내를 위한 Toast UI를 디자인 시스템에 추가하고, timo-web에서 이를 활용하는 애니메이션 래퍼와 컨테이너를 구현했습니다. 이후 리뷰에서 지적된 컴포넌트명 불일치, 영문 메시지 미대응 문제를 해결하고 컨테이너를 with-time-sidebar 레이아웃 구조로 이동했습니다.

배경

  • 기존 구조: 디자인 시스템에 사용자에게 일시적으로 메시지를 안내하는 Toast 계열 컴포넌트가 없었습니다.
  • 발생 문제: 태그는 최대 8개, 진행 중인 투두는 최대 20개로 제한할 예정이나, 한도 초과 시 사용자에게 안내할 UI가 없었고, 컨테이너는 한국어 문구만 하드코딩되어 있었습니다.
  • 해결 방향: 디자인 시스템에 경고 아이콘 기반 Toast 컴포넌트를 추가하고, timo-web에서 등장/퇴장 애니메이션과 자동 닫힘을 담당하는 래퍼로 감싸 태그/투두 각각의 안내 컨테이너를 구성했습니다. 이후 next-intl 기반으로 영문 번역을 추가하고, 컨테이너를 실제 렌더링 위치인 with-time-sidebar 레이아웃 하위로 이동했습니다.

디자인 시스템 (Toast)

  • 변경 요약: WarningWhiteIcon을 포함한 Toast 컴포넌트와 Storybook 스토리를 추가하고, 고정 너비(w-122.5)를 제거했습니다.
  • 이유: 한도 초과 등 경고성 메시지를 일관된 스타일로 노출할 공용 컴포넌트가 필요했습니다. 고정 너비는 메시지 길이와 무관하게 폭이 고정돼 레이아웃 유연성이 떨어졌습니다.
  • 구현 방식: Toastmessage: ReactNode만 받아 아이콘과 텍스트를 좌우로 배치하는 순수 표시 컴포넌트입니다. 애니메이션·타이밍 로직은 포함하지 않고, role="alert"로 스크린리더에 상태 변경을 알립니다. 너비는 내부 콘텐츠에 맞춰 유동적으로 조정됩니다.

timo-web (AnimatedToast, Container, i18n)

  • 변경 요약: 디자인 시스템 Toast를 감싸는 AnimatedToast 래퍼와 TagLimitToastContainer, TodoLimitToastContainer를 추가하고, 컨테이너를 with-time-sidebar 레이아웃 하위로 이동했습니다. 두 컨테이너의 하드코딩된 한국어 문구를 next-intl 기반 번역으로 교체했습니다.
  • 이유: 등장/퇴장 트랜지션, 일정 시간 후 자동 닫힘 같은 동작은 앱 레이어의 관심사이므로 디자인 시스템 컴포넌트와 분리했습니다. 두 컨테이너는 실제로는 with-time-sidebar 레이아웃 안에서만 사용되므로 해당 라우트 그룹 하위로 위치를 맞췄고, 영문 로케일 지원을 위해 번역 리소스가 필요했습니다.
  • 구현 방식: AnimatedToastisOpen 변경에 따라 shouldRender/isVisible 두 단계 상태로 mount 유지 시간과 transition 클래스를 제어하고, duration(기본 3000ms) 이후 onClose를 호출합니다. 두 Container는 useTranslations("Toast")messages/ko.json, messages/en.jsonToast 네임스페이스를 조회하며, 강조되는 숫자 부분은 t.rich("...", { blue: (chunks) => <span className="text-timo-blue-300">{chunks}</span> }) 형태로 렌더링합니다. 두 Container 모두 현재 isOpen 초기값을 true로 고정한 스캐폴딩 상태이며, 실제 태그/투두 개수 기반 노출 조건은 아직 연결되어 있지 않습니다.
  • 경계 · 제약: 두 Container 모두 아직 어떤 페이지에서도 import되어 있지 않습니다. 실제 태그/투두 개수 연동은 후속 작업으로 남겨두었습니다.



To Reviewers

두 Container는 여전히 실제 태그/투두 개수와 연결되지 않은 스캐폴딩 상태로, 후속 작업에서 카운트 로직과 페이지 연동이 필요합니다.
t.richblue 태그 렌더링 방식이 다른 다국어 문구에서도 재사용 가능한 패턴인지 확인해 주세요.

Screenshot 📷

image



Test Checklist ✔

  • pnpm check-types 통과
  • pnpm lint 통과
  • 브라우저에서 실제 토스트 노출/애니메이션 확인 — 미실행
  • 태그/투두 개수 제한 로직 연동 후 동작 확인 — 후속 작업

kimminna added 2 commits July 6, 2026 23:24
- 디자인 시스템에 경고 아이콘을 포함한 Toast 컴포넌트를 추가했습니다
- Toast 컴포넌트의 Storybook 스토리를 추가했습니다
- 디자인 시스템 Toast를 감싸는 애니메이션 래퍼 컴포넌트를 추가했습니다
- 태그 개수 제한 안내 컨테이너를 추가했습니다
- 투두 개수 제한 안내 컨테이너를 추가했습니다
@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
timo Ready Ready Preview, Comment Jul 8, 2026 6:33am

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

디자인 시스템에 Toast 컴포넌트와 Storybook 스토리를 신규 추가하고, timo-web에 애니메이션 처리된 AnimatedToast 래퍼 컴포넌트를 도입했다. 이를 활용해 태그(최대 8개) 및 투두(최대 20개) 개수 제한 안내 토스트 컨테이너를 구현했다.

Changes

토스트 UI 추가

Layer / File(s) Summary
디자인 시스템 Toast 컴포넌트 및 스토리
packages/timo-design-system/src/components/toast/Toast.tsx, .../toast/Toast.stories.tsx, .../components/index.ts
ToastPropsToast 컴포넌트를 정의해 아이콘과 함께 메시지를 렌더링하고, index에서 export하며 한국어/영어 Storybook 스토리를 추가했다.
AnimatedToast 애니메이션 래퍼
apps/timo-web/components/toast/AnimatedToast.tsx
isOpen 상태 변화에 따라 마운트/언마운트와 표시 애니메이션(translate/opacity)을 처리하고, duration 경과 시 자동으로 onClose를 호출하는 타이머를 구현했다.
태그/투두 제한 토스트 컨테이너
apps/timo-web/app/home/_containers/TagLimitToastContainer.tsx, .../TodoLimitToastContainer.tsx
useState로 토스트 오픈 상태를 관리하며 AnimatedToast에 최대 8개 태그 및 최대 20개 투두 안내 메시지를 전달하는 클라이언트 컴포넌트를 신규 구현했다.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Container as TagLimitToastContainer/TodoLimitToastContainer
  participant AnimatedToast
  participant Toast

  Container->>AnimatedToast: isOpen=true, message, onClose 전달
  AnimatedToast->>AnimatedToast: shouldRender=true, requestAnimationFrame으로 isVisible=true
  AnimatedToast->>Toast: message 렌더링
  Container->>AnimatedToast: onClose 호출 (isOpen=false)
  AnimatedToast->>AnimatedToast: isVisible=false, 200ms 후 shouldRender=false
  AnimatedToast->>Toast: 언마운트
Loading

Suggested labels: ✨ Feature, ⏰ Timo-web, ⌚ Timo-Design-system

Suggested reviewers: ehye1, yumin-kim2, jjangminii


🐇 토스트가 뜨고 지고, 애니메이션 살랑살랑,
태그는 여덟 개, 투두는 스무 개까지만 반짝!
새 컴포넌트 둘, 스토리북도 함께 왔네요,
리뷰어님, 가볍게 훑어봐도 좋을 것 같아요! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Toast와 래퍼는 추가됐지만 컨테이너는 아직 스캐폴딩이고 영문 메시지 대응도 미완료라 #99의 핵심 요구를 다 충족하지 못합니다. 태그/투두 실제 개수 조건에 컨테이너를 연결하고, 영문 메시지 버전까지 구현해 #99 요구사항을 완료하세요.
✅ Passed checks (4 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed 모든 변경이 토스트 UI, 래퍼, 스토리 추가로 PR 목적과 직접 일치합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed 태그·투두 개수 제한 안내용 토스트 UI 추가라는 핵심 변경을 정확히 요약합니다.
Description check ✅ Passed 토스트 UI, 애니메이션 래퍼, 컨테이너, 다국어 지원 등 변경 내용과 전반적으로 일치합니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ui/99-toast-limit-notification

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

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

@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: 6

🤖 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 `@apps/timo-web/app/home/_containers/TagLimitToastContainer.tsx`:
- Line 10: The TagLimitToastContainer still has a Korean-only TODO, so wire its
toast text through the existing English/i18n path instead of leaving a
placeholder. Update the message source in TagLimitToastContainer to use the same
localized content pattern used by Toast.stories.tsx and any shared translation
helper, so the container renders the correct language at runtime.
- Line 7: The exported component name is inconsistent with the file name, so
rename TagLimitTagContainer to match TagLimitToastContainer and update any
related references accordingly. Use the existing export in
TagLimitToastContainer.tsx as the source of truth, and keep the
component/function name aligned with the file for easier future imports and
maintenance.

In `@apps/timo-web/app/home/_containers/TodoLimitToastContainer.tsx`:
- Line 10: The TodoLimitToastContainer still has a Korean TODO placeholder and
needs the same multilingual message handling as TagLimitToastContainer. Update
TodoLimitToastContainer to use the app’s i18n/message lookup flow at the toast
render point instead of leaving a hardcoded TODO, and align the implementation
with the existing localization pattern used in TagLimitToastContainer so the
user-facing text is language-aware.

In `@apps/timo-web/components/toast/AnimatedToast.tsx`:
- Around line 1-70: AnimatedToast is placed in a shared components path even
though the architecture doc says domain-only state/timer UI should live under
app/**/_containers/. Move the AnimatedToast component out of components/toast
and into the appropriate container location used by its domain, and keep its
state/timeout logic there. Update any imports/usages so the unique symbol
AnimatedToast continues to work from its new location.
- Around line 46-54: The auto-close effect in AnimatedToast re-runs whenever
onClose gets a new function reference, which can reset the timer unexpectedly.
Update the useEffect logic to avoid depending directly on onClose by storing the
latest callback in a ref and invoking that ref inside the timeout, while keeping
the timer effect keyed only to the open/duration state.

In `@packages/timo-design-system/src/components/toast/Toast.tsx`:
- Line 15: `Toast`의 `WarningWhiteIcon`은 장식용이라 스크린리더 중복 읽기를 막아야 합니다. `Toast`
컴포넌트에서 아이콘 렌더링에 `aria-hidden="true"`를 추가해 `message` 텍스트만 읽히도록 수정하세요.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 82acbb41-2a05-40eb-888d-4662b5866780

📥 Commits

Reviewing files that changed from the base of the PR and between 258e937 and 67d528c.

⛔ Files ignored due to path filters (1)
  • packages/timo-design-system/src/icons/source/warning-white.svg is excluded by !**/*.svg
📒 Files selected for processing (6)
  • apps/timo-web/app/home/_containers/TagLimitToastContainer.tsx
  • apps/timo-web/app/home/_containers/TodoLimitToastContainer.tsx
  • apps/timo-web/components/toast/AnimatedToast.tsx
  • packages/timo-design-system/src/components/index.ts
  • packages/timo-design-system/src/components/toast/Toast.stories.tsx
  • packages/timo-design-system/src/components/toast/Toast.tsx

Comment thread apps/timo-web/app/home/_containers/TagLimitToastContainer.tsx Outdated
Comment thread apps/timo-web/components/toast/AnimatedToast.tsx
Comment thread apps/timo-web/components/toast/AnimatedToast.tsx Outdated
Comment thread packages/timo-design-system/src/components/toast/Toast.tsx
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

Storybook Preview

항목 링크
Storybook 열기
Chromatic 빌드 확인

마지막 업데이트: 2026-07-08 06:33 UTC

@kimminna kimminna self-assigned this Jul 6, 2026
@kimminna kimminna added the ♦️ 민아 민아상 label Jul 6, 2026
- onClose 콜백을 ref에 저장해 참조 변경과 무관하게 최신 콜백을 호출하도록 했습니다
- 자동 닫힘 타이머 effect의 의존성을 isOpen, duration으로만 제한했습니다
kimminna added 2 commits July 7, 2026 15:50
- TagLimitToastContainer, TodoLimitToastContainer를 app/home/_containers에서 app/[locale]/(main)/home/_containers/toast로 이동했습니다
- 더 이상 사용하지 않는 components/.gitkeep 파일을 제거했습니다

@jjangminii jjangminii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

구조 분리가 깔끔하게 잘 됐어요! 디자인 시스템의 Toast는 순수 표시 컴포넌트로만 두고 애니메이션/타이밍 로직은 AnimatedToast로 분리해서, 디자인 시스템이 앱 레이어의 관심사를 전혀 몰라도 되는 구조라 좋네요👍
사소한 코멘트 몇 가지 남겨뒀으니 확인 부탁드려요~

Comment on lines +24 to +25
const [shouldRender, setShouldRender] = useState<boolean>(isOpen);
const [isVisible, setIsVisible] = useState<boolean>(false);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

isOpen=true로 props가 들어오면 shouldRender=true로 초기화되는데, isVisible은 항상 false로 시작하고 있어요.

그러면 첫 렌더 시 토스트가 DOM에는 존재하지만(shouldRender=true) 투명한 상태(isVisible=false → opacity-0)로 잠깐 보이다가, requestAnimationFrame 콜백이 실행된 후에야 isVisible=true가 되면서 나타나게 될 것 같아요.

지금 Container들이 isOpen 초기값을 true로 주고 있어서 실제로 이 경로로 실행되고 있는 것 같은데, 의도한 동작이 맞는지 확인 부탁드려요!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

shouldRender가 isVisible 보다 먼저 true가 되어야 페인트되고, 그 다음 프레임에서 isVisible=true로 바뀌면서 css 트랜지션이 걸려야 해서 의도된 동작이 맞습니다!

export const Toast = ({ message }: ToastProps) => {
return (
<div
role="status"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

role="status"는 스크린리더가 현재 읽고 있는 내용을 마친 후에 안내해줘요. 그런데 태그/투두 한도 초과는 사용자가 즉시 알아야 하는 경고 메시지에 가까워서 현재 읽고 있는 내용을 중단하고 바로 안내해주는 role="alert"가 더 적합하다고 생각해요-!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

그렇네요! 수정해서 반영해 두겠습니다

- role="status"를 role="alert"로 변경했습니다
- 태그/투두 한도 초과 등 사용자가 즉시 인지해야 하는 경고 메시지이므로 assertive live region이 적절합니다
kimminna added 2 commits July 8, 2026 15:30
고정 너비(w-122.5)를 제거하여 메시지 길이에 따라 너비가 유동적으로 조정되도록 했습니다.
토스트 컨테이너를 with-time-sidebar 레이아웃 하위로 이동하고, next-intl 기반 영문 번역을 추가했습니다.

@yumin-kim2 yumin-kim2 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

토스트 생긴 것만 담당하는 부분(Toast)이랑, 언제 뜨고 언제 꺼질지 정하는 부분(AnimatedToast)의 구조를 나눠주신 게 정말 짱짱맨입니다. 이렇게 해두면 나중에 다른 곳에서 이 토스트 갖다 쓸 때 재사용성이 높아지겠네용 수고하셨습니다~~~~~!!

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

Labels

⌚ Timo-Design-system Timo 디자인 시스템 ⏰ Timo-web Timo 웹 서비스 ♦️ 민아 민아상

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] 태그/투두 개수 제한 안내 토스트 UI 추가

3 participants