Skip to content

Commit

Permalink
Feat(use): add useMessage useNotification (#6527)
Browse files Browse the repository at this point in the history
* feat(Message): add useMessage hook

* feat(Notification): add useNotification hook

* feat(Message): add Hook demo

* feat(Notification): add Hook demo

* test(Message): update demo snap

* test(Notification): update demo snap

* docs(Message): update docs with FAQ

* docs(Notification): update docs with FAQ
  • Loading branch information
CCherry07 authored May 5, 2023
1 parent b61c88e commit 6eb4d8f
Show file tree
Hide file tree
Showing 24 changed files with 1,465 additions and 6 deletions.
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

0 comments on commit 6eb4d8f

Please sign in to comment.