import { isUndefined } from 'lodash';
import React, { useImperativeHandle, useRef, useState } from 'react';
import ReactDOM from 'react-dom';
import { useEventCallback, useRefExtra } from '@react-devui/hooks';
import { RightOutlined } from '@react-devui/icons';
import { checkNodeExist, getClassName } from '@react-devui/utils';
import { getHorizontalSidePosition, TTANSITION_DURING_POPUP, WINDOW_SPACE } from '../../utils';
import { DPopup } from '../_popup';
import { DTransition } from '../_transition';
import { usePrefixConfig, useTranslation } from '../root';
export interface DSubProps {
children: React.ReactNode;
dId: string;
dLevel: number;
dIcon: React.ReactNode | undefined;
dList: React.ReactNode;
dPopupState: boolean | undefined;
dTrigger: 'hover' | 'click';
dZIndex: number | string | undefined;
dEmpty: boolean;
dFocusVisible: boolean;
dDisabled: boolean;
onVisibleChange: (visible: boolean) => void;
}
function Sub(props: DSubProps, ref: React.ForwardedRef<() => void>): JSX.Element | null {
const { children, dId, dLevel, dIcon, dList, dPopupState, dTrigger, dZIndex, dEmpty, dFocusVisible, dDisabled, onVisibleChange } = props;
const dPrefix = usePrefixConfig();
const liRef = useRef<HTMLLIElement>(null);
const ulRef = useRef<HTMLUListElement>(null);
const containerRef = useRefExtra(() => {
let el = document.getElementById(`${dPrefix}dropdown-root`);
if (!el) {
el = document.createElement('div');
el.id = `${dPrefix}dropdown-root`;
document.body.appendChild(el);
}
return el;
}, true);
const [t] = useTranslation();
const isVisible = !isUndefined(dPopupState);
const [popupPositionStyle, setPopupPositionStyle] = useState<React.CSSProperties>({
top: '-200vh',
left: '-200vw',
});
const [transformOrigin, setTransformOrigin] = useState<string>();
const updatePosition = useEventCallback(() => {
if (isVisible && ulRef.current && liRef.current) {
const [width, height] = [ulRef.current.offsetWidth, ulRef.current.offsetHeight];
const { top, left, transformOrigin } = getHorizontalSidePosition(
liRef.current,
{ width, height },
{
placement: 'right',
inWindow: WINDOW_SPACE,
}
);
setPopupPositionStyle({
top,
left,
});
setTransformOrigin(transformOrigin);
}
});
useImperativeHandle(ref, () => updatePosition, [updatePosition]);
const liNode = (
<li
ref={liRef}
id={dId}
className={getClassName(`${dPrefix}dropdown__item`, `${dPrefix}dropdown__item--sub`, {
'is-expand': isVisible,
'is-disabled': dDisabled,
})}
style={{ paddingLeft: 12 + dLevel * 16 }}
role="menuitem"
aria-haspopup
aria-expanded={isVisible}
aria-disabled={dDisabled}
>
{dFocusVisible && <div className={`${dPrefix}focus-outline`}></div>}
{checkNodeExist(dIcon) && <div className={`${dPrefix}dropdown__item-icon`}>{dIcon}</div>}
<div className={`${dPrefix}dropdown__item-content`}>{children}</div>
<RightOutlined className={`${dPrefix}dropdown__sub-arrow`} dSize={14} />
</li>
);
return dDisabled ? (
liNode
) : (
<DPopup
dVisible={dPopupState ?? false}
dTrigger={dTrigger}
dUpdatePosition={{
fn: updatePosition,
triggerRef: liRef,
popupRef: ulRef,
extraScrollRefs: [],
}}
onVisibleChange={onVisibleChange}
>
{({ renderTrigger, renderPopup }) => (
<>
{renderTrigger(liNode)}
{containerRef.current &&
ReactDOM.createPortal(
<DTransition dIn={isVisible} dDuring={TTANSITION_DURING_POPUP} onEnter={updatePosition}>
{(state) => {
let transitionStyle: React.CSSProperties = {};
switch (state) {
case 'enter':
transitionStyle = { transform: 'scale(0)', opacity: 0 };
break;
case 'entering':
transitionStyle = {
transition: ['transform', 'opacity'].map((attr) => `${attr} ${TTANSITION_DURING_POPUP}ms ease-out`).join(', '),
transformOrigin,
};
break;
case 'leaving':
transitionStyle = {
transform: 'scale(0)',
opacity: 0,
transition: ['transform', 'opacity'].map((attr) => `${attr} ${TTANSITION_DURING_POPUP}ms ease-in`).join(', '),
transformOrigin,
};
break;
case 'leaved':
transitionStyle = { display: 'none' };
break;
default:
break;
}
return renderPopup(
<ul
ref={ulRef}
className={`${dPrefix}dropdown__sub-popup`}
style={{
...popupPositionStyle,
zIndex: dZIndex,
...transitionStyle,
}}
role="menu"
>
{dEmpty ? (
<div className={`${dPrefix}dropdown__empty`} style={{ paddingLeft: 12 + dLevel * 16 }}>
{t('No Data')}
</div>
) : (
dList
)}
</ul>
);
}}
</DTransition>,
containerRef.current
)}
</>
)}
</DPopup>
);
}
export const DSub = React.forwardRef(Sub);