examples.md•47.8 kB
## Modal 组件示例
### 基本
基础弹框。
```tsx
import React, { useState } from 'react';
import { Button, Modal } from 'antd';
const App: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const showModal = () => {
setIsModalOpen(true);
};
const handleOk = () => {
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
closable={{ 'aria-label': 'Custom Close Button' }}
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</>
);
};
export default App;
```
### 异步关闭
点击确定后异步关闭对话框,例如提交表单。
```tsx
import React, { useState } from 'react';
import { Button, Modal } from 'antd';
const App: React.FC = () => {
const [open, setOpen] = useState(false);
const [confirmLoading, setConfirmLoading] = useState(false);
const [modalText, setModalText] = useState('Content of the modal');
const showModal = () => {
setOpen(true);
};
const handleOk = () => {
setModalText('The modal will be closed after two seconds');
setConfirmLoading(true);
setTimeout(() => {
setOpen(false);
setConfirmLoading(false);
}, 2000);
};
const handleCancel = () => {
console.log('Clicked cancel button');
setOpen(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal with async logic
</Button>
<Modal
title="Title"
open={open}
onOk={handleOk}
confirmLoading={confirmLoading}
onCancel={handleCancel}
>
<p>{modalText}</p>
</Modal>
</>
);
};
export default App;
```
### 自定义页脚
更复杂的例子,自定义了页脚的按钮,点击提交后进入 loading 状态,完成后关闭。
不需要默认确定取消按钮时,你可以把 `footer` 设为 `null`。
```tsx
import React, { useState } from 'react';
import { Button, Modal } from 'antd';
const App: React.FC = () => {
const [loading, setLoading] = useState(false);
const [open, setOpen] = useState(false);
const showModal = () => {
setOpen(true);
};
const handleOk = () => {
setLoading(true);
setTimeout(() => {
setLoading(false);
setOpen(false);
}, 3000);
};
const handleCancel = () => {
setOpen(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal with customized footer
</Button>
<Modal
open={open}
title="Title"
onOk={handleOk}
onCancel={handleCancel}
footer={[
<Button key="back" onClick={handleCancel}>
Return
</Button>,
<Button key="submit" type="primary" loading={loading} onClick={handleOk}>
Submit
</Button>,
<Button
key="link"
href="https://google.com"
target="_blank"
type="primary"
loading={loading}
onClick={handleOk}
>
Search on Google
</Button>,
]}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</>
);
};
export default App;
```
### 加载中
设置对话框加载状态。
```tsx
import React from 'react';
import { Button, Modal } from 'antd';
const App: React.FC = () => {
const [open, setOpen] = React.useState<boolean>(false);
const [loading, setLoading] = React.useState<boolean>(true);
const showLoading = () => {
setOpen(true);
setLoading(true);
// Simple loading mock. You should add cleanup logic in real world.
setTimeout(() => {
setLoading(false);
}, 2000);
};
return (
<>
<Button type="primary" onClick={showLoading}>
Open Modal
</Button>
<Modal
title={<p>Loading Modal</p>}
footer={
<Button type="primary" onClick={showLoading}>
Reload
</Button>
}
loading={loading}
open={open}
onCancel={() => setOpen(false)}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</>
);
};
export default App;
```
### 自定义页脚渲染函数
自定义页脚渲染函数,支持在原有基础上进行扩展。
```tsx
import React, { useState } from 'react';
import { Button, Modal, Space } from 'antd';
const App: React.FC = () => {
const [open, setOpen] = useState(false);
const showModal = () => {
setOpen(true);
};
const handleOk = () => {
setOpen(false);
};
const handleCancel = () => {
setOpen(false);
};
return (
<>
<Space>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Button
type="primary"
onClick={() => {
Modal.confirm({
title: 'Confirm',
content: 'Bla bla ...',
footer: (_, { OkBtn, CancelBtn }) => (
<>
<Button>Custom Button</Button>
<CancelBtn />
<OkBtn />
</>
),
});
}}
>
Open Modal Confirm
</Button>
</Space>
<Modal
open={open}
title="Title"
onOk={handleOk}
onCancel={handleCancel}
footer={(_, { OkBtn, CancelBtn }) => (
<>
<Button>Custom Button</Button>
<CancelBtn />
<OkBtn />
</>
)}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</>
);
};
export default App;
```
### 使用 hooks 获得上下文
通过 `Modal.useModal` 创建支持读取 context 的 `contextHolder`。其中仅有 hooks 方法支持 Promise `await` 操作。
```tsx
import React, { createContext } from 'react';
import { Button, Modal, Space } from 'antd';
const ReachableContext = createContext<string | null>(null);
const UnreachableContext = createContext<string | null>(null);
const config = {
title: 'Use Hook!',
content: (
<>
<ReachableContext.Consumer>{(name) => `Reachable: ${name}!`}</ReachableContext.Consumer>
<br />
<UnreachableContext.Consumer>{(name) => `Unreachable: ${name}!`}</UnreachableContext.Consumer>
</>
),
};
const App: React.FC = () => {
const [modal, contextHolder] = Modal.useModal();
return (
<ReachableContext.Provider value="Light">
<Space>
<Button
onClick={async () => {
const confirmed = await modal.confirm(config);
console.log('Confirmed: ', confirmed);
}}
>
Confirm
</Button>
<Button
onClick={() => {
modal.warning(config);
}}
>
Warning
</Button>
<Button
onClick={async () => {
modal.info(config);
}}
>
Info
</Button>
<Button
onClick={async () => {
modal.error(config);
}}
>
Error
</Button>
</Space>
{/* `contextHolder` should always be placed under the context you want to access */}
{contextHolder}
{/* Can not access this context since `contextHolder` is not in it */}
<UnreachableContext.Provider value="Bamboo" />
</ReachableContext.Provider>
);
};
export default App;
```
### 国际化
设置 `okText` 与 `cancelText` 以自定义按钮文字。
```tsx
import React, { useState } from 'react';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Button, Modal, Space } from 'antd';
const LocalizedModal = () => {
const [open, setOpen] = useState(false);
const showModal = () => {
setOpen(true);
};
const hideModal = () => {
setOpen(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Modal
</Button>
<Modal
title="Modal"
open={open}
onOk={hideModal}
onCancel={hideModal}
okText="确认"
cancelText="取消"
>
<p>Bla bla ...</p>
<p>Bla bla ...</p>
<p>Bla bla ...</p>
</Modal>
</>
);
};
const App: React.FC = () => {
const [modal, contextHolder] = Modal.useModal();
const confirm = () => {
modal.confirm({
title: 'Confirm',
icon: <ExclamationCircleOutlined />,
content: 'Bla bla ...',
okText: '确认',
cancelText: '取消',
});
};
return (
<>
<Space>
<LocalizedModal />
<Button onClick={confirm}>Confirm</Button>
</Space>
{contextHolder}
</>
);
};
export default App;
```
### 手动更新和移除
通过返回的 instance 手动更新和关闭对话框。
```tsx
import React from 'react';
import { Button, Modal } from 'antd';
const App: React.FC = () => {
const [modal, contextHolder] = Modal.useModal();
const countDown = () => {
let secondsToGo = 5;
const instance = modal.success({
title: 'This is a notification message',
content: `This modal will be destroyed after ${secondsToGo} second.`,
});
const timer = setInterval(() => {
secondsToGo -= 1;
instance.update({
content: `This modal will be destroyed after ${secondsToGo} second.`,
});
}, 1000);
setTimeout(() => {
clearInterval(timer);
instance.destroy();
}, secondsToGo * 1000);
};
return (
<>
<Button onClick={countDown}>Open modal to close in 5s</Button>
{contextHolder}
</>
);
};
export default App;
```
### 自定义位置
使用 `centered` 或类似 `style.top` 的样式来设置对话框位置。
```tsx
import React, { useState } from 'react';
import { Button, Modal } from 'antd';
const App: React.FC = () => {
const [modal1Open, setModal1Open] = useState(false);
const [modal2Open, setModal2Open] = useState(false);
return (
<>
<Button type="primary" onClick={() => setModal1Open(true)}>
Display a modal dialog at 20px to Top
</Button>
<Modal
title="20px to Top"
style={{ top: 20 }}
open={modal1Open}
onOk={() => setModal1Open(false)}
onCancel={() => setModal1Open(false)}
>
<p>some contents...</p>
<p>some contents...</p>
<p>some contents...</p>
</Modal>
<br />
<br />
<Button type="primary" onClick={() => setModal2Open(true)}>
Vertically centered modal dialog
</Button>
<Modal
title="Vertically centered modal dialog"
centered
open={modal2Open}
onOk={() => setModal2Open(false)}
onCancel={() => setModal2Open(false)}
>
<p>some contents...</p>
<p>some contents...</p>
<p>some contents...</p>
</Modal>
</>
);
};
export default App;
```
### 暗背景
第一个对话框。
```tsx
import React, { useState } from 'react';
import { ClockCircleOutlined, DownOutlined } from '@ant-design/icons';
import {
Anchor,
Badge,
Button,
Calendar,
Card,
Collapse,
DatePicker,
Dropdown,
Modal,
Slider,
Switch,
Table,
Tabs,
Timeline,
Transfer,
Tree,
Typography,
} from 'antd';
import type { TableProps, TransferProps } from 'antd';
import type { TransferKey } from 'antd/es/transfer/interface';
import dayjs from 'dayjs';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import difference from 'lodash/difference';
dayjs.extend(customParseFormat);
const { Panel } = Collapse;
const { TreeNode } = Tree;
const { TabPane } = Tabs;
const { Meta } = Card;
const { Link } = Anchor;
const { Text } = Typography;
const text = `
A dog is a type of domesticated animal.
Known for its loyalty and faithfulness,
it can be found as a welcome guest in many households across the world.
`;
interface DataType {
key: string;
title: string;
description: string;
disabled: boolean;
}
interface RecordType {
key: string;
name: string;
age: number;
address: string;
}
interface DataTableType {
key: string;
name: string;
borrow: number;
repayment: number;
}
interface ExpandDataType {
key: React.Key;
date: string;
name: string;
upgradeNum: string;
}
interface NestDataType {
key: React.Key;
name: string;
platform: string;
version: string;
upgradeNum: number;
creator: string;
createdAt: string;
}
interface FixedDataType {
key: string;
name: string;
age: number;
address: string;
}
const mockData = Array.from({ length: 20 }).map<DataType>((_, i) => ({
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`,
disabled: i % 3 < 1,
}));
const oriTargetKeys = mockData
.filter((item) => Number(item.key) % 3 > 1)
.map<TransferKey>((item) => item.key);
const dataSource: RecordType[] = [
{ key: '1', name: 'John Brown', age: 32, address: 'New York No. 1 Lake Park' },
{ key: '2', name: 'Jim Green', age: 42, address: 'London No. 1 Lake Park' },
{ key: '3', name: 'Joe Black', age: 32, address: 'Sydney No. 1 Lake Park' },
{ key: '4', name: 'Jim Red', age: 32, address: 'London No. 2 Lake Park' },
];
const columnsTable: TableProps<DataTableType>['columns'] = [
{ title: 'Name', dataIndex: 'name' },
{ title: 'Borrow', dataIndex: 'borrow' },
{ title: 'Repayment', dataIndex: 'repayment' },
];
const summaryDataSource: DataTableType[] = [
{ key: '1', name: 'John Brown', borrow: 10, repayment: 33 },
{ key: '2', name: 'Jim Green', borrow: 100, repayment: 0 },
{ key: '3', name: 'Joe Black', borrow: 10, repayment: 10 },
{ key: '4', name: 'Jim Red', borrow: 75, repayment: 45 },
];
const expandDataSource = Array.from({ length: 3 }).map<ExpandDataType>((_, i) => ({
key: i,
date: '2014-12-24 23:12:00',
name: 'This is production name',
upgradeNum: 'Upgraded: 56',
}));
const expandColumns: TableProps<ExpandDataType>['columns'] = [
{ title: 'Date', dataIndex: 'date', key: 'date' },
{ title: 'Name', dataIndex: 'name', key: 'name' },
{
title: 'Status',
key: 'state',
render: () => (
<span>
<Badge status="success" />
Finished
</span>
),
},
{ title: 'Upgrade Status', dataIndex: 'upgradeNum', key: 'upgradeNum' },
{
title: 'Action',
dataIndex: 'operation',
key: 'operation',
render: () => (
<span className="table-operation">
<a>Pause</a>
<a>Stop</a>
<Dropdown>
<a>
More <DownOutlined />
</a>
</Dropdown>
</span>
),
},
];
const expandedRowRender = () => (
<Table<ExpandDataType> columns={expandColumns} dataSource={expandDataSource} pagination={false} />
);
const columnsNest: TableProps<NestDataType>['columns'] = [
{ title: 'Name', dataIndex: 'name', key: 'name' },
{ title: 'Platform', dataIndex: 'platform', key: 'platform' },
{ title: 'Version', dataIndex: 'version', key: 'version' },
{ title: 'Upgraded', dataIndex: 'upgradeNum', key: 'upgradeNum' },
{ title: 'Creator', dataIndex: 'creator', key: 'creator' },
{ title: 'Date', dataIndex: 'createdAt', key: 'createdAt' },
{ title: 'Action', key: 'operation', render: () => <a>Publish</a> },
];
const nestDataSource = Array.from({ length: 3 }).map<NestDataType>((_, i) => ({
key: i,
name: 'Screem',
platform: 'iOS',
version: '10.3.4.5654',
upgradeNum: 500,
creator: 'Jack',
createdAt: '2014-12-24 23:12:00',
}));
const columnsFixed: TableProps<FixedDataType>['columns'] = [
{ title: 'Full Name', width: 100, dataIndex: 'name', key: 'name', fixed: 'left' },
{ title: 'Age', width: 100, dataIndex: 'age', key: 'age', fixed: 'left' },
{ title: 'Column 1', dataIndex: 'address', key: '1' },
{ title: 'Column 2', dataIndex: 'address', key: '2' },
{ title: 'Column 3', dataIndex: 'address', key: '3' },
{ title: 'Column 4', dataIndex: 'address', key: '4' },
{ title: 'Column 5', dataIndex: 'address', key: '5' },
{ title: 'Column 6', dataIndex: 'address', key: '6' },
{ title: 'Column 7', dataIndex: 'address', key: '7' },
{ title: 'Column 8', dataIndex: 'address', key: '8' },
{ title: 'Action', key: 'operation', fixed: 'right', width: 100, render: () => <a>action</a> },
];
const fixedDataSource: FixedDataType[] = [
{ key: '1', name: 'John Brown', age: 32, address: 'New York Park' },
{ key: '2', name: 'Jim Green', age: 40, address: 'London Park' },
];
const TableTransfer: React.FC<
Readonly<Partial<Record<'leftColumns' | 'rightColumns', TableProps<DataType>['columns']>>> &
TransferProps<DataType>
> = (props) => {
const { leftColumns, rightColumns, ...restProps } = props;
return (
<Transfer<DataType> {...restProps} showSelectAll={false}>
{(transferProps) => {
const {
direction,
filteredItems,
onItemSelectAll,
onItemSelect,
selectedKeys: listSelectedKeys,
disabled: listDisabled,
} = transferProps;
const columns = (direction === 'left' ? leftColumns : rightColumns) ?? [];
const rowSelection: TableProps<DataType>['rowSelection'] = {
getCheckboxProps: (item) => ({ disabled: listDisabled || item.disabled }),
onSelectAll(selected, selectedRows) {
const treeSelectedKeys = selectedRows
.filter((item) => !item.disabled)
.map(({ key }) => key);
const diffKeys = selected
? difference(treeSelectedKeys, listSelectedKeys)
: difference(listSelectedKeys, treeSelectedKeys);
onItemSelectAll(diffKeys, selected);
},
onSelect({ key }, selected) {
onItemSelect(key, selected);
},
selectedRowKeys: listSelectedKeys,
};
return (
<Table<DataType>
id="components-transfer-table"
rowSelection={rowSelection}
columns={columns}
dataSource={filteredItems}
size="small"
style={{ pointerEvents: listDisabled ? 'none' : 'auto' }}
onRow={({ key, disabled: itemDisabled }) => ({
onClick: () => {
if (itemDisabled || listDisabled) {
return;
}
onItemSelect(key, !listSelectedKeys.includes(key));
},
})}
/>
);
}}
</Transfer>
);
};
const columns: TableProps<RecordType>['columns'] = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
filters: [
{ text: 'Joe', value: 'Joe' },
{ text: 'Jim', value: 'Jim' },
],
filteredValue: null,
onFilter: (value, record) => record.name.includes(value as string),
sorter: (a, b) => a.name.length - b.name.length,
sortOrder: 'ascend',
ellipsis: true,
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
sorter: false,
sortOrder: 'ascend',
ellipsis: true,
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
filters: [
{ text: 'London', value: 'London' },
{ text: 'New York', value: 'New York' },
],
filteredValue: null,
onFilter: (value, record) => record.address.includes(value as string),
sorter: false,
sortOrder: 'ascend',
ellipsis: true,
},
];
const tableTransferColumns: TableProps<DataType>['columns'] = [
{ dataIndex: 'title', title: 'Name' },
{ dataIndex: 'description', title: 'Description' },
];
const Demo: React.FC = () => {
const [open, setOpen] = useState(false);
const [targetKeys, setTargetKeys] = useState<TransferKey[]>(oriTargetKeys);
const [selectedKeys, setSelectedKeys] = useState<TransferKey[]>([]);
const [disabled, setDisabled] = useState(false);
const [showSearch, setShowSearch] = useState(false);
const handleDisable = (isDisabled: boolean) => {
setDisabled(isDisabled);
};
const handleTableTransferChange = (nextTargetKeys: TransferKey[]) => {
setTargetKeys(nextTargetKeys);
};
const triggerDisable = (isDisabled: boolean) => {
setDisabled(isDisabled);
};
const triggerShowSearch = (isShowSearch: boolean) => {
setShowSearch(isShowSearch);
};
const handleTransferChange = (keys: TransferKey[]) => {
setTargetKeys(keys);
};
const handleTransferSelectChange = (
sourceSelectedKeys: TransferKey[],
targetSelectedKeys: TransferKey[],
) => {
setSelectedKeys([...sourceSelectedKeys, ...targetSelectedKeys]);
};
const showModal = () => {
setOpen(true);
};
const handleOk = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
console.log(e);
setOpen(false);
};
const handleCancel = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
console.log(e);
setOpen(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal title="Basic Modal" open={open} onOk={handleOk} onCancel={handleCancel}>
<Switch
unCheckedChildren="disabled"
checkedChildren="disabled"
checked={disabled}
onChange={handleDisable}
style={{ marginBottom: 16 }}
/>
<Card title="Card Title">
<Card.Grid>Content</Card.Grid>
<Card.Grid hoverable={false}>Content</Card.Grid>
<Card.Grid>Content</Card.Grid>
<Card.Grid>Content</Card.Grid>
<Card.Grid>Content</Card.Grid>
<Card.Grid>Content</Card.Grid>
<Card.Grid>Content</Card.Grid>
</Card>
<Collapse>
<Panel header="This is panel header 1" key="1">
<Collapse defaultActiveKey="1">
<Panel header="This is panel nest panel" key="1">
<p>{text}</p>
</Panel>
</Collapse>
</Panel>
<Panel header="This is panel header 2" key="2">
<p>{text}</p>
</Panel>
<Panel header="This is panel header 3" key="3">
<p>{text}</p>
</Panel>
</Collapse>
<Transfer<DataType>
dataSource={mockData}
titles={['Source', 'Target']}
targetKeys={targetKeys}
selectedKeys={selectedKeys}
onChange={handleTransferChange}
onSelectChange={handleTransferSelectChange}
render={(item) => item.title}
disabled={disabled}
/>
<TableTransfer
dataSource={mockData}
targetKeys={targetKeys}
disabled={disabled}
showSearch={showSearch}
leftColumns={tableTransferColumns}
rightColumns={tableTransferColumns}
onChange={handleTableTransferChange}
filterOption={(inputValue: string, item: any) =>
item.title?.includes(inputValue) || item.tag?.includes(inputValue)
}
/>
<Switch
unCheckedChildren="disabled"
checkedChildren="disabled"
checked={disabled}
onChange={triggerDisable}
style={{ marginTop: 16 }}
/>
<Switch
unCheckedChildren="showSearch"
checkedChildren="showSearch"
checked={showSearch}
onChange={triggerShowSearch}
style={{ marginTop: 16 }}
/>
<Anchor>
<Link href="#anchor-demo-basic" title="Basic demo" />
<Link href="#anchor-demo-static" title="Static demo" />
<Link href="#anchor-demo-basic" title="Basic demo with Target" target="_blank" />
<Link href="#API" title="API">
<Link href="#Anchor-Props" title="Anchor Props" />
<Link href="#Link-Props" title="Link Props" />
</Link>
</Anchor>
<Tabs type="card">
<TabPane tab="Tab 1" key="1">
Content of Tab Pane 1
</TabPane>
<TabPane tab="Tab 2" key="2">
Content of Tab Pane 2
</TabPane>
<TabPane tab="Tab 3" key="3">
Content of Tab Pane 3
</TabPane>
</Tabs>
<Timeline>
<Timeline.Item>Create a services site 2015-09-01</Timeline.Item>
<Timeline.Item>Solve initial network problems 2015-09-01</Timeline.Item>
<Timeline.Item dot={<ClockCircleOutlined style={{ fontSize: '16px' }} />} color="red">
Technical testing 2015-09-01
</Timeline.Item>
<Timeline.Item>Network problems being solved 2015-09-01</Timeline.Item>
</Timeline>
<Calendar />
<Tree showLine switcherIcon={<DownOutlined />} defaultExpandedKeys={['0-0-0']}>
<TreeNode title="parent 1" key="0-0">
<TreeNode title="parent 1-0" key="0-0-0">
<TreeNode title="leaf" key="0-0-0-0" />
<TreeNode title="leaf" key="0-0-0-1" />
<TreeNode title="leaf" key="0-0-0-2" />
</TreeNode>
<TreeNode title="parent 1-1" key="0-0-1">
<TreeNode title="leaf" key="0-0-1-0" />
</TreeNode>
<TreeNode title="parent 1-2" key="0-0-2">
<TreeNode title="leaf" key="0-0-2-0" />
<TreeNode title="leaf" key="0-0-2-1" />
</TreeNode>
</TreeNode>
</Tree>
<Table<RecordType> columns={columns} dataSource={dataSource} footer={() => 'Footer'} />
<Table<DataTableType>
columns={columnsTable}
dataSource={summaryDataSource}
pagination={false}
id="table-demo-summary"
bordered
summary={(pageData) => {
let totalBorrow = 0;
let totalRepayment = 0;
pageData.forEach(({ borrow, repayment }) => {
totalBorrow += borrow;
totalRepayment += repayment;
});
return (
<>
<tr>
<th>Total</th>
<td>
<Text type="danger">{totalBorrow}</Text>
</td>
<td>
<Text>{totalRepayment}</Text>
</td>
</tr>
<tr>
<th>Balance</th>
<td colSpan={2}>
<Text type="danger">{totalBorrow - totalRepayment}</Text>
</td>
</tr>
</>
);
}}
/>
<br />
<Table<NestDataType>
columns={columnsNest}
expandable={{ expandedRowRender }}
dataSource={nestDataSource}
/>
<Table<FixedDataType>
columns={columnsFixed}
dataSource={fixedDataSource}
scroll={{ x: 1300, y: 100 }}
/>
<Card
hoverable
style={{ width: 240 }}
cover={
<img
draggable={false}
alt="example"
src="https://os.alipayobjects.com/rmsportal/QBnOOoLaAfKPirc.png"
/>
}
>
<Meta title="Europe Street beat" description="www.instagram.com" />
</Card>
<Slider defaultValue={30} />
<DatePicker defaultValue={dayjs('2015/01/01', 'YYYY/MM/DD')} format="YYYY/MM/DD" />
<Badge count={5}>
<a href="#" className="head-example" />
</Badge>
</Modal>
</>
);
};
export default Demo;
```
### 自定义页脚按钮属性
传入 `okButtonProps` 和 `cancelButtonProps` 可分别自定义确定按钮和取消按钮的 props。
```tsx
import React, { useState } from 'react';
import { Button, Modal } from 'antd';
const App: React.FC = () => {
const [open, setOpen] = useState(false);
const showModal = () => {
setOpen(true);
};
const handleOk = (e: React.MouseEvent<HTMLElement>) => {
console.log(e);
setOpen(false);
};
const handleCancel = (e: React.MouseEvent<HTMLElement>) => {
console.log(e);
setOpen(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal with customized button props
</Button>
<Modal
title="Basic Modal"
open={open}
onOk={handleOk}
onCancel={handleCancel}
okButtonProps={{ disabled: true }}
cancelButtonProps={{ disabled: true }}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</>
);
};
export default App;
```
### 自定义渲染对话框
自定义渲染对话框, 可通过 `react-draggable` 来实现拖拽。
```tsx
import React, { useRef, useState } from 'react';
import { Button, Modal } from 'antd';
import type { DraggableData, DraggableEvent } from 'react-draggable';
import Draggable from 'react-draggable';
const App: React.FC = () => {
const [open, setOpen] = useState(false);
const [disabled, setDisabled] = useState(true);
const [bounds, setBounds] = useState({ left: 0, top: 0, bottom: 0, right: 0 });
const draggleRef = useRef<HTMLDivElement>(null!);
const showModal = () => {
setOpen(true);
};
const handleOk = (e: React.MouseEvent<HTMLElement>) => {
console.log(e);
setOpen(false);
};
const handleCancel = (e: React.MouseEvent<HTMLElement>) => {
console.log(e);
setOpen(false);
};
const onStart = (_event: DraggableEvent, uiData: DraggableData) => {
const { clientWidth, clientHeight } = window.document.documentElement;
const targetRect = draggleRef.current?.getBoundingClientRect();
if (!targetRect) {
return;
}
setBounds({
left: -targetRect.left + uiData.x,
right: clientWidth - (targetRect.right - uiData.x),
top: -targetRect.top + uiData.y,
bottom: clientHeight - (targetRect.bottom - uiData.y),
});
};
return (
<>
<Button onClick={showModal}>Open Draggable Modal</Button>
<Modal
title={
<div
style={{ width: '100%', cursor: 'move' }}
onMouseOver={() => {
if (disabled) {
setDisabled(false);
}
}}
onMouseOut={() => {
setDisabled(true);
}}
// fix eslintjsx-a11y/mouse-events-have-key-events
// https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/master/docs/rules/mouse-events-have-key-events.md
onFocus={() => {}}
onBlur={() => {}}
// end
>
Draggable Modal
</div>
}
open={open}
onOk={handleOk}
onCancel={handleCancel}
modalRender={(modal) => (
<Draggable
disabled={disabled}
bounds={bounds}
nodeRef={draggleRef}
onStart={(event, uiData) => onStart(event, uiData)}
>
<div ref={draggleRef}>{modal}</div>
</Draggable>
)}
>
<p>
Just don't learn physics at school and your life will be full of magic and miracles.
</p>
<br />
<p>Day before yesterday I saw a rabbit, and yesterday a deer, and today, you.</p>
</Modal>
</>
);
};
export default App;
```
### 自定义模态的宽度
使用 `width` 来设置模态对话框的宽度。
```tsx
import React, { useState } from 'react';
import { Button, Flex, Modal } from 'antd';
const App: React.FC = () => {
const [open, setOpen] = useState(false);
const [openResponsive, setOpenResponsive] = useState(false);
return (
<Flex vertical gap="middle" align="flex-start">
{/* Basic */}
<Button type="primary" onClick={() => setOpen(true)}>
Open Modal of 1000px width
</Button>
<Modal
title="Modal 1000px width"
centered
open={open}
onOk={() => setOpen(false)}
onCancel={() => setOpen(false)}
width={1000}
>
<p>some contents...</p>
<p>some contents...</p>
<p>some contents...</p>
</Modal>
{/* Responsive */}
<Button type="primary" onClick={() => setOpenResponsive(true)}>
Open Modal of responsive width
</Button>
<Modal
title="Modal responsive width"
centered
open={openResponsive}
onOk={() => setOpenResponsive(false)}
onCancel={() => setOpenResponsive(false)}
width={{
xs: '90%',
sm: '80%',
md: '70%',
lg: '60%',
xl: '50%',
xxl: '40%',
}}
>
<p>some contents...</p>
<p>some contents...</p>
<p>some contents...</p>
</Modal>
</Flex>
);
};
export default App;
```
### 静态方法
静态方法无法消费 Context,不能动态响应 ConfigProvider 提供的各项配置,启用 `layer` 时还可能导致样式异常。请优先使用 hooks 版本或者 App 组件提供的 `modal` 实例。
```tsx
import React from 'react';
import { Button, Modal, Space } from 'antd';
const info = () => {
Modal.info({
title: 'This is a notification message',
content: (
<div>
<p>some messages...some messages...</p>
<p>some messages...some messages...</p>
</div>
),
onOk() {},
});
};
const success = () => {
Modal.success({
content: 'some messages...some messages...',
});
};
const error = () => {
Modal.error({
title: 'This is an error message',
content: 'some messages...some messages...',
});
};
const warning = () => {
Modal.warning({
title: 'This is a warning message',
content: 'some messages...some messages...',
});
};
const App: React.FC = () => (
<Space wrap>
<Button onClick={info}>Info</Button>
<Button onClick={success}>Success</Button>
<Button onClick={error}>Error</Button>
<Button onClick={warning}>Warning</Button>
</Space>
);
export default App;
```
### 静态确认对话框
使用 `confirm()` 可以快捷地弹出确认框。onCancel/onOk 返回 promise 可以延迟关闭。
```tsx
import React from 'react';
import { ExclamationCircleFilled } from '@ant-design/icons';
import { Button, Modal, Space } from 'antd';
const { confirm } = Modal;
const showConfirm = () => {
confirm({
title: 'Do you want to delete these items?',
icon: <ExclamationCircleFilled />,
content: 'Some descriptions',
onOk() {
console.log('OK');
},
onCancel() {
console.log('Cancel');
},
});
};
const showPromiseConfirm = () => {
confirm({
title: 'Do you want to delete these items?',
icon: <ExclamationCircleFilled />,
content: 'When clicked the OK button, this dialog will be closed after 1 second',
onOk() {
return new Promise((resolve, reject) => {
setTimeout(Math.random() > 0.5 ? resolve : reject, 1000);
}).catch(() => console.log('Oops errors!'));
},
onCancel() {},
});
};
const showDeleteConfirm = () => {
confirm({
title: 'Are you sure delete this task?',
icon: <ExclamationCircleFilled />,
content: 'Some descriptions',
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
onOk() {
console.log('OK');
},
onCancel() {
console.log('Cancel');
},
});
};
const showPropsConfirm = () => {
confirm({
title: 'Are you sure delete this task?',
icon: <ExclamationCircleFilled />,
content: 'Some descriptions',
okText: 'Yes',
okType: 'danger',
okButtonProps: {
disabled: true,
},
cancelText: 'No',
onOk() {
console.log('OK');
},
onCancel() {
console.log('Cancel');
},
});
};
const App: React.FC = () => (
<Space wrap>
<Button onClick={showConfirm}>Confirm</Button>
<Button onClick={showPromiseConfirm}>With promise</Button>
<Button onClick={showDeleteConfirm} type="dashed">
Delete
</Button>
<Button onClick={showPropsConfirm} type="dashed">
With extra props
</Button>
</Space>
);
export default App;
```
### 自定义内部模块 className
通过 `classNames` 属性设置弹窗内部区域(header、body、footer、mask、wrapper)的 `className`。
```tsx
import React, { useState } from 'react';
import { Button, ConfigProvider, Modal, Space } from 'antd';
import { createStyles, useTheme } from 'antd-style';
const useStyle = createStyles(({ token }) => ({
'my-modal-body': {
background: token.blue1,
padding: token.paddingSM,
},
'my-modal-mask': {
boxShadow: `inset 0 0 15px #fff`,
},
'my-modal-header': {
borderBottom: `1px dotted ${token.colorPrimary}`,
},
'my-modal-footer': {
color: token.colorPrimary,
},
'my-modal-content': {
border: '1px solid #333',
},
}));
const App: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState([false, false]);
const { styles } = useStyle();
const token = useTheme();
const toggleModal = (idx: number, target: boolean) => {
setIsModalOpen((p) => {
p[idx] = target;
return [...p];
});
};
const classNames = {
body: styles['my-modal-body'],
mask: styles['my-modal-mask'],
header: styles['my-modal-header'],
footer: styles['my-modal-footer'],
content: styles['my-modal-content'],
};
const modalStyles = {
header: {
borderInlineStart: `5px solid ${token.colorPrimary}`,
borderRadius: 0,
paddingInlineStart: 5,
},
body: {
boxShadow: 'inset 0 0 5px #999',
borderRadius: 5,
},
mask: {
backdropFilter: 'blur(10px)',
},
footer: {
borderTop: '1px solid #333',
},
content: {
boxShadow: '0 0 30px #999',
},
};
return (
<>
<Space>
<Button type="primary" onClick={() => toggleModal(0, true)}>
Open Modal
</Button>
<Button type="primary" onClick={() => toggleModal(1, true)}>
ConfigProvider
</Button>
</Space>
<Modal
title="Basic Modal"
open={isModalOpen[0]}
onOk={() => toggleModal(0, false)}
onCancel={() => toggleModal(0, false)}
footer="Footer"
classNames={classNames}
styles={modalStyles}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
<ConfigProvider
modal={{
classNames,
styles: modalStyles,
}}
>
<Modal
title="Basic Modal"
open={isModalOpen[1]}
onOk={() => toggleModal(1, false)}
onCancel={() => toggleModal(1, false)}
footer="Footer"
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</ConfigProvider>
</>
);
};
export default App;
```
### 销毁确认对话框
使用 `Modal.destroyAll()` 可以销毁弹出的确认窗。通常用于路由监听当中,处理路由前进、后退不能销毁确认对话框的问题。
```tsx
import React from 'react';
import { ExclamationCircleOutlined } from '@ant-design/icons';
import { Button, Modal } from 'antd';
const { confirm } = Modal;
const destroyAll = () => {
Modal.destroyAll();
};
const showConfirm = () => {
for (let i = 0; i < 3; i += 1) {
setTimeout(() => {
confirm({
icon: <ExclamationCircleOutlined />,
content: <Button onClick={destroyAll}>Click to destroy all</Button>,
onOk() {
console.log('OK');
},
onCancel() {
console.log('Cancel');
},
});
}, i * 500);
}
};
const App: React.FC = () => <Button onClick={showConfirm}>Confirm</Button>;
export default App;
```
### 嵌套弹框
嵌套弹框
```tsx
import React, { useState } from 'react';
import { Button, message, Modal, notification, Select, Space, Switch } from 'antd';
const options = [
{
label: 'Option 1',
value: '1',
},
{
label: 'Option 2',
value: '2',
},
];
const Demo: React.FC = () => {
const [messageInstance, messageHolder] = message.useMessage();
const [notificationInstance, notificationHolder] = notification.useNotification();
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const onShowStatic = () => {
Modal.confirm({
content: <Select open value="1" options={options} />,
});
};
return (
<Space>
<Switch
style={{ position: 'relative', zIndex: isModalOpen ? 4000 : 0 }}
checkedChildren="Open"
unCheckedChildren="Close"
onChange={(open) => setIsModalOpen(open)}
/>
<Button onClick={onShowStatic}>Static</Button>
<Modal
title="Basic Modal"
open={isModalOpen}
footer={null}
destroyOnHidden
onCancel={() => setIsModalOpen(false)}
maskClosable={false}
closable={false}
styles={{
content: {
marginBlockStart: 100,
},
}}
>
<Select open value="1" options={options} />
<Modal
title="Nested Modal"
open={isModalOpen}
footer={null}
destroyOnHidden
mask={false}
onCancel={() => setIsModalOpen(false)}
maskClosable={false}
closable={false}
styles={{
content: {
marginBlockStart: 250,
},
body: {
display: 'flex',
justifyContent: 'center',
},
}}
>
<Select open value="1" options={options} />
<Modal
title="Nested Modal"
open={isModalOpen}
footer={null}
destroyOnHidden
mask={false}
maskClosable={false}
onCancel={() => setIsModalOpen(false)}
closable={false}
styles={{
content: {
marginBlockStart: 400,
},
body: {
display: 'flex',
justifyContent: 'flex-end',
},
}}
>
<Space wrap>
<Button
onClick={() => {
Modal.confirm({
title: 'Are you OK?',
content: 'I am OK',
});
}}
>
Static Confirm
</Button>
<Button
onClick={() => {
message.success('Hello World');
notification.success({
message: 'Hello World',
});
}}
>
Static Message, Notification
</Button>
<Button
onClick={() => {
messageInstance.success('Hello World');
notificationInstance.success({
message: 'Hello World',
});
}}
>
Hook Message, Notification
</Button>
<Select open value="1" options={options} />
</Space>
</Modal>
</Modal>
</Modal>
{messageHolder}
{notificationHolder}
</Space>
);
};
export default Demo;
```
### \_InternalPanelDoNotUseOrYouWillBeFired
调试用组件,请勿直接使用。
```tsx
import React from 'react';
import { Button, Modal, Space, Typography } from 'antd';
import type { ModalFuncProps } from 'antd';
/** Test usage. Do not use in your production. */
const { _InternalPanelDoNotUseOrYouWillBeFired: InternalPanel } = Modal;
const customFooterFn: ModalFuncProps['footer'] = (originNode, { OkBtn, CancelBtn }) => (
<Space direction="vertical">
<Space>{originNode}</Space>
<Space>
<CancelBtn />
<Button danger type="primary">
Custom
</Button>
<OkBtn />
</Space>
</Space>
);
export default () => (
<div style={{ display: 'flex', flexDirection: 'column', rowGap: 16 }}>
<InternalPanel title="Hello World!" style={{ width: '100%', height: 200 }}>
Hello World?!
</InternalPanel>
<InternalPanel type="success" style={{ width: 200, height: 150 }}>
A good news!
</InternalPanel>
<InternalPanel title="Confirm This?" type="confirm" style={{ width: 300, height: 200 }}>
Some descriptions.
</InternalPanel>
<InternalPanel
title="Custom Footer Render"
style={{ width: 380, height: 200 }}
footer={customFooterFn}
>
<Typography.Paragraph>
<Typography.Link href="https://github.com/ant-design/ant-design/pull/44318">
Feature #44318
</Typography.Link>
</Typography.Paragraph>
</InternalPanel>
</div>
);
```
### 控制弹框动画原点
通过 `mousePosition` 控制弹框动画原点.
```tsx
import React, { useState } from 'react';
import { Button, Modal } from 'antd';
const App: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const showModal = () => {
setIsModalOpen(true);
};
const handleOk = () => {
setIsModalOpen(false);
};
const handleCancel = () => {
setIsModalOpen(false);
};
return (
<>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
mousePosition={{ x: 300, y: 300 }}
>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</Modal>
</>
);
};
export default App;
```
### 线框风格
线框风格。
```tsx
import React from 'react';
import { ConfigProvider, Modal } from 'antd';
/** Test usage. Do not use in your production. */
const { _InternalPanelDoNotUseOrYouWillBeFired: InternalPanel } = Modal;
export default () => (
<ConfigProvider theme={{ token: { wireframe: true } }}>
<div style={{ display: 'flex', flexDirection: 'column', rowGap: 16 }}>
<InternalPanel title="Hello World!" style={{ width: '100%' }}>
Hello World?!
</InternalPanel>
<InternalPanel type="success" style={{ width: 200 }}>
A good news!
</InternalPanel>
<InternalPanel title="Confirm This?" type="confirm" style={{ width: 300 }}>
Some descriptions.
</InternalPanel>
</div>
</ConfigProvider>
);
```
### 组件 Token
Component Token Debug.
```tsx
import React from 'react';
import { ConfigProvider, Modal } from 'antd';
/** Test usage. Do not use in your production. */
const { _InternalPanelDoNotUseOrYouWillBeFired: InternalPanel } = Modal;
export default () => (
<ConfigProvider
theme={{
components: {
Modal: {
footerBg: '#f6ffed',
contentBg: '#e6fffb',
headerBg: '#f9f0ff',
titleLineHeight: 3,
titleFontSize: 12,
titleColor: '#1d39c4',
},
},
}}
>
<div style={{ display: 'flex', flexDirection: 'column', rowGap: 16 }}>
<InternalPanel title="Hello World!" style={{ width: '100%' }}>
Hello World?!
</InternalPanel>
<ConfigProvider theme={{ token: { wireframe: true } }}>
<InternalPanel title="Hello World!" style={{ width: '100%' }}>
Hello World?!
</InternalPanel>
</ConfigProvider>
<InternalPanel type="success" style={{ width: 200 }}>
A good news!
</InternalPanel>
<InternalPanel title="Confirm This?" type="confirm" style={{ width: 300 }}>
Some descriptions.
</InternalPanel>
</div>
</ConfigProvider>
);
```