Files
HMDesign/docs/components/sonner.md
T

8.0 KiB
Raw Blame History

Sonner (Toast)

현재 HM 구현의 기준 문서. 공통 원칙은 docs/guidelines/프로젝트-지침.md(§6, §7), Token 구조 원칙은 테마-토큰-가이드.md를 따른다. 이 문서는 Toast 고유의 API·Token·상태·결정만 기록한다. public export가 Toaster / toast이고 shadcn 컴포넌트명이 Sonner이므로 문서명은 sonner.md로 하며 toast.md는 만들지 않는다.

1. 상태

항목 내용
구현 상태 구현 완료 (현재 HM 규격)
기준일 2026-10-03
기반 shadcn/ui Sonner (sonner 2.0.8)
대표 Story Components/Feedback/Sonner → All (이름 "Toast")
보류 아래 §11

2. 적용 근거

  • 참고 근거: ui-viz에는 Toast 규격이 없고 color.status.success|warning|information은 TBD다. 새 Status Palette를 만들지 않았다.
  • 현재 HM 규격: surface·text·radius·shadow는 현재 HM Semantic(surface, fg, stroke, destructive)에서 정했다.
  • 구현 방식: Sonner가 queue·stack·timeout·hover pause·swipe·promise·aria-live를 소유한다. 오래된 Radix Toast는 만들지 않는다(Toast는 Sonner 하나).

3. Alert / Dialog와 차이

성격 닫힘 focus
Alert 화면 안에 지속적으로 존재하는 in-page feedback 사용자가 처리하거나 화면이 바뀔 때까지 흐름을 막지 않음
Toast 일시적인 global notification (저장 완료, 오류, 비동기 진행) timeout 또는 닫기 / action 흐름을 막지 않음
Alert Dialog 사용자의 응답이 필요한 modal 확인 응답으로만 focus trap

Toast는 anchored floating panel이 아니라 notification surface이므로 Floating Surface Semantic(--hm-sem-floating-*)을 쓰지 않고 --hm-toast-*를 별도로 둔다.

4. 기본 규격 / 상태 규격

항목 값
Width 356px (Sonner 기본 유지), 좁은 화면(≤600px)은 Sonner가 좌우 16px offset으로 줄임
Surface --hm-sem-bg-surface, 1px --hm-sem-stroke-input-default, radius --hm-radius-container(4px), 그림자 0 0 12px rgb(0 0 0 / .2)
Padding / gap 16px / 12px
Title 14 / 20 / 500, Description 13 / 18 / 400 --hm-sem-fg-secondary
Icon 16px, shadcn(lucide) CircleCheck·Info·TriangleAlert·OctagonX·LoaderCircle(spin)
Default / Success / Info / Warning / Loading 중립 면. 의미는 icon과 문구가 전달한다
Error (data-type="error") 윤곽·icon·title --hm-sem-action-destructive (Alert destructive와 같은 방침)
Action 높이 28px, radius 4px, 12/500, --hm-sem-action-primary 면 + --hm-sem-on-action-primary 글자
Cancel --hm-sem-bg-subtle 면 + --hm-sem-fg-secondary 글자
Close button Sonner closeButton 옵션. 면·윤곽은 Toast와 같고 글자 --hm-sem-fg-secondary

Action은 HM Button을 넣지 않고 Sonner의 action / cancel 버튼을 Toast 전용 token으로 스타일한다(Button token 상속 없음).

5. API

import { Toaster, toast } from "@hm-design/ui"
// 앱 root에 한 번:  <Toaster />
toast("저장했습니다")
toast.success("..."); toast.error("..."); toast.warning("..."); toast.info("..."); toast.loading("...")
toast.promise(promise, { loading, success, error })
toast("삭제했습니다", { action: { label: "실행 취소", onClick }, cancel: { label, onClick }, closeButton: true, duration })
toast.dismiss()
  • Toaster는 Sonner Toaster에 shadcn icon set만 주입한 것이다. position, closeButton, expand, richColors, duration, offset 등 Sonner props를 그대로 받는다. HM 전용 prop과 position system은 없다.
  • toast는 sonner의 toast를 re-export한다. 소비자는 sonner를 직접 import하거나 설치할 필요가 없다. 타입 ToasterProps, ExternalToast도 export한다.
  • Light/Dark 전환(next-themes)은 쓰지 않는다. theme은 Sonner 기본값이다.
  • richColors를 켜면 Sonner의 자체 상태 색이 적용되어 HM 규격 밖이 되므로 쓰지 않는다.

6. Token

Component Mapping 위치: packages/ui/src/theme/sonner.css (--hm-toast-*).

width, background, text, border-width, border-color, radius, shadow, padding, gap, font-family, title-*, description-*, icon-size, icon-color, error-border-color, error-text, action-*, cancel-*, close-*. Semantic 또는 Theme palette만 참조한다. 신규 Primitive·Semantic 없음.

Sonner 변수(--width, --border-radius, --normal-bg|border|text)는 [data-sonner-toaster]에서 이 token으로 연결한다. 상태 hook은 Sonner의 자체 속성([data-sonner-toast], [data-type], [data-button], [data-cancel], [data-close-button])이다. Sonner CSS가 런타임에 주입되므로 selector에 속성을 겹쳐 specificity를 확보했다.

Notification Semantic 후보(미승격): Toast 표면 값이 Floating surface와 현재 같지만 역할이 달라 별도 Semantic으로 승격하지 않았다. Status(success·warning·information) 색이 확정되면 --hm-sem-status-* 후보와 함께 검토한다.

7. shadcn / Sonner에서 유지한 항목

queue·stack·expand·hover pause·swipe·timeout·promise lifecycle, section[aria-live="polite"](label Notifications alt+T, aria-relevant), 단축키(Alt+T로 focus), toast의 tabindex=0, 기본 position(bottom-right), 모바일 offset·폭 처리, shadcn icon set.

8. HM에서 변경한 항목

  • shadcn Sonner의 next-themes·--popover 계열 inline style을 제거하고 theme CSS로 대체.
  • Sonner 기본 font(ui-sans-serif)를 HM font family로, 기본 radius 8px를 4px로 변경.
  • action 버튼의 Sonner 기본 반전색(--normal-bg/--normal-text)을 HM primary로 변경.

9. Storybook

  • 대표 Story All("Sonner"): 버튼으로 실제 Toast를 생성한다 — Types(Default·Success·Error·Warning·Info·Loading), Action / Action + Cancel / Close button, Promise(→ Success / → Error, 로컬 timeout), Stack ×3 / Long message / Dismiss all.
  • Toaster는 .storybook/preview.tsx에 한 번만 둔다(Story별 중복 없음). 정적 Toast specimen은 없다.
  • Root Demo에는 추가하지 않았다(필수 아님).

10. 사용자 확정 결정

  • 2026-10-03: Toast는 Sonner 하나로 통일한다(Radix Toast 미사용). Floating Semantic과 분리된 Notification UI로 설계한다. 새 Status Palette를 임의로 만들지 않는다(작업 지시).

11. 미확정 / 보류

  • Success / Warning / Info의 상태 색은 ui-viz에서 TBD라 중립 면으로 두었다. 확정되면 --hm-toast-<status>-*를 추가한다.
  • 수치(356px, 16px padding, 28px action 등)는 현재 HM 규격이며 디자인 확정 시 재검토.
  • 한국어 문장이 좁은 폭에서 어절 중간에서 줄바꿈될 수 있다(Sonner 기본 word-break). 규격이 정해지면 keep-all 검토.
  • position 비교, 키보드 focus 이동(Alt+T → Tab), 스크린 리더 낭독, swipe 닫기는 확인하지 못했다.

12. 관련 파일

packages/ui/src/components/ui/sonner.tsx
packages/ui/src/theme/sonner.css
packages/ui/src/theme.css            (import)
packages/ui/src/index.ts             (export)
packages/ui/package.json             (dependency: sonner)
.storybook/preview.tsx               (Toaster 1회)
src/components/sonner.stories.tsx

13. 검증 상태

  • npm run typecheck, npm run build: 통과(2026-10-03).
  • 실제 interaction(Browser pane): Default·Success·Error·Warning·Info·Loading 생성, Stack ×3(겹침과 hover 펼침), Action + Cancel 표시와 Action 클릭 시 닫힘, Close button 표시, Dismiss all, Promise → Error(loading → error 전환, 윤곽·icon·title destructive), timeout 자동 닫힘.
  • DOM: section[aria-live=polite][aria-relevant="additions text"], label Notifications alt+T, toast tabindex=0.
  • computed style: surface white, border rgb(222,219,210), radius 4px, shadow 0 0 12px .2, width 356px, padding 16px, title 14px, action rgb(18,51,40)·28px.
  • 390px: 폭 358px(좌우 16px), 가로 스크롤 없음, 긴 title·description·action·close button 모두 폭 안에서 줄바꿈.