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 | |