diff --git a/docs/plugin-development.md b/docs/plugin-development.md index cd03c8d..d733ae8 100644 --- a/docs/plugin-development.md +++ b/docs/plugin-development.md @@ -12,7 +12,22 @@ Plugins are the building blocks of ProductLed. They are responsible for creating To get started, create a new folder in packages/@productled folder with the name of the plugin. -Now create a file called `package.json` with the following content: +in the solution root's package.json add an new entry to the workspaces array with the path to the new plugin folder. + +```json +{ + ... + "workspaces": [ + "packages/@productled/core", + "packages/@productled/spotlights", + "packages/samples/react-sample", + + ], + ... +} +``` + +Now in the new plugin folder, create a file called `package.json` with the following content: ```json { @@ -37,7 +52,7 @@ Now create a file called `package.json` with the following content: } ``` -Create a `tsconfig.json` file with the following content: +In the new plugin folder, create a `tsconfig.json` file with the following content: ```json { @@ -61,7 +76,7 @@ Create a `tsconfig.json` file with the following content: ### Create the Source Files -Create a `src` folder and add an `index.ts` and `pluginClass.ts` file with the following content: +In the new plugin folder, create a `src` folder and add an `index.ts` and `pluginClass.ts` file with the following content: `index.ts` @@ -108,9 +123,9 @@ create(element: HTMLElement, conf: any, theme: Theme): void { The `create` method takes three parameters: `element`, `conf`, and `theme`. Here's what each parameter represents: -- **`element`** is of type `HTMLElement`. It represents the HTML element on which the plugin will create the effect. +- **`element`** is of type `HTMLElement`. It represents the HTML element on which the plugin will create the effect. - **`conf`** is of type `any`. It represents the configuration object that contains properties specified in the `productled-config.json` file. These properties will be used to customize the effect created by the plugin. -- **`theme`** is of type `Theme`. It represents the theme object that will be used to style the effect created by the plugin. +- **`theme`** is of type `Theme`. It represents the theme object that will be used to style the effect created by the plugin. In summary, the `create` method is responsible for creating an effect on a specified HTML element. It uses the provided configuration and theme to customize the spotlight's appearance and behavior. diff --git a/docs/plugins/index.md b/docs/plugins/index.md index 525f58a..f6a8d84 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -21,15 +21,16 @@ npm install @productled/spotlights Register the plugin with Productled Core at application start. e.g., in file: index.tsx ``` typescript -productled.registerPlugin(new ()); +productled.registerPlugins(...plugins: Plugin[]); ``` e.g., ``` typescript -productled.registerPlugin(new SpotlightPlugin()); +productled.registerPlugins(new SpotlightPlugin()); ``` ## Available Plugins -- [Spotlights Plugin documentation](spotlights) +- [Spotlight Plugin documentation](spotlight) +- [Tooltip Plugin documentation](tooltip) diff --git a/docs/plugins/spotlights.md b/docs/plugins/spotlight.md similarity index 100% rename from docs/plugins/spotlights.md rename to docs/plugins/spotlight.md diff --git a/docs/plugins/tooltip.md b/docs/plugins/tooltip.md new file mode 100644 index 0000000..0199f5b --- /dev/null +++ b/docs/plugins/tooltip.md @@ -0,0 +1,70 @@ +--- +title: Spotlights +parent: Plugins +nav_order: 2 +--- + +# Tooltip Plugin Documentation + +The Tooltip Plugin provides a way to display helpful tooltips to users when they hover over specific elements in your application. This guide covers the installation and configuration steps to get the Tooltip plugin working with Productled. + +## Installation +Ensure that the Productled core is installed before adding the Tooltip plugin. If you haven't installed the core library, follow the Productled Installation Guide. + +To install the Tooltip plugin, run: + +``` bash +npm install @productled/tooltip +``` + +Then, register the Tooltip plugin in your application file: + +```typescript +import productledConf from './productled-config.json'; +import { Productled } from '@productled/core'; +import { TooltipPlugin } from '@productled/tooltip'; + +// Get the Productled instance +const productled = Productled.getInstance(); + +// Load the configuration +productled.loadConfig(productledConf); + +// Register the plugin +productled.registerPlugin(new TooltipPlugin()); +``` + +## Configuring Tooltips + +Once installed, configure tooltips through the productled-conf.json file. + +Example configuration for a tooltip: + +```json +{ + "hooks": [ + { + "plugin": "tooltip", + "trigger": { + "url": "/page/subpage*", + "selector": ".hover-me", + "frequency": "always" + }, + "config": { + "title": "Tooltip Title", + "description": "This is a helpful tooltip description.", + "link": "https://learn-more-link.com", + } + } + ] +} +``` + +Explanation of Configuration: + +- plugin: Specifies the tooltip plugin. +- trigger: Defines the conditions for displaying the tooltip (e.g., URL, element selector, frequency). +- config: Contains the tooltip's title, description, link, and positioning. +Using Tooltips + +After configuring the tooltips, the Productled core automatically handles the display based on the defined conditions. You can create multiple tooltips for different elements across your application. diff --git a/package-lock.json b/package-lock.json index 4ce2dbd..7f4832e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,8 @@ "workspaces": [ "packages/@productled/core", "packages/@productled/spotlights", - "packages/samples/react-sample" + "packages/samples/react-sample", + "packages/@productled/tooltip" ], "devDependencies": { "lerna": "^8.0.1" @@ -4245,6 +4246,10 @@ "resolved": "packages/@productled/spotlights", "link": true }, + "node_modules/@productled/tooltip": { + "resolved": "packages/@productled/tooltip", + "link": true + }, "node_modules/@remix-run/router": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.0.tgz", @@ -5038,15 +5043,6 @@ "@types/json-schema": "*" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -22064,11 +22060,10 @@ "dev": true }, "node_modules/webpack": { - "version": "5.93.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.93.0.tgz", - "integrity": "sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", @@ -22077,7 +22072,7 @@ "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -24191,12 +24186,21 @@ "typescript": "^4.9.5" } }, + "packages/@productled/tooltip": { + "version": "1.0.0", + "devDependencies": { + "@types/node": "^20.14.9", + "ts-node": "^10.9.2", + "typescript": "^4.9.5" + } + }, "packages/samples/react-sample": { "version": "0.1.0", "dependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@productled/core": "^1.0.0", "@productled/spotlight": "^1.0.0", + "@productled/tooltip": "^1.0.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/package.json b/package.json index bf90bc0..38f58e3 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "workspaces": [ "packages/@productled/core", "packages/@productled/spotlights", - "packages/samples/react-sample" + "packages/samples/react-sample", + "packages/@productled/tooltip" ], "dependencies": {}, "devDependencies": { diff --git a/packages/@productled/core/src/Productled.ts b/packages/@productled/core/src/Productled.ts index 2b7145e..ae12370 100644 --- a/packages/@productled/core/src/Productled.ts +++ b/packages/@productled/core/src/Productled.ts @@ -83,13 +83,15 @@ class Productled { * @param {Plugin} plugin - The plugin to register * @returns {void} */ - public registerPlugin(plugin: Plugin) { - const pluginName = plugin.Name; - // Add the plugin to the plugin store - this.pluginStore.addPlugin(plugin); - // Get the hooks for the plugin and add them to the hook store - const hooks = this.configStore.getHooks(pluginName); - this.hookStore.addHooks(hooks, pluginName); + public registerPlugins(...plugins: Plugin[]) { + plugins.forEach((plugin) => { + const pluginName = plugin.Name; + // Add the plugin to the plugin store + this.pluginStore.addPlugin(plugin); + // Get the hooks for the plugin and add them to the hook store + const hooks = this.configStore.getHooks(pluginName); + this.hookStore.addHooks(hooks, pluginName); + }); } } diff --git a/packages/@productled/tooltip/package-lock.json b/packages/@productled/tooltip/package-lock.json new file mode 100644 index 0000000..b7ae705 --- /dev/null +++ b/packages/@productled/tooltip/package-lock.json @@ -0,0 +1,215 @@ +{ + "name": "@productled/tooltip", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@productled/tooltip", + "version": "1.0.0", + "devDependencies": { + "@types/node": "^20.14.9", + "ts-node": "^10.9.2", + "typescript": "^4.9.5" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.16.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz", + "integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + } +} diff --git a/packages/@productled/tooltips/package.json b/packages/@productled/tooltip/package.json similarity index 90% rename from packages/@productled/tooltips/package.json rename to packages/@productled/tooltip/package.json index 64edf30..936ef64 100644 --- a/packages/@productled/tooltips/package.json +++ b/packages/@productled/tooltip/package.json @@ -1,5 +1,5 @@ { - "name": "@productled/tooltips", + "name": "@productled/tooltip", "version": "1.0.0", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/@productled/tooltip/src/StylesElement.ts b/packages/@productled/tooltip/src/StylesElement.ts new file mode 100644 index 0000000..0da0dc3 --- /dev/null +++ b/packages/@productled/tooltip/src/StylesElement.ts @@ -0,0 +1,45 @@ +import { Theme } from "@productled/core"; + +class StylesElement { + private color: string; + + constructor(theme: Theme) { + this.color = theme.primaryColor; + } + + public get Element(): HTMLStyleElement { + const styleElement = document.createElement('style'); + styleElement.innerHTML = this.CssCode; + return styleElement; + } + + get CssCode(): string { + return ` + .productled-tooltip { + position: absolute; + background-color: var(--backgroundColor); + color: var(--textColor); + padding: 10px; + border-radius: 4px; + border: 1px solid var(--primaryColor); + z-index: 1000; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); + display: none; + margin-top: 10px; + } + + .productled-tooltip::before { + content: ''; + position: absolute; + bottom: 100%; /* Place the arrow at the top edge */ + left: 10%; + transform: translateX(-50%); + border-width: 8px; + border-style: solid; + border-color: transparent transparent var(--primaryColor) transparent; + } +`; + } +} + +export { StylesElement }; \ No newline at end of file diff --git a/packages/@productled/tooltip/src/Tooltip.ts b/packages/@productled/tooltip/src/Tooltip.ts new file mode 100644 index 0000000..df8e4ef --- /dev/null +++ b/packages/@productled/tooltip/src/Tooltip.ts @@ -0,0 +1,117 @@ +import { Theme } from '@productled/core'; +import { StylesElement } from './StylesElement'; + +export interface Positioning { + left: string; + top: string; +} + +export interface TooltipConf { + title: string; + description: string; + link: string; + positioning: Positioning; +} + +export class Tooltip { + private element: Element; + private theme: Theme; + private tooltip: HTMLElement | null = null; + + public static SELECTOR = 'productled-tooltip'; + public static PLUGIN_NAME = 'tooltip'; + + constructor(targetElement: Element, theme: Theme) { + this.element = targetElement; + this.theme = theme; + } + + create(conf: TooltipConf): void { + + // Create tooltip container + this.tooltip = document.createElement('div'); + this.tooltip.style.position = 'absolute'; + this.tooltip.style.backgroundColor = this.theme.backgroundColor; + this.tooltip.style.color = this.theme.textColor; + this.tooltip.style.border = `1px solid ${this.theme.primaryColor}`; + this.tooltip.style.padding = '10px'; + this.tooltip.style.fontSize = this.theme.fontSize; + this.tooltip.style.zIndex = '1000'; + this.tooltip.style.display = 'none'; // Initially hidden + + // Create styles element + const styles = new StylesElement(this.theme); + this.tooltip.appendChild(styles.Element); + + // Add class for the tooltip + this.tooltip.classList.add('productled-tooltip'); + + // Create title + const title = document.createElement('h3'); + title.innerText = conf.title; + title.style.color = this.theme.primaryColor; + this.tooltip.appendChild(title); + + // Create description + const description = document.createElement('p'); + description.innerText = conf.description; + this.tooltip.appendChild(description); + + // Create link + const link = document.createElement('a'); + link.href = conf.link; + link.innerText = 'Learn more'; + link.style.color = this.theme.secondaryColor; + link.target = '_blank'; + this.tooltip.appendChild(link); + + // Append the tooltip to the body + document.body.appendChild(this.tooltip); + + // Show tooltip on hover of target element + this.element.addEventListener('mouseenter', () => { + this.positionTooltip(); + this.tooltip!.style.display = 'block'; + }); + + // Hide tooltip when leaving both the element and the tooltip + this.element.addEventListener('mouseleave', (e) => { + this.element.addEventListener('mouseleave', (e) => { + const isMouseOnTooltip = (e as MouseEvent).relatedTarget === this.tooltip; + if (!isMouseOnTooltip) { + this.hideTooltipWithDelay(500); + } + }); + }); + + // Keep tooltip visible when hovering over the tooltip + this.tooltip.addEventListener('mouseenter', () => { + this.positionTooltip(); + this.tooltip!.style.display = 'block'; + }); + + // Hide tooltip when leaving the tooltip + this.tooltip.addEventListener('mouseleave', (e) => { + const isMouseOnElement = (e as MouseEvent).relatedTarget === this.element; + if (!isMouseOnElement) { + this.hideTooltipWithDelay(); + } + }); + } + + private positionTooltip() { + + const rect = this.element.getBoundingClientRect(); + this.tooltip!.style.left = `${rect.left + window.scrollX}px`; + this.tooltip!.style.top = `${rect.bottom + window.scrollY}px`; + }; + + // Hide tooltip after a small delay to handle fast mouse movements + private hideTooltipWithDelay(delay = 200) { + setTimeout(() => { + if (this.tooltip) { + this.tooltip.style.display = 'none'; + } + }, delay); // 200ms delay to avoid flicker during fast mouse transitions + } +} diff --git a/packages/@productled/tooltip/src/TooltipPlugin.ts b/packages/@productled/tooltip/src/TooltipPlugin.ts new file mode 100644 index 0000000..1e06749 --- /dev/null +++ b/packages/@productled/tooltip/src/TooltipPlugin.ts @@ -0,0 +1,25 @@ +import { Hook, Theme, type Plugin } from "@productled/core"; +import { Tooltip, TooltipConf } from "./Tooltip"; + +class TooltipPlugin implements Plugin { + private key: string = Tooltip.PLUGIN_NAME; + + get Name(): string { + return this.key; + } + create(element: HTMLElement, hook: Hook, theme: Theme): void { + const conf: TooltipConf = hook.config; + if (element) { + const spotlight = new Tooltip(element, theme); + spotlight.create(conf); + } + } + removeAll(): void { + const elements = document.querySelectorAll(Tooltip.SELECTOR); + elements.forEach(element => { + element.remove(); + }); + } +} + +export default TooltipPlugin; \ No newline at end of file diff --git a/packages/@productled/tooltip/src/index.ts b/packages/@productled/tooltip/src/index.ts new file mode 100644 index 0000000..73eff71 --- /dev/null +++ b/packages/@productled/tooltip/src/index.ts @@ -0,0 +1 @@ +export { default as ToolTipPlugin } from './TooltipPlugin'; diff --git a/packages/@productled/tooltips/tsconfig.json b/packages/@productled/tooltip/tsconfig.json similarity index 100% rename from packages/@productled/tooltips/tsconfig.json rename to packages/@productled/tooltip/tsconfig.json diff --git a/packages/samples/react-sample/package.json b/packages/samples/react-sample/package.json index d25d8f2..5232924 100644 --- a/packages/samples/react-sample/package.json +++ b/packages/samples/react-sample/package.json @@ -6,6 +6,7 @@ "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@productled/core": "^1.0.0", "@productled/spotlight": "^1.0.0", + "@productled/tooltip": "^1.0.0", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", diff --git a/packages/samples/react-sample/src/SubPage.tsx b/packages/samples/react-sample/src/SubPage.tsx index 7324399..3706401 100644 --- a/packages/samples/react-sample/src/SubPage.tsx +++ b/packages/samples/react-sample/src/SubPage.tsx @@ -10,7 +10,8 @@ const SubPage: React.FC = () => {

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquam, nunc id aliquet lacinia, nisl justo lacinia nunc, id lacinia lectus nisl in nunc. Sed auctor, nunc id aliquet lacinia, nisl justo lacinia nunc, id lacinia lectus nisl in nunc.

Donec auctor, nunc id aliquet lacinia, nisl justo lacinia nunc, id lacinia lectus nisl in nunc. Sed auctor, nunc id aliquet lacinia, nisl justo lacinia nunc, id lacinia lectus nisl in nunc.

- + + ); } diff --git a/packages/samples/react-sample/src/index.tsx b/packages/samples/react-sample/src/index.tsx index b4d5746..0bc2f76 100644 --- a/packages/samples/react-sample/src/index.tsx +++ b/packages/samples/react-sample/src/index.tsx @@ -3,9 +3,11 @@ import ReactDOM from 'react-dom/client'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; -import { SpotlightPlugin } from '@productled/spotlight'; + import productledConf from './productled-config.json'; import { Productled } from '@productled/core'; +import { SpotlightPlugin } from '@productled/spotlight'; +import { ToolTipPlugin } from '@productled/tooltip'; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement @@ -16,7 +18,7 @@ const productled = Productled.getInstance(); // load the configuration productled.loadConfig(productledConf); // register the plugins -productled.registerPlugin(new SpotlightPlugin()); +productled.registerPlugins(new SpotlightPlugin(), new ToolTipPlugin()); root.render( diff --git a/packages/samples/react-sample/src/productled-config.json b/packages/samples/react-sample/src/productled-config.json index 969c868..996c33a 100644 --- a/packages/samples/react-sample/src/productled-config.json +++ b/packages/samples/react-sample/src/productled-config.json @@ -21,6 +21,28 @@ "top": "10" } } + }, + { + "plugin": "tooltip", + "trigger": { + "url": "/page/subpage*", + "selector": ".tooltip-me", + + "frequency": "always", + "schedule": { + "start": { "year": "2024","month": "04", "date": "01", "time": "09:00" }, + "end": { "year": "2024", "month": "12", "date": "01", "time": "09:00" } + } + }, + "config": { + "title": "New Click Me Feature", + "description": "You can now send emails directly from here", + "link": "http://myblog.com/new_feature_intro", + "positioning": { + "left": "60", + "top": "10" + } + } } ] } \ No newline at end of file