import React, { Children, cloneElement, FunctionComponent, isValidElement, ReactElement, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, } from 'react'; import { isArrayLike } from 'mobx'; import classNames from 'classnames'; import { observer } from 'mobx-react-lite'; import ConfigContext from 'choerodon-ui/lib/config-provider/ConfigContext'; import FormContext from './FormContext'; import { defaultLabelWidth, FIELD_SUFFIX, getProperty, normalizeLabelWidth, getPropertyDSFirst, getRequiredMarkAlign } from './utils'; import { LabelLayout } from './enum'; import { FormFieldProps } from '../field/FormField'; import Row from '../row'; import Col from '../col'; import { Tooltip as LabelTooltip } from '../core/enum'; import { hide, show } from '../tooltip/singleton'; import { TooltipProps } from '../tooltip/Tooltip'; import isOverflow from '../overflow-tip/util'; import { ShowHelp } from '../field/enum'; import Icon from '../icon'; export interface ItemProps extends FormFieldProps { children: ReactElement<FormFieldProps>; } export interface LabelProps { className?: string; children?: ReactNode; tooltip?: LabelTooltip | [LabelTooltip, TooltipProps]; width?: number; help?: ReactNode; } export interface LabelHelpProps { help?: ReactNode; } export interface IItem extends FunctionComponent<ItemProps> { __PRO_FORM_ITEM?: boolean; } const Label: FunctionComponent<LabelProps> = (props) => { const { children, className, tooltip, width, help } = props; const { getTooltipTheme, getTooltipPlacement } = useContext(ConfigContext); const tooltipRef = useRef<boolean>(false); const style = useMemo(() => width ? ({ width }) : undefined, [width]); const handleMouseEnter = useCallback((e) => { const { currentTarget } = e; if (tooltip === LabelTooltip.always || (tooltip === LabelTooltip.overflow && isOverflow(currentTarget))) { show(currentTarget, { title: children, theme: getTooltipTheme('label'), placement: getTooltipPlacement('label'), }); tooltipRef.current = true; } else if (isArrayLike(tooltip)) { const tooltipType = tooltip[0]; const labelTooltipProps = tooltip[1] || {}; const duration: number = (labelTooltipProps.mouseEnterDelay || 0.1) * 1000; if (tooltipType === LabelTooltip.always || (tooltipType === LabelTooltip.overflow && isOverflow(currentTarget))) { show(currentTarget, { theme: getTooltipTheme('label'), placement: getTooltipPlacement('label'), title: labelTooltipProps.title ? labelTooltipProps.title : children, ...labelTooltipProps, }, duration); tooltipRef.current = true; } } }, [children, tooltip, tooltipRef]); const handleMouseLeave = useCallback(() => { if (tooltipRef.current) { hide(); tooltipRef.current = false; } }, [tooltipRef]); useEffect(() => () => { if (tooltipRef.current) { hide(); tooltipRef.current = false; } }, [tooltipRef]); return ( <label className={className} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} style={style} > {children} {help} </label> ); }; Label.displayName = 'Label'; const LabelHelp: FunctionComponent<LabelHelpProps> = (props) => { const { help } = props; const { getTooltipTheme, getTooltipPlacement } = useContext(ConfigContext); const tooltipRef = useRef<boolean>(false); useEffect(() => { return () => { if (tooltipRef.current) { hide(); tooltipRef.current = false; } }; }, []); const handleHelpMouseEnter = useCallback((e, help) => { const { target } = e; show(target as HTMLElement, { title: help, theme: getTooltipTheme('help'), placement: getTooltipPlacement('help'), }); tooltipRef.current = true; }, []); const handleHelpMouseLeave = useCallback(() => { hide(); tooltipRef.current = false; }, []); if (help) { return ( <Icon type="help" onMouseEnter={(e) => handleHelpMouseEnter(e, help)} onMouseLeave={handleHelpMouseLeave} /> ); } return null; } LabelHelp.displayName = 'LabelHelp'; const Item: IItem = observer((props: ItemProps): ReactElement<any> | null => { const { getConfig, dataSet, record, labelLayout = getConfig('labelLayout'), labelAlign, labelWidth: contextLabelWidth = defaultLabelWidth, labelTooltip, useColon, requiredMarkAlign, getProPrefixCls, showHelp } = useContext(FormContext); const { children, useColon: fieldUseColon = useColon, requiredMarkAlign: fieldRequiredMarkAlign = requiredMarkAlign, ...rest } = props; const child = Children.only<ReactElement<FormFieldProps>>(children); if (isValidElement<FormFieldProps>(child)) { const prefixCls = getProPrefixCls(FIELD_SUFFIX); const { props: childrenProps } = child; const { className, fieldClassName, ...otherProps } = childrenProps; const fieldProps: FormFieldProps = { ...rest, ...otherProps }; const fieldElementProps: any = { className: classNames(props.className, className, prefixCls), ...fieldProps, }; const intlFieldOutput = (child.type as any).displayName === 'IntlField' && (fieldProps as any).displayOutput; if ([LabelLayout.none, LabelLayout.float, LabelLayout.placeholder].includes(labelLayout)) { return ( <div className={`${prefixCls}-wrapper`}> {cloneElement<FormFieldProps>(child, fieldElementProps)} </div> ); } const label = getProperty(fieldProps, 'label', dataSet, record); const required = getPropertyDSFirst(fieldProps, 'required', dataSet, record); const readOnly = getProperty(fieldProps, 'readOnly', dataSet, record); const help = getProperty(fieldProps, 'help', dataSet, record); const isLabelShowHelp = (fieldElementProps.showHelp || showHelp || getConfig('showHelp')) === ShowHelp.label; const isOutput = labelLayout === LabelLayout.horizontal && ((child.type as any).displayName === 'Output' || intlFieldOutput); const labelClassName = classNames(`${prefixCls}-label`, `${prefixCls}-label-grid`, `${prefixCls}-label-${labelAlign}`, fieldClassName, { [`${prefixCls}-required`]: required && !((child.type as any).displayName === 'Output' || intlFieldOutput), [`${prefixCls}-readonly`]: readOnly, [`${prefixCls}-label-vertical`]: labelLayout === LabelLayout.vertical, [`${prefixCls}-label-output`]: isOutput, [`${prefixCls}-label-useColon`]: label && fieldUseColon, [`${prefixCls}-label-required-mark-${getRequiredMarkAlign(fieldRequiredMarkAlign)}`]: labelLayout === LabelLayout.horizontal && required && !((child.type as any).displayName === 'Output' || intlFieldOutput) && getRequiredMarkAlign(fieldRequiredMarkAlign), [`${prefixCls}-label-help`]: isLabelShowHelp, }); const wrapperClassName = classNames(`${prefixCls}-wrapper`, { [`${prefixCls}-output`]: isOutput, }); const tooltip = props.labelTooltip || labelTooltip; const helpWrap = isLabelShowHelp ? <LabelHelp help={help} /> : undefined; if (labelLayout === LabelLayout.vertical) { return ( <> <Label className={labelClassName} tooltip={tooltip} help={helpWrap}>{label}</Label> <div className={wrapperClassName}>{cloneElement(child, fieldElementProps)}</div> </> ); } const fieldLabelWidth = getProperty(fieldProps, 'labelWidth', dataSet, record); const columnLabelWidth = normalizeLabelWidth(contextLabelWidth, 1)[0]; const labelWidth = columnLabelWidth === 'auto' ? undefined : Math.max(columnLabelWidth, isNaN(fieldLabelWidth) ? 0 : fieldLabelWidth); return ( <Row className={`${prefixCls}-row`}> <Col className={`${prefixCls}-col`}> <Label className={labelClassName} width={labelWidth} tooltip={tooltip} help={helpWrap}><span>{label}</span></Label> </Col> <Col className={`${prefixCls}-col ${prefixCls}-col-control`}> <div className={wrapperClassName}>{cloneElement(child, fieldElementProps)}</div> </Col> </Row> ); } if (child) { return child; } return null; }); Item.displayName = 'FormItem'; Item.__PRO_FORM_ITEM = true; export default Item;