'use client'
import { Button, Inline, toast } from '@hina-ui/react'
export default function Demo() {
return (
<Inline align="center" className="gap-3">
<Button variant="outline" tone="neutral" onClick={() => toast('草稿已保存')}>
默认
</Button>
<Button variant="outline" tone="neutral" onClick={() => toast.success('文章已发布')}>
成功
</Button>
<Button
variant="outline"
tone="neutral"
onClick={() => toast.danger('上传失败', { description: '文件超过 20 MB。' })}
>
失败
</Button>
</Inline>
)
}
tsx
用法
import { Toaster, toast } from '@hina-ui/react'
ts
在应用的最外层挂载一个 Toaster,然后在任何地方调用 toast()。调用后返回这条提示的 id,可以用它更新或关闭这条提示。
'use client'
import { Button, toast } from '@hina-ui/react'
export default function Demo() {
return (
<Button variant="outline" tone="neutral" onClick={() => toast('草稿已保存')}>
保存草稿
</Button>
)
}
tsx
import type { ReactNode } from 'react'
import { AppShell, Toaster } from '@hina-ui/react'
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="zh-CN">
<body>
<AppShell>
{children}
<Toaster />
</AppShell>
</body>
</html>
)
}
tsx
示例
语义
五个语义各有自己的图标和颜色。loading 显示转圈图标,并且一直停留,通常再用同一个 id 把它更新为最终结果。
'use client'
import { Button, Inline, toast } from '@hina-ui/react'
export default function Demo() {
return (
<Inline align="center" className="gap-3">
<Button variant="outline" tone="neutral" onClick={() => toast.success('已加入书架')}>
success
</Button>
<Button variant="outline" tone="neutral" onClick={() => toast.danger('网络连接中断')}>
danger
</Button>
<Button variant="outline" tone="neutral" onClick={() => toast.warning('还有未保存的改动')}>
warning
</Button>
<Button variant="outline" tone="neutral" onClick={() => toast.info('新版本已经可用')}>
info
</Button>
<Button variant="outline" tone="neutral" onClick={() => toast.loading('正在同步')}>
loading
</Button>
</Inline>
)
}
tsx
说明文字
description 是消息下面的第二行,用来补充细节。
'use client'
import { Button, toast } from '@hina-ui/react'
export default function Demo() {
return (
<Button
variant="outline"
tone="neutral"
onClick={() =>
toast.success('导入完成', {
description: '共导入 128 本书,其中 3 本因格式不符被跳过。',
})
}
>
带说明的提示
</Button>
)
}
tsx
操作按钮
action 是主按钮,cancel 是次按钮,点击其中任何一个都会关闭这条提示。需要用户回应时,把 duration 设为 0。
'use client'
import { Button, Inline, toast } from '@hina-ui/react'
export default function Demo() {
return (
<Inline align="center" className="gap-3">
<Button
variant="outline"
tone="neutral"
onClick={() =>
toast('已移入回收站', {
action: { label: '撤销', onClick: () => toast.success('已还原') },
})
}
>
带操作
</Button>
<Button
variant="outline"
tone="neutral"
onClick={() =>
toast.warning('确定要清空吗', {
duration: 0,
action: { label: '清空', onClick: () => toast.success('已清空') },
cancel: { label: '取消' },
})
}
>
两个按钮
</Button>
</Inline>
)
}
tsx
异步任务
toast.promise 接收一个 Promise,自行在 loading、success 和 error 三种状态之间切换。success 和 error 可以写成函数,用结果拼出消息。
'use client'
import { Button, toast } from '@hina-ui/react'
function upload() {
const task = new Promise<{ name: string }>(resolve =>
setTimeout(() => resolve({ name: 'ATRI.epub' }), 2000),
)
toast.promise(task, {
loading: '正在上传',
success: book => `${book.name} 上传完成`,
error: '上传失败,请重试',
})
}
export default function Demo() {
return (
<Button variant="outline" tone="neutral" onClick={upload}>
上传文件
</Button>
)
}
tsx
原地更新
传入相同的 id 时,更新已有提示的语义、文字和计时,而不是新增一条。进度类的反馈就是这样实现的。
'use client'
import { Button, toast } from '@hina-ui/react'
function sync() {
toast.loading('正在同步', { id: 'sync' })
setTimeout(() => toast.loading('已同步 12 / 30', { id: 'sync' }), 1000)
setTimeout(() => toast.success('同步完成', { id: 'sync' }), 2000)
}
export default function Demo() {
return (
<Button variant="outline" tone="neutral" onClick={sync}>
开始同步
</Button>
)
}
tsx
自定义渲染
toast.custom 用自己的组件渲染提示的正文。props 里的内容原样传给它,组件还会收到一个 toastId。卡片的底色、边框、阴影和内边距仍然由 Toaster 提供。
'use client'
import { BookOpen } from 'lucide-react'
import { Button, Heading, Inline, Stack, Text, toast } from '@hina-ui/react'
function ChapterToast({ title, chapter }: { title: string; chapter: string }) {
return (
<Inline align="center" className="gap-3">
<div className="bg-inset grid size-10 shrink-0 place-items-center rounded-md">
<BookOpen className="size-5" />
</div>
<Stack gap="none" className="min-w-0 flex-1">
<Heading level={4} size="sm">
{title}
</Heading>
<Text tone="muted" size="sm">
{chapter}
</Text>
</Stack>
<Button size="sm" onClick={() => toast.dismiss('chapter')}>
去阅读
</Button>
</Inline>
)
}
function notify() {
toast.custom(ChapterToast, {
id: 'chapter',
duration: 6000,
props: { title: 'ATRI', chapter: '第 42 话' },
})
}
export default function Demo() {
return (
<Button variant="outline" tone="neutral" onClick={notify}>
新章节上线
</Button>
)
}
tsx
停留时长
duration 的单位是毫秒,默认为 4000,loading 默认为 0。为 0 表示一直停留,直到用户点击关闭按钮,或者调用 toast.dismiss(id)。
'use client'
import { Button, Inline, toast } from '@hina-ui/react'
export default function Demo() {
return (
<Inline align="center" className="gap-3">
<Button
variant="outline"
tone="neutral"
onClick={() => toast('一秒后消失', { duration: 1000 })}
>
一秒
</Button>
<Button
variant="outline"
tone="neutral"
onClick={() => toast.info('需要手动关闭', { id: 'sticky', duration: 0 })}
>
不自动关闭
</Button>
<Button variant="soft" tone="neutral" onClick={() => toast.dismiss('sticky')}>
关闭上一条
</Button>
</Inline>
)
}
tsx
行为
- 指针悬停在提示区域上,或者焦点进入其中时,计时暂停,离开后继续。
- 同时最多显示 5 条提示,更早的会退场。
- 提示按
id索引,用同一个 id 再次调用是更新,而不是新增。 - 向右滑动可以关闭一条提示。
无障碍
- 提示区域是
role="region",默认名称为“通知”,可以用label覆盖。 - 每条提示通过一个独立的
role="alert"实时区域播报,屏幕阅读器会读出消息和说明文字。 - 最上面那条提示可以用 Tab 聚焦,关闭按钮带有无障碍名称。
API
toast
| 方法 | 说明 |
|---|---|
toast(message, options?) | 中性提示,返回这条提示的 id |
toast.success(...) | 成功 |
toast.danger(...) | 失败 |
toast.warning(...) | 警告 |
toast.info(...) | 信息 |
toast.loading(...) | 进行中,一直停留 |
toast.promise(p, messages) | 跟随 Promise 的三种状态 |
toast.custom(component, {}) | 用自己的组件渲染整条提示 |
toast.dismiss(id?) | 关闭指定的一条,不传时关闭全部 |
ToastOptions
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
id | number | string | — | 相同的 id 会更新已有提示 |
description | string | — | 消息下面的第二行 |
duration | number | 4000 | 停留毫秒数,0 表示一直停留 |
action | { label, onClick } | — | 主按钮 |
cancel | { label, onClick } | — | 次按钮 |
onDismiss | (id) => void | — | 提示被关闭时调用 |
onAutoClose | (id) => void | — | 计时结束自动关闭时调用 |
Toaster
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
position | ToasterPosition | — | 不设置时窄屏在底部居中,宽屏在右上角 |
label | string | — | 提示区域的无障碍名称 |
className | string | — | 追加到提示区域上的类名 |
ToasterPosition 的取值为 top-start、top-center、top-end、bottom-start、bottom-center 和 bottom-end。