azure-devops-ui
Version:
React components for building web UI in Azure DevOps
179 lines (178 loc) • 8.92 kB
JavaScript
import "../../CommonImports";
import "../../Core/core.css";
import "./HeaderCommandBar.css";
import { ObservableLike } from '../../Core/Observable';
import { Button } from '../../Button';
import { MenuButton, MenuItemType } from '../../Menu';
import { OverflowButton, ResizeGroup } from '../../ResizeGroup';
import { css } from '../../Util';
import { FILTER_CHANGE_EVENT } from '../../Utilities/Filter';
import * as React from "react";
import { CustomHeaderCommandBar } from "./CustomHeaderCommandBar";
import { getFilterItem } from "./Items";
let headerCommandBarId = 1;
export class HeaderCommandBar extends React.Component {
constructor(props) {
super(props);
this.overflowButtonRef = React.createRef();
this.buttonRefs = {};
this.onLayoutChange = (hidden) => {
// Use rAF to ensure button refs are updated before calling updateTabIndexesFromRefs
requestAnimationFrame(() => this.updateTabIndexesFromRefs(hidden));
};
this.moreButtonId = props.moreButtonId || "header-command-bar-menu-button" + headerCommandBarId++;
}
render() {
var _a;
const { items } = this.props;
const sortedItems = items.sort((a, b) => {
var _a, _b;
const aRank = (_a = a.rank) !== null && _a !== void 0 ? _a : Number.MAX_VALUE;
const bRank = (_b = b.rank) !== null && _b !== void 0 ? _b : Number.MAX_VALUE;
return aRank > bRank ? 1 : aRank < bRank ? -1 : 0;
});
let defaultElementId = "";
this.buttonRefs = {};
const buttonItems = [];
const overflowItems = [];
const extraItems = [];
const responsiveChildren = [];
// Anything with important: true will be rendered as a button
// Anything with important: false will be rendered in overflow
// If buttonCount is supplied, that many buttons will be rendered into
// a resizeGroup, and the rest will be overflow. By default, buttonCount is 3.
let buttonCount = (_a = this.props.buttonCount) !== null && _a !== void 0 ? _a : 3;
const isMenuBar = !items.length || items[0].role !== "button";
sortedItems.forEach((value) => {
const id = value.id;
if (value.itemType === MenuItemType.Divider) {
if (value.important) {
buttonItems.push(React.createElement("div", { className: "bolt-header-command-item-separator", key: id }));
}
else {
extraItems.push(value);
}
}
else {
const buttonProps = {
ariaChecked: ObservableLike.getValue(value.checked),
ariaLabel: value.ariaLabel,
ariaRoleDescription: value.href ? "link" : "button",
ariaControls: value.ariaControls,
ariaDescribedBy: value.ariaDescribedBy,
ariaExpanded: value.ariaExpanded,
ariaHasPopup: value.ariaHasPopup,
ariaSetSize: value.ariaSetSize,
ariaPosInSet: value.ariaPosInSet,
ariaSelected: value.ariaSelected,
ariaPressed: value.ariaPressed,
className: css(value.className, "bolt-header-command-item-button"),
disabled: value.disabled,
href: value.href,
iconProps: value.iconProps,
id: id,
primary: value.isPrimary,
role: value.role || "menuitem",
subtle: value.subtle,
target: value.target,
text: value.text,
tooltipProps: value.tooltipProps
};
if (value.important === false || (value.important === undefined && buttonCount === 0)) {
extraItems.push(value);
return;
}
else {
if (value.important === undefined) {
responsiveChildren.push(buttonItems.length);
overflowItems.push(value);
}
buttonCount--;
}
let TagName = Button;
const ref = React.createRef();
this.buttonRefs[id] = ref;
if (value.subMenuProps) {
buttonProps.contextualMenuProps = { menuProps: value.subMenuProps };
buttonProps.hideDropdownIcon = value.hideDropdownIcon;
TagName = MenuButton;
}
else {
buttonProps.onClick = e => value.onActivate && value.onActivate(value, e);
}
if (!defaultElementId && !value.disabled) {
defaultElementId = id;
}
if (value.renderButton) {
buttonItems.push(value.renderButton(buttonProps));
}
else {
buttonItems.push(React.createElement(TagName, Object.assign({}, buttonProps, { key: id, ref: ref })));
}
}
});
buttonItems.push(React.createElement(OverflowButton, { className: css(this.props.overflowClassName, "bolt-header-command-item-button"), id: this.moreButtonId, key: this.moreButtonId, role: "menuitem", ref: this.overflowButtonRef, menuClassName: this.props.moreButtonMenuClassName }));
this.buttonRefs[this.moreButtonId] = this.overflowButtonRef;
// We will use a role of "menubar", unless the first item has a role of button.
// This will be the case the close button in Panel Headers.
if (items.length > 0) {
return (React.createElement(CustomHeaderCommandBar, { className: this.props.className, focusGroupProps: { defaultElementId: defaultElementId || this.moreButtonId }, role: isMenuBar ? "menubar" : undefined },
React.createElement(ResizeGroup, { responsiveLayoutProps: {
responsiveChildren: responsiveChildren.reverse(),
onLayoutChange: this.onLayoutChange
}, overflowMenuItems: overflowItems.reverse(), extraItems: extraItems, useAriaLabelForOverflow: this.props.useAriaLabelForOverflow },
React.createElement("div", { className: css(this.props.className, "bolt-header-commandbar-button-group", "flex-row flex-center flex-grow scroll-hidden rhythm-horizontal-8") }, buttonItems))));
}
return null;
}
focus(options) {
const ref = this.buttonRefs[options.commandBarItemId];
if (ref && ref.current) {
ref.current.focus();
}
}
updateTabIndexesFromRefs(hiddenCount) {
const refs = Object.entries(this.buttonRefs);
const visibleCount = refs.length - hiddenCount;
const firstEnabled = refs.find(([id, ref]) => id !== this.moreButtonId && ref.current && !ref.current.props.disabled);
const targetId = !firstEnabled || visibleCount <= 1 ? this.moreButtonId : firstEnabled[0];
refs.forEach(([id, ref]) => {
var _a;
(_a = ref.current) === null || _a === void 0 ? void 0 : _a.setTabIndex(id === targetId ? 0 : -1);
});
}
}
export class HeaderCommandBarWithFilter extends React.Component {
constructor(props) {
super(props);
this.headerCommandBarRef = React.createRef();
this.onFilterClicked = () => {
this.props.filterToggled.value = !this.props.filterToggled.value;
};
this.onFilterChanged = () => {
const hasChanges = this.props.filter.hasChangesToReset();
if (hasChanges !== this.state.filterHasChanges) {
this.setState({
filterHasChanges: hasChanges
});
}
};
this.state = { filterHasChanges: this.props.filter.hasChangesToReset() };
}
componentDidMount() {
this.props.filter.subscribe(this.onFilterChanged, FILTER_CHANGE_EVENT);
}
componentWillUnmount() {
this.props.filter.unsubscribe(this.onFilterChanged, FILTER_CHANGE_EVENT);
}
render() {
const items = this.props.items ? [...this.props.items] : [];
items.push(getFilterItem(this.onFilterClicked, this.state.filterHasChanges));
return React.createElement(HeaderCommandBar, Object.assign({}, this.props, { items: items, ref: this.headerCommandBarRef }));
}
focus(options) {
if (this.headerCommandBarRef.current) {
this.headerCommandBarRef.current.focus(options);
}
}
}