ant-design/components/input-number/index.tsx

235 lines
7.6 KiB
TypeScript

import DownOutlined from '@ant-design/icons/DownOutlined';
import UpOutlined from '@ant-design/icons/UpOutlined';
import classNames from 'classnames';
import type { InputNumberProps as RcInputNumberProps } from 'rc-input-number';
import RcInputNumber from 'rc-input-number';
import type { ValueType } from 'rc-input-number/lib/utils/MiniDecimal';
import * as React from 'react';
import { useContext } from 'react';
import ConfigProvider, { ConfigContext } from '../config-provider';
import DisabledContext from '../config-provider/DisabledContext';
import type { SizeType } from '../config-provider/SizeContext';
import SizeContext from '../config-provider/SizeContext';
import { FormItemInputContext, NoFormStyle } from '../form/context';
import { useCompactItemContext } from '../space/Compact';
import { cloneElement } from '../_util/reactNode';
import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import useStyle from './style';
export interface InputNumberProps<T extends ValueType = ValueType>
extends Omit<RcInputNumberProps<T>, 'prefix' | 'size' | 'controls'> {
prefixCls?: string;
addonBefore?: React.ReactNode;
addonAfter?: React.ReactNode;
prefix?: React.ReactNode;
size?: SizeType;
disabled?: boolean;
bordered?: boolean;
status?: InputStatus;
controls?: boolean | { upIcon?: React.ReactNode; downIcon?: React.ReactNode };
}
const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props, ref) => {
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const size = React.useContext(SizeContext);
const [focused, setFocus] = React.useState(false);
const inputRef = React.useRef<HTMLInputElement>(null);
React.useImperativeHandle(ref, () => inputRef.current!);
const {
className,
size: customizeSize,
disabled: customDisabled,
prefixCls: customizePrefixCls,
addonBefore,
addonAfter,
prefix,
bordered = true,
readOnly,
status: customStatus,
controls,
...others
} = props;
const prefixCls = getPrefixCls('input-number', customizePrefixCls);
// Style
const [wrapSSR, hashId] = useStyle(prefixCls);
const { compactSize, compactItemClassnames } = useCompactItemContext(prefixCls, direction);
let upIcon = <UpOutlined className={`${prefixCls}-handler-up-inner`} />;
let downIcon = <DownOutlined className={`${prefixCls}-handler-down-inner`} />;
const controlsTemp = typeof controls === 'boolean' ? controls : undefined;
if (typeof controls === 'object') {
upIcon =
typeof controls.upIcon === 'undefined' ? (
upIcon
) : (
<span className={`${prefixCls}-handler-up-inner`}>{controls.upIcon}</span>
);
downIcon =
typeof controls.downIcon === 'undefined' ? (
downIcon
) : (
<span className={`${prefixCls}-handler-down-inner`}>{controls.downIcon}</span>
);
}
const {
hasFeedback,
status: contextStatus,
isFormItemInput,
feedbackIcon,
} = useContext(FormItemInputContext);
const mergedStatus = getMergedStatus(contextStatus, customStatus);
const mergeSize = compactSize || customizeSize || size;
// ===================== Disabled =====================
const disabled = React.useContext(DisabledContext);
const mergedDisabled = customDisabled ?? disabled;
const inputNumberClass = classNames(
{
[`${prefixCls}-lg`]: mergeSize === 'large',
[`${prefixCls}-sm`]: mergeSize === 'small',
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-borderless`]: !bordered,
[`${prefixCls}-in-form-item`]: isFormItemInput,
},
getStatusClassNames(prefixCls, mergedStatus),
compactItemClassnames,
hashId,
className,
);
let element = (
<RcInputNumber
ref={inputRef}
disabled={mergedDisabled}
className={inputNumberClass}
upHandler={upIcon}
downHandler={downIcon}
prefixCls={prefixCls}
readOnly={readOnly}
controls={controlsTemp}
{...others}
/>
);
if (prefix != null || hasFeedback) {
const affixWrapperCls = classNames(
`${prefixCls}-affix-wrapper`,
getStatusClassNames(`${prefixCls}-affix-wrapper`, mergedStatus, hasFeedback),
{
[`${prefixCls}-affix-wrapper-focused`]: focused,
[`${prefixCls}-affix-wrapper-disabled`]: props.disabled,
[`${prefixCls}-affix-wrapper-sm`]: mergeSize === 'small',
[`${prefixCls}-affix-wrapper-lg`]: mergeSize === 'large',
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
[`${prefixCls}-affix-wrapper-readonly`]: readOnly,
[`${prefixCls}-affix-wrapper-borderless`]: !bordered,
// className will go to addon wrapper
[`${className}`]: !(addonBefore || addonAfter) && className,
},
hashId,
);
element = (
<div
className={affixWrapperCls}
style={props.style}
onMouseUp={() => inputRef.current!.focus()}
>
{prefix && <span className={`${prefixCls}-prefix`}>{prefix}</span>}
{cloneElement(element, {
style: null,
value: props.value,
onFocus: (event: React.FocusEvent<HTMLInputElement>) => {
setFocus(true);
props.onFocus?.(event);
},
onBlur: (event: React.FocusEvent<HTMLInputElement>) => {
setFocus(false);
props.onBlur?.(event);
},
})}
{hasFeedback && <span className={`${prefixCls}-suffix`}>{feedbackIcon}</span>}
</div>
);
}
if (addonBefore != null || addonAfter != null) {
const wrapperClassName = `${prefixCls}-group`;
const addonClassName = `${wrapperClassName}-addon`;
const addonBeforeNode = addonBefore ? (
<div className={addonClassName}>{addonBefore}</div>
) : null;
const addonAfterNode = addonAfter ? <div className={addonClassName}>{addonAfter}</div> : null;
const mergedWrapperClassName = classNames(`${prefixCls}-wrapper`, wrapperClassName, hashId, {
[`${wrapperClassName}-rtl`]: direction === 'rtl',
});
const mergedGroupClassName = classNames(
`${prefixCls}-group-wrapper`,
{
[`${prefixCls}-group-wrapper-sm`]: mergeSize === 'small',
[`${prefixCls}-group-wrapper-lg`]: mergeSize === 'large',
[`${prefixCls}-group-wrapper-rtl`]: direction === 'rtl',
},
getStatusClassNames(`${prefixCls}-group-wrapper`, mergedStatus, hasFeedback),
hashId,
className,
);
element = (
<div className={mergedGroupClassName} style={props.style}>
<div className={mergedWrapperClassName}>
{addonBeforeNode && (
<NoFormStyle status override>
{addonBeforeNode}
</NoFormStyle>
)}
{cloneElement(element, { style: null, disabled: mergedDisabled })}
{addonAfterNode && (
<NoFormStyle status override>
{addonAfterNode}
</NoFormStyle>
)}
</div>
</div>
);
}
return wrapSSR(element);
});
const TypedInputNumber = InputNumber as unknown as (<T extends ValueType = ValueType>(
props: React.PropsWithChildren<InputNumberProps<T>> & {
ref?: React.Ref<HTMLInputElement>;
},
) => React.ReactElement) & {
displayName?: string;
_InternalPanelDoNotUseOrYouWillBeFired: typeof PureInputNumber;
};
const PureInputNumber = (props: InputNumberProps<any>) => (
<ConfigProvider
theme={{
components: {
InputNumber: {
handleVisible: true,
},
},
}}
>
<InputNumber {...props} />
</ConfigProvider>
);
TypedInputNumber._InternalPanelDoNotUseOrYouWillBeFired = PureInputNumber;
export default TypedInputNumber;