#Modal 弹窗
在弹窗中展示内容或完成操作。
#编辑内容
basic.tsx
import { Button, Form, FormField, Input, Modal, Select } from '@ifhover/leaf-ui';
import { useRef, useState } from 'react';
export function ModalBasic() {
const [open, setOpen] = useState(false);
const formRef = useRef<HTMLFormElement>(null);
return (
<>
<Button onClick={() => setOpen(true)}>编辑项目</Button>
<Modal
open={open}
title="项目设置"
confirmText="保存"
onClose={() => setOpen(false)}
onConfirm={() => formRef.current?.requestSubmit()}
>
<Form
ref={formRef}
layout="vertical"
onSubmit={(event) => {
event.preventDefault();
setOpen(false);
}}
>
<FormField label="项目名称" required>
<Input name="name" defaultValue="Leaf UI" />
</FormField>
<FormField label="负责人">
<Select
name="owner"
options={[
{ value: 'design', label: '设计团队' },
{ value: 'engineering', label: '工程团队' },
]}
/>
</FormField>
</Form>
</Modal>
</>
);
}
#自定义底部
footer.tsx
import { Button, Modal } from '@ifhover/leaf-ui';
import { useState } from 'react';
export function ModalFooter() {
const [open, setOpen] = useState(false);
const [extra, setExtra] = useState(false);
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>
自定义底部
</Button>
<Modal
open={open}
title="扩展操作"
onClose={() => setOpen(false)}
footer={({ cancelButton, confirmButton }) => (
<>
<Button
variant="ghost"
style={{ marginRight: 'auto' }}
onClick={() => setExtra(!extra)}
>
查看详情
</Button>
{cancelButton}
{confirmButton}
</>
)}
>
<p>通过底部函数增加操作,保留默认取消与确定按钮。</p>
{extra && <p>这里展示额外的信息。</p>}
</Modal>
</>
);
}
#状态销毁与初始焦点
capabilities.tsx
import { Button, Input, Modal } from '@ifhover/leaf-ui';
import { useRef, useState } from 'react';
export function Capabilities() {
const [open, setOpen] = useState(false);
const focus = useRef<HTMLInputElement>(null);
return (
<>
<Button onClick={() => setOpen(true)}>{'打开编辑器'}</Button>
<Modal
open={open}
onClose={() => setOpen(false)}
title={'临时草稿'}
destroyOnClose
initialFocus={focus}
footer={({ cancelButton, confirmButton }) => (
<>
{cancelButton}
{confirmButton}
</>
)}
>
<Input
ref={focus}
aria-label={'草稿标题'}
placeholder={'关闭后不保留草稿'}
/>
</Modal>
</>
);
}
#图标与说明
使用 icon 和 description 为操作补充上下文。
description.tsx
import { Button, Modal } from '@ifhover/leaf-ui';
import { Save } from 'lucide-react';
import { useState } from 'react';
export function ModalDescription() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="outline" onClick={() => setOpen(true)}>
带图标与说明
</Button>
<Modal
open={open}
title="保存设置"
description="保存后,新设置将立即生效。"
icon={<Save />}
confirmText="保存"
onClose={() => setOpen(false)}
onConfirm={() => setOpen(false)}
>
确认保存当前设置?
</Modal>
</>
);
}
正文使用 Scrollbar,内容较长时内部滚动,标题和操作按钮始终可见。没有副标题时头部更紧凑,底部使用较小的纵向留白。
#Modal API
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
initialFocus | RefObject<HTMLElement | null> | (() => HTMLElement | null) | — | 指定打开后的初始焦点。 |
returnFocus | boolean | RefObject<HTMLElement | null> | (() => HTMLElement | null) | — | 关闭后返回原焦点;也可指定目标或设为 false。 |
open | boolean | 必填 | 必需。是否显示弹窗 |
title | ReactNode | — | 标题 / 内容 |
description | ReactNode | — | 补充说明。 |
icon | ReactNode | — | 标题左侧的装饰图标。 |
divided | boolean | true | 用浅分隔线划分标题、内容和底部操作区。 |
footer | ReactNode | ((actions: ModalFooterActions) => ReactNode) | 取消 + 确定 | 自定义底部;null 隐藏;函数接收默认按钮 |
confirmText | ReactNode | 语言默认值 | 按钮文字 |
cancelText | ReactNode | 语言默认值 | 按钮文字 |
confirmLoading | boolean | false | 确认加载状态 |
confirmButtonProps | ButtonProps | — | 定制默认按钮属性 |
cancelButtonProps | ButtonProps | — | 定制默认按钮属性 |
onConfirm | () => void | Promise<void> | — | 由应用控制 open;Promise 拒绝时显示错误 |
onCancel | () => void | — | 取消或关闭请求,未指定时使用 onClose |
width | number | string | 480 | 弹窗宽度,数字为像素 |
closable | boolean | true | 关闭按钮 / 点击遮罩关闭 / Escape 关闭 |
maskClosable | boolean | true | 关闭按钮 / 点击遮罩关闭 / Escape 关闭 |
keyboard | boolean | true | 关闭按钮 / 点击遮罩关闭 / Escape 关闭 |
onClose | () => void | — | 请求关闭,由应用更新 open |
afterClose | () => void | — | 关闭动画完成后触发 |
forceRender | boolean | false | 打开前也挂载内容,关闭时保持隐藏。 |
destroyOnClose | boolean | false | 关闭过渡完成后卸载内容;默认保留。 |
preserve | boolean | — | 显式控制关闭后是否保留内容,优先于 destroyOnClose。 |
container | Element | DocumentFragment | (() => Element | DocumentFragment) | — | 自定义挂载或滚动容器。 |
#ModalFooterActions
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
confirmButton | ReactNode | — | 必需。默认确认按钮,保留加载状态 |
cancelButton | ReactNode | — | 必需。默认取消按钮 |
#类型
定义组件属性或组装变量时可导入相关类型。
import type { ModalProps, ModalFooterActions } from '@ifhover/leaf-ui';#使用说明
弹窗打开后聚焦内部控件,Tab 在弹窗中移动;Escape 关闭后恢复触发前的焦点。maskClosable、keyboard 可分别关闭遮罩、键盘退出。没有 title 时请提供 aria-label。
footer 可放确认、取消等操作;也可以在内容中使用 Form。弹窗中的选择器浮层沿用区域主题。
默认底部按取消、确定顺序靠右排列。传入 onConfirm 时,完成操作后自行更新 open;未传入时确定调用 onClose。Promise 执行期间显示加载状态并避免重复操作。提交表单可在 onConfirm 中调用 form.requestSubmit(),或通过 confirmButtonProps 设置 type="submit" 与 form ID,保留表单校验。footer 函数可扩展默认按钮,footer={null} 隐藏底部。
#DialogFocusOptions
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
initialFocus | RefObject<HTMLElement | null> | (() => HTMLElement | null) | — | 指定打开后的初始焦点。 |
returnFocus | boolean | RefObject<HTMLElement | null> | (() => HTMLElement | null) | — | 关闭后返回原焦点;也可指定目标或设为 false。 |
#外观与丰富内容
description 为标题提供补充说明,icon 放在标题左侧。默认 divided 使用浅分隔线和淡灰操作区;divided={false} 使用留白分区。footer 函数可在左侧加入状态文字,使用 Space 把按钮放在同一组,窄屏时整组换行并靠右排列。