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

223 lines
7.3 KiB
TypeScript
Raw Normal View History

import * as React from 'react';
import DownOutlined from '@ant-design/icons/DownOutlined';
import UpOutlined from '@ant-design/icons/UpOutlined';
import classNames from 'classnames';
import type { InputNumberProps as RcInputNumberProps, ValueType } from 'rc-input-number';
import RcInputNumber from 'rc-input-number';
import ContextIsolator from '../_util/ContextIsolator';
import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
2023-12-27 14:42:51 +08:00
import { devUseWarning } from '../_util/warning';
import ConfigProvider, { ConfigContext } from '../config-provider';
import DisabledContext from '../config-provider/DisabledContext';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import useSize from '../config-provider/hooks/useSize';
import type { SizeType } from '../config-provider/SizeContext';
import { FormItemInputContext } from '../form/context';
import type { Variant } from '../config-provider';
2023-12-22 10:05:15 +08:00
import useVariant from '../form/hooks/useVariants';
import { useCompactItemContext } from '../space/Compact';
2022-06-21 19:40:22 +08:00
import useStyle from './style';
export interface InputNumberProps<T extends ValueType = ValueType>
extends Omit<RcInputNumberProps<T>, 'prefix' | 'size' | 'controls'> {
prefixCls?: string;
rootClassName?: string;
addonBefore?: React.ReactNode;
addonAfter?: React.ReactNode;
prefix?: React.ReactNode;
suffix?: React.ReactNode;
size?: SizeType;
disabled?: boolean;
/** @deprecated Use `variant` instead. */
bordered?: boolean;
status?: InputStatus;
controls?: boolean | { upIcon?: React.ReactNode; downIcon?: React.ReactNode };
/**
* @since 5.13.0
* @default "outlined"
*/
2023-12-22 10:05:15 +08:00
variant?: Variant;
}
const InputNumber = React.forwardRef<HTMLInputElement, InputNumberProps>((props, ref) => {
if (process.env.NODE_ENV !== 'production') {
const typeWarning = devUseWarning('InputNumber');
typeWarning.deprecated(!('bordered' in props), 'bordered', 'variant');
typeWarning(
!(props.type === 'number' && props.changeOnWheel),
'usage',
'When `type=number` is used together with `changeOnWheel`, changeOnWheel may not work properly. Please delete `type=number` if it is not necessary.',
);
}
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const inputRef = React.useRef<HTMLInputElement>(null);
React.useImperativeHandle(ref, () => inputRef.current!);
const {
className,
rootClassName,
size: customizeSize,
disabled: customDisabled,
prefixCls: customizePrefixCls,
addonBefore,
addonAfter,
prefix,
suffix,
bordered,
readOnly,
status: customStatus,
controls,
variant: customVariant,
...others
} = props;
const prefixCls = getPrefixCls('input-number', customizePrefixCls);
// Style
const rootCls = useCSSVarCls(prefixCls);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
feat: add Space.Compact (#37652) * feat: add Space.Compact * feat: update input style * feat: add CompactItem for context memoize * feat: add demo * feat: update button style * feat: update input style * feat: 提取 getCompactClassNames 公用方法 * feat: update button style * feat: update picker * feat: add block prop for Space.Compact * feat: use Space.Compact for Input#addonBefore/After * feat: update addon * refactor: compact * feat: update * feat: update * feat: migrate styles to compact for new Input/Input.Search * feat: organize input demo * feat: add more button compact demo * feat: resolve select border collapse * feat: fix input addon select style * feat: add input addon demo in Space * feat: add useCompactItemContext hook * feat: update compact-item style * feat: rollback input#addon implements * feat: rollback input#addon and input.search style * feat: select border collapse * feat: add Space.Compact demo * feat: Space.Compact vertical for Button * refactor: useCompactItemContext * feat: update Button vertical demo * feat: rtl for compact mixin * feat: rtl for compact button * feat: input rtl * feat: add docs for Space.Compact * test: add some tests for Space.Compact * test: add tests * feat: select style * feat: handle select focus style * feat: add useCompactItemContext for Picker and Cascader * style: add compact-item style for cascader * feat: support nested Space.Compact * style: Input.Search in Space.Compact * chore: clean * feat: add useCompactItemContext for TreeSelect * fix: lint issues * fix: style lint * docs: update demos for Space.Compact * docs: update demo * fix: add deps-lint-skip for space * fix: add deps-lint-skip for space * fix: handle edge case for useCompactItemContext * test: add search test case * chore: + bundlesize * style: merge button compact style into one file * style: improve style for nested compact * fix: stylelint * docs: update Space.Compact docs * test: update demo snapshot * test: update input debug test snapshot * docs: update doccs for Space.Compact * test: improve test cases for Compact * docs: clean demos for Compact * refactor: improve Space.Compact * fix: add useCompactItemContext for Input.Search * style: improve compact border-radius implementation * refactor: use context to make nested compact works * fix: input-number focused style * refactor: remove useless style * refactor: improve style for vertical compact * test: update snapshot * test: update snapshot for input * refactor: improve compact-item-border-radius * fix: search group in RTL * style: improve style for button compact * style: use after * fix: stylelint * chore: update snapshot * style: improve button compact * refactor: improve btn-icon-only style in compact
2022-10-18 16:23:10 +08:00
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,
} = React.useContext(FormItemInputContext);
const mergedStatus = getMergedStatus(contextStatus, customStatus);
const mergedSize = useSize((ctx) => customizeSize ?? compactSize ?? ctx);
// ===================== Disabled =====================
const disabled = React.useContext(DisabledContext);
const mergedDisabled = customDisabled ?? disabled;
const [variant, enableVariantCls] = useVariant('inputNumber', customVariant, bordered);
/* biome-ignore lint/complexity/noUselessFragments: avoid falsy value */
const suffixNode = hasFeedback && <>{feedbackIcon}</>;
const inputNumberClass = classNames(
{
[`${prefixCls}-lg`]: mergedSize === 'large',
[`${prefixCls}-sm`]: mergedSize === 'small',
[`${prefixCls}-rtl`]: direction === 'rtl',
[`${prefixCls}-in-form-item`]: isFormItemInput,
},
hashId,
);
const wrapperClassName = `${prefixCls}-group`;
const element = (
<RcInputNumber
ref={inputRef}
disabled={mergedDisabled}
className={classNames(cssVarCls, rootCls, className, rootClassName, compactItemClassnames)}
upHandler={upIcon}
downHandler={downIcon}
prefixCls={prefixCls}
readOnly={readOnly}
controls={controlsTemp}
prefix={prefix}
suffix={suffixNode || suffix}
addonBefore={
addonBefore && (
<ContextIsolator form space>
{addonBefore}
</ContextIsolator>
)
}
addonAfter={
addonAfter && (
<ContextIsolator form space>
{addonAfter}
</ContextIsolator>
)
}
classNames={{
input: inputNumberClass,
variant: classNames(
{
[`${prefixCls}-${variant}`]: enableVariantCls,
},
getStatusClassNames(prefixCls, mergedStatus, hasFeedback),
),
affixWrapper: classNames(
{
[`${prefixCls}-affix-wrapper-sm`]: mergedSize === 'small',
[`${prefixCls}-affix-wrapper-lg`]: mergedSize === 'large',
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',
[`${prefixCls}-affix-wrapper-without-controls`]: controls === false,
},
hashId,
),
wrapper: classNames(
{
[`${wrapperClassName}-rtl`]: direction === 'rtl',
},
hashId,
),
groupWrapper: classNames(
{
[`${prefixCls}-group-wrapper-sm`]: mergedSize === 'small',
[`${prefixCls}-group-wrapper-lg`]: mergedSize === 'large',
[`${prefixCls}-group-wrapper-rtl`]: direction === 'rtl',
[`${prefixCls}-group-wrapper-${variant}`]: enableVariantCls,
},
getStatusClassNames(`${prefixCls}-group-wrapper`, mergedStatus, hasFeedback),
hashId,
),
}}
{...others}
/>
);
return wrapCSSVar(element);
});
const TypedInputNumber = InputNumber as unknown as (<T extends ValueType = ValueType>(
props: React.PropsWithChildren<InputNumberProps<T>> & React.RefAttributes<HTMLInputElement>,
) => React.ReactElement) & {
displayName?: string;
_InternalPanelDoNotUseOrYouWillBeFired: typeof PureInputNumber;
};
/** @private Internal Component. Do not use in your production. */
const PureInputNumber: React.FC<InputNumberProps> = (props) => (
<ConfigProvider theme={{ components: { InputNumber: { handleVisible: true } } }}>
<InputNumber {...props} />
</ConfigProvider>
);
if (process.env.NODE_ENV !== 'production') {
TypedInputNumber.displayName = 'InputNumber';
}
TypedInputNumber._InternalPanelDoNotUseOrYouWillBeFired = PureInputNumber;
export default TypedInputNumber;