Drawer 抽屉面板
用于抽屉面板场景的 MoriUI 组件。
导入
import { Drawer, DrawerClose, DrawerContent, DrawerDescription, DrawerFooter, DrawerHandle, DrawerHeader, DrawerOverlay, DrawerPortal, DrawerTitle, DrawerTrigger } from 'moriui'
示例
概览
从底部滑出的编辑面板。
<script setup lang="ts">
import {
Button,
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger,
Input,
Label
} from 'moriui'
</script>
<template>
<Drawer>
<DrawerTrigger as-child>
<Button variant="outline">
编辑资料
</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>编辑资料</DrawerTitle>
<DrawerDescription>在此修改你的资料,完成后点击保存。</DrawerDescription>
</DrawerHeader>
<div class="grid gap-4 p-4">
<div class="grid gap-2">
<Label for="drawer-name">姓名</Label>
<Input id="drawer-name" default-value="张三" />
</div>
<div class="grid gap-2">
<Label for="drawer-username">用户名</Label>
<Input id="drawer-username" default-value="@zhangsan" />
</div>
</div>
<DrawerFooter>
<Button type="submit">
保存
</Button>
<DrawerClose as-child>
<Button variant="outline">
取消
</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
</template>
方向
从各侧滑入。
<script setup lang="ts">
import { ref } from 'vue'
import {
Button,
Drawer,
DrawerContent,
DrawerHeader,
DrawerTitle,
DrawerTrigger
} from 'moriui'
const openBottom = ref(false)
const openTop = ref(false)
const openLeft = ref(false)
const openRight = ref(false)
</script>
<template>
<div class="flex flex-wrap gap-2">
<Drawer v-model:open="openBottom" swipe-direction="down">
<DrawerTrigger as-child>
<Button variant="outline">
底部
</Button>
</DrawerTrigger>
<DrawerContent show-handle>
<DrawerHeader>
<DrawerTitle>底部抽屉</DrawerTitle>
</DrawerHeader>
<div class="p-4 text-sm text-muted-foreground">
从底部滑入的抽屉,适合移动端操作。
</div>
</DrawerContent>
</Drawer>
<Drawer v-model:open="openTop" swipe-direction="up">
<DrawerTrigger as-child>
<Button variant="outline">
顶部
</Button>
</DrawerTrigger>
<DrawerContent show-handle>
<DrawerHeader>
<DrawerTitle>顶部抽屉</DrawerTitle>
</DrawerHeader>
<div class="p-4 text-sm text-muted-foreground">
从顶部滑入的抽屉,适合通知或横幅。
</div>
</DrawerContent>
</Drawer>
<Drawer v-model:open="openLeft" swipe-direction="left">
<DrawerTrigger as-child>
<Button variant="outline">
左侧
</Button>
</DrawerTrigger>
<DrawerContent show-handle>
<DrawerHeader>
<DrawerTitle>左侧抽屉</DrawerTitle>
</DrawerHeader>
<div class="p-4 text-sm text-muted-foreground">
从左侧滑入的抽屉,适合导航菜单。
</div>
</DrawerContent>
</Drawer>
<Drawer v-model:open="openRight" swipe-direction="right">
<DrawerTrigger as-child>
<Button variant="outline">
右侧
</Button>
</DrawerTrigger>
<DrawerContent show-handle>
<DrawerHeader>
<DrawerTitle>右侧抽屉</DrawerTitle>
</DrawerHeader>
<div class="p-4 text-sm text-muted-foreground">
从右侧滑入的抽屉,适合详情面板。
</div>
</DrawerContent>
</Drawer>
</div>
</template>
吸附点
snapPoints 控制多级高度位置。
<script setup lang="ts">
import { ref } from 'vue'
import {
Button,
Drawer,
DrawerContent,
DrawerHeader,
DrawerTitle,
DrawerTrigger
} from 'moriui'
const snap = ref(0.5)
</script>
<template>
<Drawer v-model:snap-point="snap" :snap-points="[0.25, 0.5, 0.85]">
<DrawerTrigger as-child>
<Button variant="outline">
吸附点抽屉
</Button>
</DrawerTrigger>
<DrawerContent show-handle>
<DrawerHeader>
<DrawerTitle>吸附点</DrawerTitle>
</DrawerHeader>
<div class="p-4 text-sm text-muted-foreground">
当前吸附位置:{{ Math.round(snap * 100) }}%
</div>
<div class="px-4 pb-4 text-sm text-muted-foreground">
拖拽抽屉到不同位置会吸附到预设的点位(25%、50%、85%)。
</div>
</DrawerContent>
</Drawer>
</template>
拖拽手柄
手柄区域触发拖拽交互。
<script setup lang="ts">
import { ref } from 'vue'
import {
Button,
Drawer,
DrawerContent,
DrawerHeader,
DrawerTitle,
DrawerTrigger
} from 'moriui'
const showHandle = ref(true)
</script>
<template>
<div class="flex items-center gap-4">
<Drawer>
<DrawerTrigger as-child>
<Button variant="outline">
有拖拽手柄
</Button>
</DrawerTrigger>
<DrawerContent show-handle>
<DrawerHeader>
<DrawerTitle>拖拽手柄</DrawerTitle>
</DrawerHeader>
<div class="p-4 text-sm text-muted-foreground">
顶部拖拽手柄可以让你通过拖拽关闭抽屉。
</div>
</DrawerContent>
</Drawer>
<Drawer>
<DrawerTrigger as-child>
<Button variant="outline">
无拖拽手柄
</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader>
<DrawerTitle>无手柄</DrawerTitle>
</DrawerHeader>
<div class="p-4 text-sm text-muted-foreground">
通过传递 <code>show-handle</code> prop 控制手柄显隐。
</div>
</DrawerContent>
</Drawer>
</div>
</template>
非模态
非模态时背景可交互。
抽屉已关闭,背景按钮仍可点击
<script setup lang="ts">
import { ref } from 'vue'
import {
Button,
Drawer,
DrawerContent,
DrawerHeader,
DrawerTitle,
DrawerTrigger
} from 'moriui'
const open = ref(false)
</script>
<template>
<div class="flex flex-col items-center gap-4">
<Drawer v-model:open="open" :modal="false">
<DrawerTrigger as-child>
<Button variant="outline">
非模态抽屉
</Button>
</DrawerTrigger>
<DrawerContent show-handle>
<DrawerHeader>
<DrawerTitle>非模态抽屉</DrawerTitle>
</DrawerHeader>
<div class="p-4 text-sm text-muted-foreground">
非模态抽屉不阻挡背景交互,你可以继续与页面上的其他元素进行互动。
</div>
</DrawerContent>
</Drawer>
<p class="text-xs text-muted-foreground">
抽屉已{{ open ? '打开' : '关闭' }},背景按钮仍可点击
</p>
</div>
</template>
嵌套
在 Drawer 内打开另一个 Drawer。
<script setup lang="ts">
import {
Button,
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger
} from 'moriui'
</script>
<template>
<Drawer>
<DrawerTrigger as-child>
<Button variant="outline">
打开嵌套抽屉
</Button>
</DrawerTrigger>
<DrawerContent show-handle>
<DrawerHeader>
<DrawerTitle>主抽屉</DrawerTitle>
<DrawerDescription>这是第一层抽屉,你可以在其中打开第二个抽屉。</DrawerDescription>
</DrawerHeader>
<div class="p-4">
<Drawer>
<DrawerTrigger as-child>
<Button>打开二级抽屉</Button>
</DrawerTrigger>
<DrawerContent show-handle>
<DrawerHeader>
<DrawerTitle>二级抽屉</DrawerTitle>
<DrawerDescription>这是嵌套在第一个抽屉内部的第二个抽屉。</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<DrawerClose as-child>
<Button variant="outline">
关闭
</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
</div>
</DrawerContent>
</Drawer>
</template>
对话框
结合 Dialog 与 Drawer 的响应式模式。
<script setup lang="ts">
import { ref } from 'vue'
import {
Button,
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger
} from 'moriui'
const isDesktop = ref(false)
</script>
<template>
<div class="flex items-center gap-4">
<Drawer v-if="!isDesktop">
<DrawerTrigger as-child>
<Button variant="outline">
响应式抽屉
</Button>
</DrawerTrigger>
<DrawerContent show-handle>
<DrawerHeader>
<DrawerTitle>编辑资料</DrawerTitle>
<DrawerDescription>在移动端使用抽屉模式。</DrawerDescription>
</DrawerHeader>
<DrawerFooter>
<Button>保存</Button>
<DrawerClose as-child>
<Button variant="outline">
取消
</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
<Dialog v-else>
<DialogTrigger as-child>
<Button variant="outline">
响应式对话框
</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>编辑资料</DialogTitle>
<DialogDescription>在桌面端使用对话框模式。</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button>保存</Button>
<Button variant="outline">
取消
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
<label class="flex items-center gap-2 text-sm">
<input v-model="isDesktop" type="checkbox" class="toggle">
桌面端模式
</label>
</div>
</template>
RTL
在 RTL 页面中保持自然的方向逻辑。
<script setup lang="ts">
import {
Button,
DirectionProvider,
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger
} from 'moriui'
</script>
<template>
<DirectionProvider direction="rtl">
<Drawer dir="rtl">
<DrawerTrigger as-child>
<Button variant="outline">
فتح الدرج
</Button>
</DrawerTrigger>
<DrawerContent show-handle>
<DrawerHeader>
<DrawerTitle>درج من اليمين</DrawerTitle>
<DrawerDescription>اتجاه المحتوى من اليمين إلى اليسار.</DrawerDescription>
</DrawerHeader>
<div class="p-4 text-sm text-muted-foreground">
يدعم هذا الدرج الاتجاه من اليمين إلى اليسار للغات مثل العربية والعبرية.
</div>
<DrawerFooter>
<DrawerClose as-child>
<Button variant="outline">
إغلاق
</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
</DirectionProvider>
</template>
使用说明
组合方式
Drawer 由 Trigger、Portal、Overlay、Content 与 Handle 组成;Content 内可使用 Header、Title、Description 与 Footer 组织结构。
吸附点
snapPoints 设置内容停靠位置;activeSnapPoint 控制当前吸附点,用户拖拽时自动捕捉。
拖拽手柄
showHandle 控制交互手柄的显示;DrawerHandle 也可独立使用 Reka Drawer 原语的拖拽行为。
从各侧滑出
direction 控制抽屉从屏幕的哪一侧滑入,支持 top、right、bottom、left。
非模态
modal 控制是否阻止背景交互;非模态时用户可与背景内容交互。
RTL
direction 的左右逻辑在 RTL 上下文中自然翻转。
变体
Drawer 的 direction 支持 top、right、bottom、left;snapPoints 通过 number 定义吸附位置百分比。
可访问性
Drawer 复用 Reka UI Dialog 的焦点管理、Escape 关闭、遮罩交互和焦点归还。Content 内应提供 DrawerTitle,需要时提供 DrawerDescription。拖拽交互在触摸设备上需确保有键盘替代方式。
API 参考
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Drawer v-model:open | boolean | undefined | 抽屉的受控打开状态。 |
Drawer defaultOpen | boolean | false | 非受控初始打开状态。 |
Drawer direction | 'top' | 'right' | 'bottom' | 'left' | undefined | 抽屉从屏幕边缘滑出的方向。 |
Drawer modal | boolean | true | 是否阻断背景区域交互。 |
Drawer dismissible | boolean | true | 是否点击遮罩或拖拽关闭。 |
Drawer snapPoints | number[] | undefined | 可吸附的高度百分比位置。 |
DrawerContent showHandle | boolean | false | 是否显示拖拽手柄。 |
DrawerHandle class | HTMLAttributes["class"] | undefined | 合并到手柄的调用方类。 |
DrawerHeader as / asChild | string | Component / boolean | 'div' / false | 页眉的元素组合能力。 |
DrawerFooter as / asChild | string | Component / boolean | 'div' / false | 页脚的元素组合能力。 |
DrawerClose as / asChild | string | Component / boolean | undefined | 关闭按钮的元素组合能力。 |