mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 02:59:58 +08:00
feat: Notification support hooks for context (#21275)
* use promise for getInstance * passing hooks * hooks support * move hooks out of fiel * adjust style * update snapshot * fix test & add test case * update hooks test * fix style lint * update doc * update demo desc & eslitn rules * fix lint * docs add faq * fix less
This commit is contained in:
parent
06ba40e860
commit
3080611883
@ -27,6 +27,8 @@ const eslintrc = {
|
||||
files: ['*.ts', '*.tsx'],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': [2, { args: 'none' }],
|
||||
'no-unused-expressions': 'off',
|
||||
'@typescript-eslint/no-unused-expressions': 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -6,11 +6,15 @@ import Input from '../../input';
|
||||
|
||||
describe('Form.typescript', () => {
|
||||
it('Form.Item', () => {
|
||||
<Form>
|
||||
<Form.Item name="test">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Form>;
|
||||
const form = (
|
||||
<Form>
|
||||
<Form.Item name="test">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
|
||||
expect(form).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -44,6 +44,127 @@ exports[`renders ./components/notification/demo/duration.md correctly 1`] = `
|
||||
</button>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/notification/demo/hooks.md correctly 1`] = `
|
||||
Array [
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="radius-upleft"
|
||||
class="anticon anticon-radius-upleft"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="radius-upleft"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M656 200h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm58 624h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM192 650h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm696-696h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-348 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-174 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm174-696H358c-127 0-230 103-230 230v182c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V358c0-87.3 70.7-158 158-158h182c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span>
|
||||
topLeft
|
||||
</span>
|
||||
</button>,
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="radius-upright"
|
||||
class="anticon anticon-radius-upright"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="radius-upright"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M368 128h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-2 696h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm522-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM192 128h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm348 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm174 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-48-696H484c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h182c87.3 0 158 70.7 158 158v182c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V358c0-127-103-230-230-230z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span>
|
||||
topRight
|
||||
</span>
|
||||
</button>,
|
||||
<div
|
||||
class="ant-divider ant-divider-horizontal"
|
||||
role="separator"
|
||||
/>,
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="radius-bottomleft"
|
||||
class="anticon anticon-radius-bottomleft"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="radius-bottomleft"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M712 824h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm2-696h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM136 374h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm0-174h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm752 624h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-348 0h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-230 72h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm230 624H358c-87.3 0-158-70.7-158-158V484c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v182c0 127 103 230 230 230h182c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span>
|
||||
bottomLeft
|
||||
</span>
|
||||
</button>,
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="radius-bottomright"
|
||||
class="anticon anticon-radius-bottomright"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class=""
|
||||
data-icon="radius-bottomright"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M368 824h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-58-624h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm578 102h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM192 824h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0-174h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm292 72h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm174 0h56c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm230 276h-56c-4.4 0-8 3.6-8 8v182c0 87.3-70.7 158-158 158H484c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h182c127 0 230-103 230-230V484c0-4.4-3.6-8-8-8z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span>
|
||||
bottomRight
|
||||
</span>
|
||||
</button>,
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`renders ./components/notification/demo/placement.md correctly 1`] = `
|
||||
<div>
|
||||
<button
|
||||
|
53
components/notification/__tests__/hooks.test.js
Normal file
53
components/notification/__tests__/hooks.test.js
Normal file
@ -0,0 +1,53 @@
|
||||
/* eslint-disable jsx-a11y/control-has-associated-label */
|
||||
import React from 'react';
|
||||
import { mount } from 'enzyme';
|
||||
import notification from '..';
|
||||
import ConfigProvider from '../../config-provider';
|
||||
|
||||
describe('notification.hooks', () => {
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
notification.destroy();
|
||||
});
|
||||
|
||||
it('should work', () => {
|
||||
const Context = React.createContext('light');
|
||||
|
||||
const Demo = () => {
|
||||
const [api, holder] = notification.useNotification();
|
||||
|
||||
return (
|
||||
<ConfigProvider prefixCls="my-test">
|
||||
<Context.Provider value="bamboo">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
api.open({
|
||||
description: (
|
||||
<Context.Consumer>
|
||||
{name => <span className="hook-test-result">{name}</span>}
|
||||
</Context.Consumer>
|
||||
),
|
||||
duration: 0,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
{holder}
|
||||
</Context.Provider>
|
||||
</ConfigProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const wrapper = mount(<Demo />);
|
||||
wrapper.find('button').simulate('click');
|
||||
expect(document.querySelectorAll('.my-test-notification-notice').length).toBe(1);
|
||||
expect(document.querySelector('.hook-test-result').innerHTML).toEqual('bamboo');
|
||||
});
|
||||
});
|
63
components/notification/demo/hooks.md
Executable file
63
components/notification/demo/hooks.md
Executable file
@ -0,0 +1,63 @@
|
||||
---
|
||||
order: 8
|
||||
title:
|
||||
zh-CN: 通过 Hooks 获取上下文
|
||||
en-US: Get context with hooks
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
通过 `notification.useNotification` 创建支持读取 context 的 `contextHolder`。
|
||||
|
||||
## en-US
|
||||
|
||||
Use `notification.useNotification` to get `contextHolder` with context accessible issue.
|
||||
|
||||
```jsx
|
||||
import { Button, notification, Divider } from 'antd';
|
||||
import {
|
||||
RadiusUpleftOutlined,
|
||||
RadiusUprightOutlined,
|
||||
RadiusBottomleftOutlined,
|
||||
RadiusBottomrightOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
const Context = React.createContext({ name: 'Default' });
|
||||
|
||||
const Demo = () => {
|
||||
const [api, contextHolder] = notification.useNotification();
|
||||
|
||||
const openNotification = placement => {
|
||||
api.info({
|
||||
message: `Notification ${placement}`,
|
||||
description: <Context.Consumer>{({ name }) => `Hello, ${name}!`}</Context.Consumer>,
|
||||
placement,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Context.Provider value={{ name: 'Ant Design' }}>
|
||||
{contextHolder}
|
||||
<Button type="primary" onClick={() => openNotification('topLeft')}>
|
||||
<RadiusUpleftOutlined />
|
||||
topLeft
|
||||
</Button>
|
||||
<Button type="primary" onClick={() => openNotification('topRight')}>
|
||||
<RadiusUprightOutlined />
|
||||
topRight
|
||||
</Button>
|
||||
<Divider />
|
||||
<Button type="primary" onClick={() => openNotification('bottomLeft')}>
|
||||
<RadiusBottomleftOutlined />
|
||||
bottomLeft
|
||||
</Button>
|
||||
<Button type="primary" onClick={() => openNotification('bottomRight')}>
|
||||
<RadiusBottomrightOutlined />
|
||||
bottomRight
|
||||
</Button>
|
||||
</Context.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(<Demo />, mountNode);
|
||||
```
|
72
components/notification/hooks/useNotification.tsx
Normal file
72
components/notification/hooks/useNotification.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import * as React from 'react';
|
||||
import useRCNotification from 'rc-notification/lib/useNotification';
|
||||
import {
|
||||
NotificationInstance as RCNotificationInstance,
|
||||
NoticeContent as RCNoticeContent,
|
||||
HolderReadyCallback as RCHolderReadyCallback,
|
||||
} from 'rc-notification/lib/Notification';
|
||||
import { ConfigConsumer, ConfigConsumerProps } from '../../config-provider';
|
||||
import { NotificationInstance, ArgsProps } from '..';
|
||||
|
||||
export default function createUseNotification(
|
||||
getNotificationInstance: (
|
||||
args: ArgsProps,
|
||||
callback: (info: { prefixCls: string; instance: RCNotificationInstance }) => void,
|
||||
) => void,
|
||||
getRCNoticeProps: (args: ArgsProps, prefixCls: string) => RCNoticeContent,
|
||||
) {
|
||||
const useNotification = (): [NotificationInstance, React.ReactElement] => {
|
||||
// We can only get content by render
|
||||
let getPrefixCls: ConfigConsumerProps['getPrefixCls'];
|
||||
|
||||
// We create a proxy to handle delay created instance
|
||||
let innerInstance: RCNotificationInstance | null = null;
|
||||
const proxy = {
|
||||
add: (noticeProps: RCNoticeContent, holderCallback?: RCHolderReadyCallback) => {
|
||||
innerInstance?.component.add(noticeProps, holderCallback);
|
||||
},
|
||||
} as any;
|
||||
|
||||
const [hookNotify, holder] = useRCNotification(proxy);
|
||||
|
||||
function notify(args: ArgsProps) {
|
||||
const { prefixCls: customizePrefixCls } = args;
|
||||
const mergedPrefixCls = getPrefixCls('notification', customizePrefixCls);
|
||||
|
||||
getNotificationInstance(
|
||||
{
|
||||
...args,
|
||||
prefixCls: mergedPrefixCls,
|
||||
},
|
||||
({ prefixCls, instance }) => {
|
||||
innerInstance = instance;
|
||||
hookNotify(getRCNoticeProps(args, prefixCls));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Fill functions
|
||||
const hookAPI: any = {
|
||||
open: notify,
|
||||
};
|
||||
['success', 'info', 'warning', 'error'].forEach(type => {
|
||||
hookAPI[type] = (args: ArgsProps) =>
|
||||
hookAPI.open({
|
||||
...args,
|
||||
type,
|
||||
});
|
||||
});
|
||||
|
||||
return [
|
||||
hookAPI,
|
||||
<ConfigConsumer key="holder">
|
||||
{(context: ConfigConsumerProps) => {
|
||||
({ getPrefixCls } = context);
|
||||
return holder;
|
||||
}}
|
||||
</ConfigConsumer>,
|
||||
];
|
||||
};
|
||||
|
||||
return useNotification;
|
||||
}
|
@ -66,3 +66,26 @@ notification.config({
|
||||
| getContainer | Return the mount node for Notification | () => HTMLNode | () => document.body |
|
||||
| placement | Position of Notification, can be one of `topLeft` `topRight` `bottomLeft` `bottomRight` | string | `topRight` |
|
||||
| top | Distance from the top of the viewport, when `placement` is `topRight` or `topLeft` (unit: pixels). | number | 24 |
|
||||
|
||||
## FAQ
|
||||
|
||||
### What's different between hooks and directly call?
|
||||
|
||||
antd will dynamic create React instance by `ReactDOM.render` when directly call notification methods. Whose context is different with origin code located context.
|
||||
|
||||
When you need context info (like ConfigProvider context), you can use `notification.useNotification` to get `api` instance and `contextHolder` node. And put it in your children:
|
||||
|
||||
```tsx
|
||||
const [api, contextHolder] = notification.useNotification();
|
||||
|
||||
return (
|
||||
<Context1.Provider value="Ant">
|
||||
{contextHolder}
|
||||
<Context2.Provider value="Design">
|
||||
{/* contextHolder is out of Context2 which mean api will not get context of Context2 */}
|
||||
</Context2.Provider>
|
||||
</Context1.Provider>
|
||||
);
|
||||
```
|
||||
|
||||
**Note:** You must insert `contextHolder` into your children with hooks. You can use origin method if you do not need context connection.
|
||||
|
@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import Notification from 'rc-notification';
|
||||
import { NotificationInstance as RCNotificationInstance } from 'rc-notification/lib/Notification';
|
||||
import {
|
||||
CloseOutlined,
|
||||
CheckCircleOutlined,
|
||||
@ -7,12 +8,13 @@ import {
|
||||
ExclamationCircleOutlined,
|
||||
InfoCircleOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import createUseNotification from './hooks/useNotification';
|
||||
|
||||
export type NotificationPlacement = 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight';
|
||||
|
||||
export type IconType = 'success' | 'info' | 'error' | 'warning';
|
||||
|
||||
const notificationInstance: { [key: string]: any } = {};
|
||||
const notificationInstance: { [key: string]: RCNotificationInstance } = {};
|
||||
let defaultDuration = 4.5;
|
||||
let defaultTop = 24;
|
||||
let defaultBottom = 24;
|
||||
@ -90,49 +92,46 @@ function getPlacementStyle(
|
||||
return style;
|
||||
}
|
||||
|
||||
type NotificationInstanceProps = {
|
||||
prefixCls: string;
|
||||
placement?: NotificationPlacement;
|
||||
getContainer?: () => HTMLElement;
|
||||
top?: number;
|
||||
bottom?: number;
|
||||
closeIcon?: React.ReactNode;
|
||||
};
|
||||
|
||||
function getNotificationInstance(
|
||||
{
|
||||
prefixCls,
|
||||
args: ArgsProps,
|
||||
callback: (info: { prefixCls: string; instance: RCNotificationInstance }) => void,
|
||||
) {
|
||||
const {
|
||||
placement = defaultPlacement,
|
||||
getContainer = defaultGetContainer,
|
||||
top,
|
||||
bottom,
|
||||
getContainer = defaultGetContainer,
|
||||
closeIcon = defaultCloseIcon,
|
||||
}: NotificationInstanceProps,
|
||||
callback: (n: any) => void,
|
||||
) {
|
||||
const cacheKey = `${prefixCls}-${placement}`;
|
||||
} = args;
|
||||
const outerPrefixCls = args.prefixCls || 'ant-notification';
|
||||
const prefixCls = `${outerPrefixCls}-notice`;
|
||||
|
||||
const cacheKey = `${outerPrefixCls}-${placement}`;
|
||||
if (notificationInstance[cacheKey]) {
|
||||
callback(notificationInstance[cacheKey]);
|
||||
callback({ prefixCls, instance: notificationInstance[cacheKey] });
|
||||
return;
|
||||
}
|
||||
|
||||
const closeIconToRender = (
|
||||
<span className={`${prefixCls}-close-x`}>
|
||||
{closeIcon || <CloseOutlined className={`${prefixCls}-close-icon`} />}
|
||||
<span className={`${outerPrefixCls}-close-x`}>
|
||||
{closeIcon || <CloseOutlined className={`${outerPrefixCls}-close-icon`} />}
|
||||
</span>
|
||||
);
|
||||
|
||||
(Notification as any).newInstance(
|
||||
Notification.newInstance(
|
||||
{
|
||||
prefixCls,
|
||||
className: `${prefixCls}-${placement}`,
|
||||
prefixCls: outerPrefixCls,
|
||||
className: `${outerPrefixCls}-${placement}`,
|
||||
style: getPlacementStyle(placement, top, bottom),
|
||||
getContainer,
|
||||
closeIcon: closeIconToRender,
|
||||
},
|
||||
(notification: any) => {
|
||||
notification => {
|
||||
notificationInstance[cacheKey] = notification;
|
||||
callback(notification);
|
||||
callback({
|
||||
prefixCls,
|
||||
instance: notification,
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -164,9 +163,7 @@ export interface ArgsProps {
|
||||
closeIcon?: React.ReactNode;
|
||||
}
|
||||
|
||||
function notice(args: ArgsProps) {
|
||||
const outerPrefixCls = args.prefixCls || 'ant-notification';
|
||||
const prefixCls = `${outerPrefixCls}-notice`;
|
||||
function getRCNoticeProps(args: ArgsProps, prefixCls: string) {
|
||||
const duration = args.duration === undefined ? defaultDuration : args.duration;
|
||||
|
||||
let iconNode: React.ReactNode = null;
|
||||
@ -183,44 +180,34 @@ function notice(args: ArgsProps) {
|
||||
<span className={`${prefixCls}-message-single-line-auto-margin`} />
|
||||
) : null;
|
||||
|
||||
const { placement, top, bottom, getContainer, closeIcon } = args;
|
||||
|
||||
getNotificationInstance(
|
||||
{
|
||||
prefixCls: outerPrefixCls,
|
||||
placement,
|
||||
top,
|
||||
bottom,
|
||||
getContainer,
|
||||
closeIcon,
|
||||
},
|
||||
(notification: any) => {
|
||||
notification.notice({
|
||||
content: (
|
||||
<div className={iconNode ? `${prefixCls}-with-icon` : ''}>
|
||||
{iconNode}
|
||||
<div className={`${prefixCls}-message`}>
|
||||
{autoMarginTag}
|
||||
{args.message}
|
||||
</div>
|
||||
<div className={`${prefixCls}-description`}>{args.description}</div>
|
||||
{args.btn ? <span className={`${prefixCls}-btn`}>{args.btn}</span> : null}
|
||||
</div>
|
||||
),
|
||||
duration,
|
||||
closable: true,
|
||||
onClose: args.onClose,
|
||||
onClick: args.onClick,
|
||||
key: args.key,
|
||||
style: args.style || {},
|
||||
className: args.className,
|
||||
});
|
||||
},
|
||||
);
|
||||
return {
|
||||
content: (
|
||||
<div className={iconNode ? `${prefixCls}-with-icon` : ''}>
|
||||
{iconNode}
|
||||
<div className={`${prefixCls}-message`}>
|
||||
{autoMarginTag}
|
||||
{args.message}
|
||||
</div>
|
||||
<div className={`${prefixCls}-description`}>{args.description}</div>
|
||||
{args.btn ? <span className={`${prefixCls}-btn`}>{args.btn}</span> : null}
|
||||
</div>
|
||||
),
|
||||
duration,
|
||||
closable: true,
|
||||
onClose: args.onClose,
|
||||
onClick: args.onClick,
|
||||
key: args.key,
|
||||
style: args.style || {},
|
||||
className: args.className,
|
||||
};
|
||||
}
|
||||
|
||||
const api: any = {
|
||||
open: notice,
|
||||
open: (args: ArgsProps) => {
|
||||
getNotificationInstance(args, ({ prefixCls, instance }) => {
|
||||
instance.notice(getRCNoticeProps(args, prefixCls));
|
||||
});
|
||||
},
|
||||
close(key: string) {
|
||||
Object.keys(notificationInstance).forEach(cacheKey =>
|
||||
notificationInstance[cacheKey].removeNotice(key),
|
||||
@ -244,17 +231,24 @@ const api: any = {
|
||||
});
|
||||
|
||||
api.warn = api.warning;
|
||||
api.useNotification = createUseNotification(getNotificationInstance, getRCNoticeProps);
|
||||
|
||||
export interface NotificationApi {
|
||||
export interface NotificationInstance {
|
||||
success(args: ArgsProps): void;
|
||||
error(args: ArgsProps): void;
|
||||
info(args: ArgsProps): void;
|
||||
warn(args: ArgsProps): void;
|
||||
warning(args: ArgsProps): void;
|
||||
open(args: ArgsProps): void;
|
||||
}
|
||||
|
||||
export interface NotificationApi extends NotificationInstance {
|
||||
warn(args: ArgsProps): void;
|
||||
close(key: string): void;
|
||||
config(options: ConfigProps): void;
|
||||
destroy(): void;
|
||||
|
||||
// Hooks
|
||||
useNotification: () => [NotificationInstance, React.ReactElement];
|
||||
}
|
||||
|
||||
export default api as NotificationApi;
|
||||
|
@ -67,3 +67,26 @@ notification.config({
|
||||
| getContainer | 配置渲染节点的输出位置 | () => HTMLNode | () => document.body |
|
||||
| placement | 弹出位置,可选 `topLeft` `topRight` `bottomLeft` `bottomRight` | string | topRight |
|
||||
| top | 消息从顶部弹出时,距离顶部的位置,单位像素。 | number | 24 |
|
||||
|
||||
## FAQ
|
||||
|
||||
### hooks 和直接调用的区别是什么?
|
||||
|
||||
直接调用 notification 方法,antd 会通过 `ReactDOM.render` 动态创建新的 React 实体。其 context 与当前代码所在 context 并不相同,因而无法获取 context 信息。
|
||||
|
||||
当你需要 context 信息(例如 ConfigProvider 配置的内容)时,可以通过 `notification.useNotification` 方法会返回 `api` 实体以及 `contextHolder` 节点。将其插入到你需要获取 context 位置即可:
|
||||
|
||||
```tsx
|
||||
const [api, contextHolder] = notification.useNotification();
|
||||
|
||||
return (
|
||||
<Context1.Provider value="Ant">
|
||||
{contextHolder}
|
||||
<Context2.Provider value="Design">
|
||||
{/* contextHolder 在 Context2 外,因而不会获得此 context */}
|
||||
</Context2.Provider>
|
||||
</Context1.Provider>
|
||||
);
|
||||
```
|
||||
|
||||
**异同:**通过 hooks 创建的 `contextHolder` 必须插入到子元素节点中才会生效,当你不需要上下文信息时请直接调用。
|
||||
|
@ -33,15 +33,24 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&-hook-holder,
|
||||
&-notice {
|
||||
position: relative;
|
||||
margin-bottom: @notification-margin-bottom;
|
||||
padding: @notification-padding;
|
||||
overflow: hidden;
|
||||
line-height: 1.5;
|
||||
background: @notification-bg;
|
||||
border-radius: @border-radius-base;
|
||||
box-shadow: @shadow-2;
|
||||
}
|
||||
|
||||
&-hook-holder > &-notice {
|
||||
margin-bottom: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&-notice {
|
||||
padding: @notification-padding;
|
||||
line-height: @line-height-base;
|
||||
|
||||
&-message {
|
||||
display: inline-block;
|
||||
|
@ -7,17 +7,23 @@ const { Column, ColumnGroup } = Table;
|
||||
|
||||
describe('Table.typescript', () => {
|
||||
it('Column', () => {
|
||||
<Table>
|
||||
<Column dataIndex="test" title="test" sorter />
|
||||
</Table>;
|
||||
const table = (
|
||||
<Table>
|
||||
<Column dataIndex="test" title="test" sorter />
|
||||
</Table>
|
||||
);
|
||||
expect(table).toBeTruthy();
|
||||
});
|
||||
it('ColumnGroup', () => {
|
||||
<Table>
|
||||
<Column dataIndex="test" title="test" sorter />
|
||||
<ColumnGroup>
|
||||
const table = (
|
||||
<Table>
|
||||
<Column dataIndex="test" title="test" sorter />
|
||||
</ColumnGroup>
|
||||
</Table>;
|
||||
<ColumnGroup>
|
||||
<Column dataIndex="test" title="test" sorter />
|
||||
</ColumnGroup>
|
||||
</Table>
|
||||
);
|
||||
expect(table).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -112,7 +112,7 @@
|
||||
"rc-input-number": "~4.5.0",
|
||||
"rc-mentions": "~1.0.0-alpha.3",
|
||||
"rc-menu": "~8.0.0-alpha.7",
|
||||
"rc-notification": "~3.3.1",
|
||||
"rc-notification": "~4.0.0-rc.1",
|
||||
"rc-pagination": "~1.20.13",
|
||||
"rc-picker": "^0.0.1-rc.5",
|
||||
"rc-progress": "~2.5.0",
|
||||
@ -156,8 +156,8 @@
|
||||
"@types/react-dom": "^16.8.4",
|
||||
"@types/shallowequal": "^1.1.1",
|
||||
"@types/warning": "^3.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.0.0",
|
||||
"@typescript-eslint/parser": "^2.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.19.0",
|
||||
"@typescript-eslint/parser": "^2.19.0",
|
||||
"antd-pro-merge-less": "^2.0.28",
|
||||
"antd-theme-generator": "^1.1.6",
|
||||
"babel-eslint": "^10.0.1",
|
||||
@ -178,7 +178,7 @@
|
||||
"enzyme": "^3.10.0",
|
||||
"enzyme-adapter-react-16": "^1.14.0",
|
||||
"enzyme-to-json": "^3.3.5",
|
||||
"eslint": "^6.1.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-airbnb": "^18.0.0",
|
||||
"eslint-config-prettier": "^6.0.0",
|
||||
"eslint-plugin-babel": "^5.3.0",
|
||||
|
2
typings/custom-typings.d.ts
vendored
2
typings/custom-typings.d.ts
vendored
@ -34,8 +34,6 @@ declare module 'rc-input-number';
|
||||
|
||||
declare module 'rc-collapse';
|
||||
|
||||
declare module 'rc-notification';
|
||||
|
||||
declare module 'rc-dialog';
|
||||
|
||||
declare module 'rc-rate';
|
||||
|
Loading…
Reference in New Issue
Block a user