Dialog
功能特性
- 支持模态和非模态模式。
- 模态模式下自动实现焦点捕获。
- 可受控或非受控使用。
- 通过
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 参考
Root
包含对话框的所有部件
| Prop | Default | Type |
|---|---|---|
defaultOpen | false | booleanThe open state of the dialog when it is initially rendered. Use when you do not need to control its open state. |
modal | true | booleanThe modality of the dialog When set to |
open | booleanThe controlled open state of the dialog. Can be binded as |
| Emit | Payload |
|---|---|
update:open | [value: boolean]Event handler called when the open state of the dialog changes. |
| Slots (default) | Payload |
|---|---|
open | booleanCurrent open state |
close | (): voidClose the dialog |
Trigger
用于打开对话框的按钮
| Prop | Default | Type |
|---|---|---|
as | 'button' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
| Data Attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
Portal
使用时,会将遮罩层和内容部件传送至 body 中。
| Prop | Default | Type |
|---|---|---|
defer | booleanDefer the resolving of a Teleport target until other parts of the application have mounted (requires Vue 3.5.0+) | |
disabled | booleanDisable teleport and render the component inline | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. | |
to | string | HTMLElementVue native teleport component prop |
Overlay
对话框打开时覆盖在非活动视图区域上的一层。
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. |
| Data Attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
Content
包含要在打开的对话框中呈现的内容
| Prop | Default | Type |
|---|---|---|
as | 'div' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. | |
disableOutsidePointerEvents | booleanWhen | |
forceMount | booleanUsed to force mounting when more control is needed. Useful when controlling animation with Vue animation libraries. |
| Emit | Payload |
|---|---|
closeAutoFocus | [event: Event]Event handler called when auto-focusing on close. Can be prevented. |
escapeKeyDown | [event: KeyboardEvent]Event handler called when the escape key is down. Can be prevented. |
focusOutside | [event: FocusOutsideEvent]Event handler called when the focus moves outside of the |
interactOutside | [event: PointerDownOutsideEvent | FocusOutsideEvent]Event handler called when an interaction happens outside the |
openAutoFocus | [event: Event]Event handler called when auto-focusing on open. Can be prevented. |
pointerDownOutside | [event: PointerDownOutsideEvent]Event handler called when a |
| Data Attribute | Value |
|---|---|
[data-state] | "open" | "closed" |
Close
用于关闭对话框的按钮
| Prop | Default | Type |
|---|---|---|
as | 'button' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Title
对话框打开时向屏幕阅读器播报的可访问标题。
如需隐藏标题,可使用我们的 Visually Hidden 工具将其包裹,如 <VisuallyHidden asChild>。
| Prop | Default | Type |
|---|---|---|
as | 'h2' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
Description
对话框打开时向屏幕阅读器播报的可选可访问描述。
如需隐藏描述,可使用我们的 Visually Hidden 工具将其包裹,如 <VisuallyHidden asChild>。若要完全移除描述,请移除此部件并向 DialogContent 传递 :aria-describedby="undefined"。
| Prop | Default | Type |
|---|---|---|
as | 'p' | AsTag | ComponentThe element or component this component should render as. Can be overwritten by |
asChild | booleanChange the default rendered element for the one passed as a child, merging their props and behavior. Read our Composition guide for more details. |
示例
嵌套对话框
可嵌套多层对话框。
异步表单提交后关闭
使用受控属性在异步操作完成后以编程方式关闭对话框。
<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));
}
"
>
<!-- 一些输入项 -->
<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();
}
}"
>自定义传送容器
自定义对话框要传送到的元素。
<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>禁用外部交互关闭
例如,若您有某些全局 Toast 组件,点击它时不应关闭对话框。
可访问性
关闭图标按钮
当提供图标(或字体图标)时,请记得为屏幕阅读器用户正确标注。
<template>
<DialogRoot>
<DialogTrigger />
<DialogPortal>
<DialogOverlay />
<DialogContent>
<DialogTitle />
<DialogDescription />
<DialogClose aria-label="关闭">
<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>
<!-- 一些输入项 -->
<button type="submit" @click="close">
Submit
</button>
</form>
</DialogContent>
<DialogFooter>
<button type="submit" @click="close">
Submit
</button>
</DialogFooter>
</DialogPortal>
</DialogRoot>
</template>键盘交互
| Key | Description |
|---|---|
空格键 | 打开/关闭对话框 |
回车键 | 打开/关闭对话框 |
Tab | 将焦点移至下一个可聚焦元素。 |
Shift + Tab | 将焦点移至上一個可聚焦元素。 |
Esc | 关闭对话框并将焦点移至 DialogTrigger。 |
自定义 API
通过将原始部件抽象到您自己的组件中来创建专属 API。
抽象遮罩层和关闭按钮
此示例抽象了 DialogOverlay 和 DialogClose 部件。
用法
<script setup>
import { Dialog, DialogContent, DialogTrigger } from './your-dialog'
</script>
<template>
<Dialog>
<DialogTrigger>对话框触发器</DialogTrigger>
<DialogContent>对话框内容</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">关闭</span>
</DialogClose>
</DialogContent>
</DialogPortal>
</template>