From e03338ab02a8ffc8fcdb34d2a7339d26921f5bb0 Mon Sep 17 00:00:00 2001 From: dev <710328466@qq.com> Date: Wed, 21 Feb 2024 17:20:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20meta=E6=B7=BB=E5=8A=A0app=E3=80=81notif?= =?UTF-8?q?ication=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- global.d.ts | 4 - packages/biz/package.json | 1 + .../es/BigImagePreview/BigImagePreview.js | 10 +- .../components/BtnGroup/index.js | 3 +- packages/meta/es/VideoPlayer/VideoPlayer.js | 4 +- packages/meta/es/index.js | 4 +- .../src/BigImagePreview/BigImagePreview.tsx | 3 + .../components/BtnGroup/index.tsx | 5 +- .../meta/src/BigImagePreview/demo/base.tsx | 2 +- packages/meta/src/VideoPlayer/VideoPlayer.tsx | 1 + .../__snapshots__/demo-extend.test.ts.snap | 87 ++ .../__tests__/__snapshots__/demo.test.ts.snap | 83 ++ .../__snapshots__/index.test.tsx.snap | 17 + .../src/app/__tests__/demo-extend.test.ts | 3 + packages/meta/src/app/__tests__/demo.test.ts | 3 + packages/meta/src/app/__tests__/image.test.ts | 5 + .../meta/src/app/__tests__/index.test.tsx | 236 ++++++ packages/meta/src/app/context.ts | 26 + packages/meta/src/app/demo/basic.md | 7 + packages/meta/src/app/demo/basic.tsx | 47 ++ packages/meta/src/app/demo/config.md | 7 + packages/meta/src/app/demo/config.tsx | 36 + packages/meta/src/app/index.tsx | 94 +++ packages/meta/src/app/index.zh-CN.md | 133 +++ packages/meta/src/app/style/index.ts | 25 + packages/meta/src/index.tsx | 4 + packages/meta/src/notification/PurePanel.tsx | 135 +++ .../__snapshots__/demo-extend.test.ts.snap | 783 ++++++++++++++++++ .../__tests__/__snapshots__/demo.test.ts.snap | 761 +++++++++++++++++ .../notification/__tests__/config.test.tsx | 87 ++ .../__tests__/demo-extend.test.ts | 3 + .../src/notification/__tests__/demo.test.ts | 6 + .../src/notification/__tests__/hooks.test.tsx | 192 +++++ .../src/notification/__tests__/image.test.ts | 5 + .../src/notification/__tests__/index.test.tsx | 370 +++++++++ .../notification/__tests__/placement.test.tsx | 167 ++++ .../__tests__/static-warning.test.tsx | 57 ++ .../meta/src/notification/__tests__/util.ts | 31 + packages/meta/src/notification/demo/basic.md | 7 + packages/meta/src/notification/demo/basic.tsx | 20 + .../meta/src/notification/demo/custom-icon.md | 7 + .../src/notification/demo/custom-icon.tsx | 27 + .../src/notification/demo/custom-style.md | 7 + .../src/notification/demo/custom-style.tsx | 28 + .../meta/src/notification/demo/duration.md | 7 + .../meta/src/notification/demo/duration.tsx | 26 + packages/meta/src/notification/demo/hooks.md | 7 + packages/meta/src/notification/demo/hooks.tsx | 66 ++ .../meta/src/notification/demo/placement.md | 7 + .../meta/src/notification/demo/placement.tsx | 78 ++ .../src/notification/demo/render-panel.md | 7 + .../src/notification/demo/render-panel.tsx | 18 + packages/meta/src/notification/demo/stack.md | 7 + packages/meta/src/notification/demo/stack.tsx | 59 ++ packages/meta/src/notification/demo/update.md | 7 + .../meta/src/notification/demo/update.tsx | 34 + .../meta/src/notification/demo/with-btn.md | 7 + .../meta/src/notification/demo/with-btn.tsx | 45 + .../meta/src/notification/demo/with-icon.md | 7 + .../meta/src/notification/demo/with-icon.tsx | 30 + packages/meta/src/notification/index.tsx | 253 ++++++ packages/meta/src/notification/index.zh-CN.md | 142 ++++ packages/meta/src/notification/interface.ts | 70 ++ packages/meta/src/notification/style/index.ts | 307 +++++++ .../meta/src/notification/style/placement.ts | 113 +++ .../meta/src/notification/style/pure-panel.ts | 25 + packages/meta/src/notification/style/stack.ts | 119 +++ .../meta/src/notification/useNotification.tsx | 220 +++++ packages/meta/src/notification/util.ts | 68 ++ pnpm-lock.yaml | 3 + 70 files changed, 5263 insertions(+), 12 deletions(-) create mode 100644 packages/meta/src/app/__tests__/__snapshots__/demo-extend.test.ts.snap create mode 100644 packages/meta/src/app/__tests__/__snapshots__/demo.test.ts.snap create mode 100644 packages/meta/src/app/__tests__/__snapshots__/index.test.tsx.snap create mode 100644 packages/meta/src/app/__tests__/demo-extend.test.ts create mode 100644 packages/meta/src/app/__tests__/demo.test.ts create mode 100644 packages/meta/src/app/__tests__/image.test.ts create mode 100644 packages/meta/src/app/__tests__/index.test.tsx create mode 100644 packages/meta/src/app/context.ts create mode 100644 packages/meta/src/app/demo/basic.md create mode 100644 packages/meta/src/app/demo/basic.tsx create mode 100644 packages/meta/src/app/demo/config.md create mode 100644 packages/meta/src/app/demo/config.tsx create mode 100644 packages/meta/src/app/index.tsx create mode 100644 packages/meta/src/app/index.zh-CN.md create mode 100644 packages/meta/src/app/style/index.ts create mode 100644 packages/meta/src/notification/PurePanel.tsx create mode 100644 packages/meta/src/notification/__tests__/__snapshots__/demo-extend.test.ts.snap create mode 100644 packages/meta/src/notification/__tests__/__snapshots__/demo.test.ts.snap create mode 100644 packages/meta/src/notification/__tests__/config.test.tsx create mode 100644 packages/meta/src/notification/__tests__/demo-extend.test.ts create mode 100644 packages/meta/src/notification/__tests__/demo.test.ts create mode 100644 packages/meta/src/notification/__tests__/hooks.test.tsx create mode 100644 packages/meta/src/notification/__tests__/image.test.ts create mode 100644 packages/meta/src/notification/__tests__/index.test.tsx create mode 100644 packages/meta/src/notification/__tests__/placement.test.tsx create mode 100644 packages/meta/src/notification/__tests__/static-warning.test.tsx create mode 100644 packages/meta/src/notification/__tests__/util.ts create mode 100644 packages/meta/src/notification/demo/basic.md create mode 100644 packages/meta/src/notification/demo/basic.tsx create mode 100644 packages/meta/src/notification/demo/custom-icon.md create mode 100644 packages/meta/src/notification/demo/custom-icon.tsx create mode 100644 packages/meta/src/notification/demo/custom-style.md create mode 100644 packages/meta/src/notification/demo/custom-style.tsx create mode 100644 packages/meta/src/notification/demo/duration.md create mode 100644 packages/meta/src/notification/demo/duration.tsx create mode 100644 packages/meta/src/notification/demo/hooks.md create mode 100644 packages/meta/src/notification/demo/hooks.tsx create mode 100644 packages/meta/src/notification/demo/placement.md create mode 100644 packages/meta/src/notification/demo/placement.tsx create mode 100644 packages/meta/src/notification/demo/render-panel.md create mode 100644 packages/meta/src/notification/demo/render-panel.tsx create mode 100644 packages/meta/src/notification/demo/stack.md create mode 100644 packages/meta/src/notification/demo/stack.tsx create mode 100644 packages/meta/src/notification/demo/update.md create mode 100644 packages/meta/src/notification/demo/update.tsx create mode 100644 packages/meta/src/notification/demo/with-btn.md create mode 100644 packages/meta/src/notification/demo/with-btn.tsx create mode 100644 packages/meta/src/notification/demo/with-icon.md create mode 100644 packages/meta/src/notification/demo/with-icon.tsx create mode 100644 packages/meta/src/notification/index.tsx create mode 100644 packages/meta/src/notification/index.zh-CN.md create mode 100644 packages/meta/src/notification/interface.ts create mode 100644 packages/meta/src/notification/style/index.ts create mode 100644 packages/meta/src/notification/style/placement.ts create mode 100644 packages/meta/src/notification/style/pure-panel.ts create mode 100644 packages/meta/src/notification/style/stack.ts create mode 100644 packages/meta/src/notification/useNotification.tsx create mode 100644 packages/meta/src/notification/util.ts diff --git a/global.d.ts b/global.d.ts index 63ff4c6..e69de29 100644 --- a/global.d.ts +++ b/global.d.ts @@ -1,4 +0,0 @@ -declare module '@zhst/func'; -declare module '@zhst/hooks'; -declare module '@zhst/meta'; -declare module '@zhst/request'; diff --git a/packages/biz/package.json b/packages/biz/package.json index 41a32a0..a91041f 100644 --- a/packages/biz/package.json +++ b/packages/biz/package.json @@ -38,6 +38,7 @@ "@types/zhst": "workspace:^" }, "dependencies": { + "@ant-design/icons": "^5.2.6", "@zhst/func": "workspace:^", "@zhst/hooks": "workspace:^", "@zhst/meta": "workspace:^", diff --git a/packages/meta/es/BigImagePreview/BigImagePreview.js b/packages/meta/es/BigImagePreview/BigImagePreview.js index c1316cf..2c68f08 100644 --- a/packages/meta/es/BigImagePreview/BigImagePreview.js +++ b/packages/meta/es/BigImagePreview/BigImagePreview.js @@ -16,7 +16,11 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } import React, { useEffect, useState, useCallback, useRef, useImperativeHandle } from 'react'; import classNames from 'classnames'; import { useLatest } from '@zhst/hooks'; -import { get, pick, isNull, generateImg, dataURLToBlob, +import { get, pick, isNull, +// @ts-ignore +generateImg, +// @ts-ignore +dataURLToBlob, // @ts-ignore getOdRect, // @ts-ignore @@ -24,7 +28,9 @@ getExtendRect, // @ts-ignore getTransformRect, // @ts-ignore -getRotateImg, getTransforms, addEventListenerWrapper, getFileByRect +getRotateImg, getTransforms, addEventListenerWrapper, +// @ts-ignore +getFileByRect // @ts-ignore } from '@zhst/func'; import Align from 'rc-align'; diff --git a/packages/meta/es/BigImagePreview/components/BtnGroup/index.js b/packages/meta/es/BigImagePreview/components/BtnGroup/index.js index 52e621c..c8854d7 100644 --- a/packages/meta/es/BigImagePreview/components/BtnGroup/index.js +++ b/packages/meta/es/BigImagePreview/components/BtnGroup/index.js @@ -1,6 +1,7 @@ import React from 'react'; import classNames from 'classnames'; -import { Button, Tooltip } from 'antd'; +// @ts-ignore +import { Button, Tooltip } from "../../.."; import Icon from "../../../iconfont"; import "./index.less"; var componentName = "zhst-image__btn-group"; diff --git a/packages/meta/es/VideoPlayer/VideoPlayer.js b/packages/meta/es/VideoPlayer/VideoPlayer.js index 8bea43e..ec78758 100644 --- a/packages/meta/es/VideoPlayer/VideoPlayer.js +++ b/packages/meta/es/VideoPlayer/VideoPlayer.js @@ -14,7 +14,9 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; -import { noop, get, addEventListenerWrapper, dataURLToBlob, nextTick, toRealNumber, getTransforms, formatDurationTime +import { noop, get, addEventListenerWrapper, +// @ts-ignore +dataURLToBlob, nextTick, toRealNumber, getTransforms, formatDurationTime // @ts-ignore } from '@zhst/func'; import Align from 'rc-align'; diff --git a/packages/meta/es/index.js b/packages/meta/es/index.js index 0896856..52607d6 100644 --- a/packages/meta/es/index.js +++ b/packages/meta/es/index.js @@ -41,4 +41,6 @@ export { default as Card } from "./card"; export { default as Skeleton } from "./skeleton"; export { default as Tooltip } from "./tooltip"; export { default as Tour } from "./tour"; -export { default as Segmented } from "./segmented"; \ No newline at end of file +export { default as Segmented } from "./segmented"; +export { default as App } from "./app"; +export { default as notification } from "./notification"; \ No newline at end of file diff --git a/packages/meta/src/BigImagePreview/BigImagePreview.tsx b/packages/meta/src/BigImagePreview/BigImagePreview.tsx index 78e9752..61e2b1a 100644 --- a/packages/meta/src/BigImagePreview/BigImagePreview.tsx +++ b/packages/meta/src/BigImagePreview/BigImagePreview.tsx @@ -5,7 +5,9 @@ import { get, pick, isNull, + // @ts-ignore generateImg, + // @ts-ignore dataURLToBlob, // @ts-ignore getOdRect, @@ -17,6 +19,7 @@ import { getRotateImg, getTransforms, addEventListenerWrapper, + // @ts-ignore getFileByRect // @ts-ignore } from '@zhst/func'; diff --git a/packages/meta/src/BigImagePreview/components/BtnGroup/index.tsx b/packages/meta/src/BigImagePreview/components/BtnGroup/index.tsx index ba70c12..c7e8019 100644 --- a/packages/meta/src/BigImagePreview/components/BtnGroup/index.tsx +++ b/packages/meta/src/BigImagePreview/components/BtnGroup/index.tsx @@ -1,6 +1,7 @@ import React, { MouseEvent } from 'react'; import classNames from 'classnames'; -import { Button, Tooltip, TooltipProps } from 'antd'; +// @ts-ignore +import { Button, Tooltip, TooltipProps } from '../../..'; import Icon from '../../../iconfont'; import './index.less'; @@ -38,7 +39,7 @@ export const BtnGroup: React.FC = (props) => { > + +
+ +
+
+ +
+ + +`; + +exports[`renders components/app/demo/basic.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/app/demo/config.tsx extend context correctly 1`] = ` +
+
+
+ +
+
+ +
+
+
+`; + +exports[`renders components/app/demo/config.tsx extend context correctly 2`] = `[]`; diff --git a/packages/meta/src/app/__tests__/__snapshots__/demo.test.ts.snap b/packages/meta/src/app/__tests__/__snapshots__/demo.test.ts.snap new file mode 100644 index 0000000..293802c --- /dev/null +++ b/packages/meta/src/app/__tests__/__snapshots__/demo.test.ts.snap @@ -0,0 +1,83 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders components/app/demo/basic.tsx correctly 1`] = ` +
+
+
+ +
+
+ +
+
+ +
+
+
+`; + +exports[`renders components/app/demo/config.tsx correctly 1`] = ` +
+
+
+ +
+
+ +
+
+
+`; diff --git a/packages/meta/src/app/__tests__/__snapshots__/index.test.tsx.snap b/packages/meta/src/app/__tests__/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000..9d7d652 --- /dev/null +++ b/packages/meta/src/app/__tests__/__snapshots__/index.test.tsx.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`App rtl render component should be rendered correctly in RTL direction 1`] = ` +
+`; + +exports[`App single 1`] = ` +
+
+ Hello World +
+
+`; diff --git a/packages/meta/src/app/__tests__/demo-extend.test.ts b/packages/meta/src/app/__tests__/demo-extend.test.ts new file mode 100644 index 0000000..6ef0a0d --- /dev/null +++ b/packages/meta/src/app/__tests__/demo-extend.test.ts @@ -0,0 +1,3 @@ +import { extendTest } from '../../../tests/shared/demoTest'; + +extendTest('app'); diff --git a/packages/meta/src/app/__tests__/demo.test.ts b/packages/meta/src/app/__tests__/demo.test.ts new file mode 100644 index 0000000..fbcb9a7 --- /dev/null +++ b/packages/meta/src/app/__tests__/demo.test.ts @@ -0,0 +1,3 @@ +import demoTest from '../../../tests/shared/demoTest'; + +demoTest('app'); diff --git a/packages/meta/src/app/__tests__/image.test.ts b/packages/meta/src/app/__tests__/image.test.ts new file mode 100644 index 0000000..8d3a083 --- /dev/null +++ b/packages/meta/src/app/__tests__/image.test.ts @@ -0,0 +1,5 @@ +import { imageDemoTest } from '../../../tests/shared/imageTest'; + +describe('app', () => { + imageDemoTest('app'); +}); diff --git a/packages/meta/src/app/__tests__/index.test.tsx b/packages/meta/src/app/__tests__/index.test.tsx new file mode 100644 index 0000000..aafcac0 --- /dev/null +++ b/packages/meta/src/app/__tests__/index.test.tsx @@ -0,0 +1,236 @@ +import React, { useEffect } from 'react'; +import { SmileOutlined } from '@ant-design/icons'; +import type { NotificationConfig } from 'antd/es/notification/interface'; + +import App from '..'; +import mountTest from '../../../tests/shared/mountTest'; +import rtlTest from '../../../tests/shared/rtlTest'; +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: React.FC = () => { + const { message } = App.useApp(); + React.useEffect(() => { + message.success('Good!'); + }, [message]); + + return
Hello World
; + }; + + // Entry component + const MyApp: React.FC = () => ( + + + + ); + + const { getByText, container } = render(); + 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 }); + }); + + it('should respect notification placement config from props in priority', async () => { + let consumedConfig: AppConfig | undefined; + + const Consumer = () => { + const { notification } = App.useApp(); + consumedConfig = React.useContext(AppConfigContext); + + useEffect(() => { + notification.success({ message: 'Notification 1' }); + notification.success({ message: 'Notification 2' }); + notification.success({ message: 'Notification 3' }); + }, [notification]); + + return
; + }; + + const config: NotificationConfig = { + placement: 'bottomLeft', + top: 100, + bottom: 50, + }; + + const Wrapper = () => ( + + + + ); + + render(); + await waitFakeTimer(); + + expect(consumedConfig?.notification).toStrictEqual(config); + expect(document.querySelector('.ant-notification-topRight')).not.toBeInTheDocument(); + expect(document.querySelector('.ant-notification-bottomLeft')).toHaveStyle({ + top: '', + left: '0px', + bottom: '50px', + }); + }); + + it('support className', () => { + const { container } = render( + +
test
+
, + ); + expect(container.querySelector('.ant-app')).toHaveClass('test-class'); + }); + + it('support style', () => { + const { container } = render( + +
test
+
, + ); + expect(container.querySelector('.ant-app')).toHaveStyle('color: blue;'); + }); + + // https://github.com/ant-design/ant-design/issues/41197#issuecomment-1465803061 + describe('restIcon style', () => { + beforeEach(() => { + Array.from(document.querySelectorAll('style')).forEach((style) => { + style.parentNode?.removeChild(style); + }); + }); + + it('should work by default', () => { + const { container } = render( + + + , + ); + + expect(container.querySelector('.anticon')).toBeTruthy(); + const dynamicStyles = Array.from(document.querySelectorAll('style[data-css-hash]')); + expect( + dynamicStyles.some((style) => { + const { innerHTML } = style; + return innerHTML.startsWith('.anticon'); + }), + ).toBeTruthy(); + }); + }); + + describe('component', () => { + it('replace', () => { + const { container } = render( + +

+ , + ); + + expect(container.querySelector('section.ant-app')).toBeTruthy(); + }); + + it('to false', () => { + const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + const { container } = render( + +

+ , + ); + expect(warnSpy).not.toHaveBeenCalled(); + expect(container.querySelector('.ant-app')).toBeFalsy(); + warnSpy.mockRestore(); + }); + }); +}); diff --git a/packages/meta/src/app/context.ts b/packages/meta/src/app/context.ts new file mode 100644 index 0000000..4b3220a --- /dev/null +++ b/packages/meta/src/app/context.ts @@ -0,0 +1,26 @@ +import React from 'react'; + +import type { ConfigOptions as MessageConfig, MessageInstance } from '../message/interface'; +import type { HookAPI as ModalHookAPI } from '../modal/useModal'; +import type { NotificationConfig, NotificationInstance } from '../notification/interface'; + +export interface AppConfig { + message?: MessageConfig; + notification?: NotificationConfig; +} + +export const AppConfigContext = React.createContext({}); + +export interface useAppProps { + message: MessageInstance; + notification: NotificationInstance; + modal: ModalHookAPI; +} + +const AppContext = React.createContext({ + message: {}, + notification: {}, + modal: {}, +} as useAppProps); + +export default AppContext; diff --git a/packages/meta/src/app/demo/basic.md b/packages/meta/src/app/demo/basic.md new file mode 100644 index 0000000..9976b03 --- /dev/null +++ b/packages/meta/src/app/demo/basic.md @@ -0,0 +1,7 @@ +## zh-CN + +获取 `message`、`notification`、`modal` 实例。 + +## en-US + +Get instance for `message`, `notification`, `modal`. diff --git a/packages/meta/src/app/demo/basic.tsx b/packages/meta/src/app/demo/basic.tsx new file mode 100644 index 0000000..62692e0 --- /dev/null +++ b/packages/meta/src/app/demo/basic.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { App, Button, Space } from 'antd'; + +// Sub page +const MyPage = () => { + const { message, modal, notification } = App.useApp(); + + const showMessage = () => { + message.success('Success!'); + }; + + const showModal = () => { + modal.warning({ + title: 'This is a warning message', + content: 'some messages...some messages...', + }); + }; + + const showNotification = () => { + notification.info({ + message: `Notification topLeft`, + description: 'Hello, Ant Design!!', + placement: 'topLeft', + }); + }; + + return ( + + + + + + ); +}; + +// Entry component +export default () => ( + + + +); diff --git a/packages/meta/src/app/demo/config.md b/packages/meta/src/app/demo/config.md new file mode 100644 index 0000000..200dc53 --- /dev/null +++ b/packages/meta/src/app/demo/config.md @@ -0,0 +1,7 @@ +## zh-CN + +对 `message`、`notification` 进行配置。 + +## en-US + +Config for `message`, `notification`. diff --git a/packages/meta/src/app/demo/config.tsx b/packages/meta/src/app/demo/config.tsx new file mode 100644 index 0000000..fdc25a6 --- /dev/null +++ b/packages/meta/src/app/demo/config.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { App, Button, Space } from 'antd'; + +// Sub page +const MyPage = () => { + const { message, notification } = App.useApp(); + + const showMessage = () => { + message.success('Success!'); + }; + + const showNotification = () => { + notification.info({ + message: `Notification`, + description: 'Hello, Ant Design!!', + }); + }; + + return ( + + + + + ); +}; + +// Entry component +export default () => ( + + + +); diff --git a/packages/meta/src/app/index.tsx b/packages/meta/src/app/index.tsx new file mode 100644 index 0000000..dafb414 --- /dev/null +++ b/packages/meta/src/app/index.tsx @@ -0,0 +1,94 @@ +import type { ReactNode } from 'react'; +import React, { useContext } from 'react'; +import classNames from 'classnames'; + +import type { AnyObject, CustomComponent } from '../_util/type'; +import type { ConfigConsumerProps } from '../config-provider'; +import { ConfigContext } from '../config-provider'; +import useMessage from '../message/useMessage'; +import useModal from '../modal/useModal'; +import useNotification from '../notification/useNotification'; +import type { AppConfig, useAppProps } from './context'; +import AppContext, { AppConfigContext } from './context'; +import useStyle from './style'; + +export interface AppProps

extends AppConfig { + style?: React.CSSProperties; + className?: string; + rootClassName?: string; + prefixCls?: string; + children?: ReactNode; + component?: CustomComponent

| false; +} + +const useApp = () => React.useContext(AppContext); + +const App: React.FC & { useApp: () => useAppProps } = (props) => { + const { + prefixCls: customizePrefixCls, + children, + className, + rootClassName, + message, + notification, + style, + component = 'div', + } = props; + const { getPrefixCls } = useContext(ConfigContext); + const prefixCls = getPrefixCls('app', customizePrefixCls); + const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls); + const customClassName = classNames(hashId, prefixCls, className, rootClassName, cssVarCls); + + const appConfig = useContext(AppConfigContext); + + const mergedAppConfig = React.useMemo( + () => ({ + message: { ...appConfig.message, ...message }, + notification: { ...appConfig.notification, ...notification }, + }), + [message, notification, appConfig.message, appConfig.notification], + ); + + const [messageApi, messageContextHolder] = useMessage(mergedAppConfig.message); + const [notificationApi, notificationContextHolder] = useNotification( + mergedAppConfig.notification, + ); + const [ModalApi, ModalContextHolder] = useModal(); + + const memoizedContextValue = React.useMemo( + () => ({ + message: messageApi, + notification: notificationApi, + modal: ModalApi, + }), + [messageApi, notificationApi, ModalApi], + ); + + // ============================ Render ============================ + const Component = component === false ? React.Fragment : component; + const rootProps: AppProps = { + className: customClassName, + style, + }; + + return wrapCSSVar( + + + + {ModalContextHolder} + {messageContextHolder} + {notificationContextHolder} + {children} + + + , + ); +}; + +if (process.env.NODE_ENV !== 'production') { + App.displayName = 'App'; +} + +App.useApp = useApp; + +export default App; diff --git a/packages/meta/src/app/index.zh-CN.md b/packages/meta/src/app/index.zh-CN.md new file mode 100644 index 0000000..5b790a3 --- /dev/null +++ b/packages/meta/src/app/index.zh-CN.md @@ -0,0 +1,133 @@ +--- +category: Components +subtitle: 包裹组件 +group: 其他 +title: App 包裹组件 +demo: + cols: 2 +--- + +新的包裹组件,提供重置样式和提供消费上下文的默认环境。 + +## 何时使用 + +- 提供可消费 React context 的 `message.xxx`、`Modal.xxx`、`notification.xxx` 的静态方法,可以简化 useMessage 等方法需要手动植入 `contextHolder` 的问题。 +- 提供基于 `.ant-app` 的默认重置样式,解决原生元素没有 antd 规范样式的问题。 + +## 代码演示 + + +基本用法 +Hooks 配置 + +## 如何使用 + +### 基础用法 + +App 组件通过 `Context` 提供上下文方法调用,因而 useApp 需要作为子组件才能使用,我们推荐在应用中顶层包裹 App。 + +```ts +import React from 'react'; +import { App } from 'antd'; + +const MyPage: React.FC = () => { + const { message, notification, modal } = App.useApp(); + message.success('Good!'); + notification.info({ message: 'Good' }); + modal.warning({ title: 'Good' }); + // .... + // other message, notification, modal static function + return

Hello word
; +}; + +const MyApp: React.FC = () => ( + + + +); + +export default MyApp; +``` + +注意:App.useApp 必须在 App 之下方可使用。 + +### 与 ConfigProvider 先后顺序 + +App 组件只能在 `ConfigProvider` 之下才能使用 Design Token, 如果需要使用其样式重置能力,则 ConfigProvider 与 App 组件必须成对出现。 + +```ts + + + ... + + +``` + +### 内嵌使用场景(如无必要,尽量不做嵌套) + +```ts + + + ... + ... + + +``` + +### 全局场景(redux 场景) + +```ts +// Entry component +import { App } from 'antd'; +import type { MessageInstance } from 'antd/es/message/interface'; +import type { ModalStaticFunctions } from 'antd/es/modal/confirm'; +import type { NotificationInstance } from 'antd/es/notification/interface'; + +let message: MessageInstance; +let notification: NotificationInstance; +let modal: Omit; + +export default () => { + const staticFunction = App.useApp(); + message = staticFunction.message; + modal = staticFunction.modal; + notification = staticFunction.notification; + return null; +}; + +export { message, notification, modal }; +``` + +```ts +// sub page +import React from 'react'; +import { Button, Space } from 'antd'; + +import { message } from './store'; + +export default () => { + const showMessage = () => { + message.success('Success!'); + }; + + return ( + + + + ); +}; +``` + +## API + +通用属性参考:[通用属性](/docs/react/common-props) + +### App + +| 参数 | 说明 | 类型 | 默认值 | 版本 | +| --- | --- | --- | --- | --- | +| component | 设置渲染元素,为 `false` 则不创建 DOM 节点 | ComponentType | div | 5.11.0 | +| 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/packages/meta/src/app/style/index.ts b/packages/meta/src/app/style/index.ts new file mode 100644 index 0000000..939261f --- /dev/null +++ b/packages/meta/src/app/style/index.ts @@ -0,0 +1,25 @@ +import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal'; +import { genStyleHooks } from '../../theme/internal'; + +export type ComponentToken = {}; +// @ts-ignore +interface AppToken extends FullToken<'App'> {} + +// =============================== Base =============================== +const genBaseStyle: GenerateStyle = (token) => { + const { componentCls, colorText, fontSize, lineHeight, fontFamily } = token; + return { + [componentCls]: { + color: colorText, + fontSize, + lineHeight, + fontFamily, + }, + }; +}; +// @ts-ignore +export const prepareComponentToken: GetDefaultToken<'App'> = () => ({}); + +// ============================== Export ============================== +// @ts-ignore +export default genStyleHooks('App', genBaseStyle, prepareComponentToken); diff --git a/packages/meta/src/index.tsx b/packages/meta/src/index.tsx index 544fb69..01a2301 100644 --- a/packages/meta/src/index.tsx +++ b/packages/meta/src/index.tsx @@ -81,3 +81,7 @@ export { default as Tour } from './tour' export type { TourLocale, TourProps, TourStepProps } from './tour/interface' export { default as Segmented } from './segmented' export type { SegmentedLabeledOption, SegmentedProps, SegmentedValue } from './segmented' +export { default as App } from './app'; +export type { AppProps } from './app'; +export { default as notification } from './notification'; +export type { ArgsProps as NotificationArgsProps } from './notification'; diff --git a/packages/meta/src/notification/PurePanel.tsx b/packages/meta/src/notification/PurePanel.tsx new file mode 100644 index 0000000..7d27d45 --- /dev/null +++ b/packages/meta/src/notification/PurePanel.tsx @@ -0,0 +1,135 @@ +import * as React from 'react'; +import CheckCircleFilled from '@ant-design/icons/CheckCircleFilled'; +import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled'; +import CloseOutlined from '@ant-design/icons/CloseOutlined'; +import ExclamationCircleFilled from '@ant-design/icons/ExclamationCircleFilled'; +import InfoCircleFilled from '@ant-design/icons/InfoCircleFilled'; +import LoadingOutlined from '@ant-design/icons/LoadingOutlined'; +import classNames from 'classnames'; +import { Notice } from 'rc-notification'; +import type { NoticeProps } from 'rc-notification/lib/Notice'; + +import { ConfigContext } from '../config-provider'; +import useCSSVarCls from '../config-provider/hooks/useCSSVarCls'; +import type { IconType } from './interface'; +import useStyle from './style'; +import PurePanelStyle from './style/pure-panel'; + +export const TypeIcon = { + info: , + success: , + error: , + warning: , + loading: , +}; + +export function getCloseIcon(prefixCls: string, closeIcon?: React.ReactNode): React.ReactNode { + if (closeIcon === null || closeIcon === false) { + return null; + } + return ( + closeIcon || ( + + + + ) + ); +} + +export interface PureContentProps { + prefixCls: string; + icon?: React.ReactNode; + message?: React.ReactNode; + description?: React.ReactNode; + btn?: React.ReactNode; + type?: IconType; + role?: 'alert' | 'status'; +} + +const typeToIcon = { + success: CheckCircleFilled, + info: InfoCircleFilled, + error: CloseCircleFilled, + warning: ExclamationCircleFilled, +}; + +export const PureContent: React.FC = (props) => { + const { prefixCls, icon, type, message, description, btn, role = 'alert' } = props; + let iconNode: React.ReactNode = null; + if (icon) { + iconNode = {icon}; + } else if (type) { + iconNode = React.createElement(typeToIcon[type] || null, { + className: classNames(`${prefixCls}-icon`, `${prefixCls}-icon-${type}`), + }); + } + return ( +
+ {iconNode} +
{message}
+
{description}
+ {btn &&
{btn}
} +
+ ); +}; + +export interface PurePanelProps + extends Omit, + Omit { + prefixCls?: string; +} + +/** @private Internal Component. Do not use in your production. */ +const PurePanel: React.FC = (props) => { + const { + prefixCls: staticPrefixCls, + className, + icon, + type, + message, + description, + btn, + closable = true, + closeIcon, + className: notificationClassName, + ...restProps + } = props; + const { getPrefixCls } = React.useContext(ConfigContext); + + const prefixCls = staticPrefixCls || getPrefixCls('notification'); + const noticePrefixCls = `${prefixCls}-notice`; + + const rootCls = useCSSVarCls(prefixCls); + const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls); + + return wrapCSSVar( +
+ + + } + /> +
, + ); +}; + +export default PurePanel; diff --git a/packages/meta/src/notification/__tests__/__snapshots__/demo-extend.test.ts.snap b/packages/meta/src/notification/__tests__/__snapshots__/demo-extend.test.ts.snap new file mode 100644 index 0000000..54e8351 --- /dev/null +++ b/packages/meta/src/notification/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -0,0 +1,783 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders components/notification/demo/basic.tsx extend context correctly 1`] = ` + +`; + +exports[`renders components/notification/demo/basic.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/notification/demo/custom-icon.tsx extend context correctly 1`] = ` + +`; + +exports[`renders components/notification/demo/custom-icon.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/notification/demo/custom-style.tsx extend context correctly 1`] = ` + +`; + +exports[`renders components/notification/demo/custom-style.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/notification/demo/duration.tsx extend context correctly 1`] = ` + +`; + +exports[`renders components/notification/demo/duration.tsx extend context correctly 2`] = `[]`; + +exports[`renders components/notification/demo/hooks.tsx extend context correctly 1`] = ` +Array [ +
+
+ +
+
+ +
+
, +