Dialog (Modal)
- 组件说明:对话框,用于确认操作、展示信息或收集用户输入。别名
Modal。 - 当前状态:支持
children作为 trigger 的声明式用法、配置式内容插槽,以及命令式Dialog.open()API。 - 实现约定:
ui/dialog封装 Radix Dialog primitive,design 层提供配置式 API 与命令式弹层能力。 - 默认交互:默认将
children作为 trigger 元素。
基础用法
配置模式:直接传入 title、content,并通过 cancelText / okText 配置默认的取消 / 确认按钮,点击后 Dialog 自动关闭。
结果
Loading...
实时编辑器
render( <div style={{ padding: '40px 0' }}> <Dialog title="确认操作" content="确定要继续吗?此操作无法撤销。" cancelText="取消" okText="确认" onOk={() => console.log('confirmed')} > <Button>打开 Dialog</Button> </Dialog> </div>, )
尺寸
通过 size 切换 Dialog 宽度。default 为 464px;emphasized 为 708px,适用于内容较多或需更强呈现的场景。两者除宽度外视觉完全一致。
结果
Loading...
实时编辑器
render( <div style={{ display: 'flex', gap: 16, padding: '40px 0' }}> <Dialog title="Default(464px)" content="标准 Dialog 宽度,适用于大多数确认场景。" okText="确定" > <Button variant="secondary">Default</Button> </Dialog> <Dialog size="emphasized" title="Emphasized(708px)" content="更宽的 Dialog,适用于内容较多或需更强呈现的场景。" okText="确定" > <Button>Emphasized</Button> </Dialog> </div>, )
隐藏关闭按钮
设置 showClose={false} 可以移除关闭按钮。
结果
Loading...
实时编辑器
render( <div style={{ padding: '40px 0' }}> <Dialog title="重要提示" showClose={false} content="请仔细阅读后再继续操作。" okText="我已了解" > <Button variant="secondary">无关闭按钮</Button> </Dialog> </div>, )
Destructive 确认按钮
通过 okButtonProps 自定义确认按钮。对于删除等破坏性操作,可传入 variant="destructive"。
结果
Loading...
实时编辑器
render( <div style={{ padding: '40px 0' }}> <Dialog title="删除记录" content="此操作无法撤销,该记录将被永久删除。" cancelText="取消" okText="删除" okButtonProps={{ variant: 'destructive' }} > <Button variant="destructive-outline">删除</Button> </Dialog> </div>, )
纯内容模式
仅传入 content(不传 title、不传按钮)时,内容直接填充 Dialog 区域,不添加任何内边距包装,由业务方完全控制布局。
结果
Loading...
实时编辑器
const Demo = () => { const [open, setOpen] = useState(false) return ( <div style={{ padding: '40px 0' }}> <Button variant="secondary" onClick={() => setOpen(true)}>打开自定义 Dialog</Button> <Dialog open={open} onOpenChange={setOpen} content={ <div style={{ padding: '32px 24px', textAlign: 'center' }}> <div style={{ fontSize: 40, marginBottom: 12 }}>🎉</div> <div style={{ fontSize: 18, fontWeight: 600, marginBottom: 8 }}>操作完成!</div> <p style={{ color: '#888', marginBottom: 24 }}>您的更改已成功保存。</p> <Button onClick={() => setOpen(false)}>关闭</Button> </div> } /> </div> ) } render(<Demo />)
自定义 footer
需要更复杂布局时,可以传入 footer,此时 cancelText / okText 被忽略,关闭逻辑由调用方自行实现。
结果
Loading...
实时编辑器
const Demo = () => { const [open, setOpen] = useState(false) return ( <div style={{ padding: '40px 0' }}> <Button onClick={() => setOpen(true)}>打开 Dialog</Button> <Dialog open={open} onOpenChange={setOpen} title="自定义底部" content="完全自定义 footer 时,关闭按钮需要业务方自己实现。" footer={ <div style={{ display: 'flex', gap: 8, justifyContent: 'space-between', flex: 1 }}> <Button variant="link-color">查看详情</Button> <Button onClick={() => setOpen(false)}>知道了</Button> </div> } /> </div> ) } render(<Demo />)
长内容 + 固定底部
content 内容较长时,仅内容区滚动,标题和底部按钮始终固定。在下方内容区内滚动即可验证。
结果
Loading...
实时编辑器
const Demo = () => { const [open, setOpen] = useState(false) return ( <div style={{ padding: '40px 0' }}> <Button onClick={() => setOpen(true)}>打开长内容 Dialog</Button> <Dialog open={open} onOpenChange={setOpen} title="长内容滚动" content={ <div> {Array.from({ length: 25 }, (_, i) => ( <p key={i} style={{ marginBottom: 10 }}> 第 {i + 1} 行 —— Lorem ipsum dolor sit amet,consectetur adipiscing elit。Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua。 </p> ))} </div> } cancelText="取消" okText="确认" /> </div> ) } render(<Demo />)
弹层嵌套 Dialog(滚轮滚动)
Popover / Select 等弹层内容通过 Portal 渲染到 document.body,位于 Dialog 滚动锁的允许子树之外。修复后,在 Dialog 内使用滚轮滚动下拉列表可正常工作,不再被 react-remove-scroll 拦截。
结果
Loading...
实时编辑器
const Demo = () => { const [open, setOpen] = useState(false) return ( <div style={{ padding: '40px 0' }}> <Button onClick={() => setOpen(true)}>打开含 Select 的 Dialog</Button> <Dialog open={open} onOpenChange={setOpen} title="弹层嵌套 Dialog" content={ <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}> <p style={{ margin: 0 }}>下方 Select 的下拉列表 portal 到 document.body。请用触控板 / 鼠标滚轮滚动选项列表,应可正常滚动。</p> <Select options={Array.from({ length: 40 }, (_, i) => ({ value: String(i), label: `选项 ${i + 1}`, }))} placeholder="请选择" /> </div> } cancelText="取消" okText="确定" /> </div> ) } render(<Demo />)
受控模式
结果
Loading...
实时编辑器
const Demo = () => { const [open, setOpen] = useState(false) return ( <div style={{ display: 'flex', gap: 12, alignItems: 'center', padding: '40px 0' }}> <Button onClick={() => setOpen(true)}>打开受控 Dialog</Button> <span style={{ fontSize: 14, color: '#999' }}>open: {String(open)}</span> <Dialog open={open} onOpenChange={setOpen} title="受控 Dialog" content="此 Dialog 由外部状态控制。" okText="关闭" /> </div> ) } render(<Demo />)
命令式 API
Dialog.open() 提供命令式调用,适合异步回调、快捷键、表格 action 等非 JSX trigger 场景。需要在应用根部挂载 DesignProvider 或 OverlayHost。
import { Dialog } from '@plaud/design'
Dialog.open({
title: '删除项目',
content: <div>此操作无法撤销,请确认。</div>,
})
在内容内部关闭
content、footer 支持 render function,参数中包含 close、update 等控制器方法。
Dialog.open({
title: '确认',
content: ({ close }) => (
<div>
<p>确定要继续吗?</p>
<Button onClick={close}>关闭</Button>
</div>
),
})
更新已打开的弹窗
open() 返回控制器,可在打开后更新配置:
const controller = Dialog.open({
title: '上传中...',
content: <div>请稍候。</div>,
})
setTimeout(() => {
controller.update({
title: '上传完成',
content: <div>文件已就绪。</div>,
})
}, 1000)
控制器
interface ImperativeOverlayController<TOptions> {
id: string
close: () => void
update: (updater: Partial<TOptions> | ((prev: TOptions) => TOptions)) => void
afterClosed: Promise<void>
}
Props
Dialog(配置模式)
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
title | ReactNode | — | 标题 |
content | ReactNode | — | 主体内容 |
size | 'default' | 'emphasized' | 'default' | Dialog 宽度。default 为 464px,emphasized 为 708px |
cancelText | ReactNode | — | 取消按钮文字。点击默认关闭 Dialog。仅在未传 footer 时生效 |
okText | ReactNode | — | 确认按钮文字。点击默认关闭 Dialog。仅在未传 footer 时生效 |
onCancel | () => void | — | 取消按钮点击回调(点击后自动关闭) |
onOk | () => void | — | 确认按钮点击回调(点击后自动关闭) |
cancelButtonProps | Omit<ButtonProps, 'children'> | — | 取消按钮额外 props,如 variant。仅在未传 footer 时生效 |
okButtonProps | Omit<ButtonProps, 'children'> | — | 确认按钮额外 props,如 variant="destructive"。仅在未传 footer 时生效 |
footer | ReactNode | — | 自定义底部。传入后 cancelText / okText 失效,关闭由业务方实现 |
children | ReactNode | — | 触发器(非受控模式) |
open | boolean | — | 受控打开状态 |
onOpenChange | (open: boolean) => void | — | 打开状态变更回调 |
showClose | boolean | true | 是否显示关闭按钮 |
destroyOnClose | boolean | true | 关闭后是否销毁内容 |
contentClassName | string | — | 内容区域自定义 class |
contentProps | DialogContentProps | — | 透传给内容层的扩展属性,例如 className、数据属性和交互回调 |
使用约束
- 默认优先使用配置式写法,
children作为 trigger,内容通过 props 配置。 - 命令式 API 需要在应用根部挂载
DesignProvider或OverlayHost。