@eccenca/gui-elements
Version:
GUI elements based on other libraries, usable in React application, written in Typescript.
213 lines • 12.2 kB
JavaScript
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
import React from "react";
import { createPortal } from "react-dom";
import { Classes as BlueprintClasses } from "@blueprintjs/core";
import { createPopper } from "@popperjs/core";
import { CLASSPREFIX as eccgui } from "../../configuration/constants.js";
import { Badge, Button, Card, CardActions, CardActionsAux, CardContent, CardHeader, CardOptions, CardTitle, IconButton, Markdown, SimpleDialog, Spacing, } from "../../index.js";
var highlightElementBaseClass = "".concat(eccgui, "-visual-tour__highlighted-element");
/** A visual tour multi-step tour of the current view. */
export var VisualTour = function (_a) {
var steps = _a.steps, onClose = _a.onClose, _b = _a.closeLabel, closeLabel = _b === void 0 ? "Close" : _b, _c = _a.nextLabel, nextLabel = _c === void 0 ? "Next" : _c, _d = _a.prevLabel, prevLabel = _d === void 0 ? "Back" : _d, _e = _a.usableStepTarget, usableStepTarget = _e === void 0 ? false : _e, _f = _a.isOpen, isOpen = _f === void 0 ? false : _f;
if (isOpen === false) {
return null;
}
var _g = __read(React.useState(0), 2), currentStepIndex = _g[0], setCurrentStepIndex = _g[1];
var _h = __read(React.useState(null), 2), currentStepComponent = _h[0], setCurrentStepComponent = _h[1];
React.useEffect(function () {
var closeTour = function () {
var _a, _b;
// clear observer and disconnect
if (lastObserver) {
lastObserver.takeRecords();
lastObserver.disconnect();
}
// empty step
setCurrentStepComponent(null);
// remove highlight classes
(_a = document.querySelector(".".concat(highlightElementBaseClass))) === null || _a === void 0 ? void 0 : _a.classList.remove(highlightElementBaseClass);
(_b = document
.querySelector(".".concat(highlightElementBaseClass, "--useable"))) === null || _b === void 0 ? void 0 : _b.classList.remove("".concat(highlightElementBaseClass, "--useable"));
// call callback function from outside
onClose();
};
var step = steps[currentStepIndex];
if (!step) {
// This should not happen
closeTour();
return;
}
var highlightElementClass = (typeof step["usableStepTarget"] === "undefined" ? usableStepTarget : step["usableStepTarget"])
? "".concat(highlightElementBaseClass, "--useable")
: highlightElementBaseClass;
var hasNextStep = currentStepIndex + 1 < steps.length;
var hasPreviousStep = currentStepIndex > 0;
// Configure optional highlighting
var elementToHighlight = null;
var lastObserver = null;
var setStepComponent = function () {
var stepDisplay = (React.createElement(Badge, { tagProps: { emphasis: "weaker" }, size: "large" }, " ".concat(currentStepIndex + 1, "/").concat(steps.length, " ")));
var closeButton = React.createElement(IconButton, { name: "navigation-close", text: closeLabel, onClick: closeTour });
var titleOptions = (React.createElement(React.Fragment, null,
stepDisplay,
closeButton));
var actionButtons = [
hasNextStep ? (React.createElement(Button, { key: "next", variant: "outlined", intent: "primary", onClick: function () {
setCurrentStepIndex(currentStepIndex + 1);
}, rightIcon: "navigation-next" },
nextLabel,
": ",
steps[currentStepIndex + 1].title)) : (React.createElement(Button, { key: "close", text: closeLabel, onClick: closeTour, variant: "outlined", intent: "primary", rightIcon: "navigation-close" })),
hasPreviousStep ? (React.createElement(CardActionsAux, null,
React.createElement(Button, { key: "prev", variant: "outlined", onClick: function () {
setCurrentStepIndex(currentStepIndex - 1);
}, icon: "navigation-previous" }, prevLabel))) : null,
];
// TODO: What to do if an element should have been highlighted, but none was found?
if (elementToHighlight) {
setCurrentStepComponent(React.createElement(StepPopover, { highlightedElement: elementToHighlight, titleOption: titleOptions, actionButtons: actionButtons, step: step }));
}
else {
setCurrentStepComponent(React.createElement(StepModal, { titleOption: titleOptions, actionButtons: actionButtons, step: step, onClose: closeTour }));
}
};
var addElementHighlighting = function () {
if (step.highlightElementQuery) {
var queries = typeof step.highlightElementQuery === "string"
? [step.highlightElementQuery]
: step.highlightElementQuery;
queries.forEach(function (query) {
if (elementToHighlight == null) {
elementToHighlight = document.querySelector(query);
}
});
}
else {
elementToHighlight = null;
}
if (elementToHighlight) {
// Typescript for some reason incorrectly infers the type of elementToHighlight as never
elementToHighlight.classList.add(highlightElementClass);
elementToHighlight.scrollIntoView({
behavior: "smooth",
block: "center",
});
if (lastObserver) {
lastObserver.disconnect();
}
lastObserver = new MutationObserver(function () {
// Re-new element highlighting
if (step.highlightElementQuery) {
if (!document.body.contains(elementToHighlight)) {
// Element has been removed or replaced
elementToHighlight = null;
addElementHighlighting();
}
else if (!(elementToHighlight === null || elementToHighlight === void 0 ? void 0 : elementToHighlight.classList.contains(highlightElementClass))) {
// Only the classes have been removed
elementToHighlight === null || elementToHighlight === void 0 ? void 0 : elementToHighlight.classList.add(highlightElementClass);
}
}
});
lastObserver.observe(document.body, { childList: true, subtree: true });
}
setStepComponent();
};
addElementHighlighting();
return function () {
var _a;
// Remove previous element highlight
(_a = document.querySelector(".".concat(highlightElementClass))) === null || _a === void 0 ? void 0 : _a.classList.remove(highlightElementClass);
if (lastObserver) {
lastObserver.disconnect();
}
};
}, [currentStepIndex, usableStepTarget]);
return currentStepComponent;
};
// Main content of a step
var StepContent = function (_a) {
var step = _a.step;
return (React.createElement(React.Fragment, null,
step.image && (React.createElement(React.Fragment, null,
React.createElement("img", { src: step.image }),
React.createElement(Spacing, { size: "small" }))),
typeof step.content === "string" ? React.createElement(Markdown, null, step.content) : step.content()));
};
/** Modal that is displayed for a step. */
var StepModal = function (_a) {
var _b;
var step = _a.step, titleOption = _a.titleOption, onClose = _a.onClose, actionButtons = _a.actionButtons;
return (React.createElement(SimpleDialog, { title: step.title, headerOptions: titleOption, isOpen: true, preventSimpleClosing: true, onClose: onClose, actions: actionButtons, size: step.size === "medium" ? "regular" : (_b = step.size) !== null && _b !== void 0 ? _b : "regular", overlayClassName: "".concat(eccgui, "-visual-tour__dialog") },
React.createElement(StepContent, { step: step })));
};
/** Popover that is displayed and points at the highlighted element. */
var StepPopover = function (_a) {
var _b;
var highlightedElement = _a.highlightedElement, step = _a.step, titleOption = _a.titleOption, actionButtons = _a.actionButtons;
var tooltipRef = React.useCallback(function (tooltip) {
if (tooltip) {
createPopper(highlightedElement, tooltip, {
placement: "auto",
modifiers: [
{
name: "offset",
options: {
offset: [0, 15],
},
},
],
});
}
}, [highlightedElement]);
var backdropRef = React.useCallback(function (backdrop) {
var highlightStencil = function () {
var targetRect = highlightedElement.getBoundingClientRect();
backdrop.style.left = "calc(".concat(targetRect.left + window.scrollX + "px", " - var(--").concat(eccgui, "-visual-tour-focus-padding))");
backdrop.style.top = "calc(".concat(targetRect.top + window.scrollY + "px", " - var(--").concat(eccgui, "-visual-tour-focus-padding))");
backdrop.style.width = "calc(".concat(targetRect.width + "px", " + 2 * var(--").concat(eccgui, "-visual-tour-focus-padding))");
backdrop.style.height = "calc(".concat(targetRect.height + "px", " + 2 * var(--").concat(eccgui, "-visual-tour-focus-padding))");
};
if (backdrop) {
highlightStencil();
window.addEventListener("resize", highlightStencil);
return function () {
window.removeEventListener("resize", highlightStencil);
};
}
return;
}, [highlightedElement]);
return createPortal(React.createElement("div", { className: "".concat(eccgui, "-visual-tour") },
React.createElement("div", { className: "".concat(eccgui, "-visual-tour__focushelper"), ref: backdropRef }),
React.createElement("div", null,
React.createElement("div", { className: "".concat(eccgui, "-visual-tour__backdrop") })),
React.createElement("div", { className: "".concat(eccgui, "-visual-tour__overlay") +
" ".concat(eccgui, "-visual-tour__overlay--").concat((_b = step.size) !== null && _b !== void 0 ? _b : "large") +
" ".concat(BlueprintClasses.POPOVER), role: "tooltip", ref: tooltipRef },
React.createElement("div", { className: "".concat(eccgui, "-visual-tour__arrow ").concat(BlueprintClasses.POPOVER_ARROW), "data-popper-arrow": true, "aria-hidden": true }),
React.createElement("div", { className: "".concat(BlueprintClasses.POPOVER_CONTENT, " ").concat(eccgui, "-visual-tour__overlay__content") },
React.createElement(Card, { isOnlyLayout: true, elevation: -1, whitespaceAmount: "small" },
React.createElement(CardHeader, null,
React.createElement(CardTitle, null, step.title),
React.createElement(CardOptions, null, titleOption)),
React.createElement(CardContent, null,
React.createElement(StepContent, { step: step })),
React.createElement(CardActions, { inverseDirection: true }, actionButtons))))), document.body);
};
export default VisualTour;
//# sourceMappingURL=VisualTour.js.map