import { cx, type Colors } from '@fuel-ui/css';
import { mergeRefs } from '@react-aria/utils';
import type { ReactElement, ReactNode } from 'react';
import { cloneElement } from 'react';
import { mergeProps, useFocusRing } from 'react-aria';
import { useOnPress } from '~/hooks/useOnPress';
import { useStyles } from '~/hooks/useStore';
import {
_unstable_createComponent,
_unstable_createEl,
createPolymorphicComponent,
omit,
} from '~/utils';
import { Components } from '~/utils/components-list';
import { Icon } from '../Icon';
import { Spinner } from '../Spinner';
import type * as t from './defs';
import { styles } from './styles';
export function createIcon(
icon: string | ReactNode,
iconAriaLabel?: string,
iconSize?: number,
color?: Colors,
): ReactElement | null {
if (typeof icon === 'string') {
return (
);
}
return icon
? cloneElement(icon as ReactElement, {
label: iconAriaLabel,
size: iconSize,
...(color && { color }),
})
: null;
}
export function getIconSize(size: t.ButtonSizes, iconSize?: number) {
if (iconSize) return iconSize;
if (size === 'lg') return 20;
if (size === 'md') return 18;
return 16;
}
type GetChildrenParams = t.ButtonProps & {
iconLeft?: ReactElement | null;
iconRight?: ReactElement | null;
iconLeftClass?: string;
iconRightClass?: string;
};
function getChildren({
isLoading,
loadingText,
size = 'md',
children,
iconLeft,
iconRight,
iconLeftClass,
iconRightClass,
}: GetChildrenParams) {
if (isLoading) {
return (
<>
{loadingText || 'Loading...'}
>
);
}
return (
<>
{iconLeft && cloneElement(iconLeft, { className: iconLeftClass })}
{children}
{iconRight && cloneElement(iconRight, { className: iconRightClass })}
>
);
}
export const SPINNER_SIZE = {
xs: 12,
sm: 14,
md: 16,
lg: 20,
};
const _Button = _unstable_createComponent(
Components.Button,
({ as = 'button', size = 'md', children, ref, ...props }) => {
const {
isLoading,
loadingText,
isDisabled,
isLink,
leftIcon,
leftIconAriaLabel,
rightIcon,
rightIconAriaLabel,
} = props;
const disabled = isLoading || isDisabled;
const {
buttonProps,
isPressed,
ref: buttonRef,
} = useOnPress(props, {
isDisabled: disabled,
...(isLink && { elementType: 'a' }),
});
const customProps = {
as,
ref: mergeRefs(buttonRef, ref),
'aria-busy': isLoading,
...(!isLink && { 'aria-pressed': !disabled && isPressed }),
};
const { isFocusVisible, focusProps } = useFocusRing({
isTextInput: false,
within: true,
autoFocus: props.autoFocus,
});
const allProps = mergeProps(
omit(['onClick'], props),
{ size },
buttonProps,
customProps,
focusProps,
);
const classes = useStyles(styles, allProps);
const className = cx(classes.root.className, { focused: isFocusVisible });
const iconSize = getIconSize(size, props.iconSize);
const iconLeft = createIcon(leftIcon, leftIconAriaLabel, iconSize);
const iconRight = createIcon(rightIcon, rightIconAriaLabel, iconSize);
const finalProps = { ...allProps, className };
return _unstable_createEl(
as,
finalProps,
getChildren({
size,
isLoading,
loadingText,
children,
iconLeft,
iconRight,
}),
);
},
);
export const Button = createPolymorphicComponent(_Button);
Button.id = 'Button';