对话框
特性
- 支持模态和非模态模式。
- 在模态模式下,焦点会自动捕获。
- 可控或不可控。
- 通过
Title
和Description
组件管理屏幕阅读器公告。 - 按 Esc 键自动关闭组件。
安装
从命令行安装此组件。
$ npm add reka-ui
结构
导入所有部分并将其组合。
<script setup>
import {
DialogClose,
DialogContent,
DialogDescription,
DialogOverlay,
DialogPortal,
DialogRoot,
DialogTitle,
DialogTrigger,
} from 'reka-ui'
</script>
<template>
<DialogRoot>
<DialogTrigger />
<DialogPortal>
<DialogOverlay />
<DialogContent>
<DialogTitle />
<DialogDescription />
<DialogClose />
</DialogContent>
</DialogPortal>
</DialogRoot>
</template>
API 参考
根
包含对话框的所有部分
属性 | 默认 | 类型 |
---|---|---|
defaultOpen | false | 布尔值 对话框初始渲染时的打开状态。当您不需要控制其打开状态时使用。 |
modal | true | 布尔值 对话框的模态性。当设置为 |
open | 布尔值 对话框的受控打开状态。可以绑定为 |
事件触发 | 载荷 |
---|---|
update:open | [value: boolean] 当对话框的打开状态改变时调用的事件处理程序。 |
插槽(默认) | 载荷 |
---|---|
open | 布尔值 当前打开状态 |
close | (): void 关闭对话框 |
触发器
打开对话框的按钮
属性 | 默认 | 类型 |
---|---|---|
as | 'button' | AsTag | Component 此组件应渲染为的元素或组件。可通过 |
asChild | 布尔值 更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。 阅读我们的 组合指南了解更多详情。 |
数据属性 | 值 |
---|---|
[data-state] | "打开" | "关闭" |
Portal
使用时,将您的覆盖层和内容部分传送(portals)到 body
中。
属性 | 默认 | 类型 |
---|---|---|
defer | 布尔值 延迟解析 Teleport 目标,直到应用程序的其他部分挂载(需要 Vue 3.5.0+) | |
disabled | 布尔值 禁用传送并内联渲染组件 | |
forceMount | 布尔值 当需要更多控制时,用于强制挂载。在与 Vue 动画库控制动画时很有用。 | |
to | 字符串 | HTMLElement Vue 原生传送组件属性 |
覆盖层
对话框打开时,覆盖视图非活动部分的图层。
属性 | 默认 | 类型 |
---|---|---|
as | 'div' | AsTag | Component 此组件应渲染为的元素或组件。可通过 |
asChild | 布尔值 更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。 阅读我们的 组合指南了解更多详情。 | |
forceMount | 布尔值 当需要更多控制时,用于强制挂载。在与 Vue 动画库控制动画时很有用。 |
数据属性 | 值 |
---|---|
[data-state] | "打开" | "关闭" |
内容
包含要在打开的对话框中渲染的内容
属性 | 默认 | 类型 |
---|---|---|
as | 'div' | AsTag | Component 此组件应渲染为的元素或组件。可通过 |
asChild | 布尔值 更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。 阅读我们的 组合指南了解更多详情。 | |
disableOutsidePointerEvents | 布尔值 当设置为 | |
forceMount | 布尔值 当需要更多控制时,用于强制挂载。在与 Vue 动画库控制动画时很有用。 |
事件触发 | 载荷 |
---|---|
closeAutoFocus | [事件: Event] 关闭时自动聚焦调用的事件处理程序。可阻止其默认行为。 |
escapeKeyDown | [事件: KeyboardEvent] 按下 Esc 键时调用的事件处理程序。可阻止其默认行为。 |
focusOutside | [事件: FocusOutsideEvent] 当焦点移出 |
interactOutside | [事件: PointerDownOutsideEvent | FocusOutsideEvent] 当 |
openAutoFocus | [事件: Event] 打开时自动聚焦调用的事件处理程序。可阻止其默认行为。 |
pointerDownOutside | [事件: PointerDownOutsideEvent] 当 |
数据属性 | 值 |
---|---|
[data-state] | "打开" | "关闭" |
关闭
关闭对话框的按钮
属性 | 默认 | 类型 |
---|---|---|
as | 'button' | AsTag | Component 此组件应渲染为的元素或组件。可通过 |
asChild | 布尔值 更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。 阅读我们的 组合指南了解更多详情。 |
标题
对话框打开时宣布的可访问标题。
如果您想隐藏标题,请将其包裹在我们的 Visually Hidden 实用工具中,例如 <VisuallyHidden asChild>
。
属性 | 默认 | 类型 |
---|---|---|
as | 'h2' | AsTag | Component 此组件应渲染为的元素或组件。可通过 |
asChild | 布尔值 更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。 阅读我们的 组合指南了解更多详情。 |
描述
对话框打开时宣布的可选可访问描述。
如果您想隐藏描述,请将其包裹在我们的 Visually Hidden 实用工具中,例如 <VisuallyHidden asChild>
。如果您想完全删除描述,请移除此部分并向 DialogContent
传入 :aria-describedby="undefined"
。
属性 | 默认 | 类型 |
---|---|---|
as | 'p' | AsTag | Component 此组件应渲染为的元素或组件。可通过 |
asChild | 布尔值 更改默认渲染元素为作为子项传递的元素,合并它们的属性和行为。 阅读我们的 组合指南了解更多详情。 |
示例
嵌套对话框
您可以嵌套多层对话框。
异步表单提交后关闭
使用受控属性在异步操作完成后以编程方式关闭对话框。
<script setup>
import { DialogContent, DialogOverlay, DialogPortal, DialogRoot, DialogTrigger } from 'reka-ui'
const wait = () => new Promise(resolve => setTimeout(resolve, 1000))
const open = ref(false)
</script>
<template>
<DialogRoot v-model:open="open">
<DialogTrigger>Open</DialogTrigger>
<DialogPortal>
<DialogOverlay />
<DialogContent>
<form
@submit.prevent="
(event) => {
wait().then(() => (open = false));
}
"
>
<!-- some inputs -->
<button type="submit">
Submit
</button>
</form>
</DialogContent>
</DialogPortal>
</DialogRoot>
</template>
可滚动覆盖层
将内容移到覆盖层内部,以渲染带溢出内容的对话框。
// index.vue
<script setup>
import { DialogContent, DialogOverlay, DialogPortal, DialogRoot, DialogTrigger } from 'reka-ui'
import './styles.css'
</script>
<template>
<DialogRoot>
<DialogTrigger />
<DialogPortal>
<DialogOverlay class="DialogOverlay">
<DialogContent class="DialogContent">
...
</DialogContent>
</DialogOverlay>
</DialogPortal>
</DialogRoot>
</template>
/* styles.css */
.DialogOverlay {
background: rgba(0 0 0 / 0.5);
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: grid;
place-items: center;
overflow-y: auto;
}
.DialogContent {
min-width: 300px;
background: white;
padding: 30px;
border-radius: 4px;
}
然而,这种方法有一个注意事项,即用户可能会点击滚动条而意外关闭对话框。目前没有通用的解决方案可以解决这个问题,但您可以向 DialogContent
添加以下代码片段,以防止在点击滚动条时关闭模态框。
<DialogContent
@pointer-down-outside="(event) => {
const originalEvent = event.detail.originalEvent;
const target = originalEvent.target as HTMLElement;
if (originalEvent.offsetX > target.clientWidth || originalEvent.offsetY > target.clientHeight) {
event.preventDefault();
}
}"
>
自定义 Portal 容器
自定义对话框传送到的元素。
<script setup>
import { DialogContent, DialogOverlay, DialogPortal, DialogRoot, DialogTrigger } from 'reka-ui'
const container = ref(null)
</script>
<template>
<div>
<DialogRoot>
<DialogTrigger />
<DialogPortal to="container">
<DialogOverlay />
<DialogContent>...</DialogContent>
</DialogPortal>
</DialogRoot>
<div ref="container" />
</div>
</template>
禁用外部交互关闭
例如,如果您有一些全局的 Toaster 组件,点击它时不应关闭对话框。
可访问性
关闭图标按钮
提供图标(或字体图标)时,请记住为屏幕阅读器用户正确标注它。
<template>
<DialogRoot>
<DialogTrigger />
<DialogPortal>
<DialogOverlay />
<DialogContent>
<DialogTitle />
<DialogDescription />
<DialogClose aria-label="Close">
<span aria-hidden="true">×</span>
</DialogClose>
</DialogContent>
</DialogPortal>
</DialogRoot>
</template>
使用插槽属性关闭
或者,您可以使用 DialogRoot
插槽属性提供的 close
方法以编程方式关闭对话框。
<script setup>
import { DialogContent, DialogOverlay, DialogPortal, DialogRoot, DialogTrigger } from 'reka-ui'
</script>
<template>
<DialogRoot v-slot="{ close }">
<DialogTrigger>Open</DialogTrigger>
<DialogPortal>
<DialogOverlay />
<DialogContent>
<form>
<!-- some inputs -->
<button type="submit" @click="close">
Submit
</button>
</form>
</DialogContent>
<DialogFooter>
<button type="submit" @click="close">
Submit
</button>
</DialogFooter>
</DialogPortal>
</DialogRoot>
</template>
键盘交互
键 | 描述 |
---|---|
空格键 | 打开/关闭对话框 |
回车键 | 打开/关闭对话框 |
Tab | 将焦点移动到下一个可聚焦元素。 |
Shift + Tab | 将焦点移至上一个可聚焦元素。 |
Esc键 | 关闭对话框并将焦点移至 DialogTrigger 。 |
自定义 API
通过将原始部件抽象到您自己的组件中来创建您自己的 API。
抽象化覆盖层和关闭按钮
此示例抽象化了 DialogOverlay
和 DialogClose
部分。
用法
<script setup>
import { Dialog, DialogContent, DialogTrigger } from './your-dialog'
</script>
<template>
<Dialog>
<DialogTrigger>Dialog trigger</DialogTrigger>
<DialogContent>Dialog Content</DialogContent>
</Dialog>
</template>
实现
// your-dialog.ts
export { default as DialogContent } from 'DialogContent.vue'
export { DialogRoot as Dialog, DialogTrigger } from 'reka-ui'
<!-- DialogContent.vue -->
<script setup lang="ts">
import type { DialogContentEmits, DialogContentProps } from 'reka-ui'
import { Cross2Icon } from '@radix-icons/vue'
import { DialogClose, DialogContent, DialogOverlay, DialogPortal, useForwardPropsEmits } from 'reka-ui'
const props = defineProps<DialogContentProps>()
const emits = defineEmits<DialogContentEmits>()
const forwarded = useForwardPropsEmits(props, emits)
</script>
<template>
<DialogPortal>
<DialogOverlay />
<DialogContent v-bind="forwarded">
<slot />
<DialogClose>
<Cross2Icon />
<span class="sr-only">Close</span>
</DialogClose>
</DialogContent>
</DialogPortal>
</template>