diff --git a/components/app/__tests__/index.test.tsx b/components/app/__tests__/index.test.tsx
index 3ea627cc9c..5a4fc2071a 100644
--- a/components/app/__tests__/index.test.tsx
+++ b/components/app/__tests__/index.test.tsx
@@ -1,13 +1,24 @@
-import React from 'react';
+import React, { useEffect } from 'react';
import App from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
-import { render } from '../../../tests/utils';
+import { render, waitFakeTimer } from '../../../tests/utils';
+import type { AppConfig } from '../context';
+import { AppConfigContext } from '../context';
describe('App', () => {
mountTest(App);
rtlTest(App);
+ beforeEach(() => {
+ jest.useFakeTimers();
+ });
+
+ afterEach(() => {
+ jest.clearAllTimers();
+ jest.useRealTimers();
+ });
+
it('single', () => {
// Sub page
const MyPage = () => {
@@ -30,4 +41,85 @@ describe('App', () => {
expect(getByText('Hello World')).toBeTruthy();
expect(container.firstChild).toMatchSnapshot();
});
+
+ it('should work as message and notification config configured in app', async () => {
+ let consumedConfig: AppConfig | undefined;
+ const Consumer = () => {
+ const { message, notification } = App.useApp();
+ consumedConfig = React.useContext(AppConfigContext);
+
+ useEffect(() => {
+ message.success('Message 1');
+ message.success('Message 2');
+ notification.success({ message: 'Notification 1' });
+ notification.success({ message: 'Notification 2' });
+ notification.success({ message: 'Notification 3' });
+ }, [message, notification]);
+
+ return
;
+ };
+ const Wrapper = () => (
+
+
+
+ );
+
+ render();
+
+ await waitFakeTimer();
+
+ expect(consumedConfig?.message).toStrictEqual({ maxCount: 1 });
+ expect(consumedConfig?.notification).toStrictEqual({ maxCount: 2 });
+
+ expect(document.querySelectorAll('.ant-message-notice')).toHaveLength(1);
+ expect(document.querySelectorAll('.ant-notification-notice')).toHaveLength(2);
+ });
+
+ it('should be a merged config configured in nested app', async () => {
+ let offsetConsumedConfig: AppConfig | undefined;
+ let maxCountConsumedConfig: AppConfig | undefined;
+ const OffsetConsumer = () => {
+ offsetConsumedConfig = React.useContext(AppConfigContext);
+ return ;
+ };
+ const MaxCountConsumer = () => {
+ maxCountConsumedConfig = React.useContext(AppConfigContext);
+ return ;
+ };
+ const Wrapper = () => (
+
+
+
+
+
+
+ );
+
+ render();
+
+ expect(offsetConsumedConfig?.message).toStrictEqual({ maxCount: 1, top: 32 });
+ expect(offsetConsumedConfig?.notification).toStrictEqual({ maxCount: 2, top: 96 });
+ expect(maxCountConsumedConfig?.message).toStrictEqual({ maxCount: 1 });
+ expect(maxCountConsumedConfig?.notification).toStrictEqual({ maxCount: 2 });
+ });
+
+ it('should respect config from props in priority', async () => {
+ let config: AppConfig | undefined;
+ const Consumer = () => {
+ config = React.useContext(AppConfigContext);
+ return ;
+ };
+ const Wrapper = () => (
+
+
+
+
+
+ );
+
+ render();
+
+ expect(config?.message).toStrictEqual({ maxCount: 11, top: 20 });
+ expect(config?.notification).toStrictEqual({ maxCount: 30, bottom: 41 });
+ });
});
diff --git a/components/app/context.ts b/components/app/context.ts
index 7b83793b68..7beba3c827 100644
--- a/components/app/context.ts
+++ b/components/app/context.ts
@@ -1,8 +1,15 @@
import React from 'react';
-import type { MessageInstance } from '../message/interface';
-import type { NotificationInstance } from '../notification/interface';
+import type { MessageInstance, ConfigOptions as MessageConfig } from '../message/interface';
+import type { NotificationInstance, NotificationConfig } from '../notification/interface';
import type { ModalStaticFunctions } from '../modal/confirm';
+export type AppConfig = {
+ message?: MessageConfig;
+ notification?: NotificationConfig;
+};
+
+export const AppConfigContext = React.createContext({});
+
type ModalType = Omit;
export interface useAppProps {
message: MessageInstance;
diff --git a/components/app/index.en-US.md b/components/app/index.en-US.md
index 70b525de59..45d9003bb1 100644
--- a/components/app/index.en-US.md
+++ b/components/app/index.en-US.md
@@ -117,3 +117,12 @@ export default () => {
);
};
```
+
+## API
+
+### App
+
+| Property | Description | Type | Default | Version |
+| --- | --- | --- | --- | --- |
+| message | Global config for Message | [MessageConfig](/components/message/#messageconfig) | - | 5.3.0 |
+| notification | Global config for Notification | [NotificationConfig](/components/notification/#notificationconfig) | - | 5.3.0 |
diff --git a/components/app/index.tsx b/components/app/index.tsx
index e1eb7ef4c1..d4dda35f6e 100644
--- a/components/app/index.tsx
+++ b/components/app/index.tsx
@@ -6,8 +6,8 @@ import { ConfigContext } from '../config-provider';
import useMessage from '../message/useMessage';
import useModal from '../modal/useModal';
import useNotification from '../notification/useNotification';
-import type { useAppProps } from './context';
-import AppContext from './context';
+import AppContext, { AppConfigContext } from './context';
+import type { AppConfig, useAppProps } from './context';
import useStyle from './style';
export type AppProps = {
@@ -15,19 +15,37 @@ export type AppProps = {
rootClassName?: string;
prefixCls?: string;
children?: ReactNode;
-};
+} & AppConfig;
-const useApp = () => React.useContext(AppContext);
+const useApp = () => React.useContext(AppContext);
-const App: React.FC & { useApp: () => useAppProps } = (props) => {
- const { prefixCls: customizePrefixCls, children, className, rootClassName } = props;
+const App: React.FC & { useApp: typeof useApp } = (props) => {
+ const {
+ prefixCls: customizePrefixCls,
+ children,
+ className,
+ rootClassName,
+ message,
+ notification,
+ } = props;
const { getPrefixCls } = useContext(ConfigContext);
const prefixCls = getPrefixCls('app', customizePrefixCls);
const [wrapSSR, hashId] = useStyle(prefixCls);
const customClassName = classNames(hashId, prefixCls, className, rootClassName);
- const [messageApi, messageContextHolder] = useMessage();
- const [notificationApi, notificationContextHolder] = useNotification();
+ const appConfig = useContext(AppConfigContext);
+ const mergedAppConfig = React.useMemo(
+ () => ({
+ message: { ...appConfig.message, ...message },
+ notification: { ...appConfig.notification, ...notification },
+ }),
+ [message, notification, appConfig.message, appConfig.message],
+ );
+
+ const [messageApi, messageContextHolder] = useMessage(mergedAppConfig.message);
+ const [notificationApi, notificationContextHolder] = useNotification(
+ mergedAppConfig.notification,
+ );
const [ModalApi, ModalContextHolder] = useModal();
const memoizedContextValue = React.useMemo(
@@ -41,12 +59,14 @@ const App: React.FC & { useApp: () => useAppProps } = (props) => {
return wrapSSR(
-
- {ModalContextHolder}
- {messageContextHolder}
- {notificationContextHolder}
- {children}
-
+
+
+ {ModalContextHolder}
+ {messageContextHolder}
+ {notificationContextHolder}
+ {children}
+
+
,
);
};
diff --git a/components/app/index.zh-CN.md b/components/app/index.zh-CN.md
index 9cceb465d6..61741724d9 100644
--- a/components/app/index.zh-CN.md
+++ b/components/app/index.zh-CN.md
@@ -119,3 +119,12 @@ export default () => {
);
};
```
+
+## API
+
+### App
+
+| 参数 | 说明 | 类型 | 默认值 | 版本 |
+| --- | --- | --- | --- | --- |
+| message | App 内 Message 的全局配置 | [MessageConfig](/components/message-cn/#messageconfig) | - | 5.3.0 |
+| notification | App 内 Notification 的全局配置 | [NotificationConfig](/components/notification-cn/#notificationconfig) | - | 5.3.0 |
diff --git a/components/notification/index.en-US.md b/components/notification/index.en-US.md
index 86436755b3..9d3472c5e9 100644
--- a/components/notification/index.en-US.md
+++ b/components/notification/index.en-US.md
@@ -65,11 +65,15 @@ The properties of config are as follows:
`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)`
+### Global configuration
- > 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.
+`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.
+
+#### notification.config
```js
notification.config({
diff --git a/components/notification/index.zh-CN.md b/components/notification/index.zh-CN.md
index 3468097089..e47bc1361a 100644
--- a/components/notification/index.zh-CN.md
+++ b/components/notification/index.zh-CN.md
@@ -63,13 +63,15 @@ config 参数如下:
| onClick | 点击通知时触发的回调函数 | function | - |
| onClose | 当通知关闭时触发 | function | - |
+### 全局配置
+
还提供了一个全局配置方法,在调用前提前配置,全局一次生效。
-- `notification.config(options)`
+`notification.config(options)`
- > 当你使用 `ConfigProvider` 进行全局化配置时,系统会默认自动开启 RTL 模式。(4.3.0+)
- >
- > 当你想单独使用,可通过如下设置开启 RTL 模式。
+> 当你使用 `ConfigProvider` 进行全局化配置时,系统会默认自动开启 RTL 模式。(4.3.0+)
+>
+> 当你想单独使用,可通过如下设置开启 RTL 模式。
```js
notification.config({
@@ -80,6 +82,8 @@ notification.config({
});
```
+#### notification.config
+
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| bottom | 消息从底部弹出时,距离底部的位置,单位像素 | number | 24 | |