← Files NGS Analysis WorkbenchARCHIVED FILE

src/design-system/TooltipIconButton.tsx

1.82 KB · Sep 30, 2026 · 23:20 UTC

↓ Download file

import { Tooltip } from "@base-ui/react/tooltip";
import { useState } from "react";
import { Icon, type IconName } from "./Icon";
import ui from "../styles/ui.module.css";
import styles from "./TooltipIconButton.module.css";

export function TooltipIconButton({
  label,
  icon,
  busy = false,
  onClick,
}: {
  label: string;
  icon: IconName;
  busy?: boolean;
  onClick: () => void;
}) {
  const [trigger, setTrigger] = useState<HTMLElement | null>(null);
  const [open, setOpen] = useState(false);
  // Keep host tokens and native modal top-layer membership while escaping the
  // scrolling attempt panel. A body portal would lose both in embedded views.
  const container = trigger?.closest<HTMLElement>("dialog, .ngs-theme");

  return (
    <Tooltip.Root open={open} onOpenChange={setOpen} disabled={busy}>
      <Tooltip.Trigger
        ref={setTrigger}
        render={<button type="button" disabled={busy} />}
        className={`${ui.button} ${ui.buttonQuiet} ${ui.iconButton}`}
        aria-label={label}
        aria-busy={busy}
        onClick={onClick}
        onKeyDown={(event) => {
          if (event.key === "Escape" && open) {
            event.preventDefault();
            event.stopPropagation();
            setOpen(false);
          }
        }}
      >
        <Icon
          name={busy ? "spinner" : icon}
          className={busy ? "ngs-spin" : undefined}
          size={20}
        />
      </Tooltip.Trigger>
      <Tooltip.Portal container={container}>
        <Tooltip.Positioner
          className={styles.positioner}
          side="bottom"
          align="end"
          sideOffset={8}
          collisionPadding={8}
        >
          <Tooltip.Popup className={styles.popup} role="tooltip">
            {label}
          </Tooltip.Popup>
        </Tooltip.Positioner>
      </Tooltip.Portal>
    </Tooltip.Root>
  );
}

SHA-256: b9791af7c8af606d57b4f6351e661f01d84ade7861bbd3037c266546f6dd4280