diff --git a/components/_util/statusUtils.tsx b/components/_util/statusUtils.tsx new file mode 100644 index 0000000000..fb9171604b --- /dev/null +++ b/components/_util/statusUtils.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import CheckCircleFilled from '@ant-design/icons/CheckCircleFilled'; +import ExclamationCircleFilled from '@ant-design/icons/ExclamationCircleFilled'; +import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled'; +import LoadingOutlined from '@ant-design/icons/LoadingOutlined'; +import classNames from 'classnames'; +import { ValidateStatus } from '../form/FormItem'; +import { tuple } from './type'; + +const InputStatuses = tuple('warning', 'error', ''); +export type InputStatus = typeof InputStatuses[number]; + +const iconMap = { + success: CheckCircleFilled, + warning: ExclamationCircleFilled, + error: CloseCircleFilled, + validating: LoadingOutlined, +}; + +export const getFeedbackIcon = (prefixCls: string, status?: ValidateStatus) => { + const IconNode = status && iconMap[status]; + return IconNode ? ( + + + + ) : null; +}; + +export function getStatusClassNames( + prefixCls: string, + status?: ValidateStatus, + hasFeedback?: boolean, +) { + return classNames({ + [`${prefixCls}-status-success`]: status === 'success', + [`${prefixCls}-status-warning`]: status === 'warning', + [`${prefixCls}-status-error`]: status === 'error', + [`${prefixCls}-status-validating`]: status === 'validating', + [`${prefixCls}-has-feedback`]: hasFeedback, + }); +} diff --git a/components/config-provider/__tests__/__snapshots__/components.test.js.snap b/components/config-provider/__tests__/__snapshots__/components.test.js.snap index 7817ae165f..fc2202c243 100644 --- a/components/config-provider/__tests__/__snapshots__/components.test.js.snap +++ b/components/config-provider/__tests__/__snapshots__/components.test.js.snap @@ -13367,7 +13367,7 @@ exports[`ConfigProvider components Form configProvider 1`] = ` class="config-form-item-control-input-content" > @@ -13405,7 +13405,7 @@ exports[`ConfigProvider components Form configProvider componentSize large 1`] = class="config-form-item-control-input-content" > @@ -13443,7 +13443,7 @@ exports[`ConfigProvider components Form configProvider componentSize middle 1`] class="config-form-item-control-input-content" > @@ -13481,7 +13481,7 @@ exports[`ConfigProvider components Form configProvider virtual and dropdownMatch class="ant-form-item-control-input-content" > @@ -13519,7 +13519,7 @@ exports[`ConfigProvider components Form normal 1`] = ` class="ant-form-item-control-input-content" > @@ -13557,7 +13557,7 @@ exports[`ConfigProvider components Form prefixCls 1`] = ` class="prefix-Form-item-control-input-content" > diff --git a/components/form/FormItem.tsx b/components/form/FormItem.tsx index d04a33850f..d879c2cab0 100644 --- a/components/form/FormItem.tsx +++ b/components/form/FormItem.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useContext } from 'react'; +import { useContext, useMemo } from 'react'; import classNames from 'classnames'; import { Field, FormInstance, FieldContext, ListContext } from 'rc-field-form'; import { FieldProps } from 'rc-field-form/lib/Field'; @@ -12,7 +12,12 @@ import { tuple } from '../_util/type'; import devWarning from '../_util/devWarning'; import FormItemLabel, { FormItemLabelProps, LabelTooltipType } from './FormItemLabel'; import FormItemInput, { FormItemInputProps } from './FormItemInput'; -import { FormContext, NoStyleItemContext } from './context'; +import { + FormContext, + FormItemStatusContext, + NoStyleItemContext, + FormItemStatusContextProps, +} from './context'; import { toArray, getFieldId } from './util'; import { cloneElement, isValidElement } from '../_util/reactNode'; import useFrameState from './hooks/useFrameState'; @@ -199,6 +204,28 @@ function FormItem(props: FormItemProps): React.ReactElemen // ===================== Children Ref ===================== const getItemRef = useItemRef(); + // ======================== Status ======================== + let mergedValidateStatus: ValidateStatus = ''; + if (validateStatus !== undefined) { + mergedValidateStatus = validateStatus; + } else if (meta?.validating) { + mergedValidateStatus = 'validating'; + } else if (debounceErrors.length) { + mergedValidateStatus = 'error'; + } else if (debounceWarnings.length) { + mergedValidateStatus = 'warning'; + } else if (meta?.touched) { + mergedValidateStatus = 'success'; + } + + const formItemStatusContext = useMemo( + () => ({ + status: mergedValidateStatus, + hasFeedback, + }), + [mergedValidateStatus, hasFeedback], + ); + // ======================== Render ======================== function renderLayout( baseChildren: React.ReactNode, @@ -208,19 +235,6 @@ function FormItem(props: FormItemProps): React.ReactElemen if (noStyle && !hidden) { return baseChildren; } - // ======================== Status ======================== - let mergedValidateStatus: ValidateStatus = ''; - if (validateStatus !== undefined) { - mergedValidateStatus = validateStatus; - } else if (meta?.validating) { - mergedValidateStatus = 'validating'; - } else if (debounceErrors.length) { - mergedValidateStatus = 'error'; - } else if (debounceWarnings.length) { - mergedValidateStatus = 'warning'; - } else if (meta?.touched) { - mergedValidateStatus = 'success'; - } const itemClassName = { [`${prefixCls}-item`]: true, @@ -281,11 +295,12 @@ function FormItem(props: FormItemProps): React.ReactElemen warnings={debounceWarnings} prefixCls={prefixCls} status={mergedValidateStatus} - validateStatus={mergedValidateStatus} help={help} > - {baseChildren} + + {baseChildren} + diff --git a/components/form/FormItemInput.tsx b/components/form/FormItemInput.tsx index e67a9f502f..4b4c2c8641 100644 --- a/components/form/FormItemInput.tsx +++ b/components/form/FormItemInput.tsx @@ -1,10 +1,5 @@ import * as React from 'react'; import classNames from 'classnames'; -import LoadingOutlined from '@ant-design/icons/LoadingOutlined'; -import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled'; -import CheckCircleFilled from '@ant-design/icons/CheckCircleFilled'; -import ExclamationCircleFilled from '@ant-design/icons/ExclamationCircleFilled'; - import Col, { ColProps } from '../grid/col'; import { ValidateStatus } from './FormItem'; import { FormContext, FormItemPrefixContext } from './context'; @@ -15,8 +10,6 @@ interface FormItemInputMiscProps { children: React.ReactNode; errors: React.ReactNode[]; warnings: React.ReactNode[]; - hasFeedback?: boolean; - validateStatus?: ValidateStatus; /** @private Internal Usage, do not use in any of your production. */ _internalItemRender?: { mark: string; @@ -38,13 +31,6 @@ export interface FormItemInputProps { help?: React.ReactNode; } -const iconMap: { [key: string]: any } = { - success: CheckCircleFilled, - warning: ExclamationCircleFilled, - error: CloseCircleFilled, - validating: LoadingOutlined, -}; - const FormItemInput: React.FC = props => { const { prefixCls, @@ -53,9 +39,7 @@ const FormItemInput: React.FC = pro children, errors, warnings, - hasFeedback, _internalItemRender: formItemRender, - validateStatus, extra, help, } = props; @@ -67,15 +51,6 @@ const FormItemInput: React.FC = pro const className = classNames(`${baseClassName}-control`, mergedWrapperCol.className); - // Should provides additional icon if `hasFeedback` - const IconNode = validateStatus && iconMap[validateStatus]; - const icon = - hasFeedback && IconNode ? ( - - - - ) : null; - // Pass to sub FormItem should not with col info const subFormContext = React.useMemo(() => ({ ...formContext }), [formContext]); delete subFormContext.labelCol; @@ -84,7 +59,6 @@ const FormItemInput: React.FC = pro const inputDom = (
{children}
- {icon}
); const formItemContext = React.useMemo(() => ({ prefixCls, status }), [prefixCls, status]); diff --git a/components/form/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/form/__tests__/__snapshots__/demo-extend.test.ts.snap index 52de5e42a3..3d9f26f35f 100644 --- a/components/form/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/form/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -1477,7 +1477,7 @@ exports[`renders ./components/form/demo/disabled-input-debug.md extend context c class="ant-form-item-control-input-content" >
- -
- - + - - + + + + + + - +
- -
- - + - - + + + + + + - +
@@ -16227,38 +16243,46 @@ exports[`renders ./components/form/demo/validate-static.md extend context correc >
- -
- - + - - + + + + + + - + @@ -16283,38 +16307,46 @@ exports[`renders ./components/form/demo/validate-static.md extend context correc >
- -
- - + - - + + + + + + - +
- - - - - @@ -18402,29 +18411,6 @@ exports[`renders ./components/form/demo/validate-static.md extend context correc - - - - - @@ -18477,7 +18463,9 @@ exports[`renders ./components/form/demo/validate-static.md extend context correc
+ > + I'm Select +
- - - - - @@ -18680,7 +18645,9 @@ exports[`renders ./components/form/demo/validate-static.md extend context correc
+ > + I'm Cascader +
- - - - -
+
+
+ +
+
+
+
+
+
+ + + + + I'm TreeSelect + +
+
+
+
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+ +
+
+
- - - - - @@ -20185,7 +20276,7 @@ exports[`renders ./components/form/demo/validate-static.md extend context correc class="ant-form-item-control-input-content" > @@ -20216,32 +20307,32 @@ exports[`renders ./components/form/demo/validate-static.md extend context correc /> + + + + +
- - - - - @@ -20268,7 +20359,7 @@ exports[`renders ./components/form/demo/validate-static.md extend context correc class="ant-form-item-control-input-content" > + + + + +
- - - - - @@ -20355,7 +20446,7 @@ exports[`renders ./components/form/demo/validate-static.md extend context correc class="ant-form-item-control-input-content" > + + + + +
- - - - - @@ -20473,6 +20564,97 @@ exports[`renders ./components/form/demo/validate-static.md extend context correc +
+
+ +
+
+
+
+
+ +