UNPKG

azure-devops-ui

Version:

React components for building web UI in Azure DevOps

130 lines (129 loc) 7.66 kB
import "../../CommonImports"; import "../../Core/core.css"; import "./Breadcrumb.css"; import * as React from "react"; import { Icon } from '../../Icon'; import { Link } from '../../Link'; import { MenuCell } from '../../Menu'; import { OverflowButton, ResizeGroup } from '../../ResizeGroup'; import * as Resources from '../../Resources.Breadcrumb'; import { Tooltip } from '../../TooltipEx'; import { css, getSafeId } from '../../Util'; let breadcrumbCount = 0; export class Breadcrumb extends React.Component { constructor(props) { super(props); this.renderBreadcrumbItems = () => { const { excludeFocusZone, excludeTabStop, extraContent, role } = this.props; const { displayedItems, hiddenItemCount } = this.state; const ariaLabel = this.props.ariaLabel || Resources.BreadcrumbItemAriaLabel; // Find index of last rendered item so the divider icon // knows not to render on that item const lastItemIndex = displayedItems.length - 1; const itemElements = displayedItems.map((item, index) => (React.createElement("div", { className: "bolt-breadcrumb-list-item", key: item.key || String(index) }, this.renderItem(item), (index !== lastItemIndex || extraContent) && this.renderDivider(item.key)))); itemElements.splice(0, 0, React.createElement("div", { className: css("bolt-breadcrumb-overflow", hiddenItemCount > 0 && "bolt-breadcrumb-overflow-visible"), key: "overflowItem" }, React.createElement(OverflowButton, { excludeTabStop: excludeTabStop, excludeFocusZone: excludeFocusZone }), hiddenItemCount > 0 && hiddenItemCount < displayedItems.length && this.renderDivider("overflow"))); if (extraContent) { itemElements.push(React.createElement("div", { className: "bolt-breadcrumb-list-item", id: getSafeId("breadcrumb-extra-content"), key: "breadcrumb-extra-content" }, extraContent)); } return (React.createElement("div", { className: css("bolt-breadcrumb flex-row flex-grow", itemElements.length > 0 && "bolt-breadcrumb-with-items"), role: role, "aria-label": ariaLabel }, ...itemElements)); }; this.renderDivider = (key) => { return (React.createElement("div", { key: "BID$" + key, className: "bolt-breadcrumb-divider secondary-text cursor-default flex-noshrink" }, "/")); }; this.renderItem = (item) => { const internalItem = (React.createElement(React.Fragment, null, item.iconProps && (React.createElement(Icon, Object.assign({}, item.iconProps, { className: css("bolt-breadcrumb-item-icon", !item.text && "icon-only", item.iconProps.className) }))), item.text && (React.createElement(Tooltip, { overflowOnly: true }, React.createElement("span", { className: "bolt-breadcrumb-item-text text-ellipsis" }, item.text))))); const ariaDescribedById = "bolt-breadcrumb" + this.breadcrumbId + "-item-described-by" + item.key; if (item.onClick || item.href) { return (React.createElement("div", { className: "bolt-breadcrumb-item" }, React.createElement(Link, { ariaLabel: item.ariaLabel, ariaDescribedBy: ariaDescribedById, key: "BreadcrumbItem$" + item.key, onClick: item.onClick, href: item.href, excludeFocusZone: this.props.excludeFocusZone, excludeTabStop: this.props.excludeTabStop, removeUnderline: true }, React.createElement("div", { className: "bolt-breadcrumb-item-text-container" }, internalItem), React.createElement("div", { className: "bolt-breadcrumb-hidden-element", id: getSafeId(ariaDescribedById) }, item.ariaDescription)))); } else { return (React.createElement("div", { className: "bolt-breadcrumb-item", "aria-label": item.ariaLabel }, internalItem)); } }; this.onLayoutChanged = (hiddenCount) => { this.setState({ hiddenItemCount: hiddenCount }); }; this.onMenuItemActivate = (menuItem, event) => { const breadcrumbItem = menuItem.data; if (breadcrumbItem.onClick) { breadcrumbItem.onClick(event, breadcrumbItem); } }; this.breadcrumbId = breadcrumbCount++; this.state = Breadcrumb.getDerivedStateFromProps(props, this.state); } static getDerivedStateFromProps(props, state) { const displayedItems = arrangeItems(props.items); return Object.assign(Object.assign({}, state), { displayedItems, linkItems: getLinkItems(displayedItems) }); } render() { const items = this.state.displayedItems; const overflowMenuItems = items.map((item, index) => (Object.assign(Object.assign({ data: item, iconProps: item.iconProps, text: item.text, id: item.key, href: item.href }, item.menuItemProps), { onActivate: this.onMenuItemActivate }))); if (this.props.extraContent) { overflowMenuItems.push({ id: "overflow-item", className: css(this.props.extraContentClassName || ""), onActivate: () => { return true; }, renderMenuCell: (menuCell, menuItem, details) => { if (menuCell === MenuCell.PrimaryText) { return this.props.extraContent; } } }); } const responsiveChildren = overflowMenuItems.map((items, index) => index + 1); const responsiveLayoutProps = { responsiveChildren, onLayoutChange: this.onLayoutChanged }; return (React.createElement("div", { className: css("bolt-breadcrumb-container", items.length > 0 && "bolt-breadcrumb-with-items", this.props.className), role: "navigation", "aria-label": Resources.BreadcrumbAriaLabel }, React.createElement(ResizeGroup, { responsiveLayoutProps: responsiveLayoutProps, overflowMenuItems: overflowMenuItems }, this.renderBreadcrumbItems()))); } } Breadcrumb.defaultProps = { items: [] }; function getLinkItems(displayedItems) { return displayedItems.filter(item => { return item.href || item.onClick; }); } function arrangeItems(items) { if (items === undefined) { return []; } // Build map of what is currently in the list of breadcrumb items const existingItemsMap = {}; // If exisiting items have the same id and different priorities, the higher priority item will be shown. items.forEach(item => { if (!existingItemsMap[item.key] || (existingItemsMap[item.key].priority || 0) <= (item.priority || 0)) { existingItemsMap[item.key] = item; } }); const newItems = []; Object.keys(existingItemsMap).forEach(key => { if (!existingItemsMap[key].hidden) { newItems.push(existingItemsMap[key]); } }); const shouldSortItems = newItems.some(x => x.rank >= 0); if (shouldSortItems) { // This is not a stable sort. If we pass items with the same rank, we may not always get them back in the // same order. We have a stable sort in vssf webplatform, but we're not taking a dependency there. newItems.sort((a, b) => { const aRank = a.rank || Number.MAX_VALUE; const bRank = b.rank || Number.MAX_VALUE; return aRank - bRank; }); } return newItems; }