跳至内容

Toast

一个简短的消息,暂时显示。
vue
<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 变量。
  • 可以是受控或不受控的。

安装

从命令行安装组件。

sh
$ npm add radix-vue

解剖学

导入组件。

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 覆盖

asChild
boolean

更改作为子元素传递的默认渲染元素,合并它们的道具和行为。

阅读我们的 组合 指南以获取更多详细信息。

hotkey
['F8']
string[]

用作键盘快捷键的键,这些快捷键会将焦点移动到 Toast 视窗。

label
'Notifications ({hotkey})'
string | ((hotkey: string) => string)

Toast 视窗的作者本地化标签,为屏幕阅读器用户在导航页面地标时提供上下文。可用的 {hotkey} 占位符将被替换。或者,你可以传递一个自定义函数来生成标签。

自动关闭的 Toast。它不应该保持打开状态以 获取用户响应

道具默认类型
as
'li'
AsTag | Component

此组件应该渲染成的元素或组件。可以被 asChild 覆盖

asChild
boolean

更改作为子元素传递的默认渲染元素,合并它们的道具和行为。

阅读我们的 组合 指南以获取更多详细信息。

defaultOpen
true
boolean

对话框最初渲染时的打开状态。当你不需要控制它的打开状态时使用。

duration
number

Toast 应该保持可见的时间(以毫秒为单位)。覆盖传递给 ToastProvider 的值。

forceMount
boolean

用于在需要更多控制时强制挂载。当使用 Vue 动画库控制动画时很有用。

open
boolean

对话框的受控打开状态。可以绑定为 v-model:open

type
'foreground'
'foreground' | 'background'

控制 Toast 的敏感度以实现无障碍目的。

对于用户操作产生的 Toast,选择 foreground。从后台任务生成的 Toast 应该使用 background

发射有效载荷
escapeKeyDown
[event: KeyboardEvent]

按下 Escape 键时调用的事件处理程序。可以通过调用 event.preventDefault 来阻止它。

pause
[]

当关闭计时器暂停时调用的事件处理程序。当指针移动到视窗上、视窗获得焦点或窗口失去焦点时,就会发生这种情况。

resume
[]

当关闭计时器恢复时调用的事件处理程序。当指针从视窗上移开、视窗失去焦点或窗口获得焦点时,就会发生这种情况。

swipeCancel
[event: SwipeEvent]
swipeEnd
[event: SwipeEvent]

在滑动交互结束时调用的事件处理程序。可以通过调用 event.preventDefault 来阻止它。

swipeMove
[event: SwipeEvent]

在滑动交互期间调用的事件处理程序。可以通过调用 event.preventDefault 来阻止它。

swipeStart
[event: SwipeEvent]

开始滑动交互时调用的事件处理程序。可以通过调用 event.preventDefault 来阻止它。

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 覆盖

asChild
boolean

更改作为子元素传递的默认渲染元素,合并它们的道具和行为。

阅读我们的 组合 指南以获取更多详细信息。

描述

Toast 消息。

道具默认类型
as
'div'
AsTag | Component

此组件应该渲染成的元素或组件。可以被 asChild 覆盖

asChild
boolean

更改作为子元素传递的默认渲染元素,合并它们的道具和行为。

阅读我们的 组合 指南以获取更多详细信息。

操作

一个可以安全忽略的操作,以确保用户不需要由于 时间限制 而完成意外的副作用任务。

当需要获得用户响应时,将一个 "AlertDialog" 样式化为 Toast 并将其门户到视窗中。

道具默认类型
altText*
string

执行操作的替代方法的简短描述。对于无法轻松/快速导航到按钮的屏幕阅读器用户。

as
'div'
AsTag | Component

此组件应该渲染成的元素或组件。可以被 asChild 覆盖

asChild
boolean

更改作为子元素传递的默认渲染元素,合并它们的道具和行为。

阅读我们的 组合 指南以获取更多详细信息。

关闭

一个按钮,允许用户在 Toast 的持续时间结束之前将其关闭。

道具默认类型
as
'button'
AsTag | Component

此组件应该渲染成的元素或组件。可以被 asChild 覆盖

asChild
boolean

更改作为子元素传递的默认渲染元素,合并它们的道具和行为。

阅读我们的 组合 指南以获取更多详细信息。

示例

自定义热键

使用来自 keycode.info 的每个键的 event.code 值来覆盖默认的热键。

html
<ToastProvider>
  ...
  <ToastViewport :hotkey="['altKey', 'KeyT']" />
</ToastProvider>

自定义持续时间

自定义 Toast 的持续时间以覆盖提供者值。

vue
<ToastRoot :duration="3000">
  <ToastDescription>Saved!</ToastDescription>
</ToastRoot>

重复 Toast

当 Toast 必须在用户每次单击按钮时出现时,使用状态来渲染同一 Toast 的多个实例(见下文)。或者,你可以抽象出各个部分来创建自己的 命令式 API

html
<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]" 属性结合起来,可以为滑动关闭手势制作动画。这里有一个例子

html
<ToastProvider swipeDirection="right">
  <ToastRoot class="ToastRoot">...</ToastRoot>
  <ToastViewport />
</ToastProvider>
css
/* 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。

背景

背景提示会在下一次合适的时机宣布,例如,当屏幕阅读器完成当前句子的阅读时。它们不会清除排队的消息,因此过度使用它们可能会被屏幕阅读器用户视为滞后的用户体验,尤其是在响应用户交互时使用。

html
<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 类型立即宣布并增加持续时间,以使用户有足够的时间。

html
<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>

关闭图标按钮

当提供图标(或字体图标)时,请记住为屏幕阅读器用户正确地标记它。

html
<ToastRoot type="foreground">
  <ToastDescription>Saved!</ToastDescription>
  <ToastClose aria-label="Close">
    <span aria-hidden>×</span>
  </ToastClose>
</ToastRoot>

键盘交互

描述
F8
将焦点置于提示视窗。
Tab
将焦点移动到下一个可聚焦元素。
Shift + Tab
将焦点移动到上一个可聚焦元素。
空格
当焦点位于 ToastActionToastClose 上时,关闭提示。
Enter
当焦点位于 ToastActionToastClose 上时,关闭提示。
Esc
当焦点位于 Toast 上时,关闭提示。

自定义 API

抽象部分

通过将基本部分抽象到您自己的组件中来创建您自己的 API。

用法

vue
<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>

实现

vue
// 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,以允许 提示重复 (如果需要)。

用法

vue
<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>

实现

vue
// 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>