/// /** * Button Component - Lynx 版 MUI Button * 100% 一比一复刻 MUI Button * * 对应 MUI: packages/mui-material/src/Button/Button.js */ import './Button.css' import buttonClasses, { getButtonUtilityClass } from './buttonClasses' export { buttonClasses, getButtonUtilityClass } // ============================================= // 类型定义 - 对应 MUI Button.d.ts // ============================================= export type ButtonVariant = 'text' | 'outlined' | 'contained' export type ButtonColor = 'inherit' | 'primary' | 'secondary' | 'success' | 'error' | 'info' | 'warning' export type ButtonSize = 'small' | 'medium' | 'large' export type ButtonLoadingPosition = 'start' | 'end' | 'center' export interface ButtonProps { /** 子元素 */ children?: any /** 自定义类名 */ className?: string /** 样式类覆盖 */ classes?: Partial /** 颜色 */ color?: ButtonColor /** 是否禁用 */ disabled?: boolean /** 是否禁用阴影 */ disableElevation?: boolean /** 是否禁用焦点涟漪 */ disableFocusRipple?: boolean /** 是否禁用涟漪 */ disableRipple?: boolean /** 结束图标 */ endIcon?: any /** 焦点可见时的类名 */ focusVisibleClassName?: string /** 是否占满宽度 */ fullWidth?: boolean /** 链接地址 */ href?: string /** ID */ id?: string /** 是否加载中 */ loading?: boolean | null /** 加载指示器 */ loadingIndicator?: any /** 加载指示器位置 */ loadingPosition?: ButtonLoadingPosition /** 尺寸 */ size?: ButtonSize /** 开始图标 */ startIcon?: any /** 内联样式 */ style?: Record /** sx 属性 */ sx?: Record /** 按钮类型 */ type?: 'button' | 'submit' | 'reset' /** 变体 */ variant?: ButtonVariant /** 点击事件 */ bindtap?: (event?: any) => void onClick?: (event?: any) => void } // ============================================= // 辅助函数 // ============================================= function capitalize(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1) } function composeClasses( slots: Record, getUtilityClass: (slot: string) => string, classes?: Record ): Record { const output: Record = {} Object.keys(slots).forEach((slot) => { output[slot] = slots[slot] .filter(Boolean) .map((key) => { if (classes && classes[key as string]) { return `${getUtilityClass(key as string)} ${classes[key as string]}` } return getUtilityClass(key as string) }) .join(' ') }) return output } // ============================================= // useUtilityClasses - 对应 MUI 实现 // ============================================= interface OwnerState extends ButtonProps { loading?: boolean | null } function useUtilityClasses(ownerState: OwnerState) { const { color = 'primary', disableElevation, fullWidth, size = 'medium', variant = 'text', loading, loadingPosition = 'center', classes } = ownerState const slots = { root: [ 'root', loading && 'loading', variant, `${variant}${capitalize(color)}`, `size${capitalize(size)}`, `${variant}Size${capitalize(size)}`, `color${capitalize(color)}`, disableElevation && 'disableElevation', fullWidth && 'fullWidth', loading && `loadingPosition${capitalize(loadingPosition)}`, ], startIcon: ['icon', 'startIcon', `iconSize${capitalize(size)}`], endIcon: ['icon', 'endIcon', `iconSize${capitalize(size)}`], loadingIndicator: ['loadingIndicator'], loadingWrapper: ['loadingWrapper'], } return composeClasses(slots, getButtonUtilityClass, classes) } // ============================================= // Button 组件 - 完整实现 // ============================================= export function Button(props: ButtonProps) { const { children, className, classes: classesProp, color = 'primary', disabled = false, disableElevation = false, disableFocusRipple = false, disableRipple = false, endIcon: endIconProp, focusVisibleClassName, fullWidth = false, id, loading = null, loadingIndicator: loadingIndicatorProp, loadingPosition = 'center', size = 'medium', startIcon: startIconProp, style, sx, type = 'button', variant = 'text', bindtap, onClick, ...other } = props // 加载指示器 const loadingIndicator = loadingIndicatorProp ?? ( ) const ownerState: OwnerState = { ...props, color, disabled, disableElevation, disableFocusRipple, fullWidth, loading, loadingIndicator, loadingPosition, size, type, variant, } const classes = useUtilityClasses(ownerState) // 开始图标 const startIcon = (startIconProp || (loading && loadingPosition === 'start')) && ( {startIconProp || } ) // 结束图标 const endIcon = (endIconProp || (loading && loadingPosition === 'end')) && ( {endIconProp || } ) // 加载器 const loader = typeof loading === 'boolean' ? ( {loading && ( {loadingIndicator} )} ) : null // 构建最终类名 const rootClasses = [ classes.root, className, disabled && buttonClasses.disabled, ].filter(Boolean).join(' ') // 处理点击 const handleTap = (event?: any) => { if (disabled || loading) return if (bindtap) bindtap(event) if (onClick) onClick(event) } return ( {startIcon} {loadingPosition !== 'end' && loader} {loading && loadingPosition === 'center' ? ( {children} ) : ( {children} )} {loadingPosition === 'end' && loader} {endIcon} ) } export default Button