Browse Source

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
pull/21284/head
二货机器人 5 years ago
committed by GitHub
parent
commit
3080611883
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .eslintrc.js
  2. 14
      components/form/__tests__/type.test.tsx
  3. 121
      components/notification/__tests__/__snapshots__/demo.test.js.snap
  4. 53
      components/notification/__tests__/hooks.test.js
  5. 63
      components/notification/demo/hooks.md
  6. 72
      components/notification/hooks/useNotification.tsx
  7. 23
      components/notification/index.en-US.md
  8. 124
      components/notification/index.tsx
  9. 23
      components/notification/index.zh-CN.md
  10. 13
      components/notification/style/index.less
  11. 22
      components/table/__tests__/type.test.tsx
  12. 8
      package.json
  13. 2
      typings/custom-typings.d.ts

2
.eslintrc.js

@ -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,
},
},
],

14
components/form/__tests__/type.test.tsx

@ -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();
});
});

121
components/notification/__tests__/__snapshots__/demo.test.js.snap

@ -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

@ -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

@ -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

@ -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;
}

23
components/notification/index.en-US.md

@ -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.

124
components/notification/index.tsx

@ -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;

23
components/notification/index.zh-CN.md

@ -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` 必须插入到子元素节点中才会生效,当你不需要上下文信息时请直接调用。

13
components/notification/style/index.less

@ -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;

22
components/table/__tests__/type.test.tsx

@ -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();
});
});

8
package.json

@ -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

@ -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…
Cancel
Save