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

View File

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

View File

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

View File

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