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" />}>Open</DrawerTrigger>
  <DrawerContent>
    <DrawerHeader>
      <DrawerTitle>Are you absolutely sure?</DrawerTitle>
      <DrawerDescription>This action cannot be undone.</DrawerDescription>
    </DrawerHeader>
    <div className="p-4">{/* 此处放置内容 */}</div>
    <DrawerFooter>
      <Button>Submit</Button>
      <DrawerClose render={<Button variant="outline" />}>Cancel</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-directionuprightdownleft始终。
data-swipe-axisxy始终。
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 迁移

Aria 抽屉现在使用 Base UI 而不是 Vaul。如果你安装了之前的 Aria 抽屉,请将用法更新为 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" />}>
+   打开
+ </DrawerTrigger>

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

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

更新吸附点属性。

如果你使用吸附点,请重命名受控吸附点属性和连续吸附点属性。

  <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 专属属性。

Vaul 的 handleOnlyrepositionInputsshouldScaleBackground 等属性,在 Base UI 抽屉 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 参考

完整的 API 参考请参阅 Base UI 文档