mirror of
https://gitee.com/ant-design/ant-design.git
synced 2024-12-02 03:59:01 +08:00
* feat(App): add message & notification options * chore: test * docs: App docs * feat: config inherit in nest app * feat: provide & consume config context internally * docs(App): property literal * fix(App): repect config from props in priority * Update components/app/index.en-US.md * Update components/app/index.zh-CN.md --------- Co-authored-by: MadCcc <1075746765@qq.com>
This commit is contained in:
parent
5c1162a140
commit
9f98fc243e
@ -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 <div />;
|
||||
};
|
||||
const Wrapper = () => (
|
||||
<App message={{ maxCount: 1 }} notification={{ maxCount: 2 }}>
|
||||
<Consumer />
|
||||
</App>
|
||||
);
|
||||
|
||||
render(<Wrapper />);
|
||||
|
||||
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 <div />;
|
||||
};
|
||||
const MaxCountConsumer = () => {
|
||||
maxCountConsumedConfig = React.useContext(AppConfigContext);
|
||||
return <div />;
|
||||
};
|
||||
const Wrapper = () => (
|
||||
<App message={{ maxCount: 1 }} notification={{ maxCount: 2 }}>
|
||||
<App message={{ top: 32 }} notification={{ top: 96 }}>
|
||||
<OffsetConsumer />
|
||||
</App>
|
||||
<MaxCountConsumer />
|
||||
</App>
|
||||
);
|
||||
|
||||
render(<Wrapper />);
|
||||
|
||||
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 <div />;
|
||||
};
|
||||
const Wrapper = () => (
|
||||
<App message={{ maxCount: 10, top: 20 }} notification={{ maxCount: 30, bottom: 40 }}>
|
||||
<App message={{ maxCount: 11 }} notification={{ bottom: 41 }}>
|
||||
<Consumer />
|
||||
</App>
|
||||
</App>
|
||||
);
|
||||
|
||||
render(<Wrapper />);
|
||||
|
||||
expect(config?.message).toStrictEqual({ maxCount: 11, top: 20 });
|
||||
expect(config?.notification).toStrictEqual({ maxCount: 30, bottom: 41 });
|
||||
});
|
||||
});
|
||||
|
@ -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<AppConfig>({});
|
||||
|
||||
type ModalType = Omit<ModalStaticFunctions, 'warn'>;
|
||||
export interface useAppProps {
|
||||
message: MessageInstance;
|
||||
|
@ -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 |
|
||||
|
@ -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<useAppProps>(AppContext);
|
||||
const useApp = () => React.useContext(AppContext);
|
||||
|
||||
const App: React.FC<AppProps> & { useApp: () => useAppProps } = (props) => {
|
||||
const { prefixCls: customizePrefixCls, children, className, rootClassName } = props;
|
||||
const App: React.FC<AppProps> & { useApp: typeof useApp } = (props) => {
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
children,
|
||||
className,
|
||||
rootClassName,
|
||||
message,
|
||||
notification,
|
||||
} = props;
|
||||
const { getPrefixCls } = useContext<ConfigConsumerProps>(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<AppConfig>(
|
||||
() => ({
|
||||
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<useAppProps>(
|
||||
@ -41,12 +59,14 @@ const App: React.FC<AppProps> & { useApp: () => useAppProps } = (props) => {
|
||||
|
||||
return wrapSSR(
|
||||
<AppContext.Provider value={memoizedContextValue}>
|
||||
<div className={customClassName}>
|
||||
{ModalContextHolder}
|
||||
{messageContextHolder}
|
||||
{notificationContextHolder}
|
||||
{children}
|
||||
</div>
|
||||
<AppConfigContext.Provider value={mergedAppConfig}>
|
||||
<div className={customClassName}>
|
||||
{ModalContextHolder}
|
||||
{messageContextHolder}
|
||||
{notificationContextHolder}
|
||||
{children}
|
||||
</div>
|
||||
</AppConfigContext.Provider>
|
||||
</AppContext.Provider>,
|
||||
);
|
||||
};
|
||||
|
@ -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 |
|
||||
|
@ -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({
|
||||
|
@ -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 | |
|
||||
|
Loading…
Reference in New Issue
Block a user