UNPKG

@mskcc/carbon-react

Version:

Carbon react components for the MSKCC DSM

171 lines (167 loc) 5.69 kB
/** * MSKCC 2021, 2024 */ import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js'; import cx from 'classnames'; import PropTypes from 'prop-types'; import React__default, { useRef, useEffect } from 'react'; import { Popover, PopoverContent } from '../Popover/index.js'; import { useDelayedState } from '../../internal/useDelayedState.js'; import { useId } from '../../internal/useId.js'; import { useNoInteractiveChildren, getInteractiveContent } from '../../internal/useNoInteractiveChildren.js'; import { usePrefix } from '../../internal/usePrefix.js'; import { match } from '../../internal/keyboard/match.js'; import { Escape, Enter, Space } from '../../internal/keyboard/keys.js'; function Tooltip(_ref) { let { align = 'top', className: customClassName, children, label, description, enterDelayMs = 100, leaveDelayMs = 300, defaultOpen = false, closeOnActivation = false, disabled = false, ...rest } = _ref; const containerRef = useRef(null); const tooltipRef = useRef(null); const [open, setOpen] = useDelayedState(defaultOpen); const id = useId('tooltip'); const prefix = usePrefix(); const child = React__default.Children.only(children); const triggerProps = disabled ? {} : { onFocus: () => setOpen(true), onBlur: () => setOpen(false), onClick: () => closeOnActivation && setOpen(false), onMouseEnter: onMouseEnter }; function getChildEventHandlers(childProps) { if (disabled) { return {}; } const eventHandlerFunctions = ['onFocus', 'onBlur', 'onClick', 'onMouseEnter']; const eventHandlers = {}; eventHandlerFunctions.forEach(functionName => { eventHandlers[functionName] = evt => { triggerProps[functionName](); if (childProps?.[functionName]) { childProps?.[functionName](evt); } }; }); return eventHandlers; } if (label) { triggerProps['aria-labelledby'] = id; } else { triggerProps['aria-describedby'] = id; } function onKeyDown(event) { if (disabled) { return; } if (open && match(event, Escape)) { event.stopPropagation(); setOpen(false); } if (open && closeOnActivation && (match(event, Enter) || match(event, Space))) { setOpen(false); } } function onMouseEnter() { if (!disabled) { setOpen(true, enterDelayMs); } } function onMouseLeave() { if (!disabled) { setOpen(false, leaveDelayMs); } } useNoInteractiveChildren(tooltipRef, 'The Tooltip component must have no interactive content rendered by the' + '`label` or `description` prop'); useEffect(() => { if (containerRef !== null && containerRef.current) { const interactiveContent = getInteractiveContent(containerRef.current); if (!interactiveContent) { setOpen(false); } } }); return /*#__PURE__*/React__default.createElement(Popover, _extends({}, rest, { align: align, className: cx(`${prefix}--tooltip`, customClassName), dropShadow: false, highContrast: true, onKeyDown: disabled ? undefined : onKeyDown, onMouseLeave: disabled ? undefined : onMouseLeave, open: disabled ? false : open, ref: containerRef }), /*#__PURE__*/React__default.createElement("div", { className: `${prefix}--tooltip-trigger__wrapper` }, child !== undefined ? /*#__PURE__*/React__default.cloneElement(child, { ...triggerProps, ...getChildEventHandlers(child.props) }) : null), /*#__PURE__*/React__default.createElement(PopoverContent, { "aria-hidden": "true", className: `${prefix}--tooltip-content`, id: id, ref: tooltipRef, role: "tooltip" }, label || description)); } Tooltip.propTypes = { /** * Specify how the trigger should align with the tooltip */ align: PropTypes.oneOf(['top', 'top-left', 'top-right', 'bottom', 'bottom-left', 'bottom-right', 'left', 'left-bottom', 'left-top', 'right', 'right-bottom', 'right-top']), /** * Pass in the child to which the tooltip will be applied */ children: PropTypes.node, /** * Specify an optional className to be applied to the container node */ className: PropTypes.string, /** * Determines wether the tooltip should close when inner content is activated (click, Enter or Space) */ closeOnActivation: PropTypes.bool, /** * Specify whether the tooltip should be open when it first renders */ defaultOpen: PropTypes.bool, /** * Provide the description to be rendered inside of the Tooltip. The * description will use `aria-describedby` and will describe the child node * in addition to the text rendered inside of the child. This means that if you * have text in the child node, that it will be announced alongside the * description to the screen reader. * * Note: if label and description are both provided, label will be used and * description will not be used */ description: PropTypes.node, disabled: PropTypes.bool, /** * Specify the duration in milliseconds to delay before displaying the tooltip */ enterDelayMs: PropTypes.number, /** * Provide the label to be rendered inside of the Tooltip. The label will use * `aria-labelledby` and will fully describe the child node that is provided. * This means that if you have text in the child node, that it will not be * announced to the screen reader. * * Note: if label and description are both provided, description will not be * used */ label: PropTypes.node, /** * Specify the duration in milliseconds to delay before hiding the tooltip */ leaveDelayMs: PropTypes.number }; export { Tooltip };