refactor: popover & popconfirm

pull/6227/head
tangjinzhou 2023-02-02 22:16:55 +08:00
parent 47c84cdbad
commit 9b5a07220b
6 changed files with 98 additions and 84 deletions

View File

@ -55,6 +55,14 @@ export function booleanType(defaultVal?: any) {
return { type: Boolean, default: defaultVal as boolean };
}
export function anyType<T = any>() {
return { validator: () => true } as unknown as { type: PropType<T> };
}
export function stringType<T extends string = string>(defaultVal?: string) {
return { type: String as unknown as PropType<T>, default: defaultVal as T };
}
export function someType<T>(types: any[], defaultVal?: any) {
return { type: types as PropType<T>, default: defaultVal as T };
}

View File

@ -133,7 +133,7 @@ export default defineComponent({
});
</script>
<style scoped>
#components-a-popconfirm-demo-placement .ant-btn {
:deep(#components-a-popconfirm-demo-placement) .ant-btn {
width: 70px;
text-align: center;
padding: 0;

View File

@ -28,15 +28,16 @@ The difference with the 'confirm' modal dialog is that it's more lightweight tha
| okType | Button `type` of the Confirm button | string | `primary` | |
| showCancel | Show cancel button | boolean | true | 3.0 |
| title | title of the confirmation box | string\|slot | - | |
| description | The description of the confirmation box title | string\|slot | - | 4.0 |
| visible (v-model) | hide or show | boolean | - | |
### events
| Events Name | Description | Arguments | |
| --- | --- | --- | --- |
| cancel | callback of cancel | function(e) | - |
| confirm | callback of confirmation | function(e) | - |
| visibleChange | Callback executed when visibility of the tooltip card is changed | function(visible) | - |
| Events Name | Description | Arguments | Version |
| --- | --- | --- | --- | --- |
| cancel | callback of cancel | function(e) | - | |
| confirm | callback of confirmation | function(e) | - | |
| openChange | Callback executed when visibility of the tooltip card is changed | function(open) | - | 4.0 |
Consult [Tooltip's documentation](/components/tooltip/#API) to find more APIs.

View File

@ -1,8 +1,7 @@
import type { ExtractPropTypes, HTMLAttributes, PropType } from 'vue';
import { computed, onMounted, ref, toRef, defineComponent } from 'vue';
import Tooltip from '../tooltip';
import { computed, ref, toRef, defineComponent } from 'vue';
import Popover from '../popover';
import abstractTooltipProps from '../tooltip/abstractTooltipProps';
import PropTypes from '../_util/vue-types';
import { initDefaultProps } from '../_util/props-util';
import type { ButtonProps, LegacyButtonType } from '../button/buttonTypes';
import { convertLegacyProps } from '../button/buttonTypes';
@ -10,9 +9,9 @@ import ExclamationCircleFilled from '@ant-design/icons-vue/ExclamationCircleFill
import Button from '../button';
import { useLocaleReceiver } from '../locale-provider/LocaleReceiver';
import defaultLocale from '../locale/en_US';
import { withInstall } from '../_util/type';
import { anyType, objectType, stringType, withInstall } from '../_util/type';
import useMergedState from '../_util/hooks/useMergedState';
import devWarning from '../vc-util/devWarning';
import KeyCode from '../_util/KeyCode';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import classNames from '../_util/classNames';
@ -21,28 +20,21 @@ import { cloneVNodes } from '../_util/vnode';
import omit from '../_util/omit';
import { tooltipDefaultProps } from '../tooltip/Tooltip';
import ActionButton from '../_util/ActionButton';
import usePopconfirmStyle from './style';
export const popconfirmProps = () => ({
...abstractTooltipProps(),
prefixCls: String,
content: PropTypes.any,
title: PropTypes.any,
okType: {
type: String as PropType<LegacyButtonType>,
default: 'primary',
},
content: anyType(),
title: anyType<string | number>(),
description: anyType<string | number>(),
okType: stringType<LegacyButtonType>('primary'),
disabled: { type: Boolean, default: false },
okText: PropTypes.any,
cancelText: PropTypes.any,
icon: PropTypes.any,
okButtonProps: {
type: Object as PropType<ButtonProps & HTMLAttributes>,
default: undefined as ButtonProps & HTMLAttributes,
},
cancelButtonProps: {
type: Object as PropType<ButtonProps & HTMLAttributes>,
default: undefined as ButtonProps & HTMLAttributes,
},
okText: anyType(),
cancelText: anyType(),
icon: anyType(),
okButtonProps: objectType<ButtonProps & HTMLAttributes>(),
cancelButtonProps: objectType<ButtonProps & HTMLAttributes>(),
showCancel: { type: Boolean, default: true },
onConfirm: Function as PropType<(e: MouseEvent) => void>,
onCancel: Function as PropType<(e: MouseEvent) => void>,
@ -58,6 +50,7 @@ export interface PopconfirmLocale {
const Popconfirm = defineComponent({
compatConfig: { MODE: 3 },
name: 'APopconfirm',
inheritAttrs: false,
props: initDefaultProps(popconfirmProps(), {
...tooltipDefaultProps(),
trigger: 'click',
@ -71,37 +64,29 @@ const Popconfirm = defineComponent({
disabled: false,
}),
slots: ['title', 'content', 'okText', 'icon', 'cancelText', 'cancelButton', 'okButton'],
emits: ['update:visible', 'visibleChange'],
setup(props: PopconfirmProps, { slots, emit, expose }) {
onMounted(() => {
devWarning(
props.defaultVisible === undefined,
'Popconfirm',
`'defaultVisible' is deprecated, please use 'v-model:visible'`,
);
});
const tooltipRef = ref();
// emits: ['update:open', 'visibleChange'],
setup(props: PopconfirmProps, { slots, emit, expose, attrs }) {
const rootRef = ref();
expose({
getPopupDomNode: () => {
return tooltipRef.value?.getPopupDomNode?.();
return rootRef.value?.getPopupDomNode?.();
},
});
const [visible, setVisible] = useMergedState(false, {
value: toRef(props, 'visible'),
defaultValue: props.defaultVisible,
const [open, setOpen] = useMergedState(false, {
value: toRef(props, 'open'),
});
const settingVisible = (value: boolean, e?: MouseEvent | KeyboardEvent) => {
if (props.visible === undefined) {
setVisible(value);
const settingOpen = (value: boolean, e?: MouseEvent | KeyboardEvent) => {
if (props.open === undefined) {
setOpen(value);
}
emit('update:visible', value);
emit('visibleChange', value, e);
emit('update:open', value);
emit('openChange', value, e);
};
const close = (e: MouseEvent) => {
settingVisible(false, e);
settingOpen(false, e);
};
const onConfirm = (e: MouseEvent) => {
@ -109,37 +94,38 @@ const Popconfirm = defineComponent({
};
const onCancel = (e: MouseEvent) => {
settingVisible(false, e);
settingOpen(false, e);
props.onCancel?.(e);
};
const onKeyDown = (e: KeyboardEvent) => {
if (e.keyCode === KeyCode.ESC && visible) {
settingVisible(false, e);
if (e.keyCode === KeyCode.ESC && open) {
settingOpen(false, e);
}
};
const onVisibleChange = (value: boolean) => {
const onOpenChange = (value: boolean) => {
const { disabled } = props;
if (disabled) {
return;
}
settingVisible(value);
settingOpen(value);
};
const { prefixCls: prefixClsConfirm, getPrefixCls } = useConfigInject('popconfirm', props);
const rootPrefixCls = computed(() => getPrefixCls());
const popoverPrefixCls = computed(() => getPrefixCls('popover'));
const btnPrefixCls = computed(() => getPrefixCls('btn'));
const [wrapSSR] = usePopconfirmStyle(prefixClsConfirm);
const [popconfirmLocale] = useLocaleReceiver('Popconfirm', defaultLocale.Popconfirm);
const renderOverlay = () => {
const {
okButtonProps,
cancelButtonProps,
title = slots.title?.(),
description = slots.description?.(),
cancelText = slots.cancel?.(),
okText = slots.okText?.(),
okType,
icon = slots.icon?.(),
icon = slots.icon?.() || <ExclamationCircleFilled />,
showCancel = true,
} = props;
const { cancelButton, okButton } = slots;
@ -155,12 +141,20 @@ const Popconfirm = defineComponent({
...okButtonProps,
};
return (
<div class={`${popoverPrefixCls.value}-inner-content`}>
<div class={`${popoverPrefixCls.value}-message`}>
{icon || <ExclamationCircleFilled />}
<div class={`${popoverPrefixCls.value}-message-title`}>{title}</div>
<div class={`${prefixClsConfirm.value}-inner-content`}>
<div class={`${prefixClsConfirm.value}-message`}>
{icon && <span class={`${prefixClsConfirm.value}-message-icon`}>{icon}</span>}
<div
class={[
`${prefixClsConfirm.value}-message-title`,
{ [`${prefixClsConfirm.value}-message-title-only`]: !!description },
]}
>
{title}
</div>
</div>
<div class={`${popoverPrefixCls.value}-buttons`}>
{description && <div class={`${prefixClsConfirm.value}-description`}>{description}</div>}
<div class={`${prefixClsConfirm.value}-buttons`}>
{showCancel ? (
cancelButton ? (
cancelButton(cancelProps)
@ -188,28 +182,31 @@ const Popconfirm = defineComponent({
};
return () => {
const { placement, overlayClassName, ...restProps } = props;
const { placement, overlayClassName, trigger = 'click', ...restProps } = props;
const otherProps = omit(restProps, [
'title',
'content',
'cancelText',
'okText',
'onUpdate:visible',
'onUpdate:open',
'onConfirm',
'onCancel',
'prefixCls',
]);
const overlayClassNames = classNames(prefixClsConfirm.value, overlayClassName);
return (
<Tooltip
return wrapSSR(
<Popover
{...otherProps}
prefixCls={popoverPrefixCls.value}
{...attrs}
trigger={trigger}
placement={placement}
onVisibleChange={onVisibleChange}
visible={visible.value}
onOpenChange={onOpenChange}
open={open.value}
overlayClassName={overlayClassNames}
transitionName={getTransitionName(rootPrefixCls.value, 'zoom-big', props.transitionName)}
v-slots={{ title: renderOverlay }}
ref={tooltipRef}
v-slots={{ content: renderOverlay }}
ref={rootRef}
data-popover-inject
>
{cloneVNodes(
slots.default?.() || [],
@ -220,7 +217,7 @@ const Popconfirm = defineComponent({
},
false,
)}
</Tooltip>
</Popover>,
);
};
},

View File

@ -27,15 +27,16 @@ cover: https://gw.alipayobjects.com/zos/alicdn/fjMCD9xRq/Popconfirm.svg
| okType | 确认按钮类型 | string | primary | |
| showCancel | 是否显示取消按钮 | boolean | true | 3.0 |
| title | 确认框的描述 | string\|slot | 无 | |
| visible (v-model) | 是否显示 | boolean | - | |
| description | 确认内容的详细描述 | string\|slot | - | 4.0 |
| open (v-model) | 是否显示 | boolean | - | 4.0 |
### 事件
| 事件名称 | 说明 | 回调参数 |
| ------------- | -------------- | ----------------- |
| cancel | 点击取消的回调 | function(e) |
| confirm | 点击确认的回调 | function(e) |
| visibleChange | 显示隐藏的回调 | function(visible) |
| 事件名称 | 说明 | 回调参数 | 版本 |
| ---------- | -------------- | -------------- | ---- |
| cancel | 点击取消的回调 | function(e) | |
| confirm | 点击确认的回调 | function(e) | |
| openChange | 显示隐藏的回调 | function(open) | 4.0 |
更多属性请参考 [Tooltip](/components/tooltip-cn/#API)。

View File

@ -2,18 +2,19 @@ import type { ExtractPropTypes } from 'vue';
import { computed, defineComponent, ref } from 'vue';
import Tooltip from '../tooltip';
import abstractTooltipProps from '../tooltip/abstractTooltipProps';
import PropTypes from '../_util/vue-types';
import { filterEmpty, initDefaultProps } from '../_util/props-util';
import { withInstall } from '../_util/type';
import { anyType, withInstall } from '../_util/type';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import omit from '../_util/omit';
import { getTransitionName } from '../_util/transition';
import { tooltipDefaultProps } from '../tooltip/Tooltip';
import useStyle from './style';
import classNames from '../_util/classNames';
export const popoverProps = () => ({
...abstractTooltipProps(),
content: PropTypes.any,
title: PropTypes.any,
content: anyType(),
title: anyType(),
});
export type PopoverProps = Partial<ExtractPropTypes<ReturnType<typeof popoverProps>>>;
@ -21,6 +22,7 @@ export type PopoverProps = Partial<ExtractPropTypes<ReturnType<typeof popoverPro
const Popover = defineComponent({
compatConfig: { MODE: 3 },
name: 'APopover',
inheritAttrs: false,
props: initDefaultProps(popoverProps(), {
...tooltipDefaultProps(),
trigger: 'hover',
@ -29,7 +31,7 @@ const Popover = defineComponent({
mouseEnterDelay: 0.1,
mouseLeaveDelay: 0.1,
}),
setup(props, { expose, slots }) {
setup(props, { expose, slots, attrs }) {
const tooltipRef = ref();
expose({
@ -38,13 +40,14 @@ const Popover = defineComponent({
},
});
const { prefixCls, configProvider } = useConfigInject('popover', props);
const [wrapSSR, hashId] = useStyle(prefixCls);
const rootPrefixCls = computed(() => configProvider.getPrefixCls());
const getOverlay = () => {
const { title = filterEmpty(slots.title?.()), content = filterEmpty(slots.content?.()) } =
props;
const hasTitle = !!(Array.isArray(title) ? title.length : title);
const hasContent = !!(Array.isArray(content) ? content.length : title);
if (!hasTitle && !hasContent) return undefined;
if (!hasTitle && !hasContent) return null;
return (
<>
{hasTitle && <div class={`${prefixCls.value}-title`}>{title}</div>}
@ -53,14 +56,18 @@ const Popover = defineComponent({
);
};
return () => {
return (
const overlayCls = classNames(props.overlayClassName, hashId.value);
return wrapSSR(
<Tooltip
{...omit(props, ['title', 'content'])}
{...attrs}
prefixCls={prefixCls.value}
ref={tooltipRef}
overlayClassName={overlayCls}
v-slots={{ title: getOverlay, default: slots.default }}
transitionName={getTransitionName(rootPrefixCls.value, 'zoom-big', props.transitionName)}
/>
data-popover-inject
/>,
);
};
},