Browse Source

feat: auto-position tooltip and suport overflowing container (#3631)

David Luzar 4 years ago
parent
commit
357266e9ab
4 changed files with 100 additions and 76 deletions
  1. 1 5
      src/actions/actionExport.tsx
  2. 17 50
      src/components/Tooltip.scss
  3. 81 20
      src/components/Tooltip.tsx
  4. 1 1
      src/excalidraw-app/index.tsx

+ 1 - 5
src/actions/actionExport.tsx

@@ -67,11 +67,7 @@ export const actionChangeExportEmbedScene = register({
         onChange={(event) => updateData(event.target.checked)}
       />{" "}
       {t("labels.exportEmbedScene")}
-      <Tooltip
-        label={t("labels.exportEmbedScene_details")}
-        position="above"
-        long={true}
-      >
+      <Tooltip label={t("labels.exportEmbedScene_details")} long={true}>
         <div className="TooltipIcon">{questionCircle}</div>
       </Tooltip>
     </label>

+ 17 - 50
src/components/Tooltip.scss

@@ -1,58 +1,25 @@
 @import "../css/variables.module";
-.excalidraw {
-  .Tooltip {
-    position: relative;
-  }
-
-  .Tooltip__label {
-    --arrow-size: 4px;
-    visibility: hidden;
-    background: $oc-black;
-    color: $oc-white;
-    text-align: center;
-    border-radius: 6px;
-    padding: 8px;
-    position: absolute;
-    z-index: 10;
-    font-size: 13px;
-    line-height: 1.5;
-    font-weight: 500;
-    // extra pixel offset for unknown reasons
-    left: calc(50% + var(--arrow-size) / 2 - 1px);
-    transform: translateX(-50%);
-    word-wrap: break-word;
-
-    &::after {
-      content: "";
-      border: var(--arrow-size) solid transparent;
-      position: absolute;
-      left: calc(50% - var(--arrow-size));
-    }
+.excalidraw-tooltip {
+  position: absolute;
+  z-index: 1000;
 
-    &--above {
-      bottom: calc(100% + var(--arrow-size) + 3px);
+  padding: 8px;
+  border-radius: 6px;
+  box-sizing: border-box;
+  pointer-events: none;
+  word-wrap: break-word;
 
-      &::after {
-        border-top-color: $oc-black;
-        top: 100%;
-      }
-    }
+  background: $oc-black;
 
-    &--below {
-      top: calc(100% + var(--arrow-size) + 3px);
+  line-height: 1.5;
+  text-align: center;
+  font-size: 13px;
+  font-weight: 500;
+  color: $oc-white;
 
-      &::after {
-        border-bottom-color: $oc-black;
-        bottom: 100%;
-      }
-    }
-  }
-
-  .Tooltip:hover .Tooltip__label {
-    visibility: visible;
-  }
+  display: none;
 
-  .Tooltip__label:hover {
-    visibility: visible;
+  &.excalidraw-tooltip--visible {
+    display: block;
   }
 }

+ 81 - 20
src/components/Tooltip.tsx

@@ -1,31 +1,92 @@
 import "./Tooltip.scss";
 
-import React from "react";
+import React, { useEffect } from "react";
+
+const getTooltipDiv = () => {
+  const existingDiv = document.querySelector<HTMLDivElement>(
+    ".excalidraw-tooltip",
+  );
+  if (existingDiv) {
+    return existingDiv;
+  }
+  const div = document.createElement("div");
+  document.body.appendChild(div);
+  div.classList.add("excalidraw-tooltip");
+  return div;
+};
+
+const updateTooltip = (
+  item: HTMLDivElement,
+  tooltip: HTMLDivElement,
+  label: string,
+  long: boolean,
+) => {
+  tooltip.classList.add("excalidraw-tooltip--visible");
+  tooltip.style.minWidth = long ? "50ch" : "10ch";
+  tooltip.style.maxWidth = long ? "50ch" : "15ch";
+
+  tooltip.textContent = label;
+
+  const {
+    x: itemX,
+    bottom: itemBottom,
+    top: itemTop,
+    width: itemWidth,
+  } = item.getBoundingClientRect();
+
+  const {
+    width: labelWidth,
+    height: labelHeight,
+  } = tooltip.getBoundingClientRect();
+
+  const viewportWidth = window.innerWidth;
+  const viewportHeight = window.innerHeight;
+
+  const margin = 5;
+
+  const left = itemX + itemWidth / 2 - labelWidth / 2;
+  const offsetLeft =
+    left + labelWidth >= viewportWidth ? left + labelWidth - viewportWidth : 0;
+
+  const top = itemBottom + margin;
+  const offsetTop =
+    top + labelHeight >= viewportHeight
+      ? itemBottom - itemTop + labelHeight + margin * 2
+      : 0;
+
+  Object.assign(tooltip.style, {
+    top: `${top - offsetTop}px`,
+    left: `${left - offsetLeft}px`,
+  });
+};
 
 type TooltipProps = {
   children: React.ReactNode;
   label: string;
-  position?: "above" | "below";
   long?: boolean;
 };
 
-export const Tooltip = ({
-  children,
-  label,
-  position = "below",
-  long = false,
-}: TooltipProps) => (
-  <div className="Tooltip">
-    <span
-      className={
-        position === "above"
-          ? "Tooltip__label Tooltip__label--above"
-          : "Tooltip__label Tooltip__label--below"
+export const Tooltip = ({ children, label, long = false }: TooltipProps) => {
+  useEffect(() => {
+    return () =>
+      getTooltipDiv().classList.remove("excalidraw-tooltip--visible");
+  }, []);
+
+  return (
+    <div
+      onPointerEnter={(event) =>
+        updateTooltip(
+          event.currentTarget as HTMLDivElement,
+          getTooltipDiv(),
+          label,
+          long,
+        )
+      }
+      onPointerLeave={() =>
+        getTooltipDiv().classList.remove("excalidraw-tooltip--visible")
       }
-      style={{ width: long ? "50ch" : "10ch" }}
     >
-      {label}
-    </span>
-    {children}
-  </div>
-);
+      {children}
+    </div>
+  );
+};

+ 1 - 1
src/excalidraw-app/index.tsx

@@ -340,7 +340,7 @@ const ExcalidrawWrapper = () => {
           rel="noopener noreferrer"
           aria-label={t("encrypted.link")}
         >
-          <Tooltip label={t("encrypted.tooltip")} position="above" long={true}>
+          <Tooltip label={t("encrypted.tooltip")} long={true}>
             {shield}
           </Tooltip>
         </a>