mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
177 lines
4.5 KiB
TypeScript
177 lines
4.5 KiB
TypeScript
import type {ButtonVariantProps} from "@nextui-org/theme";
|
|
import type {AriaButtonProps} from "@react-types/button";
|
|
import type {PressEvent} from "@react-types/shared";
|
|
import type {ReactRef} from "@nextui-org/shared-utils";
|
|
import type {HTMLNextUIProps} from "@nextui-org/system";
|
|
import type {ReactNode} from "react";
|
|
|
|
import {MouseEventHandler, useCallback} from "react";
|
|
import {useButton as useAriaButton} from "@react-aria/button";
|
|
import {useFocusRing} from "@react-aria/focus";
|
|
import {mergeProps} from "@react-aria/utils";
|
|
import {useDrip} from "@nextui-org/drip";
|
|
import {useDOMRef} from "@nextui-org/dom-utils";
|
|
import {warn, clsx} from "@nextui-org/shared-utils";
|
|
import {button} from "@nextui-org/theme";
|
|
import {isValidElement, cloneElement, useMemo} from "react";
|
|
|
|
import {useButtonGroupContext} from "./button-group-context";
|
|
|
|
export interface UseButtonProps
|
|
extends HTMLNextUIProps<"button", Omit<AriaButtonProps, keyof ButtonVariantProps>>,
|
|
Omit<ButtonVariantProps, "isFocusVisible"> {
|
|
/**
|
|
* the button ref.
|
|
*/
|
|
ref?: ReactRef<HTMLButtonElement | null>;
|
|
/**
|
|
* Whether the button should display a ripple effect on press.
|
|
* @default false
|
|
*/
|
|
disableRipple?: boolean;
|
|
|
|
/**
|
|
* The button left content.
|
|
*/
|
|
leftIcon?: ReactNode;
|
|
/**
|
|
* The button right content.
|
|
*/
|
|
rightIcon?: ReactNode;
|
|
/**
|
|
* The native button click event handler.
|
|
* @deprecated - use `onPress` instead.
|
|
*/
|
|
onClick?: MouseEventHandler<HTMLButtonElement>;
|
|
}
|
|
|
|
export function useButton(props: UseButtonProps) {
|
|
const groupContext = useButtonGroupContext();
|
|
|
|
const {
|
|
ref,
|
|
as,
|
|
children,
|
|
leftIcon: leftIconProp,
|
|
rightIcon: rightIconProp,
|
|
autoFocus,
|
|
className,
|
|
fullWidth = groupContext?.fullWidth ?? false,
|
|
size = groupContext?.size ?? "md",
|
|
color = groupContext?.color ?? "neutral",
|
|
variant = groupContext?.variant ?? "solid",
|
|
disableAnimation = groupContext?.disableAnimation ?? false,
|
|
radius = groupContext?.radius ?? "lg",
|
|
disableRipple = groupContext?.disableRipple ?? false,
|
|
isDisabled = groupContext?.isDisabled ?? false,
|
|
onClick: deprecatedOnClick,
|
|
onPress,
|
|
onPressStart,
|
|
onPressEnd,
|
|
onPressChange,
|
|
onPressUp,
|
|
...otherProps
|
|
} = props;
|
|
|
|
const Component = as || "button";
|
|
|
|
const domRef = useDOMRef(ref);
|
|
|
|
const {isFocusVisible, focusProps} = useFocusRing({
|
|
autoFocus,
|
|
});
|
|
|
|
const styles = useMemo(
|
|
() =>
|
|
button({
|
|
size,
|
|
color,
|
|
variant,
|
|
radius,
|
|
fullWidth,
|
|
isDisabled,
|
|
isFocusVisible,
|
|
disableAnimation,
|
|
className,
|
|
}),
|
|
[
|
|
size,
|
|
color,
|
|
variant,
|
|
radius,
|
|
fullWidth,
|
|
isDisabled,
|
|
isFocusVisible,
|
|
disableAnimation,
|
|
className,
|
|
],
|
|
);
|
|
|
|
const {onClick: onDripClickHandler, ...dripBindings} = useDrip(false, domRef);
|
|
|
|
const handleDrip = (e: React.MouseEvent<HTMLButtonElement> | PressEvent | Event) => {
|
|
if (disableRipple || isDisabled || disableAnimation) return;
|
|
domRef.current && onDripClickHandler(e);
|
|
};
|
|
|
|
const handlePress = (e: PressEvent) => {
|
|
if (e.pointerType === "keyboard" || e.pointerType === "virtual") {
|
|
handleDrip(e);
|
|
} else if (typeof window !== "undefined" && window.event) {
|
|
handleDrip(window.event);
|
|
}
|
|
if (deprecatedOnClick) {
|
|
deprecatedOnClick(e as any);
|
|
warn("onClick is deprecated, please use onPress", "Button");
|
|
}
|
|
onPress?.(e);
|
|
};
|
|
|
|
const {buttonProps: buttonAriaProps} = useAriaButton(
|
|
{
|
|
...otherProps,
|
|
elementType: as,
|
|
onPress: handlePress,
|
|
onPressStart,
|
|
onPressEnd,
|
|
onPressChange,
|
|
onPressUp,
|
|
} as AriaButtonProps,
|
|
domRef,
|
|
);
|
|
|
|
const getButtonProps = useCallback(
|
|
() => mergeProps(buttonAriaProps, focusProps, otherProps),
|
|
[buttonAriaProps, focusProps, otherProps],
|
|
);
|
|
|
|
const getIconClone = (icon: ReactNode) =>
|
|
isValidElement(icon)
|
|
? cloneElement(icon, {
|
|
"aria-hidden": true,
|
|
focusable: false,
|
|
tabIndex: -1,
|
|
width: "70%",
|
|
height: "70%",
|
|
className: clsx("fill-current max-w-[24px]", icon.props.className),
|
|
})
|
|
: null;
|
|
|
|
const leftIcon = getIconClone(leftIconProp);
|
|
const rightIcon = getIconClone(rightIconProp);
|
|
|
|
return {
|
|
Component,
|
|
children,
|
|
domRef,
|
|
styles,
|
|
leftIcon,
|
|
rightIcon,
|
|
dripBindings,
|
|
disableRipple,
|
|
getButtonProps,
|
|
};
|
|
}
|
|
|
|
export type UseButtonReturn = ReturnType<typeof useButton>;
|