feat: CP support Input.TextArea allowClear、autoComplete、className、classNames、style、styles (#47589)

* feat: CP support Input allowClear

* feat: CP support Input allowClear

* fix: fix

* fix: fix

* revert

* revert

* Update Input.tsx

Signed-off-by: lijianan <574980606@qq.com>

* Update getAllowClear.tsx

Signed-off-by: lijianan <574980606@qq.com>

* Update getAllowClear.tsx

Signed-off-by: lijianan <574980606@qq.com>

* test: add test case

---------

Signed-off-by: lijianan <574980606@qq.com>
This commit is contained in:
lijianan 2024-02-28 09:54:48 +08:00 committed by GitHub
parent 6c0d3e46c4
commit 271daafa39
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 84 additions and 18 deletions

View File

@ -13,7 +13,7 @@ const getAllowClear = (allowClear: AllowClear): AllowClear => {
clearIcon: <CloseCircleFilled />,
};
}
return mergedAllowClear;
};

View File

@ -499,6 +499,7 @@ describe('ConfigProvider support style and className props', () => {
allowClear: {
clearIcon: <span className="cp-test-icon">cp-test-icon</span>,
},
autoComplete: 'test-cp-autocomplete',
}}
>
<Input
@ -522,6 +523,61 @@ describe('ConfigProvider support style and className props', () => {
expect(inputElement).toHaveClass('cp-classNames-input');
expect(inputElement).toHaveStyle({ color: 'blue' });
expect(inputElement?.getAttribute('autocomplete')).toBe('test-autocomplete');
expect(inputElement?.getAttribute('autocomplete')).not.toBe('test-cp-autocomplete');
expect(
container?.querySelector<HTMLSpanElement>('.ant-input-affix-wrapper .cp-test-icon'),
).toBeTruthy();
});
it('Should Input.TextArea autoComplete & className & style & classNames & styles & allowClear works', () => {
const { container } = render(
<ConfigProvider
textArea={{
className: 'cp-textArea',
style: { backgroundColor: 'yellow' },
classNames: {
textarea: 'cp-classNames-textArea',
count: 'cp-classNames-count',
},
styles: {
textarea: {
color: 'blue',
},
count: {
color: 'red',
},
},
allowClear: {
clearIcon: <span className="cp-test-icon">cp-test-icon</span>,
},
autoComplete: 'test-cp-autocomplete',
}}
>
<Input.TextArea
autoComplete="test-autocomplete"
placeholder="Basic usage"
value="test"
prefix="¥"
count={{ show: true }}
/>
</ConfigProvider>,
);
const wrapperElement = container.querySelector<HTMLSpanElement>('.ant-input-affix-wrapper');
expect(wrapperElement).toHaveClass('cp-textArea');
expect(wrapperElement).toHaveStyle({ backgroundColor: 'yellow' });
const inputElement = container.querySelector<HTMLTextAreaElement>('.ant-input');
expect(inputElement).toHaveClass('cp-classNames-textArea');
expect(inputElement).toHaveStyle({ color: 'blue' });
expect(inputElement?.getAttribute('autocomplete')).toBe('test-autocomplete');
expect(inputElement?.getAttribute('autocomplete')).not.toBe('test-cp-autocomplete');
const countElement = container.querySelector<HTMLSpanElement>(
'.ant-input-affix-wrapper .ant-input-data-count',
);
expect(countElement).toHaveClass('cp-classNames-count');
expect(countElement).toHaveStyle({ color: 'red' });
expect(
container?.querySelector<HTMLSpanElement>('.ant-input-affix-wrapper .cp-test-icon'),
).toBeTruthy();

View File

@ -10,7 +10,7 @@ import type { CollapseProps } from '../collapse';
import type { DrawerProps } from '../drawer';
import type { FlexProps } from '../flex/interface';
import type { FormProps } from '../form/Form';
import type { InputProps } from '../input';
import type { InputProps, TextAreaProps } from '../input';
import type { Locale } from '../locale';
import type { MenuProps } from '../menu';
import type { ModalProps } from '../modal';
@ -103,6 +103,9 @@ export type BadgeConfig = ComponentStyleConfig & Pick<BadgeProps, 'classNames' |
export type InputConfig = ComponentStyleConfig &
Pick<InputProps, 'autoComplete' | 'classNames' | 'styles' | 'allowClear'>;
export type TextAreaConfig = ComponentStyleConfig &
Pick<TextAreaProps, 'autoComplete' | 'classNames' | 'styles' | 'allowClear'>;
export type ButtonConfig = ComponentStyleConfig & Pick<ButtonProps, 'classNames' | 'styles'>;
export type NotificationConfig = ComponentStyleConfig & Pick<ArgsProps, 'closeIcon'>;
@ -138,6 +141,7 @@ export interface ConfigConsumerProps {
csp?: CSPConfig;
autoInsertSpaceInButton?: boolean;
input?: InputConfig;
textArea?: TextAreaConfig;
pagination?: ComponentStyleConfig & Pick<PaginationProps, 'showSizeChanger'>;
locale?: Locale;
direction?: DirectionType;

View File

@ -125,6 +125,7 @@ const {
| form | Set Form common props | { className?: string, style?: React.CSSProperties, validateMessages?: [ValidateMessages](/components/form/#validatemessages), requiredMark?: boolean \| `optional`, scrollToFirstError?: boolean \| [Options](https://github.com/stipsan/scroll-into-view-if-needed/tree/ece40bd9143f48caf4b99503425ecb16b0ad8249#options) } | - | requiredMark: 4.8.0; colon: 4.18.0; scrollToFirstError: 5.2.0; className: 5.7.0; style: 5.7.0 |
| image | Set Image common props | { className?: string, style?: React.CSSProperties, preview?: { closeIcon?: React.ReactNode } } | - | 5.7.0, closeIcon: 5.14.0 |
| input | Set Input common props | { autoComplete?: string, className?: string, style?: React.CSSProperties, allowClear?: boolean \| { clearIcon?: ReactNode } } | - | 4.2.0, allowClear: 5.15.0 |
| textArea | Set TextArea common props | { autoComplete?: string, className?: string, style?: React.CSSProperties, 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 } | - | 5.7.0 |
| menu | Set Menu common props | { className?: string, style?: React.CSSProperties, expandIcon?: ReactNode \| props => ReactNode } | - | 5.7.0, expandIcon: 5.15.0 |

View File

@ -39,6 +39,7 @@ import type {
TableConfig,
TabsConfig,
TagConfig,
TextAreaConfig,
Theme,
ThemeConfig,
TourConfig,
@ -124,6 +125,7 @@ export interface ConfigProviderProps {
form?: ComponentStyleConfig &
Pick<FormProps, 'requiredMark' | 'colon' | 'scrollToFirstError' | 'validateMessages'>;
input?: InputConfig;
textArea?: TextAreaConfig;
select?: ComponentStyleConfig & Pick<SelectProps, 'showSearch'>;
pagination?: ComponentStyleConfig & Pick<PaginationProps, 'showSizeChanger'>;
locale?: Locale;
@ -321,6 +323,7 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = (props) => {
menu,
pagination,
input,
textArea,
empty,
badge,
radio,
@ -405,6 +408,7 @@ const ProviderChildren: React.FC<ProviderChildrenProps> = (props) => {
steps,
image,
input,
textArea,
layout,
list,
mentions,

View File

@ -127,6 +127,7 @@ const {
| 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)} | - | requiredMark: 4.8.0; colon: 4.18.0; scrollToFirstError: 5.2.0; className: 5.7.0; style: 5.7.0 |
| image | 设置 Image 组件的通用属性 | { className?: string, style?: React.CSSProperties, preview?: { closeIcon?: React.ReactNode } } | - | 5.7.0, closeIcon: 5.14.0 |
| input | 设置 Input 组件的通用属性 | { autoComplete?: string, className?: string, style?: React.CSSProperties, allowClear?: boolean \| { clearIcon?: ReactNode } } | - | 5.7.0, allowClear: 5.15.0 |
| textArea | 设置 TextArea 组件的通用属性 | { autoComplete?: string, className?: string, style?: React.CSSProperties, allowClear?: boolean \| { clearIcon?: ReactNode } } | - | 5.15.0 |
| layout | 设置 Layout 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| list | 设置 List 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| menu | 设置 Menu 组件的通用属性 | { className?: string, style?: React.CSSProperties, expandIcon?: ReactNode \| props => ReactNode } | - | 5.7.0, expandIcon: 5.15.0 |

View File

@ -1,26 +1,25 @@
import * as React from 'react';
import { forwardRef } from 'react';
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
import classNames from 'classnames';
import type { BaseInputProps } from 'rc-input/lib/interface';
import type { TextAreaRef as RcTextAreaRef } from 'rc-textarea';
import RcTextArea from 'rc-textarea';
import type { TextAreaProps as RcTextAreaProps } from 'rc-textarea/lib/interface';
import getAllowClear from '../_util/getAllowClear';
import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import { devUseWarning } from '../_util/warning';
import { 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 '../form/hooks/useVariants';
import useVariant from '../form/hooks/useVariants';
import type { InputFocusOptions } from './Input';
import { triggerFocus } from './Input';
import useStyle from './style';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import type { Variant } from '../form/hooks/useVariants';
import useVariant from '../form/hooks/useVariants';
import { devUseWarning } from '../_util/warning';
export interface TextAreaProps extends Omit<RcTextAreaProps, 'suffix'> {
/** @deprecated Use `variant` instead */
@ -52,6 +51,8 @@ const TextArea = forwardRef<TextAreaRef, TextAreaProps>((props, ref) => {
classNames: classes,
rootClassName,
className,
style,
styles,
variant: customVariant,
...rest
} = props;
@ -61,7 +62,7 @@ const TextArea = forwardRef<TextAreaRef, TextAreaProps>((props, ref) => {
deprecated(!('bordered' in props), 'bordered', 'variant');
}
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const { getPrefixCls, direction, textArea } = React.useContext(ConfigContext);
// ===================== Size =====================
const mergedSize = useSize(customizeSize);
@ -91,28 +92,26 @@ const TextArea = forwardRef<TextAreaRef, TextAreaProps>((props, ref) => {
const prefixCls = getPrefixCls('input', customizePrefixCls);
// Allow clear
let mergedAllowClear: BaseInputProps['allowClear'];
if (typeof allowClear === 'object' && allowClear?.clearIcon) {
mergedAllowClear = allowClear;
} else if (allowClear) {
mergedAllowClear = { clearIcon: <CloseCircleFilled /> };
}
// ===================== Style =====================
const rootCls = useCSSVarCls(prefixCls);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
const [variant, enableVariantCls] = useVariant(customVariant, bordered);
const mergedAllowClear = getAllowClear(allowClear ?? textArea?.allowClear);
return wrapCSSVar(
<RcTextArea
autoComplete={textArea?.autoComplete}
{...rest}
style={{ ...textArea?.style, ...style }}
styles={{ ...textArea?.styles, ...styles }}
disabled={mergedDisabled}
allowClear={mergedAllowClear}
className={classNames(cssVarCls, rootCls, className, rootClassName)}
className={classNames(cssVarCls, rootCls, className, rootClassName, textArea?.className)}
classNames={{
...classes,
...textArea?.classNames,
textarea: classNames(
{
[`${prefixCls}-sm`]: mergedSize === 'small',
@ -120,6 +119,7 @@ const TextArea = forwardRef<TextAreaRef, TextAreaProps>((props, ref) => {
},
hashId,
classes?.textarea,
textArea?.classNames?.textarea,
),
variant: classNames(
{