UNPKG

@intility/bifrost-react

Version:

React library for Intility's design system, Bifrost.

133 lines (130 loc) 3.9 kB
"use client"; /* eslint-disable @typescript-eslint/ban-ts-comment */ import classNames from "classnames"; import { arrow, autoUpdate, flip, FloatingArrow, offset, shift, useDismiss, useFloating, useFocus, useHover, useInteractions } from "@floating-ui/react"; import React, { cloneElement, useRef, useState } from "react"; import reactMajor from "../../utils/reactMajor.js"; import setRef from "../../utils/setRef.js"; import Popover from "../internal/Popover.internal.js"; import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; const refIsProp = reactMajor >= 19; /** * Display a tooltip on hover * The nested content of `<Tooltip>` needs to be a single element able to hold * a `ref`, unless an element reference is passed to the `reference` prop. * * @see https://bifrost.intility.com/react/tooltip * * @example * <Tooltip content="Good job hovering!" placement="right"> * <span>Hover me</span> * </Tooltip> */ export default function Tooltip({ reference, children, className, content, visible, onShow, onHide, state = "default", variant = "basic", placement = "top", strategy = "absolute", disabled = false, offset: offsetProp = [0, 10] }) { const element = reference && "current" in reference ? reference.current : reference; const [open, setOpen] = useState(false); const arrowRef = useRef(null); const isControlled = visible !== undefined; const isOpen = visible ?? open; const { refs, floatingStyles, context } = useFloating({ whileElementsMounted: autoUpdate, strategy, open: isOpen, elements: { reference: element }, placement, onOpenChange: newOpen => { setOpen(newOpen); if (newOpen) { onShow?.(); } else { onHide?.(); } }, middleware: [offset({ mainAxis: offsetProp[1], // gap between target and popup crossAxis: offsetProp[0] // skidding }), shift({ padding: 10 // space around edge of viewport }), flip({ fallbackAxisSideDirection: "start" }), // make sure arrow is placed *after* shift arrow({ element: arrowRef, // Prevent arrow from overlapping box border padding: 8 })] }); const hover = useHover(context, { enabled: !isControlled }); const dismiss = useDismiss(context, { outsidePressEvent: "click" }); const focus = useFocus(context, { enabled: !isControlled }); const { getReferenceProps, getFloatingProps } = useInteractions([hover, dismiss, focus]); if (disabled || !content) return children; // https://github.com/vercel/next.js/discussions/81876 // https://github.com/facebook/react/issues/32392 if ( // @ts-ignore children?.$$typeof === Symbol.for("react.lazy") && typeof React.use === "function" && "_payload" in children) { // @ts-ignore children = React.use(children._payload); } return /*#__PURE__*/_jsxs(_Fragment, { children: [children && /*#__PURE__*/cloneElement(children, { ...(!isControlled && getReferenceProps(children.props)), ref: node => { refs.setReference(node); // @ts-ignore setRef(refIsProp ? children.props.ref : children.ref, node); } }), context.open && /*#__PURE__*/_jsxs(Popover, { open: isOpen, ref: refs.setFloating, style: floatingStyles, className: classNames(className, "bf-tooltip", "bf-open-sans", { "bf-tooltip-neutral": state === "neutral", "bf-tooltip-compact": variant === "compact" }), ...getFloatingProps(), children: [/*#__PURE__*/_jsx("span", { className: "bf-tooltip-content", children: content }), /*#__PURE__*/_jsx(FloatingArrow, { ref: arrowRef, context: context, className: "bf-tooltip-arrow", height: 7, width: 16 })] })] }); }