import React, { PureComponent } from 'react'; import classNames from 'classnames'; import PropTypes from 'prop-types'; import { cssClasses } from '@douyinfe/semi-foundation/form/constants'; import { IconAlertTriangle, IconAlertCircle } from '@douyinfe/semi-icons'; import type { BasicFieldError } from '@douyinfe/semi-foundation/form/interface'; const prefix = cssClasses.PREFIX; export type ReactFieldError = BasicFieldError | React.ReactNode; export interface ErrorMessageProps { error?: ReactFieldError; className?: string; style?: React.CSSProperties; showValidateIcon?: boolean; validateStatus?: string; helpText?: React.ReactNode; isInInputGroup?: boolean; errorMessageId?: string; helpTextId?: string } export default class ErrorMessage extends PureComponent { static propTypes = { error: PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.array, PropTypes.node]), className: PropTypes.string, style: PropTypes.object, validateStatus: PropTypes.string, showValidateIcon: PropTypes.bool, helpText: PropTypes.node, isInInputGroup: PropTypes.bool, // internal props errorMessageId: PropTypes.string, helpTextId: PropTypes.string, }; generatorText(error: ReactFieldError) { const { helpTextId, errorMessageId } = this.props; const propsError = this.props.error; let id = errorMessageId; if (!propsError) { id = helpTextId; } if (typeof error === 'string') { return {error}; } else if (Array.isArray(error)) { const err = error.filter(e => e); return err.length ? {err.join(', ')} : null; } else if (React.isValidElement(error)) { return error; } return null; } render() { const { error, className, style, validateStatus, helpText, showValidateIcon, isInInputGroup } = this.props; const cls = classNames( { [prefix + '-field-error-message']: Boolean(error), [prefix + '-field-help-text']: Boolean(helpText), }, className ); if (!error && !helpText) { return null; } // The async-validator `message: ''` convention means "validation failed, but suppress // the message text". Without this guard the field would silently fall back to rendering // `helpText`, which would falsely look like the success/help state. Skip rendering // entirely so that a hidden-text error stays hidden — consistent with the legacy // behavior where an unresolved promise meant nothing was rendered at all. if (error === '' && validateStatus === 'error') { return null; } const iconMap = { warning: , error: , }; const text = error ? this.generatorText(error) : this.generatorText(helpText); // Keep backward compatible behavior: // historically, when `error` is a truthy non-renderable type (e.g. number), // ErrorMessage still renders an empty wrapper with `.semi-form-field-error-message`. // Only skip rendering for array errors that have no displayable content (all falsy), // to avoid empty placeholders (e.g. InputGroup collecting [false, false]). if (Array.isArray(error) && !text && !helpText) { return null; } const iconCls = `${prefix }-field-validate-status-icon`; let icon = null; if (isInInputGroup) { icon = ; } else { if (iconMap[validateStatus]) { icon = React.cloneElement(iconMap[validateStatus], { className: iconCls }); } } return (
{showValidateIcon && text ? icon : null} {text}
); } }