Modal
Modal 从 HeroUI v2 到 v3 的迁移指南。
完整的 API 参考、样式指南与高级示例请参阅 v3 Modal 文档。本指南只关注从 HeroUI v2 的迁移。
结构变化
在 v2 中,Modal 使用相互独立的组件:
import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button, useDisclosure } from "@lenso/ui";
export default function App() { const {isOpen, onOpen, onOpenChange} = useDisclosure();
return ( <> <Button onPress={onOpen}>Open Modal</Button> <Modal isOpen={isOpen} onOpenChange={onOpenChange}> <ModalContent> <ModalHeader>Title</ModalHeader> <ModalBody>Content</ModalBody> <ModalFooter>Footer</ModalFooter> </ModalContent> </Modal> </> );}在 v3 中,Modal 使用复合组件:
import { Modal, Button } from "@lenso/ui";
export default function App() { return ( <Modal> <Button>Open Modal</Button> <Modal.Backdrop> <Modal.Container> <Modal.Dialog> <Modal.CloseTrigger /> <Modal.Header> <Modal.Heading>Title</Modal.Heading> </Modal.Header> <Modal.Body>Content</Modal.Body> <Modal.Footer>Footer</Modal.Footer> </Modal.Dialog> </Modal.Container> </Modal.Backdrop> </Modal> );}主要变化
1. 组件结构
v2: 相互独立的组件(Modal、ModalContent、ModalHeader、ModalBody、ModalFooter)
v3: 复合组件(Modal.Backdrop、Modal.Container、Modal.Dialog、Modal.Header、Modal.Body、Modal.Footer)
2. 内置触发:Modal.Trigger
v2: 需要 useDisclosure,并手动把 onPress 接到打开逻辑上。
v3: 提供 Modal.Trigger 作为内置触发组件,按下即可自动打开模态框,无需自行管理状态。
import { Modal, ModalContent, Button, useDisclosure } from "@lenso/ui";
const {isOpen, onOpen, onOpenChange} = useDisclosure();
<Button onPress={onOpen}>Open Modal</Button><Modal isOpen={isOpen} onOpenChange={onOpenChange}> <ModalContent>{/* content */}</ModalContent></Modal>Modal.Trigger 会把任意内容包装成可按压元素以打开模态框。当你需要标准 Button 以外的自定义触发元素时使用它。
3. 状态管理
v2: 使用 useDisclosure。
v3: 内置触发场景下可不需要状态;也可在 Modal.Backdrop 上用受控的 isOpen / onOpenChange,或使用 useOverlayState 并把 state 传给根组件。
useOverlayState 替代 useDisclosure
useOverlayState 可直接替代 v2 的 useDisclosure,支持受控与非受控:
import { useOverlayState } from "@lenso/ui";
// 非受控(内部自管状态)const state = useOverlayState();
// 非受控且默认打开const state = useOverlayState({ defaultOpen: true });
// 带回调const state = useOverlayState({ onOpenChange: (isOpen) => console.log("Modal is now:", isOpen),});
// 受控(由你管理状态)const [isOpen, setIsOpen] = useState(false);const state = useOverlayState({ isOpen, onOpenChange: setIsOpen });Hook API:
| 属性 | 类型 | 说明 |
|---|---|---|
state.isOpen | boolean | 浮层当前是否打开 |
state.open() | () => void | 打开浮层 |
state.close() | () => void | 关闭浮层 |
state.toggle() | () => void | 切换打开/关闭 |
state.setOpen(isOpen) | (isOpen: boolean) => void | 直接设置打开状态 |
将 state 传给 Modal 根组件以完成连接:
const state = useOverlayState();
<Modal state={state}> <Button onPress={state.open}>Open</Button> <Modal.Backdrop> <Modal.Container> <Modal.Dialog>{/* content */}</Modal.Dialog> </Modal.Container> </Modal.Backdrop></Modal>从 useDisclosure 迁移到 useOverlayState 的更多细节,请参阅 Hooks 迁移指南。
4. Prop 变更
| v2 prop | v3 位置 | 说明 |
|---|---|---|
size | size(在 Modal.Container) | 简化取值(xs、sm、md、lg、cover、full) |
radius | — | 已移除(请用 Tailwind CSS) |
shadow | — | 已移除(请用 Tailwind CSS) |
backdrop | variant(在 Modal.Backdrop) | 已重命名;取值不变(opaque、blur、transparent) |
scrollBehavior | scroll(在 Modal.Container) | 已重命名(normal → inside) |
placement | placement(在 Modal.Container) | 移到 Container |
isDismissable | isDismissable(在 Modal.Backdrop) | 移到 Modal.Backdrop |
isKeyboardDismissDisabled | isKeyboardDismissDisabled(在 Modal.Backdrop) | 移到 Modal.Backdrop |
isOpen | isOpen(在 Modal.Backdrop) | 受控状态在 Modal.Backdrop |
onOpenChange | onOpenChange(在 Modal.Backdrop) | 同上 |
onClose | — | 使用渲染 prop 提供的 close |
hideCloseButton | — | 省略 Modal.CloseTrigger 即可 |
closeButton | — | 使用 Modal.CloseTrigger 自定义内容 |
motionProps | — | 已移除(动画机制已不同) |
classNames | — | 在各子组件上使用 className |
shouldBlockScroll | — | 已移除(由组件自动处理) |
portalContainer | — | 已移除 |
迁移示例
受控 Modal
import { useDisclosure } from "@lenso/ui";
const {isOpen, onOpen, onOpenChange} = useDisclosure();
<Modal isOpen={isOpen} onOpenChange={onOpenChange}> <ModalContent> {(onClose) => ( <> <ModalHeader>Title</ModalHeader> <ModalBody>Content</ModalBody> </> )} </ModalContent></Modal>useDisclosure → useOverlayState 的完整迁移说明,请参阅 Hooks 迁移指南。
遮罩与 Container 的 prop
{/* Backdrop */}<Modal backdrop="blur"> <ModalContent>{/* content */}</ModalContent></Modal>
{/* Placement */}<Modal placement="top"> <ModalContent>{/* content */}</ModalContent></Modal>
{/* Scroll behavior */}<Modal scrollBehavior="outside"> <ModalContent>{/* content */}</ModalContent></Modal>关闭按钮
{/* Without close button */}<Modal hideCloseButton> <ModalContent>{/* content */}</ModalContent></Modal>
{/* Custom close button */}<Modal closeButton={<CustomCloseIcon />}> <ModalContent>{/* content */}</ModalContent></Modal>自定义触发器
import { Modal, ModalContent, useDisclosure } from "@lenso/ui";
const {isOpen, onOpen, onOpenChange} = useDisclosure();
{/* Any element needed manual onPress + useDisclosure */}<div onClick={onOpen} className="cursor-pointer rounded-xl bg-surface p-4"> <p className="font-semibold">Settings</p> <p className="text-xs text-muted">Manage your preferences</p></div><Modal isOpen={isOpen} onOpenChange={onOpenChange}> <ModalContent>{/* content */}</ModalContent></Modal>图标与标题
<ModalHeader className="flex flex-col gap-1"> <Icon /> Modal Title</ModalHeader>组件结构(Anatomy)
v3 的 Modal 结构如下:
Modal (Root) ├── Trigger (e.g. Button or Modal.Trigger) └── Modal.Backdrop (variant, isDismissable, isKeyboardDismissDisabled) └── Modal.Container (placement, scroll, size) └── Modal.Dialog ├── Modal.CloseTrigger (optional) ├── Modal.Header │ ├── Modal.Icon (optional) │ └── Modal.Heading ├── Modal.Body └── Modal.Footer总结
- 组件结构:必须使用复合组件(
Modal.Container、Modal.Dialog等)。 - 内置触发:自定义触发用
Modal.Trigger;或将Button作为Modal的直接子元素,无需再手动接线状态。 - 状态管理:
useDisclosure由useOverlayState替代;支持受控/非受控,并支持根Modal的stateprop。 - prop 迁移:许多 prop 从
Modal移到Modal.Backdrop与Modal.Container。 - 关闭回调:
onClose由Modal.Dialog渲染 prop 中的close替代。 - 关闭按钮:
hideCloseButton/closeButton由Modal.CloseTrigger组合方式替代。 - 尺寸:在
Modal.Container上使用size(xs、sm、md、lg、cover、full);radius与shadow已移除,请用 Tailwind CSS。 - 遮罩:
backdrop→Modal.Backdrop上的variant(opaque、blur、transparent)。 - 滚动 prop 重命名:
scrollBehavior→scroll(normal→inside)。 - 动画:
motionProps已移除,动画机制已变化。 - 新增子组件:
Modal.Trigger、Modal.Icon、Modal.Heading、Modal.CloseTrigger。