import type { DTransitionState } from '../_transition';
import { isNumber, isUndefined } from 'lodash';
import { useEffect, useRef } from 'react';
import { useAsync, useForceUpdate } from '@react-devui/hooks';
import { DCustomIcon } from '@react-devui/icons';
import { checkNodeExist, getClassName } from '@react-devui/utils';
import { registerComponentMate, TTANSITION_DURING_BASE } from '../../utils';
import { DTransition } from '../_transition';
import { useComponentConfig, usePrefixConfig } from '../root';
export interface DSpinnerProps extends React.HTMLAttributes<HTMLDivElement> {
dVisible?: boolean;
dText?: React.ReactNode;
dDelay?: number;
dSize?: number;
dAlone?: boolean;
afterVisibleChange?: (visible: boolean) => void;
}
const { COMPONENT_NAME } = registerComponentMate({ COMPONENT_NAME: 'DSpinner' as const });
export function DSpinner(props: DSpinnerProps): JSX.Element | null {
const {
children,
dVisible = true,
dText,
dDelay,
dSize = 28,
dAlone = false,
afterVisibleChange,
...restProps
} = useComponentConfig(COMPONENT_NAME, props);
const dPrefix = usePrefixConfig();
const spinnerRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const dataRef = useRef<{
delayVisible: boolean;
}>({
delayVisible: false,
});
const async = useAsync();
const forceUpdate = useForceUpdate();
if (dVisible === false) {
dataRef.current.delayVisible = false;
}
const visible = isUndefined(dDelay) ? dVisible : dataRef.current.delayVisible;
useEffect(() => {
if (isNumber(dDelay) && dVisible) {
const asyncInstance = async.create();
asyncInstance.setTimeout(() => {
dataRef.current.delayVisible = true;
forceUpdate();
}, dDelay);
return () => {
asyncInstance.clearAll();
};
}
}, [async, dDelay, dVisible, forceUpdate]);
const transitionStyles: Partial<Record<DTransitionState, React.CSSProperties>> = {
enter: { opacity: 0 },
entering: {
transition: ['opacity'].map((attr) => `${attr} ${TTANSITION_DURING_BASE}ms linear`).join(', '),
},
leaving: {
opacity: 0,
transition: ['opacity'].map((attr) => `${attr} ${TTANSITION_DURING_BASE}ms linear`).join(', '),
},
leaved: { display: 'none' },
};
return (
<DTransition
dIn={visible}
dDuring={TTANSITION_DURING_BASE}
onEnter={() => {
if (!dAlone && spinnerRef.current && containerRef.current) {
containerRef.current.style.height = `${spinnerRef.current.offsetHeight}px`;
}
}}
dDestroyWhenLeaved
afterEnter={() => {
afterVisibleChange?.(true);
}}
afterLeave={() => {
afterVisibleChange?.(false);
}}
>
{(state) => (
<div
{...restProps}
ref={spinnerRef}
className={getClassName(restProps.className, `${dPrefix}spinner`, {
[`${dPrefix}spinner--alone`]: dAlone,
})}
style={{
...restProps.style,
...transitionStyles[state],
}}
>
<div ref={containerRef} className={`${dPrefix}spinner__container`}>
<div className={`${dPrefix}spinner__icon`} style={{ fontSize: dSize }}>
{checkNodeExist(children) ? (
children
) : (
<DCustomIcon className={`${dPrefix}spinner__spinner-icon`} viewBox="0 0 50 50">
<circle cx="25" cy="25" r="21" fill="none" stroke="currentColor" strokeWidth="4" strokeLinecap="round"></circle>
</DCustomIcon>
)}
</div>
{checkNodeExist(dText) && <div className={`${dPrefix}spinner__text`}>{dText}</div>}
</div>
</div>
)}
</DTransition>
);
}