diff --git a/components/app/__tests__/__snapshots__/demo.test.js.snap b/components/app/__tests__/__snapshots__/demo.test.js.snap new file mode 100644 index 0000000000..e475ca0c78 --- /dev/null +++ b/components/app/__tests__/__snapshots__/demo.test.js.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders ./components/app/demo/basic.vue correctly 1`] = ` +
+ + + + + +
+
+ +
+ +
+ +
+
+`; + +exports[`renders ./components/app/demo/myPage.vue correctly 1`] = ` +
+
+ +
+ +
+ +
+`; diff --git a/components/app/__tests__/demo.test.js b/components/app/__tests__/demo.test.js new file mode 100644 index 0000000000..fbcb9a76c9 --- /dev/null +++ b/components/app/__tests__/demo.test.js @@ -0,0 +1,3 @@ +import demoTest from '../../../tests/shared/demoTest'; + +demoTest('app'); diff --git a/components/app/context.ts b/components/app/context.ts new file mode 100644 index 0000000000..1b3ed7498e --- /dev/null +++ b/components/app/context.ts @@ -0,0 +1,43 @@ +import { reactive, provide, inject } from 'vue'; +import type { InjectionKey } from 'vue'; +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 AppConfigContextKey: InjectionKey = Symbol('appConfigContext'); + +export const useProvideAppConfigContext = (appConfigContext: AppConfig) => { + return provide(AppConfigContextKey, appConfigContext); +}; + +export const useInjectAppConfigContext = () => { + return inject(AppConfigContextKey, {}); +}; + +type ModalType = Omit; +export interface useAppProps { + message: MessageInstance; + notification: NotificationInstance; + modal: ModalType; +} + +export const AppContextKey: InjectionKey = Symbol('appContext'); + +export const useProvideAppContext = (appContext: useAppProps) => { + return provide(AppContextKey, appContext); +}; + +const defaultAppContext: useAppProps = reactive({ + message: {}, + notification: {}, + modal: {}, +} as useAppProps); + +export const useInjectAppContext = () => { + return inject(AppContextKey, defaultAppContext); +}; diff --git a/components/app/demo/basic.vue b/components/app/demo/basic.vue new file mode 100644 index 0000000000..3f4749d638 --- /dev/null +++ b/components/app/demo/basic.vue @@ -0,0 +1,26 @@ + +--- +order: 0 +title: + zh-CN: 基本使用 + en-US: Basic Usage +--- + +## zh-CN + +获取 `message`, `notification`, `modal` 静态方法。 + +## en-US + +Static method for `message`, `notification`, `modal`. + + + + + diff --git a/components/app/demo/index.vue b/components/app/demo/index.vue new file mode 100644 index 0000000000..21b22e9605 --- /dev/null +++ b/components/app/demo/index.vue @@ -0,0 +1,23 @@ + + + diff --git a/components/app/demo/myPage.vue b/components/app/demo/myPage.vue new file mode 100644 index 0000000000..b4cb69643f --- /dev/null +++ b/components/app/demo/myPage.vue @@ -0,0 +1,32 @@ + + + diff --git a/components/app/index.en-US.md b/components/app/index.en-US.md new file mode 100644 index 0000000000..eb7fecdbc9 --- /dev/null +++ b/components/app/index.en-US.md @@ -0,0 +1,99 @@ +--- +category: Components +group: Other +title: App +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*HJz8SZos2wgAAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*oC92TK44Ex8AAAAAAAAAAAAADrJ8AQ/original +demo: + cols: 2 +--- + +New App Component which provide global style & static function replacement. + +## When To Use + +- Provide reset styles based on `.ant-app` element. +- You could use static methods of `message/notification/Modal` form `useApp` without put `contextHolder` mannully. + +## How to use + +### Basic usage + +App provides upstream and downstream method calls through `provide/inject`, because useApp needs to be used as a subcomponent, we recommend encapsulating App at the top level in the application. + +```html + + + +// myPage + + + +``` + +Note: App.useApp must be available under App. + +### Sequence with ConfigProvider + +The App component can only use the token in the `ConfigProvider`, if you need to use the Token, the ConfigProvider and the App component must appear in pairs. + +```html + + ... + +``` + +### Embedded usage scenarios (if not necessary, try not to do nesting) + +```html + + + ... + ... + + +``` + +## API + +### App + +| Property | Description | Type | Default | +| --- | --- | --- | --- | +| message | Global config for Message | [MessageConfig](/components/message/#messageconfig) | - | +| notification | Global config for Notification | [NotificationConfig](/components/notification/#notificationconfig) | - | diff --git a/components/app/index.tsx b/components/app/index.tsx new file mode 100644 index 0000000000..fb0a4cc160 --- /dev/null +++ b/components/app/index.tsx @@ -0,0 +1,94 @@ +import { defineComponent, computed } from 'vue'; +import type { PropType, App as TypeApp, Plugin } from 'vue'; +import { initDefaultProps } from '../_util/props-util'; +import classNames from '../_util/classNames'; +import type { VueNode } from '../_util/type'; +import { objectType } from '../_util/type'; +import useConfigInject from '../config-provider/hooks/useConfigInject'; +import useMessage from '../message/useMessage'; +import useModal from '../modal/useModal'; +import useNotification from '../notification/useNotification'; +import type { AppConfig } from './context'; +import { + useProvideAppConfigContext, + useInjectAppConfigContext, + useProvideAppContext, + useInjectAppContext, +} from './context'; +import useStyle from './style'; + +export const AppProps = () => { + return { + style: String, + className: String, + rootClassName: String, + children: { + type: Function as PropType<() => VueNode>, + }, + message: objectType(), + notification: objectType(), + }; +}; + +const useApp = () => { + return useInjectAppContext(); +}; + +const App = defineComponent({ + name: 'AApp', + props: initDefaultProps(AppProps(), {}), + setup(props, { slots }) { + const { prefixCls } = useConfigInject('app', props); + const [wrapSSR, hashId] = useStyle(prefixCls); + const customClassName = classNames( + hashId.value, + prefixCls.value, + props.className, + props.rootClassName, + ); + + const appConfig = useInjectAppConfigContext(); + const mergedAppConfig = computed(() => ({ + message: { ...appConfig.message, ...props.message }, + notification: { ...appConfig.notification, ...props.notification }, + })); + useProvideAppConfigContext(mergedAppConfig.value); + + const [messageApi, messageContextHolder] = useMessage(mergedAppConfig.value.message); + const [notificationApi, notificationContextHolder] = useNotification( + mergedAppConfig.value.notification, + ); + const [ModalApi, ModalContextHolder] = useModal(); + + const memoizedContextValue = computed(() => ({ + message: messageApi, + notification: notificationApi, + modal: ModalApi, + })); + useProvideAppContext(memoizedContextValue.value); + + const childNode = slots.default?.(); + return () => { + return wrapSSR( +
+ {ModalContextHolder()} + {messageContextHolder()} + {notificationContextHolder()} + {props.children} + {childNode} +
, + ); + }; + }, +}); + +App.useApp = useApp; + +App.install = function (app: TypeApp) { + app.component(App.name, App); +}; + +export default App as typeof App & + Plugin & { + readonly useApp: typeof useApp; + }; diff --git a/components/app/index.zh-CN.md b/components/app/index.zh-CN.md new file mode 100644 index 0000000000..ab259b6be9 --- /dev/null +++ b/components/app/index.zh-CN.md @@ -0,0 +1,100 @@ +--- +category: Components +subtitle: 包裹组件 +group: 其他 +title: App +cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*HJz8SZos2wgAAAAAAAAAAAAADrJ8AQ/original +coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*oC92TK44Ex8AAAAAAAAAAAAADrJ8AQ/original +demo: + cols: 2 +--- + +新的包裹组件,提供重置样式和提供消费上下文的默认环境。 + +## 何时使用 + +- 提供可消费 `provide/inject` 的 `message.xxx`、`Modal.xxx`、`notification.xxx` 的静态方法,可以简化 useMessage 等方法需要手动植入 `contextHolder` 的问题。 +- 提供基于 `.ant-app` 的默认重置样式,解决原生元素没有 antd 规范样式的问题。 + +## 如何使用 + +### 基础用法 + +App 组件通过 `provide/inject` 提供上下文方法调用,因而 useApp 需要作为子组件才能使用,我们推荐在应用中顶层包裹 App。 + +```html + + + +// myPage + + + +``` + +注意:App.useApp 必须在 App 之下方可使用。 + +### 与 ConfigProvider 先后顺序 + +App 组件只能在 `ConfigProvider` 之下才能使用 Design Token, 如果需要使用其样式重置能力,则 ConfigProvider 与 App 组件必须成对出现。 + +```html + + ... + +``` + +### 内嵌使用场景(如无必要,尽量不做嵌套) + +```html + + + ... + ... + + +``` + +## API + +### App + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| message | App 内 Message 的全局配置 | [MessageConfig](/components/message-cn/#messageconfig) | - | +| notification | App 内 Notification 的全局配置 | [NotificationConfig](/components/notification-cn/#notificationconfig) | - | diff --git a/components/app/style/index.ts b/components/app/style/index.ts new file mode 100644 index 0000000000..031b0ffd09 --- /dev/null +++ b/components/app/style/index.ts @@ -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/components.ts b/components/components.ts index f9db584a68..117becde56 100644 --- a/components/components.ts +++ b/components/components.ts @@ -261,3 +261,6 @@ export { default as QRCode } from './qrcode'; export type { TourProps, TourStepProps } from './tour'; export { default as Tour } from './tour'; + +export { default as App } from './app'; +export type { AppProps } from './app'; diff --git a/components/theme/interface/components.ts b/components/theme/interface/components.ts index 92f2fffb88..329cba4fd5 100644 --- a/components/theme/interface/components.ts +++ b/components/theme/interface/components.ts @@ -46,7 +46,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'; +import type { ComponentToken as AppComponentToken } from '../../app/style'; import type { ComponentToken as WaveToken } from '../../_util/wave/style'; export interface ComponentTokenMap { @@ -112,7 +112,7 @@ export interface ComponentTokenMap { Progress?: ProgressComponentToken; Tour?: TourComponentToken; QRCode?: QRCodeComponentToken; - // App?: AppComponentToken; + App?: AppComponentToken; // /** @private Internal TS definition. Do not use. */ Wave?: WaveToken;