Toast
<script setup lang="ts">
import { ref } from 'vue'
import { ToastAction, ToastDescription, ToastProvider, ToastRoot, ToastTitle, ToastViewport } from 'radix-vue'
const open = ref(false)
const eventDateRef = ref(new Date())
const timerRef = ref(0)
function oneWeekAway() {
const now = new Date()
const inOneWeek = now.setDate(now.getDate() + 7)
return new Date(inOneWeek)
}
function prettyDate(date: Date) {
return new Intl.DateTimeFormat('en-US', { dateStyle: 'full', timeStyle: 'short' }).format(date)
}
function handleClick() {
open.value = false
window.clearTimeout(timerRef.value)
timerRef.value = window.setTimeout(() => {
eventDateRef.value = oneWeekAway()
open.value = true
}, 100)
}
</script>
<template>
<ToastProvider>
<button
class="inline-flex items-center justify-center rounded font-medium text-[15px] px-[15px] leading-[35px] h-[35px] bg-white text-grass11 shadow-[0_2px_10px] shadow-blackA7 outline-none hover:bg-mauve3 focus:shadow-[0_0_0_2px] focus:shadow-black"
@click="handleClick"
>
Add to calendar
</button>
<ToastRoot
v-model:open="open"
class="bg-white rounded-md shadow-[hsl(206_22%_7%_/_35%)_0px_10px_38px_-10px,_hsl(206_22%_7%_/_20%)_0px_10px_20px_-15px] p-[15px] grid [grid-template-areas:_'title_action'_'description_action'] grid-cols-[auto_max-content] gap-x-[15px] items-center data-[state=open]:animate-slideIn data-[state=closed]:animate-hide data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=cancel]:transition-[transform_200ms_ease-out] data-[swipe=end]:animate-swipeOut"
>
<ToastTitle class="[grid-area:_title] mb-[5px] font-medium text-slate12 text-[15px]">
Scheduled: Catch up
</ToastTitle>
<ToastDescription as-child>
<time
class="[grid-area:_description] m-0 text-slate11 text-[13px] leading-[1.3]"
:dateTime="eventDateRef.toISOString()"
>
{{ prettyDate(eventDateRef) }}
</time>
</ToastDescription>
<ToastAction
class="[grid-area:_action]"
as-child
alt-text="Goto schedule to undo"
>
<button class="inline-flex items-center justify-center rounded font-medium text-xs px-[10px] leading-[25px] h-[25px] bg-green2 text-green11 shadow-[inset_0_0_0_1px] shadow-green7 hover:shadow-[inset_0_0_0_1px] hover:shadow-green8 focus:shadow-[0_0_0_2px] focus:shadow-green8">
Undo
</button>
</ToastAction>
</ToastRoot>
<ToastViewport class="[--viewport-padding:_25px] fixed bottom-0 right-0 flex flex-col p-[var(--viewport-padding)] gap-[10px] w-[390px] max-w-[100vw] m-0 list-none z-[2147483647] outline-none" />
</ToastProvider>
</template>
- 自动关闭。
- 在悬停、聚焦和窗口模糊时暂停关闭。
- 支持使用热键跳转到 Toast 视窗。
- 支持通过滑动手势关闭。
- 公开用于滑动手势动画的 CSS 变量。
- 可以是受控或不受控的。
安装
从命令行安装组件。
$ npm add radix-vue
解剖学
导入组件。
<script setup lang="ts">
import { ToastAction, ToastClose, ToastDescription, ToastProvider, ToastRoot, ToastTitle, ToastViewport } from 'radix-vue'
</script>
<template>
<ToastProvider>
<ToastRoot>
<ToastTitle />
<ToastDescription />
<ToastAction />
<ToastClose />
</ToastRoot>
<ToastViewport />
</ToastProvider>
</template>
API 参考
提供者
包装你的 Toast 和 Toast 视窗的提供者。它通常包装应用程序。
道具 | 默认 | 类型 |
---|---|---|
duration | 5000 | number 每个 Toast 应该保持可见的时间(以毫秒为单位)。 |
label | 'Notification' | string 每个 Toast 的作者本地化标签。用于帮助屏幕阅读器用户将中断与 Toast 相关联。 |
swipeDirection | 'right' | 'right' | 'left' | 'up' | 'down' 应关闭 Toast 的指针滑动方向。 |
swipeThreshold | 50 | number 滑动必须经过的距离(以像素为单位),才能触发关闭。 |
视窗
Toast 出现的固定区域。用户可以通过按热键跳转到视窗。由你确保键盘用户能够发现热键。
道具 | 默认 | 类型 |
---|---|---|
as | 'ol' | AsTag | Component 此组件应该渲染成的元素或组件。可以被 |
asChild | boolean 更改作为子元素传递的默认渲染元素,合并它们的道具和行为。 阅读我们的 组合 指南以获取更多详细信息。 | |
hotkey | ['F8'] | string[] 用作键盘快捷键的键,这些快捷键会将焦点移动到 Toast 视窗。 |
label | 'Notifications ({hotkey})' | string | ((hotkey: string) => string) Toast 视窗的作者本地化标签,为屏幕阅读器用户在导航页面地标时提供上下文。可用的 |
根
自动关闭的 Toast。它不应该保持打开状态以 获取用户响应。
道具 | 默认 | 类型 |
---|---|---|
as | 'li' | AsTag | Component 此组件应该渲染成的元素或组件。可以被 |
asChild | boolean 更改作为子元素传递的默认渲染元素,合并它们的道具和行为。 阅读我们的 组合 指南以获取更多详细信息。 | |
defaultOpen | true | boolean 对话框最初渲染时的打开状态。当你不需要控制它的打开状态时使用。 |
duration | number Toast 应该保持可见的时间(以毫秒为单位)。覆盖传递给 | |
forceMount | boolean 用于在需要更多控制时强制挂载。当使用 Vue 动画库控制动画时很有用。 | |
open | boolean 对话框的受控打开状态。可以绑定为 | |
type | 'foreground' | 'foreground' | 'background' 控制 Toast 的敏感度以实现无障碍目的。 对于用户操作产生的 Toast,选择 |
发射 | 有效载荷 |
---|---|
escapeKeyDown | [event: KeyboardEvent] 按下 Escape 键时调用的事件处理程序。可以通过调用 |
pause | [] 当关闭计时器暂停时调用的事件处理程序。当指针移动到视窗上、视窗获得焦点或窗口失去焦点时,就会发生这种情况。 |
resume | [] 当关闭计时器恢复时调用的事件处理程序。当指针从视窗上移开、视窗失去焦点或窗口获得焦点时,就会发生这种情况。 |
swipeCancel | [event: SwipeEvent] |
swipeEnd | [event: SwipeEvent] 在滑动交互结束时调用的事件处理程序。可以通过调用 |
swipeMove | [event: SwipeEvent] 在滑动交互期间调用的事件处理程序。可以通过调用 |
swipeStart | [event: SwipeEvent] 开始滑动交互时调用的事件处理程序。可以通过调用 |
update:open | [value: boolean] 打开状态发生变化时调用的事件处理程序 |
插槽 (默认) | 有效载荷 |
---|---|
open | boolean 当前打开状态 |
remaining | number 剩余时间(以毫秒为单位) |
duration | number Toast 将保持可见的总时间(以毫秒为单位) |
数据属性 | 值 |
---|---|
[data-state] | "open" | "closed" |
[data-swipe] | "start" | "move" | "cancel" | "end" |
[data-swipe-direction] | "up" | "down" | "left" | "right" |
CSS 变量 | 描述 |
---|---|
--radix-toast-swipe-move-x | 水平滑动时 Toast 的偏移位置 |
--radix-toast-swipe-move-y | 垂直滑动时 Toast 的偏移位置 |
--radix-toast-swipe-end-x | 水平滑动后 Toast 的偏移结束位置 |
--radix-toast-swipe-end-y | 垂直滑动后 Toast 的偏移结束位置 |
标题
Toast 的可选标题
道具 | 默认 | 类型 |
---|---|---|
as | 'div' | AsTag | Component 此组件应该渲染成的元素或组件。可以被 |
asChild | boolean 更改作为子元素传递的默认渲染元素,合并它们的道具和行为。 阅读我们的 组合 指南以获取更多详细信息。 |
描述
Toast 消息。
道具 | 默认 | 类型 |
---|---|---|
as | 'div' | AsTag | Component 此组件应该渲染成的元素或组件。可以被 |
asChild | boolean 更改作为子元素传递的默认渲染元素,合并它们的道具和行为。 阅读我们的 组合 指南以获取更多详细信息。 |
操作
一个可以安全忽略的操作,以确保用户不需要由于 时间限制 而完成意外的副作用任务。
当需要获得用户响应时,将一个 "AlertDialog" 样式化为 Toast 并将其门户到视窗中。
道具 | 默认 | 类型 |
---|---|---|
altText* | string 执行操作的替代方法的简短描述。对于无法轻松/快速导航到按钮的屏幕阅读器用户。 | |
as | 'div' | AsTag | Component 此组件应该渲染成的元素或组件。可以被 |
asChild | boolean 更改作为子元素传递的默认渲染元素,合并它们的道具和行为。 阅读我们的 组合 指南以获取更多详细信息。 |
关闭
一个按钮,允许用户在 Toast 的持续时间结束之前将其关闭。
道具 | 默认 | 类型 |
---|---|---|
as | 'button' | AsTag | Component 此组件应该渲染成的元素或组件。可以被 |
asChild | boolean 更改作为子元素传递的默认渲染元素,合并它们的道具和行为。 阅读我们的 组合 指南以获取更多详细信息。 |
示例
自定义热键
使用来自 keycode.info 的每个键的 event.code
值来覆盖默认的热键。
<ToastProvider>
...
<ToastViewport :hotkey="['altKey', 'KeyT']" />
</ToastProvider>
自定义持续时间
自定义 Toast 的持续时间以覆盖提供者值。
<ToastRoot :duration="3000">
<ToastDescription>Saved!</ToastDescription>
</ToastRoot>
重复 Toast
当 Toast 必须在用户每次单击按钮时出现时,使用状态来渲染同一 Toast 的多个实例(见下文)。或者,你可以抽象出各个部分来创建自己的 命令式 API。
<div>
<form @submit="count++">
...
<button>save</button>
</form>
<ToastRoot v-for="(_, index) in count" :key="index">
<ToastDescription>Saved!</ToastDescription>
</ToastRoot>
</div>
动画滑动手势
将 --radix-toast-swipe-move-[x|y]
和 --radix-toast-swipe-end-[x|y]
CSS 变量与 data-swipe="[start|move|cancel|end]"
属性结合起来,可以为滑动关闭手势制作动画。这里有一个例子
<ToastProvider swipeDirection="right">
<ToastRoot class="ToastRoot">...</ToastRoot>
<ToastViewport />
</ToastProvider>
/* styles.css */
.ToastRoot[data-swipe='move'] {
transform: translateX(var(--radix-toast-swipe-move-x));
}
.ToastRoot[data-swipe='cancel'] {
transform: translateX(0);
transition: transform 200ms ease-out;
}
.ToastRoot[data-swipe='end'] {
animation: slideRight 100ms ease-out;
}
@keyframes slideRight {
from {
transform: translateX(var(--radix-toast-swipe-end-x));
}
to {
transform: translateX(100%);
}
}
无障碍性
遵守 aria-live
要求。
敏感度
使用 type
道具控制屏幕阅读器对 Toast 的敏感度。
对于用户操作产生的 Toast,选择 foreground
。从后台任务生成的 Toast 应该使用 background
。
前景
前景 Toast 会立即宣布。辅助技术可能会选择在前景 Toast 出现时清除先前排队的消息。尽量避免同时堆叠不同的前景 Toast。
背景
背景提示会在下一次合适的时机宣布,例如,当屏幕阅读器完成当前句子的阅读时。它们不会清除排队的消息,因此过度使用它们可能会被屏幕阅读器用户视为滞后的用户体验,尤其是在响应用户交互时使用。
<ToastRoot type="foreground">
<ToastDescription>File removed successfully.</ToastDescription>
<ToastClose>Dismiss</ToastClose>
</ToastRoot>
<ToastRoot type="background">
<ToastDescription>We've just released Radix 1.0.</ToastDescription>
<ToastClose>Dismiss</ToastClose>
</ToastRoot>
替代操作
在 Action
上使用 altText
道具来指示屏幕阅读器用户操作提示的替代方法。
您可以将用户引导到应用程序中他们可以进行操作的永久位置,或者实现您自己的自定义热键逻辑。如果实现后者,请使用 foreground
类型立即宣布并增加持续时间,以使用户有足够的时间。
<ToastRoot type="background">
<ToastTitle>Upgrade Available!</ToastTitle>
<ToastDescription>We've just released Radix 1.0.</ToastDescription>
<ToastAction altText="Goto account settings to upgrade">
Upgrade
</ToastAction>
<ToastClose>Dismiss</ToastClose>
</ToastRoot>
<ToastRoot type="foreground" :duration="10000">
<ToastDescription>File removed successfully.</ToastDescription>
<ToastAction altText="Undo (Alt+U)">
Undo <kbd>Alt</kbd>+<kbd>U</kbd>
</ToastAction>
<ToastClose>Dismiss</ToastClose>
</ToastRoot>
关闭图标按钮
当提供图标(或字体图标)时,请记住为屏幕阅读器用户正确地标记它。
<ToastRoot type="foreground">
<ToastDescription>Saved!</ToastDescription>
<ToastClose aria-label="Close">
<span aria-hidden>×</span>
</ToastClose>
</ToastRoot>
键盘交互
键 | 描述 |
---|---|
F8 | 将焦点置于提示视窗。 |
Tab | 将焦点移动到下一个可聚焦元素。 |
Shift + Tab | 将焦点移动到上一个可聚焦元素。 |
空格 |
当焦点位于 ToastAction 或 ToastClose 上时,关闭提示。 |
Enter |
当焦点位于 ToastAction 或 ToastClose 上时,关闭提示。 |
Esc |
当焦点位于 Toast 上时,关闭提示。 |
自定义 API
抽象部分
通过将基本部分抽象到您自己的组件中来创建您自己的 API。
用法
<script setup lang="ts">
import Toast from './your-toast.vue'
</script>
<template>
<Toast
title="Upgrade available"
content="We've just released Radix 3.0!"
>
<button @click="handleUpgrade">
Upgrade
</button>
</Toast>
</template>
实现
// your-toast.vue
<script setup lang="ts">
import { ToastAction, ToastClose, ToastDescription, ToastRoot, ToastTitle } from 'radix-vue'
defineProps<{
title: string
content: string
}>()
</script>
<template>
<ToastRoot>
<ToastTitle v-if="title">
{{ title }}
</ToastTitle>
<ToastDescription>{{ content }}</ToastDescription>
<ToastAction
as-child
alt-text="toast"
>
<slot />
</ToastAction>
<ToastClose aria-label="Close">
<span aria-hidden>×</span>
</ToastClose>
</ToastRoot>
</template>
命令式 API
创建您自己的命令式 API,以允许 提示重复 (如果需要)。
用法
<script setup lang="ts">
import Toast from './your-toast.vue'
const savedRef = ref<InstanceType<typeof Toast>>()
</script>
<template>
<div>
<form @submit="savedRef.publish()">
...
</form>
<Toast ref="savedRef">
Saved successfully!
</Toast>
</div>
</template>
实现
// your-toast.vue
<script setup lang="ts">
import { ToastClose, ToastDescription, ToastRoot, ToastTitle } from 'radix-vue'
import { ref } from 'vue'
const count = ref(0)
function publish() {
count.value++
}
defineExpose({
publish
})
</script>
<template>
<ToastRoot
v-for="index in count"
:key="index"
>
<ToastDescription>
<slot />
</ToastDescription>
<ToastClose>Dismiss</ToastClose>
</ToastRoot>
</template>