vant/src/dialog/Dialog.tsx
neverland bd609e1df0
perf: add truthProp util (#8522)
* perf: add TruthyProp util

* chore: rename

* chore: upd
2021-04-14 10:26:21 +08:00

240 lines
6.3 KiB
TypeScript

import { PropType, reactive, defineComponent } from 'vue';
// Utils
import { callInterceptor, Interceptor } from '../utils/interceptor';
import {
pick,
extend,
addUnit,
truthProp,
isFunction,
unknownProp,
createNamespace,
} from '../utils';
import { BORDER_TOP, BORDER_LEFT } from '../utils/constant';
import { popupSharedProps, popupSharedPropKeys } from '../popup/shared';
// Components
import { Popup } from '../popup';
import { Button } from '../button';
import { ActionBar } from '../action-bar';
import { ActionBarButton } from '../action-bar-button';
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 = [
...popupSharedPropKeys,
'transition',
'closeOnPopstate',
] as const;
export default defineComponent({
name,
props: extend({}, popupSharedProps, {
title: String,
theme: String as PropType<DialogTheme>,
width: [Number, String],
message: [String, Function] as PropType<DialogMessage>,
callback: Function as PropType<(action?: DialogAction) => void>,
allowHtml: Boolean,
className: unknownProp,
beforeClose: Function as PropType<Interceptor>,
messageAlign: String as PropType<DialogMessageAlign>,
closeOnPopstate: truthProp,
showCancelButton: Boolean,
cancelButtonText: String,
cancelButtonColor: String,
confirmButtonText: String,
confirmButtonColor: String,
showConfirmButton: truthProp,
closeOnClickOverlay: Boolean,
transition: {
type: String,
default: 'van-dialog-bounce',
},
}),
emits: ['confirm', 'cancel', 'update:show'],
setup(props, { emit, slots }) {
const loading = reactive({
confirm: false,
cancel: false,
});
const updateShow = (value: boolean) => emit('update:show', value);
const close = (action: DialogAction) => {
updateShow(false);
if (props.callback) {
props.callback(action);
}
};
const getActionHandler = (action: DialogAction) => () => {
// should not trigger close event when hidden
if (!props.show) {
return;
}
emit(action);
if (props.beforeClose) {
loading[action] = true;
callInterceptor({
interceptor: props.beforeClose,
args: [action],
done() {
close(action);
loading[action] = false;
},
canceled() {
loading[action] = false;
},
});
} else {
close(action);
}
};
const onCancel = getActionHandler('cancel');
const onConfirm = getActionHandler('confirm');
const renderTitle = () => {
const title = slots.title ? slots.title() : props.title;
if (title) {
return (
<div
class={bem('header', {
isolated: !props.message && !slots.default,
})}
>
{title}
</div>
);
}
};
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 } = props;
if (message) {
const hasTitle = !!(title || slots.title);
return (
<div
// add key to force re-render
// see: https://github.com/youzan/vant/issues/7963
key={allowHtml ? 1 : 0}
class={bem('content', { isolated: !hasTitle })}
>
{renderMessage(hasTitle)}
</div>
);
}
};
const renderButtons = () => (
<div class={[BORDER_TOP, bem('footer')]}>
{props.showCancelButton && (
<Button
size="large"
text={props.cancelButtonText || t('cancel')}
class={bem('cancel')}
style={{ color: props.cancelButtonColor }}
loading={loading.cancel}
onClick={onCancel}
/>
)}
{props.showConfirmButton && (
<Button
size="large"
text={props.confirmButtonText || t('confirm')}
class={[bem('confirm'), { [BORDER_LEFT]: props.showCancelButton }]}
style={{ color: props.confirmButtonColor }}
loading={loading.confirm}
onClick={onConfirm}
/>
)}
</div>
);
const renderRoundButtons = () => (
<ActionBar class={bem('footer')}>
{props.showCancelButton && (
<ActionBarButton
type="warning"
text={props.cancelButtonText || t('cancel')}
class={bem('cancel')}
color={props.cancelButtonColor}
loading={loading.cancel}
onClick={onCancel}
/>
)}
{props.showConfirmButton && (
<ActionBarButton
type="danger"
text={props.confirmButtonText || t('confirm')}
class={bem('confirm')}
color={props.confirmButtonColor}
loading={loading.confirm}
onClick={onConfirm}
/>
)}
</ActionBar>
);
const renderFooter = () => {
if (slots.footer) {
return slots.footer();
}
return props.theme === 'round-button'
? renderRoundButtons()
: renderButtons();
};
return () => {
const { width, title, theme, message, className } = props;
return (
<Popup
role="dialog"
class={[bem([theme]), className]}
style={{ width: addUnit(width) }}
aria-labelledby={title || message}
{...pick(props, popupKeys)}
{...{ 'onUpdate:show': updateShow }}
>
{renderTitle()}
{renderContent()}
{renderFooter()}
</Popup>
);
};
},
});