import {forwardRef as reactForwardRef} from 'react'; import isPropValid from '@emotion/is-prop-valid'; import {css} from '@emotion/react'; import styled from '@emotion/styled'; import ExternalLink from 'sentry/components/links/externalLink'; import Link from 'sentry/components/links/link'; import Tooltip from 'sentry/components/tooltip'; import space from 'sentry/styles/space'; import mergeRefs from 'sentry/utils/mergeRefs'; import {Theme} from 'sentry/utils/theme'; /** * The button can actually also be an anchor or React router Link (which seems * to be poorly typed as `any`). So this is a bit of a workaround to receive * the proper html attributes. */ type ButtonElement = HTMLButtonElement & HTMLAnchorElement & any; interface BaseButtonProps extends Omit< React.ButtonHTMLAttributes, 'ref' | 'label' | 'size' | 'title' > { align?: 'center' | 'left' | 'right'; // This is only used with `` barId?: string; borderless?: boolean; busy?: boolean; disabled?: boolean; download?: HTMLAnchorElement['download']; external?: boolean; forwardRef?: React.Ref; href?: string; icon?: React.ReactNode; name?: string; onClick?: (e: React.MouseEvent) => void; priority?: 'default' | 'primary' | 'danger' | 'link' | 'success' | 'form'; rel?: HTMLAnchorElement['rel']; size?: 'zero' | 'xsmall' | 'small'; target?: HTMLAnchorElement['target']; title?: React.ComponentProps['title']; to?: string | object; tooltipProps?: Omit; translucentBorder?: boolean; } export interface ButtonPropsWithoutAriaLabel extends BaseButtonProps { children: React.ReactNode; } export interface ButtonPropsWithAriaLabel extends BaseButtonProps { 'aria-label': string; children?: never; } export type ButtonProps = ButtonPropsWithoutAriaLabel | ButtonPropsWithAriaLabel; type Url = ButtonProps['to'] | ButtonProps['href']; function BaseButton({ size, to, busy, href, title, icon, children, 'aria-label': ariaLabel, borderless, translucentBorder, align = 'center', priority, disabled = false, tooltipProps, onClick, ...buttonProps }: ButtonProps) { // Intercept onClick and propagate function handleClick(e: React.MouseEvent) { // Don't allow clicks when disabled or busy if (disabled || busy) { e.preventDefault(); e.stopPropagation(); return; } if (typeof onClick !== 'function') { return; } onClick(e); } function getUrl(prop: T): T | undefined { return disabled ? undefined : prop; } // Fallbacking aria-label to string children is not necessary as screen readers natively understand that scenario. // Leaving it here for a bunch of our tests that query by aria-label. const screenReaderLabel = ariaLabel || (typeof children === 'string' ? children : undefined); const hasChildren = Array.isArray(children) ? children.some(child => !!child) : !!children; // Buttons come in 4 flavors: , , , and