refactor: notification

refactor-notification
tangjinzhou 2021-12-30 16:00:10 +08:00
parent f92e75a1d9
commit 842dde0706
9 changed files with 130 additions and 36 deletions

View File

@ -114,6 +114,7 @@ export { default as Modal } from './modal';
export type { StatisticProps } from './statistic'; export type { StatisticProps } from './statistic';
export { default as Statistic, StatisticCountdown } from './statistic'; export { default as Statistic, StatisticCountdown } from './statistic';
export type { NotificationPlacement } from './notification';
export { default as notification } from './notification'; export { default as notification } from './notification';
export type { PageHeaderProps } from './page-header'; export type { PageHeaderProps } from './page-header';

View File

@ -9,6 +9,8 @@ import type { TransformCellTextProps } from '../table/interface';
import LocaleReceiver from '../locale-provider/LocaleReceiver'; import LocaleReceiver from '../locale-provider/LocaleReceiver';
import type { RequiredMark } from '../form/Form'; import type { RequiredMark } from '../form/Form';
import type { MaybeRef } from '../_util/type'; import type { MaybeRef } from '../_util/type';
import message from '../message';
import notification from '../notification';
export type SizeType = 'small' | 'middle' | 'large' | undefined; export type SizeType = 'small' | 'middle' | 'large' | undefined;
@ -248,6 +250,17 @@ const ConfigProvider = defineComponent({
); );
}; };
watchEffect(() => {
if (props.direction) {
message.config({
rtl: props.direction === 'rtl',
});
notification.config({
rtl: props.direction === 'rtl',
});
}
});
return () => ( return () => (
<LocaleReceiver children={(_, __, legacyLocale) => renderProvider(legacyLocale as Locale)} /> <LocaleReceiver children={(_, __, legacyLocale) => renderProvider(legacyLocale as Locale)} />
); );

View File

@ -31,7 +31,7 @@ export default defineComponent({
message: 'Notification Title', message: 'Notification Title',
description: description:
'This is the content of the notification. This is the content of the notification. This is the content of the notification.', 'This is the content of the notification. This is the content of the notification. This is the content of the notification.',
icon: h(SmileOutlined, { style: 'color: #108ee9' }), icon: () => h(SmileOutlined, { style: 'color: #108ee9' }),
}); });
}; };

View File

@ -46,6 +46,7 @@ import {
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { notification } from 'ant-design-vue'; import { notification } from 'ant-design-vue';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import type { NotificationPlacement } from 'ant-design-vue';
export default defineComponent({ export default defineComponent({
components: { components: {
RadiusUpleftOutlined, RadiusUpleftOutlined,
@ -54,7 +55,7 @@ export default defineComponent({
RadiusBottomrightOutlined, RadiusBottomrightOutlined,
}, },
setup() { setup() {
const openNotification = (placement: string) => { const openNotification = (placement: NotificationPlacement) => {
notification.open({ notification.open({
message: `Notification ${placement}`, message: `Notification ${placement}`,
description: description:

View File

@ -8,20 +8,27 @@ title:
## zh-CN ## zh-CN
可以通过唯一的 key 来更新内容 可以通过唯一的 key 来更新内容, 或者通过响应式数据更新
## en-US ## en-US
Update content with unique key. Update content with unique key, or use reactive data.
</docs> </docs>
<template> <template>
<a-button type="primary" @click="openNotification">Open the notification box</a-button> <a-button type="primary" @click="openNotification">
Open the notification box (update by key)
</a-button>
<br />
<br />
<a-button type="primary" @click="openNotification2">
Open the notification box (update by reactive)
</a-button>
</template> </template>
<script lang="ts"> <script lang="ts">
import { notification } from 'ant-design-vue'; import { notification } from 'ant-design-vue';
import { defineComponent } from 'vue'; import { defineComponent, ref } from 'vue';
const key = 'updatable'; const key = 'updatable';
export default defineComponent({ export default defineComponent({
setup() { setup() {
@ -39,8 +46,22 @@ export default defineComponent({
}); });
}, 1000); }, 1000);
}; };
const message = ref('Notification Title');
const description = ref('description');
const openNotification2 = () => {
// content must use function
notification.open({
message: () => message.value,
description: () => description.value,
});
setTimeout(() => {
message.value = 'New Title';
description.value = 'New description.';
}, 1000);
};
return { return {
openNotification, openNotification,
openNotification2,
}; };
}, },
}); });

View File

@ -31,30 +31,35 @@ The properties of config are as follows:
| Property | Description | Type | Default | Version | | Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| bottom | Distance from the bottom of the viewport, when `placement` is `bottomRight` or `bottomLeft` (unit: pixels). | string | `24px` | | | bottom | Distance from the bottom of the viewport, when `placement` is `bottomRight` or `bottomLeft` (unit: pixels). | string | `24px` | |
| btn | Customized close button | VNode | - | | | btn | Customized close button | VNode \| () => VNode | - | |
| class | Customized CSS class | string | - | | | class | Customized CSS class | string | - | |
| description | The content of notification box (required) | string\| VNode | - | | | description | The content of notification box (required) | string\| VNode \| () => VNode | - | |
| duration | Time in seconds before Notification is closed. When set to 0 or null, it will never be closed automatically | number | 4.5 | | | 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 | | | getContainer | Return the mount node for Notification | () => HTMLNode | () => document.body | |
| icon | Customized icon | VNode | - | | | icon | Customized icon | VNode \| () => VNode | - | |
| key | The unique identifier of the Notification | string | - | | | key | The unique identifier of the Notification | string | - | |
| message | The title of notification box (required) | string\|VNode | - | | | message | The title of notification box (required) | string\| VNode \| () => VNode | - | |
| placement | Position of Notification, can be one of `topLeft` `topRight` `bottomLeft` `bottomRight` | string | `topRight` | | | placement | Position of Notification, can be one of `topLeft` `topRight` `bottomLeft` `bottomRight` | string | `topRight` | |
| style | Customized inline style | Object \| string | - | | | style | Customized inline style | Object \| string | - | |
| onClose | Specify a function that will be called when the close button is clicked | Function | - | | | 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 | - | | | onClick | Specify a function that will be called when the notification is clicked | Function | - | |
| top | Distance from the top of the viewport, when `placement` is `topRight` or `topLeft` (unit: pixels). | string | `24px` | | | top | Distance from the top of the viewport, when `placement` is `topRight` or `topLeft` (unit: pixels). | string | `24px` | |
| closeIcon | custom close icon | VNode | - | | | closeIcon | custom close icon | VNode \| () => VNode | - | |
`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. `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.
- `notification.config(options)` - `notification.config(options)`
> When you use `ConfigProvider` for global configuration, the system will automatically start RTL mode by default.(4.3.0+)
>
> When you want to use it alone, you can start the RTL mode through the following settings.
```js ```js
notification.config({ notification.config({
placement: 'bottomRight', placement: 'bottomRight',
bottom: '50px', bottom: '50px',
duration: 3, duration: 3,
rtl: true,
}); });
``` ```
@ -64,5 +69,7 @@ notification.config({
| duration | Time in seconds before Notification is closed. When set to 0 or null, it will never be closed automatically | number | 4.5 | | | 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 | | | getContainer | Return the mount node for Notification | () => HTMLNode | () => document.body | |
| placement | Position of Notification, can be one of `topLeft` `topRight` `bottomLeft` `bottomRight` | string | `topRight` | | | placement | Position of Notification, can be one of `topLeft` `topRight` `bottomLeft` `bottomRight` | string | `topRight` | |
| rtl | Whether to enable RTL mode | boolean | false | |
| top | Distance from the top of the viewport, when `placement` is `topRight` or `topLeft` (unit: pixels). | string | `24px` | | | top | Distance from the top of the viewport, when `placement` is `topRight` or `topLeft` (unit: pixels). | string | `24px` | |
| closeIcon | custom close icon | VNode | - | | | closeIcon | custom close icon | VNode \| () => VNode | - | |
| maxCount | Max Notification show, drop oldest if exceed limit | number | - | 3.0 |

View File

@ -8,6 +8,8 @@ import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import type { VueNode } from '../_util/type'; import type { VueNode } from '../_util/type';
import { renderHelper } from '../_util/util'; import { renderHelper } from '../_util/util';
import { globalConfig } from '../config-provider'; import { globalConfig } from '../config-provider';
import type { NotificationInstance as VCNotificationInstance } from '../vc-notification/Notification';
import classNames from '../_util/classNames';
export type NotificationPlacement = 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'; export type NotificationPlacement = 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight';
@ -21,9 +23,11 @@ export interface ConfigProps {
placement?: NotificationPlacement; placement?: NotificationPlacement;
getContainer?: () => HTMLElement; getContainer?: () => HTMLElement;
closeIcon?: VueNode | (() => VueNode); closeIcon?: VueNode | (() => VueNode);
rtl?: boolean;
maxCount?: number;
} }
const notificationInstance: { [key: string]: any } = {}; const notificationInstance: { [key: string]: VCNotificationInstance } = {};
let defaultDuration = 4.5; let defaultDuration = 4.5;
let defaultTop = '24px'; let defaultTop = '24px';
let defaultBottom = '24px'; let defaultBottom = '24px';
@ -31,6 +35,8 @@ let defaultPrefixCls = '';
let defaultPlacement: NotificationPlacement = 'topRight'; let defaultPlacement: NotificationPlacement = 'topRight';
let defaultGetContainer = () => document.body; let defaultGetContainer = () => document.body;
let defaultCloseIcon = null; let defaultCloseIcon = null;
let rtl = false;
let maxCount: number;
function setNotificationConfig(options: ConfigProps) { function setNotificationConfig(options: ConfigProps) {
const { duration, placement, bottom, top, getContainer, closeIcon, prefixCls } = options; const { duration, placement, bottom, top, getContainer, closeIcon, prefixCls } = options;
@ -55,6 +61,12 @@ function setNotificationConfig(options: ConfigProps) {
if (closeIcon !== undefined) { if (closeIcon !== undefined) {
defaultCloseIcon = closeIcon; defaultCloseIcon = closeIcon;
} }
if (options.rtl !== undefined) {
rtl = options.rtl;
}
if (options.maxCount !== undefined) {
maxCount = options.maxCount;
}
} }
function getPlacementStyle( function getPlacementStyle(
@ -62,7 +74,7 @@ function getPlacementStyle(
top: string = defaultTop, top: string = defaultTop,
bottom: string = defaultBottom, bottom: string = defaultBottom,
) { ) {
let style; let style: CSSProperties;
switch (placement) { switch (placement) {
case 'topLeft': case 'topLeft':
style = { style = {
@ -106,20 +118,28 @@ function getNotificationInstance(
closeIcon = defaultCloseIcon, closeIcon = defaultCloseIcon,
appContext, appContext,
}: NotificationArgsProps, }: NotificationArgsProps,
callback: (n: any) => void, callback: (n: VCNotificationInstance) => void,
) { ) {
const { getPrefixCls } = globalConfig(); const { getPrefixCls } = globalConfig();
const prefixCls = getPrefixCls('notification', customizePrefixCls || defaultPrefixCls); const prefixCls = getPrefixCls('notification', customizePrefixCls || defaultPrefixCls);
const cacheKey = `${prefixCls}-${placement}`; const cacheKey = `${prefixCls}-${placement}-${rtl}`;
if (notificationInstance[cacheKey]) { const cacheInstance = notificationInstance[cacheKey];
callback(notificationInstance[cacheKey]); if (cacheInstance) {
Promise.resolve(cacheInstance).then(instance => {
callback(instance);
});
return; return;
} }
const notificationClass = classNames(`${prefixCls}-${placement}`, {
[`${prefixCls}-rtl`]: rtl === true,
});
Notification.newInstance( Notification.newInstance(
{ {
name: 'notification', name: 'notification',
prefixCls: customizePrefixCls || defaultPrefixCls, prefixCls: customizePrefixCls || defaultPrefixCls,
class: `${prefixCls}-${placement}`, class: notificationClass,
style: getPlacementStyle(placement, top, bottom), style: getPlacementStyle(placement, top, bottom),
appContext, appContext,
getContainer, getContainer,
@ -131,6 +151,8 @@ function getNotificationInstance(
); );
return closeIconToRender; return closeIconToRender;
}, },
maxCount,
hasTransitionName: true,
}, },
(notification: any) => { (notification: any) => {
notificationInstance[cacheKey] = notification; notificationInstance[cacheKey] = notification;
@ -206,25 +228,26 @@ function notice(args: NotificationArgsProps) {
}); });
} }
const apiBase = { const api: any = {
open: notice, open: notice,
close(key: string) { close(key: string) {
Object.keys(notificationInstance).forEach(cacheKey => Object.keys(notificationInstance).forEach(cacheKey =>
notificationInstance[cacheKey].removeNotice(key), Promise.resolve(notificationInstance[cacheKey]).then(instance => {
instance.removeNotice(key);
}),
); );
}, },
config: setNotificationConfig, config: setNotificationConfig,
destroy() { destroy() {
Object.keys(notificationInstance).forEach(cacheKey => { Object.keys(notificationInstance).forEach(cacheKey => {
notificationInstance[cacheKey].destroy(); Promise.resolve(notificationInstance[cacheKey]).then(instance => {
delete notificationInstance[cacheKey]; instance.destroy();
});
delete notificationInstance[cacheKey]; // lgtm[js/missing-await]
}); });
}, },
}; };
type NotificationApi = typeof apiBase &
Record<IconType | 'warn', (args: Omit<NotificationArgsProps, 'type'>) => void>;
const api = apiBase as any as NotificationApi;
const iconTypes: IconType[] = ['success', 'info', 'warning', 'error']; const iconTypes: IconType[] = ['success', 'info', 'warning', 'error'];
iconTypes.forEach(type => { iconTypes.forEach(type => {
api[type] = args => api[type] = args =>
@ -235,4 +258,24 @@ iconTypes.forEach(type => {
}); });
api.warn = api.warning; api.warn = api.warning;
export default api;
export interface NotificationInstance {
success(args: NotificationArgsProps): void;
error(args: NotificationArgsProps): void;
info(args: NotificationArgsProps): void;
warning(args: NotificationArgsProps): void;
open(args: NotificationArgsProps): void;
}
export interface NotificationApi extends NotificationInstance {
warn(args: NotificationArgsProps): void;
close(key: string): void;
config(options: ConfigProps): void;
destroy(): void;
}
/** @private test Only function. Not work on production */
export const getInstance = async (cacheKey: string) =>
process.env.NODE_ENV === 'test' ? notificationInstance[cacheKey] : null;
export default api as NotificationApi;

View File

@ -31,31 +31,36 @@ config 参数如下:
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| btn | 自定义关闭按钮 | VNode | - | | | btn | 自定义关闭按钮 | VNode \| () => VNode | - | |
| bottom | 消息从底部弹出时,距离底部的位置,单位像素。 | string | `24px` | | | bottom | 消息从底部弹出时,距离底部的位置,单位像素。 | string | `24px` | |
| class | 自定义 CSS class | string | - | | | class | 自定义 CSS class | string | - | |
| description | 通知提醒内容,必选 | string \|VNode | - | | | description | 通知提醒内容,必选 | string \| VNode \| () => VNode | - | |
| duration | 默认 4.5 秒后自动关闭,配置为 null 则不自动关闭 | number | 4.5 | | | duration | 默认 4.5 秒后自动关闭,配置为 null 则不自动关闭 | number | 4.5 | |
| getContainer | 配置渲染节点的输出位置 | () => HTMLNode | () => document.body | | | getContainer | 配置渲染节点的输出位置 | () => HTMLNode | () => document.body | |
| icon | 自定义图标 | VNode | - | | | icon | 自定义图标 | VNode \| () => VNode | - | |
| key | 当前通知唯一标志 | string | - | | | key | 当前通知唯一标志 | string | - | |
| message | 通知提醒标题,必选 | string \|VNode | - | | | message | 通知提醒标题,必选 | string \| VNode \| () => VNode | - | |
| placement | 弹出位置,可选 `topLeft` `topRight` `bottomLeft` `bottomRight` | string | topRight | | | placement | 弹出位置,可选 `topLeft` `topRight` `bottomLeft` `bottomRight` | string | topRight | |
| style | 自定义内联样式 | Object \| string | - | | | style | 自定义内联样式 | Object \| string | - | |
| onClose | 点击默认关闭按钮时触发的回调函数 | Function | - | | | onClose | 点击默认关闭按钮时触发的回调函数 | Function | - | |
| onClick | 点击通知时触发的回调函数 | Function | - | | | onClick | 点击通知时触发的回调函数 | Function | - | |
| top | 消息从顶部弹出时,距离顶部的位置,单位像素。 | string | `24px` | | | top | 消息从顶部弹出时,距离顶部的位置,单位像素。 | string | `24px` | |
| closeIcon | 自定义关闭图标 | VNode | - | | | closeIcon | 自定义关闭图标 | VNode \| () => VNode | - | |
还提供了一个全局配置方法,在调用前提前配置,全局一次生效。 还提供了一个全局配置方法,在调用前提前配置,全局一次生效。
- `notification.config(options)` - `notification.config(options)`
> 当你使用 `ConfigProvider` 进行全局化配置时,系统会默认自动开启 RTL 模式。(3.0+)
>
> 当你想单独使用,可通过如下设置开启 RTL 模式。
```js ```js
notification.config({ notification.config({
placement: 'bottomRight', placement: 'bottomRight',
bottom: '50px', bottom: '50px',
duration: 3, duration: 3,
rtl: true,
}); });
``` ```
@ -66,4 +71,6 @@ notification.config({
| getContainer | 配置渲染节点的输出位置 | () => HTMLNode | () => document.body | | | getContainer | 配置渲染节点的输出位置 | () => HTMLNode | () => document.body | |
| placement | 弹出位置,可选 `topLeft` `topRight` `bottomLeft` `bottomRight` | string | topRight | | | placement | 弹出位置,可选 `topLeft` `topRight` `bottomLeft` `bottomRight` | string | topRight | |
| top | 消息从顶部弹出时,距离顶部的位置,单位像素。 | string | `24px` | | | top | 消息从顶部弹出时,距离顶部的位置,单位像素。 | string | `24px` | |
| closeIcon | 自定义关闭图标 | VNode | - | | | closeIcon | 自定义关闭图标 | VNode \| () => VNode | - | |
| rtl | 是否开启 RTL 模式 | boolean | false | 3.0 |
| maxCount | 最大显示数, 超过限制时,最早的消息会被自动关闭 | number | - | 3.0 |

View File

@ -65,6 +65,7 @@ type NotificationState = {
const Notification = defineComponent<NotificationProps>({ const Notification = defineComponent<NotificationProps>({
name: 'Notification', name: 'Notification',
inheritAttrs: false,
props: ['prefixCls', 'transitionName', 'animation', 'maxCount', 'closeIcon'] as any, props: ['prefixCls', 'transitionName', 'animation', 'maxCount', 'closeIcon'] as any,
setup(props, { attrs, expose, slots }) { setup(props, { attrs, expose, slots }) {
const hookRefs = new Map<Key, HTMLDivElement>(); const hookRefs = new Map<Key, HTMLDivElement>();