feat(Dialog): allow to render JSX message (#8420)

* feat(Dialog): allow to render JSX message

* types: add DialogMessage type
This commit is contained in:
neverland 2021-03-29 15:51:02 +08:00 committed by GitHub
parent 1a6930fa73
commit eec186ac19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 60 additions and 21 deletions

View File

@ -2,7 +2,13 @@ import { PropType, reactive, defineComponent } from 'vue';
// Utils
import { callInterceptor, Interceptor } from '../utils/interceptor';
import { createNamespace, addUnit, pick, UnknownProp } from '../utils';
import {
pick,
addUnit,
isFunction,
UnknownProp,
createNamespace,
} from '../utils';
import { BORDER_TOP, BORDER_LEFT } from '../utils/constant';
import { popupSharedProps, popupSharedPropKeys } from '../popup/shared';
@ -16,6 +22,7 @@ const [name, bem, t] = createNamespace('dialog');
export type DialogTheme = 'default' | 'round-button';
export type DialogAction = 'confirm' | 'cancel';
export type DialogMessage = string | (() => JSX.Element);
export type DialogMessageAlign = 'left' | 'center' | 'right';
const popupKeys = [
@ -32,7 +39,7 @@ export default defineComponent({
title: String,
theme: String as PropType<DialogTheme>,
width: [Number, String],
message: String,
message: [String, Function] as PropType<DialogMessage>,
callback: Function as PropType<(action?: DialogAction) => void>,
allowHtml: Boolean,
className: UnknownProp,
@ -119,15 +126,31 @@ export default defineComponent({
}
};
const renderMessage = (hasTitle: boolean) => {
const { message, allowHtml, messageAlign } = props;
const classNames = bem('message', {
'has-title': hasTitle,
[messageAlign as string]: messageAlign,
});
if (allowHtml && typeof message === 'string') {
return <div class={classNames} innerHTML={message} />;
}
return (
<div class={classNames}>
{isFunction(message) ? message() : message}
</div>
);
};
const renderContent = () => {
if (slots.default) {
return <div class={bem('content')}>{slots.default()}</div>;
}
const { title, message, allowHtml, messageAlign } = props;
const { title, message, allowHtml } = props;
if (message) {
const hasTitle = title || slots.title;
const hasTitle = !!(title || slots.title);
return (
<div
// add key to force re-render
@ -135,15 +158,7 @@ export default defineComponent({
key={allowHtml ? 1 : 0}
class={bem('content', { isolated: !hasTitle })}
>
<div
class={bem('message', {
'has-title': hasTitle,
[messageAlign as string]: messageAlign,
})}
{...{
[allowHtml ? 'innerHTML' : 'textContent']: message,
}}
/>
{renderMessage(hasTitle)}
</div>
);
}

View File

@ -142,7 +142,7 @@ export default {
| --- | --- | --- | --- |
| title | Title | _string_ | - |
| width | Dialog width | _number \| string_ | `320px` |
| message | Message | _string_ | - |
| message | Message | _string \| () => JSX.ELement_ | - |
| messageAlign | Message text aligncan be set to `left` `right` | _string_ | `center` |
| theme | Theme stylecan be set to `round-button` | _string_ | `default` |
| className | Custom className | _string \| Array \| object_ | - |
@ -170,7 +170,7 @@ export default {
| v-model:show | Whether to show dialog | _boolean_ | - |
| title | Title | _string_ | - |
| width | Width | _number \| string_ | `320px` |
| message | Message | _string_ | - |
| message | Message | _string \| () => JSX.ELement_ | - |
| message-align | Message aligncan be set to `left` `right` | _string_ | `center` |
| theme | Theme stylecan be set to `round-button` | _string_ | `default` |
| show-confirm-button | Whether to show confirm button | _boolean_ | `true` |

View File

@ -175,7 +175,7 @@ export default {
| --- | --- | --- | --- |
| title | 标题 | _string_ | - |
| width | 弹窗宽度,默认单位为 `px` | _number \| string_ | `320px` |
| message | 文本内容,支持通过 `\n` 换行 | _string_ | - |
| message | 文本内容,支持通过 `\n` 换行 | _string \| () => JSX.ELement_ | - |
| messageAlign | 内容对齐方式,可选值为 `left` `right` | _string_ | `center` |
| theme | 样式风格,可选值为 `round-button` | _string_ | `default` |
| className | 自定义类名 | _string \| Array \| object_ | - |
@ -205,8 +205,8 @@ export default {
| v-model:show | 是否显示弹窗 | _boolean_ | - |
| title | 标题 | _string_ | - |
| width | 弹窗宽度,默认单位为 `px` | _number \| string_ | `320px` |
| message | 文本内容,支持通过 `\n` 换行 | _string_ | - |
| message-align | 内容对齐方式,可选值为 `left` `right` | _string_ | `center` |
| message | 文本内容,支持通过 `\n` 换行 | _string \| () => JSX.ELement_ | - |
| message-align | 内容水平对齐方式,可选值为 `left` `right` | _string_ | `center` |
| theme | 样式风格,可选值为 `round-button` | _string_ | `default` |
| show-confirm-button | 是否展示确认按钮 | _boolean_ | `true` |
| show-cancel-button | 是否展示取消按钮 | _boolean_ | `false` |

View File

@ -5,6 +5,7 @@ import { mountComponent, usePopupState } from '../utils/mount-component';
import VanDialog, {
DialogTheme,
DialogAction,
DialogMessage,
DialogMessageAlign,
} from './Dialog';
@ -12,7 +13,7 @@ export type DialogOptions = {
title?: string;
width?: string | number;
theme?: DialogTheme;
message?: string;
message?: DialogMessage;
overlay?: boolean;
teleport?: TeleportProps['to'];
className?: unknown;
@ -128,4 +129,4 @@ Dialog.Component = withInstall<typeof VanDialog>(VanDialog);
export default Dialog;
export { Dialog };
export type { DialogTheme, DialogMessageAlign };
export type { DialogTheme, DialogMessage, DialogMessageAlign };

View File

@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should allow to render JSX message 1`] = `
<div class="van-dialog__message">
<div>
foo
</div>
</div>
`;

View File

@ -49,3 +49,17 @@ test('should close dialog after calling Dialog.call', async () => {
'van-dialog-bounce-leave-active'
);
});
test('should allow to render JSX message', async () => {
const wrapper = document.createElement('div');
Dialog.alert({
message: () => <div>foo</div>,
teleport: wrapper,
});
await later();
const dialog = wrapper.querySelector('.van-dialog');
expect(
dialog.querySelector('.van-dialog__message').outerHTML
).toMatchSnapshot();
});