UNPKG

@douyinfe/semi-ui

Version:

A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.

720 lines (628 loc) 28.5 kB
--- localeCode: zh-CN order: 76 category: 展示类 title: Modal 模态对话框 icon: doc-modal brief: 模态对话框用于等待用户响应、告知用户重要信息或在不丢失上下文的情况下展示更多信息 --- ## 代码演示 ### 如何引入 ```jsx import import { Modal } from '@douyinfe/semi-ui'; ``` ### 基本 ```jsx live=true import React from 'react'; import { Modal, Button } from '@douyinfe/semi-ui'; () => { const [visible, setVisible] = useState(false); const showDialog = () => { setVisible(true); }; const handleOk = () => { setVisible(false); console.log('Ok button clicked'); }; const handleCancel = () => { setVisible(false); console.log('Cancel button clicked'); }; const handleAfterClose = () => { console.log('After Close callback executed'); }; return ( <> <Button onClick={showDialog}>打开弹窗</Button> <Modal title="基本对话框" visible={visible} onOk={handleOk} afterClose={handleAfterClose} onCancel={handleCancel} closeOnEsc={true} > This is the content of a basic modal. <br /> More content... </Modal> </> ); }; ``` ### 底部撑满 设置 footerFill 为 true 可使 Modal footer 底部按钮撑满排列 ```jsx live=true import React from 'react'; import { Modal, Button } from '@douyinfe/semi-ui'; () => { const [visible, setVisible] = useState(false); const showDialog = () => { setVisible(true); }; const handleOk = () => { setVisible(false); console.log('Ok button clicked'); }; const handleCancel = () => { setVisible(false); console.log('Cancel button clicked'); }; const handleAfterClose = () => { console.log('After Close callback executed'); }; return ( <> <Button onClick={showDialog}>打开弹窗</Button> <Modal title="基本对话框" visible={visible} onOk={handleOk} afterClose={handleAfterClose} onCancel={handleCancel} closeOnEsc={true} footerFill={true} > This is the content of a basic modal. <br /> More content... </Modal> </> ); }; ``` ### 点击遮罩层不可关闭 修改 `maskClosable` 为 `false` 则不可通过点击遮罩层来关闭对话框。 ```jsx live=true import React, { useState } from 'react'; import { Modal, Button } from '@douyinfe/semi-ui'; function ModalDemo() { const [visible, setVisible] = useState(false); const showDialog = () => { setVisible(true); }; const handleOk = () => { setVisible(false); }; const handleCancel = () => { setVisible(false); }; return ( <> <Button onClick={showDialog}>点击遮罩层不可关闭</Button> <Modal title="对话框标题" visible={visible} onOk={handleOk} onCancel={handleCancel} maskClosable={false} > <p>This is a modal that cannot be closed by clicking on the mask.</p> <p>More content...</p> </Modal> </> ); } ``` ### 自定义按钮文字 通过设置 `okText` 与 `cancelText` 属性可自定义按钮显示的文字。 注意:命令式调用的 Modal 需要通过这两个属性来设置 i18 的文本,因为我们无法修改 React 组件树,命令式调用插入的 Component 无法消费到 Locale 相关的 Context ```jsx live=true import React, { useState } from 'react'; import { Modal, Button } from '@douyinfe/semi-ui'; function ModalDemo() { const [visible, setVisible] = useState(false); const showDialog = () => { setVisible(true); }; const handleOk = () => { setVisible(false); }; const handleCancel = () => { setVisible(false); }; return ( <> <Button onClick={showDialog}>自定义按钮文字</Button> <Modal title="自定义按钮文字" visible={visible} onOk={handleOk} onCancel={handleCancel} okText={'Sounds great!'} cancelText={'No, thanks.'} > <p>This is a modal with customized button texts.</p> <p>More content...</p> </Modal> </> ); } ``` ### 自定义按钮属性 通过设置 `okButtonProps` 与 `cancelButtonProps` 属性可自定义按钮的属性。 ```jsx live=true import React, { useState } from 'react'; import { Modal, Button } from '@douyinfe/semi-ui'; function ModalDemo() { const [visible, setVisible] = useState(false); const showDialog = () => { setVisible(true); }; const handleOk = () => { setVisible(false); }; const handleCancel = () => { setVisible(false); }; return ( <> <Button onClick={showDialog}>自定义按钮属性</Button> <Modal title="自定义按钮属性" visible={visible} onOk={handleOk} onCancel={handleCancel} okButtonProps={{ size: 'small', type: 'warning' }} cancelButtonProps={{ size: 'small', disabled: true }} > <p>This is a modal with customized button props.</p> <p>More content...</p> </Modal> </> ); } ``` ### 自定义对话框头部和页脚 如果需要实现更丰富的个性化需求,可以通过 `header` 自定义头部,`footer` 自定义页脚的按钮。把 `header` 设为 `null`时则不展示头部区域;不需要显示任何按钮时,同样可以把 `footer` 设为 `null`。 ```jsx live=true import React, { useState } from 'react'; import { Modal, Button } from '@douyinfe/semi-ui'; function ModalDemo() { const [visible, setVisible] = useState(false); const showDialog = () => { setVisible(true); }; const handleOk = () => { setVisible(false); }; const handleCancel = () => { setVisible(false); }; return ( <> <Button onClick={showDialog}>自定义页脚样式</Button> <Modal title="自定义页脚" visible={visible} onOk={handleOk} onCancel={handleCancel} footer={ <Button type="primary" onClick={handleOk}> Yes, I Understand </Button> } > <p>This is a modal with a customized footer.</p> <p>More content...</p> </Modal> </> ); } ``` ### 自定义对话框的样式 通过设置 `style` 可以自定义样式及位置如 `style.top`,也可以通过 `centered` 使对话框居中显示。也可以通过设置 `maskStyle` 自定义遮罩样式,及 `bodyStyle` 自定义对话框内容样式。 ```jsx live=true import React, { useState } from 'react'; import { Modal, Button } from '@douyinfe/semi-ui'; function ModalDemo() { const [visible, setVisible] = useState(false); const showDialog = () => { setVisible(true); }; const handleOk = () => { setVisible(false); }; const handleCancel = () => { setVisible(false); }; return ( <> <Button onClick={showDialog}>自定义对话框样式</Button> <Modal title="自定义样式" visible={visible} onOk={handleOk} onCancel={handleCancel} centered bodyStyle={{ overflow: 'auto', height: 200 }} > <p style={{ lineHeight: 1.8 }}> Semi Design 是由抖音前端团队与 UED 团队共同设计开发并维护的设计系统。设计系统包含设计语言以及一整套可复用的前端组件,帮助设计师与开发者更容易地打造高质量的、用户体验一致的、符合设计规范的 Web 应用。 </p> <p style={{ lineHeight: 1.8 }}> 区别于其他的设计系统而言,Semi Design 以用户中心、内容优先、设计人性化为设计理念,具有以下优势: </p> <ul> <li> <p>Semi Design 以内容优先进行设计。</p> </li> <li> <p>更容易地自定义主题。</p> </li> <li> <p>适用国际化场景。</p> </li> <li> <p>效率场景加入人性化关怀</p> </li> </ul> </Modal> </> ); } ``` ### 自定义的对话框 通过灵活使用使用 `header`,`footer`等属性可以实现一个完全自定义的对话框。 ```jsx live=true import React, { useState } from 'react'; import { Modal, Button, List } from '@douyinfe/semi-ui'; import { IconVigoLogo, IconSemiLogo } from '@douyinfe/semi-icons'; function ModalDemo() { const [visible, setVisible] = useState(false); const showDialog = () => { setVisible(true); }; const handleOk = () => { setVisible(false); }; const handleCancel = () => { setVisible(false); }; const data = [ { icon: <IconSemiLogo style={{ fontSize: 48 }} />, title: 'Boost new feature adoption with Integration', content: 'Sample data is prepared for you to demostrate how Integration may be useful for your team', }, { icon: <IconVigoLogo style={{ fontSize: 48 }} />, title: 'Introducing Dark Mode', content: 'Sample data is prepared for you to demostrate how Integration may be useful for your team', }, { icon: <IconSemiLogo style={{ fontSize: 48 }} />, title: 'New List Component', content: 'Sample data is prepared for you to demostrate how Integration may be useful for your team', }, ]; const btnStyle = { width: 240, margin: '4px 50px', }; const footer = ( <div style={{ textAlign: 'center' }}> <Button type="primary" theme="solid" onClick={handleOk} style={btnStyle}> Continue </Button> <Button type="primary" theme="borderless" onClick={handleCancel} style={btnStyle}> Learn more features </Button> </div> ); return ( <> <Button onClick={showDialog}>自定义对话框</Button> <Modal header={null} visible={visible} onOk={handleOk} onCancel={handleCancel} footer={footer} > <h3 style={{ textAlign: 'center', fontSize: 24, margin: 40 }}>Semi Design New Features</h3> <List dataSource={data} split={false} renderItem={item => ( <List.Item header={item.icon} main={ <div> <h6 style={{ margin: 0, fontSize: 16 }}>{item.title}</h6> <p style={{ marginTop: 4, color: 'var(--semi-color-text-1)' }}> {item.content} </p> </div> } /> )} /> </Modal> </> ); } ``` ### 全屏 Modal 使用 `fullScreen={true}` 可以开启全屏对话框 ```jsx live=true import React from 'react'; import { Modal, Button } from '@douyinfe/semi-ui'; () => { const [visible, setVisible] = useState(false); const onClose = () => { setVisible(false); }; return ( <> <Button onClick={() => setVisible(true)}>打开全屏弹窗</Button> <Modal title="全屏对话框标题" fullScreen visible={visible} onOk={onClose} onCancel={onClose}> <p>This is a full screen modal</p> <p>More content...</p> </Modal> </> ); }; ``` ### 命令式调用 使用 `confirm()` 可以设置一个确认框。支持各种类型的信息提示。命令式调用也可以自定义 icon , 支持 string 和 ReactNode 类型。其他 Modal 支持的 props 都可以传入。 ```jsx live=true hideInDSM import React from 'react'; import { Modal, Button } from '@douyinfe/semi-ui'; import { IconSend } from '@douyinfe/semi-icons'; ModalComponent = function(props) { function success() { Modal.success({ title: 'This is a success message', content: 'bla bla bla...' }); } function info() { Modal.info({ title: 'Here is some info', content: 'bla bla bla...' }); } function error() { Modal.error({ title: 'Unfortunately, there is an error', content: 'bla bla bla...' }); } function warning() { Modal.warning({ title: 'Warning: be cautious ahead', content: 'bla bla bla...' }); } function confirm() { Modal.confirm({ title: 'Are you sure ?', content: 'bla bla bla...' }); } function custom() { Modal.info({ title: 'This is a custom modal', content: 'bla bla bla...', icon: <IconSend />, cancelButtonProps: { theme: 'borderless' }, okButtonProps: { theme: 'solid' }, }); } return ( <div> <Button onClick={info}>Info</Button> <br /> <br /> <Button onClick={success}>Success</Button> <br /> <br /> <Button onClick={error} type="danger"> Error </Button> <br /> <br /> <Button onClick={warning} type="warning"> Warning </Button> <br /> <br /> <Button onClick={confirm} type="primary"> Confirm </Button> <br /> <br /> <Button onClick={custom}>Custom</Button> </div> ); }; ``` ### Hooks 用法 通过 Modal.useModal 创建支持读取 context 的 contextHolder。 ```jsx live=true hideInDSM import React from 'react'; import { ConfigProvider, Button, Modal } from '@douyinfe/semi-ui'; import en_GB from '@douyinfe/semi-ui/lib/es/locale/source/en_GB'; function Demo(props = {}) { const [modal, contextHolder] = Modal.useModal(); const config = { title: 'This is a success message', content: 'Context consumer' }; return ( <ConfigProvider locale={en_GB}> <div> <Button onClick={() => { modal.confirm(config); }} > Confirm Modal </Button> </div> {contextHolder} </ConfigProvider> ); } ``` ### 可拖拽 Modal 通过 `modalRender` 自定义渲染 Modal 内容,可拖拽 Modal 通过 DragMove 组件实现。 ```jsx live=true hideInDSM import React, { useState } from 'react'; import { ConfigProvider, Button, Modal, DragMove } from '@douyinfe/semi-ui'; function Demo(props = {}) { const [visible, setVisible] = useState(false); return ( <div> <Button onClick={() => setVisible(true)}>Open Modal</Button> <Modal title="可拖拽Modal" visible={visible} onCancel={() => setVisible(false)} modalRender={(modal) => ( <DragMove>{modal}</DragMove> )} > <p>This is the content of a basic sidesheet.</p> <p>Here is more content...</p> </Modal> </div> ); } ``` ## API 参考 ### Modal | 属性 | 说明 | 类型 | 默认值 | | --- |-----------------------------------------------------------------------------------------------------------| --- | --- | | afterClose | 对话框完全关闭后的回调函数 <br/> | () => void | 无 | | bodyStyle | 对话框内容的样式 | CSSProperties | 无 | | cancelButtonProps | 取消按钮的 props | [ButtonProps](/zh-CN/input/button#API参考) | 无 | | cancelText | 取消按钮的文字 | string | 无 | | centered | 是否居中显示 | boolean | false | | className | 可用于设置样式类名 | string | 无 | | closable | 是否显示右上角的关闭按钮 | boolean | true | | closeIcon | 关闭按钮的 icon | ReactNode | <IconClose /\> | | closeOnEsc | 允许通过键盘事件 Esc 触发关闭 | boolean | true | | confirmLoading | 确认按钮 loading | boolean | false | | content | 对话框内容 | ReactNode | 无 | | footer | 对话框底部 | ReactNode | 无 | | fullScreen | 对话是否是全屏(会覆盖 width height) | boolean | false | | getPopupContainer | 指定父级 DOM,弹层将会渲染至该 DOM 中,自定义需要设置 `position: relative` 这会改变浮层 DOM 树位置,但不会改变视图渲染位置。 | () => HTMLElement | () => document.body | | hasCancel | 是否显示取消按钮 | boolean | true | | header | 对话框头部 | ReactNode | 无 | | height | 高度 | number | 无 | | icon | 自定义 icon | ReactNode | - | | keepDOM | 关闭对话框时是否保留内部组件不销毁 | boolean | false | | lazyRender | 配合 keepDOM 使用,为 true 时挂载时不会渲染对话框组件 | boolean | true | | mask | 是否显示遮罩 | boolean | true | | maskClosable | 是否允许通过点击遮罩来关闭对话框 | boolean | true | | maskStyle | 遮罩的样式 | CSSProperties | 无 | | modalContentClass | 可用于设置对话框内容的样式类名 | string | 无 | | modalRender | 自定义渲染 Modal | (modal: ReactNode) => ReactNode | - | | motion | 动画效果开关 | boolean | true | | okButtonProps | 确认按钮的 props | [ButtonProps](/zh-CN/input/button#API参考) | 无 | | okText | 确认按钮的文字 | string | 无 | | okType | 确认按钮的类型, 可选: 'primary'、'secondary'、'tertiary'、'warning'、'danger' | string | primary | | preventScroll | 指示浏览器是否应滚动文档以显示新聚焦的元素,作用于组件内的 focus 方法,不包含用户传入的组件 | boolean | | | | size | 对话框宽度尺寸,支持 `small`(448px), `medium`(684px), `large`(920px),`full-width`(100vw - 64px) | string | 'small' | | style | 可用于设置样式 | CSSProperties | 无 | | title | 对话框的标题 | ReactNode | 无 | | visible | 对话框是否可见 | boolean | false | | width | 宽度 | number | 448 | | zIndex | 遮罩的 z-index 值 | number | 1000 | | onCancel | 取消对话框时的回调函数,返回 Promise 时,取消按钮会出现 loading 态 | (e: any) => void \| Promise<any\> | 无 | | onOk | 点击确认按钮时的回调函数,返回 Promise 时,确认按钮会出现 loading 态 | (e: any) => void \| Promise<any\> | 无 | ### Modal.method() - `Modal.info` - `Modal.success` - `Modal.error` - `Modal.warning` - `Modal.confirm` | 属性 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | | bodyStyle | 对话框内容的样式 | CSSProperties | 无 | | cancelButtonProps | 取消按钮的 props | [ButtonProps](/zh-CN/input/button#API参考) | 无 | | cancelText | 取消按钮的文字 | string | 无 | | centered | 是否居中显示 | boolean | false | | className | 可用于设置样式类名 | string | 无 | | closable | 是否显示右上角的关闭按钮 | boolean | true | | confirmLoading | 确认按钮 loading | boolean | false | | content | 对话框内容 | ReactNode | 无 | | footer | 对话框底部 | ReactNode | 无 | | footerFill| 底部按钮是否撑满 (>= 2.xx.0 ) | boolean | false | | header | 对话框头部 | ReactNode | 无 | | height | 高度 | number | 无 | | icon | 自定义 icon | ReactNode | - | | mask | 是否显示遮罩 | boolean | true | | maskClosable | 是否允许通过点击遮罩来关闭对话框 | boolean | true | | maskStyle | 遮罩的样式 | CSSProperties | 无 | | modalContentClass | 可用于设置对话框内容的样式类名 | string | 无 | | modalRender | 自定义渲染 Modal | (modal: ReactNode) => ReactNode | - | | okButtonProps | 确认按钮的 props | [ButtonProps](/zh-CN/input/button#API参考) | 无 | | okText | 确认按钮的文字 | string | 无 | | okType | 确认按钮的类型 | string | primary | | style | 可用于设置样式 | CSSProperties | 无 | | title | 对话框的标题 | ReactNode | 无 | | width | 宽度 | number | 520 | | zIndex | 遮罩的 z-index 值 | number | 1000 | | onCancel | 取消回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | (e: any) => void \| Promise<any\> | 无 | | onOk | 点击确定回调,参数为关闭函数,返回 promise 时 resolve 后自动关闭 | (e: any) => void \| Promise<any\> | 无 | 以上函数调用后,会返回一个引用,可以通过该引用更新和关闭弹窗。 ``` const modal = Modal.info(); modal.update({ title: '更新的标题', content: '更新的内容', }); modal.destroy(); ``` - `Modal.destroyAll` 使用 Modal.destroyAll() 可以销毁命令式及以上`.info()`等创建的弹窗。 - `Modal.useModal` 当你需要使用 Context 时,可以通过 Modal.useModal 创建一个 contextHolder 插入相应的节点中。此时通过 hooks 创建的 Modal 将会得到 contextHolder 所在位置的所有上下文。创建的 modal 对象拥有与 [Modal.method](<#Modal.method()>) 相同的创建通知方法。 ## Accessibility ### ARIA WAI-ARIA: https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/ - role 设置为 `dialog` - aria-modal 设置为 true - aria-labelledby 对应 Modal header - aria-describedby 对应 Modal body ### 键盘和焦点 - Modal 在弹出时自动获得焦点,关闭时焦点自动回归到打开前元素。 - 键盘用户可以使用 `Tab` 键和 `Shift + Tab`,将焦点在 Modal 内移动,包括 Modal 自带的关闭按钮和确定取消按钮,此时 Modal 背后元素不可被 tab 聚焦。 - Modal 打开时默认聚焦到取消按钮, 可通过在 cancelButtonProps 或 okButtonProps 传入 autoFocus 来控制该行为。 - 可通过在 Modal 内容中需要聚焦的表单元素上添加 autoFocus 来让 Modal 打开时自动聚焦到该元素 (需同时设置 cancelButtonProps 的 autoFocus 为 false)。 - 修改 closeOnEsc 默认值为 true,允许用户通过键盘直接关闭 Modal 带来更好的体验 ## 文案规范 - 命令式 Modal 与 默认 Modal 两种模态对话框的标题使用 动词 + 名词 的格式,无论是陈述句还是问句 | ✅ 推荐用法 | ❌ 不推荐用法 | | ------------- | ------------------------------------- | | Edit ticket | Edit | | Delete form? | Are you sure you want to delete form? | - 两种模态对话框的操作按钮在保证标题描述清楚的前提下,只需要使用标题内的动词即可 | ✅ 推荐用法 | ❌ 不推荐用法 | | ----------- | ------------- | | Edit | Edit ticket | - 命令式 Modal 的正文规范 - 对标题进行具体的解释说明,不要重复标题的信息 - 确保用户知道在必要时如何采取行动 ## 设计变量 <DesignToken/> ## FAQ - #### 为什么使用 LocaleProvider 后, Modal.confirm 确认、取消按钮的文本没有国际化? Modal 使用 Portal 将浮层节点插入到 DOM 树中。但这个操作仅能改变节点在 DOM 树中的位置,无法改变节点在 React 节点树中的位置,LocalProvider是基于 Context 机制传递的,必须是从属的 React 子结点才可消费到 Local 相关 Context。因此命令式的 Modal 的内置文本无法自动适配国际化。 你可以通过 `okText` 和 `cancelText` 这两个属性来根据 Locale 重新设置 i18 的文本。 在1.2版本之后,你也可以通过 Modal.useModal 方法来返回 modal 实体以及 contextHolder 节点。将 contextHolder 插入到你需要获取 context 位置,即可使 Modal 获取到对应的 Context,如 ConfigProvider 或者 LocaleProvider 的配置。 - #### 为什么 title 和 content 的间距在命令式调用和非命令式调用下不同? 命令式调用场景下,标题和内容的相关性更强,所以用更近的距离表达这种强相关性,符合预期。用户如果不想要这种效果,可以自己做样式覆盖。 <!-- ## 相关物料 ```material 1, 55 ``` --> ## 相关物料 <semi-material-list code="1"></semi-material-list>