mirror of
				https://github.com/excalidraw/excalidraw.git
				synced 2025-11-04 04:44:31 +01:00 
			
		
		
		
	feat: auto-position tooltip and suport overflowing container (#3631)
This commit is contained in:
		@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,58 +1,25 @@
 | 
			
		||||
@import "../css/variables.module";
 | 
			
		||||
.excalidraw {
 | 
			
		||||
  .Tooltip {
 | 
			
		||||
    position: relative;
 | 
			
		||||
  }
 | 
			
		||||
.excalidraw-tooltip {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  z-index: 1000;
 | 
			
		||||
 | 
			
		||||
  .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;
 | 
			
		||||
  padding: 8px;
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  pointer-events: none;
 | 
			
		||||
  word-wrap: break-word;
 | 
			
		||||
 | 
			
		||||
    &::after {
 | 
			
		||||
      content: "";
 | 
			
		||||
      border: var(--arrow-size) solid transparent;
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      left: calc(50% - var(--arrow-size));
 | 
			
		||||
    }
 | 
			
		||||
  background: $oc-black;
 | 
			
		||||
 | 
			
		||||
    &--above {
 | 
			
		||||
      bottom: calc(100% + var(--arrow-size) + 3px);
 | 
			
		||||
  line-height: 1.5;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  font-size: 13px;
 | 
			
		||||
  font-weight: 500;
 | 
			
		||||
  color: $oc-white;
 | 
			
		||||
 | 
			
		||||
      &::after {
 | 
			
		||||
        border-top-color: $oc-black;
 | 
			
		||||
        top: 100%;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  display: none;
 | 
			
		||||
 | 
			
		||||
    &--below {
 | 
			
		||||
      top: calc(100% + var(--arrow-size) + 3px);
 | 
			
		||||
 | 
			
		||||
      &::after {
 | 
			
		||||
        border-bottom-color: $oc-black;
 | 
			
		||||
        bottom: 100%;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .Tooltip:hover .Tooltip__label {
 | 
			
		||||
    visibility: visible;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .Tooltip__label:hover {
 | 
			
		||||
    visibility: visible;
 | 
			
		||||
  &.excalidraw-tooltip--visible {
 | 
			
		||||
    display: block;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user