diff --git a/components/__tests__/__snapshots__/index.test.ts.snap b/components/__tests__/__snapshots__/index.test.ts.snap index 9364bf329b..a0b933818a 100644 --- a/components/__tests__/__snapshots__/index.test.ts.snap +++ b/components/__tests__/__snapshots__/index.test.ts.snap @@ -5,6 +5,7 @@ exports[`antd exports modules correctly 1`] = ` "Affix", "Alert", "Anchor", + "App", "AutoComplete", "Avatar", "BackTop", diff --git a/components/app/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/app/__tests__/__snapshots__/demo-extend.test.ts.snap new file mode 100644 index 0000000000..a405386610 --- /dev/null +++ b/components/app/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders ./components/app/demo/message.tsx extend context correctly 1`] = ` +
+ +
+`; + +exports[`renders ./components/app/demo/modal.tsx extend context correctly 1`] = ` +
+ +
+`; + +exports[`renders ./components/app/demo/notification.tsx extend context correctly 1`] = ` +
+ +
+`; diff --git a/components/app/__tests__/__snapshots__/demo.test.ts.snap b/components/app/__tests__/__snapshots__/demo.test.ts.snap new file mode 100644 index 0000000000..c8a98f5162 --- /dev/null +++ b/components/app/__tests__/__snapshots__/demo.test.ts.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders ./components/app/demo/message.tsx correctly 1`] = ` +
+ +
+`; + +exports[`renders ./components/app/demo/modal.tsx correctly 1`] = ` +
+ +
+`; + +exports[`renders ./components/app/demo/notification.tsx correctly 1`] = ` +
+ +
+`; diff --git a/components/app/__tests__/__snapshots__/index.test.tsx.snap b/components/app/__tests__/__snapshots__/index.test.tsx.snap new file mode 100644 index 0000000000..9d7d65275a --- /dev/null +++ b/components/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/components/app/__tests__/demo-extend.test.ts b/components/app/__tests__/demo-extend.test.ts new file mode 100644 index 0000000000..6ef0a0d13d --- /dev/null +++ b/components/app/__tests__/demo-extend.test.ts @@ -0,0 +1,3 @@ +import { extendTest } from '../../../tests/shared/demoTest'; + +extendTest('app'); diff --git a/components/app/__tests__/demo.test.ts b/components/app/__tests__/demo.test.ts new file mode 100644 index 0000000000..fbcb9a76c9 --- /dev/null +++ b/components/app/__tests__/demo.test.ts @@ -0,0 +1,3 @@ +import demoTest from '../../../tests/shared/demoTest'; + +demoTest('app'); diff --git a/components/app/__tests__/image.test.ts b/components/app/__tests__/image.test.ts new file mode 100644 index 0000000000..8d3a08346d --- /dev/null +++ b/components/app/__tests__/image.test.ts @@ -0,0 +1,5 @@ +import { imageDemoTest } from '../../../tests/shared/imageTest'; + +describe('app', () => { + imageDemoTest('app'); +}); diff --git a/components/app/__tests__/index.test.tsx b/components/app/__tests__/index.test.tsx new file mode 100644 index 0000000000..3ea627cc9c --- /dev/null +++ b/components/app/__tests__/index.test.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import App from '..'; +import mountTest from '../../../tests/shared/mountTest'; +import rtlTest from '../../../tests/shared/rtlTest'; +import { render } from '../../../tests/utils'; + +describe('App', () => { + mountTest(App); + rtlTest(App); + + it('single', () => { + // Sub page + const MyPage = () => { + const { message } = App.useApp(); + React.useEffect(() => { + message.success('Good!'); + }, [message]); + + return
Hello World
; + }; + + // Entry component + const MyApp = () => ( + + + + ); + + const { getByText, container } = render(); + expect(getByText('Hello World')).toBeTruthy(); + expect(container.firstChild).toMatchSnapshot(); + }); +}); diff --git a/components/app/context.ts b/components/app/context.ts new file mode 100644 index 0000000000..823ae1ed7a --- /dev/null +++ b/components/app/context.ts @@ -0,0 +1,19 @@ +import React from 'react'; +import type { MessageInstance } from '../message/interface'; +import type { NotificationInstance } from '../notification/interface'; +import type { ModalStaticFunctions } from '../modal/confirm'; + +type ModalType = Omit; +export interface useAppProps { + message: MessageInstance; + notification: NotificationInstance; + modal: ModalType; +} + +const AppContext = React.createContext({ + message: {} as MessageInstance, + notification: {} as NotificationInstance, + modal: {} as ModalType, +}); + +export default AppContext; diff --git a/components/app/demo/message.md b/components/app/demo/message.md new file mode 100644 index 0000000000..4996404228 --- /dev/null +++ b/components/app/demo/message.md @@ -0,0 +1,7 @@ +## zh-CN + +获取 `message` 静态方法. + +## en-US + +Static method for `message`. diff --git a/components/app/demo/message.tsx b/components/app/demo/message.tsx new file mode 100644 index 0000000000..71ecffd5b9 --- /dev/null +++ b/components/app/demo/message.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { App, Button } from 'antd'; + +// Sub page +const MyPage = () => { + const { message } = App.useApp(); + + const showMessage = () => { + message.success('Success!'); + }; + + return ( + + ); +}; + +// Entry component +export default () => ( + + + +); diff --git a/components/app/demo/modal.md b/components/app/demo/modal.md new file mode 100644 index 0000000000..f07f87c7c8 --- /dev/null +++ b/components/app/demo/modal.md @@ -0,0 +1,7 @@ +## zh-CN + +获取 `modal` 静态方法. + +## en-US + +Static method for `modal`. diff --git a/components/app/demo/modal.tsx b/components/app/demo/modal.tsx new file mode 100644 index 0000000000..3fe95c848d --- /dev/null +++ b/components/app/demo/modal.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { App, Button } from 'antd'; + +// Sub page +const MyPage = () => { + const { modal } = App.useApp(); + + const showModal = () => { + modal.warning({ + title: 'This is a warning message', + content: 'some messages...some messages...', + }); + }; + + return ( + + ); +}; + +// Entry component +export default () => ( + + + +); diff --git a/components/app/demo/notification.md b/components/app/demo/notification.md new file mode 100644 index 0000000000..28fa841aab --- /dev/null +++ b/components/app/demo/notification.md @@ -0,0 +1,7 @@ +## zh-CN + +获取 `notification` 静态方法. + +## en-US + +Static method for `notification`. diff --git a/components/app/demo/notification.tsx b/components/app/demo/notification.tsx new file mode 100644 index 0000000000..fe8613a7f4 --- /dev/null +++ b/components/app/demo/notification.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { App, Button } from 'antd'; + +// Sub page +const MyPage = () => { + const { notification } = App.useApp(); + + const showNotification = () => { + notification.info({ + message: `Notification topLeft`, + description: 'Hello, Ant Design!!', + placement: 'topLeft', + }); + }; + + return ( + + ); +}; + +// Entry component +export default () => ( + + + +); diff --git a/components/app/index.en-US.md b/components/app/index.en-US.md new file mode 100644 index 0000000000..0c9fbc2694 --- /dev/null +++ b/components/app/index.en-US.md @@ -0,0 +1,43 @@ +--- +category: Components +group: Other +title: App +cover: https://gw.alipayobjects.com/zos/bmw-prod/cc3fcbfa-bf5b-4c8c-8a3d-c3f8388c75e8.svg +demo: + cols: 2 +--- + +New App Component which provide global style & static function replacement. + +## When To Use + +Static function in React 18 concurrent mode will not well support. In v5, we recommend to use hooks for the static replacement. But it will make user manual work on define this. + +## Examples + + +message +notification +modal + +## How to use + +```javascript +import React from 'react'; +import { App } from 'antd'; +const MyPage = () => { + 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 = () => ( + + + +); +``` diff --git a/components/app/index.tsx b/components/app/index.tsx new file mode 100644 index 0000000000..0e8add0cd9 --- /dev/null +++ b/components/app/index.tsx @@ -0,0 +1,60 @@ +import React, { useContext } from 'react'; +import type { ReactNode } from 'react'; +import classNames from 'classnames'; +import type { ConfigConsumerProps } from '../config-provider'; +import { ConfigContext } from '../config-provider'; +import useStyle from './style'; +import useMessage from '../message/useMessage'; +import useNotification from '../notification/useNotification'; +import useModal from '../modal/useModal'; +import AppContext from './context'; +import type { useAppProps } from './context'; + +export type AppProps = { + className?: string; + prefixCls?: string; + children?: ReactNode; +}; + +const useApp: () => useAppProps = () => React.useContext(AppContext); + +const App: React.ForwardRefRenderFunction & { + useApp: () => useAppProps; +} = (props) => { + const { prefixCls: customizePrefixCls, children, className } = props; + const { getPrefixCls } = useContext(ConfigContext); + const prefixCls = getPrefixCls('app', customizePrefixCls); + const [wrapSSR, hashId] = useStyle(prefixCls); + const customClassName = classNames(hashId, prefixCls, className); + + const [messageApi, messageContextHolder] = useMessage(); + const [notificationApi, notificationContextHolder] = useNotification(); + const [ModalApi, ModalContextHolder] = useModal(); + + const memoizedContextValue = React.useMemo( + () => ({ + message: messageApi, + notification: notificationApi, + modal: ModalApi, + }), + [messageApi, notificationApi, ModalApi], + ); + + return wrapSSR( + +
+ {ModalContextHolder} + {messageContextHolder} + {notificationContextHolder} + {children} +
+
, + ); +}; + +if (process.env.NODE_ENV !== 'production') { + App.displayName = 'App'; +} + +App.useApp = useApp; +export default App; diff --git a/components/app/index.zh-CN.md b/components/app/index.zh-CN.md new file mode 100644 index 0000000000..2ac40509bf --- /dev/null +++ b/components/app/index.zh-CN.md @@ -0,0 +1,45 @@ +--- +category: Components +subtitle: 包裹组件 +group: 其他 +title: App +cover: https://gw.alipayobjects.com/zos/bmw-prod/cc3fcbfa-bf5b-4c8c-8a3d-c3f8388c75e8.svg +demo: + cols: 2 +--- + +新的包裹组件,提供重置样式和提供消费上下文的默认环境。 + +## 何时使用 + +- 提供可消费 React context 的 `message.xxx`、`Modal.xxx`、`notification.xxx` 的静态方法,可以简化 useMessage 等方法需要手动植入 `contextHolder` 的问题。 +- 提供基于 `.ant-app` 的默认重置样式,解决原生元素没有 antd 规范样式的问题。 + +## 代码演示 + + +message +notification +modal + +## How to use + +```javascript +import React from 'react'; +import { App } from 'antd'; +const MyPage = () => { + 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 = () => ( + + + +); +``` diff --git a/components/app/style/index.tsx b/components/app/style/index.tsx new file mode 100644 index 0000000000..700aa8098b --- /dev/null +++ b/components/app/style/index.tsx @@ -0,0 +1,22 @@ +import type { FullToken, GenerateStyle } from '../../theme/internal'; +import { genComponentStyleHook } from '../../theme/internal'; + +export type ComponentToken = {}; + +interface AppToken extends FullToken<'App'> {} + +// =============================== Base =============================== +const genBaseStyle: GenerateStyle = (token) => { + const { componentCls, colorText, fontSize, lineHeight, fontFamily } = token; + return { + [componentCls]: { + color: colorText, + fontSize, + lineHeight, + fontFamily, + }, + }; +}; + +// ============================== Export ============================== +export default genComponentStyleHook('App', (token) => [genBaseStyle(token)]); diff --git a/components/index.tsx b/components/index.tsx index a9848329c2..806fdee89a 100644 --- a/components/index.tsx +++ b/components/index.tsx @@ -135,6 +135,7 @@ export { default as Tooltip } from './tooltip'; export type { TooltipProps } from './tooltip'; export { default as Tour } from './tour'; export type { TourProps, TourStepProps } from './tour/interface'; +export { default as App } from './app'; export { default as Transfer } from './transfer'; export type { TransferProps } from './transfer'; export { default as Tree } from './tree'; diff --git a/components/style/index.tsx b/components/style/index.tsx index 743ac86d50..bcac9ee92a 100644 --- a/components/style/index.tsx +++ b/components/style/index.tsx @@ -103,15 +103,11 @@ export const genLinkStyle = (token: DerivativeToken): CSSObject => ({ }, }); -export const genCommonStyle = (token: DerivativeToken, componentPrefixCls: string): CSSObject => { - const { fontFamily, fontSize } = token; - +export const genCommonStyle = (componentPrefixCls: string): CSSObject => { const rootPrefixSelector = `[class^="${componentPrefixCls}"], [class*=" ${componentPrefixCls}"]`; return { [rootPrefixSelector]: { - fontFamily, - fontSize, boxSizing: 'border-box', '&::before, &::after': { diff --git a/components/theme/interface/components.ts b/components/theme/interface/components.ts index 5ac9468398..02678f801e 100644 --- a/components/theme/interface/components.ts +++ b/components/theme/interface/components.ts @@ -47,6 +47,7 @@ import type { ComponentToken as TypographyComponentToken } from '../../typograph import type { ComponentToken as UploadComponentToken } from '../../upload/style'; import type { ComponentToken as TourComponentToken } from '../../tour/style'; import type { ComponentToken as QRCodeComponentToken } from '../../qrcode/style'; +import type { ComponentToken as AppComponentToken } from '../../app/style'; export interface ComponentTokenMap { Affix?: {}; @@ -110,4 +111,5 @@ export interface ComponentTokenMap { Progress?: ProgressComponentToken; Tour?: TourComponentToken; QRCode?: QRCodeComponentToken; + App?: AppComponentToken; } diff --git a/components/theme/util/genComponentStyleHook.ts b/components/theme/util/genComponentStyleHook.ts index db2bd218bb..7b5d172fbc 100644 --- a/components/theme/util/genComponentStyleHook.ts +++ b/components/theme/util/genComponentStyleHook.ts @@ -87,7 +87,7 @@ export default function genComponentStyleHook{component}, + + {component}, + , ); const styleStr = extractStyle(cache);