diff --git a/components/alert/__tests__/__snapshots__/index.test.tsx.snap b/components/alert/__tests__/__snapshots__/index.test.tsx.snap index d07a3c448e..0ece9e15eb 100644 --- a/components/alert/__tests__/__snapshots__/index.test.tsx.snap +++ b/components/alert/__tests__/__snapshots__/index.test.tsx.snap @@ -108,3 +108,30 @@ exports[`Alert rtl render component should be rendered correctly in RTL directio /> `; + +exports[`Alert support closeIcon 1`] = ` + +`; diff --git a/components/dropdown/__tests__/__snapshots__/demo.test.js.snap b/components/dropdown/__tests__/__snapshots__/demo.test.js.snap index 74c5c678f9..4e4aa98da4 100644 --- a/components/dropdown/__tests__/__snapshots__/demo.test.js.snap +++ b/components/dropdown/__tests__/__snapshots__/demo.test.js.snap @@ -354,6 +354,222 @@ exports[`renders ./components/dropdown/demo/item.md correctly 1`] = ` `; +exports[`renders ./components/dropdown/demo/loading.md correctly 1`] = ` +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+`; + exports[`renders ./components/dropdown/demo/menu-full.md correctly 1`] = ` `; +exports[`renders ./components/input-number/demo/prefix.md correctly 1`] = ` +Array [ +
+ + ¥ + +
+
+ + + + + + + + + + +
+
+ +
+
+
, +
, +
, +
+
+
+ + + +
+
+ + ¥ + +
+
+ + + + + + + + + + +
+
+ +
+
+
+
+
, +
, +
, +
+ + ¥ + +
+
+ + + + + + + + + + +
+
+ +
+
+
, +
, +
, +
+
+
+ +
+
+
+
+
+ + + + + +
+
+ + + + + + + + + + +
+
+ +
+
+
+
+ + + + + +
+
+
+
, +] +`; + exports[`renders ./components/input-number/demo/size.md correctly 1`] = `
{ + focusTest( + React.forwardRef((props, ref) => ), + { refFocus: true }, + ); + it('should support className when has prefix', () => { + const wrapper = mount(); + expect(wrapper.getDOMNode().className.includes('my-class-name')).toBe(true); + expect(wrapper.find('input').getDOMNode().className.includes('my-class-name')).toBe(false); + }); + + it('should trigger focus when prefix is clicked', () => { + const wrapper = mount(123} />); + + const mockFocus = jest.spyOn(wrapper.find('input').getDOMNode(), 'focus'); + wrapper.find('i').simulate('mouseUp'); + expect(mockFocus).toBeCalled(); + }); +}); diff --git a/components/input-number/demo/prefix.md b/components/input-number/demo/prefix.md new file mode 100644 index 0000000000..04623a63c6 --- /dev/null +++ b/components/input-number/demo/prefix.md @@ -0,0 +1,44 @@ +--- +order: 7 +title: + zh-CN: 前缀 + en-US: Prefix +--- + +## zh-CN + +在输入框上添加前缀图标。 + +## en-US + +Add a prefix inside input. + +```jsx +import { Form, InputNumber, Tooltip } from 'antd'; +import { InfoCircleOutlined, SmileOutlined, UserOutlined } from '@ant-design/icons'; + +ReactDOM.render( + <> + +
+
+ } prefix="¥" style={{ width: '100%' }} /> +
+
+ +
+
+
+ + } + /> + +
+ , + mountNode, +); +``` diff --git a/components/input-number/index.en-US.md b/components/input-number/index.en-US.md index d67d067670..6b56421621 100644 --- a/components/input-number/index.en-US.md +++ b/components/input-number/index.en-US.md @@ -30,6 +30,7 @@ When a numeric value needs to be provided. | parser | Specifies the value extracted from formatter | function(string): number | - | - | | precision | The precision of input value. Will use `formatter` when config of `formatter` | number | - | - | | readOnly | If readonly the input | boolean | false | - | +| prefix | The prefix icon for the Input | ReactNode | - | 4.17.0 | | size | The height of input box | `large` \| `middle` \| `small` | - | - | | step | The number to which the current value is increased or decreased. It can be an integer or decimal | number \| string | 1 | - | | stringMode | Set value as string to support high precision decimals. Will return string value by `onChange` | boolean | false | 4.13.0 | diff --git a/components/input-number/index.tsx b/components/input-number/index.tsx index 2a0843436c..60bee2fb91 100644 --- a/components/input-number/index.tsx +++ b/components/input-number/index.tsx @@ -11,10 +11,11 @@ import { cloneElement } from '../_util/reactNode'; type ValueType = string | number; export interface InputNumberProps - extends Omit, 'size'> { + extends Omit, 'prefix' | 'size'> { prefixCls?: string; addonBefore?: React.ReactNode; addonAfter?: React.ReactNode; + prefix?: React.ReactNode; size?: SizeType; bordered?: boolean; } @@ -22,6 +23,10 @@ export interface InputNumberProps const InputNumber = React.forwardRef((props, ref) => { const { getPrefixCls, direction } = React.useContext(ConfigContext); const size = React.useContext(SizeContext); + const [focused, setFocus] = React.useState(false); + const inputRef = React.useRef(null); + + React.useImperativeHandle(ref, () => inputRef.current!); const { className, @@ -29,6 +34,7 @@ const InputNumber = React.forwardRef((props, prefixCls: customizePrefixCls, addonBefore, addonAfter, + prefix, bordered = true, readOnly, ...others @@ -50,9 +56,9 @@ const InputNumber = React.forwardRef((props, className, ); - const element = ( + let element = ( ((props, /> ); + if (prefix != null) { + const affixWrapperCls = classNames(`${prefixCls}-affix-wrapper`, { + [`${prefixCls}-affix-wrapper-focused`]: focused, + [`${prefixCls}-affix-wrapper-disabled`]: props.disabled, + [`${prefixCls}-affix-wrapper-sm`]: size === 'small', + [`${prefixCls}-affix-wrapper-lg`]: size === '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, + }); + element = ( +
inputRef.current!.focus()} + > + {prefix} + {cloneElement(element, { + style: null, + value: props.value, + onFocus: (event: React.FocusEvent) => { + setFocus(true); + props.onFocus?.(event); + }, + onBlur: (event: React.FocusEvent) => { + setFocus(false); + props.onBlur?.(event); + }, + })} +
+ ); + } + if (addonBefore != null || addonAfter != null) { const wrapperClassName = `${prefixCls}-group`; const addonClassName = `${wrapperClassName}-addon`; @@ -83,7 +124,7 @@ const InputNumber = React.forwardRef((props, }, className, ); - return ( + element = (
{addonBeforeNode} diff --git a/components/input-number/index.zh-CN.md b/components/input-number/index.zh-CN.md index 0e9f85f561..1ddfc5bbf3 100644 --- a/components/input-number/index.zh-CN.md +++ b/components/input-number/index.zh-CN.md @@ -33,6 +33,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/XOS8qZ0kU/InputNumber.svg | parser | 指定从 `formatter` 里转换回数字的方式,和 `formatter` 搭配使用 | function(string): number | - | - | | precision | 数值精度,配置 `formatter` 时会以 `formatter` 为准 | number | - | - | | readOnly | 只读 | boolean | false | - | +| prefix | 带有前缀图标的 input | ReactNode | - | 4.17.0 | | size | 输入框大小 | `large` \| `middle` \| `small` | - | - | | step | 每次改变步数,可以为小数 | number \| string | 1 | - | | stringMode | 字符值模式,开启后支持高精度小数。同时 `onChange` 将返回 string 类型 | boolean | false | 4.13.0 | diff --git a/components/input-number/style/affix.less b/components/input-number/style/affix.less new file mode 100644 index 0000000000..3724907288 --- /dev/null +++ b/components/input-number/style/affix.less @@ -0,0 +1,64 @@ +@import '../../input/style/mixin'; +@import (reference) '../../style/themes/index'; +@input-prefix-cls: ~'@{ant-prefix}-input'; + +@input-affix-margin: 4px; + +.@{ant-prefix}-input-number { + &-affix-wrapper { + .input(); + // or number handler will cover form status + position: static; + display: inline-flex; + width: 90px; + padding: 0; + padding-inline-start: @input-padding-horizontal-base; + + &:not(&-disabled):hover { + .hover(); + z-index: 1; + } + + &-focused, + &:focus { + z-index: 1; + } + + &-disabled { + .@{ant-prefix}-input-number[disabled] { + background: transparent; + } + } + + > div.@{ant-prefix}-input-number { + width: 100%; + border: none; + outline: none; + + &.@{ant-prefix}-input-number-focused { + box-shadow: none !important; + } + } + + input.@{ant-prefix}-input-number-input { + padding: 0; + } + + &::before { + width: 0; + visibility: hidden; + content: '\a0'; + } + } + + &-prefix { + display: flex; + flex: none; + align-items: center; + margin-inline-end: @input-affix-margin; + } +} + +.@{ant-prefix}-input-number-group-wrapper .@{ant-prefix}-input-number-affix-wrapper { + width: 100%; +} diff --git a/components/input-number/style/index.less b/components/input-number/style/index.less index b5f8663d3d..fd17e7fda4 100644 --- a/components/input-number/style/index.less +++ b/components/input-number/style/index.less @@ -1,6 +1,7 @@ @import '../../style/themes/index'; @import '../../style/mixins/index'; @import '../../input/style/mixin'; +@import './affix'; @input-number-prefix-cls: ~'@{ant-prefix}-input-number'; @form-item-prefix-cls: ~'@{ant-prefix}-form-item'; diff --git a/components/input/ClearableLabeledInput.tsx b/components/input/ClearableLabeledInput.tsx index 015f15dfc9..47f033a7c8 100644 --- a/components/input/ClearableLabeledInput.tsx +++ b/components/input/ClearableLabeledInput.tsx @@ -105,13 +105,13 @@ class ClearableLabeledInput extends React.Component { readOnly, bordered, } = this.props; - const suffixNode = this.renderSuffix(prefixCls); if (!hasPrefixSuffix(this.props)) { return cloneElement(element, { value, }); } + const suffixNode = this.renderSuffix(prefixCls); const prefixNode = prefix ? {prefix} : null; const affixWrapperCls = classNames(`${prefixCls}-affix-wrapper`, {