import { useRef } from 'react';
import ReactDOM from 'react-dom';
import { useRefExtra } from '@react-devui/hooks';
import { registerComponentMate } from '../../utils';
import { DAlertPopover } from '../_alert-popover';
import { DTransition } from '../_transition';
import { useComponentConfig, usePrefixConfig } from '../root';
import { DPanel } from './Panel';
export interface DNotificationProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> {
dVisible: boolean;
dType?: 'success' | 'warning' | 'error' | 'info';
dIcon?: React.ReactNode;
dTitle: React.ReactNode;
dDescription?: React.ReactNode;
dDuration?: number;
dPlacement?: 'left-top' | 'right-top' | 'left-bottom' | 'right-bottom';
dActions?: React.ReactNode[];
dEscClosable?: boolean;
onClose?: () => void;
afterVisibleChange?: (visible: boolean) => void;
}
const TTANSITION_DURING = { enter: 133, leave: 166 };
const { COMPONENT_NAME } = registerComponentMate({ COMPONENT_NAME: 'DNotification' as const });
export function DNotification(props: DNotificationProps): JSX.Element | null {
const {
dVisible,
dType,
dIcon,
dTitle,
dDescription,
dDuration = 9.6,
dPlacement = 'right-top',
dActions = ['close'],
dEscClosable = true,
onClose,
afterVisibleChange,
...restProps
} = useComponentConfig(COMPONENT_NAME, props);
const dPrefix = usePrefixConfig();
const panelRef = useRef<HTMLDivElement>(null);
const getRoot = (id: string) => {
let root = document.getElementById(`${dPrefix}notification-root`);
if (!root) {
root = document.createElement('div');
root.id = `${dPrefix}notification-root`;
document.body.appendChild(root);
}
let el = document.getElementById(id);
if (!el) {
el = document.createElement('div');
el.id = id;
root.appendChild(el);
}
return el;
};
const notificationLTRootRef = useRefExtra(() => getRoot(`${dPrefix}notification-lt-root`), true);
const notificationRTRootRef = useRefExtra(() => getRoot(`${dPrefix}notification-rt-root`), true);
const notificationLBRootRef = useRefExtra(() => getRoot(`${dPrefix}notification-lb-root`), true);
const notificationRBRootRef = useRefExtra(() => getRoot(`${dPrefix}notification-rb-root`), true);
const rootRef =
dPlacement === 'left-top'
? notificationLTRootRef
: dPlacement === 'right-top'
? notificationRTRootRef
: dPlacement === 'left-bottom'
? notificationLBRootRef
: notificationRBRootRef;
return (
rootRef.current &&
ReactDOM.createPortal(
<DTransition
dIn={dVisible}
dDuring={TTANSITION_DURING}
dSkipFirstTransition={false}
afterEnter={() => {
afterVisibleChange?.(true);
}}
afterLeave={() => {
afterVisibleChange?.(false);
}}
>
{(state) => {
let transitionStyle: React.CSSProperties = {};
switch (state) {
case 'enter':
transitionStyle = {
transform: dPlacement === 'left-top' || dPlacement === 'left-bottom' ? 'translate(-100%, 0)' : 'translate(100%, 0)',
};
break;
case 'entering':
transitionStyle = {
transition: ['transform'].map((attr) => `${attr} ${TTANSITION_DURING.enter}ms ease-out`).join(', '),
};
break;
case 'leave':
if (panelRef.current) {
const height = panelRef.current.offsetHeight;
transitionStyle = { height, overflow: 'hidden' };
}
break;
case 'leaving':
transitionStyle = {
height: 0,
overflow: 'hidden',
paddingTop: 0,
paddingBottom: 0,
marginTop: 0,
marginBottom: 0,
opacity: 0,
transition: ['height', 'padding', 'margin', 'opacity']
.map((attr) => `${attr} ${TTANSITION_DURING.leave}ms ease-in`)
.join(', '),
};
break;
case 'leaved':
transitionStyle = { display: 'none' };
break;
default:
break;
}
return (
<DAlertPopover dDuration={dDuration} dEscClosable={dEscClosable} onClose={onClose}>
{({ render }) =>
render(
<DPanel
{...restProps}
ref={panelRef}
style={{
...restProps.style,
...transitionStyle,
}}
dClassNamePrefix="notification"
dType={dType}
dIcon={dIcon}
dTitle={dTitle}
dDescription={dDescription}
dActions={dActions}
onClose={onClose}
></DPanel>
)
}
</DAlertPopover>
);
}}
</DTransition>,
rootRef.current
)
);
}