import type { DCloneHTMLElement } from '../../utils/types';
import type { DTransitionState } from '../_transition';
import type { DFormControl } from '../form';
import { isUndefined } from 'lodash';
import { useState } from 'react';
import { LoadingOutlined } from '@react-devui/icons';
import { checkNodeExist, getClassName } from '@react-devui/utils';
import { useGeneralContext, useDValue } from '../../hooks';
import { registerComponentMate } from '../../utils';
import { DBaseInput } from '../_base-input';
import { DTransition } from '../_transition';
import { useFormControl } from '../form';
import { useComponentConfig, usePrefixConfig } from '../root';
export interface DSwitchProps extends React.HTMLAttributes<HTMLElement> {
dRef?: {
input?: React.ForwardedRef<HTMLInputElement>;
};
dFormControl?: DFormControl;
dModel?: boolean;
dLabelPlacement?: 'left' | 'right';
dStateContent?: [React.ReactNode, React.ReactNode];
dDisabled?: boolean;
dLoading?: boolean;
dInputRender?: DCloneHTMLElement<React.InputHTMLAttributes<HTMLInputElement>>;
onModelChange?: (checked: boolean) => void;
}
const TTANSITION_DURING = 133;
const { COMPONENT_NAME } = registerComponentMate({ COMPONENT_NAME: 'DSwitch' as const });
export function DSwitch(props: DSwitchProps): JSX.Element | null {
const {
children,
dRef,
dFormControl,
dModel,
dLabelPlacement = 'right',
dStateContent,
dDisabled = false,
dLoading = false,
dInputRender,
onModelChange,
...restProps
} = useComponentConfig(COMPONENT_NAME, props);
const dPrefix = usePrefixConfig();
const { gDisabled } = useGeneralContext();
const [isFocus, setIsFocus] = useState(false);
const formControlInject = useFormControl(dFormControl);
const [checked, changeChecked] = useDValue<boolean, boolean>(false, dModel, onModelChange, undefined, formControlInject);
const disabled = dDisabled || gDisabled || dFormControl?.control.disabled;
const transitionStyles: Partial<Record<DTransitionState, React.CSSProperties>> = {
enter: { left: 2 },
entering: {
left: 'calc(100% - 20px)',
transition: ['width', 'padding', 'margin', 'left'].map((attr) => `${attr} ${TTANSITION_DURING}ms linear`).join(', '),
},
entered: { right: 2 },
leave: { right: 2 },
leaving: {
right: 'calc(100% - 20px)',
transition: ['width', 'padding', 'margin', 'right'].map((attr) => `${attr} ${TTANSITION_DURING}ms linear`).join(', '),
},
leaved: { left: 2 },
};
const switchNode = (
<div className={`${dPrefix}switch__state-container`}>
{dStateContent && (
<div
className={getClassName(`${dPrefix}switch__state-content`, `${dPrefix}switch__state-content--left`)}
style={{ opacity: checked ? 1 : 0 }}
>
{dStateContent[0]}
</div>
)}
{dStateContent && (
<div className={`${dPrefix}switch__state-content`} style={{ opacity: checked ? 0 : 1 }}>
{dStateContent[1]}
</div>
)}
<DBaseInput dFormControl={dFormControl} dLabelFor>
{({ render: renderBaseInput }) => {
const input = renderBaseInput(
<input
ref={dRef?.input}
className={`${dPrefix}switch__input`}
type="checkbox"
disabled={disabled || dLoading}
role="switch"
aria-checked={checked}
onChange={() => {
changeChecked(!checked);
}}
onFocus={() => {
setIsFocus(true);
}}
onBlur={() => {
setIsFocus(false);
}}
/>
);
return isUndefined(dInputRender) ? input : dInputRender(input);
}}
</DBaseInput>
<DTransition dIn={checked} dDuring={TTANSITION_DURING}>
{(state) => (
<div
className={getClassName(`${dPrefix}switch__state-dot`, {
'is-focus': isFocus,
})}
style={transitionStyles[state]}
>
{dLoading && <LoadingOutlined dSpin />}
</div>
)}
</DTransition>
</div>
);
const labelNode = checkNodeExist(children) && <div className={`${dPrefix}switch__label`}>{children}</div>;
return (
<label
{...restProps}
className={getClassName(restProps.className, `${dPrefix}switch`, {
'is-checked': checked,
'is-disabled': disabled,
'is-loading': dLoading,
})}
>
{dLabelPlacement === 'left' ? (
<>
{labelNode}
{switchNode}
</>
) : (
<>
{switchNode}
{labelNode}
</>
)}
</label>
);
}