@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
169 lines (166 loc) • 5.7 kB
JavaScript
'use client';
import { useNativeButton } from "../hooks/useNativeButton.mjs";
import { useIsClient } from "../hooks/useIsClient.mjs";
import { placementMap } from "../utils/placement.mjs";
import { TooltipArrowIcon } from "./ArrowIcon.mjs";
import TooltipContent_default from "./TooltipContent.mjs";
import { useTooltipPortalContainer } from "./TooltipPortal.mjs";
import { styles } from "./style.mjs";
import { cloneElement, isValidElement, memo, useCallback, useMemo, useState } from "react";
import { jsx, jsxs } from "react/jsx-runtime";
import { cx } from "antd-style";
import { mergeProps } from "@base-ui/react/merge-props";
import { Tooltip } from "@base-ui/react/tooltip";
import { mergeRefs } from "react-merge-refs";
//#region src/Tooltip/TooltipStandalone.tsx
const DEFAULT_OPEN_DELAY = 400;
const DEFAULT_CLOSE_DELAY = 100;
/**
* Tooltip component - displays small contextual hints on hover/focus
* Compatible with Ant Design Tooltip-like API (subset)
*/
const TooltipStandalone = memo(({ children, title, arrow = true, className, classNames, closeDelay, defaultOpen = false, disabled = false, getPopupContainer, hotkey, hotkeyProps, mouseEnterDelay, mouseLeaveDelay, onOpenChange, open, openDelay, placement = "top", portalled = true, styles: styleProps, zIndex, ref: refProp }) => {
const isClient = useIsClient();
const [uncontrolledOpen, setUncontrolledOpen] = useState(Boolean(defaultOpen));
const resolvedOpen = disabled ? false : open ?? uncontrolledOpen;
const handleOpenChange = useCallback((nextOpen) => {
if (disabled && nextOpen) return;
onOpenChange?.(nextOpen);
if (open === void 0) setUncontrolledOpen(nextOpen);
}, [
disabled,
onOpenChange,
open
]);
const resolvedOpenDelay = useMemo(() => {
if (openDelay !== void 0) return openDelay;
if (mouseEnterDelay !== void 0) return mouseEnterDelay * 1e3;
return DEFAULT_OPEN_DELAY;
}, [mouseEnterDelay, openDelay]);
const resolvedCloseDelay = useMemo(() => {
if (closeDelay !== void 0) return closeDelay;
if (mouseLeaveDelay !== void 0) return mouseLeaveDelay * 1e3;
return DEFAULT_CLOSE_DELAY;
}, [closeDelay, mouseLeaveDelay]);
const placementConfig = placementMap[placement] ?? placementMap.top;
const baseSideOffset = arrow ? 8 : 6;
const portalContainer = useTooltipPortalContainer();
const { isNativeButtonTriggerElement } = useNativeButton({ children });
const resolvedClassNames = useMemo(() => ({
arrow: cx(styles.arrow, classNames?.arrow),
popup: cx(styles.popup, className, classNames?.root, classNames?.container),
positioner: styles.positioner,
viewport: cx(styles.viewport, classNames?.content)
}), [
className,
classNames?.arrow,
classNames?.container,
classNames?.content,
classNames?.root
]);
const resolvedStyleProps = useMemo(() => {
if (typeof styleProps === "function") return void 0;
return styleProps;
}, [styleProps]);
const resolvedStyles = useMemo(() => ({
arrow: resolvedStyleProps?.arrow,
popup: {
...resolvedStyleProps?.root,
...resolvedStyleProps?.container
},
positioner: { zIndex: zIndex ?? 1100 },
viewport: resolvedStyleProps?.content
}), [resolvedStyleProps, zIndex]);
const triggerElement = useMemo(() => {
const triggerProps = {
closeDelay: resolvedCloseDelay,
delay: resolvedOpenDelay,
disabled
};
if (isValidElement(children)) return /* @__PURE__ */ jsx(Tooltip.Trigger, {
...triggerProps,
render: (props) => {
const resolvedProps = (() => {
if (isNativeButtonTriggerElement) return props;
const { type, ref: triggerRef, ...restProps } = props;
return restProps;
})();
return cloneElement(children, {
...mergeProps(children.props, resolvedProps),
ref: mergeRefs([
children.ref,
props.ref,
refProp
])
});
}
});
return /* @__PURE__ */ jsx(Tooltip.Trigger, {
...triggerProps,
ref: refProp,
children
});
}, [
children,
disabled,
isNativeButtonTriggerElement,
refProp,
resolvedCloseDelay,
resolvedOpenDelay
]);
const customContainer = useMemo(() => {
if (!getPopupContainer || !isClient) return void 0;
}, [getPopupContainer, isClient]);
const popup = useMemo(() => /* @__PURE__ */ jsx(Tooltip.Positioner, {
align: placementConfig.align,
className: resolvedClassNames.positioner,
"data-placement": placement,
side: placementConfig.side,
sideOffset: baseSideOffset,
style: resolvedStyles.positioner,
children: /* @__PURE__ */ jsxs(Tooltip.Popup, {
className: resolvedClassNames.popup,
style: resolvedStyles.popup,
children: [arrow && /* @__PURE__ */ jsx(Tooltip.Arrow, {
className: resolvedClassNames.arrow,
style: resolvedStyles.arrow,
children: TooltipArrowIcon
}), /* @__PURE__ */ jsx("div", {
className: resolvedClassNames.viewport,
style: resolvedStyles.viewport,
children: /* @__PURE__ */ jsx(TooltipContent_default, {
hotkey,
hotkeyProps,
title
})
})]
})
}), [
arrow,
baseSideOffset,
hotkey,
hotkeyProps,
placement,
placementConfig.align,
placementConfig.side,
resolvedClassNames,
resolvedStyles,
title
]);
if (title == null && !hotkey) return children;
const resolvedPortalContainer = customContainer ?? portalContainer;
return /* @__PURE__ */ jsxs(Tooltip.Root, {
defaultOpen,
disabled,
onOpenChange: handleOpenChange,
open: resolvedOpen,
children: [triggerElement, portalled ? resolvedPortalContainer ? /* @__PURE__ */ jsx(Tooltip.Portal, {
container: resolvedPortalContainer,
children: popup
}) : null : popup]
});
});
TooltipStandalone.displayName = "TooltipStandalone";
//#endregion
export { TooltipStandalone };
//# sourceMappingURL=TooltipStandalone.mjs.map