azure-devops-ui
Version:
React components for building web UI in Azure DevOps
158 lines (157 loc) • 8.21 kB
JavaScript
import "../../CommonImports";
import "../../Core/core.css";
import "./TeachingBubble.css";
import * as React from "react";
import { TeachingBubbleCornerPlacement } from "./TeachingBubble.Props";
import { Callout } from '../../Callout';
import { FocusZoneKeyStroke } from '../../FocusZone';
import { css, KeyCode } from '../../Util';
import { ScreenSize } from '../../Core/Util/Screen';
import { Location } from '../..//Utilities/Position';
import { ScreenSizeObserver } from '../../Utilities/ScreenSize';
export class CustomTeachingBubble extends React.Component {
constructor(props) {
super(props);
this.dismiss = () => {
this.setState({ fadingOut: true });
};
this.onAnimationEnd = () => {
if (this.state.fadingOut) {
this.props.onDismiss && this.props.onDismiss();
}
else if (this.state.newPosition) {
this.setState({
anchorElement: this.props.anchorElement,
anchorOrigin: this.props.anchorOrigin,
children: this.props.children,
cornerPlacement: this.props.cornerPlacement,
newPosition: false
}, this.props.onLocationChange);
}
};
this.getCalloutOrigin = (anchorOrigin, cornerPlacement) => {
if (anchorOrigin.horizontal === Location.center) {
cornerPlacement = TeachingBubbleCornerPlacement.vertical;
}
else if (anchorOrigin.vertical === Location.center) {
cornerPlacement = TeachingBubbleCornerPlacement.horizontal;
}
if (cornerPlacement === TeachingBubbleCornerPlacement.horizontal) {
if (anchorOrigin.horizontal === Location.start) {
return { horizontal: Location.end, vertical: anchorOrigin.vertical };
}
else if (anchorOrigin.horizontal === Location.end) {
return { horizontal: Location.start, vertical: anchorOrigin.vertical };
}
else {
return { horizontal: Location.center, vertical: anchorOrigin.vertical };
}
}
else {
if (anchorOrigin.vertical === Location.start) {
return { horizontal: anchorOrigin.horizontal, vertical: Location.end };
}
else if (anchorOrigin.vertical === Location.end) {
return { horizontal: anchorOrigin.horizontal, vertical: Location.start };
}
else {
return { horizontal: anchorOrigin.horizontal, vertical: Location.center };
}
}
};
this.getBeakClassName = (anchorOrigin, calloutOrigin) => {
let classNameHorizontal, classNameVertical;
if (calloutOrigin.horizontal === Location.start) {
if (anchorOrigin.horizontal === Location.end) {
classNameHorizontal = "left-primary";
}
else {
classNameHorizontal = "left-secondary";
}
}
else if (calloutOrigin.horizontal === Location.end) {
if (anchorOrigin.horizontal === Location.start) {
classNameHorizontal = "right-primary";
}
else {
classNameHorizontal = "right-secondary";
}
}
else {
classNameHorizontal = "center-h";
}
if (calloutOrigin.vertical === Location.start) {
if (anchorOrigin.vertical === Location.end) {
classNameVertical = "top-primary";
}
else {
classNameVertical = "top-secondary";
}
}
else if (calloutOrigin.vertical === Location.end) {
if (anchorOrigin.vertical === Location.start) {
classNameVertical = "bottom-primary";
}
else {
classNameVertical = "bottom-secondary";
}
}
else {
classNameVertical = "center-v";
}
return css(classNameHorizontal, classNameVertical);
};
this.state = {
anchorElement: props.anchorElement,
anchorOrigin: props.anchorOrigin,
children: props.children,
cornerPlacement: this.props.cornerPlacement
};
}
static getDerivedStateFromProps(props, state) {
if (state.anchorElement !== props.anchorElement ||
state.anchorOrigin.horizontal !== props.anchorOrigin.horizontal ||
state.anchorOrigin.vertical !== props.anchorOrigin.vertical) {
return Object.assign(Object.assign({}, state), { newPosition: true });
}
else {
return { children: props.children, cornerPlacement: props.cornerPlacement };
}
}
render() {
const { anchorElement, anchorOrigin, children, cornerPlacement = TeachingBubbleCornerPlacement.vertical, fadingOut, newPosition } = this.state;
const calloutOrigin = this.getCalloutOrigin(anchorOrigin, cornerPlacement);
return (React.createElement(ScreenSizeObserver, null, (screenSizeProps) => {
const fullScreen = screenSizeProps.screenSize === ScreenSize.xsmall;
let maxHeight;
if (!fullScreen) {
if (anchorOrigin.vertical === Location.end) {
maxHeight = window.innerHeight - anchorElement.getBoundingClientRect().bottom;
}
else if (anchorOrigin.vertical === Location.start) {
maxHeight = anchorElement.getBoundingClientRect().top;
}
else {
maxHeight = window.innerHeight;
}
}
return (React.createElement(Callout, { anchorElement: fullScreen ? undefined : anchorElement, anchorPoint: fullScreen ? { x: 0, y: 0 } : undefined, anchorOrigin: anchorOrigin, ariaDescribedBy: this.props.ariaDescribedBy, ariaLabelledBy: this.props.ariaLabeledBy, blurDismiss: this.props.textOnly, calloutOrigin: calloutOrigin, className: css(fullScreen && "absolute-fill"), contentClassName: css("bolt-bubble-callout", (fadingOut || newPosition) && "fade-out", this.getBeakClassName(anchorOrigin, calloutOrigin), fullScreen ? "bolt-bubble-fullscreen absolute-fill flex-grow scroll-auto" : "relative"), contentShadow: true, escDismiss: true, fixedLayout: true, focuszoneProps: {
circularNavigation: true,
defaultActiveElement: this.props.defaultActiveElement || ".bolt-bubble-focusable-element",
focusOnMount: true,
handleTabKey: true,
includeDefaults: true,
postprocessKeyStroke: function (event) {
// We want to prevent moving outside the bubble if there are no focusable elements in the bubble.
event.which === KeyCode.tab && event.preventDefault();
return FocusZoneKeyStroke.IgnoreParents;
}
}, onAnimationEnd: this.onAnimationEnd, onDismiss: this.dismiss, updateLayout: true, viewportChangeDismiss: false },
React.createElement("div", { "aria-live": "polite", "aria-relevant": "text", className: "bolt-bubble-container" },
!fullScreen && React.createElement("div", { className: "bolt-bubble-beak depth-8" }),
React.createElement("div", { className: "bolt-bubble-content relative scroll-auto", style: { maxHeight: maxHeight } },
React.createElement("div", { className: "bolt-bubble-focusable-element no-outline", tabIndex: -1 }),
children))));
}));
}
}