2024-10-31 15:14:55 +00:00
|
|
|
import React from "react";
|
2021-04-12 13:32:25 +00:00
|
|
|
import classnames from "classnames";
|
2022-08-29 15:21:37 +00:00
|
|
|
import Spinner from "components/Spinner";
|
2016-10-19 20:22:18 +00:00
|
|
|
|
2021-04-12 13:32:25 +00:00
|
|
|
const baseClass = "button";
|
2016-09-14 20:31:54 +00:00
|
|
|
|
2021-09-10 19:06:37 +00:00
|
|
|
export type ButtonVariant =
|
2025-04-16 13:56:09 +00:00
|
|
|
| "default"
|
2021-09-10 19:06:37 +00:00
|
|
|
| "success"
|
|
|
|
|
| "alert"
|
2025-04-16 13:56:09 +00:00
|
|
|
| "pill"
|
2024-07-09 13:18:00 +00:00
|
|
|
| "text-link" // Underlines on hover
|
2025-08-01 14:36:03 +00:00
|
|
|
| "text-link-dark" // underline on hover, dark text
|
2021-09-10 19:06:37 +00:00
|
|
|
| "text-icon"
|
2023-05-25 13:43:44 +00:00
|
|
|
| "icon" // Buttons without text
|
2021-09-10 19:06:37 +00:00
|
|
|
| "inverse"
|
|
|
|
|
| "inverse-alert"
|
2025-04-23 18:42:30 +00:00
|
|
|
| "unstyled" // Avoid as much as possible (used in registration breadcrumbs, 404/500, an old button dropdown)
|
2021-09-10 19:06:37 +00:00
|
|
|
| "unstyled-modal-query"
|
2023-05-05 17:36:13 +00:00
|
|
|
| "oversized";
|
2021-09-10 19:06:37 +00:00
|
|
|
|
2021-11-07 06:41:09 +00:00
|
|
|
export interface IButtonProps {
|
2021-03-03 16:51:39 +00:00
|
|
|
autofocus?: boolean;
|
2022-11-17 14:25:40 +00:00
|
|
|
children: React.ReactNode;
|
2021-03-03 16:51:39 +00:00
|
|
|
className?: string;
|
|
|
|
|
disabled?: boolean;
|
|
|
|
|
tabIndex?: number;
|
2021-04-12 13:32:25 +00:00
|
|
|
type?: "button" | "submit" | "reset";
|
2025-04-16 13:56:09 +00:00
|
|
|
/** Text shown on tooltip when hovering over a button */
|
2021-03-03 16:51:39 +00:00
|
|
|
title?: string;
|
2025-04-16 13:56:09 +00:00
|
|
|
/** Default: "default" */
|
2021-09-10 19:06:37 +00:00
|
|
|
variant?: ButtonVariant;
|
|
|
|
|
onClick?:
|
|
|
|
|
| ((value?: any) => void)
|
2024-10-16 15:37:38 +00:00
|
|
|
| ((
|
|
|
|
|
evt:
|
|
|
|
|
| React.MouseEvent<HTMLButtonElement>
|
|
|
|
|
| React.KeyboardEvent<HTMLButtonElement>
|
|
|
|
|
) => void);
|
2022-08-29 15:21:37 +00:00
|
|
|
isLoading?: boolean;
|
2024-10-25 15:02:52 +00:00
|
|
|
customOnKeyDown?: (e: React.KeyboardEvent) => void;
|
2025-04-23 18:42:30 +00:00
|
|
|
/** Required for buttons that contain SVG icons using`stroke` instead of`fill` for proper hover styling */
|
|
|
|
|
iconStroke?: boolean;
|
2016-10-31 21:02:06 +00:00
|
|
|
}
|
2016-09-14 20:31:54 +00:00
|
|
|
|
2021-03-03 16:51:39 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
|
|
|
interface IButtonState {}
|
|
|
|
|
|
2017-01-11 19:27:33 +00:00
|
|
|
interface Inputs {
|
|
|
|
|
button?: HTMLButtonElement;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-03 16:51:39 +00:00
|
|
|
class Button extends React.Component<IButtonProps, IButtonState> {
|
2016-09-14 20:31:54 +00:00
|
|
|
static defaultProps = {
|
2021-04-12 13:32:25 +00:00
|
|
|
type: "button",
|
2025-04-16 13:56:09 +00:00
|
|
|
variant: "default",
|
2016-09-14 20:31:54 +00:00
|
|
|
};
|
|
|
|
|
|
2021-03-01 07:48:51 +00:00
|
|
|
componentDidMount(): void {
|
2017-01-11 19:27:33 +00:00
|
|
|
const { autofocus } = this.props;
|
2021-04-12 13:32:25 +00:00
|
|
|
const {
|
|
|
|
|
inputs: { button },
|
|
|
|
|
} = this;
|
2017-01-11 19:27:33 +00:00
|
|
|
|
|
|
|
|
if (autofocus && button) {
|
|
|
|
|
button.focus();
|
|
|
|
|
}
|
2021-03-01 07:48:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setRef = (button: HTMLButtonElement): boolean => {
|
|
|
|
|
this.inputs.button = button;
|
2017-01-11 19:27:33 +00:00
|
|
|
|
|
|
|
|
return false;
|
2021-04-12 13:32:25 +00:00
|
|
|
};
|
2017-01-11 19:27:33 +00:00
|
|
|
|
2021-03-01 07:48:51 +00:00
|
|
|
inputs: Inputs = {};
|
|
|
|
|
|
2024-10-16 15:37:38 +00:00
|
|
|
handleClick = (evt: React.MouseEvent<HTMLButtonElement>): void => {
|
2016-10-27 16:14:30 +00:00
|
|
|
const { disabled, onClick } = this.props;
|
|
|
|
|
|
2016-10-31 21:02:06 +00:00
|
|
|
if (disabled) {
|
2024-10-16 15:37:38 +00:00
|
|
|
return;
|
2016-10-31 21:02:06 +00:00
|
|
|
}
|
2016-10-27 16:14:30 +00:00
|
|
|
|
2016-10-31 18:08:54 +00:00
|
|
|
if (onClick) {
|
|
|
|
|
onClick(evt);
|
|
|
|
|
}
|
2024-10-16 15:37:38 +00:00
|
|
|
};
|
2016-10-27 16:14:30 +00:00
|
|
|
|
2024-10-16 15:37:38 +00:00
|
|
|
handleKeyDown = (evt: React.KeyboardEvent<HTMLButtonElement>): void => {
|
|
|
|
|
const { disabled, onClick } = this.props;
|
|
|
|
|
|
|
|
|
|
if (disabled || evt.key !== "Enter") {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (onClick) {
|
|
|
|
|
onClick(evt as any);
|
|
|
|
|
}
|
2021-04-12 13:32:25 +00:00
|
|
|
};
|
2016-10-27 16:14:30 +00:00
|
|
|
|
2021-03-01 07:48:51 +00:00
|
|
|
render(): JSX.Element {
|
2024-10-16 15:37:38 +00:00
|
|
|
const { handleClick, handleKeyDown, setRef } = this;
|
2021-04-12 13:32:25 +00:00
|
|
|
const {
|
|
|
|
|
children,
|
|
|
|
|
className,
|
|
|
|
|
disabled,
|
|
|
|
|
tabIndex,
|
|
|
|
|
type,
|
|
|
|
|
title,
|
|
|
|
|
variant,
|
2022-08-29 15:21:37 +00:00
|
|
|
isLoading,
|
2024-10-25 15:02:52 +00:00
|
|
|
customOnKeyDown,
|
2025-04-23 18:42:30 +00:00
|
|
|
iconStroke,
|
2021-04-12 13:32:25 +00:00
|
|
|
} = this.props;
|
|
|
|
|
const fullClassName = classnames(
|
|
|
|
|
baseClass,
|
|
|
|
|
`${baseClass}--${variant}`,
|
|
|
|
|
className,
|
|
|
|
|
{
|
|
|
|
|
[`${baseClass}--disabled`]: disabled,
|
2025-04-23 18:42:30 +00:00
|
|
|
[`${baseClass}--icon-stroke`]: iconStroke,
|
2021-04-12 13:32:25 +00:00
|
|
|
}
|
|
|
|
|
);
|
2022-08-29 15:21:37 +00:00
|
|
|
const onWhite =
|
|
|
|
|
variant === "text-link" ||
|
|
|
|
|
variant === "inverse" ||
|
|
|
|
|
variant === "text-icon" ||
|
2025-04-16 13:56:09 +00:00
|
|
|
variant === "pill";
|
2016-09-14 20:31:54 +00:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<button
|
2016-10-27 16:14:30 +00:00
|
|
|
className={fullClassName}
|
2016-10-31 18:08:54 +00:00
|
|
|
disabled={disabled}
|
2016-10-27 16:14:30 +00:00
|
|
|
onClick={handleClick}
|
2024-10-25 15:02:52 +00:00
|
|
|
onKeyDown={customOnKeyDown || handleKeyDown}
|
2016-12-01 18:57:19 +00:00
|
|
|
tabIndex={tabIndex}
|
2016-09-14 20:31:54 +00:00
|
|
|
type={type}
|
2017-01-05 18:08:50 +00:00
|
|
|
title={title}
|
2017-01-11 19:27:33 +00:00
|
|
|
ref={setRef}
|
2016-09-14 20:31:54 +00:00
|
|
|
>
|
2022-08-29 15:21:37 +00:00
|
|
|
<div className={isLoading ? "transparent-text" : "children-wrapper"}>
|
|
|
|
|
{children}
|
|
|
|
|
</div>
|
|
|
|
|
{isLoading && <Spinner small button white={!onWhite} />}
|
2016-09-14 20:31:54 +00:00
|
|
|
</button>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-28 21:25:57 +00:00
|
|
|
export default Button;
|