refactor: useNotification #6527

This commit is contained in:
tangjinzhou 2023-05-05 11:55:42 +08:00
parent 02ed988cba
commit feffe70ebb
8 changed files with 148 additions and 194 deletions

View File

@ -4,7 +4,7 @@ import type { Key, VueNode } from '../_util/type';
export type NoticeType = 'info' | 'success' | 'error' | 'warning' | 'loading';
export interface ConfigOptions {
top?: number;
top?: number | string;
duration?: number;
prefixCls?: string;
getContainer?: () => HTMLElement;

View File

@ -56,11 +56,14 @@ const Holder = defineComponent({
const [, hashId] = useStyle(prefixCls);
// =============================== Style ===============================
const getStyles = () => ({
left: '50%',
transform: 'translateX(-50%)',
top: `${top ?? DEFAULT_OFFSET}px`,
});
const getStyles = () => {
const top = props.top ?? DEFAULT_OFFSET;
return {
left: '50%',
transform: 'translateX(-50%)',
top: typeof top === 'number' ? `${top}px` : top,
};
};
const getClassName = () => classNames(hashId.value, props.rtl ? `${prefixCls.value}-rtl` : '');
// ============================== Motion ===============================

View File

@ -1,4 +1,4 @@
import { computed } from 'vue';
import { computed, defineComponent } from 'vue';
import useStyle from './style';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import type { IconType } from './interface';
@ -6,13 +6,12 @@ import Notice from '../vc-notification/Notice';
import classNames from '../_util/classNames';
import type { NoticeProps } from '../vc-notification/Notice';
import type { VueNode } from '../_util/type';
import {
CheckCircleOutlined,
CloseCircleOutlined,
CloseOutlined,
ExclamationCircleOutlined,
InfoCircleOutlined,
} from '@ant-design/icons-vue';
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
import ExclamationCircleFilled from '@ant-design/icons-vue/ExclamationCircleFilled';
import CloseCircleFilled from '@ant-design/icons-vue/CloseCircleFilled';
import CheckCircleFilled from '@ant-design/icons-vue/CheckCircleFilled';
import InfoCircleFilled from '@ant-design/icons-vue/InfoCircleFilled';
import CloseOutlined from '@ant-design/icons-vue/CloseOutlined';
import { renderHelper } from '../_util/util';
export function getCloseIcon(prefixCls: string, closeIcon?: VueNode) {
@ -34,11 +33,19 @@ export interface PureContentProps {
type?: IconType;
}
export const TypeIcon = {
info: <InfoCircleFilled />,
success: <CheckCircleFilled />,
error: <CloseCircleFilled />,
warning: <ExclamationCircleFilled />,
loading: <LoadingOutlined />,
};
const typeToIcon = {
success: CheckCircleOutlined,
info: InfoCircleOutlined,
error: CloseCircleOutlined,
warning: ExclamationCircleOutlined,
success: CheckCircleFilled,
info: InfoCircleFilled,
error: CloseCircleFilled,
warning: ExclamationCircleFilled,
};
export function PureContent({
@ -74,36 +81,42 @@ export function PureContent({
export interface PurePanelProps
extends Omit<NoticeProps, 'prefixCls' | 'eventKey'>,
Omit<PureContentProps, 'prefixCls' | 'children'> {
Omit<PureContentProps, 'prefixCls'> {
prefixCls?: string;
}
/** @private Internal Component. Do not use in your production. */
export default function PurePanel(props: PurePanelProps) {
const { getPrefixCls } = useConfigInject('notification', props);
const prefixCls = computed(() => props.prefixCls || getPrefixCls('notification'));
const noticePrefixCls = `${prefixCls.value}-notice`;
export default defineComponent<PurePanelProps>({
name: 'PurePanel',
inheritAttrs: false,
props: ['prefixCls', 'icon', 'type', 'message', 'description', 'btn', 'closeIcon'] as any,
setup(props) {
const { getPrefixCls } = useConfigInject('notification', props);
const prefixCls = computed(() => props.prefixCls || getPrefixCls('notification'));
const noticePrefixCls = computed(() => `${prefixCls.value}-notice`);
const [, hashId] = useStyle(prefixCls);
return (
<Notice
{...props}
prefixCls={prefixCls.value}
class={classNames(hashId.value, `${noticePrefixCls}-pure-panel`)}
noticeKey="pure"
duration={null}
closable={props.closable}
closeIcon={getCloseIcon(prefixCls.value, props.closeIcon)}
>
<PureContent
prefixCls={noticePrefixCls}
icon={props.icon}
type={props.type}
message={props.message}
description={props.description}
btn={props.btn}
/>
</Notice>
);
}
const [, hashId] = useStyle(prefixCls);
return () => {
return (
<Notice
{...props}
prefixCls={prefixCls.value}
class={classNames(hashId.value, `${noticePrefixCls.value}-pure-panel`)}
noticeKey="pure"
duration={null}
closable={props.closable}
closeIcon={getCloseIcon(prefixCls.value, props.closeIcon)}
>
<PureContent
prefixCls={noticePrefixCls.value}
icon={props.icon}
type={props.type}
message={props.message}
description={props.description}
btn={props.btn}
/>
</Notice>
);
};
},
});

View File

@ -12,6 +12,7 @@ import type { NotificationInstance as VCNotificationInstance } from '../vc-notif
import classNames from '../_util/classNames';
import useStyle from './style';
import useNotification from './useNotification';
import { getPlacementStyle } from './util';
export type NotificationPlacement =
| 'top'
@ -77,63 +78,6 @@ function setNotificationConfig(options: ConfigProps) {
}
}
function getPlacementStyle(
placement: NotificationPlacement,
top: string = defaultTop,
bottom: string = defaultBottom,
) {
let style: CSSProperties;
switch (placement) {
case 'top':
style = {
left: '50%',
transform: 'translateX(-50%)',
right: 'auto',
top,
bottom: 'auto',
};
break;
case 'topLeft':
style = {
left: '0px',
top,
bottom: 'auto',
};
break;
case 'topRight':
style = {
right: '0px',
top,
bottom: 'auto',
};
break;
case 'bottom':
style = {
left: '50%',
transform: 'translateX(-50%)',
right: 'auto',
top: 'auto',
bottom,
};
break;
case 'bottomLeft':
style = {
left: '0px',
top: 'auto',
bottom,
};
break;
default:
style = {
right: '0px',
top: 'auto',
bottom,
};
break;
}
return style;
}
function getNotificationInstance(
{
prefixCls: customizePrefixCls,
@ -167,7 +111,7 @@ function getNotificationInstance(
prefixCls: customizePrefixCls || defaultPrefixCls,
useStyle,
class: notificationClass,
style: getPlacementStyle(placement, top, bottom),
style: getPlacementStyle(placement, top ?? defaultTop, bottom ?? defaultBottom),
appContext,
getContainer,
closeIcon: ({ prefixCls }) => {
@ -210,8 +154,8 @@ export interface NotificationArgsProps {
class?: string;
readonly type?: IconType;
onClick?: () => void;
top?: string;
bottom?: string;
top?: string | number;
bottom?: string | number;
getContainer?: () => HTMLElement;
closeIcon?: VueNode | (() => VueNode);
appContext?: any;

View File

@ -12,19 +12,19 @@ export type NotificationPlacement =
export type IconType = 'success' | 'info' | 'error' | 'warning';
export interface ArgsProps {
message: VueNode;
description?: VueNode;
btn?: VueNode;
message: (() => VueNode) | VueNode;
description?: (() => VueNode) | VueNode;
btn?: (() => VueNode) | VueNode;
key?: Key;
onClose?: () => void;
duration?: number | null;
icon?: VueNode;
icon?: (() => VueNode) | VueNode;
placement?: NotificationPlacement;
style?: CSSProperties;
className?: string;
class?: string;
readonly type?: IconType;
onClick?: () => void;
closeIcon?: VueNode;
closeIcon?: (() => VueNode) | VueNode;
}
type StaticFn = (args: ArgsProps) => void;
@ -39,20 +39,20 @@ export interface NotificationInstance {
}
export interface GlobalConfigProps {
top?: number;
bottom?: number;
top?: number | string;
bottom?: number | string;
duration?: number;
prefixCls?: string;
getContainer?: () => HTMLElement;
placement?: NotificationPlacement;
closeIcon?: VueNode;
closeIcon?: (() => VueNode) | VueNode;
rtl?: boolean;
maxCount?: number;
}
export interface NotificationConfig {
top?: number;
bottom?: number;
top?: number | string;
bottom?: number | string;
prefixCls?: string;
getContainer?: () => HTMLElement;
placement?: NotificationPlacement;

View File

@ -84,68 +84,64 @@ export function useInternalNotification(
notificationConfig?: HolderProps,
): readonly [NotificationInstance, () => VNode] {
const holderRef = shallowRef<HolderRef>(null);
const holderKey = Symbol('notificationHolderKey');
// ================================ API ================================
const wrapAPI = computed(() => {
// Wrap with notification content
// Wrap with notification content
// >>> Open
const open = (config: ArgsProps) => {
if (!holderRef.value) {
return;
}
const { open: originOpen, prefixCls, hashId } = holderRef.value;
const noticePrefixCls = `${prefixCls}-notice`;
// >>> Open
const open = (config: ArgsProps) => {
if (!holderRef.value) {
return;
}
const { open: originOpen, prefixCls, hashId } = holderRef.value;
const noticePrefixCls = `${prefixCls}-notice`;
const { message, description, icon, type, btn, className, ...restConfig } = config;
return originOpen({
placement: 'topRight',
...restConfig,
content: (
<PureContent
prefixCls={noticePrefixCls}
icon={icon}
type={type}
message={message}
description={description}
btn={btn}
/>
),
// @ts-ignore
class: classNames(type && `${noticePrefixCls}-${type}`, hashId, className),
});
};
// >>> destroy
const destroy = (key?: Key) => {
if (key !== undefined) {
holderRef.value?.close(key);
} else {
holderRef.value?.destroy();
}
};
const clone = {
open,
destroy,
} as NotificationInstance;
const keys = ['success', 'info', 'warning', 'error'] as const;
keys.forEach(type => {
clone[type] = config =>
open({
...config,
type,
});
const { message, description, icon, type, btn, class: className, ...restConfig } = config;
return originOpen({
placement: 'topRight',
...restConfig,
content: () => (
<PureContent
prefixCls={noticePrefixCls}
icon={typeof icon === 'function' ? icon() : icon}
type={type}
message={typeof message === 'function' ? message() : message}
description={typeof description === 'function' ? description() : description}
btn={typeof btn === 'function' ? btn() : btn}
/>
),
// @ts-ignore
class: classNames(type && `${noticePrefixCls}-${type}`, hashId, className),
});
};
return clone;
// >>> destroy
const destroy = (key?: Key) => {
if (key !== undefined) {
holderRef.value?.close(key);
} else {
holderRef.value?.destroy();
}
};
const wrapAPI = {
open,
destroy,
} as NotificationInstance;
const keys = ['success', 'info', 'warning', 'error'] as const;
keys.forEach(type => {
wrapAPI[type] = config =>
open({
...config,
type,
});
});
// ============================== Return ===============================
return [
wrapAPI.value,
() => <Holder key="notification-holder" {...notificationConfig} ref={holderRef} />,
wrapAPI,
() => <Holder key={holderKey} {...notificationConfig} ref={holderRef} />,
] as const;
}

View File

@ -29,7 +29,7 @@ export interface NoticeContent extends Omit<NoticeProps, 'prefixCls' | 'noticeKe
prefixCls?: string;
key?: Key;
updateMark?: string;
content?: any;
content?: string | ((arg: { prefixCls: string }) => VueNode) | VueNode;
onClose?: () => void;
style?: CSSProperties;
class?: String;

View File

@ -1,5 +1,5 @@
import type { CSSProperties } from 'vue';
import { shallowRef, watch, ref, computed } from 'vue';
import { shallowRef, watch, computed } from 'vue';
import HookNotification, { getUuid } from './HookNotification';
import type { NotificationInstance, OpenConfig, Placement } from './Notification';
import type { CSSMotionProps } from '../_util/transition';
@ -82,7 +82,7 @@ export default function useNotification(rootConfig: NotificationConfig = {}) {
...shareConfig
} = rootConfig;
const notices = ref([]);
const notices = shallowRef([]);
const notificationsRef = shallowRef<NotificationInstance>();
const add = (originNotice: NoticeContent, holderCallback?: HolderReadyCallback) => {
const key = originNotice.key || getUuid();
@ -132,29 +132,27 @@ export default function useNotification(rootConfig: NotificationConfig = {}) {
></HookNotification>
));
const taskQueue = ref([] as Task[]);
const taskQueue = shallowRef([] as Task[]);
// ========================= Refs =========================
const api = computed(() => {
return {
open: (config: OpenConfig) => {
const mergedConfig = mergeConfig(shareConfig, config);
const api = {
open: (config: OpenConfig) => {
const mergedConfig = mergeConfig(shareConfig, config);
//@ts-ignore
if (mergedConfig.key === null || mergedConfig.key === undefined) {
//@ts-ignore
if (mergedConfig.key === null || mergedConfig.key === undefined) {
//@ts-ignore
mergedConfig.key = `vc-notification-${uniqueKey}`;
uniqueKey += 1;
}
mergedConfig.key = `vc-notification-${uniqueKey}`;
uniqueKey += 1;
}
taskQueue.value = [...taskQueue.value, { type: 'open', config: mergedConfig as any }];
},
close: key => {
taskQueue.value = [...taskQueue.value, { type: 'close', key }];
},
destroy: () => {
taskQueue.value = [...taskQueue.value, { type: 'destroy' }];
},
};
});
taskQueue.value = [...taskQueue.value, { type: 'open', config: mergedConfig as any }];
},
close: key => {
taskQueue.value = [...taskQueue.value, { type: 'close', key }];
},
destroy: () => {
taskQueue.value = [...taskQueue.value, { type: 'destroy' }];
},
};
// ======================== Effect ========================
watch(taskQueue, () => {
@ -180,5 +178,5 @@ export default function useNotification(rootConfig: NotificationConfig = {}) {
});
// ======================== Return ========================
return [api.value, () => contextHolder.value] as const;
return [api, () => contextHolder.value] as const;
}