Skip to content

Feat(use): add useMessage useNotification #6527

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions components/_util/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,24 @@ export function renderHelper<T = Record<string, any>>(
}
return v ?? defaultV;
}
export function wrapPromiseFn(openFn: (resolve: VoidFunction) => VoidFunction) {
let closeFn: VoidFunction;

const closePromise = new Promise<boolean>(resolve => {
closeFn = openFn(() => {
resolve(true);
});
});

const result: any = () => {
closeFn?.();
};

result.then = (filled: VoidFunction, rejected: VoidFunction) =>
closePromise.then(filled, rejected);
result.promise = closePromise;

return result;
}

export { isOn, cacheStringFunction, camelize, hyphenate, capitalize, resolvePropValue };
79 changes: 79 additions & 0 deletions components/message/PurePanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import Notice from '../vc-notification/Notice';
import type { NoticeProps } from '../vc-notification/Notice';
import useStyle from './style';
import type { NoticeType } from './interface';
import {
CheckCircleFilled,
CloseCircleFilled,
ExclamationCircleFilled,
InfoCircleFilled,
LoadingOutlined,
} from '@ant-design/icons-vue';
import type { VueNode } from '../_util/type';
import classNames from '../_util/classNames';
import { useConfigContextInject } from '../config-provider/context';
import { computed, defineComponent } from 'vue';

export const TypeIcon = {
info: <InfoCircleFilled />,
success: <CheckCircleFilled />,
error: <CloseCircleFilled />,
warning: <ExclamationCircleFilled />,
loading: <LoadingOutlined />,
};

export interface PureContentProps {
prefixCls: string;
type?: NoticeType;
icon?: VueNode;
children: VueNode;
}

export const PureContent = defineComponent({
name: 'PureContent',
inheritAttrs: false,
props: ['prefixCls', 'type', 'icon'] as any,

setup(props, { slots }) {
return () => (
<div
class={classNames(`${props.prefixCls}-custom-content`, `${props.prefixCls}-${props.type}`)}
>
{props.icon || TypeIcon[props.type!]}
<span>{slots.default?.()}</span>
</div>
);
},
});

export interface PurePanelProps
extends Omit<NoticeProps, 'prefixCls' | 'eventKey'>,
Omit<PureContentProps, 'prefixCls' | 'children'> {
prefixCls?: string;
}

/** @private Internal Component. Do not use in your production. */

export default defineComponent({
name: 'PurePanel',
inheritAttrs: false,
props: ['prefixCls', 'class', 'type', 'icon', 'content'] as any,
setup(props, { slots, attrs }) {
const { getPrefixCls } = useConfigContextInject();
const prefixCls = computed(() => props.staticPrefixCls || getPrefixCls('message'));
const [, hashId] = useStyle(prefixCls);
return (
<Notice
{...attrs}
prefixCls={prefixCls.value}
class={classNames(hashId, `${prefixCls.value}-notice-pure-panel`)}
noticeKey="pure"
duration={null}
>
<PureContent prefixCls={props.prefixCls} type={props.type} icon={props.icon}>
{slots.default?.()}
</PureContent>
</Notice>
);
},
});
8 changes: 8 additions & 0 deletions components/message/__tests__/__snapshots__/demo.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ exports[`renders ./components/message/demo/duration.vue correctly 1`] = `
</button>
`;

exports[`renders ./components/message/demo/hook.vue correctly 1`] = `
<!--teleport start-->
<!--teleport end-->
<button class="ant-btn ant-btn-primary" type="button">
<!----><span>Display normal message</span>
</button>
`;

exports[`renders ./components/message/demo/info.vue correctly 1`] = `
<button class="ant-btn ant-btn-primary" type="button">
<!----><span>Display normal message</span>
Expand Down
31 changes: 31 additions & 0 deletions components/message/demo/hook.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<docs>
---
order: 10
title:
zh-CN: Hooks 调用(推荐)
en-US: Hooks Usage (Recommend)
---

## zh-CN

通过 `message.useMessage` 创建支持读取 context 的 `contextHolder`。请注意,我们推荐通过顶层注册的方式代替 `message` 静态方法,因为静态方法无法消费上下文,因而 ConfigProvider 的数据也不会生效。

## en-US

Use `message.useMessage` to get `contextHolder` with context accessible issue. Please note that, we recommend to use top level registration instead of `message` static method, because static method cannot consume context, and ConfigProvider data will not work.

</docs>

<template>
<contextHolder />
<a-button type="primary" @click="info">Display normal message</a-button>
</template>

<script lang="ts" setup>
import { message } from 'ant-design-vue';
const [messageApi, contextHolder] = message.useMessage();

const info = () => {
messageApi.info('Hello, Ant Design!');
};
</script>
3 changes: 3 additions & 0 deletions components/message/demo/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<template>
<demo-sort>
<Hook />
<info />
<duration />
<other />
Expand All @@ -20,6 +21,7 @@ import customStyleVue from './custom-style.vue';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
import { defineComponent } from 'vue';
import Hook from './hook.vue';
export default defineComponent({
CN,
US,
Expand All @@ -31,6 +33,7 @@ export default defineComponent({
Thenable,
Update,
customStyleVue,
Hook,
},
setup() {
return {};
Expand Down
31 changes: 31 additions & 0 deletions components/message/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Methods for global configuration and destruction are also provided:

- `message.config(options)`
- `message.destroy()`
- `message.useMessage()`

#### message.config

Expand All @@ -85,3 +86,33 @@ message.config({
| prefixCls | The prefix className of message node | string | `ant-message` | 3.0 |
| rtl | Whether to enable RTL mode | boolean | false | 3.0 |
| top | distance from top | string | `8px` | |

## FAQ

### Why I can not access context, Pinia, ConfigProvider `locale/prefixCls/theme` in message?

antdv will dynamic create Vue instance by `Vue.render` when call message methods. Whose context is different with origin code located context.

When you need context info (like ConfigProvider context), you can use `message.useMessage` to get `api` instance and `contextHolder` node. And put it in your children:

```html
<template>
<contextHolder />
<!-- <component :is='contextHolder'/> -->
</template>
<script setup>
import { message } from 'ant-design-vue';
const [messageApi, contextHolder] = message.useMessage();
messageApi.open({
// ...
});
</script>
```

**Note:** You must insert `contextHolder` into your children with hooks. You can use origin method if you do not need context connection.

> [App Package Component](/components/app) can be used to simplify the problem of `useMessage` and other methods that need to manually implant contextHolder.

### How to set static methods prefixCls ?

You can config with [`ConfigProvider.config`](/components/config-provider#configproviderconfig-4130)
6 changes: 4 additions & 2 deletions components/message/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type { Key, VueNode } from '../_util/type';
import type { NotificationInstance } from '../vc-notification/Notification';
import classNames from '../_util/classNames';
import useStyle from './style';

import useMessage from './useMessage';
let defaultDuration = 3;
let defaultTop: string;
let messageInstance: NotificationInstance;
Expand Down Expand Up @@ -70,6 +70,7 @@ function getMessageInstance(args: MessageArgsProps, callback: (i: NotificationIn
callback(messageInstance);
return;
}

Notification.newInstance(
{
appContext: args.appContext,
Expand Down Expand Up @@ -225,14 +226,15 @@ export function attachTypeApi(originalApi: MessageApi, type: NoticeType) {
typeList.forEach(type => attachTypeApi(api, type));

api.warn = api.warning;

api.useMessage = useMessage;
export interface MessageInstance {
info(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose): MessageType;
success(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose): MessageType;
error(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose): MessageType;
warning(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose): MessageType;
loading(content: JointContent, duration?: ConfigDuration, onClose?: ConfigOnClose): MessageType;
open(args: MessageArgsProps): MessageType;
useMessage: typeof useMessage;
}

export interface MessageApi extends MessageInstance {
Expand Down
31 changes: 31 additions & 0 deletions components/message/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*7qMTRoq3ZGkAAA

- `message.config(options)`
- `message.destroy()`
- `message.useMessage()`

#### message.config

Expand All @@ -88,3 +89,33 @@ message.config({
| prefixCls | 消息节点的 className 前缀 | string | `ant-message` | 3.0 | |
| rtl | 是否开启 RTL 模式 | boolean | false | | |
| top | 消息距离顶部的位置 | string | `8px` | | |

## FAQ

### 为什么 message 不能获取 context、Pinia 的内容和 ConfigProvider 的 `locale/prefixCls/theme` 等配置?

直接调用 message 方法,antdv 会通过 `Vue.render` 动态创建新的 Vue 实体。其 context 与当前代码所在 context 并不相同,因而无法获取 context 信息。

当你需要 context 信息(例如 ConfigProvider 配置的内容)时,可以通过 `message.useMessage` 方法会返回 `api` 实体以及 `contextHolder` 节点。将其插入到你需要获取 context 位置即可:

```html
<template>
<contextHolder />
<!-- <component :is='contextHolder'/> -->
</template>
<script setup>
import { message } from 'ant-design-vue';
const [messageApi, contextHolder] = message.useMessage();
messageApi.open({
// ...
});
</script>
```

**异同**:通过 hooks 创建的 `contextHolder` 必须插入到子元素节点中才会生效,当你不需要上下文信息时请直接调用。

> 可通过 [App 包裹组件](/components/app-cn) 简化 `useMessage` 等方法需要手动植入 contextHolder 的问题。

### 静态方法如何设置 prefixCls ?

你可以通过 [`ConfigProvider.config`](/components/config-provider-cn#configproviderconfig-4130) 进行设置。
48 changes: 48 additions & 0 deletions components/message/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { CSSProperties } from 'vue';
import type { Key, VueNode } from '../_util/type';

export type NoticeType = 'info' | 'success' | 'error' | 'warning' | 'loading';

export interface ConfigOptions {
top?: number;
duration?: number;
prefixCls?: string;
getContainer?: () => HTMLElement;
transitionName?: string;
maxCount?: number;
rtl?: boolean;
}

export interface ArgsProps {
content: VueNode;
duration?: number;
type?: NoticeType;
onClose?: () => void;
icon?: VueNode;
key?: string | number;
style?: CSSProperties;
className?: string;
onClick?: (e: Event) => void;
}

export type JointContent = VueNode | ArgsProps;

export interface MessageType extends PromiseLike<boolean> {
(): void;
}

export type TypeOpen = (
content: JointContent,
duration?: number | VoidFunction, // Also can use onClose directly
onClose?: VoidFunction,
) => MessageType;

export interface MessageInstance {
info: TypeOpen;
success: TypeOpen;
error: TypeOpen;
warning: TypeOpen;
loading: TypeOpen;
open(args: ArgsProps): MessageType;
destroy(key?: Key): void;
}
Loading