ProComponents, templates & AI tooling
HeroUI
27.7k

Drawer

Drawer 从 HeroUI v2 到 v3 的迁移指南。

完整的 API 参考、样式指南与高级示例请参阅 v3 Drawer 文档。本指南只关注从 HeroUI v2 的迁移。

结构变化

在 v2 中,DrawerModal 共享相同的 API,使用 DrawerContentDrawerHeaderDrawerBodyDrawerFooter,并采用渲染回调模式:

import { Drawer, DrawerContent, DrawerHeader, DrawerBody, DrawerFooter, Button, useDisclosure } from "@heroui/react";

export default function App() {
  const { isOpen, onOpen, onOpenChange } = useDisclosure();

  return (
    <>
      <Button onPress={onOpen}>Open Drawer</Button>
      <Drawer isOpen={isOpen} onOpenChange={onOpenChange} placement="right">
        <DrawerContent>
          {(onClose) => (
            <>
              <DrawerHeader>Drawer Title</DrawerHeader>
              <DrawerBody>
                <p>Drawer content goes here.</p>
              </DrawerBody>
              <DrawerFooter>
                <Button onPress={onClose}>Close</Button>
              </DrawerFooter>
            </>
          )}
        </DrawerContent>
      </Drawer>
    </>
  );
}

在 v3 中,Drawer 改用复合组件模式,提供显式子组件与内置触发器支持:

import { Drawer, Button } from "@heroui/react";

export default function App() {
  return (
    <Drawer>
      <Button>Open Drawer</Button>
      <Drawer.Backdrop>
        <Drawer.Content placement="right">
          <Drawer.Dialog>
            <Drawer.Handle />
            <Drawer.CloseTrigger />
            <Drawer.Header>
              <Drawer.Heading>Drawer Title</Drawer.Heading>
            </Drawer.Header>
            <Drawer.Body>
              <p>Drawer content goes here.</p>
            </Drawer.Body>
            <Drawer.Footer>
              <Button slot="close">Close</Button>
            </Drawer.Footer>
          </Drawer.Dialog>
        </Drawer.Content>
      </Drawer.Backdrop>
    </Drawer>
  );
}

主要变化

1. 组件结构

v2: Drawer 包裹 DrawerContent,并使用渲染回调模式;触发器需要通过 useDisclosure 单独管理
v3: 复合组件:DrawerDrawer.BackdropDrawer.ContentDrawer.DialogDrawer.HeaderDrawer.HeadingDrawer.BodyDrawer.FooterDrawer.HandleDrawer.CloseTriggerDrawer 的第一个子节点会成为触发器。

2. 触发模式

v2: 使用外部触发器,并通过 useDisclosure 钩子与 isOpen / onOpenChange 管理状态
v3: 内置触发器,Drawer 的第一个子节点会自动成为触发器。受控状态可通过 useOverlayState 钩子管理。

3. v3 新特性

  • 拖动关闭:在手柄、头部和底部区域内置基于指针的拖动手势
  • 拖动手柄Drawer.Handle 是视觉拖动指示器组件
  • 内置关闭触发器Drawer.CloseTrigger 会渲染一个关闭按钮
  • 基于 slot 的关闭:带有 slot="close" 的按钮会自动关闭 Drawer

4. Prop 变更

v2 propv3 等效项说明
isOpenDrawer.BackdropisOpen或使用 useOverlayState
onOpenChangeDrawer.BackdroponOpenChange或使用 useOverlayState
onClose-在按钮上使用 onOpenChangeslot="close"
placementDrawer.Contentplacement"right""right""left""left""top""top""bottom""bottom"。默认值从 "right" 改为 "bottom"
size-已移除(请在 Drawer.Dialog 上使用 Tailwind CSS)
radius-已移除(请改用 Tailwind CSS)
backdropDrawer.Backdropvariant值保持一致:"opaque""blur""transparent"
isDismissableDrawer.BackdropisDismissable保持一致
isKeyboardDismissDisabledDrawer.BackdropisKeyboardDismissDisabled保持一致
shouldBlockScroll-v3 中始终会阻止滚动
hideCloseButton-省略 Drawer.CloseTrigger 即可隐藏
closeButton-将自定义内容传给 Drawer.CloseTrigger
motionProps-已移除(v3 使用基于 CSS 的动画)
disableAnimation-已移除
portalContainer-已移除
classNames-在各个复合组件上使用 className

5. Hook 变更

v2: 使用 useDisclosure 钩子管理打开 / 关闭状态
v3: 使用 useOverlayState 钩子(替代 useDisclosure

// v2
const { isOpen, onOpen, onOpenChange } = useDisclosure();

// v3
const state = useOverlayState();
// state.isOpen, state.open(), state.close(), state.toggle()

迁移示例

基本抽屉

import { Drawer, DrawerContent, DrawerHeader, DrawerBody, DrawerFooter, Button, useDisclosure } from "@heroui/react";

const { isOpen, onOpen, onOpenChange } = useDisclosure();

<>
  <Button onPress={onOpen}>Open</Button>
  <Drawer isOpen={isOpen} onOpenChange={onOpenChange}>
    <DrawerContent>
      {(onClose) => (
        <>
          <DrawerHeader>Title</DrawerHeader>
          <DrawerBody>Content</DrawerBody>
          <DrawerFooter>
            <Button onPress={onClose}>Close</Button>
          </DrawerFooter>
        </>
      )}
    </DrawerContent>
  </Drawer>
</>
import { Drawer, Button } from "@heroui/react";

<Drawer>
  <Button>Open</Button>
  <Drawer.Backdrop>
    <Drawer.Content>
      <Drawer.Dialog>
        <Drawer.CloseTrigger />
        <Drawer.Header>
          <Drawer.Heading>Title</Drawer.Heading>
        </Drawer.Header>
        <Drawer.Body>Content</Drawer.Body>
        <Drawer.Footer>
          <Button slot="close">Close</Button>
        </Drawer.Footer>
      </Drawer.Dialog>
    </Drawer.Content>
  </Drawer.Backdrop>
</Drawer>

位置

<Drawer isOpen={isOpen} onOpenChange={onOpenChange} placement="left">
  <DrawerContent>
    {(onClose) => (
      <>
        <DrawerHeader>Left Drawer</DrawerHeader>
        <DrawerBody>Content</DrawerBody>
      </>
    )}
  </DrawerContent>
</Drawer>
<Drawer>
  <Button>Open</Button>
  <Drawer.Backdrop>
    <Drawer.Content placement="left">
      <Drawer.Dialog>
        <Drawer.CloseTrigger />
        <Drawer.Header>
          <Drawer.Heading>Left Drawer</Drawer.Heading>
        </Drawer.Header>
        <Drawer.Body>Content</Drawer.Body>
      </Drawer.Dialog>
    </Drawer.Content>
  </Drawer.Backdrop>
</Drawer>

遮罩变体

<Drawer isOpen={isOpen} onOpenChange={onOpenChange} backdrop="blur">
  <DrawerContent>
    {(onClose) => (
      <>
        <DrawerHeader>Blurred Backdrop</DrawerHeader>
        <DrawerBody>Content</DrawerBody>
      </>
    )}
  </DrawerContent>
</Drawer>
<Drawer>
  <Button>Open</Button>
  <Drawer.Backdrop variant="blur">
    <Drawer.Content>
      <Drawer.Dialog>
        <Drawer.CloseTrigger />
        <Drawer.Header>
          <Drawer.Heading>Blurred Backdrop</Drawer.Heading>
        </Drawer.Header>
        <Drawer.Body>Content</Drawer.Body>
      </Drawer.Dialog>
    </Drawer.Content>
  </Drawer.Backdrop>
</Drawer>

受控状态

import { useDisclosure } from "@heroui/react";

const { isOpen, onOpen, onOpenChange } = useDisclosure();

<>
  <Button onPress={onOpen}>Open</Button>
  <Drawer isOpen={isOpen} onOpenChange={onOpenChange}>
    <DrawerContent>
      {(onClose) => (
        <>
          <DrawerHeader>Controlled</DrawerHeader>
          <DrawerBody>Content</DrawerBody>
          <DrawerFooter>
            <Button onPress={onClose}>Close</Button>
          </DrawerFooter>
        </>
      )}
    </DrawerContent>
  </Drawer>
</>
import { useOverlayState } from "@heroui/react";

const state = useOverlayState();

<>
  <Button onPress={state.open}>Open</Button>
  <Drawer state={state}>
    <Drawer.Backdrop>
      <Drawer.Content>
        <Drawer.Dialog>
          <Drawer.CloseTrigger />
          <Drawer.Header>
            <Drawer.Heading>Controlled</Drawer.Heading>
          </Drawer.Header>
          <Drawer.Body>Content</Drawer.Body>
          <Drawer.Footer>
            <Button onPress={state.close}>Close</Button>
          </Drawer.Footer>
        </Drawer.Dialog>
      </Drawer.Content>
    </Drawer.Backdrop>
  </Drawer>
</>

不可关闭

<Drawer
  isOpen={isOpen}
  onOpenChange={onOpenChange}
  isDismissable={false}
  hideCloseButton
>
  <DrawerContent>
    {(onClose) => (
      <>
        <DrawerHeader>Confirm Action</DrawerHeader>
        <DrawerBody>Are you sure?</DrawerBody>
        <DrawerFooter>
          <Button onPress={onClose}>Confirm</Button>
        </DrawerFooter>
      </>
    )}
  </DrawerContent>
</Drawer>
<Drawer>
  <Button>Open</Button>
  <Drawer.Backdrop isDismissable={false}>
    <Drawer.Content>
      <Drawer.Dialog>
        <Drawer.Header>
          <Drawer.Heading>Confirm Action</Drawer.Heading>
        </Drawer.Header>
        <Drawer.Body>Are you sure?</Drawer.Body>
        <Drawer.Footer>
          <Button slot="close">Confirm</Button>
        </Drawer.Footer>
      </Drawer.Dialog>
    </Drawer.Content>
  </Drawer.Backdrop>
</Drawer>

样式变化

v2:classNames prop

<Drawer
  classNames={{
    wrapper: "custom-wrapper",
    base: "custom-base",
    backdrop: "custom-backdrop",
    header: "custom-header",
    body: "custom-body",
    footer: "custom-footer",
    closeButton: "custom-close",
  }}
/>

v3:直接使用 className prop

<Drawer>
  <Button>Open</Button>
  <Drawer.Backdrop className="custom-backdrop">
    <Drawer.Content>
      <Drawer.Dialog className="custom-base">
        <Drawer.CloseTrigger className="custom-close" />
        <Drawer.Header className="custom-header">
          <Drawer.Heading>Title</Drawer.Heading>
        </Drawer.Header>
        <Drawer.Body className="custom-body">Content</Drawer.Body>
        <Drawer.Footer className="custom-footer">Actions</Drawer.Footer>
      </Drawer.Dialog>
    </Drawer.Content>
  </Drawer.Backdrop>
</Drawer>

组件结构

v3 Drawer 遵循以下结构:

Drawer (Root)
  ├── [Trigger element] (first child becomes trigger)
  └── Drawer.Backdrop
      └── Drawer.Content (placement)
          └── Drawer.Dialog
              ├── Drawer.Handle (optional, drag indicator)
              ├── Drawer.CloseTrigger (optional, close button)
              ├── Drawer.Header
              │   └── Drawer.Heading
              ├── Drawer.Body (scrollable)
              └── Drawer.Footer

总结

  1. 组件结构:渲染回调模式 → 带显式子组件的复合组件
  2. 触发模式:外部 useDisclosure + onPress → 内置触发器(Drawer 的第一个子节点)
  3. 状态钩子useDisclosureuseOverlayState,并使用 open()close()toggle() 方法
  4. 位置Drawer 上的 prop → Drawer.Content 上的 prop。默认值从 "right" 改为 "bottom"
  5. 遮罩backdrop prop → Drawer.Backdropvariant prop
  6. 关闭按钮hideCloseButton / closeButton props → 省略或自定义 Drawer.CloseTrigger
  7. 基于 slot 的关闭:带有 slot="close" 的按钮会自动关闭 Drawer
  8. 新功能:通过 Drawer.Handle 拖动关闭,并支持基于速度的关闭
  9. 动画motionProps(Framer Motion)→ 基于 CSS 的动画
  10. 已移除样式 propsizeradius → 使用 Tailwind CSS
  11. 已移除 classNames:在各个复合组件上使用 className

本页目录