///
/**
* 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