@vectara/vectara-ui
Version:
Vectara's design system, codified as a React and Sass component library
77 lines (76 loc) • 3.72 kB
JavaScript
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, cloneElement } from "react";
import { Tooltip } from "react-tooltip";
import { useVuiContext } from "../context/Context";
import { VuiPortal } from "../portal/Portal";
import { VuiText } from "../typography/Text";
import { VuiTextColor } from "../typography/TextColor";
const generateTooltipId = () => {
return `tooltip-${Math.random().toString(36).slice(2, 9)}`;
};
// Naturally focusable elements that don't need tabIndex.
const FOCUSABLE_ELEMENTS = ["a", "button", "input", "select", "textarea"];
// Determine if the element needs tabIndex to be keyboard accessible.
const needsTabIndex = (element) => {
var _a;
if (!element || typeof element !== "object" || !("type" in element)) {
return false;
}
const child = element;
const elementType = typeof child.type === "string" ? child.type.toLowerCase() : "";
// Don't add tabIndex if element is naturally focusable.
if (FOCUSABLE_ELEMENTS.includes(elementType)) {
return false;
}
// Don't add tabIndex if it already has one.
if (((_a = child.props) === null || _a === void 0 ? void 0 : _a.tabIndex) !== undefined) {
return false;
}
return true;
};
export const VuiTooltip = (_a) => {
var { children, darkTheme, position, tip, usePortal } = _a, rest = __rest(_a, ["children", "darkTheme", "position", "tip", "usePortal"]);
const { getThemeStyle } = useVuiContext();
const [tooltipId] = useState(generateTooltipId());
const target = cloneElement(children, Object.assign(Object.assign({ "data-tooltip-id": tooltipId }, (needsTabIndex(children) && { tabIndex: 0 })), rest));
// Tooltips can be used in a dark-themed component, so we need to explicitly set
// the light theme class in order to enable having a different theme than the
// parent.
const style = getThemeStyle(darkTheme ? "dark" : "light");
const content = typeof tip === "string" ? (_jsx(VuiText, Object.assign({ size: "xs" }, { children: _jsx("p", { children: _jsx(VuiTextColor, Object.assign({ color: "empty" }, { children: tip })) }) }))) : (tip);
const tooltip = (_jsx(Tooltip, Object.assign({ id: tooltipId, offset: 10, className: "vuiTooltip", style: style, opacity: 1, place: position }, { children: content })));
return (_jsxs(_Fragment, { children: [target, usePortal ? _jsx(VuiPortal, { children: tooltip }) : tooltip] }));
};
// This is a workaround for the issue with ResizeObserver in ReactTooltip.
// Without this, uncaught runtime errors are thrown: "ResizeObserver loop
// completed with undelivered notifications."
// See https://github.com/ReactTooltip/react-tooltip/issues/1104
const debounce = (callback, delay) => {
let tid;
return function (...args) {
// eslint-disable-next-line no-restricted-globals
const ctx = self;
tid && clearTimeout(tid);
tid = setTimeout(() => {
callback.apply(ctx, args);
}, delay);
};
};
const _ = window.ResizeObserver;
window.ResizeObserver = class ResizeObserver extends _ {
constructor(callback) {
callback = debounce(callback, 20);
super(callback);
}
};