FEATURE: unified popover implementation (#7244)

FEATURE: unified popover implementation (#7244)

diff --git a/app/assets/javascripts/admin/components/admin-report.js.es6 b/app/assets/javascripts/admin/components/admin-report.js.es6
index d1c186e..c7f0311 100644
--- a/app/assets/javascripts/admin/components/admin-report.js.es6
+++ b/app/assets/javascripts/admin/components/admin-report.js.es6
@@ -4,10 +4,6 @@ import { exportEntity } from "discourse/lib/export-csv";
 import { outputExportResult } from "discourse/lib/export-result";
 import { SCHEMA_VERSION, default as Report } from "admin/models/report";
 import computed from "ember-addons/ember-computed-decorators";
-import {
-  registerHoverTooltip,
-  unregisterHoverTooltip
-} from "discourse/lib/tooltip";
 
 const TABLE_OPTIONS = {
   perPage: 8,
@@ -102,18 +98,6 @@ export default Ember.Component.extend({
     }
   },
 
-  didRender() {
-    this._super(...arguments);
-
-    registerHoverTooltip($(".info[data-tooltip]"));
-  },
-
-  willDestroyElement() {
-    this._super(...arguments);
-
-    unregisterHoverTooltip($(".info[data-tooltip]"));
-  },
-
   showError: Ember.computed.or(
     "showTimeoutError",
     "showExceptionError",
diff --git a/app/assets/javascripts/discourse/initializers/d-popover.js.es6 b/app/assets/javascripts/discourse/initializers/d-popover.js.es6
new file mode 100644
index 0000000..0fae353
--- /dev/null
+++ b/app/assets/javascripts/discourse/initializers/d-popover.js.es6
@@ -0,0 +1,18 @@
+import { showPopover, hidePopover } from "discourse/lib/d-popover";
+
+const SELECTORS =
+  "[data-html-popover],[data-tooltip],[data-popover],[data-html-tooltip]";
+
+export default {
+  name: "d-popover",
+
+  initialize() {
+    $("#main").on("click.d-popover mouseenter.d-popover", SELECTORS, event =>
+      showPopover(event)
+    );
+
+    $("#main").on("mouseleave.d-popover", SELECTORS, event =>
+      hidePopover(event)
+    );
+  }
+};
diff --git a/app/assets/javascripts/discourse/lib/d-popover.js.es6 b/app/assets/javascripts/discourse/lib/d-popover.js.es6
new file mode 100644
index 0000000..a1a7076
--- /dev/null
+++ b/app/assets/javascripts/discourse/lib/d-popover.js.es6
@@ -0,0 +1,173 @@
+import { siteDir } from "discourse/lib/text-direction";
+
+const D_POPOVER_ID = "d-popover";
+
+const D_POPOVER_TEMPLATE = `
+  <div id="${D_POPOVER_ID}" class="is-under">
+    <div class="d-popover-arrow d-popover-top-arrow"></div>
+    <div class="d-popover-content">
+      <div class="spinner small"></div>
+    </div>
+    <div class="d-popover-arrow d-popover-bottom-arrow"></div>
+  </div>
+`;
+
+const D_ARROW_HEIGHT = 10;
+
+const D_HORIZONTAL_MARGIN = 5;
+
+export function hidePopover() {
+  getPopover()
+    .fadeOut()
+    .remove();
+
+  return getPopover();
+}
+
+export function showPopover(event, options = {}) {
+  const $enteredElement = $(event.currentTarget);
+
+  if (isRetina()) {
+    getPopover().addClass("retina");
+  }
+
+  if (!getPopover().length) {
+    $("body").append($(D_POPOVER_TEMPLATE));
+  }
+
+  setPopoverHtmlContent($enteredElement, options.htmlContent);
+  setPopoverTextContent($enteredElement, options.textContent);
+
+  getPopover().fadeIn();
+
+  positionPopover($enteredElement);
+
+  return {
+    html: content => replaceHtmlContent($enteredElement, content),
+    text: content => replaceTextContent($enteredElement, content),
+    hide: hidePopover
+  };
+}
+
+function setPopoverHtmlContent($enteredElement, content) {
+  content =
+    content ||
+    $enteredElement.attr("data-html-popover") ||
+    $enteredElement.attr("data-html-tooltip");
+
+  replaceHtmlContent($enteredElement, content);
+}
+
+function setPopoverTextContent($enteredElement, content) {
+  content =
+    content ||
+    $enteredElement.attr("data-popover") ||
+    $enteredElement.attr("data-tooltip");
+
+  replaceTextContent($enteredElement, content);
+}
+
+function replaceTextContent($enteredElement, content) {
+  if (content) {
+    getPopover()
+      .find(".d-popover-content")
+      .text(content);
+    window.requestAnimationFrame(() => positionPopover($enteredElement));
+  }
+}
+
+function replaceHtmlContent($enteredElement, content) {
+  if (content) {
+    getPopover()
+      .find(".d-popover-content")
+      .html(content);
+    window.requestAnimationFrame(() => positionPopover($enteredElement));
+  }
+}
+
+function positionPopover($element) {
+  const $popover = getPopover();
+  $popover.removeClass("is-above is-under is-left-aligned is-right-aligned");
+
+  const $dHeader = $(".d-header");
+  const windowRect = {
+    left: 0,
+    top: $dHeader.length ? $dHeader[0].getBoundingClientRect().bottom : 0,
+    width: $(window).width(),
+    height: $(window).height()
+  };
+
+  const popoverRect = {
+    width: $popover.width(),
+    height: $popover.height(),
+    left: null,
+    right: null
+  };
+
+  if (popoverRect.width > windowRect.width - D_HORIZONTAL_MARGIN * 2) {
+    popoverRect.width = windowRect.width - D_HORIZONTAL_MARGIN * 2;
+    $popover.width(popoverRect.width);
+  }
+
+  const targetRect = $element[0].getBoundingClientRect();
+  const underSpace = windowRect.height - targetRect.bottom - D_ARROW_HEIGHT;
+  const topSpace = targetRect.top - windowRect.top - D_ARROW_HEIGHT;
+
+  if (
+    underSpace > popoverRect.height + D_HORIZONTAL_MARGIN ||
+    underSpace > topSpace
+  ) {
+    $popover
+      .css("top", targetRect.bottom + window.pageYOffset + D_ARROW_HEIGHT)
+      .addClass("is-under");
+  } else {
+    $popover
+      .css(
+        "top",
+        targetRect.top +
+          window.pageYOffset -
+          popoverRect.height -
+          D_ARROW_HEIGHT
+      )
+      .addClass("is-above");
+  }
+
+  const leftSpace = targetRect.left + targetRect.width / 2;
+
+  if (siteDir() === "ltr") {
+    if (leftSpace > popoverRect.width / 2 + D_HORIZONTAL_MARGIN) {
+      popoverRect.left = leftSpace - popoverRect.width / 2;
+      $popover.css("left", popoverRect.left);
+    } else {
+      popoverRect.left = D_HORIZONTAL_MARGIN;
+      $popover.css("left", popoverRect.left).addClass("is-left-aligned");
+    }
+  } else {
+    const rightSpace = windowRect.width - targetRect.right;
+
+    if (rightSpace > popoverRect.width / 2 + D_HORIZONTAL_MARGIN) {
+      popoverRect.left = leftSpace - popoverRect.width / 2;
+      $popover.css("left", popoverRect.left);
+    } else {
+      popoverRect.left =
+        windowRect.width - popoverRect.width - D_HORIZONTAL_MARGIN * 2;
+      $popover.css("left", popoverRect.left).addClass("is-right-aligned");
+    }
+  }
+
+  let arrowPosition;
+  if (siteDir() === "ltr") {
+    arrowPosition = Math.abs(targetRect.left - popoverRect.left);
+  } else {
+    arrowPosition = targetRect.left - popoverRect.left + targetRect.width / 2;
+  }
+  $popover.find(".d-popover-arrow").css("left", arrowPosition);
+}
+
+function isRetina() {
+  return window.devicePixelRatio && window.devicePixelRatio > 1;
+}
+
+function getPopover() {
+  return $(document.getElementById(D_POPOVER_ID));
+}
diff --git a/app/assets/javascripts/discourse/lib/tooltip.js.es6 b/app/assets/javascripts/discourse/lib/tooltip.js.es6
index ecf93d8..852451b 100644
--- a/app/assets/javascripts/discourse/lib/tooltip.js.es6
+++ b/app/assets/javascripts/discourse/lib/tooltip.js.es6
@@ -1,3 +1,4 @@
+import deprecated from "discourse-common/lib/deprecated";
 import { escapeExpression } from "discourse/lib/utilities";
 
 const fadeSpeed = 300;
@@ -77,12 +78,16 @@ export function hideTooltip() {
 }
 
 export function registerTooltip(jqueryContext) {
+  deprecated("tooltip is getting deprecated. Use d-popover instead");
+
   if (jqueryContext.length) {
     jqueryContext.off("click").on("click", event => showTooltip(event));
   }
 }
 
 export function registerHoverTooltip(jqueryContext) {
+  deprecated("tooltip is getting deprecated. Use d-popover instead");
+
   if (jqueryContext.length) {
     jqueryContext
       .off("mouseenter mouseleave click")
diff --git a/app/assets/stylesheets/common/base/d-popover.scss b/app/assets/stylesheets/common/base/d-popover.scss
new file mode 100644
index 0000000..c6e90d1
--- /dev/null

[... diff too long, it was truncated ...]

GitHub sha: 8fb63b27

3 Likes