From aadf623dc6d39354345c5aee5122eb4940810e1c Mon Sep 17 00:00:00 2001 From: Rex Date: Fri, 27 Jan 2017 17:12:25 +0800 Subject: [PATCH] support notification pop up from topLeft or bottomRight or bottomLeft (#4700) * update snapshots * update snapshots * support notification pop up from topLeft or bottomRight or bottomLeft * update snapshots * 1. add test 2. update doc 3. support `placement` arguments in `open` method * update doc * update doc. --- .../__tests__/Notification.placement.test.js | 121 ++++++++++++++++++ .../__tests__/__snapshots__/demo.test.js.snap | 40 ++++++ components/notification/demo/placement.md | 49 +++++++ components/notification/index.en-US.md | 16 ++- components/notification/index.tsx | 74 +++++++++-- components/notification/index.zh-CN.md | 14 +- components/notification/style/index.less | 22 ++++ 7 files changed, 315 insertions(+), 21 deletions(-) create mode 100644 components/notification/__tests__/Notification.placement.test.js create mode 100755 components/notification/demo/placement.md diff --git a/components/notification/__tests__/Notification.placement.test.js b/components/notification/__tests__/Notification.placement.test.js new file mode 100644 index 0000000000..c62aa7054f --- /dev/null +++ b/components/notification/__tests__/Notification.placement.test.js @@ -0,0 +1,121 @@ +import notification from '..'; + +describe('Notification.placement', () => { + function $$(className) { + return document.body.querySelectorAll(className); + } + + function getStyle(el, prop, getComputedStyle, style) { + getComputedStyle = window.getComputedStyle; + style = getComputedStyle ? getComputedStyle(el) : el.currentStyle; + + // If a css property's value is `auto`, it will return an empty string. + return prop ? style[prop] : style; + } + + function open(args) { + notification.open({ + message: 'Notification Title', + description: 'This is the content of the notification.', + ...args, + }); + } + + function config(args) { + notification.config({ + ...args, + }); + open(); + } + + it('change notification placement by `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(''); + + + // topRight + open({ + placement: 'topRight', + }); + style = getStyle($$('.ant-notification-topRight')[0]); + expect(style.top).toBe(defaultTop); + expect(style.right).toBe('0px'); + expect(style.bottom).toBe(''); + + // bottomRight + open({ + placement: 'bottomRight', + }); + style = getStyle($$('.ant-notification-bottomRight')[0]); + expect(style.top).toBe(''); + expect(style.right).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); + }); + + it('change notification placement by `config` method', () => { + let style; + + // topLeft + config({ + placement: 'topLeft', + top: 50, + bottom: 50, + }); + style = getStyle($$('.ant-notification-topLeft')[1]); + 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')[1]); + 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')[1]); + 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')[1]); + expect(style.top).toBe(''); + expect(style.left).toBe('0px'); + expect(style.bottom).toBe('50px'); + }); +}); diff --git a/components/notification/__tests__/__snapshots__/demo.test.js.snap b/components/notification/__tests__/__snapshots__/demo.test.js.snap index a428230e9e..ab603c0d13 100644 --- a/components/notification/__tests__/__snapshots__/demo.test.js.snap +++ b/components/notification/__tests__/__snapshots__/demo.test.js.snap @@ -28,6 +28,46 @@ exports[`test renders ./components/notification/demo/duration.md correctly 1`] = `; +exports[`test renders ./components/notification/demo/placement.md correctly 1`] = ` +
+
+ +
+ +
+`; + exports[`test renders ./components/notification/demo/with-btn.md correctly 1`] = ` + +, mountNode); +```` diff --git a/components/notification/index.en-US.md b/components/notification/index.en-US.md index 64df249b71..5b860860cb 100644 --- a/components/notification/index.en-US.md +++ b/components/notification/index.en-US.md @@ -8,7 +8,7 @@ title: Notification To display a notification message globally. ## When To Use -To display a notification message at the top right of the view port. Typically it can be +To display a notification message at the four corner of the view port. Typically it can be used in the following cases: - A notification with complex content. @@ -36,22 +36,26 @@ The properties of config are as follows: | icon | Customized icon | React.Node | _ | | key | The unique identifier of current notification | String | - | | onClose | Specify a function that will be called after clicking the default close button | Function | - | -| duration | A notification box is closed after 4.5s by default. When specifying `duration` to null or 0, it will never be closed automatically | Number | 4.5 | +| duration | A notification box is closed after 4.5s by default. When specifying `duration` to null or 0, it will never be closed automatically | number | 4.5 | +| placement | To set the position, which can be one of `topLeft` `topRight` `bottomLeft` `bottomRight` | string | topRight | `notification` also provide 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 before displaying. - `notification.config(options)` - ```js notification.config({ - top: 100, + placement: 'bottomRight', + bottom: 50, duration: 3, }); ``` | Property | Description | Type | Default | |------------|--------------------|----------------------------|--------------| -| top | Offset to top of message | Number | 24px | -| duration | A duration to close notification automatically by default (unit: second) | Number | 4.5 | +| placement | To set the position, which can be one of `topLeft` `topRight` `bottomLeft` `bottomRight` | string | topRight | +| top | Offset to top, when message pop up from `topRight` or `topLeft` (unit: pixels). | number | 24 | +| bottom | Offset to bottom, when message pop up from `bottomRight` or `bottomLeft` (unit: pixels). | number | 24 | +| duration | A duration to close notification automatically by default (unit: second) | number | 4.5 | + diff --git a/components/notification/index.tsx b/components/notification/index.tsx index 2849d9868d..6226ec98e8 100755 --- a/components/notification/index.tsx +++ b/components/notification/index.tsx @@ -2,9 +2,47 @@ import React from 'react'; import Notification from 'rc-notification'; import Icon from '../icon'; import assign from 'object-assign'; -let defaultTop = 24; let notificationInstance; let defaultDuration = 4.5; +let defaultTop = 24; +let defaultBottom = 24; +let defaultPlacement = 'topRight'; + +export type notificationPlacement = 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'; + +function getPlacementStyle(placement) { + let style; + switch (placement) { + case 'topLeft': + style = { + left: 0, + top: defaultTop, + bottom: 'auto', + }; + break; + case 'bottomLeft': + style = { + left: 0, + top: 'auto', + bottom: defaultBottom, + }; + break; + case 'bottomRight': + style = { + right: 0, + top: 'auto', + bottom: defaultBottom, + }; + break; + default: + style = { + right: 0, + top: defaultTop, + bottom: 'auto', + }; + } + return style; +} export interface ArgsProps { message: React.ReactNode | string; @@ -14,11 +52,14 @@ export interface ArgsProps { onClose?: () => void; duration?: number; icon?: React.ReactNode; + placement?: notificationPlacement; } export interface ConfigProps { top?: number; + bottom?: number; duration?: number; + placement?: notificationPlacement; } function getNotificationInstance(prefixCls) { @@ -27,10 +68,8 @@ function getNotificationInstance(prefixCls) { } notificationInstance = (Notification as any).newInstance({ prefixCls: prefixCls, - style: { - top: defaultTop, - right: 0, - }, + className: `${prefixCls}-${defaultPlacement}`, + style: getPlacementStyle(defaultPlacement), }); return notificationInstance; } @@ -39,6 +78,11 @@ function notice(args) { const outerPrefixCls = args.prefixCls || 'ant-notification'; const prefixCls = `${outerPrefixCls}-notice`; + if (args.placement !== undefined) { + defaultPlacement = args.placement; + notificationInstance = null; // delete notificationInstance for new defaultPlacement + } + let duration; if (args.duration === undefined) { duration = defaultDuration; @@ -113,12 +157,22 @@ const api: { } }, config(options: ConfigProps) { - if (options.top !== undefined) { - defaultTop = options.top; - notificationInstance = null; // delete notificationInstance for new defaultTop + const { duration, placement, bottom, top } = options; + if (placement !== undefined) { + defaultPlacement = placement; } - if (options.duration !== undefined) { - defaultDuration = options.duration; + if (bottom !== undefined) { + defaultBottom = bottom; + } + if (top !== undefined) { + defaultTop = top; + } + // delete notificationInstance + if (placement !== undefined || bottom !== undefined || top !== undefined) { + notificationInstance = null; + } + if (duration !== undefined) { + defaultDuration = duration; } }, destroy() { diff --git a/components/notification/index.zh-CN.md b/components/notification/index.zh-CN.md index b578b825ef..6fa70fcf5b 100644 --- a/components/notification/index.zh-CN.md +++ b/components/notification/index.zh-CN.md @@ -10,7 +10,7 @@ subtitle: 通知提醒框 ## 何时使用 -在系统右上角显示通知提醒信息。经常用于以下情况: +在系统四个角显示通知提醒信息。经常用于以下情况: - 较为复杂的通知内容。 - 带有交互的通知,给出用户下一步的行动点。 @@ -36,7 +36,8 @@ config 参数如下: | icon | 自定义图标 | React.Node | - | | key | 当前通知唯一标志 | String | - | | onClose | 点击默认关闭按钮时触发的回调函数 | Function | - | -| duration | 默认 4.5 秒后自动关闭,配置为 null 则不自动关闭 | Number | 4.5 | +| duration | 默认 4.5 秒后自动关闭,配置为 null 则不自动关闭 | number | 4.5 | +| placement | 弹出位置,可选 `topLeft` `topRight` `bottomLeft` `bottomRight` | string | topRight | 还提供了一个全局配置方法,在调用前提前配置,全局一次生效。 @@ -44,12 +45,15 @@ config 参数如下: ```js notification.config({ - top: 100, + placement: 'bottomRight', + bottom: 50, duration: 3, }); ``` | 参数 | 说明 | 类型 | 默认值 | |------------|--------------------|----------------------------|--------------| -| top | 消息距离顶部的位置 | Number | 24px | -| duration | 默认自动关闭延时,单位秒 | Number | 4.5 | +| placement | 弹出位置,可选 `topLeft` `topRight` `bottomLeft` `bottomRight` | string | topRight | +| top | 消息从顶部弹出时,距离顶部的位置,单位像素。 | number | 24 | +| bottom | 消息从底部弹出时,距离底部的位置,单位像素。 | number | 24 | +| duration | 默认自动关闭延时,单位秒 | number | 4.5 | diff --git a/components/notification/style/index.less b/components/notification/style/index.less index 4e58edb825..ce3cac6d77 100644 --- a/components/notification/style/index.less +++ b/components/notification/style/index.less @@ -12,6 +12,17 @@ width: @notification-width; margin-right: 24px; + &-topLeft, + &-bottomLeft { + margin-left: 24px; + margin-right: 0; + + .@{notification-prefix-cls}-fade-enter.@{notification-prefix-cls}-fade-enter-active, + .@{notification-prefix-cls}-fade-appear.@{notification-prefix-cls}-fade-appear-active { + animation-name: NotificationLeftFadeIn; + } + } + &-notice { padding: @notification-padding; border-radius: @border-radius-base; @@ -133,6 +144,17 @@ } } +@keyframes NotificationLeftFadeIn { + 0% { + opacity: 0; + right: @notification-width; + } + 100% { + right: 0; + opacity: 1; + } +} + @keyframes NotificationFadeOut { 0% { opacity: 1;