UNPKG

azure-devops-ui

Version:

React components for building web UI in Azure DevOps

179 lines (178 loc) 8.92 kB
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); } } }