🌈 An enterprise-class UI components based on Ant Design and Vue. 🐜
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

230 lines
6.9 KiB

import type { CSSProperties } from 'vue';
import { watch, computed, defineComponent, ref, TransitionGroup } from 'vue';
import type { NoticeProps } from './Notice';
import Notice from './Notice';
import type { CSSMotionProps } from '../_util/transition';
import { getTransitionGroupProps } from '../_util/transition';
import type { Key, VueNode } from '../_util/type';
import classNames from '../_util/classNames';
import Portal from '../_util/Portal';
let seed = 0;
const now = Date.now();
export function getUuid() {
const id = seed;
seed += 1;
return `rcNotification_${now}_${id}`;
}
export type Placement = 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight';
export interface OpenConfig extends NoticeProps {
key: Key;
placement?: Placement;
content?: VueNode;
duration?: number | null;
}
export type Placements = Partial<Record<Placement, OpenConfig[]>>;
export interface NoticeContent extends Omit<NoticeProps, 'prefixCls' | 'noticeKey' | 'onClose'> {
prefixCls?: string;
key?: Key;
updateMark?: string;
content?: any;
onClose?: () => void;
style?: CSSProperties;
class?: String;
placement?: Placement;
}
export type NoticeFunc = (noticeProps: NoticeContent) => void;
export type HolderReadyCallback = (
div: HTMLDivElement,
noticeProps: NoticeProps & { key: Key },
) => void;
export interface NotificationInstance {
notice: NoticeFunc;
removeNotice: (key: Key) => void;
destroy: () => void;
add: (noticeProps: NoticeContent) => void;
component: Notification;
}
export interface HookNotificationProps {
prefixCls?: string;
transitionName?: string;
animation?: string | CSSMotionProps | ((placement?: Placement) => CSSMotionProps);
maxCount?: number;
closeIcon?: any;
hashId?: string;
// Hook Notification
remove: (key: Key) => void;
notices: NotificationState;
getStyles?: (placement?: Placement) => CSSProperties;
getClassName?: (placement?: Placement) => string;
onAllRemoved?: VoidFunction;
getContainer?: () => HTMLElement;
}
type NotificationState = {
notice: NoticeContent & {
userPassKey?: Key;
};
holderCallback?: HolderReadyCallback;
}[];
const Notification = defineComponent<HookNotificationProps>({
name: 'HookNotification',
inheritAttrs: false,
props: [
'prefixCls',
'transitionName',
'animation',
'maxCount',
'closeIcon',
'hashId',
'remove',
'notices',
'getStyles',
'getClassName',
'onAllRemoved',
'getContainer',
] as any,
setup(props, { attrs, slots }) {
const hookRefs = new Map<Key, HTMLDivElement>();
const notices = computed(() => props.notices);
const transitionProps = computed(() => {
let name = props.transitionName;
if (!name && props.animation) {
switch (typeof props.animation) {
case 'string':
name = props.animation;
break;
case 'function':
name = props.animation().name;
break;
case 'object':
name = props.animation.name;
break;
default:
name = `${props.prefixCls}-fade`;
break;
}
}
return getTransitionGroupProps(name);
});
const remove = (key: Key) => props.remove(key);
const placements = ref({} as Record<Placement, NotificationState>);
watch(notices, () => {
const nextPlacements = {} as any;
// init placements with animation
Object.keys(placements.value).forEach(placement => {
nextPlacements[placement] = [];
});
props.notices.forEach(config => {
const { placement = 'topRight' } = config.notice;
if (placement) {
nextPlacements[placement] = nextPlacements[placement] || [];
nextPlacements[placement].push(config);
}
});
placements.value = nextPlacements;
});
const placementList = computed(() => Object.keys(placements.value) as Placement[]);
return () => {
const { prefixCls, closeIcon = slots.closeIcon?.({ prefixCls }) } = props;
const noticeNodes = placementList.value.map(placement => {
const noticesForPlacement = placements.value[placement];
const classes = props.getClassName?.(placement);
const styles = props.getStyles?.(placement);
const noticeNodesForPlacement = noticesForPlacement.map(
({ notice, holderCallback }, index) => {
const updateMark = index === notices.value.length - 1 ? notice.updateMark : undefined;
const { key, userPassKey } = notice;
const { content } = notice;
const noticeProps = {
prefixCls,
closeIcon: typeof closeIcon === 'function' ? closeIcon({ prefixCls }) : closeIcon,
...(notice as any),
...notice.props,
key,
noticeKey: userPassKey || key,
updateMark,
onClose: (noticeKey: Key) => {
remove(noticeKey);
notice.onClose?.();
},
onClick: notice.onClick,
};
if (holderCallback) {
return (
<div
key={key}
class={`${prefixCls}-hook-holder`}
ref={(div: HTMLDivElement) => {
if (typeof key === 'undefined') {
return;
}
if (div) {
hookRefs.set(key, div);
holderCallback(div, noticeProps);
} else {
hookRefs.delete(key);
}
}}
/>
);
}
return (
<Notice {...noticeProps} class={classNames(noticeProps.class, props.hashId)}>
{typeof content === 'function' ? content({ prefixCls }) : content}
</Notice>
);
},
);
const className = {
[prefixCls]: 1,
[`${prefixCls}-${placement}`]: 1,
[attrs.class as string]: !!attrs.class,
[props.hashId]: true,
[classes]: !!classes,
};
function onAfterLeave() {
if (noticesForPlacement.length > 0) {
return;
}
Reflect.deleteProperty(placements.value, placement);
props.onAllRemoved?.();
}
return (
<div
key={placement}
class={className}
style={
(attrs.style as CSSProperties) ||
styles || {
top: '65px',
left: '50%',
}
}
>
<TransitionGroup tag="div" {...transitionProps.value} onAfterLeave={onAfterLeave}>
{noticeNodesForPlacement}
</TransitionGroup>
</div>
);
});
return <Portal getContainer={props.getContainer}>{noticeNodes}</Portal>;
};
},
});
export default Notification;