Skip to content

Commit

Permalink
feat(App): add App component (#6735)
Browse files Browse the repository at this point in the history
* feat(App): add App component

* docs: modification error

* docs: doucment update

docs: doucment update
  • Loading branch information
selicens authored Aug 6, 2023
1 parent 0a738a3 commit 8f95cdd
Show file tree
Hide file tree
Showing 12 changed files with 535 additions and 2 deletions.
41 changes: 41 additions & 0 deletions components/app/__tests__/__snapshots__/demo.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders ./components/app/demo/basic.vue correctly 1`] = `
<div class="ant-app">
<!--teleport start-->
<!--teleport end-->
<!--teleport start-->
<!--teleport end-->
<div class="ant-space ant-space-horizontal ant-space-align-center">
<div class="ant-space-item" style="margin-right: 8px;"><button class="ant-btn ant-btn-primary" type="button">
<!----><span>Open message</span>
</button></div>
<!---->
<div class="ant-space-item" style="margin-right: 8px;"><button class="ant-btn ant-btn-primary" type="button">
<!----><span>Open modal</span>
</button></div>
<!---->
<div class="ant-space-item"><button class="ant-btn ant-btn-primary" type="button">
<!----><span>Open notification</span>
</button></div>
<!---->
</div>
</div>
`;

exports[`renders ./components/app/demo/myPage.vue correctly 1`] = `
<div class="ant-space ant-space-horizontal ant-space-align-center">
<div class="ant-space-item" style="margin-right: 8px;"><button class="ant-btn ant-btn-primary" type="button">
<!----><span>Open message</span>
</button></div>
<!---->
<div class="ant-space-item" style="margin-right: 8px;"><button class="ant-btn ant-btn-primary" type="button">
<!----><span>Open modal</span>
</button></div>
<!---->
<div class="ant-space-item"><button class="ant-btn ant-btn-primary" type="button">
<!----><span>Open notification</span>
</button></div>
<!---->
</div>
`;
3 changes: 3 additions & 0 deletions components/app/__tests__/demo.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import demoTest from '../../../tests/shared/demoTest';

demoTest('app');
44 changes: 44 additions & 0 deletions components/app/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { provide, inject, reactive } 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<AppConfig> = Symbol('appConfigContext');

export const useProvideAppConfigContext = (appConfigContext: AppConfig) => {
return provide(AppConfigContextKey, appConfigContext);
};

export const useInjectAppConfigContext = () => {
return inject(AppConfigContextKey, {});
};

type ModalType = Omit<ModalStaticFunctions, 'warn'>;

export interface useAppProps {
message: MessageInstance;
notification: NotificationInstance;
modal: ModalType;
}

export const AppContextKey: InjectionKey<useAppProps> = 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);
};
26 changes: 26 additions & 0 deletions components/app/demo/basic.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<docs>
---
order: 0
title:
zh-CN: 基本使用
en-US: Basic Usage
---

## zh-CN

获取 `message`、`notification`、`modal` 静态方法。

## en-US

Static method for `message`, `notification`, `modal`.
</docs>

<template>
<a-app>
<my-page />
</a-app>
</template>

<script lang="ts" setup>
import myPage from './myPage.vue';
</script>
20 changes: 20 additions & 0 deletions components/app/demo/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<template>
<demo-sort :cols="1">
<basic />
</demo-sort>
</template>

<script lang="ts">
import Basic from './basic.vue';
import CN from '../index.zh-CN.md';
import US from '../index.en-US.md';
import { defineComponent } from 'vue';
export default defineComponent({
CN,
US,
components: {
Basic,
},
});
</script>
32 changes: 32 additions & 0 deletions components/app/demo/myPage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<template>
<a-space>
<a-button type="primary" @click="showMessage">Open message</a-button>
<a-button type="primary" @click="showModal">Open modal</a-button>
<a-button type="primary" @click="showNotification">Open notification</a-button>
</a-space>
</template>

<script setup lang="ts">
import { App } from 'ant-design-vue';
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 Vue!!',
placement: 'topLeft',
});
};
</script>
129 changes: 129 additions & 0 deletions components/app/index.en-US.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
---
category: Components
cols: 1
type: Other
title: App
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*TBTSR4PyVmkAAAAAAAAAAAAADrJ8AQ/original
coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*JGb3RIzyOCkAAAAAAAAAAAAADrJ8AQ/original
---

Application wrapper for some global usages.

## When To Use

- Provide reset styles based on `.ant-app` element.
- You could use static methods of `message/notification/Modal` form `useApp` without writing `contextHolder` manually.

## API

### App

| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| message | Global config for Message | [MessageConfig](/components/message/#messageconfig) | - | 4.x |
| notification | Global config for Notification | [NotificationConfig](/components/notification/#notificationconfig) | - | 4.x |

## 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.vue*/
<template>
<a-space>
<a-button type="primary" @click="showMessage">Open message</a-button>
<a-button type="primary" @click="showModal">Open modal</a-button>
<a-button type="primary" @click="showNotification">Open notification</a-button>
</a-space>
</template>

<script setup lang="ts">
import { App } from 'ant-design-vue';
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 Vue!!',
placement: 'topLeft',
});
};
</script>
```

Note: App.useApp must be available under App.

#### Embedded usage scenarios (if not necessary, try not to do nesting)

```html
<a-app>
<a-space>
...
<a-app>...</a-app>
</a-space>
</a-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
<a-config-provider theme="{{ ... }}">
<a-app>...</a-app>
</a-config-provider>
```

#### Global scene (pinia scene)

```ts
import { App } from 'ant-design-vue';
import type { MessageInstance } from 'ant-design-vue/es/message/interface';
import type { ModalStaticFunctions } from 'ant-design-vue/es/modal/confirm';
import type { NotificationInstance } from 'ant-design-vue/es/notification/interface';

export const useGloablStore = defineStore('global', () => {
const message: MessageInstance = ref();
const notification: NotificationInstance = ref();
const modal: Omit<ModalStaticFunctions, 'warn'> = ref();
(() => {
const staticFunction = App.useApp();
message.value = staticFunction.message;
modal.value = staticFunction.modal;
notification.value = staticFunction.notification;
})();

return { message, notification, modal };
});
```

```html
// sub page
<template>
<a-space>
<a-button type="primary" @click="showMessage">Open message</a-button>
</a-space>
</template>

<script setup>
import { useGlobalStore } from '@/stores/global';
const global = useGlobalStore();
const showMessage = () => {
global.message.success('Success!');
};
</script>
```
83 changes: 83 additions & 0 deletions components/app/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { defineComponent, computed } from 'vue';
import type { App as TypeApp, Plugin } from 'vue';
import { initDefaultProps } from '../_util/props-util';
import classNames from '../_util/classNames';
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 {
rootClassName: String,
message: objectType<AppConfig['message']>(),
notification: objectType<AppConfig['notification']>(),
};
};

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 = computed(() => {
return classNames(hashId.value, prefixCls.value, 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);

return () => {
return wrapSSR(
<div class={customClassName.value}>
{ModalContextHolder()}
{messageContextHolder()}
{notificationContextHolder()}
{slots.default?.()}
</div>,
);
};
},
});

App.useApp = useApp;

App.install = function (app: TypeApp) {
app.component(App.name, App);
};

export default App as typeof App &
Plugin & {
readonly useApp: typeof useApp;
};
Loading

0 comments on commit 8f95cdd

Please sign in to comment.