From 163a21f2597bb2d53ec46cdd47f85b32fcfc8748 Mon Sep 17 00:00:00 2001 From: Benjamin Amelot Date: Tue, 16 Apr 2019 11:44:02 +0200 Subject: [PATCH] feat: Notification functions accept top, bottom and getContainer as arguments --- .../notification/__tests__/placement.test.js | 247 ++++++++++-------- components/notification/index.en-US.md | 7 +- components/notification/index.tsx | 49 +++- components/notification/index.zh-CN.md | 7 +- 4 files changed, 187 insertions(+), 123 deletions(-) diff --git a/components/notification/__tests__/placement.test.js b/components/notification/__tests__/placement.test.js index da7c8a9f2b..8d59eddb49 100644 --- a/components/notification/__tests__/placement.test.js +++ b/components/notification/__tests__/placement.test.js @@ -28,126 +28,157 @@ describe('Notification.placement', () => { open(); } - it('change notification placement by `open` method', () => { - const defaultTop = '24px'; - const defaultBottom = '24px'; - let style; + describe('placement', () => { + it('can be configured per notification using the `open` method', () => { + const defaultTop = '24px'; + const defaultBottom = '24px'; + let style; - // topLeft - open({ - placement: 'topLeft', - }); - style = getStyle($$('.ant-notification-topLeft')[0]); - expect(style.top).toBe(defaultTop); - expect(style.left).toBe('0px'); - expect(style.bottom).toBe(''); + // topLeft + open({ + placement: 'topLeft', + top: 50, + }); + style = getStyle($$('.ant-notification-topLeft')[0]); + expect(style.top).toBe('50px'); + expect(style.left).toBe('0px'); + expect(style.bottom).toBe(''); - open({ - placement: 'topLeft', - }); - expect($$('.ant-notification-topLeft').length).toBe(1); + open({ + placement: 'topLeft', + }); + expect($$('.ant-notification-topLeft').length).toBe(1); - // topRight - open({ - placement: 'topRight', - }); - style = getStyle($$('.ant-notification-topRight')[0]); - expect(style.top).toBe(defaultTop); - expect(style.right).toBe('0px'); - expect(style.bottom).toBe(''); + // topRight + open({ + placement: 'topRight', + }); + style = getStyle($$('.ant-notification-topRight')[0]); + expect(style.top).toBe(defaultTop); + expect(style.right).toBe('0px'); + expect(style.bottom).toBe(''); - open({ - placement: 'topRight', - }); - expect($$('.ant-notification-topRight').length).toBe(1); + open({ + placement: 'topRight', + }); + expect($$('.ant-notification-topRight').length).toBe(1); - // bottomRight - open({ - placement: 'bottomRight', - }); - style = getStyle($$('.ant-notification-bottomRight')[0]); - expect(style.top).toBe(''); - expect(style.right).toBe('0px'); - expect(style.bottom).toBe(defaultBottom); + // bottomRight + open({ + placement: 'bottomRight', + bottom: 100, + }); + style = getStyle($$('.ant-notification-bottomRight')[0]); + expect(style.top).toBe(''); + expect(style.right).toBe('0px'); + expect(style.bottom).toBe('100px'); - open({ - placement: 'bottomRight', - }); - expect($$('.ant-notification-bottomRight').length).toBe(1); + open({ + placement: 'bottomRight', + }); + expect($$('.ant-notification-bottomRight').length).toBe(1); - // bottomLeft - open({ - placement: 'bottomLeft', - }); - style = getStyle($$('.ant-notification-bottomLeft')[0]); - expect(style.top).toBe(''); - expect(style.left).toBe('0px'); - expect(style.bottom).toBe(defaultBottom); + // bottomLeft + open({ + placement: 'bottomLeft', + }); + style = getStyle($$('.ant-notification-bottomLeft')[0]); + expect(style.top).toBe(''); + expect(style.left).toBe('0px'); + expect(style.bottom).toBe(defaultBottom); - open({ - placement: 'bottomLeft', + open({ + placement: 'bottomLeft', + }); + expect($$('.ant-notification-bottomLeft').length).toBe(1); + }); + + it('can be configured globally using the `config` method', () => { + let style; + + // topLeft + config({ + placement: 'topLeft', + top: 50, + bottom: 50, + }); + style = getStyle($$('.ant-notification-topLeft')[0]); + expect(style.top).toBe('50px'); + expect(style.left).toBe('0px'); + expect(style.bottom).toBe(''); + + // topRight + config({ + placement: 'topRight', + top: 100, + bottom: 50, + }); + style = getStyle($$('.ant-notification-topRight')[0]); + expect(style.top).toBe('100px'); + expect(style.right).toBe('0px'); + expect(style.bottom).toBe(''); + + // bottomRight + config({ + placement: 'bottomRight', + top: 50, + bottom: 100, + }); + style = getStyle($$('.ant-notification-bottomRight')[0]); + expect(style.top).toBe(''); + expect(style.right).toBe('0px'); + expect(style.bottom).toBe('100px'); + + // bottomLeft + config({ + placement: 'bottomLeft', + top: 100, + bottom: 50, + }); + style = getStyle($$('.ant-notification-bottomLeft')[0]); + expect(style.top).toBe(''); + expect(style.left).toBe('0px'); + expect(style.bottom).toBe('50px'); }); - expect($$('.ant-notification-bottomLeft').length).toBe(1); }); - it('change notification placement by `config` method', () => { - let style; - - // topLeft - config({ - placement: 'topLeft', - top: 50, - bottom: 50, - }); - style = getStyle($$('.ant-notification-topLeft')[0]); - expect(style.top).toBe('50px'); - expect(style.left).toBe('0px'); - expect(style.bottom).toBe(''); - - // topRight - config({ - placement: 'topRight', - top: 100, - bottom: 50, - }); - style = getStyle($$('.ant-notification-topRight')[0]); - expect(style.top).toBe('100px'); - expect(style.right).toBe('0px'); - expect(style.bottom).toBe(''); - - // bottomRight - config({ - placement: 'bottomRight', - top: 50, - bottom: 100, - }); - style = getStyle($$('.ant-notification-bottomRight')[0]); - expect(style.top).toBe(''); - expect(style.right).toBe('0px'); - expect(style.bottom).toBe('100px'); - - // bottomLeft - config({ - placement: 'bottomLeft', - top: 100, - bottom: 50, - }); - style = getStyle($$('.ant-notification-bottomLeft')[0]); - expect(style.top).toBe(''); - expect(style.left).toBe('0px'); - expect(style.bottom).toBe('50px'); - }); - it('change notification mountNode by `config` method', () => { + describe('mountNode', () => { const $container = document.createElement('div'); - document.body.appendChild($container); - config({ - top: 50, - bottom: 100, - getContainer() { - return $container; - }, + beforeEach(() => { + document.body.appendChild($container); + }); + afterEach(() => { + $container.remove(); + }); + + it('can be configured per notification using the `open` method', () => { + open({ + getContainer: () => $container, + }); + expect($container.querySelector('.ant-notification')).not.toBe(null); + + notification.destroy(); + + setTimeout(() => { + // Upcoming notifications still use their default mountNode and not $container + open(); + expect($container.querySelector('.ant-notification')).toBe(null); + }); + }); + + it('can be configured globally using the `config` method', () => { + config({ + getContainer: () => $container, + }); + expect($container.querySelector('.ant-notification')).not.toBe(null); + + notification.destroy(); + + setTimeout(() => { + // Upcoming notifications are mounted in $container + open(); + expect($container.querySelector('.ant-notification')).not.toBe(null); + }); }); - expect($container.querySelector('.ant-notification')).not.toBe(null); - $container.remove(); }); }); diff --git a/components/notification/index.en-US.md b/components/notification/index.en-US.md index f7e74b1e20..38d494660f 100644 --- a/components/notification/index.en-US.md +++ b/components/notification/index.en-US.md @@ -32,17 +32,20 @@ The properties of config are as follows: | Property | Description | Type | Default | | -------- | ----------- | ---- | ------- | +| bottom | Distance from the bottom of the viewport, when `placement` is `bottomRight` or `bottomLeft` (unit: pixels). | number | 24 | | btn | Customized close button | ReactNode | - | | className | Customized CSS class | string | - | | description | The content of notification box (required) | string\|ReactNode | - | | duration | Time in seconds before Notification is closed. When set to 0 or null, it will never be closed automatically | number | 4.5 | +| getContainer | Return the mount node for Notification | () => HTMLNode | () => document.body | | icon | Customized icon | ReactNode | - | | key | The unique identifier of the Notification | string | - | | message | The title of notification box (required) | string\|ReactNode | - | -| placement | Position of Notification, can be one of `topLeft` `topRight` `bottomLeft` `bottomRight` | string | `topRight` | -| style | Customized inline style | [React.CSSProperties](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/e434515761b36830c3e58a970abf5186f005adac/types/react/index.d.ts#L794) | - | | onClose | Specify a function that will be called when the close button is clicked | Function | - | | onClick | Specify a function that will be called when the notification is clicked | Function | - | +| placement | Position of Notification, can be one of `topLeft` `topRight` `bottomLeft` `bottomRight` | string | `topRight` | +| style | Customized inline style | [React.CSSProperties](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/e434515761b36830c3e58a970abf5186f005adac/types/react/index.d.ts#L794) | - | +| top | Distance from the top of the viewport, when `placement` is `topRight` or `topLeft` (unit: pixels). | number | 24 | `notification` also provides a global `config()` method that can be used for specifying the default options. Once this method is used, all the notification boxes will take into account these globally defined options when displaying. diff --git a/components/notification/index.tsx b/components/notification/index.tsx index 9134f11cdb..a38c89dfa6 100755 --- a/components/notification/index.tsx +++ b/components/notification/index.tsx @@ -40,20 +40,24 @@ function setNotificationConfig(options: ConfigProps) { } } -function getPlacementStyle(placement: NotificationPlacement) { +function getPlacementStyle( + placement: NotificationPlacement, + top: number = defaultTop, + bottom: number = defaultBottom, +) { let style; switch (placement) { case 'topLeft': style = { left: 0, - top: defaultTop, + top, bottom: 'auto', }; break; case 'topRight': style = { right: 0, - top: defaultTop, + top, bottom: 'auto', }; break; @@ -61,23 +65,36 @@ function getPlacementStyle(placement: NotificationPlacement) { style = { left: 0, top: 'auto', - bottom: defaultBottom, + bottom, }; break; default: style = { right: 0, top: 'auto', - bottom: defaultBottom, + bottom, }; break; } return style; } +type NotificationInstanceProps = { + prefixCls: string; + placement?: NotificationPlacement; + getContainer?: () => HTMLElement; + top?: number; + bottom?: number; +}; + function getNotificationInstance( - prefixCls: string, - placement: NotificationPlacement, + { + prefixCls, + placement = defaultPlacement, + getContainer = defaultGetContainer, + top, + bottom, + }: NotificationInstanceProps, callback: (n: any) => void, ) { const cacheKey = `${prefixCls}-${placement}`; @@ -89,8 +106,8 @@ function getNotificationInstance( { prefixCls, className: `${prefixCls}-${placement}`, - style: getPlacementStyle(placement), - getContainer: defaultGetContainer, + style: getPlacementStyle(placement, top, bottom), + getContainer, closeIcon: , }, (notification: any) => { @@ -121,6 +138,9 @@ export interface ArgsProps { className?: string; readonly type?: IconType; onClick?: () => void; + top?: number; + bottom?: number; + getContainer?: () => HTMLElement; } function notice(args: ArgsProps) { @@ -143,9 +163,16 @@ function notice(args: ArgsProps) { ) : null; + const { placement, top, bottom, getContainer } = args; + getNotificationInstance( - outerPrefixCls, - args.placement || defaultPlacement, + { + prefixCls: outerPrefixCls, + placement, + top, + bottom, + getContainer, + }, (notification: any) => { notification.notice({ content: ( diff --git a/components/notification/index.zh-CN.md b/components/notification/index.zh-CN.md index fafb5b2b6c..5a6836cac6 100644 --- a/components/notification/index.zh-CN.md +++ b/components/notification/index.zh-CN.md @@ -32,16 +32,19 @@ config 参数如下: | 参数 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | | btn | 自定义关闭按钮 | ReactNode | - | +| bottom | 消息从底部弹出时,距离底部的位置,单位像素。 | number | 24 | | className | 自定义 CSS class | string | - | | description | 通知提醒内容,必选 | string\|ReactNode | - | | duration | 默认 4.5 秒后自动关闭,配置为 null 则不自动关闭 | number | 4.5 | +| getContainer | 配置渲染节点的输出位置 | () => HTMLNode | () => document.body | | icon | 自定义图标 | ReactNode | - | | key | 当前通知唯一标志 | string | - | | message | 通知提醒标题,必选 | string\|ReactNode | - | -| placement | 弹出位置,可选 `topLeft` `topRight` `bottomLeft` `bottomRight` | string | topRight | -| style | 自定义内联样式 | [React.CSSProperties](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/e434515761b36830c3e58a970abf5186f005adac/types/react/index.d.ts#L794) | - | | onClose | 点击默认关闭按钮时触发的回调函数 | Function | - | | onClick | 点击通知时触发的回调函数 | Function | - | +| placement | 弹出位置,可选 `topLeft` `topRight` `bottomLeft` `bottomRight` | string | topRight | +| style | 自定义内联样式 | [React.CSSProperties](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/e434515761b36830c3e58a970abf5186f005adac/types/react/index.d.ts#L794) | - | +| top | 消息从顶部弹出时,距离顶部的位置,单位像素。 | number | 24 | 还提供了一个全局配置方法,在调用前提前配置,全局一次生效。