UNPKG

@eccenca/gui-elements

Version:

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

200 lines 11.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.VisualTour = void 0; const react_1 = __importDefault(require("react")); const react_dom_1 = require("react-dom"); const core_1 = require("@blueprintjs/core"); const core_2 = require("@popperjs/core"); const constants_1 = require("../../configuration/constants"); const index_1 = require("../../index"); const highlightElementBaseClass = `${constants_1.CLASSPREFIX}-visual-tour__highlighted-element`; /** A visual tour multi-step tour of the current view. */ const VisualTour = ({ steps, onClose, closeLabel = "Close", nextLabel = "Next", prevLabel = "Back", usableStepTarget = false, isOpen = false, }) => { if (isOpen === false) { return null; } const [currentStepIndex, setCurrentStepIndex] = react_1.default.useState(0); const [currentStepComponent, setCurrentStepComponent] = react_1.default.useState(null); react_1.default.useEffect(() => { const closeTour = () => { var _a, _b; // clear observer and disconnect if (lastObserver) { lastObserver.takeRecords(); lastObserver.disconnect(); } // empty step setCurrentStepComponent(null); // remove highlight classes (_a = document.querySelector(`.${highlightElementBaseClass}`)) === null || _a === void 0 ? void 0 : _a.classList.remove(highlightElementBaseClass); (_b = document .querySelector(`.${highlightElementBaseClass}--useable`)) === null || _b === void 0 ? void 0 : _b.classList.remove(`${highlightElementBaseClass}--useable`); // call callback function from outside onClose(); }; const step = steps[currentStepIndex]; if (!step) { // This should not happen closeTour(); return; } const highlightElementClass = (typeof step["usableStepTarget"] === "undefined" ? usableStepTarget : step["usableStepTarget"]) ? `${highlightElementBaseClass}--useable` : highlightElementBaseClass; const hasNextStep = currentStepIndex + 1 < steps.length; const hasPreviousStep = currentStepIndex > 0; // Configure optional highlighting let elementToHighlight = null; let lastObserver = null; const setStepComponent = () => { const stepDisplay = (react_1.default.createElement(index_1.Badge, { tagProps: { emphasis: "weaker" }, size: "large" }, ` ${currentStepIndex + 1}/${steps.length} `)); const closeButton = react_1.default.createElement(index_1.IconButton, { name: "navigation-close", text: closeLabel, onClick: closeTour }); const titleOptions = (react_1.default.createElement(react_1.default.Fragment, null, stepDisplay, closeButton)); const actionButtons = [ hasNextStep ? (react_1.default.createElement(index_1.Button, { key: "next", variant: "outlined", intent: "primary", onClick: () => { setCurrentStepIndex(currentStepIndex + 1); }, rightIcon: "navigation-next" }, nextLabel, ": ", steps[currentStepIndex + 1].title)) : (react_1.default.createElement(index_1.Button, { key: "close", text: closeLabel, onClick: closeTour, variant: "outlined", intent: "primary", rightIcon: "navigation-close" })), hasPreviousStep ? (react_1.default.createElement(index_1.CardActionsAux, null, react_1.default.createElement(index_1.Button, { key: "prev", variant: "outlined", onClick: () => { 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_1.default.createElement(StepPopover, { highlightedElement: elementToHighlight, titleOption: titleOptions, actionButtons: actionButtons, step: step })); } else { setCurrentStepComponent(react_1.default.createElement(StepModal, { titleOption: titleOptions, actionButtons: actionButtons, step: step, onClose: closeTour })); } }; const addElementHighlighting = () => { if (step.highlightElementQuery) { const queries = typeof step.highlightElementQuery === "string" ? [step.highlightElementQuery] : step.highlightElementQuery; queries.forEach((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 () => { var _a; // Remove previous element highlight (_a = document.querySelector(`.${highlightElementClass}`)) === null || _a === void 0 ? void 0 : _a.classList.remove(highlightElementClass); if (lastObserver) { lastObserver.disconnect(); } }; }, [currentStepIndex, usableStepTarget]); return currentStepComponent; }; exports.VisualTour = VisualTour; // Main content of a step const StepContent = ({ step }) => { return (react_1.default.createElement(react_1.default.Fragment, null, step.image && (react_1.default.createElement(react_1.default.Fragment, null, react_1.default.createElement("img", { src: step.image }), react_1.default.createElement(index_1.Spacing, { size: "small" }))), typeof step.content === "string" ? react_1.default.createElement(index_1.Markdown, null, step.content) : step.content())); }; /** Modal that is displayed for a step. */ const StepModal = ({ step, titleOption, onClose, actionButtons }) => { var _a; return (react_1.default.createElement(index_1.SimpleDialog, { title: step.title, headerOptions: titleOption, isOpen: true, preventSimpleClosing: true, onClose: onClose, actions: actionButtons, size: step.size === "medium" ? "regular" : (_a = step.size) !== null && _a !== void 0 ? _a : "regular", overlayClassName: `${constants_1.CLASSPREFIX}-visual-tour__dialog` }, react_1.default.createElement(StepContent, { step: step }))); }; /** Popover that is displayed and points at the highlighted element. */ const StepPopover = ({ highlightedElement, step, titleOption, actionButtons }) => { var _a; const tooltipRef = react_1.default.useCallback((tooltip) => { if (tooltip) { (0, core_2.createPopper)(highlightedElement, tooltip, { placement: "auto", modifiers: [ { name: "offset", options: { offset: [0, 15], }, }, ], }); } }, [highlightedElement]); const backdropRef = react_1.default.useCallback((backdrop) => { const highlightStencil = () => { const targetRect = highlightedElement.getBoundingClientRect(); backdrop.style.left = `calc(${targetRect.left + window.scrollX + "px"} - var(--${constants_1.CLASSPREFIX}-visual-tour-focus-padding))`; backdrop.style.top = `calc(${targetRect.top + window.scrollY + "px"} - var(--${constants_1.CLASSPREFIX}-visual-tour-focus-padding))`; backdrop.style.width = `calc(${targetRect.width + "px"} + 2 * var(--${constants_1.CLASSPREFIX}-visual-tour-focus-padding))`; backdrop.style.height = `calc(${targetRect.height + "px"} + 2 * var(--${constants_1.CLASSPREFIX}-visual-tour-focus-padding))`; }; if (backdrop) { highlightStencil(); window.addEventListener("resize", highlightStencil); return () => { window.removeEventListener("resize", highlightStencil); }; } return; }, [highlightedElement]); return (0, react_dom_1.createPortal)(react_1.default.createElement("div", { className: `${constants_1.CLASSPREFIX}-visual-tour` }, react_1.default.createElement("div", { className: `${constants_1.CLASSPREFIX}-visual-tour__focushelper`, ref: backdropRef }), react_1.default.createElement("div", null, react_1.default.createElement("div", { className: `${constants_1.CLASSPREFIX}-visual-tour__backdrop` })), react_1.default.createElement("div", { className: `${constants_1.CLASSPREFIX}-visual-tour__overlay` + ` ${constants_1.CLASSPREFIX}-visual-tour__overlay--${(_a = step.size) !== null && _a !== void 0 ? _a : "large"}` + ` ${core_1.Classes.POPOVER}`, role: "tooltip", ref: tooltipRef }, react_1.default.createElement("div", { className: `${constants_1.CLASSPREFIX}-visual-tour__arrow ${core_1.Classes.POPOVER_ARROW}`, "data-popper-arrow": true, "aria-hidden": true }), react_1.default.createElement("div", { className: `${core_1.Classes.POPOVER_CONTENT} ${constants_1.CLASSPREFIX}-visual-tour__overlay__content` }, react_1.default.createElement(index_1.Card, { isOnlyLayout: true, elevation: -1, whitespaceAmount: "small" }, react_1.default.createElement(index_1.CardHeader, null, react_1.default.createElement(index_1.CardTitle, null, step.title), react_1.default.createElement(index_1.CardOptions, null, titleOption)), react_1.default.createElement(index_1.CardContent, null, react_1.default.createElement(StepContent, { step: step })), react_1.default.createElement(index_1.CardActions, { inverseDirection: true }, actionButtons))))), document.body); }; exports.default = exports.VisualTour; //# sourceMappingURL=VisualTour.js.map