diff --git a/components/_util/statusUtils.tsx b/components/_util/statusUtils.tsx index 25153ba93f..c88e93a6dc 100644 --- a/components/_util/statusUtils.tsx +++ b/components/_util/statusUtils.tsx @@ -1,8 +1,3 @@ -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'; @@ -10,22 +5,6 @@ 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, diff --git a/components/cascader/index.tsx b/components/cascader/index.tsx index 03abae2c21..ac64f80ca6 100644 --- a/components/cascader/index.tsx +++ b/components/cascader/index.tsx @@ -149,7 +149,12 @@ const Cascader = React.forwardRef((props: CascaderProps, ref: React.Ref, ref: React.Ref( generateConfig: GenerateConfig, @@ -39,23 +38,6 @@ export default function generateRangePicker( } }; - renderFeedback = (prefixCls: string) => ( - - {({ hasFeedback, status: contextStatus }) => { - const { status: customStatus } = this.props; - const status = getMergedStatus(contextStatus, customStatus); - return hasFeedback && getFeedbackIcon(prefixCls, status); - }} - - ); - - renderSuffix = (prefixCls: string, mergedPicker?: PickerMode) => ( - <> - {mergedPicker === 'time' ? : } - {this.renderFeedback(prefixCls)} - - ); - renderPicker = (contextLocale: PickerLocale) => { const locale = { ...contextLocale, ...this.props.locale }; const { getPrefixCls, direction, getPopupContainer } = this.context; @@ -89,46 +71,55 @@ export default function generateRangePicker( return ( - {({ hasFeedback, status: contextStatus }) => ( - - separator={ - - - - } - ref={this.pickerRef} - dropdownAlign={transPlacement2DropdownAlign(direction, placement)} - placeholder={getRangePlaceholder(picker, locale, placeholder)} - suffixIcon={this.renderSuffix(prefixCls, picker)} - clearIcon={} - prevIcon={} - nextIcon={} - superPrevIcon={} - superNextIcon={} - allowClear - transitionName={`${rootPrefixCls}-slide-up`} - {...restProps} - {...additionalOverrideProps} - className={classNames( - { - [`${prefixCls}-${mergedSize}`]: mergedSize, - [`${prefixCls}-borderless`]: !bordered, - }, - getStatusClassNames( - prefixCls, - getMergedStatus(contextStatus, customStatus), - hasFeedback, - ), - className, - )} - locale={locale!.lang} - prefixCls={prefixCls} - getPopupContainer={customGetPopupContainer || getPopupContainer} - generateConfig={generateConfig} - components={Components} - direction={direction} - /> - )} + {({ hasFeedback, status: contextStatus, feedbackIcon }) => { + const suffixNode = ( + <> + {picker === 'time' ? : } + {hasFeedback && feedbackIcon} + + ); + + return ( + + separator={ + + + + } + ref={this.pickerRef} + dropdownAlign={transPlacement2DropdownAlign(direction, placement)} + placeholder={getRangePlaceholder(picker, locale, placeholder)} + suffixIcon={suffixNode} + clearIcon={} + prevIcon={} + nextIcon={} + superPrevIcon={} + superNextIcon={} + allowClear + transitionName={`${rootPrefixCls}-slide-up`} + {...restProps} + {...additionalOverrideProps} + className={classNames( + { + [`${prefixCls}-${mergedSize}`]: mergedSize, + [`${prefixCls}-borderless`]: !bordered, + }, + getStatusClassNames( + prefixCls, + getMergedStatus(contextStatus, customStatus), + hasFeedback, + ), + className, + )} + locale={locale!.lang} + prefixCls={prefixCls} + getPopupContainer={customGetPopupContainer || getPopupContainer} + generateConfig={generateConfig} + components={Components} + direction={direction} + /> + ); + }} ); }} diff --git a/components/date-picker/generatePicker/generateSinglePicker.tsx b/components/date-picker/generatePicker/generateSinglePicker.tsx index 2e182f900a..6fbae02e3d 100644 --- a/components/date-picker/generatePicker/generateSinglePicker.tsx +++ b/components/date-picker/generatePicker/generateSinglePicker.tsx @@ -22,12 +22,7 @@ import { } from '.'; import { PickerComponentClass } from './interface'; import { FormItemInputContext } from '../../form/context'; -import { - getFeedbackIcon, - getMergedStatus, - getStatusClassNames, - InputStatus, -} from '../../_util/statusUtils'; +import { getMergedStatus, getStatusClassNames, InputStatus } from '../../_util/statusUtils'; export default function generatePicker(generateConfig: GenerateConfig) { type DatePickerProps = PickerProps & { @@ -68,23 +63,6 @@ export default function generatePicker(generateConfig: GenerateConfig< } }; - renderFeedback = (prefixCls: string) => ( - - {({ hasFeedback, status: contextStatus }) => { - const { status: customStatus } = this.props; - const status = getMergedStatus(contextStatus, customStatus); - return hasFeedback && getFeedbackIcon(prefixCls, status); - }} - - ); - - renderSuffix = (prefixCls: string, mergedPicker?: PickerMode) => ( - <> - {mergedPicker === 'time' ? : } - {this.renderFeedback(prefixCls)} - - ); - renderPicker = (contextLocale: PickerLocale) => { const locale = { ...contextLocale, ...this.props.locale }; const { getPrefixCls, direction, getPopupContainer } = this.context; @@ -128,42 +106,51 @@ export default function generatePicker(generateConfig: GenerateConfig< return ( - {({ hasFeedback, status: contextStatus }) => ( - - ref={this.pickerRef} - placeholder={getPlaceholder(mergedPicker, locale, placeholder)} - suffixIcon={this.renderSuffix(prefixCls, mergedPicker)} - dropdownAlign={transPlacement2DropdownAlign(direction, placement)} - clearIcon={} - prevIcon={} - nextIcon={} - superPrevIcon={} - superNextIcon={} - allowClear - transitionName={`${rootPrefixCls}-slide-up`} - {...additionalProps} - {...restProps} - {...additionalOverrideProps} - locale={locale!.lang} - className={classNames( - { - [`${prefixCls}-${mergedSize}`]: mergedSize, - [`${prefixCls}-borderless`]: !bordered, - }, - getStatusClassNames( - prefixCls, - getMergedStatus(contextStatus, customStatus), - hasFeedback, - ), - className, - )} - prefixCls={prefixCls} - getPopupContainer={customizeGetPopupContainer || getPopupContainer} - generateConfig={generateConfig} - components={Components} - direction={direction} - /> - )} + {({ hasFeedback, status: contextStatus, feedbackIcon }) => { + const suffixNode = ( + <> + {mergedPicker === 'time' ? : } + {hasFeedback && feedbackIcon} + + ); + + return ( + + ref={this.pickerRef} + placeholder={getPlaceholder(mergedPicker, locale, placeholder)} + suffixIcon={suffixNode} + dropdownAlign={transPlacement2DropdownAlign(direction, placement)} + clearIcon={} + prevIcon={} + nextIcon={} + superPrevIcon={} + superNextIcon={} + allowClear + transitionName={`${rootPrefixCls}-slide-up`} + {...additionalProps} + {...restProps} + {...additionalOverrideProps} + locale={locale!.lang} + className={classNames( + { + [`${prefixCls}-${mergedSize}`]: mergedSize, + [`${prefixCls}-borderless`]: !bordered, + }, + getStatusClassNames( + prefixCls, + getMergedStatus(contextStatus, customStatus), + hasFeedback, + ), + className, + )} + prefixCls={prefixCls} + getPopupContainer={customizeGetPopupContainer || getPopupContainer} + generateConfig={generateConfig} + components={Components} + direction={direction} + /> + ); + }} ); }} diff --git a/components/date-picker/style/status.less b/components/date-picker/style/status.less index be3adba330..4087bf5cf4 100644 --- a/components/date-picker/style/status.less +++ b/components/date-picker/style/status.less @@ -21,10 +21,6 @@ .active(@text-color, @hoverBorderColor, @outlineColor); } } - - .@{picker-prefix-cls}-feedback-icon { - color: @text-color; - } } .@{picker-prefix-cls} { @@ -35,18 +31,4 @@ &-status-warning { .picker-status-color(@warning-color, @warning-color, @input-bg, @warning-color-hover, @warning-color-outline); } - - &-status-validating { - .@{picker-prefix-cls}-feedback-icon { - display: inline-block; - color: @primary-color; - } - } - - &-status-success { - .@{picker-prefix-cls}-feedback-icon { - color: @success-color; - animation-name: diffZoomIn1 !important; - } - } } diff --git a/components/form/FormItem.tsx b/components/form/FormItem.tsx index 2772ec5967..654c3af108 100644 --- a/components/form/FormItem.tsx +++ b/components/form/FormItem.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { useContext, useMemo } from 'react'; +import { ReactNode, 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'; @@ -7,6 +7,10 @@ import { Meta, NamePath } from 'rc-field-form/lib/interface'; import { supportRef } from 'rc-util/lib/ref'; import useState from 'rc-util/lib/hooks/useState'; import omit from 'rc-util/lib/omit'; +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 Row from '../grid/row'; import { ConfigContext } from '../config-provider'; import { tuple } from '../_util/type'; @@ -88,6 +92,13 @@ function genEmptyMeta(): Meta { }; } +const iconMap = { + success: CheckCircleFilled, + warning: ExclamationCircleFilled, + error: CloseCircleFilled, + validating: LoadingOutlined, +}; + function FormItem(props: FormItemProps): React.ReactElement { const { name, @@ -219,14 +230,29 @@ function FormItem(props: FormItemProps): React.ReactElemen mergedValidateStatus = 'success'; } - const formItemStatusContext = useMemo( - () => ({ + const formItemStatusContext = useMemo(() => { + let feedbackIcon: ReactNode; + if (hasFeedback) { + const IconNode = mergedValidateStatus && iconMap[mergedValidateStatus]; + feedbackIcon = IconNode ? ( + + + + ) : null; + } + + return { status: mergedValidateStatus, hasFeedback, + feedbackIcon, isFormItemInput: true, - }), - [mergedValidateStatus, hasFeedback], - ); + }; + }, [mergedValidateStatus, hasFeedback]); // ======================== Render ======================== function renderLayout( diff --git a/components/form/__tests__/__snapshots__/demo-extend.test.ts.snap b/components/form/__tests__/__snapshots__/demo-extend.test.ts.snap index 50d6db95ec..79f3367bef 100644 --- a/components/form/__tests__/__snapshots__/demo-extend.test.ts.snap +++ b/components/form/__tests__/__snapshots__/demo-extend.test.ts.snap @@ -5188,7 +5188,7 @@ exports[`renders ./components/form/demo/register.md extend context correctly 1`] class="ant-form-item-control-input-content" >