@nex-ui/react
Version:
🎉 A beautiful, modern, and reliable React component library.
173 lines (170 loc) • 5.8 kB
JavaScript
"use client";
import { jsx, jsxs } from 'react/jsx-runtime';
import { LoadingOutlined } from '@nex-ui/icons';
import { useFocusVisible, useEvent } from '@nex-ui/hooks';
import { nex } from '@nex-ui/styled';
import { useRef } from 'react';
import { mergeRefs } from '@nex-ui/utils';
import { useNexUI } from '../provider/Context.mjs';
import { useDefaultProps } from '../utils/useDefaultProps.mjs';
import { useStyles } from '../utils/useStyles.mjs';
import { useSlotProps } from '../utils/useSlotProps.mjs';
import { Ripple } from '../utils/ripple/Ripple.mjs';
import { composeClasses } from '../utils/composeClasses.mjs';
import { buttonRecipe } from '../../theme/recipes/button.mjs';
import { getUtilityClass } from '../utils/getUtilityClass.mjs';
const useSlotClasses = (ownerState)=>{
const { prefix } = useNexUI();
const btnRoot = `${prefix}-btn`;
const { color, variant, radius, size, iconOnly, loading, disabled, fullWidth, classes, disableRipple } = ownerState;
const slots = {
root: [
'root',
`variant-${variant}`,
`radius-${radius}`,
`size-${size}`,
`color-${color}`,
iconOnly && `icon-only`,
loading && `loading`,
disabled && `disabled`,
fullWidth && `full-width`,
disableRipple && 'disable-ripple'
],
startIcon: [
`icon`,
`start-icon`,
`icon-size-${size}`,
loading && `icon-loading`
],
endIcon: [
`icon`,
`end-icon`,
`icon-size-${size}`
]
};
const composedClasses = composeClasses(slots, getUtilityClass(btnRoot), classes);
return composedClasses;
};
const useSlotAriaProps = (ownerState)=>{
const { as, disabled: disabledProps, role, loading, tabIndex } = ownerState;
const disabled = disabledProps || loading;
let root = {
tabIndex: disabled ? -1 : tabIndex
};
if (as === 'button') {
root = {
...root,
disabled
};
} else {
root = {
...root,
role: role ?? 'button',
'aria-disabled': disabled || undefined
};
}
return {
root
};
};
const Button = (inProps)=>{
const { primaryThemeColor } = useNexUI();
const props = useDefaultProps({
name: 'Button',
props: inProps
});
const { as, ref, children, slotProps, spinner, href, type = 'button', tabIndex = 0, color = primaryThemeColor, spinnerPlacement = 'start', variant = 'solid', size = 'md', radius = size, iconOnly = false, loading = false, disabled: disabledProp = false, fullWidth = false, disableRipple = false, startIcon: startIconProp, endIcon: endIconProp, ...remainingProps } = props;
const btnRef = useRef(null);
const mergedRefs = mergeRefs(btnRef, ref);
const disabled = loading || disabledProp;
const rootElement = as !== undefined ? as : typeof href === 'string' && href ? 'a' : 'button';
const ownerState = {
...props,
type,
tabIndex,
variant,
size,
radius,
iconOnly,
loading,
fullWidth,
color,
disableRipple,
disabled: disabledProp,
as: rootElement
};
const [focusVisible] = useFocusVisible({
ref: btnRef
});
const classes = useSlotClasses(ownerState);
const styles = useStyles({
ownerState: {
...ownerState,
disabled
},
name: 'Button',
recipe: buttonRecipe
});
const handleKeyDown = useEvent((event)=>{
// Limit the repeated triggering of the click event when the Enter key is pressed.
if (focusVisible && rootElement === 'button' && event.code === 'Enter') {
event.preventDefault();
}
});
const handleKeyUp = useEvent((event)=>{
// Keyboard accessibility for non interactive elements
if (focusVisible && as !== 'button' && (event.code === 'Space' || event.code === 'Enter')) {
event.currentTarget.click();
}
});
const slotAriaProps = useSlotAriaProps(ownerState);
const rootProps = useSlotProps({
ownerState,
externalForwardedProps: remainingProps,
classNames: classes.root,
sx: styles.root,
additionalProps: {
href,
type,
onKeyUp: handleKeyUp,
onKeyDown: handleKeyDown,
ref: mergedRefs,
as: rootElement,
...slotAriaProps.root
}
});
const startIconProps = useSlotProps({
ownerState,
externalSlotProps: slotProps?.startIcon,
classNames: classes.startIcon,
sx: styles.startIcon
});
const endIconProps = useSlotProps({
ownerState,
externalSlotProps: slotProps?.endIcon,
classNames: classes.endIcon,
sx: styles.endIcon
});
const loadingIcon = spinner ?? /*#__PURE__*/ jsx(LoadingOutlined, {});
const startIcon = (spinnerPlacement === 'start' && loading || startIconProp) && /*#__PURE__*/ jsx(nex.span, {
...startIconProps,
children: loading ? loadingIcon : startIconProp
});
const endIcon = (spinnerPlacement === 'end' && loading || endIconProp) && /*#__PURE__*/ jsx(nex.span, {
...endIconProps,
children: loading ? loadingIcon : endIconProp
});
return /*#__PURE__*/ jsx(Ripple, {
disabled: disableRipple ?? disabled,
children: /*#__PURE__*/ jsxs(nex.button, {
...rootProps,
children: [
startIcon,
children,
endIcon
]
})
});
};
Button.displayName = 'Button';
export { Button };