Drawer
Drawer 从 HeroUI v2 到 v3 的迁移指南。
完整的 API 参考、样式指南与高级示例请参阅 v3 Drawer 文档。本指南只关注从 HeroUI v2 的迁移。
结构变化
在 v2 中,Drawer 与 Modal 共享相同的 API,使用 DrawerContent、DrawerHeader、DrawerBody 和 DrawerFooter,并采用渲染回调模式:
import { Drawer, DrawerContent, DrawerHeader, DrawerBody, DrawerFooter, Button, useDisclosure } from "@lenso/ui";
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 "@lenso/ui";
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: 复合组件:Drawer、Drawer.Backdrop、Drawer.Content、Drawer.Dialog、Drawer.Header、Drawer.Heading、Drawer.Body、Drawer.Footer、Drawer.Handle、Drawer.CloseTrigger。Drawer 的第一个子节点会成为触发器。
2. 触发模式
v2: 使用外部触发器,并通过 useDisclosure 钩子与 isOpen / onOpenChange 管理状态
v3: 内置触发器,Drawer 的第一个子节点会自动成为触发器。受控状态可通过 useOverlayState 钩子管理。
3. v3 新特性
- 拖动关闭:在手柄、头部和底部区域内置基于指针的拖动手势
- 拖动手柄:
Drawer.Handle是视觉拖动指示器组件 - 内置关闭触发器:
Drawer.CloseTrigger会渲染一个关闭按钮 - 基于 slot 的关闭:带有
slot="close"的按钮会自动关闭 Drawer
4. Prop 变更
| v2 prop | v3 等效项 | 说明 |
|---|---|---|
isOpen | Drawer.Backdrop 的 isOpen | 或使用 useOverlayState |
onOpenChange | Drawer.Backdrop 的 onOpenChange | 或使用 useOverlayState |
onClose | - | 在按钮上使用 onOpenChange 或 slot="close" |
placement | Drawer.Content 的 placement | "right" → "right"、"left" → "left"、"top" → "top"、"bottom" → "bottom"。默认值从 "right" 改为 "bottom" |
size | - | 已移除(请在 Drawer.Dialog 上使用 Tailwind CSS) |
radius | - | 已移除(请改用 Tailwind CSS) |
backdrop | Drawer.Backdrop 的 variant | 值保持一致:"opaque"、"blur"、"transparent" |
isDismissable | Drawer.Backdrop 的 isDismissable | 保持一致 |
isKeyboardDismissDisabled | Drawer.Backdrop 的 isKeyboardDismissDisabled | 保持一致 |
shouldBlockScroll | - | v3 中始终会阻止滚动 |
hideCloseButton | - | 省略 Drawer.CloseTrigger 即可隐藏 |
closeButton | - | 将自定义内容传给 Drawer.CloseTrigger |
motionProps | - | 已移除(v3 使用基于 CSS 的动画) |
disableAnimation | - | 已移除 |
portalContainer | - | 已移除 |
classNames | - | 在各个复合组件上使用 className |
5. Hook 变更
v2: 使用 useDisclosure 钩子管理打开 / 关闭状态
v3: 使用 useOverlayState 钩子(替代 useDisclosure)
// v2const { isOpen, onOpen, onOpenChange } = useDisclosure();
// v3const state = useOverlayState();// state.isOpen, state.open(), state.close(), state.toggle()迁移示例
基本抽屉
import { Drawer, DrawerContent, DrawerHeader, DrawerBody, DrawerFooter, Button, useDisclosure } from "@lenso/ui";
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></>位置
<Drawer isOpen={isOpen} onOpenChange={onOpenChange} placement="left"> <DrawerContent> {(onClose) => ( <> <DrawerHeader>Left Drawer</DrawerHeader> <DrawerBody>Content</DrawerBody> </> )} </DrawerContent></Drawer>遮罩变体
<Drawer isOpen={isOpen} onOpenChange={onOpenChange} backdrop="blur"> <DrawerContent> {(onClose) => ( <> <DrawerHeader>Blurred Backdrop</DrawerHeader> <DrawerBody>Content</DrawerBody> </> )} </DrawerContent></Drawer>受控状态
import { useDisclosure } from "@lenso/ui";
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></>不可关闭
<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>样式变化
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总结
- 组件结构:渲染回调模式 → 带显式子组件的复合组件
- 触发模式:外部
useDisclosure+onPress→ 内置触发器(Drawer的第一个子节点) - 状态钩子:
useDisclosure→useOverlayState,并使用open()、close()、toggle()方法 - 位置:
Drawer上的 prop →Drawer.Content上的 prop。默认值从"right"改为"bottom" - 遮罩:
backdropprop →Drawer.Backdrop的variantprop - 关闭按钮:
hideCloseButton/closeButtonprops → 省略或自定义Drawer.CloseTrigger - 基于 slot 的关闭:带有
slot="close"的按钮会自动关闭 Drawer - 新功能:通过
Drawer.Handle拖动关闭,并支持基于速度的关闭 - 动画:
motionProps(Framer Motion)→ 基于 CSS 的动画 - 已移除样式 prop:
size、radius→ 使用 Tailwind CSS - 已移除 classNames:在各个复合组件上使用
className