import { clsx } from 'clsx';
import { ElementType, forwardRef, MouseEvent } from 'react';
import { useIntl } from 'react-intl';
import {
Size,
ControlType,
Priority,
ControlTypeAccent,
ControlTypeNegative,
ControlTypePositive,
PriorityPrimary,
PrioritySecondary,
PriorityTertiary,
SizeExtraSmall,
SizeSmall,
SizeMedium,
SizeLarge,
} from '../common';
import ProcessIndicator from '../processIndicator';
import messages from '../i18n/commonMessages/Button.messages';
import { typeClassMap, priorityClassMap } from './classMap';
import { establishNewPriority, establishNewType, logDeprecationNotices } from './legacyUtils';
import { ButtonReferenceType } from './Button.types';
/** @deprecated */
type DeprecatedTypes = 'primary' | 'pay' | 'secondary' | 'danger' | 'link';
/** @deprecated */
type DeprecatedSizes = SizeExtraSmall;
/**
* @deprecated Use new button instead of these APIs.
* @example
* ```
* import { Button, ButtonProps } from '@transferwise/components';
*
*
* ```
*/
export type CommonProps = {
v2?: false;
/** Makes the button take up the full width of its container */
block?: boolean;
/**
* The `target` attribute for HTML anchor.
* If set to `_blank`, `rel="noopener noreferrer"` is automatically added to the rendered node.
*/
target?: string;
/**
* Toggles the disabled state
* @default false
*/
disabled?: boolean;
/**
* Toggles the loading state
* @default false
*/
loading?: boolean;
type?: ControlTypeAccent | ControlTypeNegative | ControlTypePositive | DeprecatedTypes | null;
priority?: PriorityPrimary | PrioritySecondary | PriorityTertiary | null;
size?: SizeSmall | SizeMedium | SizeLarge | DeprecatedSizes;
htmlType?: 'submit' | 'reset' | 'button';
};
/**
* @deprecated Use new button instead of APIs.
* @example
* ```
* import { Button, ButtonProps } from '@transferwise/components';
*
*
* ```
*/
export type ButtonProps = CommonProps &
Omit, 'type'> & {
as?: 'button';
};
type AnchorProps = CommonProps &
Omit, 'type'> & {
as?: 'a';
};
/**
* @deprecated Use new button instead of APIs.
* @example
* ```
* import { Button, ButtonProps } from '@transferwise/components';
*
*
* ```
*/
export type LegacyButtonProps = ButtonProps | AnchorProps;
/**
* @deprecated Use new Button component via `` and new props.
*/
const LegacyButton = forwardRef(
(
{
as: component = 'button',
block = false,
children,
className,
disabled,
loading = false,
priority = Priority.PRIMARY,
size = Size.MEDIUM,
type = ControlType.ACCENT,
onClick,
...rest
}: LegacyButtonProps,
ref,
) => {
const intl = useIntl();
logDeprecationNotices({ size, type });
const newType = establishNewType(type);
const newPriority = establishNewPriority(priority, type);
const classes = clsx(
`btn btn-${size}`,
`np-btn np-btn-${size}`,
{
'btn-loading': loading,
'btn-block np-btn-block': block,
disabled,
},
// @ts-expect-error fix when refactor `typeClassMap` to TypeScript
typeClassMap[newType],
// @ts-expect-error fix when refactor `typeClassMap` to TypeScript
priorityClassMap[newPriority],
className,
);
function processIndicatorSize() {
return ['sm', 'xs'].includes(size) ? 'xxs' : 'xs';
}
const Element = (component as ElementType) ?? 'button';
let props;
if (Element === 'button') {
const { htmlType = 'button', ...restProps } = rest as ButtonProps;
props = {
...restProps,
disabled,
'aria-disabled': loading,
type: htmlType,
};
} else {
props = {
...rest,
'aria-disabled': loading,
} as AnchorProps;
}
/**
* Ensures that the button cannot be activated in loading or disabled mode,
* when `aria-disabled` might be used over the `disabled` HTML attribute
*/
const handleClick =
(handler: LegacyButtonProps['onClick']) =>
(event: MouseEvent & MouseEvent) => {
if (disabled || loading) {
event.preventDefault();
} else if (typeof handler === 'function') {
handler(event);
}
};
return (
}
className={classes}
onClick={handleClick(onClick)}
{...props}
aria-live={loading ? 'polite' : 'off'}
aria-busy={loading}
aria-label={loading ? intl.formatMessage(messages.loadingAriaLabel) : rest['aria-label']}
>
{children}
{loading && (
)}
);
},
);
export default LegacyButton;