120k

抽屉

React 的抽屉组件。

"use client"

import * as React from "react"

安装

pnpm dlx shadcn@latest add drawer

将以下内容添加到你的全局样式中。在 iOS Safari 上,抽屉遮罩层采用绝对定位,并且在页面滚动后需要一个设置了定位的 body 才能覆盖视口。详情请参阅 Base UI 文档

body {
  position: relative;
}

用法

import {
  Drawer,
  DrawerClose,
  DrawerContent,
  DrawerDescription,
  DrawerFooter,
  DrawerHeader,
  DrawerTitle,
  DrawerTrigger,
} from "@/components/ui/drawer"
<Drawer>
  <DrawerTrigger render={<Button variant="outline" />}>打开</DrawerTrigger>
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>你确定吗?</DrawerTitle>
      <DrawerDescription>此操作无法撤销。</DrawerDescription>
    </DrawerHeader>
    <div className="p-4">{/* 内容在这里 */}</div>
    <DrawerFooter>
      <Button>提交</Button>
      <DrawerClose render={<Button variant="outline" />}>取消</DrawerClose>
    </DrawerFooter>
  </DrawerContent>
</Drawer>

组合

使用以下组合来构建 Drawer:

Drawer
├── DrawerTrigger
└── DrawerContent
    ├── DrawerHeader
    │   ├── DrawerTitle
    │   └── DrawerDescription
    └── DrawerFooter

DrawerContent 组合了来自 Base UI 的 portal、overlay、viewport 和 popup。对于更低级别的控制,DrawerPortalDrawerOverlayDrawerSwipeHandle 也已导出。

自定义尺寸

垂直抽屉会根据其内容自动设置大小,并默认上限为 calc(100dvh - 6rem)。侧边抽屉占据视口宽度的 75%,在更大的屏幕上则为 24rem

要自定义垂直抽屉的高度,请在 DrawerContent 上使用 h-*max-h-* 工具类。

<DrawerContent className="h-[50vh]">

要自定义侧边抽屉的宽度,请在 DrawerContent 上使用 w-*max-w-* 工具类。

<DrawerContent className="w-96">

当同一个组件以多种方向渲染时,可使用 data-[swipe-axis=*] 变体将覆盖范围限定到某一个轴。

<DrawerContent className="data-[swipe-axis=y]:max-h-[50vh] data-[swipe-axis=x]:w-96">

要让抽屉中的某个区域可滚动,请将滚动容器设置为 flex 项。避免使用 h-full,因为它在内容自适应高度的抽屉中无法正确解析。

<DrawerContent>
  <DrawerHeader>...</DrawerHeader>
  <div className="flex-1 overflow-y-auto p-4">{/* 可滚动内容 */}</div>
  <DrawerFooter>...</DrawerFooter>
</DrawerContent>

样式

抽屉提供了用于样式级自定义的 CSS 变量。请在 DrawerContent 上设置尺寸变量。请在你的 CSS 中的 [data-slot=drawer-overlay] 上设置遮罩变量。

变量默认值描述
--drawer-inset0px使抽屉从视口边缘浮起。
--drawer-bleed-backgroundvar(--color-popover)在滑动越界时填充抽屉后面的空隙。
--drawer-overlay-min-opacity0遮罩的最小不透明度。启用吸附点时默认为 0.5

抽屉还会设置一些数据属性,你可以在 DrawerContent 上使用诸如 data-[swipe-direction=down]: 的变体来针对它们进行样式设置,或者在其后代上使用 group-data-[swipe-axis=y]/drawer-popup:

属性设置时机
data-swipe-directionup, right, down, left始终。
data-swipe-axisx, y始终。
data-snap-points存在抽屉具有吸附点。
data-expanded存在抽屉处于完整吸附点。
data-swiping存在正在进行滑动。
data-nested-drawer-open存在上方打开了一个嵌套抽屉。

示例

位置

使用 swipeDirection 属性来设置抽屉的侧边。

可用选项为 uprightdownleft

import { Button } from "@/components/ui/button"
import {
  Drawer,

滑动把手

Drawer 上使用 showSwipeHandle 来渲染一个滑动把手。

"use client"

import { Button } from "@/components/ui/button"

嵌套

从另一个抽屉内部打开抽屉。父抽屉会保持挂载,并堆叠在最前面的抽屉后面。

"use client"

import { useIsMobile } from "@/hooks/use-mobile"

非模态

设置 modal={false} 以允许在抽屉打开时与页面其余部分交互。可与 disablePointerDismissal 结合使用,以防止在点击外部时关闭抽屉。使用 modal="trap-focus" 可以在保持滚动和指针交互不受限制的同时,将焦点保持在抽屉内。

import { Button } from "@/components/ui/button"
import {
  Drawer,

吸附点

使用 snapPoints 将抽屉吸附到预设高度。介于 01 之间的数字表示视口的比例。大于 1 的数字将被视为像素值。字符串值支持 pxrem 单位。吸附点适用于垂直抽屉。

使用受控的 snapPointonSnapPointChange 属性跟踪当前活动的吸附点。在完全吸附点时,抽屉会获得一个 data-expanded 属性,你可以使用 data-expanded: 变体对其进行样式设置。

"use client"

import { Button } from "@/components/ui/button"

响应式

你可以将 DialogDrawer 组件结合使用,创建响应式对话框。在桌面端渲染 Dialog 组件,在移动端渲染 Drawer 组件。

"use client"

import * as React from "react"

从 Vaul 迁移

基础抽屉现在使用 Base UI 而不是 Vaul。如果你之前安装了基础抽屉,请将你的用法更新为 Base UI API。

更新依赖项。

- npm install vaul
+ npm install @base-ui/react

direction 替换为 swipeDirection

使用 down 代替 bottom,使用 up 代替 topleftright 保持不变。

- <Drawer direction="bottom">
+ <Drawer swipeDirection="down">

asChild 替换为 render

对于 DrawerTrigger,将触发元素传递给 render 属性。

- <DrawerTrigger asChild>
-   <Button variant="outline">Open</Button>
- </DrawerTrigger>
+ <DrawerTrigger render={<Button variant="outline" />}>
+   Open
+ </DrawerTrigger>

对于 DrawerClose,将关闭元素传递给 render 属性。

- <DrawerClose asChild>
-   <Button variant="outline">Cancel</Button>
- </DrawerClose>
+ <DrawerClose render={<Button variant="outline" />}>
+   Cancel
+ </DrawerClose>

更新 snap point 属性。

如果你使用 snap points,请重命名受控的 snap point 属性以及顺序 snap point 属性。

  <Drawer
    snapPoints={[0.25, 0.5, 1]}
-   activeSnapPoint={snapPoint}
-   setActiveSnapPoint={setSnapPoint}
-   snapToSequentialPoint
+   snapPoint={snapPoint}
+   onSnapPointChange={setSnapPoint}
+   snapToSequentialPoints
  >

更新动画和焦点属性。

- <Drawer onAnimationEnd={(open) => setDone(open)}>
+ <Drawer onOpenChangeComplete={(open) => setDone(open)}>
- <DrawerContent onOpenAutoFocus={(event) => event.preventDefault()}>
+ <DrawerContent initialFocus={false}>

检查仅 Vaul 支持的属性。

handleOnlyrepositionInputsshouldScaleBackground 这样的 Vaul 属性在基础抽屉 API 中没有一一对应的替代项。请使用 Base UI 属性,例如 disablePointerDismissalmodalsnapPoints,或受控的 open 状态来实现你需要的行为。

- <Drawer handleOnly repositionInputs={false} shouldScaleBackground>
+ <Drawer>
- <Drawer dismissible={false}>
+ <Drawer disablePointerDismissal>

更新自定义数据属性选择器。

将 Vaul 的 data-vaul-drawer-direction 选择器替换为 Base UI 的 data-swipe-direction 选择器。

- <DrawerContent className="data-[vaul-drawer-direction=bottom]:max-h-[50vh]">
+ <DrawerContent className="data-[swipe-direction=down]:max-h-[50vh]">

Base UI 还公开了诸如 data-swipingdata-starting-styledata-ending-style 之类的属性,用于滑动和过渡状态。DrawerContent 内部的后代元素可以使用 group-data-[swipe-axis=x]/drawer-popupgroup-data-[swipe-axis=y]/drawer-popup 来进行按轴特定的样式设置。

API 参考

请参阅 Base UI 文档 以获取完整的 API 参考。