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 type NoticeType = 'info' | 'success' | 'error' | 'warning' | 'loading';
export interface ConfigOptions { export interface ConfigOptions {
top?: number; top?: number | string;
duration?: number; duration?: number;
prefixCls?: string; prefixCls?: string;
getContainer?: () => HTMLElement; getContainer?: () => HTMLElement;

View File

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

View File

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

View File

@ -12,6 +12,7 @@ import type { NotificationInstance as VCNotificationInstance } from '../vc-notif
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import useStyle from './style'; import useStyle from './style';
import useNotification from './useNotification'; import useNotification from './useNotification';
import { getPlacementStyle } from './util';
export type NotificationPlacement = export type NotificationPlacement =
| 'top' | '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( function getNotificationInstance(
{ {
prefixCls: customizePrefixCls, prefixCls: customizePrefixCls,
@ -167,7 +111,7 @@ function getNotificationInstance(
prefixCls: customizePrefixCls || defaultPrefixCls, prefixCls: customizePrefixCls || defaultPrefixCls,
useStyle, useStyle,
class: notificationClass, class: notificationClass,
style: getPlacementStyle(placement, top, bottom), style: getPlacementStyle(placement, top ?? defaultTop, bottom ?? defaultBottom),
appContext, appContext,
getContainer, getContainer,
closeIcon: ({ prefixCls }) => { closeIcon: ({ prefixCls }) => {
@ -210,8 +154,8 @@ export interface NotificationArgsProps {
class?: string; class?: string;
readonly type?: IconType; readonly type?: IconType;
onClick?: () => void; onClick?: () => void;
top?: string; top?: string | number;
bottom?: string; bottom?: string | number;
getContainer?: () => HTMLElement; getContainer?: () => HTMLElement;
closeIcon?: VueNode | (() => VueNode); closeIcon?: VueNode | (() => VueNode);
appContext?: any; appContext?: any;

View File

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

View File

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

View File

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

View File

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