azure-devops-ui
Version:
React components for building web UI in Azure DevOps
146 lines (145 loc) • 7.98 kB
JavaScript
import "../../CommonImports";
import "../../Core/core.css";
import "./Button.css";
import "./ExpandableButton.css";
import * as React from "react";
import { FocusGroupContext } from '../../FocusGroup';
import { FocusZoneContext } from '../../FocusZone';
import { Icon, IconSize } from '../../Icon';
import { Tooltip } from '../../TooltipEx';
import { childCount, css, getSafeId, KeyCode } from '../../Util';
import { getTabIndex } from '../../Utilities/Focus';
export class Button extends React.Component {
constructor() {
super(...arguments);
this.buttonElement = React.createRef();
this.onClick = (event) => {
if (!this.props.disabled) {
const { onClick } = this.props;
// @NOTE: Safari doesnt set focus to buttons when they are clicked, we need this
// to help manage the focus state for callouts.
this.focus();
if (onClick) {
onClick(event);
}
}
else {
event.preventDefault();
}
};
this.onFocus = (event) => {
if (this.props.onFocus) {
this.props.onFocus(event);
}
if (this.props.id) {
// @NOTE: Due to test issues using React.16.3.2 we MUST validate the onFocus method.
this.context.onFocus && this.context.onFocus(this.props.id);
}
};
this.onKeyDown = (event) => {
if (!event.defaultPrevented && !this.props.disabled) {
if (event.which === KeyCode.enter || event.which === KeyCode.space) {
if (this.props.onClick) {
this.props.onClick(event);
}
if (!this.props.href) {
event.preventDefault();
}
}
else if (event.which === KeyCode.escape) {
// Prevent ESC from bubbling up to parent components to avoid unintended dismissals
// Remove focus from the button when ESC is pressed
if (this.buttonElement.current) {
this.buttonElement.current.blur();
document.dispatchEvent(new CustomEvent('vss-telemetry-proxy', {
detail: {
area: "vss-widgets",
component: "Button",
feature: "VssWidgets.Button",
level: 3,
method: "onKeyDown",
message: "Remove focus from button on Escape key press"
},
bubbles: true
}));
}
event.preventDefault();
event.stopPropagation();
}
else if (this.props.onKeyDown) {
this.props.onKeyDown(event);
}
}
};
this.onMouseDown = (event) => {
if (!event.defaultPrevented) {
if (this.props.disabled) {
event.preventDefault();
}
}
const { onMouseDown } = this.props;
if (onMouseDown) {
onMouseDown(event);
}
};
this.onMouseLeave = (event) => {
if (!this.props.disabled) {
const { onMouseLeave } = this.props;
if (onMouseLeave) {
onMouseLeave(event);
}
}
};
this.onMouseOver = (event) => {
if (!this.props.disabled) {
const { onMouseOver } = this.props;
if (onMouseOver) {
onMouseOver(event);
}
}
};
}
render() {
if (false) {
if (this.props.danger && this.props.primary) {
console.warn("primary and danger props are both set to true on Button, only one should be set to true at a time.");
}
}
// Determine if the button is an iconOnly button.
const iconOnly = this.props.iconProps && !this.props.text && childCount(this.props.children) === 0;
const tooltipProps = this.props.tooltipProps !== undefined
? this.props.tooltipProps
: iconOnly && this.props.ariaLabel
? { text: this.props.ariaLabel }
: undefined;
return (React.createElement(FocusZoneContext.Consumer, null, (zoneContext) => {
const ButtonType = this.props.href ? "a" : "button";
// @TODO (line-height): remove the body-m from the text once the line-height is applied globally.
let role = this.props.role || (this.props.href ? "link" : "button");
let button = (
// @ts-ignore TypeScript no longer works with dynamic intrinsic component types.
React.createElement(ButtonType, { autoFocus: !this.props.href ? this.props.autoFocus : undefined, "aria-controls": getSafeId(this.props.ariaControls), "aria-describedby": getSafeId(this.props.ariaDescribedBy), "aria-disabled": this.props.disabled || this.props.ariaDisabled, "aria-expanded": this.props.ariaExpanded, "aria-haspopup": this.props.ariaHasPopup, "aria-hidden": this.props.ariaHidden, "aria-label": this.props.ariaLabel, "aria-labelledby": this.props.ariaLabelledBy, "aria-setsize": this.props.ariaSetSize, "aria-posinset": this.props.ariaPosInSet, "aria-selected": this.props.ariaSelected, "aria-checked": this.props.ariaChecked, "aria-pressed": this.props.ariaPressed, "aria-roledescription": this.props.ariaRoleDescription, className: css(this.props.className, "bolt-button", this.props.href && "bolt-link-button", this.props.iconProps && "bolt-icon-button", this.props.danger && "danger", this.props.disabled ? "disabled" : "enabled", this.props.primary && "primary", this.props.subtle && "subtle", iconOnly && "icon-only", "bolt-focus-treatment"), "data-focuszone": !this.props.disabled && !this.props.excludeFocusZone
? css(this.props.focusZoneId, zoneContext.focuszoneId)
: undefined, "data-index": this.props.dataIndex, "data-is-focusable": !this.props.excludeFocusZone, href: !this.props.disabled ? this.props.href : undefined, id: getSafeId(this.props.id), onBlur: this.props.onBlur, onClick: this.onClick, onMouseLeave: this.onMouseLeave, onMouseOver: this.onMouseOver, onKeyDown: this.onKeyDown, onMouseDown: this.onMouseDown, onFocus: this.onFocus, rel: this.props.rel, role: role, style: this.props.style, tabIndex: getTabIndex(this.props, this.context), target: this.props.target, type: this.props.type ? this.props.type : !this.props.href ? "button" : undefined, ref: this.buttonElement },
this.props.iconProps &&
Icon(Object.assign(Object.assign({ size: IconSize.medium }, this.props.iconProps), { className: css(this.props.iconProps.className, "left-icon") })),
this.props.text && React.createElement("span", { className: "bolt-button-text body-m" }, this.props.text),
this.props.children));
if (tooltipProps) {
button = (React.createElement(Tooltip, Object.assign({ addAriaDescribedBy: true }, tooltipProps), button));
}
return button;
}));
}
focus() {
if (this.buttonElement.current) {
this.buttonElement.current.focus();
}
}
setTabIndex(index) {
if (this.buttonElement.current) {
this.buttonElement.current.tabIndex = index;
}
}
}
Button.contextType = FocusGroupContext;