跳到主要内容

Dialog (Modal)

  • 组件说明:对话框,用于确认操作、展示信息或收集用户输入。别名 Modal
  • 当前状态:支持 children 作为 trigger 的声明式用法、配置式内容插槽,以及命令式 Dialog.open() API。
  • 实现约定ui/dialog 封装 Radix Dialog primitive,design 层提供配置式 API 与命令式弹层能力。
  • 默认交互:默认将 children 作为 trigger 元素。

基础用法

配置模式:直接传入 titlecontent,并通过 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,此时 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 场景。需要在应用根部挂载 DesignProviderOverlayHost

import { Dialog } from '@plaud/design'

Dialog.open({
title: '删除项目',
content: <div>此操作无法撤销,请确认。</div>,
})

在内容内部关闭

contentfooter 支持 render function,参数中包含 closeupdate 等控制器方法。

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(配置模式)

属性类型默认值说明
titleReactNode标题
contentReactNode主体内容
size'default' | 'emphasized''default'Dialog 宽度。default 为 464px,emphasized 为 708px
cancelTextReactNode取消按钮文字。点击默认关闭 Dialog。仅在未传 footer 时生效
okTextReactNode确认按钮文字。点击默认关闭 Dialog。仅在未传 footer 时生效
onCancel() => void取消按钮点击回调(点击后自动关闭)
onOk() => void确认按钮点击回调(点击后自动关闭)
cancelButtonPropsOmit<ButtonProps, 'children'>取消按钮额外 props,如 variant。仅在未传 footer 时生效
okButtonPropsOmit<ButtonProps, 'children'>确认按钮额外 props,如 variant="destructive"。仅在未传 footer 时生效
footerReactNode自定义底部。传入后 cancelText / okText 失效,关闭由业务方实现
childrenReactNode触发器(非受控模式)
openboolean受控打开状态
onOpenChange(open: boolean) => void打开状态变更回调
showClosebooleantrue是否显示关闭按钮
destroyOnClosebooleantrue关闭后是否销毁内容
contentClassNamestring内容区域自定义 class
contentPropsDialogContentProps透传给内容层的扩展属性,例如 className、数据属性和交互回调

使用约束

  • 默认优先使用配置式写法,children 作为 trigger,内容通过 props 配置。
  • 命令式 API 需要在应用根部挂载 DesignProviderOverlayHost