Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(App): add App component #6735

Merged
merged 3 commits into from
Aug 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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