import * as React from "react"; import classNames from "classnames"; import filterInvalidDOMProps from "filter-invalid-dom-props"; import { AriaButtonProps, mergeProps, useButton, useHover } from "react-aria"; import { useSetRef } from "#gdq/utils/RefUtils"; import { animated, useSpring } from "@react-spring/web"; import styles from "./Button.module.css"; const COLOR_VARIANTS = { primary: styles.primary, default: styles.default, success: styles.success, warning: styles.warning, danger: styles.danger, info: styles.info, link: styles.link, }; const LOOK_VARIANTS = { filled: styles.filled, outline: styles.outline, }; const SIZE_VARIANTS = { sm: styles.small, md: styles.medium, lg: styles.large, }; export type ButtonVariantColor = keyof typeof COLOR_VARIANTS; export type ButtonVariantLook = keyof typeof LOOK_VARIANTS; export type ButtonVariantSize = keyof typeof SIZE_VARIANTS; export type ButtonVariant = | ButtonVariantColor | `${ButtonVariantColor}/${ButtonVariantLook}` | `${ButtonVariantColor}/${ButtonVariantLook}/${ButtonVariantSize}`; function getVariantPieces( variant: ButtonVariant, ): [ButtonVariantColor, ButtonVariantLook | undefined, ButtonVariantSize | undefined] { const [color, look, size] = variant.split("/"); return [ color as ButtonVariantColor, look as ButtonVariantLook | undefined, size as ButtonVariantSize | undefined, ]; } export interface ButtonIconProps { className: string; size: string; } export interface ButtonProps extends AriaButtonProps<"button"> { variant?: ButtonVariant; icon?: React.ComponentType; className?: string; } /** * Return the class name(s) used to style a button like the given variant. If * `variant` is undefined, no button styles will be applied, but `className` will * still be returned. */ export function getButtonClassNames( variant: ButtonVariant | undefined, { className }: { className?: string } = {}, ) { if (variant == null) return className; const [color, look = "filled", size = "md"] = getVariantPieces(variant); return classNames( styles.button, LOOK_VARIANTS[look], COLOR_VARIANTS[color], SIZE_VARIANTS[size], className, ); } export const Button = React.forwardRef(function Button( props: ButtonProps, ref: React.ForwardedRef, ) { const { variant = "default", icon: Icon, children, ...nativeProps } = props; const innerRef = React.useRef(null); const { buttonProps, isPressed } = useButton(nativeProps, innerRef); const { hoverProps, isHovered } = useHover(nativeProps); const setRef = useSetRef(ref, innerRef); const [{ transform }] = useSpring( () => ({ transform: `scale(${isPressed ? 0.98 : 1})`, config: { tension: 400, friction: 20 }, }), [isPressed, isHovered], ); return ( {children} {Icon != null ? : null} ); });