import { secureExternalLink } from "@twilio-paste/anchor"; import { animated, useSpring } from "@twilio-paste/animation-library"; import { Box } from "@twilio-paste/box"; import { ArrowForwardIcon } from "@twilio-paste/icons/esm/ArrowForwardIcon"; import { LinkExternalIcon } from "@twilio-paste/icons/esm/LinkExternalIcon"; import { Spinner } from "@twilio-paste/spinner"; import * as React from "react"; import { DestructiveButton } from "./DestructiveButton"; import { DestructiveIconButton } from "./DestructiveIconButton"; import { DestructiveLinkButton } from "./DestructiveLinkButton"; import { DestructiveSecondaryButton } from "./DestructiveSecondaryButton"; import { InverseButton } from "./InverseButton"; import { InverseLinkButton } from "./InverseLinkButton"; import { LinkButton } from "./LinkButton"; import { PrimaryButton } from "./PrimaryButton"; import { PrimaryIconButton } from "./PrimaryIconButton"; import { ResetButton } from "./ResetButton"; import { SecondaryButton } from "./SecondaryButton"; import { SecondaryIconButton } from "./SecondaryIconButton"; import type { ButtonContentsProps, ButtonProps, ButtonSizes, ButtonStates, ButtonVariants, DirectButtonProps, } from "./types"; const AnimatedBox = animated(Box); /** * If size isn't passed, come up with a smart default: * - 'reset' for variant 'link' * - 'icon' if there's 1 child that's an icon * - 'default' otherwise */ const getButtonSize = (variant: ButtonVariants, children: React.ReactNode, size?: ButtonSizes): ButtonSizes => { let smartSize: ButtonSizes = "default"; if (size != null) { smartSize = size; } else if (variant === "link" || variant === "destructive_link" || variant === "reset") { smartSize = "reset"; } else if (React.Children.count(children) === 1) { React.Children.forEach(children, (child) => { if ( React.isValidElement(child) && // @ts-expect-error we know displayName will exist in React typeof child.type.displayName === "string" && // @ts-expect-error we know displayName will exist in React child.type.displayName.includes("Icon") ) { smartSize = "icon"; } }); } return smartSize; }; /** * Determine the button state based on if it is disabled or loading. * * @param disabled - If the button is disabled. * @param loading - If the button is loading. * @returns The button state. */ const getButtonState = (disabled?: boolean, loading?: boolean): ButtonStates => { if (disabled) { return "disabled"; } if (loading) { return "loading"; } return "default"; }; /** * Validation ensuring the button is not being used in an inaccessible way. * * @throws Error if the button is being used in an inaccessible way. */ const handlePropValidation = ({ as, href, tabIndex, variant, size, fullWidth, children, disabled, loading, pressed, }: ButtonProps): void => { const hasHref = href != null && href !== ""; const hasTabIndex = tabIndex != null; // Link validation if (as !== "a" && hasHref) { throw new Error(`[Paste: Button] You cannot pass href into a button without the 'a' tag. Use 'as="a"'.`); } if (as === "a") { if (!hasHref) { throw new Error("[Paste: Button] Missing href prop for link button."); } if (variant === "link" || variant === "inverse_link") { throw new Error("[Paste: Button] Using Button component as an Anchor. Use the Paste Anchor component instead."); } if (variant !== "primary" && variant !== "secondary" && variant !== "reset" && variant !== "inverse") { throw new Error(`[Paste: Button]