UNPKG

@eccenca/gui-elements

Version:

GUI elements based on other libraries, usable in React application, written in Typescript.

213 lines 12.2 kB
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