diff --git a/app/assets/javascripts/discourse/app/components/post-text-selection.gjs b/app/assets/javascripts/discourse/app/components/post-text-selection.gjs index 773e85bfcf5..1c054715272 100644 --- a/app/assets/javascripts/discourse/app/components/post-text-selection.gjs +++ b/app/assets/javascripts/discourse/app/components/post-text-selection.gjs @@ -80,7 +80,7 @@ export default class PostTextSelection extends Component { super.willDestroy(...arguments); cancel(this.debouncedSelectionChanged); - this.menuInstance?.destroy(); + this.menuInstance?.close(); } @bind diff --git a/app/assets/javascripts/discourse/app/components/user-tip.gjs b/app/assets/javascripts/discourse/app/components/user-tip.gjs index 16d1041e0cf..c144a1ff1de 100644 --- a/app/assets/javascripts/discourse/app/components/user-tip.gjs +++ b/app/assets/javascripts/discourse/app/components/user-tip.gjs @@ -41,7 +41,7 @@ export default class UserTip extends Component { buttonText = `${iconHTML(this.args.buttonIcon)} ${buttonText}`; } - instance = new DTooltipInstance(getOwner(this), trigger || element, { + instance = new DTooltipInstance(getOwner(this), { identifier: "user-tip", interactive: true, closeOnScroll: false, @@ -60,6 +60,8 @@ export default class UserTip extends Component { showSkipButton: this.args.showSkipButton, }, }); + instance.trigger = trigger || element; + instance.detachedTrigger = true; this.tooltip.show(instance); diff --git a/app/assets/javascripts/discourse/app/lib/user-status-message.js b/app/assets/javascripts/discourse/app/lib/user-status-message.js index 7100c3adcd5..e4197ff0f0c 100644 --- a/app/assets/javascripts/discourse/app/lib/user-status-message.js +++ b/app/assets/javascripts/discourse/app/lib/user-status-message.js @@ -22,6 +22,10 @@ export class UserStatusMessage { } destroy() { + if (this.tooltip.isDestroyed) { + return; + } + this.tooltipInstance.destroy(); } diff --git a/app/assets/javascripts/discourse/app/modifiers/close-on-click-outside.js b/app/assets/javascripts/discourse/app/modifiers/close-on-click-outside.js index bba9400d0a6..7abdc2e270f 100644 --- a/app/assets/javascripts/discourse/app/modifiers/close-on-click-outside.js +++ b/app/assets/javascripts/discourse/app/modifiers/close-on-click-outside.js @@ -8,9 +8,13 @@ export default class CloseOnClickOutside extends Modifier { registerDestructor(this, (instance) => instance.cleanup()); } - modify(element, [closeFn, { targetSelector, secondaryTargetSelector }]) { + modify( + element, + [closeFn, { targetSelector, secondaryTargetSelector, target }] + ) { this.closeFn = closeFn; this.element = element; + this.target = target; this.targetSelector = targetSelector; this.secondaryTargetSelector = secondaryTargetSelector; @@ -25,8 +29,10 @@ export default class CloseOnClickOutside extends Modifier { return; } + const target = this.target ?? document.querySelector(this.targetSelector); + if ( - document.querySelector(this.targetSelector)?.contains(event.target) || + target?.contains(event.target) || (this.secondaryTargetSelector && document .querySelector(this.secondaryTargetSelector) diff --git a/app/assets/javascripts/discourse/app/templates/application.hbs b/app/assets/javascripts/discourse/app/templates/application.hbs index 38e2792b95f..73159bb226b 100644 --- a/app/assets/javascripts/discourse/app/templates/application.hbs +++ b/app/assets/javascripts/discourse/app/templates/application.hbs @@ -126,6 +126,6 @@ {{/if}} - - + + \ No newline at end of file diff --git a/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-menu-test.js b/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-menu-test.js index 5693f95fd69..a01f3a8be29 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-menu-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-menu-test.js @@ -1,3 +1,4 @@ +import { getOwner } from "@ember/application"; import { click, find, @@ -255,4 +256,13 @@ module("Integration | Component | FloatKit | d-menu", function (hooks) { assert.dom(document.activeElement).hasClass("my-button"); }); + + test("a menu can be closed by identifier", async function (assert) { + await render(hbs`test`); + await open(); + + await getOwner(this).lookup("service:menu").close("test"); + + assert.dom(".fk-d-menu__content.test-content").doesNotExist(); + }); }); diff --git a/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-tooltip-test.js b/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-tooltip-test.js index a2aeac55722..946a29182eb 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-tooltip-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/float-kit/d-tooltip-test.js @@ -1,3 +1,4 @@ +import { getOwner } from "@ember/application"; import { click, find, @@ -40,7 +41,7 @@ module("Integration | Component | FloatKit | d-tooltip", function (hooks) { ); await hover(); - assert.dom(".fk-d-tooltip").hasText("content"); + assert.dom(".fk-d-tooltip__content").hasText("content"); }); test("@onRegisterApi", async function (assert) { @@ -107,7 +108,7 @@ module("Integration | Component | FloatKit | d-tooltip", function (hooks) { await hover(); - assert.dom(".fk-d-tooltip").hasAttribute("data-identifier", "tip"); + assert.dom(".fk-d-tooltip__content").hasAttribute("data-identifier", "tip"); }); test("aria-expanded attribute", async function (assert) { @@ -135,7 +136,7 @@ module("Integration | Component | FloatKit | d-tooltip", function (hooks) { await hover(); - assert.dom(".fk-d-tooltip").hasText("content"); + assert.dom(".fk-d-tooltip__content").hasText("content"); }); test("content role attribute", async function (assert) { @@ -143,7 +144,7 @@ module("Integration | Component | FloatKit | d-tooltip", function (hooks) { await hover(); - assert.dom(".fk-d-tooltip").hasAttribute("role", "tooltip"); + assert.dom(".fk-d-tooltip__content").hasAttribute("role", "tooltip"); }); test("@component", async function (assert) { @@ -155,11 +156,11 @@ module("Integration | Component | FloatKit | d-tooltip", function (hooks) { await hover(); - assert.dom(".fk-d-tooltip").containsText("content"); + assert.dom(".fk-d-tooltip__content").containsText("content"); - await click(".fk-d-tooltip .btn"); + await click(".fk-d-tooltip__content .btn"); - assert.dom(".fk-d-tooltip").doesNotExist(); + assert.dom(".fk-d-tooltip__content").doesNotExist(); }); test("content aria-labelledby attribute", async function (assert) { @@ -169,7 +170,9 @@ module("Integration | Component | FloatKit | d-tooltip", function (hooks) { assert.strictEqual( document.querySelector(".fk-d-tooltip__trigger").id, - document.querySelector(".fk-d-tooltip").getAttribute("aria-labelledby") + document + .querySelector(".fk-d-tooltip__content") + .getAttribute("aria-labelledby") ); }); @@ -180,7 +183,7 @@ module("Integration | Component | FloatKit | d-tooltip", function (hooks) { await hover(); await close(); - assert.dom(".fk-d-tooltip").doesNotExist(); + assert.dom(".fk-d-tooltip__content").doesNotExist(); await render( hbs`` @@ -188,7 +191,7 @@ module("Integration | Component | FloatKit | d-tooltip", function (hooks) { await hover(); await close(); - assert.dom(".fk-d-tooltip").exists(); + assert.dom(".fk-d-tooltip__content").exists(); }); test("@closeOnClickOutside", async function (assert) { @@ -198,7 +201,7 @@ module("Integration | Component | FloatKit | d-tooltip", function (hooks) { await hover(); await triggerEvent(".test", "pointerdown"); - assert.dom(".fk-d-tooltip").doesNotExist(); + assert.dom(".fk-d-tooltip__content").doesNotExist(); await render( hbs`test` @@ -206,7 +209,7 @@ module("Integration | Component | FloatKit | d-tooltip", function (hooks) { await hover(); await triggerEvent(".test", "pointerdown"); - assert.dom(".fk-d-tooltip").exists(); + assert.dom(".fk-d-tooltip__content").exists(); }); test("@maxWidth", async function (assert) { @@ -216,15 +219,32 @@ module("Integration | Component | FloatKit | d-tooltip", function (hooks) { await hover(); assert.ok( - find(".fk-d-tooltip").getAttribute("style").includes("max-width: 20px;") + find(".fk-d-tooltip__content") + .getAttribute("style") + .includes("max-width: 20px;") ); }); test("applies position", async function (assert) { - await render(hbs``); + await render(hbs``); await hover(); - assert.ok(find(".fk-d-tooltip").getAttribute("style").includes("left: ")); - assert.ok(find(".fk-d-tooltip").getAttribute("style").includes("top: ")); + assert.ok( + find(".fk-d-tooltip__content").getAttribute("style").includes("left: ") + ); + assert.ok( + find(".fk-d-tooltip__content").getAttribute("style").includes("top: ") + ); + }); + + test("a tooltip can be closed by identifier", async function (assert) { + await render( + hbs`test` + ); + await open(); + + await getOwner(this).lookup("service:tooltip").close("test"); + + assert.dom(".fk-d-tooltip__content.test-content").doesNotExist(); }); }); diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-info-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-info-test.js index 632d7950ed2..bd3a5388d06 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/user-info-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/user-info-test.js @@ -125,7 +125,7 @@ module("Integration | Component | user-info", function (hooks) { this.currentUser.status = { emoji: "tooth", description: "off to dentist" }; await render( - hbs`` + hbs`` ); await triggerEvent(query(".user-status-message"), "mousemove"); diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-status-message-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-status-message-test.js index 7ad481a912f..39aa996a3b0 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/user-status-message-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/user-status-message-test.js @@ -48,7 +48,7 @@ module("Integration | Component | user-status-message", function (hooks) { this.status.ends_at = "2100-02-01T12:30:00.000Z"; await render( - hbs`` + hbs`` ); await mouseenter(); @@ -66,7 +66,7 @@ module("Integration | Component | user-status-message", function (hooks) { this.status.ends_at = "2100-02-02T12:30:00.000Z"; await render( - hbs`` + hbs`` ); await mouseenter(); @@ -84,7 +84,7 @@ module("Integration | Component | user-status-message", function (hooks) { this.status.ends_at = null; await render( - hbs`` + hbs`` ); await mouseenter(); @@ -97,7 +97,7 @@ module("Integration | Component | user-status-message", function (hooks) { test("it shows tooltip by default", async function (assert) { await render( - hbs`` + hbs`` ); await mouseenter(); diff --git a/app/assets/javascripts/discourse/tests/integration/components/widgets/post-test.js b/app/assets/javascripts/discourse/tests/integration/components/widgets/post-test.js index c77a6344ce8..2913dbe3e31 100644 --- a/app/assets/javascripts/discourse/tests/integration/components/widgets/post-test.js +++ b/app/assets/javascripts/discourse/tests/integration/components/widgets/post-test.js @@ -576,7 +576,7 @@ module("Integration | Component | Widget | post", function (hooks) { this.set("args", { canManage: true }); await render( - hbs`` + hbs`` ); assert @@ -597,7 +597,7 @@ module("Integration | Component | Widget | post", function (hooks) { this.set("permanentlyDeletePost", () => (this.deleted = true)); await render( - hbs`` + hbs`` ); await click(".post-menu-area .show-post-admin-menu"); @@ -616,7 +616,7 @@ module("Integration | Component | Widget | post", function (hooks) { await render(hbs` - + `); await click(".post-menu-area .show-post-admin-menu"); @@ -637,7 +637,7 @@ module("Integration | Component | Widget | post", function (hooks) { await render(hbs` - + `); await click(".post-menu-area .show-post-admin-menu"); @@ -657,7 +657,7 @@ module("Integration | Component | Widget | post", function (hooks) { await render(hbs` - + `); await click(".post-menu-area .show-post-admin-menu"); @@ -678,7 +678,7 @@ module("Integration | Component | Widget | post", function (hooks) { await render(hbs` - + `); await click(".post-menu-area .show-post-admin-menu"); @@ -701,7 +701,7 @@ module("Integration | Component | Widget | post", function (hooks) { await render(hbs` - + `); await click(".post-menu-area .show-post-admin-menu"); diff --git a/app/assets/javascripts/float-kit/addon/components/d-float-body.gjs b/app/assets/javascripts/float-kit/addon/components/d-float-body.gjs index 8bbfd4468c4..0340f2196fe 100644 --- a/app/assets/javascripts/float-kit/addon/components/d-float-body.gjs +++ b/app/assets/javascripts/float-kit/addon/components/d-float-body.gjs @@ -1,13 +1,13 @@ import Component from "@glimmer/component"; -import { concat } from "@ember/helper"; +import { concat, hash } from "@ember/helper"; import { htmlSafe } from "@ember/template"; import { modifier as modifierFn } from "ember-modifier"; import concatClass from "discourse/helpers/concat-class"; +import closeOnClickOutside from "discourse/modifiers/close-on-click-outside"; import TrapTab from "discourse/modifiers/trap-tab"; import DFloatPortal from "float-kit/components/d-float-portal"; import { getScrollParent } from "float-kit/lib/get-scroll-parent"; import FloatKitApplyFloatingUi from "float-kit/modifiers/apply-floating-ui"; -import FloatKitCloseOnClickOutside from "float-kit/modifiers/close-on-click-outside"; import FloatKitCloseOnEscape from "float-kit/modifiers/close-on-escape"; export default class DFloatBody extends Component { @@ -38,7 +38,11 @@ export default class DFloatBody extends Component { } get trigger() { - return this.args.instance.trigger; + return this.args.instance?.trigger; + } + + get content() { + return this.args.instance?.content; } get options() { @@ -48,7 +52,7 @@ export default class DFloatBody extends Component { ; + +export default DHeadlessMenu; diff --git a/app/assets/javascripts/float-kit/addon/components/d-headless-tooltip.gjs b/app/assets/javascripts/float-kit/addon/components/d-headless-tooltip.gjs new file mode 100644 index 00000000000..e3ecc2b8c65 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/components/d-headless-tooltip.gjs @@ -0,0 +1,15 @@ +import { and } from "truth-helpers"; +import DInlineFloat from "float-kit/components/d-inline-float"; + +const DHeadlessTooltip = ; + +export default DHeadlessTooltip; diff --git a/app/assets/javascripts/float-kit/addon/components/d-inline-float.gjs b/app/assets/javascripts/float-kit/addon/components/d-inline-float.gjs index 74d8b76d932..032353a56bc 100644 --- a/app/assets/javascripts/float-kit/addon/components/d-inline-float.gjs +++ b/app/assets/javascripts/float-kit/addon/components/d-inline-float.gjs @@ -32,7 +32,7 @@ export default class DInlineFloat extends Component { @mainClass={{@mainClass}} @innerClass={{@innerClass}} @role={{@role}} - @portalOutletElement={{@portalOutletElement}} + @portalOutletElement={{@instance.portalOutletElement}} @inline={{@inline}} > {{#if @instance.options.component}} diff --git a/app/assets/javascripts/float-kit/addon/components/d-inline-menu.gjs b/app/assets/javascripts/float-kit/addon/components/d-inline-menu.gjs deleted file mode 100644 index fba7bf704eb..00000000000 --- a/app/assets/javascripts/float-kit/addon/components/d-inline-menu.gjs +++ /dev/null @@ -1,26 +0,0 @@ -import Component from "@glimmer/component"; -import didInsert from "@ember/render-modifiers/modifiers/did-insert"; -import { service } from "@ember/service"; -import DInlineFloat from "float-kit/components/d-inline-float"; -import { MENU } from "float-kit/lib/constants"; - -export default class DInlineMenu extends Component { - @service menu; - - -} diff --git a/app/assets/javascripts/float-kit/addon/components/d-inline-tooltip.gjs b/app/assets/javascripts/float-kit/addon/components/d-inline-tooltip.gjs deleted file mode 100644 index 5b2ad497716..00000000000 --- a/app/assets/javascripts/float-kit/addon/components/d-inline-tooltip.gjs +++ /dev/null @@ -1,30 +0,0 @@ -import Component from "@glimmer/component"; -import didInsert from "@ember/render-modifiers/modifiers/did-insert"; -import { service } from "@ember/service"; -import { and } from "truth-helpers"; -import DInlineFloat from "float-kit/components/d-inline-float"; -import { TOOLTIP } from "float-kit/lib/constants"; - -export default class DInlineTooltip extends Component { - @service tooltip; - - -} diff --git a/app/assets/javascripts/float-kit/addon/components/d-menu.gjs b/app/assets/javascripts/float-kit/addon/components/d-menu.gjs index e4b157ecf53..81c86d82b28 100644 --- a/app/assets/javascripts/float-kit/addon/components/d-menu.gjs +++ b/app/assets/javascripts/float-kit/addon/components/d-menu.gjs @@ -1,8 +1,9 @@ import Component from "@glimmer/component"; -import { tracked } from "@glimmer/tracking"; +import { cached } from "@glimmer/tracking"; import { getOwner } from "@ember/application"; import { concat } from "@ember/helper"; import { action } from "@ember/object"; +import { next } from "@ember/runloop"; import { inject as service } from "@ember/service"; import { modifier } from "ember-modifier"; import { and } from "truth-helpers"; @@ -18,33 +19,25 @@ export default class DMenu extends Component { @service menu; @service site; - @tracked menuInstance = null; + registerTrigger = modifier((element) => { + if (!this.menuInstance.trigger) { + next(() => { + this.menuInstance.trigger = element; + this.options.onRegisterApi?.(this.menuInstance); + }); + } + }); - registerTrigger = modifier((element, [properties]) => { - const options = { - ...properties, + @cached + get menuInstance() { + return new DMenuInstance(getOwner(this), { + ...this.allowedProperties(), ...{ autoUpdate: true, listeners: true, - beforeTrigger: () => { - this.menu.close(); - }, }, - }; - const instance = new DMenuInstance(getOwner(this), element, options); - - this.menuInstance = instance; - - this.options.onRegisterApi?.(this.menuInstance); - - return () => { - instance.destroy(); - - if (this.isDestroying) { - this.menuInstance = null; - } - }; - }); + }); + } get menuId() { return `d-menu-${this.menuInstance.id}`; @@ -88,7 +81,7 @@ export default class DMenu extends Component { @translatedTitle={{@title}} @disabled={{@disabled}} aria-expanded={{if this.menuInstance.expanded "true" "false"}} - {{this.registerTrigger (this.allowedProperties)}} + {{this.registerTrigger}} ...attributes > {{#if (has-block "trigger")}} @@ -126,12 +119,12 @@ export default class DMenu extends Component { @trapTab={{this.options.trapTab}} @mainClass={{concatClass "fk-d-menu" + "fk-d-menu__content" (concat this.options.identifier "-content") }} @innerClass="fk-d-menu__inner-content" @role="dialog" @inline={{this.options.inline}} - @portalOutletElement={{this.menu.portalOutletElement}} > {{#if (has-block)}} {{yield this.componentArgs}} diff --git a/app/assets/javascripts/float-kit/addon/components/d-menus.gjs b/app/assets/javascripts/float-kit/addon/components/d-menus.gjs new file mode 100644 index 00000000000..ddc730277c8 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/components/d-menus.gjs @@ -0,0 +1,17 @@ +import Component from "@glimmer/component"; +import { inject as service } from "@ember/service"; +import DHeadlessMenu from "float-kit/components/d-headless-menu"; + +export default class DMenus extends Component { + @service menu; + + +} diff --git a/app/assets/javascripts/float-kit/addon/components/d-tooltip.gjs b/app/assets/javascripts/float-kit/addon/components/d-tooltip.gjs index 9b9090994bc..7bbbff7351f 100644 --- a/app/assets/javascripts/float-kit/addon/components/d-tooltip.gjs +++ b/app/assets/javascripts/float-kit/addon/components/d-tooltip.gjs @@ -1,7 +1,9 @@ import Component from "@glimmer/component"; -import { tracked } from "@glimmer/tracking"; +import { cached } from "@glimmer/tracking"; import { getOwner } from "@ember/application"; +import { concat } from "@ember/helper"; import { action } from "@ember/object"; +import { next } from "@ember/runloop"; import { service } from "@ember/service"; import { modifier } from "ember-modifier"; import { and } from "truth-helpers"; @@ -15,34 +17,23 @@ export default class DTooltip extends Component { @service tooltip; @service internalTooltip; - @tracked tooltipInstance = null; - - registerTrigger = modifier((element, [properties]) => { - const options = { - ...properties, - ...{ - listeners: true, - beforeTrigger: (instance) => { - this.internalTooltip.activeTooltip?.close?.(); - this.internalTooltip.activeTooltip = instance; - }, - }, - }; - const instance = new DTooltipInstance(getOwner(this), element, options); - - this.tooltipInstance = instance; - - this.options.onRegisterApi?.(instance); - - return () => { - instance.destroy(); - - if (this.isDestroying) { - this.tooltipInstance = null; - } - }; + registerTrigger = modifier((element) => { + if (!this.tooltipInstance?.trigger) { + next(() => { + this.tooltipInstance.trigger = element; + this.options.onRegisterApi?.(this.tooltipInstance); + }); + } }); + @cached + get tooltipInstance() { + return new DTooltipInstance(getOwner(this), { + ...this.allowedProperties(), + ...{ autoUpdate: true, listeners: true }, + }); + } + get options() { return this.tooltipInstance?.options; } @@ -98,11 +89,13 @@ export default class DTooltip extends Component { {{#if (has-block)}} {{yield this.componentArgs}} diff --git a/app/assets/javascripts/float-kit/addon/components/d-tooltips.gjs b/app/assets/javascripts/float-kit/addon/components/d-tooltips.gjs new file mode 100644 index 00000000000..8a199b87f16 --- /dev/null +++ b/app/assets/javascripts/float-kit/addon/components/d-tooltips.gjs @@ -0,0 +1,17 @@ +import Component from "@glimmer/component"; +import { inject as service } from "@ember/service"; +import DHeadlessTooltip from "float-kit/components/d-headless-tooltip"; + +export default class DTooltips extends Component { + @service tooltip; + + +} diff --git a/app/assets/javascripts/float-kit/addon/lib/d-menu-instance.js b/app/assets/javascripts/float-kit/addon/lib/d-menu-instance.js index 4433fafd145..8a7ad299125 100644 --- a/app/assets/javascripts/float-kit/addon/lib/d-menu-instance.js +++ b/app/assets/javascripts/float-kit/addon/lib/d-menu-instance.js @@ -1,4 +1,5 @@ -import { setOwner } from "@ember/application"; +import { tracked } from "@glimmer/tracking"; +import { getOwner, setOwner } from "@ember/application"; import { action } from "@ember/object"; import { guidFor } from "@ember/object/internals"; import { service } from "@ember/service"; @@ -10,54 +11,97 @@ export default class DMenuInstance extends FloatKitInstance { @service site; @service modal; - constructor(owner, trigger, options = {}) { + /** + * Indicates whether the menu is expanded or not. + * @property {boolean} expanded - Tracks the state of menu expansion, initially set to false. + */ + @tracked expanded = false; + + /** + * Specifies whether the trigger for opening/closing the menu is detached from the menu itself. + * This is the case when a menu is trigger programmaticaly instead of through the component. + * @property {boolean} detachedTrigger - Tracks whether the trigger is detached, initially set to false. + */ + @tracked detachedTrigger = false; + + /** + * Configuration options for the DMenuInstance. + * @property {Object} options - Options object that configures the menu behavior and display. + */ + @tracked options; + + @tracked _trigger; + + constructor(owner, options = {}) { super(...arguments); setOwner(this, owner); this.options = { ...MENU.options, ...options }; - this.id = trigger.id || guidFor(trigger); - this.trigger = trigger; + } + + get portalOutletElement() { + return document.getElementById("d-menu-portals"); + } + + get trigger() { + return this._trigger; + } + + set trigger(element) { + this._trigger = element; + this.id = element.id || guidFor(element); this.setupListeners(); } @action - close() { + async close() { + if (getOwner(this).isDestroying) { + return; + } + + await super.close(...arguments); + if (this.site.mobileView && this.options.modalForMobile) { - this.modal.close(); + await this.modal.close(); } - super.close(...arguments); + await this.menu.close(this); } @action - onMouseMove(event) { - if (this.trigger.contains(event.target) && this.expanded) { + async show() { + await super.show(...arguments); + await this.menu.show(this); + } + + @action + async onMouseMove(event) { + if (this.expanded && this.trigger.contains(event.target)) { return; } - this.onTrigger(event); + await this.onTrigger(event); } @action - onClick(event) { + async onClick(event) { if (this.expanded && this.untriggers.includes("click")) { - this.onUntrigger(event); - return; + return await this.onUntrigger(event); } - this.onTrigger(event); + await this.onTrigger(event); } @action - onMouseLeave(event) { + async onMouseLeave(event) { if (this.untriggers.includes("hover")) { - this.onUntrigger(event); + await this.onUntrigger(event); } } @action async onTrigger() { - this.options.beforeTrigger?.(this); + await this.options.beforeTrigger?.(this); await this.show(); } @@ -67,8 +111,7 @@ export default class DMenuInstance extends FloatKitInstance { } @action - async destroy() { - await this.close(); + destroy() { this.tearDownListeners(); } } diff --git a/app/assets/javascripts/float-kit/addon/lib/d-tooltip-instance.js b/app/assets/javascripts/float-kit/addon/lib/d-tooltip-instance.js index f6dfbe0a9b0..43ab8ec57e1 100644 --- a/app/assets/javascripts/float-kit/addon/lib/d-tooltip-instance.js +++ b/app/assets/javascripts/float-kit/addon/lib/d-tooltip-instance.js @@ -1,3 +1,4 @@ +import { tracked } from "@glimmer/tracking"; import { setOwner } from "@ember/application"; import { action } from "@ember/object"; import { guidFor } from "@ember/object/internals"; @@ -8,45 +9,89 @@ import FloatKitInstance from "float-kit/lib/float-kit-instance"; export default class DTooltipInstance extends FloatKitInstance { @service tooltip; - constructor(owner, trigger, options = {}) { + /** + * Indicates whether the tooltip is expanded or not. + * @property {boolean} expanded - Tracks the state of tooltip expansion, initially set to false. + */ + @tracked expanded = false; + + /** + * Specifies whether the trigger for opening/closing the tooltip is detached from the tooltip itself. + * This is the case when a tooltip is trigger programmaticaly instead of through the component. + * @property {boolean} detachedTrigger - Tracks whether the trigger is detached, initially set to false. + */ + @tracked detachedTrigger = false; + + /** + * Configuration options for the DTooltipInstance. + * @property {Object} options - Options object that configures the tooltip behavior and display. + */ + @tracked options; + + @tracked _trigger; + + constructor(owner, options = {}) { super(...arguments); setOwner(this, owner); this.options = { ...TOOLTIP.options, ...options }; - this.id = trigger.id || guidFor(trigger); - this.trigger = trigger; + } + + get trigger() { + return this._trigger; + } + + set trigger(element) { + this._trigger = element; + this.id = element.id || guidFor(element); this.setupListeners(); } - @action - onMouseMove(event) { - if (this.trigger.contains(event.target) && this.expanded) { - return; - } - - this.onTrigger(event); + get portalOutletElement() { + return document.getElementById("d-tooltip-portals"); } @action - onClick(event) { + async show() { + await this.tooltip.show(this); + await super.show(...arguments); + } + + @action + async close() { + await this.tooltip.close(this); + + await super.close(...arguments); + } + + @action + async onMouseMove(event) { + if (this.expanded && this.trigger.contains(event.target)) { + return; + } + + await this.onTrigger(event); + } + + @action + async onClick(event) { if (this.expanded && this.untriggers.includes("click")) { - this.onUntrigger(event); - return; + return await this.onUntrigger(event); } - this.onTrigger(event); + await this.onTrigger(event); } @action - onMouseLeave(event) { + async onMouseLeave(event) { if (this.untriggers.includes("hover")) { - this.onUntrigger(event); + await this.onUntrigger(event); } } @action async onTrigger() { - this.options.beforeTrigger?.(this); + await this.options.beforeTrigger?.(this); await this.show(); } @@ -57,7 +102,6 @@ export default class DTooltipInstance extends FloatKitInstance { @action destroy() { - this.close(); this.tearDownListeners(); } } diff --git a/app/assets/javascripts/float-kit/addon/lib/float-kit-instance.js b/app/assets/javascripts/float-kit/addon/lib/float-kit-instance.js index bebcbbeb174..a6cfe6bc215 100644 --- a/app/assets/javascripts/float-kit/addon/lib/float-kit-instance.js +++ b/app/assets/javascripts/float-kit/addon/lib/float-kit-instance.js @@ -1,6 +1,6 @@ import { tracked } from "@glimmer/tracking"; import { action } from "@ember/object"; -import { cancel, next } from "@ember/runloop"; +import { cancel } from "@ember/runloop"; import { makeArray } from "discourse-common/lib/helpers"; import discourseLater from "discourse-common/lib/later"; import { bind } from "discourse-common/utils/decorators"; @@ -13,48 +13,44 @@ function cancelEvent(event) { } export default class FloatKitInstance { - @tracked expanded = false; @tracked id = null; - trigger = null; - content = null; - @action - show() { - this.expanded = true; - - next(() => { - this.options.onShow?.(); - }); + async show() { + await this.options.onShow?.(); } @action - close() { - this.expanded = false; - - next(() => { - this.options.onClose?.(); - }); + async close() { + await this.options.onClose?.(); } @action - onFocus(event) { - this.onTrigger(event); + async onFocus(event) { + await this.onTrigger(event); } @action - onBlur(event) { - this.onTrigger(event); + async onBlur(event) { + await this.onTrigger(event); } @action - onFocusIn(event) { - this.onTrigger(event); + async onFocusIn(event) { + await this.onTrigger(event); } @action - onFocusOut(event) { - this.onTrigger(event); + async onFocusOut(event) { + await this.onTrigger(event); + } + + @action + trapPointerDown(event) { + // this is done to avoid trigger on click outside when you click on your own trigger + // given trigger and content are not in the same div, we can't just check if target is + // inside the menu + event.stopPropagation(); } @action @@ -105,7 +101,11 @@ export default class FloatKitInstance { } tearDownListeners() { - if (!this.options.listeners) { + if (typeof this.trigger.addEventListener === "function") { + this.trigger.removeEventListener("pointerdown", this.trapPointerDown); + } + + if (!this.options?.listeners) { return; } @@ -141,7 +141,11 @@ export default class FloatKitInstance { } setupListeners() { - if (!this.options.listeners) { + if (typeof this.trigger.addEventListener === "function") { + this.trigger.addEventListener("pointerdown", this.trapPointerDown); + } + + if (!this.options?.listeners) { return; } diff --git a/app/assets/javascripts/float-kit/addon/modifiers/close-on-click-outside.js b/app/assets/javascripts/float-kit/addon/modifiers/close-on-click-outside.js deleted file mode 100644 index 219c9154b6f..00000000000 --- a/app/assets/javascripts/float-kit/addon/modifiers/close-on-click-outside.js +++ /dev/null @@ -1,40 +0,0 @@ -import { registerDestructor } from "@ember/destroyable"; -import Modifier from "ember-modifier"; -import { bind } from "discourse-common/utils/decorators"; - -export default class FloatKitCloseOnClickOutside extends Modifier { - constructor(owner, args) { - super(owner, args); - registerDestructor(this, (instance) => instance.cleanup()); - } - - modify(element, [trigger, closeFn]) { - this.closeFn = closeFn; - this.trigger = trigger; - this.element = element; - - document.addEventListener("pointerdown", this.check, { - passive: true, - }); - } - - @bind - check(event) { - if (this.element.contains(event.target)) { - return; - } - - if ( - this.trigger instanceof HTMLElement && - this.trigger.contains(event.target) - ) { - return; - } - - this.closeFn(); - } - - cleanup() { - document.removeEventListener("pointerdown", this.check); - } -} diff --git a/app/assets/javascripts/float-kit/addon/services/menu.js b/app/assets/javascripts/float-kit/addon/services/menu.js index 182b4d447aa..f743d6883c5 100644 --- a/app/assets/javascripts/float-kit/addon/services/menu.js +++ b/app/assets/javascripts/float-kit/addon/services/menu.js @@ -1,14 +1,12 @@ import { tracked } from "@glimmer/tracking"; import { getOwner } from "@ember/application"; import { action } from "@ember/object"; -import { guidFor } from "@ember/object/internals"; +import { schedule } from "@ember/runloop"; import Service from "@ember/service"; import DMenuInstance from "float-kit/lib/d-menu-instance"; -import { updatePosition } from "float-kit/lib/update-position"; export default class Menu extends Service { - @tracked activeMenu; - @tracked portalOutletElement; + @tracked registeredMenus = []; /** * Render a menu @@ -34,36 +32,49 @@ export default class Menu extends Service { if (arguments[0] instanceof DMenuInstance) { instance = arguments[0]; - if (this.activeMenu === instance && this.activeMenu.expanded) { + if (instance.expanded) { return; } } else { - const trigger = arguments[0]; - if ( - this.activeMenu && - this.activeMenu.id === - (trigger?.id?.length ? trigger.id : guidFor(trigger)) && - this.activeMenu.expanded - ) { - this.activeMenu?.close(); - return; - } + instance = this.registeredMenus.find( + (registeredMenu) => registeredMenu.trigger === arguments[0] + ); - instance = new DMenuInstance(getOwner(this), trigger, arguments[1]); + if (!instance) { + instance = new DMenuInstance(getOwner(this), arguments[1]); + instance.trigger = arguments[0]; + instance.detachedTrigger = true; + } } - await this.replace(instance); - instance.expanded = true; - return instance; - } + if (instance.options.identifier) { + for (const menu of this.registeredMenus) { + if ( + menu.options.identifier === instance.options.identifier && + menu !== instance + ) { + await this.close(menu); + } + } + } - /** - * Replaces any active menu- - */ - @action - async replace(menu) { - await this.activeMenu?.close(); - this.activeMenu = menu; + if (instance.expanded) { + return await this.close(instance); + } + + await new Promise((resolve) => { + if (!this.registeredMenus.includes(instance)) { + this.registeredMenus = this.registeredMenus.concat(instance); + } + + instance.expanded = true; + + schedule("afterRender", () => { + resolve(); + }); + }); + + return instance; } /** @@ -72,26 +83,27 @@ export default class Menu extends Service { */ @action async close(menu) { - if (this.activeMenu && menu && this.activeMenu.id !== menu.id) { + if (typeof menu === "string") { + menu = this.registeredMenus.find( + (registeredMenu) => registeredMenu.options.identifier === menu + ); + } + + if (!menu) { return; } - await this.activeMenu?.close(); - this.activeMenu = null; - } + await new Promise((resolve) => { + menu.expanded = false; - /** - * Update the menu position - * @param {DMenuInstance} [menu] - the menu to update, if not provider will update any active menu - */ - @action - async update(menu) { - const instance = menu || this.activeMenu; - if (!instance) { - return; - } - await updatePosition(instance.trigger, instance.content, instance.options); - await instance.show(); + this.registeredMenus = this.registeredMenus.filter( + (registeredMenu) => menu.id !== registeredMenu.id + ); + + schedule("afterRender", () => { + resolve(); + }); + }); } /** @@ -104,17 +116,12 @@ export default class Menu extends Service { */ @action register(trigger, options = {}) { - return new DMenuInstance(getOwner(this), trigger, { + const instance = new DMenuInstance(getOwner(this), { ...options, listeners: true, - beforeTrigger: async (menu) => { - await this.replace(menu); - }, }); - } - - @action - registerPortalOutletElement(element) { - this.portalOutletElement = element; + instance.trigger = trigger; + instance.detachedTrigger = true; + return instance; } } diff --git a/app/assets/javascripts/float-kit/addon/services/tooltip.js b/app/assets/javascripts/float-kit/addon/services/tooltip.js index ba70cdd8fe7..c88ac3edd7d 100644 --- a/app/assets/javascripts/float-kit/addon/services/tooltip.js +++ b/app/assets/javascripts/float-kit/addon/services/tooltip.js @@ -1,14 +1,12 @@ import { tracked } from "@glimmer/tracking"; import { getOwner } from "@ember/application"; import { action } from "@ember/object"; -import { guidFor } from "@ember/object/internals"; +import { schedule } from "@ember/runloop"; import Service from "@ember/service"; import DTooltipInstance from "float-kit/lib/d-tooltip-instance"; -import { updatePosition } from "float-kit/lib/update-position"; export default class Tooltip extends Service { - @tracked activeTooltip; - @tracked portalOutletElement; + @tracked registeredTooltips = []; /** * Render a tooltip @@ -34,36 +32,48 @@ export default class Tooltip extends Service { if (arguments[0] instanceof DTooltipInstance) { instance = arguments[0]; - if (this.activeTooltip === instance && this.activeTooltip.expanded) { + if (instance.expanded) { return; } } else { - const trigger = arguments[0]; - if ( - this.activeTooltip && - this.activeTooltip.id === - (trigger?.id?.length ? trigger.id : guidFor(trigger)) && - this.activeTooltip.expanded - ) { - this.activeTooltip?.close(); - return; + instance = this.registeredTooltips.find( + (registeredTooltips) => registeredTooltips.trigger === arguments[0] + ); + if (!instance) { + instance = new DTooltipInstance(getOwner(this), arguments[1]); + instance.trigger = arguments[0]; + instance.detachedTrigger = true; } - - instance = new DTooltipInstance(getOwner(this), trigger, arguments[1]); } - await this.replace(instance); - instance.expanded = true; - return instance; - } + if (instance.options.identifier) { + for (const tooltip of this.registeredTooltips) { + if ( + tooltip.options.identifier === instance.options.identifier && + tooltip !== instance + ) { + await this.close(tooltip); + } + } + } - /** - * Replaces any active tooltip - */ - @action - async replace(tooltip) { - await this.activeTooltip?.close(); - this.activeTooltip = tooltip; + if (instance.expanded) { + return await this.close(instance); + } + + await new Promise((resolve) => { + if (!this.registeredTooltips.includes(instance)) { + this.registeredTooltips = this.registeredTooltips.concat(instance); + } + + instance.expanded = true; + + schedule("afterRender", () => { + resolve(); + }); + }); + + return instance; } /** @@ -72,26 +82,27 @@ export default class Tooltip extends Service { */ @action async close(tooltip) { - if (this.activeTooltip && tooltip && this.activeTooltip.id !== tooltip.id) { + if (typeof tooltip === "string") { + tooltip = this.registeredTooltips.find( + (registeredTooltip) => registeredTooltip.options.identifier === tooltip + ); + } + + if (!tooltip) { return; } - await this.activeTooltip?.close(); - this.activeTooltip = null; - } + tooltip.expanded = false; - /** - * Update the tooltip position - * @param {DTooltipInstance} [tooltip] - the tooltip to update, if not provider will update any active tooltip - */ - @action - async update(tooltip) { - const instance = tooltip || this.activeTooltip; - if (!instance) { - return; - } - await updatePosition(instance.trigger, instance.content, instance.options); - await instance.show(); + await new Promise((resolve) => { + this.registeredTooltips = this.registeredTooltips.filter( + (registeredTooltips) => tooltip.id !== registeredTooltips.id + ); + + schedule("afterRender", () => { + resolve(); + }); + }); } /** @@ -104,17 +115,12 @@ export default class Tooltip extends Service { */ @action register(trigger, options = {}) { - return new DTooltipInstance(getOwner(this), trigger, { + const instance = new DTooltipInstance(getOwner(this), { ...options, listeners: true, - beforeTrigger: async (tooltip) => { - await this.replace(tooltip); - }, }); - } - - @action - registerPortalOutletElement(element) { - this.portalOutletElement = element; + instance.trigger = trigger; + instance.detachedTrigger = true; + return instance; } } diff --git a/app/assets/javascripts/float-kit/app/components/d-headless-menu.js b/app/assets/javascripts/float-kit/app/components/d-headless-menu.js new file mode 100644 index 00000000000..b56bfdcb16d --- /dev/null +++ b/app/assets/javascripts/float-kit/app/components/d-headless-menu.js @@ -0,0 +1 @@ +export { default } from "float-kit/components/d-headless-menu"; diff --git a/app/assets/javascripts/float-kit/app/components/d-headless-tooltip.js b/app/assets/javascripts/float-kit/app/components/d-headless-tooltip.js new file mode 100644 index 00000000000..39173d022d7 --- /dev/null +++ b/app/assets/javascripts/float-kit/app/components/d-headless-tooltip.js @@ -0,0 +1 @@ +export { default } from "float-kit/components/d-headless-tooltip"; diff --git a/app/assets/javascripts/float-kit/app/components/d-inline-menu.js b/app/assets/javascripts/float-kit/app/components/d-inline-menu.js deleted file mode 100644 index 65c4b0de930..00000000000 --- a/app/assets/javascripts/float-kit/app/components/d-inline-menu.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from "float-kit/components/d-inline-menu"; diff --git a/app/assets/javascripts/float-kit/app/components/d-inline-tooltip.js b/app/assets/javascripts/float-kit/app/components/d-inline-tooltip.js deleted file mode 100644 index 173122c7b20..00000000000 --- a/app/assets/javascripts/float-kit/app/components/d-inline-tooltip.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from "float-kit/components/d-inline-tooltip"; diff --git a/app/assets/javascripts/float-kit/app/components/d-menus.js b/app/assets/javascripts/float-kit/app/components/d-menus.js new file mode 100644 index 00000000000..fd438bd86d2 --- /dev/null +++ b/app/assets/javascripts/float-kit/app/components/d-menus.js @@ -0,0 +1 @@ +export { default } from "float-kit/components/d-menus"; diff --git a/app/assets/javascripts/float-kit/app/components/d-tooltips.js b/app/assets/javascripts/float-kit/app/components/d-tooltips.js new file mode 100644 index 00000000000..07aab3a9743 --- /dev/null +++ b/app/assets/javascripts/float-kit/app/components/d-tooltips.js @@ -0,0 +1 @@ +export { default } from "float-kit/components/d-tooltips"; diff --git a/app/assets/stylesheets/common/float-kit/d-tooltip.scss b/app/assets/stylesheets/common/float-kit/d-tooltip.scss index 6d6418279dd..4c2d6146e98 100644 --- a/app/assets/stylesheets/common/float-kit/d-tooltip.scss +++ b/app/assets/stylesheets/common/float-kit/d-tooltip.scss @@ -9,17 +9,6 @@ } .fk-d-tooltip { - background-color: var(--secondary); - border-radius: var(--d-border-radius); - border: 1px solid var(--primary-low); - box-shadow: var(--shadow-menu-panel); - z-index: z("max"); - width: max-content; - position: absolute; - top: 0; - display: flex !important; - padding: 0; - &__trigger { display: inline-flex; cursor: pointer; @@ -33,26 +22,6 @@ } } - &.-animated { - animation: d-tooltip-opening 0.15s ease-in; - - &[data-placement^="bottom"] { - transform-origin: top center; - } - - &[data-placement^="top"] { - transform-origin: bottom center; - } - - &[data-placement^="right"] { - transform-origin: center left; - } - - &[data-placement^="left"] { - transform-origin: center right; - } - } - &__inner-content { display: flex; overflow: hidden; @@ -61,41 +30,74 @@ align-items: center; } - .arrow { + &__content { + background-color: var(--secondary); + border-radius: var(--d-border-radius); + border: 1px solid var(--primary-low); + box-shadow: var(--shadow-menu-panel); z-index: z("max"); + width: max-content; position: absolute; - } + top: 0; + display: flex !important; + padding: 0; - &[data-placement^="top"] { - .arrow { - bottom: -10px; - rotate: 180deg; + &.-animated { + animation: d-tooltip-opening 0.15s ease-in; + + &[data-placement^="bottom"] { + transform-origin: top center; + } + + &[data-placement^="top"] { + transform-origin: bottom center; + } + + &[data-placement^="right"] { + transform-origin: center left; + } + + &[data-placement^="left"] { + transform-origin: center right; + } } - } - &[data-placement^="top-start"] { .arrow { - margin-left: 10px; + z-index: z("max"); + position: absolute; } - } - &[data-placement^="bottom"] { - .arrow { - top: -10px; + &[data-placement^="top"] { + .arrow { + bottom: -10px; + rotate: 180deg; + } } - } - &[data-placement^="right"] { - .arrow { - rotate: -90deg; - left: -10px; + &[data-placement^="top-start"] { + .arrow { + margin-left: 10px; + } } - } - &[data-placement^="left"] { - .arrow { - rotate: 90deg; - right: -10px; + &[data-placement^="bottom"] { + .arrow { + top: -10px; + } + } + + &[data-placement^="right"] { + .arrow { + rotate: -90deg; + left: -10px; + } + } + + &[data-placement^="left"] { + .arrow { + rotate: 90deg; + right: -10px; + } } } } diff --git a/plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.gjs b/plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.gjs index 4d75d2d3948..52cdfb1c5c7 100644 --- a/plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.gjs +++ b/plugins/chat/assets/javascripts/discourse/components/chat-message-reaction.gjs @@ -61,8 +61,6 @@ export default class ChatMessageReaction extends Component { this.args.reaction.emoji, this.args.reaction.reacted ? "remove" : "add" ); - - this.tooltip.close(); } @cached diff --git a/plugins/chat/test/javascripts/components/chat-channel-test.js b/plugins/chat/test/javascripts/components/chat-channel-test.js index 69db4e461f9..8afcf5effa5 100644 --- a/plugins/chat/test/javascripts/components/chat-channel-test.js +++ b/plugins/chat/test/javascripts/components/chat-channel-test.js @@ -158,9 +158,7 @@ module( }); test("it shows status tooltip", async function (assert) { - await render( - hbs`` - ); + await render(hbs``); await triggerEvent(statusSelector(mentionedUser.username), "mousemove"); assert.equal( diff --git a/plugins/discourse-local-dates/assets/javascripts/initializers/discourse-local-dates.js b/plugins/discourse-local-dates/assets/javascripts/initializers/discourse-local-dates.js index 1b1b45187a2..47af250c236 100644 --- a/plugins/discourse-local-dates/assets/javascripts/initializers/discourse-local-dates.js +++ b/plugins/discourse-local-dates/assets/javascripts/initializers/discourse-local-dates.js @@ -361,7 +361,7 @@ export default { }, ]); - return tooltip.close(); + return tooltip.close("local-date"); } if (!event?.target?.classList?.contains("discourse-local-date")) { @@ -370,6 +370,7 @@ export default { const siteSettings = this.container.lookup("service:site-settings"); return tooltip.show(event.target, { + identifier: "local-date", content: htmlSafe(buildHtmlPreview(event.target, siteSettings)), }); }, diff --git a/spec/system/user_tips_spec.rb b/spec/system/user_tips_spec.rb index 7bb36f6ec33..db187c08c09 100644 --- a/spec/system/user_tips_spec.rb +++ b/spec/system/user_tips_spec.rb @@ -14,7 +14,7 @@ describe "Homepage", type: :system do sign_in user visit "/" - expect(page).to have_no_css(".fk-d-tooltip .user-tip__title") + expect(page).to have_no_css(".fk-d-tooltip__content .user-tip__title") end it "does not show the boostrapping tip to an admin user" do @@ -22,7 +22,7 @@ describe "Homepage", type: :system do sign_in admin visit "/" - expect(page).to have_no_css(".fk-d-tooltip .user-tip__title") + expect(page).to have_no_css(".fk-d-tooltip__content .user-tip__title") end end @@ -35,20 +35,23 @@ describe "Homepage", type: :system do visit "/" - expect(page).to have_css(".fk-d-tooltip .user-tip__title", text: "Your first notification!") + expect(page).to have_css( + ".fk-d-tooltip__content .user-tip__title", + text: "Your first notification!", + ) - find(".d-header #current-user").click + find(".d-header").click # Clicking outside element dismisses the tip expect(page).to have_no_css( - ".fk-d-tooltip .user-tip__title", + ".fk-d-tooltip__content .user-tip__title", text: "Your first notification!", ) page.refresh expect(page).to have_no_css( - ".fk-d-tooltip .user-tip__title", + ".fk-d-tooltip__content .user-tip__title", text: "Your first notification!", ) end @@ -57,25 +60,25 @@ describe "Homepage", type: :system do sign_in user visit "/" - find(".fk-d-tooltip .user-tip__buttons .btn-primary").click - expect(page).to have_no_css(".fk-d-tooltip .user-tip__title") + find(".fk-d-tooltip__content .user-tip__buttons .btn-primary").click + expect(page).to have_no_css(".fk-d-tooltip__content .user-tip__title") discovery.topic_list.visit_topic(topics[0]) - expect(page).to have_css(".fk-d-tooltip .user-tip__title", text: "Topic timeline") + expect(page).to have_css(".fk-d-tooltip__content .user-tip__title", text: "Topic timeline") - find(".fk-d-tooltip .user-tip__buttons .btn-primary").click - expect(page).to have_css(".fk-d-tooltip .user-tip__title", text: "Keep reading!") + find(".fk-d-tooltip__content .user-tip__buttons .btn-primary").click + expect(page).to have_css(".fk-d-tooltip__content .user-tip__title", text: "Keep reading!") end it "can skip all tips" do sign_in user visit "/" - find(".fk-d-tooltip .user-tip__buttons .btn", text: "Skip tips").click - expect(page).to have_no_css(".fk-d-tooltip .user-tip__title") + find(".fk-d-tooltip__content .user-tip__buttons .btn", text: "Skip tips").click + expect(page).to have_no_css(".fk-d-tooltip__content .user-tip__title") discovery.topic_list.visit_topic(topics[0]) - expect(page).to have_no_css(".fk-d-tooltip .user-tip__title") + expect(page).to have_no_css(".fk-d-tooltip__content .user-tip__title") end end end