"use client"; import type { KeyboardEvent, PressEvent } from "@react-types/shared"; import clsx from "clsx"; import { ElementType, forwardRef, useContext, useRef, useState } from "react"; import { useButton, useHover } from "react-aria"; import { DisabledContext } from "../../contexts/DisabledContext"; import { IconSizeContext } from "../../contexts/IconSizeContext"; import reset from "../../styles/reset/reset.module.css"; import { mergeRefs } from "../../utils/merge-refs"; import { Spinner } from "../Spinner"; import styles from "./button.module.css"; type IntrinsicProps = React.DetailedHTMLProps< React.ButtonHTMLAttributes, HTMLButtonElement >; export interface Props extends Omit< IntrinsicProps, "prefix" | "type" | "onClick" | "onKeyDown" | "onMouseDown" > { size?: "small" | "large"; prefix?: JSX.Element | string; suffix?: JSX.Element | string; align?: "start" | "grow" | "center"; type?: "secondary" | "success" | "error" | "warning" | "alert" | "violet"; shape?: "square" | "circle"; variant?: "shadow" | "ghost" | "unstyled"; loading?: boolean; onClick?: (e: PressEvent) => void; onKeyDown?: (e: KeyboardEvent) => void; onMouseDown?: (e: PressEvent) => void; svgOnly?: boolean; Component?: ElementType; typeName?: IntrinsicProps["type"]; } // let {Component: n="button", typeName: h="submit", className: g, href: b, as: w, target: E, rel: C, disabled: A, loading: j, width: S, type: k, size: T, prefix: O, normalStyle: D, hoverStyle: L, suffix: I, onClick: R, variant: P="invert", shape: N, align: M, children: V, onMouseDown: F, onMouseUp: B, svgOnly: H, passthroughOnClick: $, passthroughOnMouseEnter: U, touchEventWorkaround: z=!1, ...W} = const Button: React.ComponentType = forwardRef( ( { // custom props size, prefix, suffix, align, type, shape, variant, // native props className, children, disabled, loading, onClick, onKeyDown, onMouseDown, svgOnly, Component = "button", typeName = "submit", ...props }, externalRef, ) => { const ctxDisabled = useContext(DisabledContext); const isDisabled = disabled ?? ctxDisabled; const [isFocused, setFocused] = useState(false); const ref = useRef(); const { buttonProps, isPressed } = useButton( { ...props, elementType: Component, type: "submit", isDisabled: isDisabled || loading, onFocusChange: setFocused, onKeyDown: (e) => { // prevent holding down from rapid firing // https://stackoverflow.com/a/38241109 if (e.repeat) return null; onKeyDown?.(e); return e; }, onPress: (e) => { onClick?.(e); return e; }, onPressStart(e) { // prevent focus ring on mouse click. // and prevent FocusScopes from restoring focus to this button if (e.pointerType === "mouse") { // https://stackoverflow.com/a/3995570 // @ts-expect-error - blur may or may not be defined document.activeElement?.blur?.(); onMouseDown?.(e); } return e; }, }, ref, ); const iconSizeContextValue = { size: size === "large" ? 24 : size === "small" ? 16 : 20, }; const { hoverProps, isHovered } = useHover({ isDisabled: isDisabled || loading, }); return ( {prefix && ( {loading ? ( ) : ( prefix )} )} {children} {suffix && {suffix}} ); }, ); Button.displayName = "Button"; export default Button;