import cx from 'classnames'; import { isElement, isFragment } from 'react-is'; import { Children, cloneElement, useCallback, useContext, useRef } from 'react'; import Icon, { IconType } from '../icon'; import { DisabledContext } from '../disabled'; import { PopoverHoverTriggerContext } from '../popover'; import { renderCompatibleChildren } from './utils'; export interface IButtonDirectiveChildProps { className?: string; disabled?: boolean; children?: React.ReactNode; 'data-zv'?: string; onClick?: React.MouseEventHandler; } export type IButtonSize = 'medium' | 'large' | 'small'; export type IButtonType = | 'default' | 'primary' | 'secondary' | 'danger' | 'warning' | 'error' | 'success' | 'text' | 'icon'; export type IButtonHtmlType = 'button' | 'submit' | 'reset'; export interface IButtonDirectiveProps< ChildProps extends Omit > { size?: IButtonSize; type?: IButtonType; disabled?: boolean; loading?: boolean; outline?: boolean; bordered?: boolean; style?: React.CSSProperties; icon?: IconType; block?: boolean; children: React.ReactElement; onMouseEnter?: React.MouseEventHandler; onMouseLeave?: React.MouseEventHandler; } export function ButtonDirective( props: IButtonDirectiveProps ) { const disabledContext = useContext(DisabledContext); const popoverHoverTriggerContext = useContext(PopoverHoverTriggerContext); const { outline, type = 'default', size = 'medium', block, loading, disabled = disabledContext.value, bordered = true, icon, children, onMouseEnter, onMouseLeave, } = props; if (!isElement(children) || isFragment(children)) { throw new Error( 'Button Directive child must be a non fragment element, string | number | boolean | null is not accepted' ); } const disabledRef = useRef(disabled); disabledRef.current = disabled; const propsRef = useRef(props); propsRef.current = props; const childElement = children as React.ReactElement; const onClick = useCallback((e: React.MouseEvent) => { const { loading, children } = propsRef.current; const { onClick } = children.props; const disabled = disabledRef.current; if (loading || disabled) { e.preventDefault(); return; } onClick?.(e); }, []); const iconNode = icon ? : null; // icon text 或者 outline 为false 不需要outline const needOutline = type !== 'text' && type !== 'icon' && outline; const className = cx( { [`zent-btn-${type}${needOutline ? '-outline' : ''}`]: type !== 'default', [`zent-btn-${size}`]: size !== 'medium', 'zent-btn-block': block, 'zent-btn-loading': loading, 'zent-btn-disabled': disabled, 'zent-btn-border-transparent': !bordered, }, 'zent-btn', childElement.props.className ); const commonChildren = cloneElement( children, { className, disabled: !!(disabled || loading), onClick, 'data-zv': __ZENT_VERSION__, } as Partial, iconNode, // Wrap text in a `span`, or we won't be able to control icon margins ...(Children.map(childElement.props.children, child => typeof child === 'string' ? {child} : child ) || []) ); return renderCompatibleChildren(commonChildren, { fixMouseEventsOnDisabledChildren: popoverHoverTriggerContext.fixMouseEventsOnDisabledChildren, onMouseEnter, onMouseLeave, }); }