feat: input support semantic dom (#53958)

* update

* update

* feat: input support completely semantic dom

* add test

* fix test

* update

* chore: add jest types to tsconfig

* update snapshot

* update

* update

* update

* update

* update

* update

* update

* update

* update

* update

---------

Co-authored-by: thinkasany <480968828@qq.com>
This commit is contained in:
aojunhao123 2025-06-03 16:28:09 +08:00 committed by GitHub
parent 6b2e46fe98
commit bff829f763
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 658 additions and 187 deletions

View File

@ -513,6 +513,7 @@ describe('Button', () => {
fireEvent.click(getByRole('link'));
expect(handleClick).toHaveBeenCalled();
});
it('should support classnames and styles', () => {
const cusomStyles = {
root: { color: 'red' },
@ -524,20 +525,27 @@ describe('Button', () => {
icon: 'custom-icon',
content: 'custom-content',
};
const { container } = render(
const { container, rerender, getByText } = render(
<Button classNames={customClassNames} styles={cusomStyles} icon={<SearchOutlined />}>
antd
</Button>,
);
const root = container.querySelector('.ant-btn') as HTMLElement;
const icon = container.querySelector('.ant-btn-icon') as HTMLElement;
const root = container.querySelector('.ant-btn');
const icon = container.querySelector('.ant-btn-icon');
const content = getByText('antd');
expect(root).toHaveClass(customClassNames.root);
expect(icon).toHaveClass(customClassNames.icon);
expect(root).toHaveStyle(cusomStyles.root);
expect(icon).toHaveStyle(cusomStyles.icon);
expect(container.querySelector(`.${customClassNames.content}`)).toHaveStyle(
cusomStyles.content,
expect(content).toHaveStyle(cusomStyles.content);
rerender(
<Button classNames={customClassNames} styles={cusomStyles} loading>
antd
</Button>,
);
const loadingIcon = container.querySelector('.ant-btn-icon');
expect(loadingIcon).toHaveClass(customClassNames.icon);
expect(loadingIcon).toHaveStyle(cusomStyles.icon);
});
it('should support customizing the background color of default type button in disabled state', () => {

View File

@ -27,7 +27,7 @@ import Compact from './style/compact';
export type LegacyButtonType = ButtonType | 'danger';
type SemanticName = 'root' | 'icon' | 'content';
export type ButtonSemanticName = 'root' | 'icon' | 'content';
export interface BaseButtonProps {
type?: ButtonType;
color?: ButtonColorType;
@ -46,8 +46,8 @@ export interface BaseButtonProps {
block?: boolean;
children?: React.ReactNode;
[key: `data-${string}`]: string;
classNames?: Partial<Record<SemanticName, string>>;
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
classNames?: Partial<Record<ButtonSemanticName, string>>;
styles?: Partial<Record<ButtonSemanticName, React.CSSProperties>>;
}
type MergedHTMLAttributes = Omit<
@ -351,6 +351,8 @@ const InternalCompoundedButton = React.forwardRef<
</IconWrapper>
) : (
<DefaultLoadingIcon
className={iconClasses}
style={iconStyle}
existIcon={!!icon}
prefixCls={prefixCls}
loading={innerLoading}

View File

@ -18,6 +18,7 @@ import type { CollapseProps } from '../collapse';
import type { ColorPickerProps } from '../color-picker';
import type { DatePickerProps, RangePickerProps } from '../date-picker';
import type { DescriptionsProps } from '../descriptions';
import type { DividerProps } from '../divider';
import type { DrawerProps } from '../drawer';
import type { DropdownProps } from '../dropdown';
import type { EmptyProps } from '../empty';
@ -25,8 +26,9 @@ import type { FlexProps } from '../flex/interface';
import type { FloatButtonGroupProps } from '../float-button/interface';
import type { FormProps } from '../form/Form';
import type { ImageProps } from '../image';
import type { InputProps, TextAreaProps } from '../input';
import type { InputProps, SearchProps, TextAreaProps } from '../input';
import type { InputNumberProps } from '../input-number';
import type { OTPProps } from '../input/OTP';
import type { ListItemProps } from '../list';
import type { Locale } from '../locale';
import type { MasonryProps } from '../masonry';
@ -65,7 +67,6 @@ import type { TreeProps } from '../tree';
import type { TreeSelectProps } from '../tree-select';
import type { UploadProps } from '../upload';
import type { RenderEmptyHandler } from './defaultRenderEmpty';
import type { DividerProps } from '../divider';
export const defaultPrefixCls = 'ant';
export const defaultIconPrefixCls = 'anticon';
@ -216,9 +217,13 @@ export type BreadcrumbConfig = ComponentStyleConfig &
export type InputConfig = ComponentStyleConfig &
Pick<InputProps, 'autoComplete' | 'classNames' | 'styles' | 'allowClear' | 'variant'>;
export type InputSearchConfig = ComponentStyleConfig & Pick<SearchProps, 'classNames' | 'styles'>;
export type TextAreaConfig = ComponentStyleConfig &
Pick<TextAreaProps, 'autoComplete' | 'classNames' | 'styles' | 'allowClear' | 'variant'>;
export type OTPConfig = ComponentStyleConfig & Pick<OTPProps, 'classNames' | 'styles'>;
export type ButtonConfig = ComponentStyleConfig &
Pick<ButtonProps, 'classNames' | 'styles' | 'autoInsertSpace' | 'variant' | 'color'>;
@ -367,7 +372,9 @@ export interface WaveConfig {
export interface ConfigComponentProps {
input?: InputConfig;
inputSearch?: InputSearchConfig;
textArea?: TextAreaConfig;
otp?: OTPConfig;
inputNumber?: InputNumberConfig;
pagination?: PaginationConfig;
space?: SpaceConfig;

View File

@ -136,7 +136,9 @@ const {
| image | Set Image common props | { className?: string, style?: React.CSSProperties, preview?: { closeIcon?: React.ReactNode, classNames?:[ImageConfig\["classNames"\]](/components/image#semantic-dom), styles?: [ImageConfig\["styles"\]](/components/image#semantic-dom) } } | - | 5.7.0, `closeIcon`: 5.14.0, `classNames` and `styles`: 6.0.0 |
| input | Set Input common props | { autoComplete?: string, className?: string, style?: React.CSSProperties, allowClear?: boolean \| { clearIcon?: ReactNode } } | - | 4.2.0, `allowClear`: 5.15.0 |
| inputNumber | Set InputNumber common props | { className?: string, style?: React.CSSProperties, classNames?: [InputNumberConfig\["classNames"\]](/components/input-number#semantic-dom), styles?: [InputNumberConfig\["styles"\]](/components/input-number#semantic-dom) } | - | |
| textArea | Set TextArea common props | { autoComplete?: string, className?: string, style?: React.CSSProperties, allowClear?: boolean \| { clearIcon?: ReactNode } } | - | 5.15.0 |
| otp | Set OTP common props | { className?: string, style?: React.CSSProperties, classNames?: [OTPConfig\["classNames"\]](/components/input#semantic-otp), styles?: [OTPConfig\["styles"\]](/components/input#semantic-otp) } | - | |
| inputSearch | Set Search common props | { className?: string, style?: React.CSSProperties, classNames?: [InputSearchConfig\["classNames"\]](/components/input#semantic-search), styles?: [InputSearchConfig\["styles"\]](/components/input#semantic-search) } | - | |
| textArea | Set TextArea common props | { autoComplete?: string, className?: string, style?: React.CSSProperties,classNames?:[TextAreaConfig\["classNames"\]](/components/input#semantic-textarea), styles?: [TextAreaConfig\["styles"\]](/components/input#semantic-textarea), allowClear?: boolean \| { clearIcon?: ReactNode } } | - | 5.15.0 |
| layout | Set Layout common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| list | Set List common props | { className?: string, style?: React.CSSProperties, item?:{ classNames: [ListItemProps\["classNames"\]](/components/list#listitem), styles: [ListItemProps\["styles"\]](/components/list#listitem) } } | - | 5.7.0 |
| masonry | Set Masonry common props | { className?: string, style?: React.CSSProperties, classNames?: [MasonryProps\["classNames"\]](/components/masonry#semantic-dom), styles?: [MasonryProps\["styles"\]](/components/masonry#semantic-dom) } | - | |

View File

@ -35,7 +35,9 @@ import type {
FormConfig,
ImageConfig,
InputConfig,
InputSearchConfig,
InputNumberConfig,
OTPConfig,
ListConfig,
MasonryConfig,
MentionsConfig,
@ -158,6 +160,8 @@ export interface ConfigProviderProps {
variant?: Variant;
form?: FormConfig;
input?: InputConfig;
inputSearch?: InputSearchConfig;
otp?: OTPConfig;
inputNumber?: InputNumberConfig;
textArea?: TextAreaConfig;
select?: SelectConfig;
@ -372,6 +376,7 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = (props) => {
pagination,
input,
textArea,
otp,
empty,
badge,
radio,
@ -470,6 +475,7 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = (props) => {
image,
input,
textArea,
otp,
layout,
list,
mentions,

View File

@ -136,9 +136,11 @@ const {
| floatButtonGroup | 设置 FloatButton.Group 组件的通用属性 | { closeIcon?: React.ReactNode } | - | 5.16.0 |
| form | 设置 Form 组件的通用属性 | { className?: string, style?: React.CSSProperties, validateMessages?: [ValidateMessages](/components/form-cn#validatemessages), requiredMark?: boolean \| `optional`, colon?: boolean, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options), classNames?:[FormConfig\["classNames"\]](/components/form-cn#semantic-dom), styles?: [FormConfig\["styles"\]](/components/form-cn#semantic-dom) } | - | `requiredMark`: 4.8.0; `colon`: 4.18.0; `scrollToFirstError`: 5.2.0; `className``style`: 5.7.0 |
| image | 设置 Image 组件的通用属性 | { className?: string, style?: React.CSSProperties, preview?: { closeIcon?: React.ReactNode, classNames?:[ImageConfig\["classNames"\]](/components/image-cn#semantic-dom), styles?: [ImageConfig\["styles"\]](/components/image-cn#semantic-dom) } } | - | 5.7.0, `closeIcon`: 5.14.0, `classNames``styles`: 6.0.0 |
| input | 设置 Input 组件的通用属性 | { autoComplete?: string, className?: string, style?: React.CSSProperties, allowClear?: boolean \| { clearIcon?: ReactNode } } | - | 5.7.0, `allowClear`: 5.15.0 |
| input | 设置 Input 组件的通用属性 | { autoComplete?: string, className?: string, style?: React.CSSProperties,classNames?:[InputConfig\["classNames"\]](/components/input-cn#semantic-input), styles?: [InputConfig\["styles"\]](/components/input-cn#semantic-input), allowClear?: boolean \| { clearIcon?: ReactNode } } | - | 5.7.0, `allowClear`: 5.15.0 |
| inputNumber | 设置 Input 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [InputNumberConfig\["classNames"\]](/components/input-number-cn#semantic-dom), styles?: [InputNumberConfig\["styles"\]](/components/input-number-cn#semantic-dom) } | - | |
| textArea | 设置 TextArea 组件的通用属性 | { autoComplete?: string, className?: string, style?: React.CSSProperties, allowClear?: boolean \| { clearIcon?: ReactNode } } | - | 5.15.0 |
| otp | 设置 OTP 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [OTPConfig\["classNames"\]](/components/input-cn#semantic-otp), styles?: [OTPConfig\["styles"\]](/components/input-cn#semantic-otp) } | - | |
| inputSearch | 设置 Search 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [InputSearchConfig\["classNames"\]](/components/input-cn#semantic-search), styles?: [InputSearchConfig\["styles"\]](/components/input-cn#semantic-search) } | - | |
| textArea | 设置 TextArea 组件的通用属性 | { autoComplete?: string, className?: string, style?: React.CSSProperties,classNames?:[TextAreaConfig\["classNames"\]](/components/input-cn#semantic-textarea), styles?: [TextAreaConfig\["styles"\]](/components/input-cn#semantic-textarea), allowClear?: boolean \| { clearIcon?: ReactNode } } | - | 5.15.0 |
| layout | 设置 Layout 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| list | 设置 List 组件的通用属性 | { className?: string, style?: React.CSSProperties, item?:{ classNames: [ListItemProps\["classNames"\]](/components/list-cn#listitem), styles: [ListItemProps\["styles"\]](/components/list-cn#listitem) } } | - | 5.7.0 |
| masonry | 设置 Masonry 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [MasonryProps\["classNames"\]](/components/masonry#semantic-dom), styles?: [MasonryProps\["styles"\]](/components/masonry#semantic-dom) } | - | |

View File

@ -3,7 +3,7 @@ import type { InputRef, InputProps as RcInputProps } from '@rc-component/input';
import RcInput from '@rc-component/input';
import { InputFocusOptions, triggerFocus } from '@rc-component/input/lib/utils/commonUtils';
import { composeRef } from '@rc-component/util/lib/ref';
import classNames from 'classnames';
import cls from 'classnames';
import ContextIsolator from '../_util/ContextIsolator';
import getAllowClear from '../_util/getAllowClear';
@ -22,11 +22,14 @@ import { useCompactItemContext } from '../space/Compact';
import useRemovePasswordTimeout from './hooks/useRemovePasswordTimeout';
import useStyle, { useSharedStyle } from './style';
import { hasPrefixSuffix } from './utils';
import useMergeSemantic from '../_util/hooks/useMergeSemantic';
export type { InputFocusOptions };
export type { InputRef };
export { triggerFocus };
type SemanticName = 'root' | 'prefix' | 'suffix' | 'input' | 'count';
export interface InputProps
extends Omit<
RcInputProps,
@ -43,6 +46,8 @@ export interface InputProps
* @default "outlined"
*/
variant?: Variant;
classNames?: Partial<Record<SemanticName, string>>;
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
[key: `data-${string}`]: string | undefined;
}
@ -64,7 +69,7 @@ const Input = forwardRef<InputRef, InputProps>((props, ref) => {
styles,
rootClassName,
onChange,
classNames: classes,
classNames,
variant: customVariant,
...rest
} = props;
@ -103,6 +108,11 @@ const Input = forwardRef<InputRef, InputProps>((props, ref) => {
const disabled = React.useContext(DisabledContext);
const mergedDisabled = customDisabled ?? disabled;
const [mergedClassNames, mergedStyles] = useMergeSemantic(
[contextClassNames, classNames],
[contextStyles, styles],
);
// ===================== Status =====================
const { status: contextStatus, hasFeedback, feedbackIcon } = useContext(FormItemInputContext);
const mergedStatus = getMergedStatus(contextStatus, customStatus);
@ -166,17 +176,18 @@ const Input = forwardRef<InputRef, InputProps>((props, ref) => {
disabled={mergedDisabled}
onBlur={handleBlur}
onFocus={handleFocus}
style={{ ...contextStyle, ...style }}
styles={{ ...contextStyles, ...styles }}
style={{ ...mergedStyles.root, ...contextStyle, ...style }}
styles={mergedStyles}
suffix={suffixNode}
allowClear={mergedAllowClear}
className={classNames(
className={cls(
className,
rootClassName,
cssVarCls,
rootCls,
compactItemClassnames,
contextClassName,
mergedClassNames.root,
)}
onChange={handleChange}
addonBefore={
@ -194,25 +205,23 @@ const Input = forwardRef<InputRef, InputProps>((props, ref) => {
)
}
classNames={{
...classes,
...contextClassNames,
input: classNames(
...mergedClassNames,
input: cls(
{
[`${prefixCls}-sm`]: mergedSize === 'small',
[`${prefixCls}-lg`]: mergedSize === 'large',
[`${prefixCls}-rtl`]: direction === 'rtl',
},
classes?.input,
contextClassNames.input,
mergedClassNames.input,
hashId,
),
variant: classNames(
variant: cls(
{
[`${prefixCls}-${variant}`]: enableVariantCls,
},
getStatusClassNames(prefixCls, mergedStatus),
),
affixWrapper: classNames(
affixWrapper: cls(
{
[`${prefixCls}-affix-wrapper-sm`]: mergedSize === 'small',
[`${prefixCls}-affix-wrapper-lg`]: mergedSize === 'large',
@ -220,13 +229,13 @@ const Input = forwardRef<InputRef, InputProps>((props, ref) => {
},
hashId,
),
wrapper: classNames(
wrapper: cls(
{
[`${prefixCls}-group-rtl`]: direction === 'rtl',
},
hashId,
),
groupWrapper: classNames(
groupWrapper: cls(
{
[`${prefixCls}-group-wrapper-sm`]: mergedSize === 'small',
[`${prefixCls}-group-wrapper-lg`]: mergedSize === 'large',

View File

@ -1,13 +1,14 @@
import * as React from 'react';
import useEvent from '@rc-component/util/lib/hooks/useEvent';
import pickAttrs from '@rc-component/util/lib/pickAttrs';
import classNames from 'classnames';
import cls from 'classnames';
import useMergeSemantic from '../../_util/hooks/useMergeSemantic';
import { getMergedStatus } from '../../_util/statusUtils';
import type { InputStatus } from '../../_util/statusUtils';
import { devUseWarning } from '../../_util/warning';
import { ConfigContext } from '../../config-provider';
import type { Variant } from '../../config-provider';
import { useComponentConfig } from '../../config-provider/context';
import useSize from '../../config-provider/hooks/useSize';
import type { SizeType } from '../../config-provider/SizeContext';
import { FormItemInputContext } from '../../form/context';
@ -17,6 +18,8 @@ import useStyle from '../style/otp';
import OTPInput from './OTPInput';
import type { OTPInputProps } from './OTPInput';
type SemanticName = 'root' | 'input' | 'separator';
export interface OTPRef {
focus: VoidFunction;
blur: VoidFunction;
@ -51,6 +54,9 @@ export interface OTPProps
type?: React.HTMLInputTypeAttribute;
onInput?: (value: string[]) => void;
classNames?: Partial<Record<SemanticName, string>>;
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
}
function strToArr(str: string) {
@ -61,15 +67,21 @@ interface SeparatorProps {
index: number;
prefixCls: string;
separator: OTPProps['separator'];
className?: string;
style?: React.CSSProperties;
}
const Separator: React.FC<Readonly<SeparatorProps>> = (props) => {
const { index, prefixCls, separator } = props;
const { index, prefixCls, separator, className: semanticClassName, style: semanticStyle } = props;
const separatorNode = typeof separator === 'function' ? separator(index) : separator;
if (!separatorNode) {
return null;
}
return <span className={`${prefixCls}-separator`}>{separatorNode}</span>;
return (
<span className={cls(`${prefixCls}-separator`, semanticClassName)} style={semanticStyle}>
{separatorNode}
</span>
);
};
const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
@ -90,6 +102,10 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
type,
onInput,
inputMode,
classNames,
styles,
className,
style,
...restProps
} = props;
@ -102,9 +118,21 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
);
}
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const {
classNames: contextClassNames,
styles: contextStyles,
getPrefixCls,
direction,
style: contextStyle,
className: contextClassName,
} = useComponentConfig('otp');
const prefixCls = getPrefixCls('otp', customizePrefixCls);
const [mergedClassNames, mergedStyles] = useMergeSemantic(
[contextClassNames, classNames],
[contextStyles, styles],
);
const domAttrs = pickAttrs(restProps, {
aria: true,
data: true,
@ -248,7 +276,8 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
<div
{...domAttrs}
ref={containerRef}
className={classNames(
className={cls(
className,
prefixCls,
{
[`${prefixCls}-sm`]: mergedSize === 'small',
@ -257,7 +286,10 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
},
cssVarCls,
hashId,
contextClassName,
mergedClassNames.root,
)}
style={{ ...mergedStyles.root, ...contextStyle, ...style }}
role="group"
>
<FormItemInputContext.Provider value={proxyFormContext}>
@ -273,7 +305,8 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
index={index}
size={mergedSize}
htmlSize={1}
className={`${prefixCls}-input`}
className={cls(mergedClassNames.input, `${prefixCls}-input`)}
style={mergedStyles.input}
onChange={onInputChange}
value={singleValue}
onActiveChange={onInputActiveChange}
@ -281,7 +314,13 @@ const OTP = React.forwardRef<OTPRef, OTPProps>((props, ref) => {
{...inputSharedProps}
/>
{index < length - 1 && (
<Separator separator={separator} index={index} prefixCls={prefixCls} />
<Separator
separator={separator}
index={index}
prefixCls={prefixCls}
className={cls(mergedClassNames.separator)}
style={mergedStyles.separator}
/>
)}
</React.Fragment>
);

View File

@ -1,16 +1,20 @@
import * as React from 'react';
import SearchOutlined from '@ant-design/icons/SearchOutlined';
import { composeRef } from '@rc-component/util/lib/ref';
import classNames from 'classnames';
import cls from 'classnames';
import useMergeSemantic from '../_util/hooks/useMergeSemantic';
import { cloneElement } from '../_util/reactNode';
import Button from '../button';
import { ConfigContext } from '../config-provider';
import type { ButtonSemanticName } from '../button/button';
import { useComponentConfig } from '../config-provider/context';
import useSize from '../config-provider/hooks/useSize';
import { useCompactItemContext } from '../space/Compact';
import type { InputProps, InputRef } from './Input';
import Input from './Input';
type SemanticName = 'root' | 'input' | 'prefix' | 'suffix' | 'count';
export interface SearchProps extends InputProps {
inputPrefixCls?: string;
onSearch?: (
@ -26,6 +30,12 @@ export interface SearchProps extends InputProps {
enterButton?: React.ReactNode;
loading?: boolean;
onPressEnter?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
classNames?: Partial<Record<SemanticName, string>> & {
button?: Partial<Record<ButtonSemanticName, string>>;
};
styles?: Partial<Record<SemanticName, React.CSSProperties>> & {
button?: Partial<Record<ButtonSemanticName, React.CSSProperties>>;
};
}
const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
@ -45,10 +55,27 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
onCompositionEnd,
variant,
onPressEnter: customOnPressEnter,
classNames,
styles,
...restProps
} = props;
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const {
direction,
getPrefixCls,
classNames: contextClassNames,
styles: contextStyles,
} = useComponentConfig('inputSearch');
const [mergedClassNames, mergedStyles] = useMergeSemantic(
[contextClassNames, classNames],
[contextStyles, styles],
{
button: {
_default: 'root',
},
},
);
const composedRef = React.useRef<boolean>(false);
@ -92,7 +119,7 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
};
const searchIcon = typeof enterButton === 'boolean' ? <SearchOutlined /> : null;
const btnClassName = `${prefixCls}-button`;
const btnClassName = cls(`${prefixCls}-button`, mergedClassNames.button?.root);
let button: React.ReactNode;
const enterButtonAsElement = (enterButton || {}) as React.ReactElement;
@ -120,6 +147,8 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
} else {
button = (
<Button
classNames={mergedClassNames.button}
styles={mergedStyles.button}
className={btnClassName}
color={enterButton ? 'primary' : 'default'}
size={size}
@ -151,7 +180,7 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
];
}
const cls = classNames(
const mergedClassName = cls(
prefixCls,
{
[`${prefixCls}-rtl`]: direction === 'rtl',
@ -159,6 +188,7 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
[`${prefixCls}-with-button`]: !!enterButton,
},
className,
mergedClassNames.root,
);
const handleOnCompositionStart: React.CompositionEventHandler<HTMLInputElement> = (e) => {
@ -173,7 +203,9 @@ const Search = React.forwardRef<InputRef, SearchProps>((props, ref) => {
const inputProps: InputProps = {
...restProps,
className: cls,
className: mergedClassName,
classNames: mergedClassNames,
styles: mergedStyles,
prefixCls: inputPrefixCls,
type: 'search',
size,

View File

@ -5,7 +5,7 @@ import type {
TextAreaRef as RcTextAreaRef,
} from '@rc-component/textarea';
import RcTextArea from '@rc-component/textarea';
import classNames from 'classnames';
import cls from 'classnames';
import getAllowClear from '../_util/getAllowClear';
import type { InputStatus } from '../_util/statusUtils';
@ -24,6 +24,9 @@ import type { InputFocusOptions } from './Input';
import { triggerFocus } from './Input';
import { useSharedStyle } from './style';
import useStyle from './style/textarea';
import useMergeSemantic from '../_util/hooks/useMergeSemantic';
type SemanticName = 'root' | 'textarea' | 'count';
export interface TextAreaProps extends Omit<RcTextAreaProps, 'suffix'> {
/** @deprecated Use `variant` instead */
@ -36,6 +39,8 @@ export interface TextAreaProps extends Omit<RcTextAreaProps, 'suffix'> {
* @default "outlined"
*/
variant?: Variant;
classNames?: Partial<Record<SemanticName, string>>;
styles?: Partial<Record<SemanticName, React.CSSProperties>>;
}
export interface TextAreaRef {
@ -52,7 +57,7 @@ const TextArea = forwardRef<TextAreaRef, TextAreaProps>((props, ref) => {
disabled: customDisabled,
status: customStatus,
allowClear,
classNames: classes,
classNames,
rootClassName,
className,
style,
@ -92,6 +97,11 @@ const TextArea = forwardRef<TextAreaRef, TextAreaProps>((props, ref) => {
} = React.useContext(FormItemInputContext);
const mergedStatus = getMergedStatus(contextStatus, customStatus);
const [mergedClassNames, mergedStyles] = useMergeSemantic(
[contextClassNames, classNames],
[contextStyles, styles],
);
// ===================== Ref ======================
const innerRef = React.useRef<RcTextAreaRef>(null);
@ -157,40 +167,39 @@ const TextArea = forwardRef<TextAreaRef, TextAreaProps>((props, ref) => {
<RcTextArea
autoComplete={contextAutoComplete}
{...rest}
style={{ ...contextStyle, ...style }}
styles={{ ...contextStyles, ...styles }}
style={{ ...mergedStyles.root, ...contextStyle, ...style }}
styles={mergedStyles}
disabled={mergedDisabled}
allowClear={mergedAllowClear}
className={classNames(
className={cls(
cssVarCls,
rootCls,
className,
rootClassName,
compactItemClassnames,
contextClassName,
mergedClassNames.root,
// Only for wrapper
resizeDirty && `${prefixCls}-textarea-affix-wrapper-resize-dirty`,
)}
classNames={{
...classes,
...contextClassNames,
textarea: classNames(
...mergedClassNames,
textarea: cls(
{
[`${prefixCls}-sm`]: mergedSize === 'small',
[`${prefixCls}-lg`]: mergedSize === 'large',
},
hashId,
classes?.textarea,
contextClassNames.textarea,
mergedClassNames.textarea,
isMouseDown && `${prefixCls}-mouse-active`,
),
variant: classNames(
variant: cls(
{
[`${prefixCls}-${variant}`]: enableVariantCls,
},
getStatusClassNames(prefixCls, mergedStatus),
),
affixWrapper: classNames(
affixWrapper: cls(
`${prefixCls}-textarea-affix-wrapper`,
{
[`${prefixCls}-affix-wrapper-rtl`]: direction === 'rtl',

View File

@ -1,5 +1,107 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Input allowClear semantic dom snapshot 1`] = `
<div>
<span
class="ant-input-affix-wrapper ant-input-outlined custom-class css-var-root ant-input-css-var"
style="background-color: red;"
>
<span
class="ant-input-prefix custom-prefix"
style="color: blue;"
>
prefix
</span>
<input
class="ant-input custom-input"
style="color: red;"
type="text"
value="123"
/>
<span
class="ant-input-suffix custom-suffix"
style="color: yellow;"
>
<span
class="ant-input-show-count-suffix ant-input-show-count-has-suffix custom-count"
style="color: green;"
>
3
</span>
suffix
</span>
</span>
<span
class="ant-input-group-wrapper ant-input-group-wrapper-outlined custom-class css-var-root ant-input-css-var"
style="background-color: red;"
>
<span
class="ant-input-wrapper ant-input-group"
>
<span
class="ant-input-affix-wrapper ant-input-outlined"
>
<span
class="ant-input-prefix custom-prefix"
style="color: blue;"
>
prefix
</span>
<input
class="ant-input custom-input"
style="color: red;"
type="text"
value="123"
/>
<span
class="ant-input-suffix custom-suffix"
style="color: yellow;"
>
<span
class="ant-input-show-count-suffix ant-input-show-count-has-suffix custom-count"
style="color: green;"
>
3
</span>
suffix
</span>
</span>
<span
class="ant-input-group-addon"
>
addon
</span>
</span>
</span>
<input
class="ant-input custom-input ant-input-outlined custom-class css-var-root ant-input-css-var"
style="color: red; background-color: red;"
type="text"
value="123"
/>
<span
class="ant-input-group-wrapper ant-input-group-wrapper-outlined custom-class css-var-root ant-input-css-var"
style="background-color: red;"
>
<span
class="ant-input-wrapper ant-input-group"
>
<input
class="ant-input custom-input ant-input-outlined"
style="color: red;"
type="text"
value="123"
/>
<span
class="ant-input-group-addon"
>
addon
</span>
</span>
</span>
</div>
`;
exports[`Input allowClear should change type when click 1`] = `
<span
class="ant-input-affix-wrapper ant-input-outlined css-var-root ant-input-css-var"
@ -336,108 +438,6 @@ exports[`Input allowClear should not show icon if value is undefined, null or em
</span>
`;
exports[`Input allowClear should support classNames and styles 1`] = `
<div>
<span
class="rc-input-affix-wrapper rc-input-outlined custom-class css-var-root rc-input-css-var"
style="background-color: red;"
>
<span
class="rc-input-prefix custom-prefix"
style="color: blue;"
>
prefix
</span>
<input
class="rc-input custom-input"
style="color: red;"
type="text"
value="123"
/>
<span
class="rc-input-suffix custom-suffix"
style="color: yellow;"
>
<span
class="rc-input-show-count-suffix rc-input-show-count-has-suffix custom-count"
style="color: green;"
>
3
</span>
suffix
</span>
</span>
<span
class="rc-input-group-wrapper rc-input-group-wrapper-outlined custom-class css-var-root rc-input-css-var"
style="background-color: red;"
>
<span
class="rc-input-wrapper rc-input-group"
>
<span
class="rc-input-affix-wrapper rc-input-outlined"
>
<span
class="rc-input-prefix custom-prefix"
style="color: blue;"
>
prefix
</span>
<input
class="rc-input custom-input"
style="color: red;"
type="text"
value="123"
/>
<span
class="rc-input-suffix custom-suffix"
style="color: yellow;"
>
<span
class="rc-input-show-count-suffix rc-input-show-count-has-suffix custom-count"
style="color: green;"
>
3
</span>
suffix
</span>
</span>
<span
class="rc-input-group-addon"
>
addon
</span>
</span>
</span>
<input
class="rc-input custom-input rc-input-outlined custom-class css-var-root rc-input-css-var"
style="color: red; background-color: red;"
type="text"
value="123"
/>
<span
class="rc-input-group-wrapper rc-input-group-wrapper-outlined custom-class css-var-root rc-input-css-var"
style="background-color: red;"
>
<span
class="rc-input-wrapper rc-input-group"
>
<input
class="rc-input custom-input rc-input-outlined"
style="color: red;"
type="text"
value="123"
/>
<span
class="rc-input-group-addon"
>
addon
</span>
</span>
</span>
</div>
`;
exports[`Input rtl render component should be rendered correctly in RTL direction 1`] = `
<input
class="ant-input ant-input-rtl ant-input-outlined css-var-root ant-input-css-var"

View File

@ -3,13 +3,13 @@
exports[`TextArea allowClear classNames and styles should work 1`] = `
<div>
<textarea
class="ant-input custom-textarea ant-input-outlined css-var-root ant-input-css-var custom-class"
class="ant-input custom-textarea ant-input-outlined css-var-root ant-input-css-var custom-class custom-root"
style="color: red; background: red;"
/>
<span
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper ant-input-textarea-show-count ant-input-show-count ant-input-outlined css-var-root ant-input-css-var custom-class"
class="ant-input-affix-wrapper ant-input-textarea-affix-wrapper ant-input-textarea-show-count ant-input-show-count ant-input-outlined css-var-root ant-input-css-var custom-class custom-root"
data-count="0"
style="background: red;"
style="color: red; background: red;"
>
<textarea
class="ant-input custom-textarea"

View File

@ -439,13 +439,12 @@ describe('Input allowClear', () => {
expect(container.querySelector('.ant-input-clear-icon')?.textContent).toBe('clear');
});
it('should support classNames and styles', () => {
it('semantic dom snapshot', () => {
const { container } = render(
<>
<Input
value="123"
showCount
prefixCls="rc-input"
prefix="prefix"
suffix="suffix"
className="custom-class"
@ -467,7 +466,6 @@ describe('Input allowClear', () => {
value="123"
addonAfter="addon"
showCount
prefixCls="rc-input"
prefix="prefix"
suffix="suffix"
className="custom-class"
@ -487,7 +485,6 @@ describe('Input allowClear', () => {
/>
<Input
value="123"
prefixCls="rc-input"
className="custom-class"
style={{ backgroundColor: 'red' }}
classNames={{
@ -499,7 +496,6 @@ describe('Input allowClear', () => {
/>
<Input
value="123"
prefixCls="rc-input"
className="custom-class"
addonAfter="addon"
style={{ backgroundColor: 'red' }}

View File

@ -0,0 +1,166 @@
import React from 'react';
import { render } from '@testing-library/react';
import Input from '..';
const testClassNames = {
root: 'custom-root',
prefix: 'custom-prefix',
input: 'custom-input',
textarea: 'custom-textarea',
suffix: 'custom-suffix',
count: 'custom-count',
separator: 'custom-separator',
button: {
root: 'custom-button-root',
icon: 'custom-button-icon',
content: 'custom-button-content',
},
};
const testStyles = {
root: { color: 'red' },
input: { color: 'blue' },
textarea: { color: 'green' },
prefix: { color: 'yellow' },
suffix: { color: 'purple' },
count: { color: 'orange' },
separator: { color: 'pink' },
button: {
root: { color: 'cyan' },
icon: { color: 'magenta' },
content: { color: 'lime' },
},
};
describe('semantic dom', () => {
it('input should support classNames and styles', () => {
const { container } = render(
<Input
value="123"
showCount
prefix="prefix"
suffix="suffix"
classNames={testClassNames}
styles={testStyles}
/>,
);
const root = container.querySelector('.ant-input-affix-wrapper');
const input = container.querySelector('.ant-input');
const prefix = container.querySelector('.ant-input-prefix');
const suffix = container.querySelector('.ant-input-suffix');
const count = container.querySelector('.ant-input-show-count-suffix');
expect(root).toHaveClass(testClassNames.root);
expect(root).toHaveStyle(testStyles.root);
expect(input).toHaveClass(testClassNames.input);
expect(input).toHaveStyle(testStyles.input);
expect(prefix).toHaveClass(testClassNames.prefix);
expect(prefix).toHaveStyle(testStyles.prefix);
expect(suffix).toHaveClass(testClassNames.suffix);
expect(suffix).toHaveStyle(testStyles.suffix);
expect(count).toHaveClass(testClassNames.count);
expect(count).toHaveStyle(testStyles.count);
});
it('textarea should support classNames and styles', () => {
const { container } = render(
<Input.TextArea classNames={testClassNames} styles={testStyles} showCount />,
);
const root = container.querySelector('.ant-input-textarea-affix-wrapper');
const textarea = container.querySelector('textarea');
const count = container.querySelector('.ant-input-data-count');
expect(root).toHaveClass(testClassNames.root);
expect(root).toHaveStyle(testStyles.root);
expect(textarea).toHaveClass(testClassNames.textarea);
expect(textarea).toHaveStyle(testStyles.textarea);
expect(count).toHaveClass(testClassNames.count);
expect(count).toHaveStyle(testStyles.count);
});
it('search should support classNames and styles', () => {
const { container, getByText } = render(
<Input.Search
loading
enterButton="button text"
classNames={testClassNames}
styles={testStyles}
showCount
prefix="prefix"
suffix="suffix"
/>,
);
const root = container.querySelector('.ant-input-search');
const input = container.querySelector('.ant-input');
const prefix = container.querySelector('.ant-input-prefix');
const suffix = container.querySelector('.ant-input-suffix');
const button = container.querySelector('.ant-btn');
const buttonIcon = container.querySelector('.ant-btn-icon');
const buttonContent = getByText('button text');
const count = container.querySelector('.ant-input-show-count-suffix');
expect(root).toHaveClass(testClassNames.root);
expect(root).toHaveStyle(testStyles.root);
expect(input).toHaveClass(testClassNames.input);
expect(input).toHaveStyle(testStyles.input);
expect(prefix).toHaveClass(testClassNames.prefix);
expect(prefix).toHaveStyle(testStyles.prefix);
expect(suffix).toHaveClass(testClassNames.suffix);
expect(suffix).toHaveStyle(testStyles.suffix);
expect(button).toHaveClass(testClassNames.button.root);
expect(button).toHaveStyle(testStyles.button.root);
expect(buttonIcon).toHaveClass(testClassNames.button.icon);
expect(buttonIcon).toHaveStyle(testStyles.button.icon);
expect(buttonContent).toHaveClass(testClassNames.button.content);
expect(buttonContent).toHaveStyle(testStyles.button.content);
expect(count).toHaveClass(testClassNames.count);
expect(count).toHaveStyle(testStyles.count);
});
it('password should support classNames and styles', () => {
const { container } = render(
<Input.Password
classNames={testClassNames}
styles={testStyles}
showCount
prefix="prefix"
suffix="suffix"
/>,
);
const root = container.querySelector('.ant-input-affix-wrapper');
const input = container.querySelector('.ant-input');
const prefix = container.querySelector('.ant-input-prefix');
const suffix = container.querySelector('.ant-input-suffix');
const count = container.querySelector('.ant-input-show-count-suffix');
expect(root).toHaveClass(testClassNames.root);
expect(root).toHaveStyle(testStyles.root);
expect(input).toHaveClass(testClassNames.input);
expect(input).toHaveStyle(testStyles.input);
expect(prefix).toHaveClass(testClassNames.prefix);
expect(prefix).toHaveStyle(testStyles.prefix);
expect(suffix).toHaveClass(testClassNames.suffix);
expect(suffix).toHaveStyle(testStyles.suffix);
expect(count).toHaveClass(testClassNames.count);
expect(count).toHaveStyle(testStyles.count);
});
it('otp should support classNames and styles', () => {
const { container } = render(
<Input.OTP separator="-" classNames={testClassNames} styles={testStyles} />,
);
const root = container.querySelector('.ant-otp');
const input = container.querySelector('.ant-input');
const separator = container.querySelector('.ant-otp-separator');
expect(root).toHaveClass(testClassNames.root);
expect(root).toHaveStyle(testStyles.root);
expect(input).toHaveClass(testClassNames.input);
expect(input).toHaveStyle(testStyles.input);
expect(separator).toHaveClass(testClassNames.separator);
expect(separator).toHaveStyle(testStyles.separator);
});
});

View File

@ -489,10 +489,14 @@ describe('TextArea allowClear', () => {
className="custom-class"
style={{ background: 'red' }}
classNames={{
root: 'custom-root',
textarea: 'custom-textarea',
count: 'custom-count',
}}
styles={{
root: {
color: 'red',
},
textarea: {
color: 'red',
},
@ -506,10 +510,14 @@ describe('TextArea allowClear', () => {
className="custom-class"
style={{ background: 'red' }}
classNames={{
root: 'custom-root',
textarea: 'custom-textarea',
count: 'custom-count',
}}
styles={{
root: {
color: 'red',
},
textarea: {
color: 'red',
},

View File

@ -7,12 +7,14 @@ import useLocale from '../../../.dumi/hooks/useLocale';
const locales = {
cn: {
root: '根元素',
input: '输入框元素',
prefix: '前缀的包裹元素',
suffix: '后缀的包裹元素',
count: '文字计数元素',
},
en: {
root: 'root element',
input: 'input element',
prefix: 'prefix element',
suffix: 'suffix element',
@ -26,10 +28,11 @@ const App: React.FC = () => {
<SemanticPreview
componentName="Input"
semantics={[
{ name: 'input', desc: locale.input, version: '5.4.0' },
{ name: 'prefix', desc: locale.prefix, version: '5.4.0' },
{ name: 'suffix', desc: locale.suffix, version: '5.4.0' },
{ name: 'count', desc: locale.count, version: '5.4.0' },
{ name: 'root', desc: locale.root },
{ name: 'prefix', desc: locale.prefix },
{ name: 'input', desc: locale.input },
{ name: 'suffix', desc: locale.suffix },
{ name: 'count', desc: locale.count },
]}
>
<Input

View File

@ -0,0 +1,36 @@
import React from 'react';
import { Input } from 'antd';
import SemanticPreview from '../../../.dumi/theme/common/SemanticPreview';
import useLocale from '../../../.dumi/hooks/useLocale';
const locales = {
cn: {
root: '根元素',
input: '输入框元素',
separator: '分隔符',
},
en: {
root: 'root element',
input: 'input element',
separator: 'separator element',
},
};
const App: React.FC = () => {
const [locale] = useLocale(locales);
return (
<SemanticPreview
componentName="Input.OTP"
semantics={[
{ name: 'root', desc: locale.root },
{ name: 'input', desc: locale.input },
{ name: 'separator', desc: locale.separator },
]}
>
<Input.OTP separator="-" />
</SemanticPreview>
);
};
export default App;

View File

@ -0,0 +1,48 @@
import React from 'react';
import { EditOutlined, UserOutlined } from '@ant-design/icons';
import { Input } from 'antd';
import SemanticPreview from '../../../.dumi/theme/common/SemanticPreview';
import useLocale from '../../../.dumi/hooks/useLocale';
const locales = {
cn: {
root: '根元素',
input: '输入框元素',
prefix: '前缀的包裹元素',
suffix: '后缀的包裹元素',
count: '文字计数元素',
},
en: {
root: 'root element',
input: 'input element',
prefix: 'prefix element',
suffix: 'suffix element',
count: 'count element',
},
};
const App: React.FC = () => {
const [locale] = useLocale(locales);
return (
<SemanticPreview
componentName="Input.Password"
semantics={[
{ name: 'root', desc: locale.root },
{ name: 'prefix', desc: locale.prefix },
{ name: 'input', desc: locale.input },
{ name: 'suffix', desc: locale.suffix },
{ name: 'count', desc: locale.count },
]}
>
<Input.Password
showCount
prefix={<UserOutlined />}
suffix={<EditOutlined />}
defaultValue="Hello, Ant Design"
/>
</SemanticPreview>
);
};
export default App;

View File

@ -0,0 +1,59 @@
import React from 'react';
import { EditOutlined, UserOutlined } from '@ant-design/icons';
import { Input } from 'antd';
import useLocale from '../../../.dumi/hooks/useLocale';
import SemanticPreview from '../../../.dumi/theme/common/SemanticPreview';
const locales = {
cn: {
root: '根元素',
input: '输入框元素',
prefix: '前缀的包裹元素',
suffix: '后缀的包裹元素',
count: '文字计数元素',
'button.root': '按钮根元素',
'button.icon': '按钮图标元素',
'button.content': '按钮内容元素',
},
en: {
root: 'root element',
input: 'input element',
prefix: 'prefix element',
suffix: 'suffix element',
count: 'count element',
'button.root': 'button root element',
'button.icon': 'button icon element',
'button.content': 'button content element',
},
};
const App: React.FC = () => {
const [locale] = useLocale(locales);
return (
<SemanticPreview
componentName="Input.Search"
semantics={[
{ name: 'root', desc: locale.root },
{ name: 'prefix', desc: locale.prefix },
{ name: 'input', desc: locale.input },
{ name: 'suffix', desc: locale.suffix },
{ name: 'count', desc: locale.count },
{ name: 'button.root', desc: locale['button.root'] },
{ name: 'button.icon', desc: locale['button.icon'] },
{ name: 'button.content', desc: locale['button.content'] },
]}
>
<Input.Search
loading
enterButton="Searching..."
showCount
prefix={<UserOutlined />}
suffix={<EditOutlined />}
defaultValue="Hello, Ant Design"
/>
</SemanticPreview>
);
};
export default App;

View File

@ -6,10 +6,12 @@ import useLocale from '../../../.dumi/hooks/useLocale';
const locales = {
cn: {
textarea: '输入框元素',
root: '根元素',
textarea: '文本域元素',
count: '文字计数元素',
},
en: {
root: 'root element',
textarea: 'textarea element',
count: 'count element',
},
@ -19,10 +21,11 @@ const App: React.FC = () => {
const [locale] = useLocale(locales);
return (
<SemanticPreview
componentName="TextArea"
componentName="Input.TextArea"
semantics={[
{ name: 'textarea', desc: locale.textarea, version: '5.4.0' },
{ name: 'count', desc: locale.count, version: '5.4.0' },
{ name: 'root', desc: locale.root },
{ name: 'textarea', desc: locale.textarea },
{ name: 'count', desc: locale.count },
]}
>
<Input.TextArea defaultValue="Hello, Ant Design" rows={3} count={{ max: 100, show: true }} />

View File

@ -55,7 +55,7 @@ Common props ref[Common props](/docs/react/common-props)
| addonBefore | The label text displayed before (on the left side of) the input field | ReactNode | - | |
| allowClear | If allow to remove input content with clear icon | boolean \| { clearIcon: ReactNode } | false | |
| ~~bordered~~ | Whether has border style, please use `variant` instead | boolean | true | 4.5.0 |
| classNames | Semantic DOM class | Record<[SemanticDOM](#input-1), string> | - | 5.4.0 |
| classNames | Semantic DOM class | Record<[SemanticDOM](#semantic-input), string> | - | 5.4.0 |
| count | Character count config | [CountConfig](#countconfig) | - | 5.10.0 |
| defaultValue | The initial input content | string | - | |
| disabled | Whether the input is disabled | boolean | false | |
@ -64,7 +64,7 @@ Common props ref[Common props](/docs/react/common-props)
| prefix | The prefix icon for the Input | ReactNode | - | |
| showCount | Whether to show character count | boolean \| { formatter: (info: { value: string, count: number, maxLength?: number }) => ReactNode } | false | 4.18.0 info.value: 4.23.0 |
| status | Set validation status | 'error' \| 'warning' | - | 4.19.0 |
| styles | Semantic DOM style | Record<[SemanticDOM](#input-1), CSSProperties> | - | 5.4.0 |
| styles | Semantic DOM style | Record<[SemanticDOM](#semantic-input), CSSProperties> | - | 5.4.0 |
| size | The size of the input box. Note: in the context of a form, the `middle` size is used | `large` \| `middle` \| `small` | - | |
| suffix | The suffix icon for the Input | ReactNode | - | |
| type | The type of input, see: [MDN](https://developer.mozilla.org/docs/Web/HTML/Element/input#Form_%3Cinput%3E_types)( use `Input.TextArea` instead of `type="textarea"`) | string | `text` | |
@ -100,18 +100,20 @@ Same as Input, and more:
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| autoSize | Height auto size feature, can be set to true \| false or an object { minRows: 2, maxRows: 6 } | boolean \| object | false | |
| classNames | Semantic DOM class | Record<[SemanticDOM](#inputtextarea-1), string> | - | 5.4.0 |
| styles | Semantic DOM style | Record<[SemanticDOM](#inputtextarea-1), CSSProperties> | - | 5.4.0 |
| classNames | Semantic DOM class | Record<[SemanticDOM](#semantic-textarea), string> | - | 5.4.0 |
| styles | Semantic DOM style | Record<[SemanticDOM](#semantic-textarea), CSSProperties> | - | 5.4.0 |
The rest of the props of `Input.TextArea` are the same as the original [textarea](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea).
### Input.Search
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| --- | --- | --- | --- | --- |
| classNames | Semantic DOM class | Record<[SemanticDOM](#semantic-search), string> | - | |
| enterButton | false displays the default button color, true uses the primary color, or you can provide a custom button. Conflicts with addonAfter. | ReactNode | false |
| loading | Search box with loading | boolean | false |
| onSearch | The callback function triggered when you click on the search-icon, the clear-icon or press the Enter key | function(value, event, { source: "input" \| "clear" }) | - |
| styles | Semantic DOM style | Record<[SemanticDOM](#semantic-search), CSSProperties> | - | |
Supports all props of `Input`.
@ -119,7 +121,9 @@ Supports all props of `Input`.
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| classNames | Semantic DOM class | Record<[SemanticDOM](#semantic-password), string> | - | |
| iconRender | Custom toggle button | (visible) => ReactNode | (visible) => (visible ? &lt;EyeOutlined /> : &lt;EyeInvisibleOutlined />) | 4.3.0 |
| styles | Semantic DOM style | Record<[SemanticDOM](#semantic-password), CSSProperties> | - | |
| visibilityToggle | Whether show toggle button or control password visible | boolean \| [VisibilityToggle](#visibilitytoggle) | true | |
### Input.OTP
@ -132,10 +136,12 @@ Added in `5.16.0`.
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| classNames | Semantic DOM class | Record<[SemanticDOM](#semantic-otp), string> | - | |
| defaultValue | Default value | string | - | |
| disabled | Whether the input is disabled | boolean | false | |
| formatter | Format display, blank fields will be filled with ` ` | (value: string) => string | - | |
| separator | render the separator after the input box of the specified index | ReactNode \|((i: number) => ReactNode) | - | 5.24.0 |
| styles | Semantic DOM style | Record<[SemanticDOM](#semantic-otp), CSSProperties> | - | |
| mask | Custom display, the original value will not be modified | boolean \| string | `false` | `5.17.0` |
| length | The number of input elements | number | 6 | |
| status | Set validation status | 'error' \| 'warning' | - | |
@ -161,14 +167,26 @@ Added in `5.16.0`.
## Semantic DOM
### Input
### Input {#semantic-input}
<code src="./demo/_semantic_input.tsx" simplify="true"></code>
### Input.TextArea
### Input.TextArea {#semantic-textarea}
<code src="./demo/_semantic_textarea.tsx" simplify="true"></code>
### Input.Search {#semantic-search}
<code src="./demo/_semantic_search.tsx" simplify="true"></code>
### Input.Password {#semantic-password}
<code src="./demo/_semantic_password.tsx" simplify="true"></code>
### Input.OTP {#semantic-otp}
<code src="./demo/_semantic_otp.tsx" simplify="true"></code>
## Design Token
<ComponentTokenTable component="Input"></ComponentTokenTable>

View File

@ -56,7 +56,7 @@ demo:
| addonBefore | 带标签的 input设置前置标签 | ReactNode | - | |
| allowClear | 可以点击清除图标删除内容 | boolean \| { clearIcon: ReactNode } | - | |
| ~~bordered~~ | 是否有边框, 请使用 `variant` 替换 | boolean | true | 4.5.0 |
| classNames | 语义化结构 class | Record<[SemanticDOM](#input-1), string> | - | 5.4.0 |
| classNames | 语义化结构 class | Record<[SemanticDOM](#semantic-input), string> | - | 5.4.0 |
| count | 字符计数配置 | [CountConfig](#countconfig) | - | 5.10.0 |
| defaultValue | 输入框默认内容 | string | - | |
| disabled | 是否禁用状态,默认为 false | boolean | false | |
@ -65,7 +65,7 @@ demo:
| prefix | 带有前缀图标的 input | ReactNode | - | |
| showCount | 是否展示字数 | boolean \| { formatter: (info: { value: string, count: number, maxLength?: number }) => ReactNode } | false | 4.18.0 info.value: 4.23.0 |
| status | 设置校验状态 | 'error' \| 'warning' | - | 4.19.0 |
| styles | 语义化结构 style | Record<[SemanticDOM](#input-1), CSSProperties> | - | 5.4.0 |
| styles | 语义化结构 style | Record<[SemanticDOM](#semantic-input), CSSProperties> | - | 5.4.0 |
| size | 控件大小。注:标准表单内的输入框大小限制为 `middle` | `large` \| `middle` \| `small` | - | |
| suffix | 带有后缀图标的 input | ReactNode | - | |
| type | 声明 input 类型,同原生 input 标签的 type 属性,见:[MDN](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/input#属性)(请直接使用 `Input.TextArea` 代替 `type="textarea"`) | string | `text` | |
@ -101,18 +101,20 @@ interface CountConfig {
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| autoSize | 自适应内容高度,可设置为 true \| false 或对象:{ minRows: 2, maxRows: 6 } | boolean \| object | false | |
| classNames | 语义化结构 class | Record<[SemanticDOM](#inputtextarea-1), string> | - | 5.4.0 |
| styles | 语义化结构 style | Record<[SemanticDOM](#inputtextarea-1), CSSProperties> | - | 5.4.0 |
| classNames | 语义化结构 class | Record<[SemanticDOM](#semantic-textarea), string> | - | 5.4.0 |
| styles | 语义化结构 style | Record<[SemanticDOM](#semantic-textarea), CSSProperties> | - | 5.4.0 |
`Input.TextArea` 的其他属性和浏览器自带的 [textarea](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea) 一致。
### Input.Search
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| --- | --- | --- | --- | --- |
| classNames | 语义化结构 class | Record<[SemanticDOM](#semantic-search), string> | - | |
| enterButton | 是否有确认按钮,可设为按钮文字。该属性会与 `addonAfter` 冲突。 | ReactNode | false |
| loading | 搜索 loading | boolean | false |
| onSearch | 点击搜索图标、清除图标,或按下回车键时的回调 | function(value, event, { source: "input" \| "clear" }) | - |
| styles | 语义化结构 style | Record<[SemanticDOM](#semantic-search), CSSProperties> | - | |
其余属性和 Input 一致。
@ -120,7 +122,9 @@ interface CountConfig {
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| classNames | 语义化结构 class | Record<[SemanticDOM](#semantic-password), string> | - | |
| iconRender | 自定义切换按钮 | (visible) => ReactNode | (visible) => (visible ? &lt;EyeOutlined /> : &lt;EyeInvisibleOutlined />) | 4.3.0 |
| styles | 语义化结构 style | Record<[SemanticDOM](#semantic-password), CSSProperties> | - | |
| visibilityToggle | 是否显示切换按钮或者控制密码显隐 | boolean \| [VisibilityToggle](#visibilitytoggle) | true | |
### Input.OTP
@ -133,6 +137,7 @@ interface CountConfig {
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| classNames | 语义化结构 class | Record<[SemanticDOM](#semantic-otp), string> | - | |
| defaultValue | 默认值 | string | - | |
| disabled | 是否禁用 | boolean | false | |
| formatter | 格式化展示,留空字段会被 ` ` 填充 | (value: string) => string | - | |
@ -140,6 +145,7 @@ interface CountConfig {
| mask | 自定义展示,和 `formatter` 的区别是不会修改原始值 | boolean \| string | `false` | `5.17.0` |
| length | 输入元素数量 | number | 6 | |
| status | 设置校验状态 | 'error' \| 'warning' | - | |
| styles | 语义化结构 style | Record<[SemanticDOM](#semantic-otp), CSSProperties> | - | |
| size | 输入框大小 | `small` \| `middle` \| `large` | `middle` | |
| variant | 形态变体 | `outlined` \| `borderless` \| `filled` \| `underlined` | `outlined` | `underlined`: 5.24.0 |
| value | 输入框内容 | string | - | |
@ -162,14 +168,26 @@ interface CountConfig {
## Semantic DOM
### Input
### Input {#semantic-input}
<code src="./demo/_semantic_input.tsx" simplify="true"></code>
### Input.TextArea
### Input.TextArea {#semantic-textarea}
<code src="./demo/_semantic_textarea.tsx" simplify="true"></code>
### Input.Search {#semantic-search}
<code src="./demo/_semantic_search.tsx" simplify="true"></code>
### Input.Password {#semantic-password}
<code src="./demo/_semantic_password.tsx" simplify="true"></code>
### Input.OTP {#semantic-otp}
<code src="./demo/_semantic_otp.tsx" simplify="true"></code>
## 主题变量Design Token
<ComponentTokenTable component="Input"></ComponentTokenTable>

View File

@ -116,9 +116,9 @@ exports[`site test Component components/image en Page 1`] = `4`;
exports[`site test Component components/image zh Page 1`] = `4`;
exports[`site test Component components/input en Page 1`] = `7`;
exports[`site test Component components/input en Page 1`] = `6`;
exports[`site test Component components/input zh Page 1`] = `7`;
exports[`site test Component components/input zh Page 1`] = `6`;
exports[`site test Component components/input-number en Page 1`] = `2`;