import type { ReactNode } from "react" import type { Meta, StoryObj } from "@storybook/react-vite" import { Button, toast, Toaster } from "@hm-design/ui" import { StateBoard } from "./story-board" import { STORY_KICKER, StoryPage, StoryPageHeader, StorySection } from "./story-page" // Toast is created with the real Sonner API (`toast`, re-exported by @hm-design/ui); nothing is a static specimen. // One is mounted in .storybook/preview.tsx for every Story (an app mounts it once at its root). const wait = (ms: number, fail?: boolean) => new Promise((resolve, reject) => setTimeout(() => (fail ? reject(new Error("fail")) : resolve("완료")), ms)) function Trigger({ onClick, children }: { onClick: () => void; children: ReactNode }) { return ( ) } // `ids` picks the specimens for one section of the common Story page. function Board({ ids }: { ids: string[] }) { return ( toast("알림이 도착했습니다", { description: "기본 알림입니다." })}>Default toast.success("저장했습니다", { description: "변경 사항이 저장되었습니다." })}>Success toast.error("저장하지 못했습니다", { description: "잠시 후 다시 시도하세요." })}>Error toast.warning("저장 공간이 부족합니다", { description: "불필요한 파일을 정리하세요." })}>Warning toast.info("새 버전이 있습니다")}>Info toast.loading("처리 중입니다", { duration: 4000 })}>Loading ), }, { id: "action", label: "Action / Close", children: (
toast("파일을 삭제했습니다", { action: { label: "실행 취소", onClick: () => toast.success("복원했습니다") } })}> Action toast("변경 사항을 적용할까요?", { description: "취소하면 이전 상태가 유지됩니다.", duration: Infinity, action: { label: "적용", onClick: () => undefined }, cancel: { label: "취소", onClick: () => undefined }, }) } > Action + Cancel toast("닫기 버튼이 있는 알림", { closeButton: true, duration: Infinity })}>Close button
), }, { id: "promise", label: "Promise", noteAfter: true, note: "Loading, then Success or Error (local timeout, no network).", children: (
toast.promise(wait(1500), { loading: "저장 중...", success: "저장했습니다", error: "저장하지 못했습니다" })}> Promise → Success toast.promise(wait(1500, true), { loading: "저장 중...", success: "저장했습니다", error: "저장하지 못했습니다" })}> Promise → Error
), }, { id: "stack", label: "Stack / Long", children: (
{ toast("첫 번째 알림") toast.success("두 번째 알림") toast.error("세 번째 알림") }} > Stack ×3 toast.info("긴 메시지 확인용 제목입니다. 좁은 화면에서도 줄바꿈되어야 합니다.", { description: "설명도 길게 작성하면 여러 줄로 나뉘고 토스트 폭을 넘치지 않아야 합니다. 이 문장은 줄바꿈을 확인하기 위한 예시입니다.", action: { label: "자세히 보기", onClick: () => undefined }, closeButton: true, }) } > Long message toast.dismiss()}>Dismiss all
), }, ].filter((s) => ids.includes(s.id))} /> ) } const meta = { title: "Components/Feedback/Sonner", component: Toaster, parameters: { layout: "padded" } } satisfies Meta export default meta type Story = StoryObj // Representative Story: buttons create real toasts through Sonner (types, action / cancel, close, promise, stack, long message). export const All: Story = { name: "Sonner", parameters: { layout: "fullscreen", controls: { disable: true } }, render: () => ( ), }