@mskcc/carbon-react
Version:
Carbon react components for the MSKCC DSM
171 lines (167 loc) • 5.69 kB
JavaScript
/**
* 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 };