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